Repository: johnlinvc/erruby Branch: develop Commit: 60df66495a01 Files: 77 Total size: 63.1 KB Directory structure: gitextract_mz0uvchy/ ├── .gitignore ├── .gitmodules ├── .travis.yml ├── COPYING.txt ├── Gemfile ├── 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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .eunit deps *.o *.beam *.plt .*-* erl_crash.dump ebin rel/example_project .concrete/DEV_MODE .rebar rb_test/*.out ================================================ FILE: .gitmodules ================================================ [submodule "ext/ruby/spec"] path = ext/ruby/spec url = https://github.com/ruby/spec.git [submodule "ext/ruby/mspec"] path = ext/ruby/mspec url = https://github.com/ruby/mspec.git ================================================ FILE: .travis.yml ================================================ language: erlang otp_release: - 18.0 - 18.1 before_script: - rvm install 2.3.1 - rvm use 2.3.1 - bundle install script: - rebar compile && ./test.rb ================================================ FILE: COPYING.txt ================================================ The MIT License (MIT) Copyright (c) 2015 Yu Hsiang Lin. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Gemfile ================================================ ruby "2.3.1" source "https://rubygems.org" gem 'erlport-ast_mapping', github: "johnlinvc/erlport-ast_mapping", branch: "ruby_2_3" gem 'guard' gem 'guard-shell' gem 'guard-rebar', github: "johnlinvc/guard-rebar", branch: "feature/update_to_guard_2_13" ================================================ FILE: Guardfile ================================================ # A sample Guardfile # More info at https://github.com/guard/guard#readme ## Uncomment and set this to only include directories you want to watch # directories %w(app lib config test spec features) \ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} ## Note: if you are using the `directories` clause above and you are not ## watching the project directory ('.'), then you will want to move ## the Guardfile to a watched dir and symlink it back, e.g. # # $ mkdir config # $ mv Guardfile config/ # $ ln -s config/Guardfile . # # and, you'll have to watch "config/Guardfile" instead of "Guardfile" guard 'rebar-compile', all_on_start: true do watch(%r{src/.*\.erl}) watch(%r{test/.*\.erl}) end guard :shell do watch(/rb_test\/(.*)\.rb/) {|m| `./test.rb -v #{m[0]}` } end ================================================ FILE: README.md ================================================ # ErRuby - an implementation of the Ruby language on Erlang [![Build Status](https://travis-ci.org/johnlinvc/erruby.svg?branch=develop)](https://travis-ci.org/johnlinvc/erruby) ## About ErRuby is an implementation of the Ruby language using Erlang. It aims to bring some concurrency features to ruby by experimenting. It's still a work in progress. So use it at your own risk. ## Install ### Prerequisites - erlang vm - rebar2 - ruby (2.3.1) To install erlang & rebar on OS X, using homebrew brew install erlang rebar ### Building After getting the source of ErRuby, get the gems for parser with bundler using: bundle install Then get the deps of erlang modules by using: rebar get-deps Last, compile ErRuby with: rebar compile Test the build result with: ./test.rb It should output `everything pass` ## Goals - Concurrent Features. - Run mspec. - GC. - Friendly installation with rvm/rbenv ## Supported features Currently it support some of the basic ruby constructs. Supported features: - `method` definition & calling. - singleton methods, class methods. - `class` and inheritance. - `block` and `yield`. - Constants. - Local variables. - Instance variables. - `load` & `require_relative`. - `Boolean` & `Integer` with basic methods. - `String` literal. - `Array` literal. Unsupported core features - class initializer, class instance variables. - `module` definition, `include`, `extend`. - variadic argument in function. - keyword argument in function. - GC. ### Class & inherentance ```ruby class Foo def to_s "foo" end end class Bar < Foo end class Alice < Bar def to_s "i'm alice" end def self.name "Alice" end end puts Foo.new.to_s # "foo" puts Bar.new.to_s # "foo" puts Alice.new.to_s # "i'm alice" puts Alice.name # "Alice" ``` ### block ```ruby def yield_with_arg(s,x) yield s,x end yield_with_arg("yield with","arg") do |ss, xx| puts ss # "yield with" puts xx # "arg" end 3.times do |i| puts i.to_s 4.times do |j| puts j.to_s end end ([1,2,3]*1000).pmap do |x| x+1 end ``` ## License ErRuby is licensed to you under MIT license. See the [COPYING.txt](COPYING.txt) file for more details. ================================================ FILE: TODO.md ================================================ restructure to standard erlang style design the object system of ruby in erlang - everything is object, just like ruby - every object is a process. message invoke with message passing using the actor model. use otp to run the code ================================================ FILE: erruby ================================================ #!/bin/bash pushd `dirname $0` > /dev/null export ERRUBY_PATH=`pwd` popd > /dev/null if [[ $* == *"-f"* ]] then escript "${ERRUBY_PATH}/ebin/erruby.beam" $* else erl -noshell +P 134217727 -pa ebin -s erruby main "{$*}" -s init stop fi ================================================ FILE: erruby_test/pipe_dot.rb ================================================ def cal 1 + 1 end x = self|.cal puts(x.to_s) puts(x.to_s) puts(1|.to_s) 1|.to_s def str "hello future" end puts(self|.str) ================================================ FILE: erruby_test/undefined_method.rb ================================================ class Foo end f = Foo.new f.bar ================================================ FILE: pre_compile ================================================ #!/bin/bash pushd ./deps/erlport > /dev/null make > /dev/null popd > /dev/null pushd ./deps/plists > /dev/null ./make.sh popd > /dev/null ================================================ FILE: rb_src/erruby.rb ================================================ #!/usr/bin/env ruby require 'rubygems' require 'bundler/setup' require 'erlport/ast_mapping' def parse(src) ErlPort::AstMapping.parse(src) end def install_encoder ErlPort::AstMapping.install_encoder end if __FILE__ == $0 p parse("[1,2,3]".each_char.map(&:ord)) end ================================================ FILE: rb_test/_load.rb ================================================ puts "loaded" ================================================ FILE: rb_test/_require_relative.rb ================================================ puts "required file" ================================================ FILE: rb_test/and_dot.rb ================================================ puts(1&.to_s) nil&.not_exist_method ================================================ FILE: rb_test/array_class.rb ================================================ [true, false] [nil, nil] ["a","b","c"] [1,2].map{|x| puts(x.to_s)} puts "mul" ([1,2,3] * 3).map{|x| puts x.to_s} #a = 0 #[1,2,3].map do |x| #a = a + x #end #puts a puts ["a","b","c"].at(1) puts ["a","b","c"].first puts ["a","b","c"].last puts [false].empty?.to_s puts [].empty?.to_s puts [1, 2, 3].length.to_s puts [1, 2, 3].size.to_s ary = ["a","b"] ary.concat ["c", "d"] puts ary.last ary2 = ary + ["e", "f"] puts ary2.last ary = ["a","b","c"] ary.push "d" puts ary.last ary << "e" << "f" puts ary.last array = ["b", "c"] array.unshift("a") puts array.first puts array.shift puts array.first puts [1, 2, 3, 4, 5].drop(1).length.to_s puts [1, 2, 3, 4, 5].drop(3).length.to_s puts [1, 2, 3, 4, 5].drop(0).length.to_s puts [1, 2, 3, 4, 5].drop(5).length.to_s puts [1, 2, 3, 4, 5].drop(10).length.to_s ================================================ FILE: rb_test/boolean_class.rb ================================================ puts (true).to_s puts (false).to_s puts (!true).to_s puts (!false).to_s puts (true == false).to_s puts (true == true).to_s puts (false == true).to_s puts (false == false).to_s puts (true & true).to_s puts (true & false).to_s puts (false & true).to_s puts (false & false).to_s puts (true ^ true).to_s puts (true ^ false).to_s puts (false ^ true).to_s puts (false ^ false).to_s puts (true | true).to_s puts (true | false).to_s puts (false | true).to_s puts (false | false).to_s puts true.to_s puts false.to_s puts true.inspect puts false.inspect ================================================ FILE: rb_test/class_cross_call.rb ================================================ class Foo def msg "hello" end end class Bar def say puts Foo.new.msg end end Bar.new.say ================================================ FILE: rb_test/class_def.rb ================================================ class Foo def message "hello world" end def hello puts message end end class Bar def message "hello bar" end def hello puts message end end f = Foo.new f.hello ff = Foo.new Bar.new.hello ================================================ FILE: rb_test/class_inherent.rb ================================================ class Foo def to_s "foo" end end class Bar < Foo end class Alice < Bar def to_s "i'm alice" end end puts Foo.new.to_s # "foo" puts Bar.new.to_s # "foo" puts Alice.new.to_s # "i'm alice" ================================================ FILE: rb_test/class_method.rb ================================================ class Foo def self.bar puts "bar" end end Foo.bar ================================================ FILE: rb_test/const_def.rb ================================================ HELLO_CONST = "hello const" puts HELLO_CONST ================================================ FILE: rb_test/file.rb ================================================ puts __FILE__ puts File.expand_path('../../../erruby/rb_test', __FILE__) ================================================ FILE: rb_test/fixnum.rb ================================================ puts 1.to_s puts 2.to_s puts -1.to_s puts 0.to_s puts 576460752303423488.to_s puts -576460752303423489.to_s puts (1+1).to_s puts (2-1).to_s puts (-(1)).to_s puts (3 % 2).to_s puts (4 * 2).to_s puts (3 / 2).to_s puts (4 ** 2).to_s puts (1 < 2).to_s puts (2 < 2).to_s puts (3 < 2).to_s puts (1 <= 2).to_s puts (2 <= 2).to_s puts (3 <= 2).to_s puts (1 > 2).to_s puts (2 > 2).to_s puts (3 > 2).to_s puts (1 >= 2).to_s puts (2 >= 2).to_s puts (3 >= 2).to_s puts (1 == 2).to_s puts (2 == 2).to_s puts (3 == 2).to_s puts (1 <=> 2).to_s puts (2 <=> 2).to_s puts (3 <=> 2).to_s ================================================ FILE: rb_test/global_var.rb ================================================ $Gvar = "hello gvar" puts $Gvar ================================================ FILE: rb_test/hello_world.rb ================================================ puts "hello world" ================================================ FILE: rb_test/integer_class.rb ================================================ puts 5.to_i.to_s puts 5.to_int.to_s puts 5.floor.to_s puts 5.ceil.to_s puts 5.truncate.to_s puts 5.numerator.to_s puts 5.ord.to_s puts 56.denominator.to_s puts 2.even?.to_s puts 3.even?.to_s puts 2.odd?.to_s puts 3.odd?.to_s puts 2.gcd(2).to_s puts 6.gcd(8).to_s puts 8.gcd(6).to_s puts 0.gcd(8).to_s puts 0.gcd(0).to_s puts 6.gcd(0).to_s puts 2.lcm(2).to_s puts 6.lcm(8).to_s puts 8.lcm(6).to_s puts 0.lcm(8).to_s puts 6.lcm(0).to_s puts 1.integer?.to_s puts 1.next.to_s puts (-1).next.to_s puts 1.succ.to_s puts (-1).succ.to_s puts "times" puts 5.times {|i| puts i.to_s}.to_s puts 0.times {|i| puts i.to_s}.to_s puts (-5).times {|i| puts i.to_s}.to_s puts "upto" puts (-2).upto(2){|i| puts i.to_s}.to_s puts (2).upto(2){|i| puts i.to_s}.to_s puts (3).upto(2){|i| puts i.to_s}.to_s puts "downto" puts (2).downto(-2){|i| puts i.to_s}.to_s puts (2).downto(2){|i| puts i.to_s}.to_s puts (5).downto(6){|i| puts i.to_s}.to_s puts "abs" puts (2).abs.to_s puts (-2).abs.to_s puts "magnitude" puts (2).magnitude.to_s puts (-2).magnitude.to_s ================================================ FILE: rb_test/ivar.rb ================================================ class Foo def setup @ivar = "hello" end def ivar @ivar end end foo = Foo.new foo.setup puts foo.ivar ================================================ FILE: rb_test/load.rb ================================================ load 'rb_test/_load.rb' load 'rb_test/_load.rb' ================================================ FILE: rb_test/method_def.rb ================================================ str = "not this" def hello_world_method_0 "hello world no arg" end def hello_world_method_1(name) str = name str end def hello_world_method_block(n) puts n puts yield end def yield_with_arg(s,x) yield s,x end yield_with_arg("yield with","arg") do |ss, xx| puts ss # "yield with" puts xx # "arg" end puts hello_world_method_0 puts hello_world_method_1("my name is erruby") hello_world_method_block("arg") { "hello block" } #hello_world_method_block(4){"hello"}.block1{"this"}.block2{"world"} ================================================ FILE: rb_test/nested_block.rb ================================================ 3.times do |i| puts i.to_s 4.times do |j| puts j.to_s end end ================================================ FILE: rb_test/nested_const.rb ================================================ class Foo Bar = "hello nested" class Alice end end Bob = "outside bob" puts "Foo::Bar" puts Foo::Bar Foo::Alice::Bob = "inside Bob" puts Foo::Alice::Bob puts Bob ================================================ FILE: rb_test/nil_class.rb ================================================ nil nil & nil nil & true nil & false nil & "" nil ^ nil nil ^ true nil ^ false nil ^ "" nil | nil nil | true nil | false nil | "" nil.inspect nil.nil? nil.to_s ================================================ FILE: rb_test/require_relative.rb ================================================ require_relative "_require_relative" require_relative "_require_relative" ================================================ FILE: rb_test/sysrb_out/.gitkeep ================================================ ================================================ FILE: rb_test/sysrb_out/and_dot.out ================================================ 1 ================================================ FILE: rb_test/sysrb_out/array_class.out ================================================ 1 2 mul 1 2 3 1 2 3 1 2 3 b a c false true 3 3 d f d f a a b 4 2 5 0 0 ================================================ FILE: rb_test/sysrb_out/boolean_class.out ================================================ true false false true false true false true true false false false false true true false true true true false true false true false ================================================ FILE: rb_test/sysrb_out/class_cross_call.out ================================================ hello ================================================ FILE: rb_test/sysrb_out/class_def.out ================================================ hello world hello bar ================================================ FILE: rb_test/sysrb_out/class_inherent.out ================================================ foo foo i'm alice ================================================ FILE: rb_test/sysrb_out/class_method.out ================================================ bar ================================================ FILE: rb_test/sysrb_out/const_def.out ================================================ hello const ================================================ FILE: rb_test/sysrb_out/file.out ================================================ rb_test/file.rb /Users/johnlinvc/Projs/erruby/erruby/rb_test ================================================ FILE: rb_test/sysrb_out/fixnum.out ================================================ 1 2 -1 0 576460752303423488 -576460752303423489 2 1 -1 1 8 1 16 true false false true true false false false true false true true false true false -1 0 1 ================================================ FILE: rb_test/sysrb_out/global_var.out ================================================ hello gvar ================================================ FILE: rb_test/sysrb_out/hello_world.out ================================================ hello world ================================================ FILE: rb_test/sysrb_out/integer_class.out ================================================ 5 5 5 5 5 5 5 1 true false false true 2 2 2 8 0 6 2 24 24 0 0 true 2 0 2 0 times 0 1 2 3 4 5 0 -5 upto -2 -1 0 1 2 -2 2 2 3 downto 2 1 0 -1 -2 2 2 2 5 abs 2 2 magnitude 2 2 ================================================ FILE: rb_test/sysrb_out/ivar.out ================================================ hello ================================================ FILE: rb_test/sysrb_out/load.out ================================================ loaded loaded ================================================ FILE: rb_test/sysrb_out/method_def.out ================================================ yield with arg hello world no arg my name is erruby arg hello block ================================================ FILE: rb_test/sysrb_out/nested_block.out ================================================ 0 0 1 2 3 1 0 1 2 3 2 0 1 2 3 ================================================ FILE: rb_test/sysrb_out/nested_const.out ================================================ Foo::Bar hello nested inside Bob outside bob ================================================ FILE: rb_test/sysrb_out/nil_class.out ================================================ ================================================ FILE: rb_test/sysrb_out/require_relative.out ================================================ required file ================================================ FILE: rb_test/sysrb_out/var_assign.out ================================================ hello world ================================================ FILE: rb_test/var_assign.rb ================================================ hello = "hello world" puts hello ================================================ FILE: rebar.config ================================================ {deps, [ {erlport, ".*", {git, "https://github.com/johnlinvc/erlport.git", {branch, "erruby"}}, [raw]}, {plists, ".*", {git, "https://github.com/johnlinvc/plists.git", {branch, "master"}}, [raw]}, {getopt, "0.8.2", {git, "https://github.com/jcomellas/getopt.git", {tag, "v0.8.2"}}} ] }. {pre_hooks, [{compile, "./pre_compile"}]}. ================================================ FILE: src/erb.erl ================================================ -module(erb). -export([find_or_init_class/2]). find_or_init_class(Name, InitFun) -> case whereis(Name) of undefined -> InitFun(); Pid -> {ok, Pid} end. ================================================ FILE: src/erruby.app.src ================================================ {application, erruby, [ {description, ""}, {vsn, "1"}, {registered, []}, {applications, [ kernel, stdlib ]}, {mod, { erruby_app, []}}, {env, []} ]}. ================================================ FILE: src/erruby.erl ================================================ -module(erruby). -include("rb.hrl"). -export([eruby/1, start_ruby/0, stop_ruby/1, parse_ast/2, main/1]). opt_spec_list() -> [ {debug, $d, "debug", {integer, 0}, "Verbose level for debugging"}, {verbose, $v, "verbose", undefined, "print version number and enter verbose mode"}, {fast, $f, "fast", undefined, "use escript instead of erl for faster bootup"}, {help, $h, "help", undefined, "Show this help"} ]. handle_opts({debug, DebugLevel}) -> erruby_debug:set_debug_level(DebugLevel); handle_opts(help ) -> show_help(); handle_opts(verbose) -> io:format("erruby 0.1.0~n"), erruby_debug:set_debug_level(2); handle_opts(_Opts) -> ok. parse_args([ArgsAtom]) when is_atom(ArgsAtom) -> ArgsString = atom_to_list(ArgsAtom), ArgsUntokened = string:centre(ArgsString,length(ArgsString)-2), string:tokens(ArgsUntokened, " "); parse_args(Args) -> Args. main(RawArgs) -> Args = parse_args(RawArgs), add_lib_path(), erruby_debug:start_link(0), {ok, {Opts, Extra}} = getopt(Args), lists:foreach(fun handle_opts/1, Opts), [SrcFileName | RubyArgs] = Extra, try erruby_debug:debug_2("input file name ~s\n", [SrcFileName]), erruby_debug:debug_2("input args ~s\n", [RubyArgs]), eruby(SrcFileName) catch _:known_error -> erruby_debug:debug_1("known error ~n", []), erruby_debug:debug_1("~p~n",[erlang:get_stacktrace()]); _:E -> io:format("error ~p ~n", [E]), erlang:display(erlang:get_stacktrace()) end. eruby(SrcFileName) -> {ok, Binary} = file:read_file(SrcFileName), FileLines = binary:bin_to_list(Binary), Ruby = start_ruby(), Ast = parse_ast(Ruby, FileLines), stop_ruby(Ruby), erruby_vm:eval_file(Ast, SrcFileName). getopt(Args) -> getopt:parse(opt_spec_list(), Args). show_help() -> getopt:usage(opt_spec_list(), "erruby", "[programfile] [arguments]"), halt(1). install_encoder(Ruby) -> ruby:call(Ruby, erruby_rb_path() , 'install_encoder',[]). erruby_path() -> os:getenv("ERRUBY_PATH") ++ "/ebin". relative_path(Path) -> erruby_path() ++ Path. erruby_rb_path() -> list_to_atom(relative_path("/../rb_src/erruby.rb")). parse_ast(Ruby, String) -> ruby:call(Ruby, erruby_rb_path(),'parse', [String]). add_lib_path() -> code:add_path(relative_path("/../deps/erlport/ebin")), code:add_path(relative_path("/../deps/getopt/ebin")), code:add_path(relative_path("/../deps/plists/ebin")), code:add_path(erruby_path()). stop_ruby(Ruby) -> ruby:stop(Ruby). start_ruby() -> {ok, Ruby} = ruby:start(), install_encoder(Ruby), Ruby. ================================================ FILE: src/erruby_app.erl ================================================ -module(erruby_app). -behaviour(application). %% Application callbacks -export([start/2, stop/1]). %% =================================================================== %% Application callbacks %% =================================================================== start(_StartType, _StartArgs) -> erruby_sup:start_link(). stop(_State) -> ok. ================================================ FILE: src/erruby_array.erl ================================================ -module(erruby_array). -include("rb.hrl"). -export([install_array_classes/0, new_array/2, new_array/1]). -export([array_to_list/1, push/2]). %TODO find a way to define module_function install_array_classes() -> {ok, ArrayClass} = erruby_class:new_class(), 'Array' = erruby_object:def_global_const('Array', ArrayClass), erruby_object:def_method(ArrayClass, map, fun method_map/1), erruby_object:def_method(ArrayClass, pmap, fun method_pmap/1), erruby_object:def_method(ArrayClass, '*' , fun method_multiplication/2), erruby_object:def_method(ArrayClass, '+', fun method_plus/2), erruby_object:def_method(ArrayClass, 'concat', fun method_concat/2), erruby_object:def_method(ArrayClass, 'at' , fun method_at/2), erruby_object:def_method(ArrayClass, 'first' , fun method_first/1), erruby_object:def_method(ArrayClass, 'last' , fun method_last/1), erruby_object:def_method(ArrayClass, 'empty?', fun method_empty_q/1), erruby_object:def_method(ArrayClass, 'length', fun method_length/1), erruby_object:def_method(ArrayClass, 'size', fun method_length/1), erruby_object:def_method(ArrayClass, 'push' , fun method_push/2), erruby_object:def_method(ArrayClass, '<<' , fun method_push/2), erruby_object:def_method(ArrayClass, 'unshift' , fun method_unshift/2), erruby_object:def_method(ArrayClass, 'shift' , fun method_shift/1), erruby_object:def_method(ArrayClass, 'drop' , fun method_drop/2), ok. drop_elements(_List, Count) when Count =< 0 -> _List; drop_elements([Head | Tail], Count) -> drop_elements(Tail, Count - 1). method_drop(#{self := Self}=Env, IntObj) -> Int = erruby_fixnum:fix_to_int(IntObj), List = array_to_list(Self), if length(List) < Int -> new_array(Env, []); true -> ResultList = drop_elements(List, Int), new_array(Env, ResultList) end. method_map(#{self := Self}=Env) -> List = array_to_list(Self), FoldFun = fun(X, EnvAcc) -> erruby_vm:yield(EnvAcc, [X]) end, Envs = erruby_vm:scanl(FoldFun, Env, List), Results = lists:map(fun erruby_rb:ret_val/1, Envs), erruby_rb:return(Results, lists:last(Envs)). repeat_list(_List, Count) when Count =< 0 -> new_array([]); repeat_list(List, 1) -> List; repeat_list(List, Count) -> lists:append(List, repeat_list(List, Count-1)). method_multiplication(#{self := Self}=Env, IntObj) -> Int = erruby_fixnum:fix_to_int(IntObj), List = array_to_list(Self), ResultList = repeat_list(List, Int), new_array(Env, ResultList). method_plus(#{self := Self}=Env, Another) -> Elements = array_to_list(Self), AnotherElements = array_to_list(Another), NewElements = Elements ++ AnotherElements, new_array(Env, NewElements). method_concat(#{self := Self}=Env, Another) -> Elements = array_to_list(Self), AnotherElements = array_to_list(Another), NewElements = Elements ++ AnotherElements, Properties = erruby_object:get_properties(Self), NewProperties = Properties#{ elements := NewElements}, erruby_object:set_properties(Self, NewProperties), erruby_rb:return(Self, Env). method_pmap(#{self := Self}=Env) -> List = array_to_list(Self), MapFun = fun(X) -> erruby_vm:yield(Env, [X]) end, Envs = plists:map(MapFun, List, {processes, erlang:system_info(schedulers_online)}), Results = lists:map(fun erruby_rb:ret_val/1, Envs), erruby_rb:return(Results, lists:last(Envs)). method_at(#{self := Self}=Env, IntObj) -> Int = erruby_fixnum:fix_to_int(IntObj), erruby_rb:return(at(Self, Int), Env). method_first(#{self := Self}=Env) -> List = array_to_list(Self), [ Head | _Tail ] = List, erruby_rb:return(Head, Env). method_last(#{self := Self}=Env) -> List = array_to_list(Self), erruby_rb:return(lists:last(List), Env). method_empty_q(#{self := Self}=Env) -> List = array_to_list(Self), case length(List) < 1 of true -> erruby_boolean:new_true(Env); false -> erruby_boolean:new_false(Env) end. method_length(#{self := Self}=Env) -> List = array_to_list(Self), erruby_fixnum:new_fixnum(Env, length(List)). method_push(#{self := Self}=Env, Append) -> push(Self, Append), erruby_rb:return(Self, Env). method_unshift(#{self := Self}=Env, Head) -> Elements = array_to_list(Self), NewElements = [Head | Elements], Properties = erruby_object:get_properties(Self), NewProperties = Properties#{ elements := NewElements} , erruby_object:set_properties(Self, NewProperties), erruby_rb:return(Self, Env). method_shift(#{self := Self}=Env) -> Elements = array_to_list(Self), [Head | Rest] = Elements, Properties = erruby_object:get_properties(Self), NewProperties = Properties#{ elements := Rest} , erruby_object:set_properties(Self, NewProperties), erruby_rb:return(Head, Env). %TODO maybe use pid to find class new_array(Env, Elements) -> erruby_rb:return(new_array(Elements), Env). new_array(Elements) -> ArrayClass = erruby_object:find_global_const('Array'), Properties = #{elements => Elements}, {ok, Array} = erruby_object:new_object(ArrayClass, Properties), Array. %% @doc the Index is 0-based, not the 1-based of usual erlang at(Array, Index) -> Properties = erruby_object:get_properties(Array), #{ elements := Elements} = Properties, lists:nth(Index+1, Elements). push(Array, Elem) -> Elements = array_to_list(Array), NewElements = Elements ++ [Elem], Properties = erruby_object:get_properties(Array), NewProperties = Properties#{ elements := NewElements} , erruby_object:set_properties(Array, NewProperties). array_to_list(Array) -> Properties = erruby_object:get_properties(Array), #{ elements := Elements} = Properties, Elements. ================================================ FILE: src/erruby_boolean.erl ================================================ -module(erruby_boolean). -export([install_boolean_classes/0,new_true/1,new_false/1,true_instance/0,false_instance/0]). %TODO register the True & False class in Const install_boolean_classes() -> {ok, TrueClass} = erruby_class:new_class(), {ok, FalseClass} = erruby_class:new_class(), 'TrueClass' = erruby_object:def_global_const('TrueClass', TrueClass), 'FalseClass' = erruby_object:def_global_const('FalseClass', FalseClass), install_method(TrueClass, FalseClass, '!', fun method_not/1), install_method(TrueClass, FalseClass, '&', fun method_and/2), install_method(TrueClass, FalseClass, '^', fun method_xor/2), install_method(TrueClass, FalseClass, '|', fun method_or/2), erruby_object:def_method(TrueClass, to_s, fun method_true_to_s/1), erruby_object:def_method(FalseClass, to_s, fun method_false_to_s/1), erruby_object:def_method(TrueClass, inspect, fun method_true_to_s/1), erruby_object:def_method(FalseClass, inspect, fun method_false_to_s/1), erruby_object:new_object_with_pid_symbol(erruby_boolean_true, TrueClass), erruby_object:new_object_with_pid_symbol(erruby_boolean_false, FalseClass), ok. install_method(TC, FC, Name, Func) -> erruby_object:def_method(TC, Name, Func), erruby_object:def_method(FC, Name, Func). new_true(Env) -> erruby_rb:return(true_instance(), Env). new_false(Env) -> erruby_rb:return(false_instance(), Env). true_instance() -> whereis(erruby_boolean_true). false_instance() -> whereis(erruby_boolean_false). method_not(#{self := Self} = Env) -> True = true_instance(), False = false_instance(), RetVal = case Self of True -> False; False -> True end, erruby_rb:return(RetVal, Env). method_and(#{self := Self} = Env, Object) -> Another = object_to_boolean(Object), RetVal = and_op(Self, Another), erruby_rb:return(RetVal, Env). object_to_boolean(Object) -> NilObject = erruby_nil:nil_instance(), False = false_instance(), case Object of NilObject -> false_instance(); False -> false_instance(); _ -> true_instance() end. and_op(B1,B2) -> True = true_instance(), False = false_instance(), case B1 of True -> B2; False -> False end. or_op(B1,B2) -> True = true_instance(), False = false_instance(), case B1 of True -> True; False -> B2 end. not_op(Boolean) -> True = true_instance(), False = false_instance(), case Boolean of True -> False; False -> True end. method_or(#{self := Self} = Env, Object) -> Another = object_to_boolean(Object), RetVal = or_op(Self, Another), erruby_rb:return(RetVal, Env). method_xor(#{self := Self} = Env, Object) -> Another = object_to_boolean(Object), NotAandB = and_op(not_op(Self),Another), AandNotB = and_op(Self, not_op(Another)), RetVal = or_op(NotAandB, AandNotB), erruby_rb:return(RetVal, Env). method_true_to_s(Env) -> erruby_vm:new_string("true", Env). method_false_to_s(Env) -> erruby_vm:new_string("false",Env). ================================================ FILE: src/erruby_class.erl ================================================ -module(erruby_class). -export([new_class/0, new_named_class/1, new_class/1, install_class_class_methods/0, init_class_class/0, class_name/1]). %TODO return self when calling method_class on self %TODO add name parameter %TODO should call initialize method when new new_class() -> new_named_class(undefined). new_named_class(Name) -> Properties = #{name => Name}, erruby_object:start_link(class_class(), Properties). class_name(Self) -> Properties = erruby_object:get_properties(Self), #{name := Name} = Properties, Name. new_class(SuperClass) -> Properties = #{superclass => SuperClass}, erruby_object:start_link(class_class(), Properties). install_class_class_methods() -> erruby_object:def_method(class_class(), 'new', fun method_new/1), ok. %FIXME new a real class method_new(#{self := Klass}=Env) -> {ok, NewObject} = erruby_object:start_link(Klass), erruby_rb:return(NewObject, Env). init_class_class() -> erb:find_or_init_class(erruby_class_class, fun init_class_class_internal/0). init_class_class_internal() -> Properties = #{superclass => erruby_object:object_class()}, {ok, Pid} = erruby_object:new_object_with_pid_symbol(erruby_class_class, erruby_object:object_class()), ok = install_class_class_methods(), {ok, Pid}. class_class() -> whereis(erruby_class_class). ================================================ FILE: src/erruby_debug.erl ================================================ -module(erruby_debug). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -export([start_link/1, debug/3, debug_1/2,debug_2/2, debug_tmp/2, set_debug_level/1]). -export([print_env/1]). init([DebugLevel]) -> {ok, #{debug_level => DebugLevel}}. terminate(_Arg, _State) -> {ok, dead}. code_change(_OldVsn, State, _Extra) -> {ok, State}. debug(Format, Args, Level) -> FullFormat = lists:concat(["debug level ", Level, ": ", Format]), gen_server:call(erruby_debug_pid, #{level => Level, format => FullFormat, args => Args}). debug_1(Format, Args) -> debug(Format, Args, 1). debug_2(Format, Args) -> debug(Format, Args, 2). debug_tmp(Format, Args) -> io:format(Format, Args). set_debug_level(Level) -> gen_server:cast(erruby_debug_pid, #{new_level => Level}). start_link(DebugLevel) -> gen_server:start_link({local, erruby_debug_pid} ,?MODULE, [DebugLevel], []). handle_info(Info, State) -> io:format("Got unkwon info:~n~p~n", [Info]), {ok, State}. handle_call(#{level := Level, format := Format, args := Args}, _From, #{debug_level := DebugLevel} = State) when DebugLevel >= Level -> io:format(Format, Args), {reply, ok, State}; handle_call(#{level := Level}, _From, #{debug_level := DebugLevel} = State) when DebugLevel < Level -> {reply, ok, State}; handle_call(_Req, _From, State) -> io:format("handle unknow call ~p ~p ~p ~n",[_Req, _From, State]), NewState = State, {reply, done, NewState}. handle_cast(#{new_level := NewLevel}, State) -> {noreply, State#{ debug_level => NewLevel } }; handle_cast(_Req, State) -> io:format("handle unknown cast ~p ~p ~n",[_Req, State]), NewState = State, {noreply, NewState}. print_env(Env) -> debug_1("Env: ~p ~n",[Env]). ================================================ FILE: src/erruby_fixnum.erl ================================================ -module(erruby_fixnum). -export([install_fixnum_class/0, new_fixnum/2, fix_to_int/1]). %% %% @TODO inherent from integer & numeric install_fixnum_class() -> IntegerClass = erruby_object:find_global_const('Integer'), {ok, FixnumClass} = erruby_class:new_class(IntegerClass), 'Fixnum' = erruby_object:def_global_const('Fixnum', FixnumClass), erruby_object:def_method(FixnumClass, to_s, fun method_to_s/1), erruby_object:def_method(FixnumClass, '-@', fun method_neg/1), erruby_object:def_method(FixnumClass, '+', fun method_add/2), erruby_object:def_method(FixnumClass, '-', fun method_minus/2), erruby_object:def_method(FixnumClass, '*', fun method_multiplication/2), erruby_object:def_method(FixnumClass, '**', fun method_power/2), erruby_object:def_method(FixnumClass, '<', fun method_less/2), erruby_object:def_method(FixnumClass, '>', fun method_greater/2), erruby_object:def_method(FixnumClass, '<=', fun method_less_equal/2), erruby_object:def_method(FixnumClass, '>=', fun method_greater_equal/2), erruby_object:def_method(FixnumClass, '==', fun method_equal/2), erruby_object:def_method(FixnumClass, '<=>', fun method_cmp/2), erruby_object:def_method(FixnumClass, '/', fun method_division/2), erruby_object:def_method(FixnumClass, '%', fun method_module/2), ok. fixnum_class() -> erruby_object:find_global_const('Fixnum'). new_fixnum(Env, N) -> {ok, Obj} = erruby_object:new_object(fixnum_class(), #{val => N}), erruby_rb:return(Obj, Env). fix_to_int(Fixnum) -> get_val(Fixnum). get_val(Fixnum) -> #{val := Val} = erruby_object:get_properties(Fixnum), Val. binary_op(#{self := Self}=Env, AnotherFixnum, Fun) -> Val = Fun(get_val(Self), get_val(AnotherFixnum)), new_fixnum(Env, Val). %% @TODO use new_string instead method_to_s(#{self := Self}=Env) -> Val = get_val(Self), erruby_rb:return(integer_to_list(Val), Env). %% TODO handle case where the other is not Fixnum method_add(Env, AnotherFixnum) -> binary_op(Env, AnotherFixnum, fun (A,B) -> A+B end). method_minus(Env, AnotherFixnum) -> binary_op(Env, AnotherFixnum, fun (A,B) -> A-B end). method_multiplication(Env, AnotherFixnum) -> binary_op(Env, AnotherFixnum, fun (A,B) -> A*B end). method_division(Env, AnotherFixnum) -> binary_op(Env, AnotherFixnum, fun (A,B) -> trunc(A/B) end). method_power(Env, AnotherFixnum) -> binary_op(Env, AnotherFixnum, fun (A,B) -> trunc(math:pow(A,B)) end). method_neg(#{self := Self}=Env) -> Val = - get_val(Self), new_fixnum(Env, Val). method_module(Env, AnotherFixnum) -> binary_op(Env, AnotherFixnum, fun (A,B) -> A rem B end). binary_cmp(#{self := Self}=Env, AnotherFixnum, Fun) -> Val = get_val(Self), AnotherVal = get_val(AnotherFixnum), case Fun(Val,AnotherVal) of true -> erruby_boolean:new_true(Env); false -> erruby_boolean:new_false(Env) end. %%TODO move these to the comparator module method_less(Env, AnotherFixnum) -> binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X < Y end). method_less_equal(Env, AnotherFixnum) -> binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X =< Y end). method_greater(Env, AnotherFixnum) -> binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X > Y end). method_greater_equal(Env, AnotherFixnum) -> binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X >= Y end). method_equal(Env, AnotherFixnum) -> binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X =:= Y end). cmp_helper(X,Y) when X < Y -> -1; cmp_helper(X,Y) when X =:= Y -> 0; cmp_helper(X,Y) when X > Y -> 1. method_cmp(Env, AnotherFixnum) -> binary_op(Env, AnotherFixnum, fun cmp_helper/2). ================================================ FILE: src/erruby_integer.erl ================================================ -module(erruby_integer). -export([install_integer_class/0]). install_integer_class() -> {ok, IntegerClass} = erruby_class:new_class(), 'Integer' = erruby_object:def_global_const('Integer', IntegerClass), erruby_object:def_method(IntegerClass, to_i, fun method_to_i/1), erruby_object:def_method(IntegerClass, to_int, fun method_to_i/1), erruby_object:def_method(IntegerClass, floor, fun method_to_i/1), erruby_object:def_method(IntegerClass, ceil, fun method_to_i/1), erruby_object:def_method(IntegerClass, truncate, fun method_to_i/1), erruby_object:def_method(IntegerClass, numerator, fun method_to_i/1), erruby_object:def_method(IntegerClass, ord, fun method_to_i/1), erruby_object:def_method(IntegerClass, denominator, fun method_denominator/1), erruby_object:def_method(IntegerClass, 'even?', fun method_even_q/1), erruby_object:def_method(IntegerClass, 'odd?', fun method_odd_q/1), erruby_object:def_method(IntegerClass, 'gcd', fun method_gcd/2), erruby_object:def_method(IntegerClass, 'lcm', fun method_lcm/2), erruby_object:def_method(IntegerClass, 'integer?', fun method_integer_q/1), erruby_object:def_method(IntegerClass, 'succ', fun method_succ/1), erruby_object:def_method(IntegerClass, 'next', fun method_succ/1), erruby_object:def_method(IntegerClass, 'times', fun method_times/1), erruby_object:def_method(IntegerClass, 'upto', fun method_upto/2), erruby_object:def_method(IntegerClass, 'downto', fun method_downto/2), erruby_object:def_method(IntegerClass, 'abs', fun method_abs/1), erruby_object:def_method(IntegerClass, 'magnitude', fun method_abs/1), ok. method_to_i(#{self := Self}=Env) -> erruby_rb:return(Self, Env). method_denominator(Env) -> erruby_fixnum:new_fixnum(Env, 1). %%TODO handle Bignum method_even_q(#{self := Self}=Env) -> Int = erruby_fixnum:fix_to_int(Self), case Int rem 2 of 0 -> erruby_boolean:new_true(Env); 1 -> erruby_boolean:new_false(Env) end. method_odd_q(#{self := Self}=Env) -> Int = erruby_fixnum:fix_to_int(Self), case Int rem 2 of 1 -> erruby_boolean:new_true(Env); 0 -> erruby_boolean:new_false(Env) end. %%TODO use binary gcd algo instead %%TODO move this to rational gcd(X,Y) when X < Y -> gcd(Y,X); gcd(X,Y) when Y =:= 0 -> X; gcd(X,Y) -> gcd(Y, X rem Y). method_gcd(#{self := Self}=Env, AnotherInt) -> X = abs(erruby_fixnum:fix_to_int(Self)), Y = abs(erruby_fixnum:fix_to_int(AnotherInt)), case min(X,Y) of 0 -> erruby_fixnum:new_fixnum(Env, max(X,Y)); _ -> erruby_fixnum:new_fixnum(Env, gcd(X,Y)) end. lcm(0,_Y) -> 0; lcm(_X,0) -> 0; lcm(X,Y) -> trunc(X / gcd(X,Y) * Y). method_lcm(#{self := Self}=Env, AnotherInt) -> X = abs(erruby_fixnum:fix_to_int(Self)), Y = abs(erruby_fixnum:fix_to_int(AnotherInt)), case min(X,Y) of 0 -> erruby_fixnum:new_fixnum(Env, 0); _ -> erruby_fixnum:new_fixnum(Env, lcm(X,Y)) end. method_integer_q(Env) -> erruby_boolean:new_true(Env). method_succ(#{self := Self}=Env) -> erruby_fixnum:new_fixnum(Env, erruby_fixnum:fix_to_int(Self)+1). yield_in_range(#{self := Self} = Env,Range) -> FoldFun = fun (X, EnvAcc) -> IntEnv = erruby_fixnum:new_fixnum(EnvAcc, X), FixInt = erruby_rb:ret_val(IntEnv), erruby_vm:yield(IntEnv, [FixInt]) end, LastEnv = lists:foldl(FoldFun, Env, Range), erruby_rb:return(Self, LastEnv). times_range(X) when X =< 0 -> []; times_range(X) -> lists:seq(0, X-1). %%TODO handle empty block case %%TODO handle Bigdecimal method_times(#{self := Self}=Env) -> Int = erruby_fixnum:fix_to_int(Self), Range = times_range(Int), yield_in_range(Env,Range). upto_range(Start,End) when Start > End -> []; upto_range(Start,End) -> lists:seq(Start,End). method_upto(#{self := Self}=Env, AnotherInteger) -> Int = erruby_fixnum:fix_to_int(Self), AnotherInt = erruby_fixnum:fix_to_int(AnotherInteger), Range = upto_range(Int,AnotherInt), yield_in_range(Env,Range). downto_range(Start,End) -> lists:reverse(upto_range(End,Start)). method_downto(#{self := Self}=Env, AnotherInteger) -> Int = erruby_fixnum:fix_to_int(Self), AnotherInt = erruby_fixnum:fix_to_int(AnotherInteger), Range = downto_range(Int,AnotherInt), yield_in_range(Env,Range). method_abs(#{self := Self}=Env) -> erruby_fixnum:new_fixnum(Env, abs(erruby_fixnum:fix_to_int(Self))). ================================================ FILE: src/erruby_lib/erruby_file.erl ================================================ -module(erruby_file). -include("../rb.hrl"). -export([install_file_classes/0]). install_file_classes() -> {ok, FileClass} = erruby_class:new_class(), 'File' = erruby_object:def_global_const('File', FileClass), erruby_object:def_singleton_method(FileClass, 'expand_path', fun method_expand_path/3), ok. method_expand_path(Env, Filename, RelativeDirOrFileName) -> {ok, Cwd} = file:get_cwd(), DirOrFileName = filename:absname_join(Cwd, RelativeDirOrFileName), ExpanedPath = filename:absname_join(DirOrFileName, Filename), FlattenedPath = flatten_path(ExpanedPath), erruby_vm:new_string(FlattenedPath, Env). flatten_path(Path)-> Components = filename:split(Path), FlattenedComponents = flatten_path_components(Components,[]), filename:join(FlattenedComponents). flatten_path_components([], Acc) -> lists:reverse(Acc); flatten_path_components([".."|T], [])-> flatten_path_components(T, []); flatten_path_components([".."|T], ["/"])-> flatten_path_components(T, ["/"]); flatten_path_components([".."|T], [_H|Acc])-> flatten_path_components(T, Acc); flatten_path_components([H|T], Acc)-> flatten_path_components(T, [H|Acc]). ================================================ FILE: src/erruby_nil.erl ================================================ -module(erruby_nil). -export([new_nil/1, install_nil_class/0, nil_instance/0]). install_nil_class() -> {ok, NilClass} = erruby_class:new_class(), 'NilClass' = erruby_object:def_global_const('NilClass', NilClass), erruby_object:def_method(NilClass, '&', fun method_and/2), erruby_object:def_method(NilClass, '^', fun method_xor/2), erruby_object:def_method(NilClass, '|', fun method_xor/2), erruby_object:def_method(NilClass, inspect, fun method_inspect/1), erruby_object:def_method(NilClass, 'nil?', fun 'method_nil_q'/1), erruby_object:def_method(NilClass, 'to_s', fun 'method_to_s'/1), erruby_object:new_object_with_pid_symbol(erruby_nil, NilClass), ok. new_nil(Env) -> erruby_rb:return(nil_instance(), Env). nil_instance() -> whereis(erruby_nil). method_and(Env, _Obj) -> erruby_boolean:new_false(Env). method_xor(Env, Obj) -> Nil = nil_instance(), False = erruby_boolean:false_instance(), case Obj of Nil -> erruby_boolean:new_false(Env); False -> erruby_boolean:new_false(Env); _ -> erruby_boolean:new_true(Env) end. method_inspect(Env) -> erruby_vm:new_string("nil",Env). method_nil_q(Env) -> erruby_boolean:new_true(Env). method_to_s(Env) -> erruby_vm:new_string("",Env). ================================================ FILE: src/erruby_object.erl ================================================ -module(erruby_object). -include("rb.hrl"). -behavior(gen_server). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). %for vm -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]). -export([def_singleton_method/4, def_singleton_method/3]). -export([def_global_var/2, find_global_var/1]). %for other buildtin class -export([def_method/3, new_object_with_pid_symbol/2, new_object/2]). -export([def_ivar/3, find_ivar/2]). -export([init_main_object/0, main_object/0]). -export([start_link/2, start_link/1]). -export([get_properties/1, set_properties/2]). -export([get_class/1]). init([#{class := Class, properties := Properties}]) -> DefaultState = default_state(), StateWithClass = add_class_to_state(DefaultState, Class), {ok, add_property_to_state(StateWithClass, Properties)}; init([#{class := Class}]) -> DefaultState = default_state(), {ok, add_class_to_state(DefaultState, Class)}; init([]) -> {ok, default_state()}. add_class_to_state(State, Class) -> State#{class => Class}. add_property_to_state(State, Properties) -> State#{properties => Properties}. %TODO in method_class return defalut object_class if no class is present default_state() -> Methods = #{}, IVars = #{}, Consts = #{}, #{self => self(), methods => Methods, ivars => IVars, properties => #{}, consts => Consts}. %TODO unify these? start_link(Class) -> gen_server:start_link(?MODULE, [#{class => Class }], []). start_link(Class, Properties) -> gen_server:start_link(?MODULE, [#{class => Class, properties => Properties}], []). terminate(_Arg, _State) -> {ok, dead}. code_change(_OldVsn, State, _Extra) -> {ok, State}. get_class(Self) -> gen_server:call(Self, #{type => get_class}). find_instance_method(Self, Name) -> SingletonMethod = find_instance_method_in_singleton_class(Self, Name), case SingletonMethod of {ok, Method} -> Method; {not_found, _} -> find_instance_method_in_class(Self, Name) end. find_instance_method_in_singleton_class(Self, Name) -> SingletonClass = singleton_class(Self), case SingletonClass of not_found -> {not_found, Name}; _ -> Result = gen_server:call(SingletonClass, #{type => find_method, name => Name}), case Result of not_found -> {not_found, Name}; _ -> {ok, Result} end end. find_instance_method_in_class(Self, Name) -> %erruby_debug:debug_tmp("finding instance method ~p in ~p",[ Name, Self]), Klass = get_class(Self), Result = gen_server:call(Klass, #{type => find_method, name => Name}), case Result of not_found -> {not_found, Name}; _ -> Result end. find_method(Self, Name) -> gen_server:call(Self, #{type => find_method, name => Name}). self_or_object_class(Self) -> MainObject = main_object(), case Self of MainObject -> object_class(); _ -> Self end. singleton_class(Self) -> Properties = get_properties(Self), maps:get(singleton_class, Properties, not_found). get_or_create_singleton_class(Self) -> SingletonClass = singleton_class(Self), case SingletonClass of not_found -> {ok, NewSingletonClass} = erruby_class:new_named_class("singleton class"), Properties = get_properties(Self), NewProperties = Properties#{ singleton_class => NewSingletonClass }, set_properties(Self, NewProperties), NewSingletonClass; _ -> SingletonClass end. def_method(Self, Name, Args, Body) -> Receiver = self_or_object_class(Self), gen_server:call(Receiver, #{type => def_method, name => Name, args => Args, body => Body}). def_method(Self,Name,Func) when is_function(Func) -> Receiver = self_or_object_class(Self), gen_server:call(Receiver, #{type => def_method, name => Name, func => Func}). def_singleton_method(Self, Name, Args, Body) -> Receiver = get_or_create_singleton_class(Self), gen_server:call(Receiver, #{type => def_method, name => Name, args => Args, body => Body}). def_singleton_method(Self,Name,Func) when is_function(Func) -> Receiver = get_or_create_singleton_class(Self), gen_server:call(Receiver, #{type => def_method, name => Name, func => Func}). %TODO call def_const instead def_global_const(Name, Value) -> gen_server:call(object_class(), #{type => def_const, name => Name, value => Value}). find_global_const(Name) -> find_const(object_class(), Name). %TODO define on basic object instead %TODO ability to use custom getter/setter def_global_var(Name, Value) -> Msg = #{type => def_global_var, name => Name, value => Value}, gen_server:call(object_class(), Msg). find_global_var(Name) -> gen_server:call(object_class(), #{type => find_global_var, name => Name}). def_const(Self, Name, Value) -> Receiver = self_or_object_class(Self), gen_server:call(Receiver, #{type => def_const, name => Name, value => Value}). find_const(Self, Name) -> erruby_debug:debug_2("finding on ~p for const:~p~n",[Self, Name]), gen_server:call(Self, #{type => find_const, name => Name}). def_ivar(Self, Name, Value)-> gen_server:call(Self, #{type => def_ivar, name => Name, value => Value}). find_ivar(Self, Name) -> erruby_debug:debug_2("finding on ~p for ivar:~p~n",[Self, Name]), gen_server:call(Self, #{type => find_ivar, name => Name}). get_properties(Self) -> gen_server:call(Self, #{type => get_properties}). set_properties(Self, Properties) -> gen_server:call(Self, #{type => set_properties, properties => Properties}). handle_info(Info, State) -> io:format("Got unkwon info:~n~p~n", [Info]), {ok, State}. handle_call(#{ type := def_method , name := Name, body := Body, args := Args}=_Msg, _From, #{methods := Methods} =State) -> NewMethods = Methods#{ Name => #{ args => Args, body => Body, argc => length(Args) } }, NewState = State#{ methods := NewMethods}, {reply, Name, NewState}; handle_call(#{ type := def_method, name := Name, func := Func}=_Msg, _From, #{methods := Methods} = State) -> NewMethods = Methods#{ Name => Func }, NewState = State#{ methods := NewMethods}, {reply, Name, NewState}; handle_call(#{ type := find_method, name := Name }, _From, #{methods := Methods} = State) -> erruby_debug:debug_2("finding method:~p~n in State:~p~n",[Name, State]), case maps:is_key(Name,Methods) of true -> #{Name := Method} = Methods, {reply, Method, State}; false -> %TODO use error classes %io:format("Method ~p not found~n",[Name]), erruby_debug:debug_2("finding in ancestors:~p~n",[ancestors(State)]), Method = find_method_in_ancestors(ancestors(State), Name), {reply, Method, State} end; handle_call(#{ type := get_properties }, _From, #{properties := Properties}=State) -> {reply, Properties, State}; handle_call(#{ type := set_properties, properties := Properties }, _From, State) -> NewState = State#{ properties := Properties}, {reply, NewState, NewState}; handle_call(#{ type := def_ivar, name := Name, value := Value }, _From, #{ivars := IVars}=State) -> NewIvars = IVars#{Name => Value}, NewState = State#{ivars := NewIvars}, {reply, Name, NewState}; handle_call(#{ type := find_ivar, name := Name }, _From, #{ivars := IVars}=State) -> erruby_debug:debug_2("finding ivar:~p~nin State:~p~n",[Name, State]), Value = maps:get(Name, IVars, erruby_nil:nil_instance()), {reply, Value, State}; handle_call(#{ type := def_const, name := Name, value := Value }, _From, #{consts := Consts}=State) -> NewConsts = Consts#{Name => Value}, NewState = State#{consts := NewConsts}, {reply, Name, NewState}; handle_call(#{ type := find_const, name := Name }, _From, #{consts := Consts}=State) -> erruby_debug:debug_2("finding const:~p~nin State:~p~n",[Name, State]), Value = maps:get(Name, Consts, not_found), {reply, Value, State}; handle_call(#{ type := def_global_var, name := Name, value := Value}, _From, #{properties := #{global_var_tbl := GVarTbl} } = State) -> NewGVarTbl = GVarTbl#{ Name => Value}, #{properties := Properties} = State, NewProperties = Properties#{ global_var_tbl := NewGVarTbl }, NewState = State#{ properties := NewProperties}, {reply, Name, NewState}; handle_call(#{ type := find_global_var, name := Name}, _From, #{properties := #{global_var_tbl := GVarTbl} } = State) -> Value = maps:get(Name, GVarTbl, not_found), {reply, Value, State}; handle_call(#{ type := get_class}, _From, State) -> Value = maps:get(class, State, object_class()), {reply, Value, State}; handle_call(_Req, _From, State) -> io:format("handle unknow call ~p ~n ~p ~n ~p ~n",[_Req, _From, State]), NewState = State, {reply, done, NewState}. handle_cast(_Req, State) -> io:format("handle unknown cast ~p ~p ~n",[_Req, State]), NewState = State, {reply, done, NewState}. %TODO support va args method_puts(Env, String) -> io:format("~s~n", [String]), erruby_nil:new_nil(Env). append_rb_extension(FileName) -> case filename:extension(FileName) of [] -> string:concat(FileName, ".rb"); _ -> FileName end. %TODO extract to Kernal %TODO raise error if file not found method_require_relative(Env, FileName) -> RelativeFileName = relativeFileName(Env, FileName), RelativeFileNameWithExt = append_rb_extension(RelativeFileName), LoadedFeatures = find_global_var("$LOADED_FEATURES"), LoadedFeaturesList = erruby_array:array_to_list(LoadedFeatures), Contains = lists:member( RelativeFileNameWithExt, LoadedFeaturesList), case Contains of true -> erruby_boolean:new_false(Env); _ -> load_file(Env, RelativeFileNameWithExt), erruby_array:push(LoadedFeatures, RelativeFileNameWithExt), erruby_boolean:new_true(Env) end. relativeFileName(Env, FileName) -> SrcFile = erruby_vm:file_name(Env), SrcDir = filename:dirname(SrcFile), filename:join([SrcDir, FileName]). load_file(Env, RelativeFileNameWithExt) -> try erruby:eruby(RelativeFileNameWithExt), erruby_boolean:new_true(Env) catch _:_E -> erruby_debug:debug_2("cant require_relative file ~p~n", [RelativeFileNameWithExt]), erruby_boolean:new_false(Env) end. %TODO raise error if file not found % @TODO find a better way to get filename method_load(Env, FileName)-> Pwd = os:getenv("PWD"), RelativeFileNameWithExt = filename:join([Pwd, FileName]), load_file(Env, RelativeFileNameWithExt). method_self(#{self := Self}=Env) -> erruby_rb:return(Self, Env). method_inspect(#{self := Self}=Env) -> S = io_lib:format("#",[Self]), erruby_vm:new_string(S,Env). method_to_s(#{self := Self}=Env) -> S = io_lib:format("~p",[Self]), erruby_vm:new_string(S,Env). %TODO support property? new_object_with_pid_symbol(Symbol, Class) -> gen_server:start_link({local, Symbol}, ?MODULE, [#{class => Class}], []). new_object(Class, Payload) when is_map(Payload) -> start_link(Class, Payload). init_object_class() -> erb:find_or_init_class(erruby_object_class, fun init_object_class_internal/0). init_object_class_internal() -> {ok, Pid} = gen_server:start_link({local, erruby_object_class}, ?MODULE, [],[]), install_object_class_methods(), 'Object' = def_const(Pid, 'Object', Pid), set_properties(object_class(), #{global_var_tbl => #{}}), def_global_var("$LOADED_FEATURES", erruby_array:new_array([])), {ok, Pid}. init_main_object() -> erb:find_or_init_class(erruby_main_object, fun init_main_object_internal/0). init_main_object_internal() -> new_object_with_pid_symbol(erruby_main_object, object_class()). object_class() -> whereis(erruby_object_class). main_object() -> whereis(erruby_main_object). install_object_class_methods() -> %TODO use this after inherent is done %def_method(object_class(), '==', fun method_eq/2). def_method(object_class(), 'puts', fun method_puts/2), def_method(object_class(), 'self', fun method_self/1), def_method(object_class(), 'inspect', fun method_inspect/1), def_method(object_class(), 'to_s', fun method_to_s/1), def_method(object_class(), '==', fun method_eq/2), def_method(object_class(), 'require_relative', fun method_require_relative/2), def_method(object_class(), 'load', fun method_load/2), ok. method_eq(#{self := Self}=Env, Object) -> case Object of Self -> erruby_boolean:new_true(Env); _ -> erruby_boolean:new_false(Env) end. super_class(#{properties := Properties}=_State) -> maps:get(superclass, Properties, object_class()). %TODO handle include & extend ancestors(State) -> SuperClass = super_class(State), ObjectClass = object_class(), case self() of ObjectClass -> []; _ -> [SuperClass, ObjectClass] end. find_method_in_ancestors([], _Name) -> not_found; find_method_in_ancestors(Ancestors, Name) -> [Klass | Rest] = Ancestors, Method = find_method(Klass, Name), case Method of not_found -> find_method_in_ancestors(Rest, Name); _ -> Method end. ================================================ FILE: src/erruby_rb.erl ================================================ %%% TODO move general runtime api to here -module(erruby_rb). -export([ret_self/1,ret_val/1,return/2]). ret_self( #{self := Self} = Env ) -> Env#{ret_val => Self}. ret_val( #{ret_val := RetVal} ) -> RetVal. return(Value, Env) -> Env#{ret_val => Value}. ================================================ FILE: src/erruby_sup.erl ================================================ -module(erruby_sup). -behaviour(supervisor). %% API -export([start_link/0]). %% Supervisor callbacks -export([init/1]). %% Helper macro for declaring children of supervisor -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). %% =================================================================== %% API functions %% =================================================================== start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% =================================================================== %% Supervisor callbacks %% =================================================================== init([]) -> {ok, { {one_for_one, 5, 10}, []} }. ================================================ FILE: src/erruby_vm.erl ================================================ -module(erruby_vm). -include("rb.hrl"). -export([eval_file/2, scanl/3]). -export([new_nil/1, new_string/2]). -export([eval_method_with_exit/5, yield/2]). -export([file_name/1]). print_ast(Ast) -> erruby_debug:debug_1("Ast: ~p ~n",[Ast]). print_env(Env) -> erruby_debug:debug_1("Env: ~p ~n",[Env]). scanl(_F, Acc, []) -> [Acc]; scanl(F, Acc0, [H | T]) -> Acc = apply(F, [H, Acc0]), [Acc0 | scanl(F, Acc, T)]. eval_file(Ast, FileName) -> DefaultEnv = default_env(), Env = set_filename(DefaultEnv, FileName), eval_ast(Ast, Env). set_filename(Env, FileName) -> Env#{'FileName' => FileName}. file_name(Env) -> case maps:find('FileName', Env) of {ok, Value} -> Value; error -> file_name(find_prev_frame(Env)) end. eval_ast({ast,type,'begin',children, Children}, Env) -> erruby_debug:debug_2("eval begin~n",[]), lists:foldl(fun eval_ast/2, Env, Children); eval_ast({ast, type, self, children, []}, Env) -> #{ self := Self } = Env, erruby_rb:return(Self, Env); eval_ast({ast, type, str, children, Children}, Env) -> [SBin|_T] = Children, new_string(binary_to_list(SBin), Env); eval_ast({ast, type, nil, children, []}, Env) -> erruby_nil:new_nil(Env); eval_ast({ast, type, true, children, []}, Env) -> erruby_boolean:new_true(Env); eval_ast({ast, type, false, children, []}, Env) -> erruby_boolean:new_false(Env); eval_ast({ast, type, array, children, Args}, Env) -> {EvaledArgs, LastEnv} = eval_args(Args, Env), erruby_array:new_array(LastEnv, EvaledArgs); eval_ast({ast, type, int, children, [N]}, Env) -> erruby_fixnum:new_fixnum(Env, N); eval_ast({ast, type, '__FILE__', children, []}, #{'FileName' := Filename } = Env) -> new_string(Filename, Env); eval_ast({ast, type, csend, children, Children}, Env)-> erruby_debug:debug_1("csend~n",[]), [print_ast(Ast) || Ast <- Children], [Receiver | [Msg | Args]] = Children, ReceiverFrame = receiver_or_self(Receiver, Env), UnresolvedTarget = erruby_rb:ret_val(ReceiverFrame), Target = resolve_future(UnresolvedTarget), Nil = erruby_nil:nil_instance(), case Target of Nil -> Nil; _ -> {EvaledArgs, LastEnv} = eval_args(Args, ReceiverFrame), Method = erruby_object:find_instance_method(Target, Msg), eval_method(Target,Method, EvaledArgs, LastEnv) end; eval_ast({ast, type, psend, children, Children}, Env)-> erruby_debug:debug_1("psend~n",[]), [print_ast(Ast) || Ast <- Children], [Receiver | [Msg | Args]] = Children, ReceiverFrame = receiver_or_self(Receiver, Env), UnresolvedTarget = erruby_rb:ret_val(ReceiverFrame), Target = resolve_future(UnresolvedTarget), {EvaledArgs, LastEnv} = eval_args(Args, ReceiverFrame), Method = erruby_object:find_instance_method(Target, Msg), process_eval_method(Target,Method, EvaledArgs, LastEnv); %TODO call method using method object eval_ast({ast,type,send, children, Children}, Env) -> erruby_debug:debug_1("send~n",[]), [print_ast(Ast) || Ast <- Children], [Receiver | [Msg | Args]] = Children, ReceiverFrame = receiver_or_self(Receiver, Env), UnresolvedTarget = erruby_rb:ret_val(ReceiverFrame), Target = resolve_future(UnresolvedTarget), {EvaledArgs, LastEnv} = eval_args(Args, ReceiverFrame), Method = erruby_object:find_instance_method(Target, Msg), eval_method(Target,Method, EvaledArgs, LastEnv); eval_ast({ast, type, block, children, [Method | [Args | [Body]]]= _Children}, Env) -> Block = #{body => Body, args => Args}, BlockEnv = Env#{block => Block}, Result = eval_ast(Method, BlockEnv), Result#{block => not_exist}; eval_ast({ast, type, yield, children, Args}, Env) -> {EvaledArgs, LastEnv} = eval_args(Args, Env), yield(LastEnv, EvaledArgs); %FIXME return the value eval_ast({ast, type, lvasgn, children, Children}, Env) -> [Name, ValAst] = Children, NewEnv = eval_ast(ValAst, Env), RetVal = erruby_rb:ret_val(NewEnv), bind_lvar(Name, RetVal, NewEnv); eval_ast({ast, type, lvar, children, [Name]}, Env) -> erruby_debug:debug_1("searching lvar ~p~n in frame~p~n", [Name, Env]), #{ lvars := #{Name := Val}} = Env, erruby_rb:return(Val, Env); %FIXME return the value eval_ast({ast, type, ivasgn, children, Children}, #{self := Self}=Env) -> [Name, ValAst] = Children, NewEnv = eval_ast(ValAst, Env), RetVal = erruby_rb:ret_val(NewEnv), erruby_object:def_ivar(Self, Name, RetVal), erruby_rb:return(RetVal, Env); eval_ast({ast, type, ivar, children, [Name]}, #{self := Self}=Env) -> Val = erruby_object:find_ivar(Self, Name), erruby_rb:return(Val, Env); eval_ast({ast, type, gvasgn, children, Children}, Env) -> [Name, ValAst] = Children, NewEnv = eval_ast(ValAst, Env), RetVal = erruby_rb:ret_val(NewEnv), erruby_object:def_global_var(Name, RetVal), erruby_rb:return(Name, NewEnv); eval_ast({ast, type, gvar, children, [Name]}, Env) -> Val = erruby_object:find_global_var(Name), erruby_rb:return(Val, Env); eval_ast({ast, type, defs, children, Children}, Env) -> [ReceiverAst , Name , {ast, type, args, children, Args}, Body] = Children, ReceiverEnv = eval_ast(ReceiverAst, Env), Receiver = erruby_rb:ret_val(ReceiverEnv), erruby_object:def_singleton_method(Receiver, Name, Args, Body), new_symbol(Name, Env); eval_ast({ast, type, def, children, Children}, Env) -> [Name | [ {ast, type, args, children, Args} , Body ] ] = Children, #{ self := Self } = Env, erruby_object:def_method(Self, Name, Args, Body), new_symbol(Name, Env); %TODO figure out the Unknown field in AST %TODO impl ancestors eval_ast({ast, type, class, children, [NameAst,undefined,Body] = _Children}, #{ self := Self } = Env) -> {_,_,const,_,[_,Name]} = NameAst, NameEnv = eval_ast(NameAst,Env), ClassConst = erruby_rb:ret_val(NameEnv), Class = case ClassConst of not_found -> {ok, NewClass} = erruby_class:new_named_class(Name), erruby_object:def_const(Self, Name, NewClass), NewClass; _ -> ClassConst end, case Body of undefined -> NameEnv; _ -> NewFrame = new_frame(NameEnv, Class), ResultFrame = eval_ast(Body,NewFrame), pop_frame(ResultFrame) end; %TODO refactor with the one without SupperClass eval_ast({ast, type, class, children, [NameAst,SuperClassAst,Body] = _Children}, #{ self := Self } = Env) -> {_,_,const,_,[_,Name]} = NameAst, NameEnv = eval_ast(NameAst,Env), ClassConst = erruby_rb:ret_val(NameEnv), SuperClassEnv = eval_ast(SuperClassAst,NameEnv), SuperClassConst = erruby_rb:ret_val(SuperClassEnv), %TODO should fail when SuperClassConst is not defined Class = case ClassConst of not_found -> {ok, NewClass} = erruby_class:new_class(SuperClassConst), erruby_object:def_const(Self, Name, NewClass), NewClass; _ -> ClassConst end, case Body of undefined -> SuperClassEnv; _ -> NewFrame = new_frame(SuperClassEnv, Class), ResultFrame = eval_ast(Body,NewFrame), pop_frame(ResultFrame) end; eval_ast({ast, type, casgn, children, [ParentConstAst, Name, ValAst] }, Env) -> ParentConstEnv = parent_const_env(ParentConstAst, Env), ParentConst = erruby_rb:ret_val(ParentConstEnv), NewEnv = eval_ast(ValAst, ParentConstEnv), Val = erruby_rb:ret_val(NewEnv), erruby_object:def_const(ParentConst, Name, Val), NewEnv; %TODO throw error when not_found eval_ast({ast, type, const, children, [ParentConstAst, Name]}, Env) -> ParentConstEnv = parent_const_env(ParentConstAst, Env), ParentConst = erruby_rb:ret_val(ParentConstEnv), LocalConst = erruby_object:find_const(ParentConst, Name), Const = case LocalConst of not_found -> erruby_object:find_const(erruby_object:object_class(), Name); _ -> LocalConst end, erruby_rb:return(Const, Env); eval_ast(Ast, Env) -> erruby_debug:debug_1("Unhandled eval~n",[]), print_ast(Ast), print_env(Env). process_eval_method(Target,Method,Args,Env) -> Pid = spawn(?MODULE, eval_method_with_exit, [Target,Method,Args,Env, self()]), erruby_rb:return({future, Pid}, Env). resolve_future({future, Pid}) -> receive {future, Pid, Result} -> print_env(Result), %erruby_debug:debug_tmp("resolve_future future ~p~n", [Result]), self() ! {future, Pid, Result}, erruby_rb:ret_val(Result) end; resolve_future(Any) -> print_env(Any), %erruby_debug:debug_tmp("resolve_future Any ~p~n", [Any]), Any. eval_method_with_exit(Target,Method,Args,Env, Sender) -> try Result = eval_method(Target, Method, Args, Env), Respond = {future, self(),Result}, Sender ! Respond catch _:E -> io:format("error ~p ~n", [E]), erlang:display(erlang:get_stacktrace()) end, exit(normal). eval_method(Target,Method, UnresolvedArgs, Env) when is_function(Method) -> NewFrame = new_frame(Env,Target), Args = lists:map(fun resolve_future/1, UnresolvedArgs), MethodArgs = [NewFrame | Args], ResultFrame = apply(Method, MethodArgs), pop_frame(ResultFrame); eval_method(Target, {not_found, Name}, _, _) -> %TODO raise exception instead TargetClass = erruby_object:get_class(Target), TargetClassName = erruby_class:class_name(TargetClass), io:format("Undefined Method ~s for ~p~n", [Name, TargetClassName]), exit(known_error); eval_method(Target,#{body := Body, args := ArgNamesAst} = _Method, Args, Env) -> NewFrame = new_frame(Env,Target), ArgNames = [ArgName || {ast, type, arg, children, [ArgName]} <- ArgNamesAst], NameWithArgs = lists:zip( ArgNames, Args), NewFrameWithArgs = lists:foldl(fun ({Name, Arg}, EnvAcc) -> bind_lvar(Name, Arg, EnvAcc) end, NewFrame, NameWithArgs), pop_frame( eval_ast(Body, NewFrameWithArgs)). bind_lvar(Name, Val, #{ lvars := LVars } = Env) -> Env#{ lvars := LVars#{ Name => Val }}. receiver_or_self(undefined, Env) -> #{ self := Self } = Env, erruby_rb:return(Self, Env); receiver_or_self(Receiver, Env) -> eval_ast(Receiver,Env). new_string(String, Env) -> erruby_rb:return(String, Env). new_symbol(Symbol, Env) -> erruby_rb:return(Symbol, Env). new_frame(Env, Self) -> Env#{lvars => #{}, ret_val => not_exist, self => Self, prev_frame => Env}. new_nil(Env) -> erruby_nil:new_nil(Env). %TODO move to another place find_prev_frame(Env) -> case maps:get(prev_frame, Env, no_prev_frame) of no_prev_frame -> throw(cant_find_block); Frame -> Frame end. find_block(Env) -> case maps:get(block, Env) of not_exist -> find_block(find_prev_frame(Env)); Block -> Block end. parent_const_env(ParentConstAst, Env) -> case ParentConstAst of undefined -> erruby_rb:ret_self(Env); {ast, type, const, children, _} -> eval_ast(ParentConstAst, Env) end. eval_args(ArgAsts, Env) -> [_ |Envs] = scanl(fun eval_ast/2, Env, ArgAsts), EvaledArgs = lists:map( fun erruby_rb:ret_val/1, Envs), LastEnv = case Envs of [] -> Env; _ -> lists:last(Envs) end, {EvaledArgs, LastEnv}. yield(Env, Args)-> Block = find_block(Env), #{body := Body, args := {ast, type, args, children, ArgNamesAst}} = Block, ArgNames = [ArgName || {ast, type, arg, children, [ArgName]} <- ArgNamesAst], NameWithArgs = lists:zip( ArgNames, Args), NewFrameWithArgs = lists:foldl(fun ({Name, Arg}, EnvAcc) -> bind_lvar(Name, Arg, EnvAcc) end, Env, NameWithArgs), Result = eval_ast(Body,NewFrameWithArgs), Result. pop_frame(Frame) -> #{ret_val := RetVal, prev_frame := PrevFrame} = Frame, erruby_rb:return(RetVal, PrevFrame). default_env() -> {ok, _ObjectClass} = erruby_object:init_object_class(), {ok, _ClassClass} = erruby_class:init_class_class(), {ok, MainObject} = erruby_object:init_main_object(), init_builtin_class(), #{self => MainObject, lvars => #{}}. init_builtin_class() -> ok = erruby_nil:install_nil_class(), ok = erruby_array:install_array_classes(), ok = erruby_integer:install_integer_class(), ok = erruby_fixnum:install_fixnum_class(), ok = erruby_boolean:install_boolean_classes(), ok = erruby_file:install_file_classes(), ok. ================================================ FILE: src/rb.hrl ================================================ -define(RB_DEBUG_T(T),erruby_debug:debug_tmp("~s:~p~n",[??T,T])). ================================================ FILE: test.rb ================================================ #!/usr/bin/env ruby require 'optparse' options = {} OptionParser.new do |opts| opts.banner = "Usage: test.rb [options] test_case.rb" opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| options[:verbose] = v end opts.on("-h", "--help", "Print this help") do puts(opts) exit 1 end end.parse! verbose = options[:verbose] def run_single_mri_test(fn, verbose:false) basename = File.basename(fn,'.rb') outname = "rb_test/sysrb_out/#{basename}.out" puts "testing #{fn}" if verbose system("ruby #{fn} > #{outname}") system("./erruby #{fn} | diff #{outname} -") end def run_mri_tests(verbose:) fail_case = [] if ARGV[0] && File.exist?(ARGV[0]) fn = ARGV[0] basename = File.basename(fn,'.rb') test_result = run_single_mri_test(fn, verbose: verbose) fail_case << fn unless test_result else Dir.glob("rb_test/*.rb") do |fn| basename = File.basename(fn,'.rb') next if basename.start_with?("_") unless run_single_mri_test(fn, verbose: verbose) fail_case << fn end end end fail_case end fail_case = run_mri_tests(verbose: verbose) if fail_case.empty? puts "everything pass" exit 0 else fail_case.each do |fn| puts "test #{fn} failed" end exit 1 end