Example #1
0
 Object* rbx_cast_for_splat_block_arg(STATE, CallFrame* call_frame, Arguments& args) {
   if(args.total() == 1) {
     Object* obj = args.get_argument(0);
     if(!kind_of<Array>(obj)) {
       /* Yes, you are reading this code correctly: In Ruby 1.8, calling a
        * block with these forms { |*| } and { |*a| } with a single argument
        * that is not an Array and which responds to #to_ary will cause #to_ary
        * to be called and its return value ignored. Ultimately, the original
        * object itself is wrapped in an Array and passed to the block.
        */
       if(CBOOL(obj->respond_to(state, state->symbol("to_ary"), cFalse))) {
         OnStack<1> os(state, obj);
         Object* ignored = obj->send(state, call_frame, state->symbol("to_ary"));
         if(!ignored->nil_p() && !kind_of<Array>(ignored)) {
           Exception::type_error(state, "to_ary must return an Array", call_frame);
           return 0;
         }
       }
     }
     Array* ary = Array::create(state, 1);
     ary->set(state, 0, obj);
     return ary;
   } else {
     Array* ary = Array::create(state, args.total());
     for(size_t i = 0; i < args.total(); i++) {
       ary->set(state, i, args.get_argument(i));
     }
     return ary;
   }
 }
Example #2
0
  Object* rbx_cast_for_multi_block_arg(STATE, CallFrame* call_frame, Arguments& args) {
    /* If there is only one argument and that thing is an array...
     AND the thing being invoked is not a lambda... */
    if(!(call_frame->flags & CallFrame::cIsLambda) &&
        args.total() == 1) {
      Object* obj = args.get_argument(0);
      if(kind_of<Array>(obj)) {
        return obj;
      } else if(CBOOL(obj->respond_to(state, state->symbol("to_ary"), cFalse))) {
        obj = obj->send(state, call_frame, state->symbol("to_ary"));
        if(kind_of<Array>(obj)) {
          return obj;
        } else {
          Exception::type_error(state, "to_ary must return an Array", call_frame);
          return 0;
        }
      }
      // One argument and it's not Array or Array-ish at all, so fall through
      // and let it be wrapped in an array.
    }

    Array* ary = Array::create(state, args.total());
    assert(kind_of<Array>(ary));

    for(size_t i = 0; i < args.total(); i++) {
      assert(kind_of<Array>(ary));
      ary->set(state, i, args.get_argument(i));
    }

    assert(kind_of<Array>(ary));

    return ary;
  }
Example #3
0
  Object* rbx_destructure_args(STATE, CallFrame* call_frame, Arguments& args) {
    if(args.total() == 1) {
      Object* obj = args.get_argument(0);
      if(Array* ary = try_as<Array>(obj)) {
        args.use_array(ary);
      } else if(CBOOL(obj->respond_to(state, state->symbol("to_ary"), cFalse))) {
        obj = obj->send(state, call_frame, state->symbol("to_ary"));
        if(Array* ary2 = try_as<Array>(obj)) {
          args.use_array(ary2);
        } else {
          Exception::type_error(state, "to_ary must return an Array", call_frame);
          return 0;
        }
      }
    }

    return cNil;
  }
    inline bool cast_for_splat_block_arg(STATE, CallFrame* call_frame) {
      if(!call_frame->arguments) {
        Exception::internal_error(state, "no arguments object");
        return false;
      }

      if(call_frame->arguments->total() == 1) {
        Object* obj = call_frame->arguments->get_argument(0);
        if(!rubinius::kind_of<Array>(obj)) {
          /* Yes, you are reading this code correctly: In Ruby 1.8, calling a
           * block with these forms { |*| } and { |*a| } with a single argument
           * that is not an Array and which responds to #to_ary will cause #to_ary
           * to be called and its return value ignored. Ultimately, the original
           * object itself is wrapped in an Array and passed to the block.
           */
          if(CBOOL(obj->respond_to(state, G(sym_to_ary), cFalse))) {
            OnStack<1> os(state, obj);
            Object* ignored = obj->send(state, G(sym_to_ary));
            if(!ignored) return false;
            if(!ignored->nil_p() && !rubinius::kind_of<Array>(ignored)) {
              Exception::type_error(state, "to_ary must return an Array");
              return false;
            }
          }
        }
        Array* ary = Array::create(state, 1);
        ary->set(state, 0, obj);
        stack_push(ary);
      } else {
        Array* ary = Array::create(state, call_frame->arguments->total());
        for(size_t i = 0; i < call_frame->arguments->total(); i++) {
          ary->set(state, i, call_frame->arguments->get_argument(i));
        }
        stack_push(ary);
      }

      return true;
    }
