Package org.ek9lang.compiler.support.streams


package org.ek9lang.compiler.support.streams
THE classification of stream stages by what they need from the stream - use these classes, never a list of stage tokens of your own. Phase 3 (E10050 on an unbounded IO source), phase 5 (E10050 on a source proved infinite) and phase 7 (the demand rule, StreamDemand) all ask here, so the three cannot disagree about which stage drains. Each rule is one class, held as an instance by the class that asks.

Two questions, deliberately separate:

  • EndsDemandSegment - is the stage lowered as a drain (decision 10, rule B)? A head after it cannot bound what flows INTO it. This is the never-completes axis.
  • WaitsForEnd - does the stage emit nothing until its input has ended? That is every drain except async, which emits as tasks complete. On an INFINITE source such a stage never emits at all.

⚠️ uniq and a retaining tee are NOT here. They stream - cat Forever() | uniq | head 2 terminates under rule B - and are refused on Stdin/UDP/TCPConnection by phase 3 for MEMORY, not because they wait for the end. Phase 3 adds them to its own predicate; an infinite source must not inherit them (decision 15(a), P1).

The other rules: IsAsyncBarrier, KeepsUnsetElements, KeepsUnsetIntoList, ReplacesTargetOnCompletion (D77, with SinkBufferFor and NeedsFreshValueError), OutputStepFor, IsOutputChannel, IsWriteThroughSink, IsCollectionBuffer, IsForRangeBuffer, and THE boundedness walk, BoundednessWalk.

  • Class
    Description
    True when any stage's operator is opType.
    The outcome of walking a pipeline's stages from a source that is not bounded (BoundednessWalk).
    THE walk of a pipeline whose source may be unbounded: head bounds the stream; while it is not bounded every stage needsBounded accepts is a violation; and once an async has run on the unbounded stream, an async barrier (IsAsyncBarrier) is a violation even after a head.
    True for a stage that is lowered as a drain (or emits only after its loop): a head downstream of it cannot bound what flows into it, so the demand segment ends there.
    The index of the first stage whose operator is opType, or -1.
    True for a stage that is a pre-loop barrier after an async: on a source that is not bounded before the async, a later head bounds only the async's output, and the windowed async driver cannot feed a trailing barrier - the head must come before the async.
    True for a stage that buffers its input into a collection (group, split, async): the stages a pre-sort drain must not run through, because it would treat them as a no-op pre-sort stage.
    True for a stage that forces a for-range stream down the BUFFERED (drain-to-buffer) path: every stage that ends the demand segment (EndsDemandSegment) except join, which streams (it emits its result after the loop, so the range needs no buffer for it).
    Is the type the named built-in type itself, or does it have it as a trait: IS-A by the type hierarchy.
    THE test of a built-in OUTPUT CHANNEL (Steve, 2026-10-27: "> udpConnection and > stdout, > stderr all feel normal"): the type IS-A org.ek9.lang::IO - Stdout, Stderr, a file output, a UDP or TCP connection, a StringOutput, and a user type taking those traits.
    A sink is write-through - it never accumulates what it is given - only when its type IS-A org.ek9.lang::StringOutput (Stdout, Stderr, a StringOutput).
    True for a stage whose buffer keeps UNSET elements, which is EVERY stage lowered with a buffer (each stage that ends the demand segment, EndsDemandSegment: sort, tail, join, group, split, async): a stream STAGE never drops an unset element - removing them is the developer's filter (Steve, 2026-10-21: "async also passes unset results"; the D24 sort rule, 2026-10-20, places them after every sorted group).
    True when a TERMINAL's target is a List (the built-in, any parameterisation): collect as List of X, > list, >> list and a tee ... in list target append each element with the List's += (_addAss), which keeps an unset element, never with its | (_pipe), which drops it (Steve, 2026-10-21: "keep them through +=").
    True when a > would have to replace its target but the target cannot make a fresh value (E08278): it is abstract or a trait, has no accessible no-argument constructor, or has no :=: to swap the fresh value in.
    What a stream statement does to an output channel (IsOutputChannel, e.g.
    THE decision of which OutputStep a termination gives its sink: > into an output channel clears, >> appends, anything that is not a channel (a collection, a fold, a user type) gets none.
    True when a stream statement's > REPLACES its target when the pipeline completes (Steve, 2026-10-21: "> 'clobbers' and >> 'appends'", the Unix shell meaning; made ALL-OR-NOTHING the same day, and extended to a PriorityQueue and a JSON: "> always means REPLACE"; to a scalar 2026-10-26; and finally to EVERY target, no type list, 2026-10-27: "let's make > consistent"): the termination is > (never >>) and the target is anything but an output channel (IsOutputChannel).
    Where the fresh sink of a > that replaces comes from.
    THE decision of SinkBuffer for a target type (asked beside ReplacesTargetOnCompletion, which is defined by it).
    A stream statement's termination as the replace decision reads it.
    Is target the built-in generic itself or a parameterisation of it.
    True for a stage that emits nothing until its whole input has ended: every stage that ends the demand segment (EndsDemandSegment) except async, which emits each result as its task completes.