*/ void Do_Command(REBVAL *value) /* ** Evaluates the arguments for a command function and creates ** a resulting stack frame (struct or object) for command processing. ** ** A command value consists of: ** args - same as other funcs ** spec - same as other funcs ** body - [ext-obj func-index] ** ***********************************************************************/ { REBVAL *val = BLK_HEAD(VAL_FUNC_BODY(value)); REBEXT *ext; REBCNT cmd; REBCNT argc; REBCNT n; RXIFRM frm; // args stored here // All of these were checked above on definition: val = BLK_HEAD(VAL_FUNC_BODY(value)); cmd = (int)VAL_INT64(val+1); ext = &Ext_List[VAL_I32(VAL_OBJ_VALUE(val, 1))]; // Handler // Copy args to command frame (array of args): RXA_COUNT(&frm) = argc = SERIES_TAIL(VAL_FUNC_ARGS(value))-1; // not self if (argc > 7) Trap0(RE_BAD_COMMAND); val = DS_ARG(1); for (n = 1; n <= argc; n++, val++) { RXA_TYPE(&frm, n) = Reb_To_RXT[VAL_TYPE(val)]; frm.args[n] = Value_To_RXI(val); } // Call the command: n = ext->call(cmd, &frm, 0); val = DS_RETURN; switch (n) { case RXR_VALUE: RXI_To_Value(val, frm.args[1], RXA_TYPE(&frm, 1)); break; case RXR_BLOCK: RXI_To_Block(&frm, val); break; case RXR_UNSET: SET_UNSET(val); break; case RXR_NONE: SET_NONE(val); break; case RXR_TRUE: SET_TRUE(val); break; case RXR_FALSE: SET_FALSE(val); break; case RXR_ERROR: default: SET_UNSET(val); } }
*/ REBINT Check_Error(REBVAL *val) /* ** Process a loop exceptions. Pass in the TOS value, returns: ** ** 2 - if break/return, change val to that set by break ** 1 - if break ** -1 - if continue, change val to unset ** 0 - if not break or continue ** else: error if not an ERROR value ** ***********************************************************************/ { // It's UNSET, not an error: if (!IS_ERROR(val)) Trap0(RE_NO_RETURN); //!!! change to special msg // If it's a BREAK, check for /return value: if (IS_BREAK(val)) { if (VAL_ERR_VALUE(val)) { *val = *VAL_ERR_VALUE(val); return 2; } else { SET_UNSET(val); return 1; } } if (IS_CONTINUE(val)) { SET_UNSET(val); return -1; } return 0; // Else: Let all other errors return as values. }
*/ void Do_Function(REBVAL *func) /* ***********************************************************************/ { REBVAL *result; REBVAL *ds; #if !defined(NDEBUG) const REBYTE *name = Get_Word_Name(DSF_LABEL(DSF)); #endif Eval_Functions++; //Dump_Block(VAL_FUNC_BODY(func)); result = Do_Blk(VAL_FUNC_BODY(func), 0); ds = DS_OUT; if (IS_ERROR(result) && IS_RETURN(result)) { // Value below is kept safe from GC because no-allocation is // done between point of SET_THROW and here. if (VAL_ERR_VALUE(result)) *ds = *VAL_ERR_VALUE(result); else SET_UNSET(ds); } else *ds = *result; // Set return value (atomic) }
*/ REBVAL *Append_Frame(REBSER *frame, REBVAL *word, REBCNT sym) /* ** Append a word to the frame word list. Expands the list ** if necessary. Returns the value cell for the word. (Set to ** UNSET by default to avoid GC corruption.) ** ** If word is not NULL, use the word sym and bind the word value, ** otherwise use sym. ** ***********************************************************************/ { REBSER *words = FRM_WORD_SERIES(frame); REBVAL *value; // Add to word list: EXPAND_SERIES_TAIL(words, 1); value = BLK_LAST(words); Val_Init_Word_Typed(value, REB_WORD, word ? VAL_WORD_SYM(word) : sym, ALL_64); BLK_TERM(words); // Bind the word to this frame: if (word) { VAL_WORD_FRAME(word) = frame; VAL_WORD_INDEX(word) = frame->tail; } // Add unset value to frame: EXPAND_SERIES_TAIL(frame, 1); word = BLK_LAST(frame); SET_UNSET(word); BLK_TERM(frame); return word; // The value cell for word. }
// // Do_Array_At_Core: C // // Most common case of evaluator invocation in Rebol: the data lives in an // array series. Generic routine takes flags and may act as either a DO // or a DO/NEXT at the position given. Option to provide an element that // may not be resident in the array to kick off the execution. // REBIXO Do_Array_At_Core( REBVAL *out, const REBVAL *opt_first, REBARR *array, REBCNT index, REBFLGS flags ) { struct Reb_Frame f; if (opt_first) { f.value = opt_first; f.indexor = index; } else { // Do_Core() requires caller pre-seed first value, always // f.value = ARR_AT(array, index); f.indexor = index + 1; } if (IS_END(f.value)) { SET_UNSET(out); return END_FLAG; } f.out = out; f.source.array = array; f.flags = flags; f.mode = CALL_MODE_GUARD_ARRAY_ONLY; Do_Core(&f); return f.indexor; }
*/ void Do_Native(REBVAL *func) /* ***********************************************************************/ { REBVAL *ds; REBINT n; #if !defined(NDEBUG) const REBYTE *fname = Get_Word_Name(DSF_LABEL(DSF)); #endif Eval_Natives++; if ((n = VAL_FUNC_CODE(func)(DS_OUT))) { ds = DS_OUT; switch (n) { case R_OUT: // for compiler opt break; case R_TOS: *ds = *DS_TOP; break; case R_TOS1: *ds = *DS_NEXT; break; case R_NONE: SET_NONE(ds); break; case R_UNSET: SET_UNSET(ds); break; case R_TRUE: SET_TRUE(ds); break; case R_FALSE: SET_FALSE(ds); break; case R_ARG1: *ds = *D_ARG(1); break; case R_ARG2: *ds = *D_ARG(2); break; case R_ARG3: *ds = *D_ARG(3); break; } } }
*/ void Do_Native(REBVAL *func) /* ***********************************************************************/ { REBVAL *ds; REBINT n; #ifdef DEBUGGING REBYTE *fname = Get_Word_Name(DSF_WORD(DSF)); // for DEBUG Debug_Str(fname); #endif Eval_Natives++; if (NZ(n = VAL_FUNC_CODE(func)(DS_RETURN))) { ds = DS_RETURN; switch (n) { case R_RET: // for compiler opt break; case R_TOS: *ds = *DS_TOP; break; case R_TOS1: *ds = *DS_NEXT; break; case R_NONE: SET_NONE(ds); break; case R_UNSET: SET_UNSET(ds); break; case R_TRUE: SET_TRUE(ds); break; case R_FALSE: SET_FALSE(ds); break; case R_ARG1: *ds = *D_ARG(1); break; case R_ARG2: *ds = *D_ARG(2); break; case R_ARG3: *ds = *D_ARG(3); break; } } }
*/ void Do_Act(REBVAL *ds, REBCNT type, REBCNT act) /* ***********************************************************************/ { REBACT action; REBINT ret; action = Value_Dispatch[type]; //assert(action != 0, RP_NO_ACTION); if (!action) Trap_Action(type, act); ret = action(ds, act); if (ret > 0) { ds = DS_OUT; switch (ret) { case R_OUT: // for compiler opt break; case R_TOS: *ds = *DS_TOP; break; case R_TOS1: *ds = *DS_NEXT; break; case R_NONE: SET_NONE(ds); break; case R_UNSET: SET_UNSET(ds); break; case R_TRUE: SET_TRUE(ds); break; case R_FALSE: SET_FALSE(ds); break; case R_ARG1: *ds = *D_ARG(1); break; case R_ARG2: *ds = *D_ARG(2); break; case R_ARG3: *ds = *D_ARG(3); break; } } }
*/ void Do_Closure(REBVAL *func) /* ** Do a closure by cloning its body and binding it to ** a new frame of words/values. ** ** This could be made faster by pre-binding the body, ** then using Rebind_Block to rebind the words in it. ** ***********************************************************************/ { REBSER *body; REBSER *frame; REBVAL *result; REBVAL *ds; Eval_Functions++; //DISABLE_GC; // Clone the body of the function to allow rebinding to it: body = Clone_Block(VAL_FUNC_BODY(func)); // Copy stack frame args as the closure object (one extra at head) frame = Copy_Values(BLK_SKIP(DS_Series, DS_ARG_BASE), SERIES_TAIL(VAL_FUNC_ARGS(func))); SET_FRAME(BLK_HEAD(frame), 0, VAL_FUNC_ARGS(func)); // Rebind the body to the new context (deeply): //Rebind_Block(VAL_FUNC_ARGS(func), frame, body); Bind_Block(frame, BLK_HEAD(body), BIND_DEEP); // | BIND_NO_SELF); ds = DS_RETURN; SET_OBJECT(ds, body); // keep it GC safe result = Do_Blk(body, 0); // GC-OK - also, result returned on DS stack ds = DS_RETURN; if (IS_ERROR(result) && IS_RETURN(result)) { // Value below is kept safe from GC because no-allocation is // done between point of SET_THROW and here. if (VAL_ERR_VALUE(result)) *ds = *VAL_ERR_VALUE(result); else SET_UNSET(ds); } else *ds = *result; // Set return value (atomic) }
*/ REBVAL *Append_Frame(REBSER *frame, REBVAL *word, REBCNT sym) /* ** Append a word to the frame word list. Expands the list ** if necessary. Returns the value cell for the word. (Set to ** UNSET by default to avoid GC corruption.) ** ** If word is not NULL, use the word sym and bind the word value, ** otherwise use sym. ** ** WARNING: Invalidates pointers to values within the frame ** because the frame block may get expanded. (Use indexes.) ** ***********************************************************************/ { REBSER *words = FRM_WORD_SERIES(frame); REBVAL *value; // Add to word list: EXPAND_SERIES_TAIL(words, 1); value = BLK_LAST(words); if (word) Init_Frame_Word(value, VAL_WORD_SYM(word)); else Init_Frame_Word(value, sym); BLK_TERM(words); // Bind the word to this frame: if (word) { VAL_WORD_FRAME(word) = frame; VAL_WORD_INDEX(word) = frame->tail; } // Add unset value to frame: EXPAND_SERIES_TAIL(frame, 1); word = BLK_LAST(frame); SET_UNSET(word); BLK_TERM(frame); return word; // The value cell for word. }
*/ REBOOL Loop_Throw_Should_Return(REBVAL *val) /* ** Process values thrown during loop, and tell the loop whether ** to take that processed value and return it up the stack. ** If not, then the throw was a continue...and the code ** should just keep going. ** ** Note: This modifies the input value. If it returns FALSE ** then the value is guaranteed to not be THROWN(), but it ** may-or-may-not be THROWN() if TRUE is returned. ** ***********************************************************************/ { assert(THROWN(val)); // Using words for BREAK and CONTINUE to parallel old VAL_ERR_SYM() // code. So if the throw wasn't a word it can't be either of those, // hence the loop doesn't handle it and needs to bubble up the THROWN() if (!IS_WORD(val)) return TRUE; // If it's a CONTINUE then wipe out the THROWN() value with UNSET, // and tell the loop it doesn't have to return. if (VAL_WORD_SYM(val) == SYM_CONTINUE) { SET_UNSET(val); return FALSE; } // If it's a BREAK, get the /WITH value (UNSET! if no /WITH) and // say it should be returned. if (VAL_WORD_SYM(val) == SYM_BREAK) { TAKE_THROWN_ARG(val, val); return TRUE; } // Else: Let all other THROWN() values bubble up. return TRUE; }
*/ void Resolve_Context(REBSER *target, REBSER *source, REBVAL *only_words, REBFLG all, REBFLG expand) /* ** Only_words can be a block of words or an index in the target ** (for new words). ** ***********************************************************************/ { REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here REBVAL *words; REBVAL *vals; REBINT n; REBINT m; REBCNT i = 0; CHECK_BIND_TABLE; if (IS_PROTECT_SERIES(target)) Trap0(RE_PROTECTED); if (IS_INTEGER(only_words)) { // Must be: 0 < i <= tail i = VAL_INT32(only_words); // never <= 0 if (i == 0) i = 1; if (i >= target->tail) return; } Collect_Start(BIND_NO_SELF); // DO NOT TRAP IN THIS SECTION n = 0; // If limited resolve, tag the word ids that need to be copied: if (i) { // Only the new words of the target: for (words = FRM_WORD(target, i); NOT_END(words); words++) binds[VAL_BIND_CANON(words)] = -1; n = SERIES_TAIL(target) - 1; } else if (IS_BLOCK(only_words)) { // Limit exports to only these words: for (words = VAL_BLK_DATA(only_words); NOT_END(words); words++) { if (IS_WORD(words) || IS_SET_WORD(words)) { binds[VAL_WORD_CANON(words)] = -1; n++; } } } // Expand target as needed: if (expand && n > 0) { // Determine how many new words to add: for (words = FRM_WORD(target, 1); NOT_END(words); words++) if (binds[VAL_BIND_CANON(words)]) n--; // Expand frame by the amount required: if (n > 0) Expand_Frame(target, n, 0); else expand = 0; } // Maps a word to its value index in the source context. // Done by marking all source words (in bind table): words = FRM_WORDS(source)+1; for (n = 1; NOT_END(words); n++, words++) { if (IS_NONE(only_words) || binds[VAL_BIND_CANON(words)]) binds[VAL_WORD_CANON(words)] = n; } // Foreach word in target, copy the correct value from source: n = i ? i : 1; vals = FRM_VALUE(target, n); for (words = FRM_WORD(target, n); NOT_END(words); words++, vals++) { if ((m = binds[VAL_BIND_CANON(words)])) { binds[VAL_BIND_CANON(words)] = 0; // mark it as set if (!VAL_PROTECTED(words) && (all || IS_UNSET(vals))) { if (m < 0) SET_UNSET(vals); // no value in source context else *vals = *FRM_VALUE(source, m); //Debug_Num("type:", VAL_TYPE(vals)); //Debug_Str(Get_Word_Name(words)); } } } // Add any new words and values: if (expand) { REBVAL *val; words = FRM_WORDS(source)+1; for (n = 1; NOT_END(words); n++, words++) { if (binds[VAL_BIND_CANON(words)]) { // Note: no protect check is needed here binds[VAL_BIND_CANON(words)] = 0; val = Append_Frame(target, 0, VAL_BIND_SYM(words)); *val = *FRM_VALUE(source, n); } } } else { // Reset bind table (do not use Collect_End): if (i) { for (words = FRM_WORD(target, i); NOT_END(words); words++) binds[VAL_BIND_CANON(words)] = 0; } else if (IS_BLOCK(only_words)) { for (words = VAL_BLK_DATA(only_words); NOT_END(words); words++) { if (IS_WORD(words) || IS_SET_WORD(words)) binds[VAL_WORD_CANON(words)] = 0; } } else { for (words = FRM_WORDS(source)+1; NOT_END(words); words++) binds[VAL_BIND_CANON(words)] = 0; } } CHECK_BIND_TABLE; RESET_TAIL(BUF_WORDS); // allow reuse, trapping ok now }
int kbd_scancode(key_event_state_t * state, unsigned char c, key_event_t * event) { /* Convert scancodes to a series of keys */ event->keycode = 0; event->action = 0; event->modifiers = 0; event->key = 0; #if DEBUG_SCANCODES fprintf(stderr, "[%d] %d\n", state->kbd_s_state, (int)c); #endif event->modifiers |= state->kl_ctrl ? KEY_MOD_LEFT_CTRL : 0; event->modifiers |= state->kl_shift ? KEY_MOD_LEFT_SHIFT : 0; event->modifiers |= state->kl_alt ? KEY_MOD_LEFT_ALT : 0; event->modifiers |= state->kl_super ? KEY_MOD_LEFT_SUPER : 0; event->modifiers |= state->kr_ctrl ? KEY_MOD_RIGHT_CTRL : 0; event->modifiers |= state->kr_shift ? KEY_MOD_RIGHT_SHIFT : 0; event->modifiers |= state->kr_alt ? KEY_MOD_RIGHT_ALT : 0; event->modifiers |= state->kr_super ? KEY_MOD_RIGHT_SUPER : 0; if (!state->kbd_s_state) { if (c == 0xE0) { state->kbd_s_state = 1; /* Literally nothing */ return 0; } if (c & KEY_UP_MASK) { c ^= KEY_UP_MASK; event->action = KEY_ACTION_UP; } else { event->action = KEY_ACTION_DOWN; } int down = (event->action == KEY_ACTION_DOWN); switch (key_method[c]) { case norm: { event->keycode = kbd_us[c]; if (state->k_ctrl) { int out = (int)(kbd_us_l2[c] - KEY_CTRL_MASK); if (out < 0 || out > 0x1F) { event->key = kbd_us[c]; } else { event->key = out; } } else { event->key = state->k_shift ? kbd_us_l2[c] : kbd_us[c]; } } break; case spec: switch (c) { case 0x01: event->key = '\033'; event->keycode = KEY_ESCAPE; break; case 0x1D: state->k_ctrl = down; state->kl_ctrl = down; event->keycode = KEY_LEFT_CTRL; SET_UNSET(event->modifiers, KEY_MOD_LEFT_CTRL, down); break; case 0x2A: state->k_shift = down; state->kl_shift = down; event->keycode = KEY_LEFT_SHIFT; SET_UNSET(event->modifiers, KEY_MOD_LEFT_SHIFT, down); break; case 0x36: state->k_shift = down; state->kr_shift = down; event->keycode = KEY_RIGHT_SHIFT; SET_UNSET(event->modifiers, KEY_MOD_RIGHT_SHIFT, down); break; case 0x38: state->k_alt = down; state->kl_alt = down; event->keycode = KEY_LEFT_ALT; SET_UNSET(event->modifiers, KEY_MOD_LEFT_ALT, down); break; default: break; } break; case func: switch (c) { case KEY_SCANCODE_F1: event->keycode = KEY_F1; break; case KEY_SCANCODE_F2: event->keycode = KEY_F2; break; case KEY_SCANCODE_F3: event->keycode = KEY_F3; break; case KEY_SCANCODE_F4: event->keycode = KEY_F4; break; case KEY_SCANCODE_F5: event->keycode = KEY_F5; break; case KEY_SCANCODE_F6: event->keycode = KEY_F6; break; case KEY_SCANCODE_F7: event->keycode = KEY_F7; break; case KEY_SCANCODE_F8: event->keycode = KEY_F8; break; case KEY_SCANCODE_F9: event->keycode = KEY_F9; break; case KEY_SCANCODE_F10: event->keycode = KEY_F10; break; case KEY_SCANCODE_F11: event->keycode = KEY_F11; break; case KEY_SCANCODE_F12: event->keycode = KEY_F12; break; } break; default: break; } if (event->key) { return down; } return 0; } else if (state->kbd_s_state == 1) { if (c & KEY_UP_MASK) { c ^= KEY_UP_MASK; event->action = KEY_ACTION_UP; } else { event->action = KEY_ACTION_DOWN; } int down = (event->action == KEY_ACTION_DOWN); switch (c) { case 0x5B: state->k_super = down; state->kl_super = down; event->keycode = KEY_LEFT_SUPER; SET_UNSET(event->modifiers, KEY_MOD_LEFT_SUPER, down); break; case 0x5C: state->k_super = down; state->kr_super = down; event->keycode = KEY_RIGHT_SUPER; SET_UNSET(event->modifiers, KEY_MOD_RIGHT_SUPER, down); break; case 0x1D: state->kr_ctrl = down; state->k_ctrl = down; event->keycode = KEY_RIGHT_CTRL; SET_UNSET(event->modifiers, KEY_MOD_RIGHT_CTRL, down); break; case 0x38: state->kr_alt = down; state->k_alt = down; event->keycode = KEY_RIGHT_ALT; SET_UNSET(event->modifiers, KEY_MOD_RIGHT_ALT, down); break; case 0x48: event->keycode = KEY_ARROW_UP; break; case 0x4D: event->keycode = KEY_ARROW_RIGHT; break; case 0x50: event->keycode = KEY_ARROW_DOWN; break; case 0x4B: event->keycode = KEY_ARROW_LEFT; break; case 0x49: event->keycode = KEY_PAGE_UP; break; case 0x51: event->keycode = KEY_PAGE_DOWN; break; default: break; } state->kbd_s_state = 0; return 0; } return 0; }
*/ void Do_Commands(REBSER *cmds, void *context) /* ** Evaluate a block of commands as efficiently as possible. ** The arguments to each command must already be reduced or ** use only variable lookup. ** ** Returns the last evaluated value, if provided. ** ***********************************************************************/ { REBVAL *blk; REBCNT index = 0; REBVAL *set_word = 0; REBVAL *cmd_word; REBSER *words; REBVAL *args; REBVAL *val; REBVAL *func; RXIFRM frm; // args stored here REBCNT n; REBEXT *ext; REBCEC *ctx; if ((ctx = context)) ctx->block = cmds; blk = BLK_HEAD(cmds); while (NOT_END(blk)) { // var: command result if IS_SET_WORD(blk) { set_word = blk++; index++; }; // get command function if (IS_WORD(cmd_word = blk)) { // Optimized var fetch: n = VAL_WORD_INDEX(blk); if (n > 0) func = FRM_VALUES(VAL_WORD_FRAME(blk)) + n; else func = Get_Var(blk); // fallback } else func = blk; if (!IS_COMMAND(func)) Trap2(RE_EXPECT_VAL, Get_Type_Word(REB_COMMAND), blk); // Advance to next value blk++; if (ctx) ctx->index = index; // position of function index++; // get command arguments and body words = VAL_FUNC_WORDS(func); RXA_COUNT(&frm) = SERIES_TAIL(VAL_FUNC_ARGS(func))-1; // not self // collect each argument (arg list already validated on MAKE) n = 0; for (args = BLK_SKIP(words, 1); NOT_END(args); args++) { //Debug_Type(args); val = blk++; index++; if (IS_END(val)) Trap2(RE_NO_ARG, cmd_word, args); //Debug_Type(val); // actual arg is a word, lookup? if (VAL_TYPE(val) >= REB_WORD) { if (IS_WORD(val)) { if (IS_WORD(args)) val = Get_Var(val); } else if (IS_PATH(val)) { if (IS_WORD(args)) val = Get_Any_Var(val); // volatile value! } else if (IS_PAREN(val)) { val = Do_Blk(VAL_SERIES(val), 0); // volatile value! } // all others fall through } // check datatype if (!TYPE_CHECK(args, VAL_TYPE(val))) Trap3(RE_EXPECT_ARG, cmd_word, args, Of_Type(val)); // put arg into command frame n++; RXA_TYPE(&frm, n) = Reb_To_RXT[VAL_TYPE(val)]; frm.args[n] = Value_To_RXI(val); } // Call the command (also supports different extension modules): func = BLK_HEAD(VAL_FUNC_BODY(func)); n = (REBCNT)VAL_INT64(func + 1); ext = &Ext_List[VAL_I32(VAL_OBJ_VALUE(func, 1))]; // Handler n = ext->call(n, &frm, context); val = DS_RETURN; switch (n) { case RXR_VALUE: RXI_To_Value(val, frm.args[1], RXA_TYPE(&frm, 1)); break; case RXR_BLOCK: RXI_To_Block(&frm, val); break; case RXR_UNSET: SET_UNSET(val); break; case RXR_NONE: SET_NONE(val); break; case RXR_TRUE: SET_TRUE(val); break; case RXR_FALSE: SET_FALSE(val); break; case RXR_ERROR: default: SET_UNSET(val); } if (set_word) { Set_Var(set_word, val); set_word = 0; } } }
*/ static REB_R Loop_Each(struct Reb_Call *call_, LOOP_MODE mode) /* ** Common implementation code of FOR-EACH, REMOVE-EACH, MAP-EACH, ** and EVERY. ** ***********************************************************************/ { REBSER *body; REBVAL *vars; REBVAL *words; REBSER *frame; // `data` is the series/object/map/etc. being iterated over // Note: `data_is_object` flag is optimized out, but hints static analyzer REBVAL *data = D_ARG(2); REBSER *series; const REBOOL data_is_object = ANY_OBJECT(data); REBSER *out; // output block (needed for MAP-EACH) REBINT index; // !!!! should these be REBCNT? REBINT tail; REBINT windex; // write REBINT rindex; // read REBOOL break_with = FALSE; REBOOL every_true = TRUE; REBCNT i; REBCNT j; REBVAL *ds; if (IS_NONE(data)) return R_NONE; body = Init_Loop(D_ARG(1), D_ARG(3), &frame); // vars, body Val_Init_Object(D_ARG(1), frame); // keep GC safe Val_Init_Block(D_ARG(3), body); // keep GC safe SET_NONE(D_OUT); // Default result to NONE if the loop does not run if (mode == LOOP_MAP_EACH) { // Must be managed *and* saved...because we are accumulating results // into it, and those results must be protected from GC // !!! This means we cannot Free_Series in case of a BREAK, we // have to leave it to the GC. Should there be a variant which // lets a series be a GC root for a temporary time even if it is // not SER_KEEP? out = Make_Array(VAL_LEN(data)); MANAGE_SERIES(out); SAVE_SERIES(out); } // Get series info: if (data_is_object) { series = VAL_OBJ_FRAME(data); out = FRM_WORD_SERIES(series); // words (the out local reused) index = 1; //if (frame->tail > 3) raise Error_Invalid_Arg(FRM_WORD(frame, 3)); } else if (IS_MAP(data)) { series = VAL_SERIES(data); index = 0; //if (frame->tail > 3) raise Error_Invalid_Arg(FRM_WORD(frame, 3)); } else { series = VAL_SERIES(data); index = VAL_INDEX(data); if (index >= cast(REBINT, SERIES_TAIL(series))) { if (mode == LOOP_REMOVE_EACH) { SET_INTEGER(D_OUT, 0); } else if (mode == LOOP_MAP_EACH) { UNSAVE_SERIES(out); Val_Init_Block(D_OUT, out); } return R_OUT; } } windex = index; // Iterate over each value in the data series block: while (index < (tail = SERIES_TAIL(series))) { rindex = index; // remember starting spot j = 0; // Set the FOREACH loop variables from the series: for (i = 1; i < frame->tail; i++) { vars = FRM_VALUE(frame, i); words = FRM_WORD(frame, i); // var spec is WORD if (IS_WORD(words)) { if (index < tail) { if (ANY_BLOCK(data)) { *vars = *BLK_SKIP(series, index); } else if (data_is_object) { if (!VAL_GET_EXT(BLK_SKIP(out, index), EXT_WORD_HIDE)) { // Alternate between word and value parts of object: if (j == 0) { Val_Init_Word(vars, REB_WORD, VAL_WORD_SYM(BLK_SKIP(out, index)), series, index); if (NOT_END(vars+1)) index--; // reset index for the value part } else if (j == 1) *vars = *BLK_SKIP(series, index); else raise Error_Invalid_Arg(words); j++; } else { // Do not evaluate this iteration index++; goto skip_hidden; } } else if (IS_VECTOR(data)) { Set_Vector_Value(vars, series, index); } else if (IS_MAP(data)) { REBVAL *val = BLK_SKIP(series, index | 1); if (!IS_NONE(val)) { if (j == 0) { *vars = *BLK_SKIP(series, index & ~1); if (IS_END(vars+1)) index++; // only words } else if (j == 1) *vars = *BLK_SKIP(series, index); else raise Error_Invalid_Arg(words); j++; } else { index += 2; goto skip_hidden; } } else { // A string or binary if (IS_BINARY(data)) { SET_INTEGER(vars, (REBI64)(BIN_HEAD(series)[index])); } else if (IS_IMAGE(data)) { Set_Tuple_Pixel(BIN_SKIP(series, index), vars); } else { VAL_SET(vars, REB_CHAR); VAL_CHAR(vars) = GET_ANY_CHAR(series, index); } } index++; } else SET_NONE(vars); } // var spec is SET_WORD: else if (IS_SET_WORD(words)) { if (ANY_OBJECT(data) || IS_MAP(data)) *vars = *data; else Val_Init_Block_Index(vars, series, index); //if (index < tail) index++; // do not increment block. } else raise Error_Invalid_Arg(words); } if (index == rindex) { // the word block has only set-words: for-each [a:] [1 2 3][] index++; } if (Do_Block_Throws(D_OUT, body, 0)) { if (IS_WORD(D_OUT) && VAL_WORD_SYM(D_OUT) == SYM_CONTINUE) { if (mode == LOOP_REMOVE_EACH) { // signal the post-body-execution processing that we // *do not* want to remove the element on a CONTINUE SET_FALSE(D_OUT); } else { // CONTINUE otherwise acts "as if" the loop body execution // returned an UNSET! SET_UNSET(D_OUT); } } else if (IS_WORD(D_OUT) && VAL_WORD_SYM(D_OUT) == SYM_BREAK) { // If it's a BREAK, get the /WITH value (UNSET! if no /WITH) // Though technically this doesn't really tell us if a // BREAK/WITH happened, as you can BREAK/WITH an UNSET! TAKE_THROWN_ARG(D_OUT, D_OUT); if (!IS_UNSET(D_OUT)) break_with = TRUE; index = rindex; break; } else { // Any other kind of throw, with a WORD! name or otherwise... index = rindex; break; } } switch (mode) { case LOOP_FOR_EACH: // no action needed after body is run break; case LOOP_REMOVE_EACH: // If FALSE return, copy values to the write location // !!! Should UNSET! also act as conditional false here? Error? if (IS_CONDITIONAL_FALSE(D_OUT)) { REBYTE wide = SERIES_WIDE(series); // memory areas may overlap, so use memmove and not memcpy! // !!! This seems a slow way to do it, but there's probably // not a lot that can be done as the series is expected to // be in a good state for the next iteration of the body. :-/ memmove( series->data + (windex * wide), series->data + (rindex * wide), (index - rindex) * wide ); windex += index - rindex; } break; case LOOP_MAP_EACH: // anything that's not an UNSET! will be added to the result if (!IS_UNSET(D_OUT)) Append_Value(out, D_OUT); break; case LOOP_EVERY: if (every_true) { // !!! This currently treats UNSET! as true, which ALL // effectively does right now. That's likely a bad idea. // When ALL changes, so should this. // every_true = IS_CONDITIONAL_TRUE(D_OUT); } break; default: assert(FALSE); } skip_hidden: ; } switch (mode) { case LOOP_FOR_EACH: // Nothing to do but return last result (will be UNSET! if an // ordinary BREAK was used, the /WITH if a BREAK/WITH was used, // and an UNSET! if the last loop iteration did a CONTINUE.) return R_OUT; case LOOP_REMOVE_EACH: // Remove hole (updates tail): if (windex < index) Remove_Series(series, windex, index - windex); SET_INTEGER(D_OUT, index - windex); return R_OUT; case LOOP_MAP_EACH: UNSAVE_SERIES(out); if (break_with) { // If BREAK is given a /WITH parameter that is not an UNSET!, it // is assumed that you want to override the accumulated mapped // data so far and return the /WITH value. (which will be in // D_OUT when the loop above is `break`-ed) // !!! Would be nice if we could Free_Series(out), but it is owned // by GC (we had to make it that way to use SAVE_SERIES on it) return R_OUT; } // If you BREAK/WITH an UNSET! (or just use a BREAK that has no // /WITH, which is indistinguishable in the thrown value) then it // returns the accumulated results so far up to the break. Val_Init_Block(D_OUT, out); return R_OUT; case LOOP_EVERY: // Result is the cumulative TRUE? state of all the input (with any // unsets taken out of the consideration). The last TRUE? input // if all valid and NONE! otherwise. (Like ALL.) If the loop // never runs, `every_true` will be TRUE *but* D_OUT will be NONE! if (!every_true) SET_NONE(D_OUT); return R_OUT; } DEAD_END; }