forked from rebolsource/r3
/
c-eval.c
1615 lines (1360 loc) · 59.9 KB
/
c-eval.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// File: %c-eval.c
// Summary: "Central Interpreter Evaluator"
// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)"
// Homepage: https://github.com/metaeducation/ren-c/
//
//=////////////////////////////////////////////////////////////////////////=//
//
// Copyright 2012-2020 Ren-C Open Source Contributors
// Copyright 2012 REBOL Technologies
// REBOL is a trademark of REBOL Technologies
//
// See README.md and CREDITS.md for more information
//
// Licensed under the Lesser GPL, Version 3.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.gnu.org/licenses/lgpl-3.0.html
//
//=////////////////////////////////////////////////////////////////////////=//
//
// This file contains Eval_Maybe_Stale_Throws(), which is the central
// evaluator implementation. Most callers should use higher level wrappers,
// because the long name conveys any direct caller must handle the following:
//
// * _Maybe_Stale_ => The evaluation targets an output cell which must be
// preloaded or set to END. If there is no result (e.g. due to being just
// comments) then whatever was in that cell will still be there -but- will
// carry OUT_NOTE_STALE. This is just an alias for NODE_FLAG_MARKED, and
// it must be cleared off before passing pointers to the cell to a routine
// which may interpret that flag differently.
//
// * _Throws => The return result is a boolean which all callers *must* heed.
// There is no "thrown value" data type or cell flag, so the only indication
// that a throw happened comes from this flag. See %sys-throw.h
//
// Eval_Throws() is a small stub which takes care of the first concern,
// though some low-level clients actually want the stale flag.
//
//=//// NOTES /////////////////////////////////////////////////////////////=//
//
// * See %sys-eval.h for wrappers that make it easier to set up frames and
// use the evaluator for a single step.
//
// * See %sys-do.h for wrappers that make it easier to run multiple evaluator
// steps in a frame and return the final result, giving VOID! by default.
//
// * Eval_Maybe_Stale_Throws() is LONG. That is largely a purposeful choice.
// Breaking it into functions would add overhead (in the debug build if not
// also release builds) and prevent interesting tricks and optimizations.
// It is separated into sections, and the invariants in each section are
// made clear with comments and asserts.
//
// * The evaluator only moves forward, and operates on a strict window of
// visibility of two elements at a time (current position and "lookback").
// See `Reb_Feed` for the code that provides this abstraction over Rebol
// arrays as well as C va_list.
//
#include "sys-core.h"
#if defined(DEBUG_COUNT_TICKS) // <-- THIS IS VERY USEFUL, SEE %sys-eval.h!
//
// This counter is incremented each time a function dispatcher is run
// or a parse rule is executed. See UPDATE_TICK_COUNT().
//
REBTCK TG_Tick;
// *** DON'T COMMIT THIS v-- KEEP IT AT ZERO! ***
REBTCK TG_Break_At_Tick = 0;
// *** DON'T COMMIT THIS --^ KEEP IT AT ZERO! ***
#endif // ^-- SERIOUSLY: READ ABOUT C-DEBUG-BREAK AND PLACES TICKS ARE STORED
// The frame contains a "feed" whose ->value typically represents a "current"
// step in the feed. But the evaluator is organized in a way that the
// notion of what is "current" can get out of sync with the feed. An example
// would be when a SET-WORD! evaluates its right hand side, causing the feed
// to advance an arbitrary amount.
//
// So the frame has its own frame state for tracking the "current" position,
// and maintains the optional cache of what the fetched value of that is.
// These macros help make the code less ambiguous.
//
#undef f_value
#undef f_gotten
#define f_next f->feed->value
#define f_next_gotten f->feed->gotten
// We make the macro for getting specifier a bit more complex here, to
// account for reevaluation. To help annotate why it's weird, we call it
// `v_specifier` instead.
//
// https://forum.rebol.info/t/should-reevaluate-apply-let-bindings/1521
//
#undef f_specifier
#define v_specifier \
(STATE_BYTE(f) == ST_EVALUATOR_REEVALUATING \
? SPECIFIED \
: FEED_SPECIFIER(f->feed))
// In debug builds, the KIND_BYTE() calls enforce cell validity...but slow
// things down a little. So we only use the checked version in the main
// switch statement. This abbreviation is also shorter and more legible.
//
#define kind_current KIND3Q_BYTE_UNCHECKED(v)
// In the early development of FRAME!, the REBFRM* for evaluating across a
// block was reused for each ACTION! call. Since no more than one action was
// running at a time, this seemed to work. However, that didn't allow for
// a separate "reified" entry for users to point at. While giving each
// action its own REBFRM* has performance downsides, it makes the objects
// correspond to what they are...and may be better for cohering the "executor"
// pattern by making it possible to use a constant executor per frame.
//
#define DECLARE_ACTION_SUBFRAME(f,parent) \
DECLARE_FRAME (f, (parent)->feed, \
EVAL_MASK_DEFAULT | /*EVAL_FLAG_KEEP_STALE_BIT |*/ ((parent)->flags.bits \
& (EVAL_FLAG_FULFILLING_ARG | EVAL_FLAG_RUNNING_ENFIX \
| EVAL_FLAG_DIDNT_LEFT_QUOTE_PATH)))
#ifdef DEBUG_EXPIRED_LOOKBACK
#define CURRENT_CHANGES_IF_FETCH_NEXT \
(f->feed->stress != nullptr)
#else
#define CURRENT_CHANGES_IF_FETCH_NEXT \
(v == &f->feed->lookback)
#endif
inline static void Expire_Out_Cell_Unless_Invisible(REBFRM *f) {
SET_CELL_FLAG(f->out, OUT_NOTE_STALE);
}
// SET-WORD!, SET-PATH!, SET-GROUP!, and SET-BLOCK! all want to do roughly
// the same thing as the first step of their evaluation. They evaluate the
// right hand side into f->out.
//
// -but- because you can be asked to evaluate something like `x: y: z: ...`,
// there could be any number of SET-XXX! before the value to assign is found.
//
// This inline function attempts to keep that stack by means of the local
// variable `v`, if it points to a stable location. If so, it simply reuses
// the frame it already has.
//
// What makes this slightly complicated is that the current value may be in
// a place that doing a Fetch_Next_In_Frame() might corrupt it. This could
// be accounted for by pushing the value to some other stack--e.g. the data
// stack. But for the moment this (uncommon?) case uses a new frame.
//
inline static bool Rightward_Evaluate_Nonvoid_Into_Out_Throws(
REBFRM *f,
const RELVAL *v
){
if (GET_FEED_FLAG(f->feed, NEXT_ARG_FROM_OUT)) { // e.g. `10 -> x:`
CLEAR_FEED_FLAG(f->feed, NEXT_ARG_FROM_OUT);
CLEAR_CELL_FLAG(f->out, UNEVALUATED); // this helper counts as eval
return false;
}
if (IS_END(f_next)) // `do [x:]`, `do [o/x:]`, etc. are illegal
fail (Error_Need_Non_End_Core(v, v_specifier));
// Using a SET-XXX! means you always have at least two elements; it's like
// an arity-1 function. `1 + x: whatever ...`. This overrides the no
// lookahead behavior flag right up front.
//
CLEAR_FEED_FLAG(f->feed, NO_LOOKAHEAD);
REBFLGS flags = EVAL_MASK_DEFAULT
| (f->flags.bits & EVAL_FLAG_FULFILLING_ARG); // if f was, we are
SET_END(f->out); // `1 x: comment "hi"` shouldn't set x to 1!
if (CURRENT_CHANGES_IF_FETCH_NEXT) { // must use new frame
if (Eval_Step_In_Subframe_Throws(f->out, f, flags))
return true;
}
else { // !!! Reusing the frame, would inert optimization be worth it?
do {
// !!! If reevaluating, this will forget that we are doing so.
//
STATE_BYTE(f) = ST_EVALUATOR_INITIAL_ENTRY;
if (Eval_Maybe_Stale_Throws(f)) // reuse `f`
return true;
// Keep evaluating as long as evaluations vanish, e.g.
// `x: comment "hi" 2` shouldn't fail.
//
// !!! Note this behavior is already handled by FULFILLING_ARG
// but but we are reusing a frame that may-or-may-not be
// fulfilling. There may be a better way of centralizing this.
//
} while (IS_END(f->out) and NOT_END(f_next));
}
if (IS_END(f->out)) // e.g. `do [x: ()]` or `(x: comment "hi")`.
fail (Error_Need_Non_End_Core(v, v_specifier));
CLEAR_CELL_FLAG(f->out, UNEVALUATED); // this helper counts as eval
return false;
}
//
// Eval_Maybe_Stale_Throws: C
//
// See notes at top of file for general remarks on this central function's
// name, and that wrappers should nearly always be used to call it.
//
// More detailed assertions of the preconditions, postconditions, and state
// at each evaluation step are contained in %d-eval.c, to keep this file
// more manageable in length.
//
bool Eval_Maybe_Stale_Throws(REBFRM * const f)
{
#ifdef DEBUG_ENSURE_FRAME_EVALUATES
f->was_eval_called = true; // see definition for why this flag exists
#endif
#if defined(DEBUG_COUNT_TICKS)
REBTCK tick = f->tick = TG_Tick; // snapshot tick for C watchlist viewing
#endif
#if !defined(NDEBUG)
REBFLGS initial_flags = f->flags.bits & ~(
EVAL_FLAG_FULFILL_ONLY // can be requested or <blank> can trigger
| EVAL_FLAG_RUNNING_ENFIX // can be requested with REEVALUATE_CELL
| FLAG_STATE_BYTE(255) // state is forgettable
); // should be unchanged on exit
#endif
assert(DSP >= f->dsp_orig); // REDUCE accrues, APPLY adds refinements
assert(not IS_TRASH_DEBUG(f->out)); // all invisible will preserve output
assert(f->out != f_spare); // overwritten by temporary calculations
// A barrier shouldn't cause an error in evaluation if code would be
// willing to accept an <end>. So we allow argument gathering to try to
// run, but it may error if that's not acceptable.
//
if (GET_FEED_FLAG(f->feed, BARRIER_HIT)) {
if (GET_EVAL_FLAG(f, FULFILLING_ARG)) {
f->out->header.bits |= CELL_FLAG_OUT_NOTE_STALE;
return false;
}
CLEAR_FEED_FLAG(f->feed, BARRIER_HIT); // not an argument, clear flag
}
const RELVAL *v; // shorthand for the value we are switch()-ing on
TRASH_POINTER_IF_DEBUG(v);
option(const REBVAL*) gotten;
TRASH_OPTION_IF_DEBUG(gotten);
// Given how the evaluator is written, it's inevitable that there will
// have to be a test for points to `goto` before running normal eval.
// This cost is paid on every entry to Eval_Core().
//
switch (STATE_BYTE(f)) {
case ST_EVALUATOR_INITIAL_ENTRY:
break;
case ST_EVALUATOR_LOOKING_AHEAD:
goto lookahead;
case ST_EVALUATOR_REEVALUATING: { // v-- IMPORTANT: Keep STATE_BYTE()
//
// It's important to leave STATE_BYTE() as ST_EVALUATOR_REEVALUATING
// during the switch state, because that's how the evaluator knows
// not to redundantly apply LET bindings. See `v_specifier` above.
// The re-evaluate functionality may not want to heed the enfix state
// in the action itself. See REBNATIVE(shove)'s /ENFIX for instance.
// So we go by the state of EVAL_FLAG_RUNNING_ENFIX on entry.
//
if (GET_EVAL_FLAG(f, RUNNING_ENFIX)) {
CLEAR_EVAL_FLAG(f, RUNNING_ENFIX); // for assertion
DECLARE_ACTION_SUBFRAME (subframe, f);
Push_Frame(f->out, subframe);
Push_Action(
subframe,
VAL_ACTION(f->u.reval.value),
VAL_ACTION_BINDING(f->u.reval.value)
);
Begin_Enfix_Action(subframe, VAL_ACTION_LABEL(f->u.reval.value));
// ^-- invisibles cache NO_LOOKAHEAD
goto process_action;
}
if (NOT_FEED_FLAG(f->feed, NEXT_ARG_FROM_OUT))
SET_CELL_FLAG(f->out, OUT_NOTE_STALE);
v = f->u.reval.value;
gotten = nullptr;
goto evaluate; }
default:
assert(false);
}
#if !defined(NDEBUG)
Eval_Core_Expression_Checks_Debug(f);
assert(NOT_EVAL_FLAG(f, DIDNT_LEFT_QUOTE_PATH));
if (NOT_EVAL_FLAG(f, FULFILLING_ARG))
assert(NOT_FEED_FLAG(f->feed, NO_LOOKAHEAD));
assert(NOT_FEED_FLAG(f->feed, DEFERRING_ENFIX));
#endif
//=//// START NEW EXPRESSION ////////////////////////////////////////////=//
assert(Eval_Count >= 0);
if (--Eval_Count == 0) {
//
// Note that Do_Signals_Throws() may do a recycle step of the GC, or
// it may spawn an entire interactive debugging session via
// breakpoint before it returns. It may also FAIL and longjmp out.
//
if (Do_Signals_Throws(f->out))
goto return_thrown;
}
assert(NOT_FEED_FLAG(f->feed, NEXT_ARG_FROM_OUT));
SET_CELL_FLAG(f->out, OUT_NOTE_STALE); // out won't act as enfix input
UPDATE_EXPRESSION_START(f); // !!! See FRM_INDEX() for caveats
// If asked to evaluate `[]` then we have now done all the work the
// evaluator needs to do--including marking the output stale.
//
// See DEBUG_ENSURE_FRAME_EVALUATES for why an empty array does not
// bypass calling into the evaluator.
//
if (KIND3Q_BYTE(f_next) == REB_0_END)
goto finished;
gotten = f_next_gotten;
v = Lookback_While_Fetching_Next(f);
// ^-- can't just `v = f_next`, fetch may overwrite--request lookback!
evaluate: ; // meaningful semicolon--subsequent macro may declare things
// ^-- doesn't advance expression index: `reeval x` starts with `reeval`
//=//// LOOKAHEAD FOR ENFIXED FUNCTIONS THAT QUOTE THEIR LEFT ARG ///////=//
// Ren-C has an additional lookahead step *before* an evaluation in order
// to take care of this scenario. To do this, it pre-emptively feeds the
// frame one unit that f->value is the *next* value, and a local variable
// called "current" holds the current head of the expression that the
// main switch would process.
UPDATE_TICK_DEBUG(v);
// v-- This is the TG_Break_At_Tick or C-DEBUG-BREAK landing spot --v
if (KIND3Q_BYTE(f_next) != REB_WORD) // right's kind - END would be REB_0
goto give_up_backward_quote_priority;
assert(not f_next_gotten); // Fetch_Next_In_Frame() cleared it
f_next_gotten = Lookup_Word(f_next, FEED_SPECIFIER(f->feed));
if (not f_next_gotten or not IS_ACTION(unwrap(f_next_gotten)))
goto give_up_backward_quote_priority; // note only ACTION! is ENFIXED
if (GET_ACTION_FLAG(VAL_ACTION(unwrap(f_next_gotten)), IS_BARRIER)) {
//
// In a situation like `foo |`, we want FOO to be able to run...it
// may take 0 args or it may be able to tolerate END. But we should
// not be required to run the barrier in the same evaluative step
// as the left hand side. (It can be enfix, or it can not be.)
//
SET_FEED_FLAG(f->feed, BARRIER_HIT);
goto give_up_backward_quote_priority;
}
if (NOT_ACTION_FLAG(VAL_ACTION(unwrap(f_next_gotten)), ENFIXED))
goto give_up_backward_quote_priority;
blockscope {
REBACT *enfixed = VAL_ACTION(unwrap(f_next_gotten));
if (NOT_ACTION_FLAG(enfixed, QUOTES_FIRST))
goto give_up_backward_quote_priority;
// If the action soft quotes its left, that means it's aware that its
// "quoted" argument may be evaluated sometimes. If there's evaluative
// material on the left, treat it like it's in a group.
//
if (
GET_ACTION_FLAG(enfixed, POSTPONES_ENTIRELY)
or (
GET_FEED_FLAG(f->feed, NO_LOOKAHEAD)
and not ANY_SET_KIND(kind_current) // not SET-WORD!, SET-PATH!...
)
){
// !!! cache this test?
//
const REBPAR *first = First_Unspecialized_Param(nullptr, enfixed);
if (
VAL_PARAM_CLASS(first) == REB_P_SOFT
or VAL_PARAM_CLASS(first) == REB_P_MODAL
){
goto give_up_backward_quote_priority; // yield as an exemption
}
}
// Let the <skip> flag allow the right hand side to gracefully decline
// interest in the left hand side due to type. This is how DEFAULT works,
// such that `case [condition [...] default [...]]` does not interfere
// with the BLOCK! on the left, but `x: default [...]` gets the SET-WORD!
//
if (GET_ACTION_FLAG(enfixed, SKIPPABLE_FIRST)) {
const REBPAR *first = First_Unspecialized_Param(nullptr, enfixed);
if (not TYPE_CHECK(first, kind_current)) // left's kind
goto give_up_backward_quote_priority;
}
// Lookback args are fetched from f->out, then copied into an arg
// slot. Put the backwards quoted value into f->out.
//
Derelativize(f->out, v, v_specifier); // for NEXT_ARG_FROM_OUT
SET_CELL_FLAG(f->out, UNEVALUATED); // so lookback knows it was quoted
// We skip over the word that invoked the action (e.g. ->-, OF, =>).
// v will then hold a pointer to that word (possibly now resident in the
// frame spare). (f->out holds what was the left)
//
gotten = f_next_gotten;
v = Lookback_While_Fetching_Next(f);
if (
IS_END(f_next) // v-- out is what used to be on left
and (
KIND3Q_BYTE(f->out) == REB_WORD
or KIND3Q_BYTE(f->out) == REB_PATH
)
){
// We make a special exemption for left-stealing arguments, when
// they have nothing to their right. They lose their priority
// and we run the left hand side with them as a priority instead.
// This lets us do e.g. `(just =>)` or `help of`
//
// Swap it around so that what we had put in the f->out goes back
// to being in the lookback cell and can be used as current. Then put
// what was current into f->out so it can be consumed as the first
// parameter of whatever that was.
Copy_Cell(&f->feed->lookback, f->out);
Derelativize(f->out, v, v_specifier);
SET_CELL_FLAG(f->out, UNEVALUATED);
// leave *next at END
v = &f->feed->lookback;
gotten = nullptr;
SET_EVAL_FLAG(f, DIDNT_LEFT_QUOTE_PATH); // for better error message
SET_FEED_FLAG(f->feed, NEXT_ARG_FROM_OUT); // literal right op is arg
goto give_up_backward_quote_priority; // run PATH!/WORD! normal
}
}
// Wasn't the at-end exception, so run normal enfix with right winning.
//
blockscope {
DECLARE_ACTION_SUBFRAME (subframe, f);
Push_Frame(f->out, subframe);
Push_Action(
subframe,
VAL_ACTION(unwrap(gotten)),
VAL_ACTION_BINDING(unwrap(gotten))
);
Begin_Enfix_Action(subframe, VAL_WORD_SYMBOL(v));
goto process_action; }
give_up_backward_quote_priority:
//=//// BEGIN MAIN SWITCH STATEMENT /////////////////////////////////////=//
// This switch is done with a case for all REB_XXX values, in order to
// facilitate use of a "jump table optimization":
//
// http://stackoverflow.com/questions/17061967/c-switch-and-jump-tables
//
// Subverting the jump table optimization with specialized branches for
// fast tests like ANY_INERT() and IS_NULLED_OR_VOID_OR_END() has shown
// to reduce performance in practice. The compiler does the right thing.
switch (KIND3Q_BYTE(v)) { // checked version (once, else kind_current)
case REB_0_END:
goto finished;
//=//// NULL //////////////////////////////////////////////////////////=//
//
// Since nulled cells can't be in BLOCK!s, the evaluator shouldn't usually
// see them. It is technically possible to see one using REEVAL, such as
// with `reeval first []`. However, the more common way to encounter this
// situation would be in the API:
//
// REBVAL *v = nullptr;
// bool is_null = rebDid("null?", v); // oops, should be rebQ(v)
//
// Note: It seems tempting to let NULL evaluate to NULL as a convenience
// for such cases. But this breaks the system in subtle ways--like
// making it impossible to "reify" the instruction stream as a BLOCK!
// for the debugger. Mechanically speaking, this is best left an error.
case REB_NULL:
fail (Error_Evaluate_Null_Raw());
//=//// COMMA! ////////////////////////////////////////////////////////=//
//
// A comma is a lightweight looking expression barrier.
case REB_COMMA:
if (GET_EVAL_FLAG(f, FULFILLING_ARG)) {
CLEAR_FEED_FLAG(f->feed, NO_LOOKAHEAD);
SET_FEED_FLAG(f->feed, BARRIER_HIT);
goto finished;
}
break;
//=//// ACTION! ///////////////////////////////////////////////////////=//
//
// If an action makes it to the SWITCH statement, that means it is either
// literally an action value in the array (`do compose [1 (:+) 2]`) or is
// being retriggered via REEVAL.
//
// Most action evaluations are triggered from a WORD! or PATH! case.
case REB_ACTION: {
DECLARE_ACTION_SUBFRAME (subframe, f);
Push_Frame(f->out, subframe);
Push_Action(subframe, VAL_ACTION(v), VAL_ACTION_BINDING(v));
Begin_Prefix_Action(subframe, VAL_ACTION_LABEL(v));
// We'd like `10 -> = 5 + 5` to work, and to do so it reevaluates in
// a new frame, but has to run the `=` as "getting its next arg from
// the output slot, but not being run in an enfix mode".
//
if (NOT_FEED_FLAG(subframe->feed, NEXT_ARG_FROM_OUT))
Expire_Out_Cell_Unless_Invisible(subframe);
goto process_action; }
//=//// ACTION! ARGUMENT FULFILLMENT AND/OR TYPE CHECKING PROCESS /////=//
// This one processing loop is able to handle ordinary action
// invocation, specialization, and type checking of an already filled
// action frame. It walks through both the formal parameters (in
// the spec) and the actual arguments (in the call frame) using
// pointer incrementation.
//
// Based on the parameter type, it may be necessary to "consume" an
// expression from values that come after the invocation point. But
// not all parameters will consume arguments for all calls.
process_action: {
FS_TOP->dsp_orig = f->dsp_orig; // !!! How did this work in stackless
// Gather args and execute function (the arg gathering makes nested
// eval calls that lookahead, but no lookahead after the action runs)
//
bool threw = Process_Action_Maybe_Stale_Throws(FS_TOP);
assert(NOT_FEED_FLAG(f->feed, NEXT_ARG_FROM_OUT)); // must consume
if (threw) {
Abort_Frame(FS_TOP);
goto return_thrown;
}
Drop_Frame(FS_TOP);
// The Action_Executor does not get involved in Lookahead; so you
// only get lookahead behavior when an action has been spawned from
// a parent frame (such as one evaluating a block, or evaluating an
// action's arguments). Trying to dispatch lookahead from the
// Action_Executor causes pain with `null then [x] => [1] else [2]`
// cases (for instance).
//
// However, the evaluation of an invisible can leave a stale value
// which indicates a need to invoke another evaluation. Consider
// `do [comment "hi" 10]`.
//
if (
GET_EVAL_FLAG(f, FULFILLING_ARG)
and GET_CELL_FLAG(f->out, OUT_NOTE_STALE)
and NOT_END(f_next)
){
gotten = f_next_gotten;
v = Lookback_While_Fetching_Next(f);
goto evaluate;
}
break; }
//=//// WORD! /////////////////////////////////////////////////////////=//
//
// A plain word tries to fetch its value through its binding. It fails
// if the word is unbound (or if the binding is to a variable which is
// set, but VOID!). Should the word look up to an action, then that
// action will be invoked.
//
// NOTE: The usual dispatch of enfix functions is *not* via a REB_WORD in
// this switch, it's by some code at the `lookahead:` label. You only see
// enfix here when there was nothing to the left, so cases like `(+ 1 2)`
// or in "stale" left hand situations like `10 comment "hi" + 20`.
process_word:
case REB_WORD:
if (not gotten)
gotten = Lookup_Word_May_Fail(v, v_specifier);
if (IS_ACTION(unwrap(gotten))) { // before IS_VOID() is common case
REBACT *act = VAL_ACTION(unwrap(gotten));
if (GET_ACTION_FLAG(act, ENFIXED)) {
if (
GET_ACTION_FLAG(act, POSTPONES_ENTIRELY)
or GET_ACTION_FLAG(act, DEFERS_LOOKBACK)
){
if (GET_EVAL_FLAG(f, FULFILLING_ARG)) {
CLEAR_FEED_FLAG(f->feed, NO_LOOKAHEAD);
SET_FEED_FLAG(f->feed, DEFERRING_ENFIX);
SET_END(f->out);
goto finished;
}
}
}
DECLARE_ACTION_SUBFRAME (subframe, f);
Push_Frame(f->out, subframe);
Push_Action(subframe, act, VAL_ACTION_BINDING(unwrap(gotten)));
Begin_Action_Core(
subframe,
VAL_WORD_SYMBOL(v), // use word as label
GET_ACTION_FLAG(act, ENFIXED)
);
goto process_action;
}
if (IS_VOID(unwrap(gotten))) // need GET/ANY if it's void ("undefined")
fail (Error_Need_Non_Void_Core(v, v_specifier, unwrap(gotten)));
Copy_Cell(f->out, unwrap(gotten)); // no copy CELL_FLAG_UNEVALUATED
Decay_If_Nulled(f->out);
break;
//=//// SET-WORD! /////////////////////////////////////////////////////=//
//
// Right side is evaluated into `out`, and then copied to the variable.
//
// Null and void assigns are allowed: https://forum.rebol.info/t/895/4
process_set_word:
case REB_SET_WORD: {
if (Rightward_Evaluate_Nonvoid_Into_Out_Throws(f, v)) // see notes
goto return_thrown;
set_word_with_out:
Copy_Cell(Sink_Word_May_Fail(v, v_specifier), f->out);
break; }
//=//// GET-WORD! /////////////////////////////////////////////////////=//
//
// A GET-WORD! does no dispatch on functions. It will fetch other values
// as normal, but will error on VOID! and direct you to GET/ANY.
//
// This handling of voids matches Rebol2 behavior, choosing to break with
// R3-Alpha and Red which will give back "voided" values ("UNSET!").
// The choice was made to make typos less likely to bite those whose
// intent with GET-WORD! was merely to use ACTION!s inertly:
//
// https://forum.rebol.info/t/1301
process_get_word:
case REB_GET_WORD:
if (not gotten)
gotten = Lookup_Word_May_Fail(v, v_specifier);
if (IS_VOID(unwrap(gotten)))
fail (Error_Need_Non_Void_Core(v, v_specifier, unwrap(gotten)));
Copy_Cell(f->out, unwrap(gotten));
Decay_If_Nulled(f->out);
if (IS_ACTION(unwrap(gotten))) // cache the word's label in the cell
INIT_VAL_ACTION_LABEL(f->out, VAL_WORD_SYMBOL(v));
break;
//=//// GROUP! ////////////////////////////////////////////////////////=//
//
// A GROUP! whose contents wind up vaporizing wants to be invisible:
//
// >> 1 + 2 ()
// == 3
//
// >> 1 + 2 (comment "hi")
// == 3
//
// But there's a limit with group invisibility and enfix. A single step
// of the evaluator only has one lookahead, because it doesn't know if it
// wants to evaluate the next thing or not:
//
// >> evaluate [1 (2) + 3]
// == [(2) + 3] ; takes one step...so next step will add 2 and 3
//
// >> evaluate [1 (comment "hi") + 3]
// == [(comment "hi") + 3] ; next step errors: + has no left argument
//
// It is supposed to be possible for DO to be implemented as a series of
// successive single EVALUATE steps, giving no input beyond the block. So
// that means even though the `f->out` may technically still hold bits of
// the last evaluation such that `do [1 (comment "hi") + 3]` *could* draw
// from them to give a left hand argument, it should not do so...and it's
// why those bits are marked "stale".
//
// The right of the operator is different story. Turning up no result,
// the group can just invoke a reevaluate without breaking any rules:
//
// >> evaluate [1 + (2) 3]
// == [3]
//
// >> evaluate [1 + (comment "hi") 3]
// == []
//
// This subtlety means running a GROUP! must be able to notice when no
// result was produced (an output of END) and then re-trigger a step in
// the parent frame, e.g. to pick up the 3 above.
case REB_GROUP:
eval_group: {
f_next_gotten = nullptr; // arbitrary code changes fetched variables
// The IS_VOID() case here is specifically for REEVAL with invisibles,
// because it's desirable for `void? reeval :comment "hi" 1` to be
// 1 and not #[false]. The problem is that REEVAL is not invisible,
// and hence it wants to make sure something is written to the output
// so that standard invisibility doesn't kick in...hence it preloads
// with a non-stale void.
//
assert(
IS_END(f->out)
or GET_CELL_FLAG(f->out, OUT_NOTE_STALE)
or IS_VOID(f->out)
);
DECLARE_FEED_AT_CORE (subfeed, v, v_specifier);
// "Maybe_Stale" variant leaves f->out as-is if no result generated
// However, it sets OUT_NOTE_STALE in that case (note we may be
// leaving an END in f->out by doing this.)
//
// !!! Review why the stale bit was left here. It must be cleared
// if the group evaluation finished, otherwise `any [(10 elide "hi")]`
// would result in NULL instead of 10.
//
if (Do_Feed_To_End_Maybe_Stale_Throws(
f->out,
subfeed,
EVAL_MASK_DEFAULT | EVAL_FLAG_ALLOCATED_FEED
)){
goto return_thrown;
}
// We want `3 = (1 + 2 ()) 4` to not treat the 1 + 2 as "stale", thus
// skipping it and trying to compare `3 = 4`. But `3 = () 1 + 2`
// should consider the empty group stale.
//
if (IS_END(f->out)) {
if (IS_END(f_next))
goto finished; // nothing after to try evaluating
gotten = f_next_gotten;
v = Lookback_While_Fetching_Next(f);
goto evaluate;
}
CLEAR_CELL_FLAG(f->out, UNEVALUATED); // `(1)` considered evaluative
CLEAR_CELL_FLAG(f->out, OUT_NOTE_STALE); // any [(10 elide "hi")]
break; }
//=//// PATH! and TUPLE! //////////////////////////////////////////////=//
//
// PATH! and GET-PATH! have similar mechanisms, with the difference being
// that if a PATH! looks up to an action it will execute it.
//
// Paths looking up to VOID! are handled consistently with WORD! and
// GET-WORD!, and will error...directing you use GET/ANY if fetching
// voids is what you actually intended.
//
// PATH!s starting with inert values do not evaluate. `/foo/bar` has a
// blank at its head, and it evaluates to itself.
//
// !!! The dispatch of TUPLE! is a work in progress, with concepts about
// being less willing to execute functions under some notations.
case REB_PATH:
case REB_TUPLE: {
if (HEART_BYTE(v) == REB_WORD)
goto process_word; // special `/` or `.` case with hidden word
const RELVAL *head = VAL_SEQUENCE_AT(f_spare, v, 0);
if (ANY_INERT(head)) {
Derelativize(f->out, v, v_specifier);
break;
}
// !!! This is a special exemption added so that BLANK!-headed tuples
// at the head of a PATH! carry over the inert evaluative behavior.
// (The concept of evaluator treatment of PATH!s and TUPLE!s is to
// not heed them structurally, but merely to see them as a sequence
// of ordered dots and slashes...it will have to be seen how this
// ultimately plays out.)
//
if (IS_TUPLE(head)) {
//
// VAL_SEQUENCE_AT() allows the same use of the `store` as the
// sequence, which may be the case if it wrote spare above.
//
if (IS_BLANK(VAL_SEQUENCE_AT(f_spare, head, 0))) {
Derelativize(f->out, v, v_specifier);
break;
}
}
REBVAL *where = GET_FEED_FLAG(f->feed, NEXT_ARG_FROM_OUT)
? f_spare
: f->out;
if (Eval_Path_Throws_Core(
where,
v, // !!! may not be array-based
v_specifier,
nullptr, // `setval`: null means don't treat as SET-PATH!
EVAL_MASK_DEFAULT | EVAL_FLAG_PUSH_PATH_REFINES
)){
if (where != f->out)
Copy_Cell(f->out, where);
goto return_thrown;
}
if (IS_ACTION(where)) { // try this branch before fail on void+null
REBACT *act = VAL_ACTION(where);
// PATH! dispatch is costly and can error in more ways than WORD!:
//
// e: trap [do make block! ":a"] e/id = 'not-bound
// ^-- not ready @ lookahead
//
// Plus with GROUP!s in a path, their evaluations can't be undone.
//
if (GET_ACTION_FLAG(act, ENFIXED))
fail ("Use `>-` to shove left enfix operands into PATH!s");
DECLARE_ACTION_SUBFRAME (subframe, f);
Push_Frame(f->out, subframe);
Push_Action(
subframe,
VAL_ACTION(where),
VAL_ACTION_BINDING(where)
);
Begin_Prefix_Action(subframe, VAL_ACTION_LABEL(where));
if (where == subframe->out)
Expire_Out_Cell_Unless_Invisible(subframe);
goto process_action;
}
if (IS_VOID(where)) // need `:x/y` if it's void (unset)
fail (Error_Need_Non_Void_Core(v, v_specifier, where));
if (where != f->out)
Copy_Cell(f->out, where); // won't move CELL_FLAG_UNEVALUATED
else
CLEAR_CELL_FLAG(f->out, UNEVALUATED);
Decay_If_Nulled(f->out);
break; }
//=//// SET-PATH! /////////////////////////////////////////////////////=//
//
// See notes on SET-WORD! SET-PATH!s are handled in a similar way.
//
// !!! The evaluation ordering is dictated by the fact that there isn't a
// separate "evaluate path to target location" and "set target' step.
// This is because some targets of assignments (e.g. gob/size/x:) do not
// correspond to a cell that can be returned; the path operation "encodes
// as it goes" and requires the value to set as a parameter. Yet it is
// counterintuitive given the "left-to-right" nature of the language:
//
// >> foo: make object! [[bar][bar: 10]]
//
// >> foo/(print "left" 'bar): (print "right" 20)
// right
// left
// == 20
//
// VOID! and NULL assigns are allowed: https://forum.rebol.info/t/895/4
case REB_SET_PATH:
case REB_SET_TUPLE: {
if (HEART_BYTE(v) == REB_WORD) {
assert(VAL_WORD_ID(v) == SYM__SLASH_1_);
goto process_set_word;
}
if (Rightward_Evaluate_Nonvoid_Into_Out_Throws(f, v))
goto return_thrown;
set_path_with_out:
if (Eval_Path_Throws_Core(
f_spare, // output if thrown, used as scratch space otherwise
v, // !!! may not be array-based
v_specifier,
f->out,
EVAL_MASK_DEFAULT // evaluating GROUP!s ok
)){
Copy_Cell(f->out, f_spare);
goto return_thrown;
}
break; }
//=//// GET-PATH! and GET-TUPLE! //////////////////////////////////////=//
//
// Note that the GET native on a PATH! won't allow GROUP! execution:
//
// foo: [X]
// path: 'foo/(print "side effect!" 1)
// get path ; not allowed, due to surprising side effects
//
// However a source-level GET-PATH! allows them, since they are at the
// callsite and you are assumed to know what you are doing:
//
// :foo/(print "side effect" 1) ; this is allowed
//
// Consistent with GET-WORD!, a GET-PATH! won't allow VOID! access.
case REB_GET_PATH:
case REB_GET_TUPLE:
if (HEART_BYTE(v) == REB_WORD) {
assert(VAL_WORD_ID(v) == SYM__SLASH_1_);
goto process_get_word;
}
if (Get_Path_Throws_Core(f->out, v, v_specifier))
goto return_thrown;
if (IS_VOID(f->out)) // need GET/ANY if it's void ("undefined")
fail (Error_Need_Non_Void_Core(v, v_specifier, f->out));
// !!! This didn't appear to be true for `-- "hi" "hi"`, processing
// GET-PATH! of a variadic. Review if it should be true.
//
/* assert(NOT_CELL_FLAG(f->out, CELL_FLAG_UNEVALUATED)); */
CLEAR_CELL_FLAG(f->out, UNEVALUATED);
Decay_If_Nulled(f->out);
break;
//=//// GET-GROUP! ////////////////////////////////////////////////////=//
//
// This was initially conceived such that `:(x)` was a shorthand for the
// expression `get x`. But that's already pretty short--and arguably a
// cleaner way of saying the same thing. So instead, it's given the same
// meaning in the evaluator as plain GROUP!...which seems wasteful on the
// surface, but it means dialects can be free to use it to make a
// distinction. For instance, it's used to escape soft quoted slots.
case REB_GET_GROUP:
goto eval_group;
//=//// SET-GROUP! ////////////////////////////////////////////////////=//
//
// Synonym for SET on the produced thing, unless it's an action...in which