Skip to content

Commit

Permalink
Kill off grievous @ hack which decayed quasiforms
Browse files Browse the repository at this point in the history
Suppressing evaluation in the API was at first done with rebQ():

     rebElide("append block", rebQ(word));

A nice trick came along to use the @ operator, which was cleaner:

     rebElide("append block @", word);

But by design you can't splice antiforms in feeds.  So this wouldn't work for
antiforms like NULL/etc.

A hack was put in that the feed splicing mechanic would splice antiforms as
quasiforms, and then @ would turn quasiforms into antiforms:

     >> @ ~null~
     == ~null~  ; anti

It gave the impression of working, because it wasn't too common to use
quasiforms.  But it had negative effects of splicing antiforms in places that
were not expecting them (e.g. actions would wind up not running when they
should likely have run), and it was broken in handling quasiforms.

This redoes the mechanism so the feed splices a meta form for antiforms and
voids, with a note about the fact that it has done that.  Only the @ operator
will tolerate the situation, other code will error when it reaches that
point in the feed.
  • Loading branch information
hostilefork committed Feb 27, 2024
1 parent 27aeb8b commit f8ac583
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 35 deletions.
23 changes: 7 additions & 16 deletions src/core/evaluator/c-eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -563,24 +563,19 @@ Bounce Evaluator_Executor(Level* L)
//
// & acts like TYPE OF
//
// @ acts like THE with the tweak that it turns quasiforms to antiforms:
// @ acts like THE:
//
// >> @ abc
// == abc
//
// >> @ ~null~
// == ~null~ ; anti
//
// This is done as a convenience for the API so people can write:
// 1. There's a twist, that @ can actually handle antiforms if they are
// coming in via an API feed. This is a convenience so you can write:
//
// rebElide("append block maybe @", value_might_be_null);
// rebElide("append block maybe @", value_might_be_null);
//
// ...instead of:
// ...instead of:
//
// rebElide("append block maybe", rebQ(value_might_be_null));
//
// Because this breaks quasiforms, tighter integration with the feed
// machinery would be better (now possible as @ is built-in!)
// rebElide("append block maybe", rebQ(value_might_be_null));
//