Example #5
0
  int rbx_destructure_args(STATE, CallFrame* call_frame, Arguments& args) {
    Object* obj = args.get_argument(0);
    Array* ary = 0;

    if(!(ary = try_as<Array>(obj))) {
      if(CBOOL(obj->respond_to(state, G(sym_to_ary), cFalse))) {
        if(!(obj = obj->send(state, call_frame, G(sym_to_ary)))) {
          return -1;
        }

        if(!(ary = try_as<Array>(obj)) && !obj->nil_p()) {
          Exception::type_error(state, "to_ary must return an Array", call_frame);
          return -1;
        }
      }
    }

    if(ary) {
      args.use_array(ary);
    }

    return args.total();
  }
    inline bool cast_for_multi_block_arg(STATE, CallFrame* call_frame) {
      if(!call_frame->arguments) {
        Exception::internal_error(state, "no arguments object");
        return false;
      }

      /* If there is only one argument and that thing is an array...
         AND the thing being invoked is not a lambda... */
      if(!(call_frame->flags & CallFrame::cIsLambda) &&
           call_frame->arguments->total() == 1) {
        Object* obj = call_frame->arguments->get_argument(0);
        if(rubinius::kind_of<Array>(obj)) {
          stack_push(obj);
        } else if(CBOOL(obj->respond_to(state, G(sym_to_ary), cFalse))) {
          obj = obj->send(state, G(sym_to_ary));
          if(!obj) return false;
          if(rubinius::kind_of<Array>(obj)) {
            stack_push(obj);
          } else {
            Exception::type_error(state, "to_ary must return an Array");
            return false;
          }
        } else {
          Array* ary = Array::create(state, 1);
          ary->set(state, 0, obj);
          stack_push(ary);
        }
      } else {
        Array* ary = Array::create(state, call_frame->arguments->total());
        for(size_t i = 0; i < call_frame->arguments->total(); i++) {
          ary->set(state, i, call_frame->arguments->get_argument(i));
        }
        stack_push(ary);
      }

      return true;
    }
    static bool call(STATE, CallFrame* call_frame,
                     MachineCode* mcode, StackVariables* scope,
                     Arguments& args, int flags)
    {
      /* There are 5 types of arguments, illustrated here:
       *    m(a, b=1, *c, d, e: 2)
       *
       *  where:
       *    a is a head (pre optional/splat) fixed position argument
       *    b is an optional argument
       *    c is a rest argument
       *    d is a post (optional/splat) argument
       *    e is a keyword argument, which may be required (having no default
       *      value), optional, or keyword rest argument (**kw).
       *
       * The arity checking above ensures that we have at least one argument
       * on the stack for each fixed position argument (ie arguments a and d
       * above).
       *
       * We assign the arguments in the following order: first the fixed
       * arguments (head and post) and possibly the keyword argument, then the
       * optional arguments, and the remainder (if any) are combined in an
       * array for the rest argument.
       *
       * We assign values from the sender's side to local variables on the
       * receiver's side. Which values to assign are computed as follows:
       *
       *  sender indexes (arguments)
       *  -v-v-v-v-v-v-v-v-v-v-v-v--
       *
       *   0...H  H...H+ON  H+ON...N-P-K  N-P-K...N-K  N-K
       *   |      |         |             |            |
       *   H      O         R             P            K
       *   |      |         |             |            |
       *   0...H  H...H+O   RI            PI...PI+P    KI
       *
       *  -^-^-^-^-^-^-^-^-^-^-^-^-
       *  receiver indexes (locals)
       *
       * where:
       *
       *  arguments passed by sender
       *  --------------------------
       *    N  : total number of arguments passed
       *    H* : number of head arguments
       *    E  : number of extra arguments
       *    ON : number or arguments assigned to optional parameters
       *    RN : number of arguments assigned to the rest argument
       *    P* : number of post arguments
       *    K  : number of keyword arguments passed, 1 if the last argument is
       *         a Hash or if #to_hash returns a Hash, 0 otherwise
       *    HL : maximum number of head arguments passed
       *    PM : post arguments missing when N < M
       *
       *  parameters defined by receiver
       *  ------------------------------
       *    T  : total number of parameters
       *    M  : number of head + post parameters
       *    H* : number of head parameters
       *    O  : number of optional parameters
       *    RP : true if a rest parameter is defined, false otherwise
       *    RI : index of rest parameter if RP is true, else -1
       *    P* : number of post parameters
       *    PI : index of the first post parameter
       *    KP : true if a keyword parameter is defined, false otherwise
       *    KA : true if the keyword argument was extracted from the passed
       *         argument leaving a remaining value
       *    KI : index of keyword rest parameter
       *
       *  (*) The values of H and P are fixed and they represent the same
       *  values at both the sender and receiver, so they are named the same.
       *
       *  formulas
       *  --------
       *    K  = KP && !KA && N > M ? 1 : 0
       *    E  = N - M - K
       *    O  = T - M - (keywords ? 1 : 0)
       *    ON = (X = MIN(O, E)) > 0 ? X : 0
       *    RN = RP && (X = E - ON) > 0 ? X : 0
       *    PI = H + O + (RP ? 1 : 0)
       *    KI = RP ? T : T - 1
       *    HL = (H - N) > 0 ? MIN(N, H - N) : H
       *    PM = N - H > 0 ? P - (N - H) : P
       *
       */

      native_int N = args.total();
      const native_int T = mcode->total_args;
      const native_int M = mcode->required_args;
      const native_int O = T - M - (mcode->keywords ? 1 : 0);


      /* TODO: Clean up usage to uniformly refer to 'splat' as N arguments
       * passed from sender at a single position and 'rest' as N arguments
       * collected into a single argument at the receiver.
       */
      const native_int RI = mcode->splat_position;
      const bool RP = (RI >= 0);

      // expecting 0, got 0.
      if(T == 0 && N == 0) {
        if(RP) {
          scope->set_local(mcode->splat_position, Array::create(state, 0));
        }

        return true;
      }

      const bool lambda = ((flags & CallFrame::cIsLambda) == CallFrame::cIsLambda);

      // Only do destructuring in non-lambda mode
      if(!lambda) {
        /* If only one argument was yielded and the block takes more than one
         * argument or has form { |a, | }:
         *
         *  1. If the object is an Array, assign elements to arguments.
         *  2. If the object returns 'nil' from #to_ary, assign the object
         *     to the first argument.
         *  3. If the object returns an Array from #to_ary, assign the
         *     elements to the arguments.
         *  4. If the object returns non-Array from #to_ary, raise a TypeError.
         */
        if(N == 1 && (T > 1 || (RP && T > 0) || RI < -2)) {
          Object* obj = args.get_argument(0);
          Array* ary = 0;

          if(!(ary = try_as<Array>(obj))) {
            if(CBOOL(obj->respond_to(state, G(sym_to_ary), cFalse))) {
              if(!(obj = obj->send(state, call_frame, G(sym_to_ary)))) {
                return false;
              }

              if(!(ary = try_as<Array>(obj)) && !obj->nil_p()) {
                Exception::type_error(state, "to_ary must return an Array", call_frame);
                return false;
              }
            }
          }

          if(ary) {
            if(RI == -4 && M == 1) {
              args.use_argument(ary);
            } else {
              args.use_array(ary);
            }

            N = args.total();
          }
        }
      }

      const native_int P = mcode->post_args;
      const native_int H = M - P;

      // Too many args (no rest argument!)
      if(!RP && N > T) {
        if(lambda) return false;

        N = T;
      }

      // Too few args!
      if(lambda && N < M) return false;

      Object* kw = 0;
      Object* kw_remainder = 0;
      bool KP = false;
      bool KA = false;

      if(mcode->keywords && N > M) {
        Object* obj = args.get_argument(args.total() - 1);

        OnStack<1> os(state, obj);
        Object* arguments[2];

        arguments[0] = obj;
        arguments[1] = RBOOL(O > 0 || RP);
        Arguments args(G(sym_keyword_object), G(runtime), 2, arguments);
        Dispatch dis(G(sym_keyword_object));

        Object* kw_result = dis.send(state, call_frame, args);

        if(kw_result) {
          if(Array* ary = try_as<Array>(kw_result)) {
            Object* o = 0;

            if(!(o = ary->get(state, 0))->nil_p()) {
              kw_remainder = o;
              KA = true;
            }

            kw = ary->get(state, 1);
            KP = true;
          }
        } else {
          return false;
        }
      }

      // A single kwrest argument
      if(mcode->keywords && !RP && !KP && N >= T) {
        if(lambda) return false;

        N = T - 1;
      }

      const native_int K = (KP && !KA && N > M) ? 1 : 0;
      const native_int N_M_K = N - M - K;
      const native_int E = N_M_K > 0 ? N_M_K : 0;

      native_int X;

      const native_int ON = (X = MIN(O, E)) > 0 ? X : 0;
      const native_int RN = (RP && (X = E - ON) > 0) ? X : 0;
      const native_int PI = H + O + (RP ? 1 : 0);
      const native_int KI = RP ? T : T - 1;

      native_int a = 0;   // argument index
      native_int l = 0;   // local index

      // head arguments
      if(H > 0) {
        for(; l < H && a < N; l++, a++) {
          scope->set_local(l, args.get_argument(a));
        }

        for(; l < H; l++) {
          scope->set_local(l, cNil);
        }
      }

      // optional arguments
      if(O > 0) {
        for(; l < H + O && a < MIN(N, H + ON); l++, a++) {
          if(unlikely(kw_remainder && !RP && (a == N - 1))) {
            scope->set_local(l, kw_remainder);
          } else {
            scope->set_local(l, args.get_argument(a));
          }
        }

        for(; l < H + O; l++) {
          scope->set_local(l, G(undefined));
        }
      }

      // rest arguments
      if(RP) {
        Array* ary;

        if(RN > 0) {
          ary = Array::create(state, RN);

          for(int i = 0; i < RN && a < N - P - K; i++, a++) {
            if(unlikely(kw_remainder && (a == N - 1))) {
              ary->set(state, i, kw_remainder);
            } else {
              ary->set(state, i, args.get_argument(a));
            }
          }
        } else {
          ary = Array::create(state, 0);
        }

        scope->set_local(RI, ary);
      }

      // post arguments
      if(P > 0) {
        const native_int N_K = (X = MIN(N, N - K)) > 0 ? X : N;

        for(l = PI; l < PI + P && a < N_K; l++, a++) {
          scope->set_local(l, args.get_argument(a));
        }

        for(; l < PI + P; l++) {
          scope->set_local(l, cNil);
        }
      }

      // keywords
      if(kw) {
        scope->set_local(KI, kw);
      }

      return true;
    }
