Skip to content

Commit

Permalink
PRINT/FAIL new design, recursion, |, BINARY! UTF8
Browse files Browse the repository at this point in the history
This adds features to PRINT and FAIL, and offers the functionality to
make a string via FORM/NEW (because there is speculation that FORM may
wind up being the word taken for this behavior, despite breaking legacy
compatibility with the quoted-vs-non-quoting default).

Largely this is folding a native implementation of what was called
"COMBINE" into the basic behavior of PRINT.  Changes are:

BINARY! is not molded as a Rebol constant, rather interpreted as UTF8;
if Rebol molding is desired that must be requested explicitly:

    >> print ["Hello" #{52656E2D43} "World"]
    Hello Ren-C World

    >> print ["Hello" mold #{52656E2D43} "World"]
    Hello #{52656E2D43} World

Blocks can be used as in PARSE for grouping or recursion.  Also as in
PARSE, this creates the opportunity for stack overflows (but if one
is running a general evaluation anyway, you could have already done
that if you wanted).

    >> x-rule: ["The value of x is" space x]
    == ["The value of x is" space x]

    >> print [(x: 10 |) x-rule newline (x: 20 |) x-rule]
    The value of x is 10
    The value of x is 20

By default, the top level of the block is separated by spaces while
nested blocks are not:

    >> print ["Line" "One" ["Line" "Two"]]
    Line One LineTwo

This may be overridden by /DELIMIT, which is able to take either a
single delimiter or a block of delimiters--with each element of the
block corresponding to a depth:

    >> print/delimit [
            "Level" ["Level" ["Level" "Three"] "Two"] "One"
        ] ["1" "2" "3"]

    Level1Level2Level3Three2Two1One

When the block runs out of delimiters but there is an extra level of
depth, it will repeat the last delimiter.  NONE! or BAR! may be used
to suppress this.  Hence the default delimiter is [#" " |].

The behavior of BAR! is not yet customizable, but follows the same
logic--to insert different characters at different depths, but to also
suppress other delimiting.  At the outermost level it acts as a newline
while inner levels it acts as a space, by default:

    >> print [["a" "b"] | "c" "d" | ["e" | "f"]]
    ab
    c d
    e f

Included in the design is the suppression of delimiting when content
does not add to the output.

    >> print/delimit ["the" {} ["brown" () "fox"]] "; "
    the; brown; fox

A quoted version is also provided:

    >> print/quote [a b | [1 + 2]]
    a b
    1+2
  • Loading branch information
hostilefork committed Jan 26, 2016
1 parent 7c34552 commit bdecddd
Show file tree
Hide file tree
Showing 6 changed files with 435 additions and 107 deletions.
6 changes: 6 additions & 0 deletions src/boot/root.r
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ return-native
leave-native
parse-native

;; PRINT takes a /DELIMIT which can be a block specifying delimiters at each
;; level of depth in the recursion of blocks. The default is [#" " |], which
;; is a signal to put spaces at the first level and then after that nothing.
;;
default-print-delimiter

;; The BREAKPOINT instruction needs to be able to re-transmit a RESUME
;; instruction in the case that it wants to leapfrog another breakpoint
;; sandbox on the stack, and needs access to the resume native for the label
Expand Down
12 changes: 12 additions & 0 deletions src/core/b-init.c
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,18 @@ static void Init_Root_Context(void)
SET_ARR_FLAG(VAL_ARRAY(ROOT_LEAVE_BLOCK), SERIES_FLAG_LOCKED);
SET_ARR_FLAG(VAL_ARRAY(ROOT_LEAVE_BLOCK), SERIES_FLAG_FIXED_SIZE);

// Used by REBNATIVE(print)
//
Val_Init_Block(ROOT_DEFAULT_PRINT_DELIMITER, Make_Array(2));
SET_CHAR(VAL_ARRAY_HEAD(ROOT_DEFAULT_PRINT_DELIMITER), ' ');
SET_BAR(VAL_ARRAY_AT_HEAD(ROOT_DEFAULT_PRINT_DELIMITER, 1));
SET_END(VAL_ARRAY_AT_HEAD(ROOT_DEFAULT_PRINT_DELIMITER, 2));
SET_ARRAY_LEN(VAL_ARRAY(ROOT_DEFAULT_PRINT_DELIMITER), 2);
SET_ARR_FLAG(VAL_ARRAY(ROOT_DEFAULT_PRINT_DELIMITER), SERIES_FLAG_LOCKED);
SET_ARR_FLAG(
VAL_ARRAY(ROOT_DEFAULT_PRINT_DELIMITER), SERIES_FLAG_FIXED_SIZE
);

// We can't actually put an end value in the middle of a block, so we poke
// this one into a program global. We also dynamically allocate it in
// order to get uninitialized memory for everything but the header (if
Expand Down
297 changes: 292 additions & 5 deletions src/core/d-print.c
Original file line number Diff line number Diff line change
Expand Up @@ -951,14 +951,273 @@ void Form_Args(REB_MOLD *mo, const char *fmt, ...)
**
***********************************************************************/

static const REBVAL *Pending_Format_Delimiter(
const REBVAL* delimiter,
REBCNT depth
) {
if (!IS_BLOCK(delimiter))
return delimiter;

if (VAL_ARRAY_LEN_AT(delimiter) == 0)
return NONE_VALUE;

if (depth >= VAL_ARRAY_LEN_AT(delimiter))
depth = VAL_ARRAY_LEN_AT(delimiter) - 1;

// We allow the block passed in to not be at the head, so we have to
// account for the index and offset from it.
//
delimiter = VAL_ARRAY_AT_HEAD(delimiter, VAL_INDEX(delimiter) + depth);

// BAR! at the end signals a stop, not to keep repeating the last
// delimiter for higher levels. Same effect as a NONE!
//
if (IS_BAR(delimiter) || IS_NONE(delimiter))
return NONE_VALUE;

// We have to re-type-check against the legal delimiter types here.
//
if (ANY_STRING(delimiter) || IS_CHAR(delimiter))
return delimiter;

// !!! TBD: Error message, more comprehensive type check
//
fail (Error(RE_MISC));
}


//
// Prin_Value: C
// Format_GC_Safe_Value_Throws: C
//
// This implements new logic (which should ultimately also power FAIL, FORM,
// and other print-like things). The dialect will recurse on evaluated
// blocks, treat literal blocks as grouping, and a BAR! `|` signals a
// newline (as well as acting as an expression barrier)
//
REBOOL Format_GC_Safe_Value_Throws(
REBVAL *out,
REB_MOLD *mold,
REBVAL *pending_delimiter,
const REBVAL *val_gc_safe,
REBOOL reduce,
const REBVAL *delimiter,
REBCNT depth
) {
struct Reb_Call c;

if (IS_BLOCK(val_gc_safe)) {
c.value = VAL_ARRAY_AT(val_gc_safe);
if (IS_END(c.value))
return FALSE; // no mold output added on empty blocks

c.indexor = VAL_INDEX(val_gc_safe) + 1;
c.source.array = VAL_ARRAY(val_gc_safe);
}
else {
// Prefetch with value + an empty array to use same code path

c.value = val_gc_safe;
c.indexor = END_FLAG;
c.source.array = EMPTY_ARRAY;
}

c.flags = DO_FLAG_NEXT | DO_FLAG_EVAL_NORMAL | DO_FLAG_LOOKAHEAD;
c.eval_fetched = NULL;

do {
if (IS_UNSET(c.value)) {
//
// Unsets format to nothing (!!! should NONE! do the same?)
//
FETCH_NEXT_ONLY_MAYBE_END(&c);
}
else if (IS_BAR(c.value)) {
//
// !!! At each depth BAR! is always a barrier. However, it may
// also mean inserting a delimiter. The default is to assume it
// means to insert a newline at the outermost level and then
// spaces at every inner level. This should be overrideable
// via something like a /bar refinement.

if (depth == 0)
Append_Codepoint_Raw(mold->series, '\n');
else
Append_Codepoint_Raw(mold->series, ' ');

SET_END(pending_delimiter);
FETCH_NEXT_ONLY_MAYBE_END(&c);
}
else if (IS_CHAR(c.value)) {
//
// Characters are inserted with no spacing. This is because the
// cases in which spaced-out-characters are most likely to be
// interesting are cases that are probably debug-oriented in
// which case MOLD should be used anyway.

assert(
SER_WIDE(mold->series) == sizeof(c.value->payload.character)
);
Append_Codepoint_Raw(mold->series, c.value->payload.character);

SET_END(pending_delimiter); // no delimiting before/after chars
FETCH_NEXT_ONLY_MAYBE_END(&c);
}
else if (IS_BINARY(c.value)) {
//
// Rather than introduce Rebol's specialized MOLD notation for
// BINARY! into ordinary printing, the assumption is that it
// should be interpreted as UTF-8 bytes.
//
if (VAL_LEN_AT(c.value) > 0) {
if (!IS_END(pending_delimiter) && !IS_NONE(pending_delimiter))
Mold_Value(mold, pending_delimiter, FALSE);

Append_UTF8_May_Fail(
mold->series, VAL_BIN_AT(c.value), VAL_LEN_AT(c.value)
);

*pending_delimiter
= *Pending_Format_Delimiter(delimiter, depth);
}

FETCH_NEXT_ONLY_MAYBE_END(&c);
}
else {
const REBVAL *item;
if (reduce) {
DO_NEXT_REFETCH_MAY_THROW(out, &c, DO_FLAG_LOOKAHEAD);
if (c.indexor == THROWN_FLAG)
return TRUE;

item = out;
}
else
item = c.value;

if (IS_BLOCK(item)) {
//
// If an expression was a block then recurse and consider it
// a new depth level. If it *evaluated* to a block, treat it
// the same--pretend the block was literally in the spot.

REBVAL maybe_thrown;
VAL_INIT_WRITABLE_DEBUG(&maybe_thrown);

if (reduce) PUSH_GUARD_VALUE(out);

if (Format_GC_Safe_Value_Throws(
&maybe_thrown, // not interested in value unless thrown
mold,
pending_delimiter,
item, // this level's output is recursion's input
reduce,
delimiter,
depth + 1
)) {
if (reduce) DROP_GUARD_VALUE(out);
*out = maybe_thrown;
return TRUE;
}

// If there's a delimiter pending (even a NONE!), then convert
// it back to pending the delimiter of the *outer* element.
//
if (!IS_END(pending_delimiter)) {
*pending_delimiter = *Pending_Format_Delimiter(
delimiter, depth
);
}

if (reduce)
DROP_GUARD_VALUE(out);
else
FETCH_NEXT_ONLY_MAYBE_END(&c);
}
else if (reduce) {
//
// If we got here via a reduction step, we might have gotten
// a BINARY! or a BAR! or some other type. Don't call MOLD
// directly because it won't necessarily do what this routine
// wants...recurse with reduce=FALSE to pick those up.

Format_GC_Safe_Value_Throws(
NULL, // can't throw--no need for output slot
mold,
pending_delimiter,
item, // this level's output is recursion's input
FALSE, // don't reduce the value again
delimiter,
depth // not nested block so no depth increment
);

// If there's a delimiter pending (even a NONE!), then convert
// it back to pending the delimiter of the *outer* element.
//
if (!IS_END(pending_delimiter)) {
*pending_delimiter
= *Pending_Format_Delimiter(delimiter, depth);
}

// The DO_NEXT already refetched...
}
else {
// This is where the recursion bottoms out...the need to FORM
// a terminal value. We don't know in advance if the forming
// will produce output, and if it doesn't we suppress the
// delimiter...so to do that, we have to roll back.

REBCNT rollback_point = UNI_LEN(mold->series);
REBCNT mold_point;

if (!IS_END(pending_delimiter) && !IS_NONE(pending_delimiter))
Mold_Value(mold, pending_delimiter, FALSE);

mold_point = UNI_LEN(mold->series);

Mold_Value(mold, item, FALSE);

if (UNI_LEN(mold->series) == mold_point) {
//
// The mold didn't add anything, so roll back and don't
// update the pending delimiter.
//
SET_UNI_LEN(mold->series, rollback_point);
UNI_TERM(mold->series);
SET_NONE(pending_delimiter);
}
else {
*pending_delimiter
= *Pending_Format_Delimiter(delimiter, depth);
}

FETCH_NEXT_ONLY_MAYBE_END(&c);
}
}

if (c.indexor == END_FLAG)
break;
} while (TRUE);

return FALSE;
}


//
// Prin_GC_Safe_Value_Throws: C
//
// Print a value or block's contents for user viewing.
// Can limit output to a given size. Set limit to 0 for full size.
//
void Prin_Value(const REBVAL *value, REBCNT limit, REBOOL mold)
{
REBOOL Prin_GC_Safe_Value_Throws(
REBVAL *out_if_reduce,
const REBVAL *value,
const REBVAL *delimiter,
REBCNT limit,
REBOOL mold,
REBOOL reduce
) {
REBVAL pending_delimiter;

REB_MOLD mo;
CLEARS(&mo);
if (limit != 0) {
Expand All @@ -967,7 +1226,25 @@ void Prin_Value(const REBVAL *value, REBCNT limit, REBOOL mold)
}
Push_Mold(&mo);

Mold_Value(&mo, value, mold);
VAL_INIT_WRITABLE_DEBUG(&pending_delimiter);
SET_END(&pending_delimiter);

if (mold)
Mold_Value(&mo, value, TRUE);
else {
if (Format_GC_Safe_Value_Throws(
out_if_reduce,
&mo,
&pending_delimiter,
value,
LOGICAL(reduce && IS_BLOCK(value)), // `print 'word` won't GET it
delimiter,
0 // depth
)) {
return TRUE;
}
}

Throttle_Mold(&mo); // not using Pop_Mold(), must do explicitly

assert(SER_WIDE(mo.series) == sizeof(REBUNI));
Expand All @@ -978,6 +1255,7 @@ void Prin_Value(const REBVAL *value, REBCNT limit, REBOOL mold)
);

Drop_Mold(&mo);
return FALSE;
}


Expand All @@ -989,7 +1267,16 @@ void Prin_Value(const REBVAL *value, REBCNT limit, REBOOL mold)
//
void Print_Value(const REBVAL *value, REBCNT limit, REBOOL mold)
{
Prin_Value(value, limit, mold);
// Note: Does not reduce
//
REBVAL delimiter;
VAL_INIT_WRITABLE_DEBUG(&delimiter);
SET_CHAR(&delimiter, ' ');

(void)Prin_GC_Safe_Value_Throws(
NULL, value, &delimiter, limit, mold, FALSE
);

Print_OS_Line();
}

Expand Down
Loading

0 comments on commit bdecddd

Please sign in to comment.