diff options
39 files changed, 586 insertions, 179 deletions
@@ -1,4 +1,4 @@ -Copyright (c) 2017 mruby developers +Copyright (c) 2018 mruby developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -17,7 +17,7 @@ of the Ministry of Economy, Trade and Industry of Japan. ## How to get mruby -The stable version 1.3.0 of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/archive/1.3.0.zip](https://github.com/mruby/mruby/archive/1.3.0.zip) +The stable version 1.4.0 of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/archive/1.4.0.zip](https://github.com/mruby/mruby/archive/1.4.0.zip) The latest development version of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/zipball/master](https://github.com/mruby/mruby/zipball/master) diff --git a/doc/guides/debugger.md b/doc/guides/debugger.md index 72f2c2b32..a41b6ce45 100644 --- a/doc/guides/debugger.md +++ b/doc/guides/debugger.md @@ -38,7 +38,7 @@ To confirm mrdb was installed properly, run mrdb with the `--version` option: ```bash $ mrdb --version -mruby 1.3.0 (2017-7-4) +mruby 1.4.0 (2018-1-16) ``` ## 2.2 Basic Operation diff --git a/doc/limitations.md b/doc/limitations.md index 134ff6e26..855ae16ee 100644 --- a/doc/limitations.md +++ b/doc/limitations.md @@ -38,7 +38,7 @@ puts [1,2,3] 3 ``` -#### mruby [1.3.0 (2017-7-4)] +#### mruby [1.4.0 (2018-1-16)] ``` [1, 2, 3] @@ -61,7 +61,7 @@ end ```ZeroDivisionError``` is raised. -#### mruby [1.3.0 (2017-7-4)] +#### mruby [1.4.0 (2018-1-16)] No exception is raised. @@ -89,7 +89,7 @@ p Liste.new "foobar" ``` [] ``` -#### mruby [1.3.0 (2017-7-4)] +#### mruby [1.4.0 (2018-1-16)] ```ArgumentError``` is raised. @@ -119,7 +119,7 @@ false true ``` -#### mruby [1.3.0 (2017-7-4)] +#### mruby [1.4.0 (2018-1-16)] ``` true @@ -142,7 +142,7 @@ defined?(Foo) nil ``` -#### mruby [1.3.0 (2017-7-4)] +#### mruby [1.4.0 (2018-1-16)] ```NameError``` is raised. @@ -159,7 +159,7 @@ alias $a $__a__ ``` nil ``` -#### mruby [1.3.0 (2017-7-4)] +#### mruby [1.4.0 (2018-1-16)] Syntax error @@ -181,7 +181,7 @@ end ```ArgumentError``` is raised. The re-defined ```+``` operator does not accept any arguments. -#### mruby [1.3.0 (2017-7-4)] +#### mruby [1.4.0 (2018-1-16)] ``` 'ab' ``` Behavior of the operator wasn't changed. diff --git a/include/mrbconf.h b/include/mrbconf.h index 96ab6b77f..cc28acfaa 100644 --- a/include/mrbconf.h +++ b/include/mrbconf.h @@ -36,12 +36,10 @@ /* size of the method cache (need to be the power of 2) */ //#define MRB_METHOD_CACHE_SIZE (1<<7) -/* add -DMRB_METHOD_TABLE_INLINE unless platform uses MSB of pointers */ +/* add -DMRB_METHOD_TABLE_INLINE to reduce the size of method table */ +/* MRB_METHOD_TABLE_INLINE requires LSB of function pointers to be zero */ +/* you might need to specify --falign-functions=n (where n>1) */ //#define MRB_METHOD_TABLE_INLINE -/* turn MRB_METHOD_TABLE_INLINE on for linux by default */ -#if !defined(MRB_METHOD_TABLE_INLINE) && defined(__linux__) -# define MRB_METHOD_TABLE_INLINE -#endif /* add -DMRB_INT16 to use 16bit integer for mrb_int; conflict with MRB_INT64 */ //#define MRB_INT16 diff --git a/include/mruby.h b/include/mruby.h index d93874dfa..f4d8e229a 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -1,7 +1,7 @@ /* ** mruby - An embeddable Ruby implementation ** -** Copyright (c) mruby developers 2010-2017 +** Copyright (c) mruby developers 2010-2018 ** ** Permission is hereby granted, free of charge, to any person obtaining ** a copy of this software and associated documentation files (the diff --git a/include/mruby/proc.h b/include/mruby/proc.h index 3f246314b..aa281b6dd 100644 --- a/include/mruby/proc.h +++ b/include/mruby/proc.h @@ -98,8 +98,8 @@ MRB_API mrb_value mrb_proc_cfunc_env_get(mrb_state*, mrb_int); #ifdef MRB_METHOD_TABLE_INLINE -#define MRB_METHOD_FUNC_FL ((uintptr_t)1U<<(sizeof(uintptr_t)*8-1)) -#define MRB_METHOD_FUNC_P(m) ((uintptr_t)(m)&MRB_METHOD_FUNC_FL) +#define MRB_METHOD_FUNC_FL ((uintptr_t)1) +#define MRB_METHOD_FUNC_P(m) (((uintptr_t)(m))&MRB_METHOD_FUNC_FL) #define MRB_METHOD_FUNC(m) ((mrb_func_t)((uintptr_t)(m)&(~MRB_METHOD_FUNC_FL))) #define MRB_METHOD_FROM_FUNC(m,fn) m=(mrb_method_t)((struct RProc*)((uintptr_t)(fn)|MRB_METHOD_FUNC_FL)) #define MRB_METHOD_FROM_PROC(m,pr) m=(mrb_method_t)(struct RProc*)(pr) diff --git a/include/mruby/version.h b/include/mruby/version.h index 8414bf204..3ba2db031 100644 --- a/include/mruby/version.h +++ b/include/mruby/version.h @@ -42,7 +42,7 @@ MRB_BEGIN_DECL /* * Minor release version number. */ -#define MRUBY_RELEASE_MINOR 3 +#define MRUBY_RELEASE_MINOR 4 /* * Tiny release version number. @@ -62,17 +62,17 @@ MRB_BEGIN_DECL /* * Release year. */ -#define MRUBY_RELEASE_YEAR 2017 +#define MRUBY_RELEASE_YEAR 2018 /* * Release month. */ -#define MRUBY_RELEASE_MONTH 7 +#define MRUBY_RELEASE_MONTH 1 /* * Release day. */ -#define MRUBY_RELEASE_DAY 4 +#define MRUBY_RELEASE_DAY 16 /* * Release date as a string. diff --git a/lib/mruby/build.rb b/lib/mruby/build.rb index 7d6aa49e1..c06a62282 100644 --- a/lib/mruby/build.rb +++ b/lib/mruby/build.rb @@ -334,6 +334,7 @@ EOS attr_accessor :host_target, :build_target def initialize(name, build_dir=nil, &block) + @endian = nil @test_runner = Command::CrossTestRunner.new(self) super end @@ -351,5 +352,26 @@ EOS @test_runner.run(mrbtest) end end + + def big_endian + if @endian + puts "Endian has already specified as #{@endian}." + return + end + @endian = :big + @mrbc.compile_options += ' -E' + compilers.each do |c| + c.defines += %w(MRB_ENDIAN_BIG) + end + end + + def little_endian + if @endian + puts "Endian has already specified as #{@endian}." + return + end + @endian = :little + @mrbc.compile_options += ' -e' + end end # CrossBuild end # MRuby diff --git a/mrbgems/default.gembox b/mrbgems/default.gembox index 65584681d..7ddbb16d1 100644 --- a/mrbgems/default.gembox +++ b/mrbgems/default.gembox @@ -1,4 +1,10 @@ MRuby::GemBox.new do |conf| + # Use standard IO/File class + conf.gem :core => "mruby-io" + + # Use standard Array#pack, String#unpack methods + conf.gem :core => "mruby-pack" + # Use standard Kernel#sprintf method conf.gem :core => "mruby-sprintf" diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c b/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c index 62f7670e0..21fe64127 100644 --- a/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c @@ -84,7 +84,7 @@ dirname(mrb_state *mrb, const char *path) static source_file* source_file_new(mrb_state *mrb, mrb_debug_context *dbg, char *filename) { - source_file *file = NULL; + source_file *file; file = (source_file*)mrb_malloc(mrb, sizeof(source_file)); diff --git a/mrbgems/mruby-class-ext/src/class.c b/mrbgems/mruby-class-ext/src/class.c index 5506c4829..705db9949 100644 --- a/mrbgems/mruby-class-ext/src/class.c +++ b/mrbgems/mruby-class-ext/src/class.c @@ -15,6 +15,42 @@ mrb_mod_singleton_class_p(mrb_state *mrb, mrb_value self) return mrb_bool_value(mrb_type(self) == MRB_TT_SCLASS); } +/* + * call-seq: + * module_exec(arg...) {|var...| block } -> obj + * class_exec(arg...) {|var...| block } -> obj + * + * Evaluates the given block in the context of the + * class/module. The method defined in the block will belong + * to the receiver. Any arguments passed to the method will be + * passed to the block. This can be used if the block needs to + * access instance variables. + * + * class Thing + * end + * Thing.class_exec{ + * def hello() "Hello there!" end + * } + * puts Thing.new.hello() + */ + +static mrb_value +mrb_mod_module_exec(mrb_state *mrb, mrb_value self) +{ + const mrb_value *argv; + mrb_int argc; + mrb_value blk; + + mrb_get_args(mrb, "*&", &argv, &argc, &blk); + + if (mrb_nil_p(blk)) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "no block given"); + } + + mrb->c->ci->target_class = mrb_class_ptr(self); + return mrb_yield_cont(mrb, blk, self, argc, argv); +} + void mrb_mruby_class_ext_gem_init(mrb_state *mrb) { @@ -22,6 +58,8 @@ mrb_mruby_class_ext_gem_init(mrb_state *mrb) mrb_define_method(mrb, mod, "name", mrb_mod_name, MRB_ARGS_NONE()); mrb_define_method(mrb, mod, "singleton_class?", mrb_mod_singleton_class_p, MRB_ARGS_NONE()); + mrb_define_method(mrb, mod, "module_exec", mrb_mod_module_exec, MRB_ARGS_ANY()|MRB_ARGS_BLOCK()); + mrb_define_method(mrb, mod, "class_exec", mrb_mod_module_exec, MRB_ARGS_ANY()|MRB_ARGS_BLOCK()); } void diff --git a/mrbgems/mruby-class-ext/test/module.rb b/mrbgems/mruby-class-ext/test/module.rb index 65abde108..ed6713aac 100644 --- a/mrbgems/mruby-class-ext/test/module.rb +++ b/mrbgems/mruby-class-ext/test/module.rb @@ -32,3 +32,24 @@ assert 'Module#singleton_class?' do assert_false cls.singleton_class? assert_true scl.singleton_class? end + +assert 'Module#module_eval' do + mod = Module.new + mod.class_exec(1,2,3) do |a,b,c| + assert_equal([1,2,3], [a,b,c]) + def hi + "hi" + end + end + cls = Class.new + cls.class_exec(42) do |x| + assert_equal(42, x) + include mod + def hello + "hello" + end + end + obj = cls.new + assert_equal("hi", obj.hi) + assert_equal("hello", obj.hello) +end diff --git a/mrbgems/mruby-compiler/core/codegen.c b/mrbgems/mruby-compiler/core/codegen.c index c4227ca8e..eb82110b8 100644 --- a/mrbgems/mruby-compiler/core/codegen.c +++ b/mrbgems/mruby-compiler/core/codegen.c @@ -149,8 +149,14 @@ new_label(codegen_scope *s) static inline int genop(codegen_scope *s, mrb_code i) { - if (s->pc == s->icapa) { + if (s->pc >= s->icapa) { s->icapa *= 2; + if (s->pc >= MAXARG_sBx) { + codegen_error(s, "too big code block"); + } + if (s->icapa > MAXARG_sBx) { + s->icapa = MAXARG_sBx; + } s->iseq = (mrb_code *)codegen_realloc(s, s->iseq, sizeof(mrb_code)*s->icapa); if (s->lines) { s->lines = (uint16_t*)codegen_realloc(s, s->lines, sizeof(short)*s->icapa); diff --git a/mrbgems/mruby-compiler/core/parse.y b/mrbgems/mruby-compiler/core/parse.y index 01269d8da..7f848b4df 100644 --- a/mrbgems/mruby-compiler/core/parse.y +++ b/mrbgems/mruby-compiler/core/parse.y @@ -781,7 +781,7 @@ new_dxstr(parser_state *p, node *a) static node* new_dsym(parser_state *p, node *a) { - return cons((node*)NODE_DSYM, new_dstr(p, a)); + return cons((node*)NODE_DSYM, a); } /* (:regx . (s . (opt . enc))) */ @@ -1106,9 +1106,9 @@ heredoc_end(parser_state *p) keyword__FILE__ keyword__ENCODING__ -%token <id> tIDENTIFIER tFID tGVAR tIVAR tCONSTANT tCVAR tLABEL +%token <id> tIDENTIFIER tFID tGVAR tIVAR tCONSTANT tCVAR tLABEL_TAG %token <nd> tINTEGER tFLOAT tCHAR tXSTRING tREGEXP -%token <nd> tSTRING tSTRING_PART tSTRING_MID tLABEL_END +%token <nd> tSTRING tSTRING_PART tSTRING_MID %token <nd> tNTH_REF tBACK_REF %token <num> tREGEXP_END @@ -1180,7 +1180,7 @@ heredoc_end(parser_state *p) %right keyword_not %right '=' tOP_ASGN %left modifier_rescue -%right '?' ':' +%right '?' ':' tLABEL_TAG %nonassoc tDOT2 tDOT3 %left tOROP %left tANDOP @@ -1926,6 +1926,10 @@ arg : lhs '=' arg_rhs { $$ = new_if(p, cond($1), $3, $6); } + | arg '?' arg opt_nl tLABEL_TAG arg + { + $$ = new_if(p, cond($1), $3, $6); + } | primary { $$ = $1; @@ -2568,7 +2572,7 @@ lambda_body : tLAMBEG compstmt '}' { $$ = $2; } - | keyword_do_LAMBDA compstmt keyword_end + | keyword_do_LAMBDA bodystmt keyword_end { $$ = $2; } @@ -2861,7 +2865,7 @@ symbol : basic_symbol | tSYMBEG tSTRING_BEG string_rep tSTRING { p->lstate = EXPR_ENDARG; - $$ = new_dsym(p, push($3, $4)); + $$ = new_dsym(p, new_dstr(p, push($3, $4))); } ; @@ -3266,25 +3270,20 @@ assoc : arg tASSOC arg void_expr_error(p, $3); $$ = cons($1, $3); } - | tLABEL arg - { - void_expr_error(p, $2); - $$ = cons(new_sym(p, $1), $2); - } - | tLABEL_END arg - { - void_expr_error(p, $2); - $$ = cons(new_sym(p, new_strsym(p, $1)), $2); - } - | tSTRING_BEG tLABEL_END arg + | tIDENTIFIER tLABEL_TAG arg { void_expr_error(p, $3); - $$ = cons(new_sym(p, new_strsym(p, $2)), $3); + $$ = cons(new_sym(p, $1), $3); } - | tSTRING_BEG string_rep tLABEL_END arg + | string tLABEL_TAG arg { - void_expr_error(p, $4); - $$ = cons(new_dsym(p, push($2, $3)), $4); + void_expr_error(p, $3); + if ($1->car == (node*)NODE_DSTR) { + $$ = cons(new_dsym(p, $1), $3); + } + else { + $$ = cons(new_sym(p, new_strsym(p, $1)), $3); + } } ; @@ -3973,7 +3972,6 @@ parse_string(parser_state *p) int beg = intn(p->lex_strterm->cdr->cdr->car); int end = intn(p->lex_strterm->cdr->cdr->cdr); parser_heredoc_info *hinf = (type & STR_FUNC_HEREDOC) ? parsing_heredoc_inf(p) : NULL; - int cmd_state = p->cmd_start; if (beg == 0) beg = -3; /* should never happen */ if (end == 0) end = -3; @@ -4188,13 +4186,6 @@ parse_string(parser_state *p) return tREGEXP; } pylval.nd = new_str(p, tok(p), toklen(p)); - if (IS_LABEL_POSSIBLE()) { - if (IS_LABEL_SUFFIX(0)) { - p->lstate = EXPR_BEG; - nextc(p); - return tLABEL_END; - } - } return tSTRING; } @@ -5012,14 +5003,19 @@ parser_yylex(parser_state *p) p->lstate = EXPR_DOT; return tCOLON2; } - if (IS_END() || ISSPACE(c)) { + if (!space_seen && IS_END()) { pushback(p, c); p->lstate = EXPR_BEG; - return ':'; + return tLABEL_TAG; + } + if (!ISSPACE(c) || IS_BEG()) { + pushback(p, c); + p->lstate = EXPR_FNAME; + return tSYMBEG; } pushback(p, c); - p->lstate = EXPR_FNAME; - return tSYMBEG; + p->lstate = EXPR_BEG; + return ':'; case '/': if (IS_BEG()) { @@ -5443,11 +5439,10 @@ parser_yylex(parser_state *p) if (IS_LABEL_POSSIBLE()) { if (IS_LABEL_SUFFIX(0)) { - p->lstate = EXPR_BEG; - nextc(p); + p->lstate = EXPR_END; tokfix(p); pylval.id = intern_cstr(tok(p)); - return tLABEL; + return tIDENTIFIER; } } if (p->lstate != EXPR_DOT) { diff --git a/mrbgems/mruby-enum-ext/mrblib/enum.rb b/mrbgems/mruby-enum-ext/mrblib/enum.rb index 327b573d4..e489698ec 100644 --- a/mrbgems/mruby-enum-ext/mrblib/enum.rb +++ b/mrbgems/mruby-enum-ext/mrblib/enum.rb @@ -451,20 +451,30 @@ module Enumerable ## # call-seq: # enum.none? [{ |obj| block }] -> true or false + # enum.none?(pattern) -> true or false # # Passes each element of the collection to the given block. The method # returns <code>true</code> if the block never returns <code>true</code> # for all elements. If the block is not given, <code>none?</code> will return # <code>true</code> only if none of the collection members is true. # + # If a pattern is supplied instead, the method returns whether + # <code>pattern === element</code> for none of the collection members. + # # %w(ant bear cat).none? { |word| word.length == 5 } #=> true # %w(ant bear cat).none? { |word| word.length >= 4 } #=> false + # %w{ant bear cat}.none?(/d/) #=> true + # [1, 3.14, 42].none?(Float) #=> false # [].none? #=> true # [nil, false].none? #=> true # [nil, true].none? #=> false - def none?(&block) - if block + def none?(pat=NONE, &block) + if pat != NONE + self.each do |*val| + return false if pat === val.__svalue + end + elsif block self.each do |*val| return false if block.call(*val) end @@ -479,6 +489,7 @@ module Enumerable ## # call-seq: # enum.one? [{ |obj| block }] -> true or false + # enum.one?(pattern) -> true or false # # Passes each element of the collection to the given block. The method # returns <code>true</code> if the block returns <code>true</code> @@ -486,16 +497,26 @@ module Enumerable # <code>true</code> only if exactly one of the collection members is # true. # + # If a pattern is supplied instead, the method returns whether + # <code>pattern === element</code> for exactly one collection member. + # # %w(ant bear cat).one? { |word| word.length == 4 } #=> true # %w(ant bear cat).one? { |word| word.length > 4 } #=> false # %w(ant bear cat).one? { |word| word.length < 4 } #=> false + # %w{ant bear cat}.one?(/t/) #=> false # [nil, true, 99].one? #=> false # [nil, true, false].one? #=> true - # + # [ nil, true, 99 ].one?(Integer) #=> true + # [].one? #=> false - def one?(&block) + def one?(pat=NONE, &block) count = 0 - if block + if pat!=NONE + self.each do |*val| + count += 1 if pat === val.__svalue + return false if count > 1 + end + elsif block self.each do |*val| count += 1 if block.call(*val) return false if count > 1 @@ -510,6 +531,71 @@ module Enumerable count == 1 ? true : false end + # ISO 15.3.2.2.1 + # call-seq: + # enum.all? [{ |obj| block } ] -> true or false + # enum.all?(pattern) -> true or false + # + # Passes each element of the collection to the given block. The method + # returns <code>true</code> if the block never returns + # <code>false</code> or <code>nil</code>. If the block is not given, + # Ruby adds an implicit block of <code>{ |obj| obj }</code> which will + # cause #all? to return +true+ when none of the collection members are + # +false+ or +nil+. + # + # If a pattern is supplied instead, the method returns whether + # <code>pattern === element</code> for every collection member. + # + # %w[ant bear cat].all? { |word| word.length >= 3 } #=> true + # %w[ant bear cat].all? { |word| word.length >= 4 } #=> false + # %w[ant bear cat].all?(/t/) #=> false + # [1, 2i, 3.14].all?(Numeric) #=> true + # [nil, true, 99].all? #=> false + # + def all?(pat=NONE, &block) + if pat != NONE + self.each{|*val| return false unless pat === val.__svalue} + elsif block + self.each{|*val| return false unless block.call(*val)} + else + self.each{|*val| return false unless val.__svalue} + end + true + end + + # ISO 15.3.2.2.2 + # call-seq: + # enum.any? [{ |obj| block }] -> true or false + # enum.any?(pattern) -> true or false + # + # Passes each element of the collection to the given block. The method + # returns <code>true</code> if the block ever returns a value other + # than <code>false</code> or <code>nil</code>. If the block is not + # given, Ruby adds an implicit block of <code>{ |obj| obj }</code> that + # will cause #any? to return +true+ if at least one of the collection + # members is not +false+ or +nil+. + # + # If a pattern is supplied instead, the method returns whether + # <code>pattern === element</code> for any collection member. + # + # %w[ant bear cat].any? { |word| word.length >= 3 } #=> true + # %w[ant bear cat].any? { |word| word.length >= 4 } #=> true + # %w[ant bear cat].any?(/d/) #=> false + # [nil, true, 99].any?(Integer) #=> true + # [nil, true, 99].any? #=> true + # [].any? #=> false + # + def any?(pat=NONE, &block) + if pat != NONE + self.each{|*val| return true if pat === val.__svalue} + elsif block + self.each{|*val| return true if block.call(*val)} + else + self.each{|*val| return true if val.__svalue} + end + false + end + ## # call-seq: # enum.each_with_object(obj) { |(*args), memo_obj| ... } -> obj diff --git a/mrbgems/mruby-enum-ext/test/enum.rb b/mrbgems/mruby-enum-ext/test/enum.rb index e772f85bf..7b9989861 100644 --- a/mrbgems/mruby-enum-ext/test/enum.rb +++ b/mrbgems/mruby-enum-ext/test/enum.rb @@ -100,6 +100,7 @@ end assert("Enumerable#none?") do assert_true %w(ant bear cat).none? { |word| word.length == 5 } assert_false %w(ant bear cat).none? { |word| word.length >= 4 } + assert_false [1, 3.14, 42].none?(Float) assert_true [].none? assert_true [nil, false].none? assert_false [nil, true].none? @@ -109,8 +110,21 @@ assert("Enumerable#one?") do assert_true %w(ant bear cat).one? { |word| word.length == 4 } assert_false %w(ant bear cat).one? { |word| word.length > 4 } assert_false %w(ant bear cat).one? { |word| word.length < 4 } + assert_true [1, 3.14, 42].one?(Float) assert_false [nil, true, 99].one? assert_true [nil, true, false].one? + assert_true [ nil, true, 99 ].one?(Integer) + assert_false [].one? +end + +assert("Enumerable#all? (enhancement)") do + assert_false [1, 2, 3.14].all?(Integer) + assert_true [1, 2, 3.14].all?(Numeric) +end + +assert("Enumerable#any? (enhancement)") do + assert_false [1, 2, 3].all?(Float) + assert_true [nil, true, 99].any?(Integer) end assert("Enumerable#each_with_object") do diff --git a/mrbgems/mruby-hash-ext/mrblib/hash.rb b/mrbgems/mruby-hash-ext/mrblib/hash.rb index d1a709325..549bca0a8 100644 --- a/mrbgems/mruby-hash-ext/mrblib/hash.rb +++ b/mrbgems/mruby-hash-ext/mrblib/hash.rb @@ -165,7 +165,7 @@ class Hash elsif none != NONE none else - raise KeyError, "Key not found: #{key}" + raise KeyError, "Key not found: #{key.inspect}" end else self[key] diff --git a/mrbgems/mruby-hash-ext/src/hash-ext.c b/mrbgems/mruby-hash-ext/src/hash-ext.c index 952f2eb64..6619f5268 100644 --- a/mrbgems/mruby-hash-ext/src/hash-ext.c +++ b/mrbgems/mruby-hash-ext/src/hash-ext.c @@ -72,6 +72,45 @@ hash_compact_bang(mrb_state *mrb, mrb_value hash) return hash; } +/* + * call-seq: + * hsh.slice(*keys) -> a_hash + * + * Returns a hash containing only the given keys and their values. + * + * h = { a: 100, b: 200, c: 300 } + * h.slice(:a) #=> {:a=>100} + * h.slice(:b, :c, :d) #=> {:b=>200, :c=>300} + */ +static mrb_value +hash_slice(mrb_state *mrb, mrb_value hash) +{ + khash_t(ht) *h = RHASH_TBL(hash); + mrb_value *argv, result; + mrb_int argc, i; + khiter_t k; + int ai; + + mrb_get_args(mrb, "*", &argv, &argc); + if (argc == 0 || h == NULL) { + return mrb_hash_new_capa(mrb, argc); + } + result = mrb_hash_new_capa(mrb, argc); + ai = mrb_gc_arena_save(mrb); + for (i = 0; i < argc; i++) { + mrb_value key = argv[i]; + + k = kh_get(ht, mrb, h, key); + if (k != kh_end(h)) { + mrb_value val = kh_value(h, k).v; + + mrb_hash_set(mrb, result, key, val); + } + mrb_gc_arena_restore(mrb, ai); + } + return result; +} + void mrb_mruby_hash_ext_gem_init(mrb_state *mrb) { @@ -80,6 +119,7 @@ mrb_mruby_hash_ext_gem_init(mrb_state *mrb) h = mrb->hash_class; mrb_define_method(mrb, h, "values_at", hash_values_at, MRB_ARGS_ANY()); mrb_define_method(mrb, h, "compact!", hash_compact_bang, MRB_ARGS_NONE()); + mrb_define_method(mrb, h, "slice", hash_slice, MRB_ARGS_ANY()); } void diff --git a/mrbgems/mruby-hash-ext/test/hash.rb b/mrbgems/mruby-hash-ext/test/hash.rb index ca4e346fb..269da800d 100644 --- a/mrbgems/mruby-hash-ext/test/hash.rb +++ b/mrbgems/mruby-hash-ext/test/hash.rb @@ -271,24 +271,30 @@ end assert("Hash#transform_keys") do h = {"1" => 100, "2" => 200} - assert_equal(h.transform_keys{|k| k+"!"}, - {"1!" => 100, "2!" => 200}) - assert_equal(h.transform_keys{|k|k.to_i}, - {1 => 100, 2 => 200}) - assert_equal(h.transform_keys.with_index{|k, i| "#{k}.#{i}"}, - {"1.0" => 100, "2.1" => 200}) - assert_equal(h.transform_keys!{|k|k.to_i}, h) + assert_equal({"1!" => 100, "2!" => 200}, + h.transform_keys{|k| k+"!"}) + assert_equal({1 => 100, 2 => 200}, + h.transform_keys{|k|k.to_i}) + assert_equal({"1.0" => 100, "2.1" => 200}, + h.transform_keys.with_index{|k, i| "#{k}.#{i}"}) + assert_equal(h, h.transform_keys!{|k|k.to_i}) assert_equal(h, {1 => 100, 2 => 200}) end assert("Hash#transform_values") do h = {a: 1, b: 2, c: 3} - assert_equal(h.transform_values{|v| v * v + 1}, - {a: 2, b: 5, c: 10}) - assert_equal(h.transform_values{|v|v.to_s}, - {a: "1", b: "2", c: "3"}) - assert_equal(h.transform_values.with_index{|v, i| "#{v}.#{i}"}, - {a: "1.0", b: "2.1", c: "3.2"}) - assert_equal(h.transform_values!{|v|v.to_s}, h) - assert_equal(h, {a: "1", b: "2", c: "3"}) + assert_equal({a: 2, b: 5, c: 10}, + h.transform_values{|v| v * v + 1}) + assert_equal({a: "1", b: "2", c: "3"}, + h.transform_values{|v|v.to_s}) + assert_equal({a: "1.0", b: "2.1", c: "3.2"}, + h.transform_values.with_index{|v, i| "#{v}.#{i}"}) + assert_equal(h, h.transform_values!{|v|v.to_s}) + assert_equal({a: "1", b: "2", c: "3"}, h) +end + +assert("Hash#slice") do + h = { a: 100, b: 200, c: 300 } + assert_equal({:a=>100}, h.slice(:a)) + assert_equal({:b=>200, :c=>300}, h.slice(:b, :c, :d)) end diff --git a/mrbgems/mruby-io/src/file.c b/mrbgems/mruby-io/src/file.c index 1018e90e6..3dcfe3a0b 100644 --- a/mrbgems/mruby-io/src/file.c +++ b/mrbgems/mruby-io/src/file.c @@ -332,6 +332,20 @@ mrb_file__gethome(mrb_state *mrb, mrb_value klass) #endif } +static mrb_value +mrb_file_mtime(mrb_state *mrb, mrb_value self) +{ + mrb_value obj; + struct stat st; + int fd; + + obj = mrb_obj_value(mrb_class_get(mrb, "Time")); + fd = (int)mrb_fixnum(mrb_io_fileno(mrb, self)); + if (fstat(fd, &st) == -1) + return mrb_false_value(); + return mrb_funcall(mrb, obj, "at", 1, mrb_float_value(mrb, st.st_mtime)); +} + mrb_value mrb_file_flock(mrb_state *mrb, mrb_value self) { @@ -471,6 +485,7 @@ mrb_init_file(mrb_state *mrb) mrb_define_class_method(mrb, file, "_gethome", mrb_file__gethome, MRB_ARGS_OPT(1)); mrb_define_method(mrb, file, "flock", mrb_file_flock, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, file, "mtime", mrb_file_mtime, MRB_ARGS_NONE()); cnst = mrb_define_module_under(mrb, file, "Constants"); mrb_define_const(mrb, cnst, "LOCK_SH", mrb_fixnum_value(LOCK_SH)); diff --git a/mrbgems/mruby-io/src/io.c b/mrbgems/mruby-io/src/io.c index 21a733b00..58bcdd1ee 100644 --- a/mrbgems/mruby-io/src/io.c +++ b/mrbgems/mruby-io/src/io.c @@ -180,7 +180,7 @@ mrb_io_flags_to_modenum(mrb_state *mrb, int flags) return modenum; } -void +static void mrb_fd_cloexec(mrb_state *mrb, int fd) { #if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) @@ -188,7 +188,8 @@ mrb_fd_cloexec(mrb_state *mrb, int fd) flags = fcntl(fd, F_GETFD); if (flags == -1) { - mrb_sys_fail(mrb, "fcntl"); + mrb_bug(mrb, "mrb_fd_cloexec: fcntl(%S, F_GETFD) failed: %S", + mrb_fixnum_value(fd), mrb_fixnum_value(errno)); } if (fd <= 2) { flags2 = flags & ~FD_CLOEXEC; /* Clear CLOEXEC for standard file descriptors: 0, 1, 2. */ @@ -198,7 +199,8 @@ mrb_fd_cloexec(mrb_state *mrb, int fd) } if (flags != flags2) { if (fcntl(fd, F_SETFD, flags2) == -1) { - mrb_sys_fail(mrb, "fcntl"); + mrb_bug(mrb, "mrb_fd_cloexec: fcntl(%S, F_SETFD, %S) failed: %S", + mrb_fixnum_value(fd), mrb_fixnum_value(flags2), mrb_fixnum_value(errno)); } } #endif @@ -577,11 +579,17 @@ mrb_io_initialize_copy(mrb_state *mrb, mrb_value copy) if (failed) { mrb_sys_fail(mrb, 0); } - fptr_copy->fd2 = mrb_dup(mrb, fptr_orig->fd2, &failed); - if (failed) { - close(fptr_copy->fd); - mrb_sys_fail(mrb, 0); + mrb_fd_cloexec(mrb, fptr_copy->fd); + + if (fptr_orig->fd2 != -1) { + fptr_copy->fd2 = mrb_dup(mrb, fptr_orig->fd2, &failed); + if (failed) { + close(fptr_copy->fd); + mrb_sys_fail(mrb, 0); + } + mrb_fd_cloexec(mrb, fptr_copy->fd2); } + fptr_copy->pid = fptr_orig->pid; fptr_copy->readable = fptr_orig->readable; fptr_copy->writable = fptr_orig->writable; @@ -676,7 +684,7 @@ fptr_finalize(mrb_state *mrb, struct mrb_io *fptr, int quiet) io_set_process_status(mrb, pid, status); } #else - HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, fptr->pid); + HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, fptr->pid); DWORD status; if (WaitForSingleObject(h, INFINITE) && GetExitCodeProcess(h, &status)) if (!quiet) diff --git a/mrbgems/mruby-io/test/file.rb b/mrbgems/mruby-io/test/file.rb index e5c127746..dc6fe369a 100644 --- a/mrbgems/mruby-io/test/file.rb +++ b/mrbgems/mruby-io/test/file.rb @@ -54,7 +54,7 @@ assert('File.extname') do assert_equal '', File.extname('.foo') end -assert('IO#flock') do +assert('File#flock') do f = File.open $mrbtest_io_rfname begin assert_equal(f.flock(File::LOCK_SH), 0) @@ -68,6 +68,22 @@ assert('IO#flock') do end end +assert('File#mtime') do + unless Object.const_defined?(:Time) + skip "File#mtime require Time" + end + begin + now = Time.now.to_i + mt = 0 + File.open('mtime-test', 'w') do |f| + mt = f.mtime.to_i + end + assert_equal true, mt >= now + ensure + File.delete('mtime-test') + end +end + assert('File.join') do assert_equal "", File.join() assert_equal "a", File.join("a") diff --git a/mrbgems/mruby-io/test/io.rb b/mrbgems/mruby-io/test/io.rb index 96c3495d0..e06b14996 100644 --- a/mrbgems/mruby-io/test/io.rb +++ b/mrbgems/mruby-io/test/io.rb @@ -216,6 +216,10 @@ assert('IO#dup for readable') do dup = io.dup assert_true io != dup assert_true io.fileno != dup.fileno + begin + assert_true dup.close_on_exec? + rescue NotImplementedError + end assert_equal 'm', dup.sysread(1) assert_equal 'r', io.sysread(1) assert_equal 'u', dup.sysread(1) diff --git a/mrbgems/mruby-numeric-ext/src/numeric_ext.c b/mrbgems/mruby-numeric-ext/src/numeric_ext.c index f71236a32..1d6a07769 100644 --- a/mrbgems/mruby-numeric-ext/src/numeric_ext.c +++ b/mrbgems/mruby-numeric-ext/src/numeric_ext.c @@ -1,13 +1,34 @@ #include <limits.h> #include <mruby.h> +static inline mrb_int +to_int(mrb_value x) +{ + double f; + + if (mrb_fixnum_p(x)) return mrb_fixnum(x); + f = mrb_float(x); + return (mrb_int)f; +} + +/* + * Document-method: Integer#chr + * call-seq: + * int.chr -> string + * + * Returns a string containing the character represented by the +int+'s value + * according to +encoding+. + * + * 65.chr #=> "A" + * 230.chr #=> "\xE6" + */ static mrb_value mrb_int_chr(mrb_state *mrb, mrb_value x) { mrb_int chr; char c; - chr = mrb_fixnum(x); + chr = to_int(x); if (chr >= (1 << CHAR_BIT)) { mrb_raisef(mrb, E_RANGE_ERROR, "%S out of char range", x); } @@ -16,12 +37,63 @@ mrb_int_chr(mrb_state *mrb, mrb_value x) return mrb_str_new(mrb, &c, 1); } +/* + * call-seq: + * int.allbits?(mask) -> true or false + * + * Returns +true+ if all bits of <code>+int+ & +mask+</code> are 1. + */ +static mrb_value +mrb_int_allbits(mrb_state *mrb, mrb_value self) +{ + mrb_int n, m; + + n = to_int(self); + mrb_get_args(mrb, "i", &m); + return mrb_bool_value((n & m) == m); +} + +/* + * call-seq: + * int.anybits?(mask) -> true or false + * + * Returns +true+ if any bits of <code>+int+ & +mask+</code> are 1. + */ +static mrb_value +mrb_int_anybits(mrb_state *mrb, mrb_value self) +{ + mrb_int n, m; + + n = to_int(self); + mrb_get_args(mrb, "i", &m); + return mrb_bool_value((n & m) != 0); +} + +/* + * call-seq: + * int.nobits?(mask) -> true or false + * + * Returns +true+ if no bits of <code>+int+ & +mask+</code> are 1. + */ +static mrb_value +mrb_int_nobits(mrb_state *mrb, mrb_value self) +{ + mrb_int n, m; + + n = to_int(self); + mrb_get_args(mrb, "i", &m); + return mrb_bool_value((n & m) == 0); +} + void mrb_mruby_numeric_ext_gem_init(mrb_state* mrb) { - struct RClass *i = mrb_class_get(mrb, "Integer"); + struct RClass *i = mrb_module_get(mrb, "Integral"); mrb_define_method(mrb, i, "chr", mrb_int_chr, MRB_ARGS_NONE()); + mrb_define_method(mrb, i, "allbits?", mrb_int_allbits, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, i, "anybits?", mrb_int_anybits, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, i, "nobits?", mrb_int_nobits, MRB_ARGS_REQ(1)); } void diff --git a/mrbgems/mruby-pack/src/pack.c b/mrbgems/mruby-pack/src/pack.c index ceb862d3f..3a7489f56 100644 --- a/mrbgems/mruby-pack/src/pack.c +++ b/mrbgems/mruby-pack/src/pack.c @@ -833,9 +833,10 @@ pack_x(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, long count, u { long i; + if (count < 0) return 0; dst = str_len_ensure(mrb, dst, didx + count); - for (i = 0; i < count; i++) { - RSTRING_PTR(dst)[didx] = '\0'; + for (i = didx; i < count; i++) { + RSTRING_PTR(dst)[i] = '\0'; } return count; } @@ -843,6 +844,7 @@ pack_x(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, long count, u static int unpack_x(mrb_state *mrb, const void *src, int slen, mrb_value ary, int count, unsigned int flags) { + if (count < 0) return slen; if (slen < count) { mrb_raise(mrb, E_ARGUMENT_ERROR, "x outside of string"); } @@ -1050,6 +1052,9 @@ alias: count = ch - '0'; while (tmpl->idx < tlen && isdigit(tptr[tmpl->idx])) { count = count * 10 + (tptr[tmpl->idx++] - '0'); + if (count < 0) { + mrb_raisef(mrb, E_RUNTIME_ERROR, "too big template length"); + } } continue; /* special case */ } else if (ch == '*') { diff --git a/mrbgems/mruby-socket/mrbgem.rake b/mrbgems/mruby-socket/mrbgem.rake index dcb1f77a2..8096815eb 100644 --- a/mrbgems/mruby-socket/mrbgem.rake +++ b/mrbgems/mruby-socket/mrbgem.rake @@ -11,7 +11,7 @@ MRuby::Gem::Specification.new('mruby-socket') do |spec| spec.linker.libraries << "ws2_32" end - spec.add_dependency('mruby-io') - spec.add_dependency('mruby-pack') + spec.add_dependency('mruby-io', :core => 'mruby-io') + spec.add_dependency('mruby-pack', :core => 'mruby-pack') # spec.add_dependency('mruby-mtest') end diff --git a/mrbgems/mruby-socket/test/socket.rb b/mrbgems/mruby-socket/test/socket.rb index 41f5d1cbe..aa893588f 100644 --- a/mrbgems/mruby-socket/test/socket.rb +++ b/mrbgems/mruby-socket/test/socket.rb @@ -15,7 +15,7 @@ assert('Socket::getaddrinfo') do assert_equal "127.0.0.1", a[3] assert_equal Socket::AF_INET, a[4] assert_equal Socket::SOCK_DGRAM, a[5] - assert_equal Socket::IPPROTO_UDP, a[6] + assert_equal Socket::IPPROTO_UDP, a[6] unless SocketTest.cygwin? end assert('Socket#recvfrom') do diff --git a/mrbgems/mruby-socket/test/sockettest.c b/mrbgems/mruby-socket/test/sockettest.c index 835606141..086bc4892 100644 --- a/mrbgems/mruby-socket/test/sockettest.c +++ b/mrbgems/mruby-socket/test/sockettest.c @@ -2,18 +2,54 @@ #include <stdlib.h> #include "mruby.h" +#include "mruby/error.h" + +#if defined(_WIN32) || defined(_WIN64) + +#include <io.h> + +#ifdef _MSC_VER + +#include <fcntl.h> +#include <sys/stat.h> +#define close _close +#define unlink _unlink + +static int +mkstemp(char *p) +{ + int fd; + char* fname = _mktemp(p); + if (fname == NULL) + return -1; + fd = open(fname, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE); + if (fd >= 0) + return fd; + return -1; +} +#endif + +#else + +#include <unistd.h> -#ifdef _WIN32 - #define tempnam _tempnam #endif mrb_value mrb_sockettest_tmppath(mrb_state *mrb, mrb_value klass) { - char *tmp = tempnam(NULL, "mruby-socket"); - mrb_value str = mrb_str_new_cstr(mrb, tmp); - free(tmp); - return str; + char name[] = "mruby-socket.XXXXXXXX"; + int fd = mkstemp(name); + if (fd == -1) { + mrb_sys_fail(mrb, 0); + } + if (close(fd) == -1) { + mrb_sys_fail(mrb, 0); + } + if (unlink(name) == -1) { + mrb_sys_fail(mrb, 0); + } + return mrb_str_new_cstr(mrb, name); } mrb_value @@ -26,10 +62,21 @@ mrb_sockettest_win_p(mrb_state *mrb, mrb_value klass) #endif } +mrb_value +mrb_sockettest_cygwin_p(mrb_state *mrb, mrb_value klass) +{ +#if defined(__CYGWIN__) || defined(__CYGWIN32__) + return mrb_true_value(); +#else + return mrb_false_value(); +#endif +} + void mrb_mruby_socket_gem_test(mrb_state* mrb) { struct RClass *c = mrb_define_module(mrb, "SocketTest"); mrb_define_class_method(mrb, c, "tmppath", mrb_sockettest_tmppath, MRB_ARGS_NONE()); mrb_define_class_method(mrb, c, "win?", mrb_sockettest_win_p, MRB_ARGS_NONE()); + mrb_define_class_method(mrb, c, "cygwin?", mrb_sockettest_cygwin_p, MRB_ARGS_NONE()); } diff --git a/mrbgems/mruby-socket/test/unix.rb b/mrbgems/mruby-socket/test/unix.rb index 2a29ddae7..4a88fba21 100644 --- a/mrbgems/mruby-socket/test/unix.rb +++ b/mrbgems/mruby-socket/test/unix.rb @@ -1,4 +1,4 @@ -unless SocketTest.win? +unless SocketTest.win? || SocketTest.cygwin? def unixserver_test_block path = SocketTest.tmppath diff --git a/mrbgems/mruby-time/src/time.c b/mrbgems/mruby-time/src/time.c index e6c6b9904..cfd51ac63 100644 --- a/mrbgems/mruby-time/src/time.c +++ b/mrbgems/mruby-time/src/time.c @@ -67,6 +67,11 @@ double round(double x) { /* define following macro to use probably faster timegm() on the platform */ /* #define USE_SYSTEM_TIMEGM */ +/* time_t */ +/* If your platform supports time_t as uint (e.g. uint32_t, uint64_t), */ +/* uncomment following macro. */ +/* #define MRB_TIME_T_UINT */ + /** end of Time class configuration */ #ifndef NO_GETTIMEOFDAY @@ -138,8 +143,14 @@ timegm(struct tm *tm) int i; unsigned int *nday = (unsigned int*) ndays[is_leapyear(tm->tm_year+1900)]; - for (i = 70; i < tm->tm_year; ++i) - r += is_leapyear(i+1900) ? 366*24*60*60 : 365*24*60*60; + static const int epoch_year = 70; + if(tm->tm_year >= epoch_year) { + for (i = epoch_year; i < tm->tm_year; ++i) + r += is_leapyear(i+1900) ? 366*24*60*60 : 365*24*60*60; + } else { + for (i = tm->tm_year; i < epoch_year; ++i) + r -= is_leapyear(i+1900) ? 366*24*60*60 : 365*24*60*60; + } for (i = 0; i < tm->tm_mon; ++i) r += nday[i] * 24 * 60 * 60; r += (tm->tm_mday - 1) * 24 * 60 * 60; @@ -234,13 +245,21 @@ time_alloc(mrb_state *mrb, double sec, double usec, enum mrb_timezone timezone) mrb_check_num_exact(mrb, (mrb_float)sec); mrb_check_num_exact(mrb, (mrb_float)usec); - +#ifndef MRB_TIME_T_UINT if (sizeof(time_t) == 4 && (sec > (double)INT32_MAX || (double)INT32_MIN > sec)) { goto out_of_range; } if (sizeof(time_t) == 8 && (sec > (double)INT64_MAX || (double)INT64_MIN > sec)) { goto out_of_range; } +#else + if (sizeof(time_t) == 4 && (sec > (double)UINT32_MAX || (double)0 > sec)) { + goto out_of_range; + } + if (sizeof(time_t) == 8 && (sec > (double)UINT64_MAX || (double)0 > sec)) { + goto out_of_range; + } +#endif tsec = (time_t)sec; if ((sec > 0 && tsec < 0) || (sec < 0 && (double)tsec > sec)) { out_of_range: diff --git a/mrbgems/mruby-time/test/time.rb b/mrbgems/mruby-time/test/time.rb index 52b931177..54c446ca3 100644 --- a/mrbgems/mruby-time/test/time.rb +++ b/mrbgems/mruby-time/test/time.rb @@ -226,3 +226,7 @@ assert('2000 times 500us make a second') do end t.usec == 0 end + +assert('Time.gm with Dec 31 23:59:59 1969 raise ArgumentError') do + assert_raise(ArgumentError) {Time.gm(1969, 12, 31, 23, 59, 59)} +end diff --git a/src/class.c b/src/class.c index 132cf0176..3f24528ca 100644 --- a/src/class.c +++ b/src/class.c @@ -1744,7 +1744,7 @@ mrb_class_path(mrb_state *mrb, struct RClass *c) return mrb_class_find_path(mrb, c); } else if (mrb_symbol_p(path)) { - /* topleve class/module */ + /* toplevel class/module */ const char *str; mrb_int len; diff --git a/src/error.c b/src/error.c index 1b24f3065..5445b51bf 100644 --- a/src/error.c +++ b/src/error.c @@ -229,7 +229,8 @@ mrb_exc_set(mrb_state *mrb, mrb_value exc) } else { mrb->exc = mrb_obj_ptr(exc); - if ((struct RBasic*)mrb->exc == mrb->gc.arena[mrb->gc.arena_idx-1]) { + if (mrb->gc.arena_idx > 0 && + (struct RBasic*)mrb->exc == mrb->gc.arena[mrb->gc.arena_idx-1]) { mrb->gc.arena_idx--; } if (!mrb->gc.out_of_memory) { @@ -658,7 +658,7 @@ gc_mark_children(mrb_state *mrb, mrb_gc *gc, struct RBasic *obj) struct REnv *e = (struct REnv*)obj; mrb_int i, len; - if (MRB_ENV_STACK_SHARED_P(e) && e->cxt->fib) { + if (MRB_ENV_STACK_SHARED_P(e) && e->cxt && e->cxt->fib) { mrb_gc_mark(mrb, (struct RBasic*)e->cxt->fib); } len = MRB_ENV_STACK_LEN(e); diff --git a/src/kernel.c b/src/kernel.c index 1ac49ed04..e9dc93bbd 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -956,26 +956,7 @@ mrb_obj_remove_instance_variable(mrb_state *mrb, mrb_value self) void mrb_method_missing(mrb_state *mrb, mrb_sym name, mrb_value self, mrb_value args) { - mrb_sym inspect; - mrb_value repr; - - inspect = mrb_intern_lit(mrb, "inspect"); - if (mrb->c->ci > mrb->c->cibase && mrb->c->ci[-1].mid == inspect) { - /* method missing in inspect; avoid recursion */ - repr = mrb_any_to_s(mrb, self); - } - else if (mrb_respond_to(mrb, self, inspect) && mrb->c->ci - mrb->c->cibase < 16) { - repr = mrb_funcall_argv(mrb, self, inspect, 0, 0); - if (mrb_string_p(repr) && RSTRING_LEN(repr) > 64) { - repr = mrb_any_to_s(mrb, self); - } - } - else { - repr = mrb_any_to_s(mrb, self); - } - - mrb_no_method_error(mrb, name, args, "undefined method '%S' for %S", - mrb_sym2str(mrb, name), repr); + mrb_no_method_error(mrb, name, args, "undefined method '%S'", mrb_sym2str(mrb, name)); } /* 15.3.1.3.30 */ diff --git a/src/variable.c b/src/variable.c index 968fc2fc1..de36efac6 100644 --- a/src/variable.c +++ b/src/variable.c @@ -955,27 +955,61 @@ find_class_sym(mrb_state *mrb, struct RClass *outer, struct RClass *c) return arg.sym; } +static struct RClass* +outer_class(mrb_state *mrb, struct RClass *c) +{ + mrb_value ov; + + ov = mrb_obj_iv_get(mrb, (struct RObject*)c, mrb_intern_lit(mrb, "__outer__")); + if (mrb_nil_p(ov)) return NULL; + switch (mrb_type(ov)) { + case MRB_TT_CLASS: + case MRB_TT_MODULE: + return mrb_class_ptr(ov); + default: + break; + } + return NULL; +} + +static mrb_bool +detect_outer_loop(mrb_state *mrb, struct RClass *c) +{ + struct RClass *t = c; /* tortoise */ + struct RClass *h = c; /* hare */ + + for (;;) { + if (h == NULL) return FALSE; + h = outer_class(mrb, h); + if (h == NULL) return FALSE; + h = outer_class(mrb, h); + t = outer_class(mrb, t); + if (t == h) return TRUE; + } +} + mrb_value mrb_class_find_path(mrb_state *mrb, struct RClass *c) { - mrb_value outer, path; + struct RClass *outer; + mrb_value path; mrb_sym name; const char *str; mrb_int len; - mrb_sym osym = mrb_intern_lit(mrb, "__outer__"); - outer = mrb_obj_iv_get(mrb, (struct RObject*)c, osym); - if (mrb_nil_p(outer)) return outer; - name = find_class_sym(mrb, mrb_class_ptr(outer), c); + if (detect_outer_loop(mrb, c)) return mrb_nil_value(); + outer = outer_class(mrb, c); + if (outer == NULL) return mrb_nil_value(); + name = find_class_sym(mrb, outer, c); if (name == 0) return mrb_nil_value(); - str = mrb_class_name(mrb, mrb_class_ptr(outer)); + str = mrb_class_name(mrb, outer); path = mrb_str_new_capa(mrb, 40); mrb_str_cat_cstr(mrb, path, str); mrb_str_cat_cstr(mrb, path, "::"); str = mrb_sym2name_len(mrb, name, &len); mrb_str_cat(mrb, path, str, len); - iv_del(mrb, c->iv, osym, NULL); + iv_del(mrb, c->iv, mrb_intern_lit(mrb, "__outer__"), NULL); iv_put(mrb, c->iv, mrb_intern_lit(mrb, "__classname__"), path); mrb_field_write_barrier_value(mrb, (struct RBasic*)c, path); return path; @@ -455,7 +455,7 @@ mrb_funcall_with_block(mrb_state *mrb, mrb_value self, mrb_sym mid, mrb_int argc } else if (argc >= CALL_MAXARGS) { mrb_value args = mrb_ary_new_from_values(mrb, argc, argv); - stack_extend(mrb, ci->nregs); + stack_extend(mrb, ci->nregs+2); mrb->c->stack[1] = args; ci->argc = -1; argc = 1; @@ -511,7 +511,6 @@ mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p) mrb->c->stack[0] = self; ci->proc = p; - ci->target_class = MRB_PROC_TARGET_CLASS(p); if (MRB_PROC_CFUNC_P(p)) { return MRB_PROC_CFUNC(p)(mrb, self); } @@ -1352,6 +1351,7 @@ RETRY_TRY_BLOCK: for (n=0; n<a; n++) { proc = mrb->c->ensure[epos+n]; mrb->c->ensure[epos+n] = NULL; + if (proc == NULL) continue; irep = proc->body.irep; ci = cipush(mrb); ci->mid = ci[-1].mid; @@ -1420,7 +1420,7 @@ RETRY_TRY_BLOCK: if (MRB_METHOD_UNDEF_P(m)) { mrb_sym missing = mrb_intern_lit(mrb, "method_missing"); m = mrb_method_search_vm(mrb, &c, missing); - if (MRB_METHOD_UNDEF_P(m)) { + if (MRB_METHOD_UNDEF_P(m) || (missing == mrb->c->ci->mid && mrb_obj_eq(mrb, regs[0], recv))) { mrb_value args = (argc < 0) ? regs[a+1] : mrb_ary_new_from_values(mrb, n, regs+a+1); ERR_PC_SET(mrb, pc); mrb_method_missing(mrb, mid, recv, args); @@ -1607,6 +1607,12 @@ RETRY_TRY_BLOCK: } } recv = regs[0]; + if (!mrb_obj_is_kind_of(mrb, recv, target_class)) { + mrb_value exc = mrb_exc_new_str_lit(mrb, E_TYPE_ERROR, + "self has wrong type to call super in this context"); + mrb_exc_set(mrb, exc); + goto L_RAISE; + } blk = regs[bidx]; if (!mrb_nil_p(blk) && mrb_type(blk) != MRB_TT_PROC) { blk = mrb_convert_type(mrb, blk, MRB_TT_PROC, "Proc", "to_proc"); @@ -2929,9 +2935,6 @@ RETRY_TRY_BLOCK: CASE(OP_STOP) { /* stop VM */ L_STOP: - while (mrb->c->ci > mrb->c->cibase) { - cipop(mrb); - } while (mrb->c->eidx > 0) { ecall(mrb); } diff --git a/test/t/kernel.rb b/test/t/kernel.rb index 4bd102392..eaae78e47 100644 --- a/test/t/kernel.rb +++ b/test/t/kernel.rb @@ -366,48 +366,14 @@ assert('Kernel#method_missing', '15.3.1.3.30') do begin no_super_test.no_super_method_named_this rescue NoMethodError => e - assert_equal "undefined method 'no_super_method_named_this' for #{no_super_test}", e.message + assert_equal "undefined method 'no_super_method_named_this'", e.message end a = String.new begin a.no_method_named_this rescue NoMethodError => e - assert_equal "undefined method 'no_method_named_this' for \"\"", e.message - end - - class ShortInspectClass - def inspect - 'An inspect string' - end - end - b = ShortInspectClass.new - begin - b.no_method_named_this - rescue NoMethodError => e - assert_equal "undefined method 'no_method_named_this' for An inspect string", e.message - end - - class LongInspectClass - def inspect - "A" * 70 - end - end - c = LongInspectClass.new - begin - c.no_method_named_this - rescue NoMethodError => e - assert_equal "undefined method 'no_method_named_this' for #{c}", e.message - end - - class NoInspectClass - undef inspect - end - d = NoInspectClass.new - begin - d.no_method_named_this - rescue NoMethodError => e - assert_equal "undefined method 'no_method_named_this' for #{d}", e.message + assert_equal "undefined method 'no_method_named_this'", e.message end end |