case REB_SIGIL: {
Expand All @@ -595,11 +590,7 @@ Bounce Evaluator_Executor(Level* L)
if (Is_Feed_At_End(L->feed)) // no literal to take if `(@)`
fail (Error_Need_Non_End(L_current));

const Element* at = At_Feed(L->feed);
Inertly_Derelativize_Inheriting_Const(OUT, at, L->feed);

if (Is_Quasiform(OUT)) // !!! hacky behavior
Meta_Unquotify_Undecayed(OUT);
Copy_At_Feed_Antiforms_Ok(OUT, L->feed); // special API trick [1]

Fetch_Next_In_Feed(L->feed); // !!! review enfix interop
break; }
Expand Down
5 changes: 4 additions & 1 deletion src/core/l-scan.c
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,10 @@ static Token Maybe_Locate_Token_May_Push_Mold(
return TOKEN_END;

case DETECTED_AS_CELL: {
Copy_Reified_Variadic_Feed_Cell(PUSH(), L->feed);
Copy_Reified_Variadic_Feed_Cell(
PUSH(),
c_cast(Cell*, L->feed->p)
);
if (Get_Scan_Executor_Flag(L, NEWLINE_PENDING)) {
Clear_Scan_Executor_Flag(L, NEWLINE_PENDING);
Set_Cell_Flag(TOP, NEWLINE_BEFORE);
Expand Down
1 change: 1 addition & 0 deletions src/include/structs/struct-cell.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ typedef struct StubStruct Stub; // forward decl for DEBUG_USE_UNION_PUNS
#define CELL_FLAG_NOTE_REMOVE CELL_FLAG_NOTE
#define CELL_FLAG_BIND_NOTE_REUSE CELL_FLAG_NOTE
#define CELL_FLAG_STACK_NOTE_SEALED CELL_FLAG_NOTE
#define CELL_FLAG_FEED_NOTE_META CELL_FLAG_NOTE


//=//// CELL_FLAG_NEWLINE_BEFORE //////////////////////////////////////////=//
Expand Down
75 changes: 57 additions & 18 deletions src/include/sys-feed.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,36 @@
INLINE const Element* At_Feed(Feed* feed) {
assert(Not_Feed_Flag(feed, NEEDS_SYNC));
assert(feed->p != &PG_Feed_At_End);
return c_cast(Element*, feed->p);

const Element* elem = c_cast(Element*, feed->p);
if (Get_Cell_Flag(elem, FEED_NOTE_META)) {
DECLARE_VALUE (temp);
Copy_Cell(temp, elem);
Meta_Unquotify_Known_Stable(temp);
fail (Error_Bad_Antiform(temp));
}
return elem;
}

INLINE const Value* Copy_At_Feed_Antiforms_Ok(Sink(Value*) out, Feed* feed) {
assert(Not_Feed_Flag(feed, NEEDS_SYNC));
assert(feed->p != &PG_Feed_At_End);

const Element* elem = c_cast(Element*, feed->p);
Copy_Cell(out, elem);

if (Get_Cell_Flag(elem, FEED_NOTE_META))
Meta_Unquotify_Known_Stable(out);

return out;
}

INLINE const Element* Try_At_Feed(Feed* feed) {
assert(Not_Feed_Flag(feed, NEEDS_SYNC));
return c_cast(Element*, feed->p);
if (feed->p == &PG_Feed_At_End)
return nullptr;

return At_Feed(feed);
}

INLINE Option(va_list*) FEED_VAPTR(Feed* feed) {
Expand Down Expand Up @@ -186,27 +210,39 @@ INLINE void Finalize_Variadic_Feed(Feed* feed) {
}


// A cell pointer in a variadic feed should be fine to use directly, because
// all such "spliced" cells should be specific.
// Function used by the scanning machinery when transforming a pointer from
// the variadic API feed (the pointer already identified as a cell).
//
// 1. The API enforces use of C's nullptr (0) as the signal for ~null~
// antiforms. (That's handled by a branch that skips this routine.)
// But internally, cells have an antiform WORD! payload for this case,
// and those internal cells are legal to pass to the API.
//
// 2. Various mechanics rely on the array feed being a "generic array", that
// can be put into a REB_BLOCK. This means it cannot hold antiforms
// (or voids). But we want to hold antiforms and voids in suspended
// animation in case there is an @ operator in the feed that will turn
// them back into those forms. So in those cases, meta it and set a
// cell flag to notify the At_Feed() machinery about the strange case
// (it will error, the @ code in the evaluator uses a different function).
//
INLINE const Element* Copy_Reified_Variadic_Feed_Cell(
Sink(Element*) out,
Feed* feed
const Cell* cell
){
const Value* v = c_cast(Value*, feed->p);

if (Is_Nulled(v)) // API enforces use of C's nullptr (0) for NULL
assert(not Is_Api_Value(v)); // but internal cells can be nulled

if (Is_Antiform(v)) { // @ will turn these back into antiforms
Copy_Meta_Cell(out, v);
return out;
if (Is_Nulled(cell))
assert(not Is_Api_Value(cell)); // only internals can be nulled [1]

if (
QUOTE_BYTE(cell) == ANTIFORM_0
or (QUOTE_BYTE(cell) == NOQUOTE_1 and HEART_BYTE(cell) == REB_VOID)
){
Copy_Meta_Cell(out, cell);
Set_Cell_Flag(out, FEED_NOTE_META); // @ turns back [2]
}
else
Copy_Cell(out, c_cast(Element*, cell));

if (Is_Void(v))
fail ("Attempt to splice void in variadic feed");

Copy_Cell(out, c_cast(Element*, v));
return out;
}

Expand Down Expand Up @@ -268,7 +304,10 @@ INLINE Option(const Value*) Try_Reify_Variadic_Feed_Series(

Value* single = Stub_Cell(inst1);
feed->p = single;
feed->p = Copy_Reified_Variadic_Feed_Cell(&feed->fetched, feed);
feed->p = Copy_Reified_Variadic_Feed_Cell(
&feed->fetched,
c_cast(Cell*, feed->p)
);
rebRelease(single); // *is* the instruction
break; }

Expand Down

0 comments on commit f8ac583

Please sign in to comment.