Example #8
0
  Object* Proc::call(STATE, CallFrame* call_frame, Arguments& args) {
    bool lambda_style = RTEST(lambda_);
    int flags = 0;

    // Check the arity in lambda mode
    if(lambda_style) {
      flags = CallFrame::cIsLambda;
      int total = block_->code()->total_args()->to_native();
      int required = block_->code()->required_args()->to_native();

      bool arity_ok = false;
      if(Fixnum* fix = try_as<Fixnum>(block_->code()->splat())) {
        switch(fix->to_native()) {
        case -2:
          arity_ok = true;
          break;
        case -4:
          // splat = -4 means { |(a, b)| }
          if(args.total() == 1) {
            Array* ary = 0;
            Object* obj = args.get_argument(0);

            if(!(ary = try_as<Array>(obj))) {
              if(RTEST(obj->respond_to(state, state->symbol("to_ary"), Qfalse))) {
                obj = obj->send(state, call_frame, state->symbol("to_ary"));
                if(!(ary = try_as<Array>(obj))) {
                  Exception::type_error(state, "to_ary must return an Array", call_frame);
                  return 0;
                }
              }
            }

            if(ary) args.use_argument(ary);
          }
          // fall through for arity check
        case -3:
          // splat = -3 is used to distinguish { |a, | } from { |a| }
          if(args.total() == (size_t)required) arity_ok = true;
          break;
        default:
          if(args.total() >= (size_t)required) {
            arity_ok = true;
          }
        }

      /* For blocks taking one argument { |a|  }, in 1.8, there is a warning
       * issued but no exception raised when less than or more than one
       * argument is passed. If more than one is passed, 'a' receives an Array
       * of all the arguments.
       */
      } else if(required == 1 && LANGUAGE_18_ENABLED(state)) {
        arity_ok = true;
      } else {
        arity_ok = args.total() <= (size_t)total &&
                   args.total() >= (size_t)required;
      }

      if(!arity_ok) {
        Exception* exc =
          Exception::make_argument_error(state, required, args.total(),
              state->symbol("__block__"));
        exc->locations(state, Location::from_call_stack(state, call_frame));
        state->thread_state()->raise_exception(exc);
        return NULL;
      }
    }

    Object* ret;
    if(bound_method_->nil_p()) {
      ret = block_->call(state, call_frame, args, flags);
    } else if(NativeMethod* nm = try_as<NativeMethod>(bound_method_)) {
      ret = nm->execute(state, call_frame, nm, G(object), args);
    } else {
      Dispatch dis(state->symbol("__yield__"));
      ret = dis.send(state, call_frame, args);
    }

    return ret;
  }
Example #9
0
    static bool call(STATE, CallFrame* call_frame,
                     MachineCode* mcode, StackVariables* scope,
                     Arguments& args, int flags)
    {
      const bool has_splat = (mcode->splat_position >= 0);
      native_int total_args = args.total();

      // expecting 0, got 0.
      if(mcode->total_args == 0 && total_args == 0) {
        if(has_splat) {
          scope->set_local(mcode->splat_position, Array::create(state, 0));
        }

        return true;
      }

      // Only do destructuring in non-lambda mode
      if((flags & CallFrame::cIsLambda) == 0) {
        /* If only one argument was yielded and:
         *
         *  1. the block takes two or more arguments
         *  2. OR takes one argument and a splat
         *  3. OR has the form { |a, | }
         *  4. OR has the form { |(a, b)| }
         *  5. OR has the form { |(a, b), c| }
         *
         * then we check if the one argument is an Array. If it is not, call
         * #to_ary to convert it to an Array and raise if #to_ary does not
         * return an Array.
         *
         * Finally, in cases 1-3, and 5 above, we destructure the Array into
         * the block's arguments.
         */
        if(total_args == 1
            && (mcode->required_args > 1
              || (mcode->required_args == 1
                && (has_splat || mcode->splat_position < -2)))) {
          Object* obj = args.get_argument(0);
          Array* ary = 0;

          if(!(ary = try_as<Array>(obj))) {
            if(CBOOL(obj->respond_to(state, G(sym_to_ary), cFalse))) {
              obj = obj->send(state, call_frame, G(sym_to_ary));
              if(!obj) return false;
              if(!(ary = try_as<Array>(obj))) {
                Exception::type_error(state, "to_ary must return an Array", call_frame);
                return false;
              }
            }
          }

          if(ary) {
            if(mcode->splat_position == -4 && mcode->required_args == 1) {
              args.use_argument(ary);
            } else {
              args.use_array(ary);
            }
          }
        }
      }

      const native_int P = mcode->post_args;
      const native_int R = mcode->required_args;

      // M is for mandatory
      const native_int M = R - P;
      const native_int T = args.total();

      // DT is for declared total
      const native_int DT = mcode->total_args;
      const native_int O = DT - R;

      // HS is for has splat
      const native_int HS = mcode->splat_position >= 0 ? 1 : 0;

      // CT is for clamped total
      const native_int CT = HS ? T : MIN(T, DT);

      // Z is for the available # of post args
      const native_int Z = CT - M;

      // U is for the available # of optional args
      const native_int U = Z - P;

      // PAO is for the post-args offset
      // PLO is for the post-arg locals offset
      const native_int PAO = CT - MIN(Z, P);
      const native_int PLO = M + O + HS;

      /* There are 4 types of arguments, illustrated here:
       *    m(a, b=1, *c, d)
       *
       *  where:
       *    a is a (pre optional/splat) fixed position argument
       *    b is an optional argument
       *    c is a splat argument
       *    d is a post (optional/splat) argument
       *
       *  The arity checking above ensures that we have at least one argument
       *  on the stack for each fixed position argument (ie arguments a and d
       *  above).
       *
       *  The number of (pre) fixed arguments is 'required_args - post_args'.
       *
       *  The number of optional arguments is 'total_args - required_args'.
       *
       *  We fill in the required arguments, then the optional arguments, and
       *  the rest (if any) go into an array for the splat.
       */

      // Phase 1, mandatory args
      for(native_int i = 0, l = MIN(M,T);
          i < l;
          i++)
      {
        scope->set_local(i, args.get_argument(i));
      }

      // Phase 2, post args
      for(native_int i = 0; i < MIN(Z, P); i++)
      {
        scope->set_local(PLO + i, args.get_argument(PAO + i));
      }

      // Phase 3, optionals

      for(native_int i = M, limit = M + MIN(U, O);
          i < limit;
          i++)
      {
        scope->set_local(i, args.get_argument(i));
      }


      if(has_splat) {
        Array* ary;
        /* There is a splat. So if the passed in arguments are greater
         * than the total number of fixed arguments, put the rest of the
         * arguments into the Array.
         *
         * Otherwise, generate an empty Array.
         *
         * NOTE: remember that total includes the number of fixed arguments,
         * even if they're optional, so we can get args.total() == 0, and
         * total == 1 */
        int splat_size = T - DT;
        if(splat_size > 0) {
          ary = Array::create(state, splat_size);

          for(int i = 0, n = M + O;
              i < splat_size;
              i++, n++)
          {
            ary->set(state, i, args.get_argument(n));
          }
        } else {
          ary = Array::create(state, 0);
        }

        scope->set_local(mcode->splat_position, ary);
      }

      return true;
    }
Example #10
0
  Object* Proc::call(STATE, CallFrame* call_frame, Arguments& args) {
    bool lambda_style = CBOOL(lambda_);
    int flags = 0;

    Proc* self = this;
    OnStack<1> os(state, self);
    // Check the arity in lambda mode
    if(lambda_style && !block_->nil_p()) {
      flags = CallFrame::cIsLambda;
      int total = self->block_->compiled_code()->total_args()->to_native();
      int required = self->block_->compiled_code()->required_args()->to_native();

      bool arity_ok = false;
      if(Fixnum* fix = try_as<Fixnum>(self->block_->compiled_code()->splat())) {
        switch(fix->to_native()) {
        case -2:
          arity_ok = true;
          break;
        case -4:
          // splat = -4 means { |(a, b)| }
          if(args.total() == 1) {
            Array* ary = 0;
            Object* obj = args.get_argument(0);

            if(!(ary = try_as<Array>(obj))) {
              if(CBOOL(obj->respond_to(state, G(sym_to_ary), cFalse))) {
                obj = obj->send(state, call_frame, G(sym_to_ary));
                if(!(ary = try_as<Array>(obj))) {
                  Exception::type_error(state, "to_ary must return an Array", call_frame);
                  return 0;
                }
              }
            }

            if(ary) args.use_argument(ary);
          }
          // fall through for arity check
        case -3:
          // splat = -3 is used to distinguish { |a, | } from { |a| }
          if(args.total() == (size_t)required) arity_ok = true;
          break;
        default:
          if(args.total() >= (size_t)required) {
            arity_ok = true;
          }
        }

      } else {
        arity_ok = args.total() <= (size_t)total &&
                   args.total() >= (size_t)required;
      }

      if(!arity_ok) {
        Exception* exc =
          Exception::make_argument_error(state, required, args.total(),
              block_->compiled_code()->name());
        exc->locations(state, Location::from_call_stack(state, call_frame));
        state->raise_exception(exc);
        return NULL;
      }
    }

    if(self->bound_method_->nil_p()) {
      if(self->block_->nil_p()) {
        Dispatch dis(state->symbol("__yield__"));
        return dis.send(state, call_frame, args);
      } else {
        return self->block_->call(state, call_frame, args, flags);
      }
    } else if(NativeMethod* nm = try_as<NativeMethod>(self->bound_method_)) {
      return nm->execute(state, call_frame, nm, G(object), args);
    } else if(NativeFunction* nf = try_as<NativeFunction>(self->bound_method_)) {
      return nf->call(state, args, call_frame);
    } else {
      Exception* exc =
        Exception::make_type_error(state, BlockEnvironment::type, self->bound_method_, "NativeMethod nor NativeFunction bound to proc");
      exc->locations(state, Location::from_call_stack(state, call_frame));
      state->raise_exception(exc);
      return NULL;
    }
  }