Blog › ICP guides

Rebol developer on retainer: parse dialect, series operations, block! code-and-data duality, Red reactive system, and Rebol/Red language engineering on monthly retainer

October 20, 2026 · ~18 min read

A configuration processing system built in Rebol had been producing wrong parse output on five nested block structures per day. The parser used a custom parse dialect rule to process hierarchical configuration files, where each top-level entry could contain either scalar values or nested block! sub-sections. The Rebol developer on retainer diagnosed the root cause: the parse rule used | alternation at the outer level without accounting for the fact that nested block! values were being passed to the rule as if they were the next token in the outer series, not as a sub-series to be recursed into. The | choice operator was selecting the wrong alternative because the block! value at the series position matched a word!-based rule that shared its first token with a keyword defined in the outer context. The fix required restructuring the rule to use into [any inner-rule] for block! content traversal — into temporarily descends into a block! or paren! value and applies the sub-rule to its contents, after which parsing resumes at the position after the block! in the outer series. Restructuring the rule with into for nested block! traversal and collect/keep for structured output assembly eliminated the ambiguity: wrong parse results: 5 per day → 0.

The work log entry read “fixed config parser rule, 9h.” It names the symptom and the duration. It cannot explain to a client why the fix required understanding that Rebol's parse dialect operates on series (ordered sequences of Rebol values) rather than on character streams, why | alternation in a parse rule selects the first matching alternative rather than the longest match, why nested block! structures require into rather than a recursive rule reference at the outer series level, or why the correct tool for extracting structured parse output without manual series manipulation is collect/keep. The diagnosis required understanding that a Rebol block! value appearing in a parse input series is a single value at that series position — the parse engine does not automatically descend into it. A parse rule [word! | block!] matches either a word! or a block! at the current position but does nothing with the block! content. The rule [word! | into [any sub-rule]] matches either a word! or a block! whose content satisfies any sub-rule, processing the block!'s interior as a new series context. The 9 hours of parse rule redesign (tracing the alternation failure through the value sequence), rule restructuring (into for block! descent, collect/keep for tree-shaped output), and regression testing across the configuration corpus are not visible in the diff beyond the restructured rule. The parse failures: gone. The structured output: correct.

Rebol’s parse dialect: rules, alternation, series traversal, and collect/keep

Rebol’s parse dialect is a pattern-matching engine that operates on series values — block!, string!, binary!, and path! — using rule blocks that describe sequences of patterns to match, quantities, and alternation. A parse rule is itself a block! value: parse [a b c] [word! word! word!] succeeds if the input series contains three word! values at the current position. The atomic rule types: a value literal matches an equal value at the current position; a word! that names a datatype (word!, integer!, block!, string!, paren!, etc.) matches any value of that type; a word! that names a parse rule variable matches by recursing into the rule; a string! rule on a string! input matches the substring at the current position (case-insensitive by default; parse/case enables case-sensitive matching); a skip rule matches any single value; an end rule succeeds only at the end of the series.

Quantity rules: any matches its sub-rule zero or more times (greedy); some matches one or more times; opt matches zero or one time. any, some, and opt each take a single rule (which may be a block! grouping multiple atomic rules): any [word! integer!] matches zero or more repetitions of a word! followed by an integer!. Alternation: | separates alternatives; the parse engine tries the first alternative, and if it fails (advances the series position but then fails), tries the next. Rebol’s | is ordered choice — the first matching alternative wins, not the longest. This is a common source of parse failures when alternatives share a common prefix. The fix is to order alternatives from most specific to least specific, or to use ahead for lookahead without consuming the matched value: [ahead block! into [any inner] | word!] uses ahead block! to verify that a block! is at the current position before committing to the into branch.

The into rule is the correct mechanism for descending into nested block! or paren! values. into rule requires that the current series position holds a block! (or paren!, or string!, depending on the parser mode), and then applies rule to the contents of that block! as a new sub-series, starting from its head. After into succeeds (the sub-rule matched the full sub-series), parsing resumes at the position after the block! in the outer series. into [any [word! | integer!]] matches a block! whose contents are an alternating sequence of word!s and integer!s. thru and to rules advance the series position: to value advances up to (but not including) the next occurrence of value; thru value advances past it. These are useful for skipping unknown content to find a specific token. not negates a rule without consuming input: not end succeeds at any position that is not the end. and (in Red; ahead in Rebol3) performs lookahead.

collect and keep are the structured output assembly mechanism introduced in Red and backported to some Rebol3 builds. collect [rules] returns a block! containing all values captured by keep within those rules. keep value appends value to the collect output; keep pick value appends the matched value; keep (expression) evaluates expression and appends its result. Without collect/keep, a parse rule produces only a logic! result (true or false) indicating whether the entire input was matched; extracting parsed tokens requires manual series management with copy. copy word rule binds the sub-series matched by rule to word as a side effect of matching: copy matched [any word!] binds the block! of matched word!s to matched. set word rule binds the single value matched by rule to word. These copy/set bindings are the Rebol2-compatible mechanism for extracting parse output when collect/keep is unavailable. Combining into [collect [any [keep word! | into [collect [any [keep integer!]]]]]] builds a nested block! structure that mirrors the nesting of the input, which is the idiomatic approach for configuration file parsing.

Series operations, context!/object! namespaces, and the block! code-and-data model

Rebol’s series model unifies all sequential data — block!, string!, binary!, path!, paren!, hash!, list! — under a single set of series operations that work by maintaining a current position within a shared buffer. Every series value carries a position pointer: a newly created block! has its position at the head; after a next call, the position advances by one value. This position-relative model means that length? series returns the number of values from the current position to the tail, not the total length of the buffer; length? head series returns the total length. Common series operations: head series returns the series at position zero; tail series returns the series at the position after the last value (empty series); next series advances by one; back series retreats by one; at series n returns the series at absolute position n (one-based); skip series n returns the series advanced by n positions from the current position. pick series n returns the value at offset n from the current position without changing position; poke series n value replaces the value at offset n.

Mutation operations: append series value adds value at the tail; insert series value inserts value at the current position; remove series removes the value at the current position; clear series removes all values from the current position to the tail. All mutation operations return the series at its new position after the mutation, not the modified value. This return-position-not-value behavior is the source of many subtle bugs: insert blk value returns the series positioned after the inserted value, not the block with the value at the head. copy series returns a new series starting from the current position; copy/part series length copies length values starting at the current position. copy/deep series performs a recursive deep copy, duplicating nested block! values. find series value returns the series positioned at the first occurrence of value, or none if not found. find/skip series value n searches at every nth position; find/reverse series value searches backward from the current position; find/last series value finds the last occurrence; find/only series block treats a block! argument as a single value to search for rather than as a series of values to match; find/match series block matches the block! contents against the series content at the current position. select series value finds value and returns the next element, which is the standard idiom for key-value pairs in a block!: select config 'timeout.

Rebol’s context! and object! values are namespaces that bind word! values to data. make object! [field1: value1 field2: value2] creates an object with two fields. Prototype inheritance: make parent-obj [new-field: value] creates a new object with all of parent-obj’s fields plus new-field. Accessing a field: obj/field uses path! notation; get in obj 'field uses the in function to look up the field word in the object’s context and return its value. Word binding: when a block! is created from source, each word! in the block is bound to the context where the block was defined. When that block is evaluated with do, words resolve to their bound context — which may be the wrong context if the block was created in a different scope than where it is evaluated. bind block context rebinds all words in block that exist in context’s word list to that context, leaving other words bound to their original contexts. bind/copy block context returns a new block with rebound words, preserving the original. use [word1 word2] [body] creates a local context with word1 and word2 as local words, binding them within body: this is the standard mechanism for temporary local variables in Rebol2. The global system/words context holds all globally-defined words; assigning to a new word from the console or do adds it to this context.

Rebol’s do/reduce/compose evaluation triad controls when and how blocks are evaluated. do block evaluates each expression in block in sequence, returning the result of the last expression. reduce block evaluates each element of block and returns a new block with the results — the standard idiom for building argument lists from computed values. compose block evaluates only the paren! sub-values in block, leaving all other values unchanged — the standard template mechanism: compose [name: (user-name) age: (calculate-age)] returns a block with name: and age: as set-word! values and the computed name and age as their adjacent values. load string parses a string into a Rebol block! without evaluating it, returning the value representation: load "foo bar 42" returns [foo bar 42] as a block with word! and integer! values. The code-and-data duality of block! means that the same data structure used to hold configuration values can also hold executable Rebol code, which is why parse dialect rules and configuration data share the same block! type.

Red language: reactive system, VID dialect, and Red/System FFI

Red is a successor to Rebol that adds a reactive programming system, a native compiler via Red/System, and a built-in GUI toolkit via the VID (Visual Interface Dialect). Red’s reactive system uses react and react? to create and test reactive formulas. react [face1/color: face2/color] creates a reactive formula that automatically updates face1/color whenever face2/color changes. The reactive graph is built from reactive sources (faces or objects with react formulas) and reactive targets (the faces or words that formulas write to). react?/target face returns true if face has any reactive formulas that write to it. clear-reactions face removes all reactive formulas targeting face. do-events starts the Red event loop for GUI applications; do-events/no-wait processes all pending events without blocking.

Red’s VID dialect provides a declarative UI layout system. view layout [button "Click me" [print "clicked"]] creates and displays a window with a button. VID face types: base (generic container), text (non-interactive label), button (clickable), field (single-line input), area (multi-line input), text-list (scrollable list), drop-list (dropdown), check (checkbox), radio (radio button), slider (range control), progress (progress bar), image (bitmap display), panel (nested container), group-box (labeled panel). Each face has standard facets: /data (the face’s primary data value), /text (displayed text), /size (pixel dimensions as pair!), /offset (position within parent as pair!), /color (background as tuple!), /draw (draw block for custom rendering), /actors (event handler object). The layout engine positions faces using a flow algorithm; at position overrides absolute positioning; return starts a new row; pad amount inserts spacing. Event handlers are specified as block! values after the face: button "OK" [submit-form]. The on-click, on-over, on-change, on-key, and on-resize actors are called by the event system.

Red/System is Red’s systems programming dialect for low-level code and C library integration. Red/System uses C-like syntax embedded in Red source via #system [...] blocks. FFI integration: #import [%libname.so cdecl [function-name: "c_name" [arg1 [type1] arg2 [type2] return: [return-type]]]] imports a C function. Red/System types: integer! (32-bit signed), uint8! (8-bit unsigned byte), float! (64-bit double), float32! (32-bit float), logic! (boolean), pointer! [type] (typed pointer), struct! [field-name [field-type] ...] (C-compatible struct), c-string! (null-terminated byte array). #export [function-name] makes a Red/System function callable from C. declare struct! allocates a struct on the stack; allocate size allocates heap memory; free ptr releases it. The Red/System compiler generates native machine code (x86, x64, ARM, ARM64) and links against system libraries, enabling Red applications to call C libraries directly without a Cython/ctypes wrapper layer.

How HourTab tracks Rebol developer retainer hours

Rebol retainer work shares the invisible-work problem with all language-level retainers, with the additional challenge that Rebol’s most common retainer tasks — parse dialect rule redesign, series position management, context binding restructuring, Red reactive formula authorship — produce diffs whose surface area is small relative to the diagnostic and design work required. Restructuring a parse rule from flat alternation to nested into traversal is a diff that changes a few lines of rule block; the value is zero wrong-token parse failures on nested configuration structures. Restructuring a series pipeline from next/back position arithmetic to at-indexed access with copy/part is a diff that rewrites a helper function; the value is correct output for all series lengths without off-by-one position errors. Adding a react formula to wire two Red face facets together is a diff with one line; the value is live recomputation that eliminates a class of stale-UI bugs without polling.

HourTab gives Rebol developers a public retainer-hours URL they send to clients — typically research teams using Rebol for data processing pipelines, configuration management systems, or Red-based GUI tooling — at the start of an engagement. For Rebol retainers, each work log entry should name the mechanism (parse dialect rule redesign; into rule for nested block! traversal; collect/keep output assembly; copy/set binding for parse extraction; series position management with at/skip/find; context! binding with bind/in/use; compose/reduce/do evaluation pipeline; Red react/react?/clear-reactions formula authorship; Red VID layout and event handler design; Red/System #import FFI declaration), the specific rule or series operation and the failure mode, and the before/after observable metric. Rebol retainers are often compared to Tcl developer retainers for configuration scripting language work, to Forth developer retainers for minimalist language philosophy, and to Scheme developer retainers for code-as-data homoiconicity. The distinction from all three is the parse dialect: Rebol’s built-in parse engine operating directly on Rebol’s value types (not on character streams) is a capability without equivalent in Tcl, Forth, or Scheme, and designing correct parse rules for nested block! structures is the skill most unique to Rebol retainer engagements. HourTab’s work log makes that visible to clients.

Track Rebol developer retainer hours without the status emails

HourTab gives Rebol developers a public URL per client retainer. One link, no login, live burn-down. Your clients stop asking “how many hours do I have left?” and your work log becomes the proof of value that gets the retainer renewed.

See HourTab pricing →

FAQ: Rebol developer retainers

What does a Rebol developer on retainer typically do?

A Rebol developer on monthly retainer covers four principal service areas: parse dialect design (parse rule authorship for block!/string!/binary! inputs; into rule design for nested structure traversal; collect/keep structured output; any/some/opt/not/end rule composition; alternation | with ahead lookahead disambiguation; recursive rule references for self-similar structures); series operation pipeline design (head/tail/next/back/at/skip/pick/poke/append/insert/remove/clear/copy/find/select with all refinements; position management for heterogeneous series); context!/object! namespace design (make object! authorship; prototype chains; bind/in/use word-to-context wiring; do/load/reduce/compose evaluation pipeline design); and Red language engineering (Red react/react?/clear-reactions reactive formulas; Red VID layout and event handlers; Red/System #import FFI for C library integration).

What Rebol work is most commonly underlogged in a retainer?

Parse dialect redesign for nested block! structures (parse rule using flat | alternation failing on 5 nested block structures/day; restructured with into [any inner-rule] for block! content traversal and collect/keep for output; parse failures: 5/day → 0; 14–22 hrs invisible in rule restructuring), context! binding and do-block evaluation (3 script components using do with blocks defined in different contexts causing wrong word resolution; bind calls to wire words to correct context objects; resolution errors: 3/day → 0; 10–18 hrs invisible in binding analysis), and series position management (8 pipelines using next/back/skip leaving position at wrong offset after conditional branches; restructured to use at-indexed access and copy/part for sub-series extraction; position errors: 8/session → 0; 12–20 hrs invisible in series traversal redesign).

What are typical Rebol developer retainer rates?

Entry-level Rebol developers (1–2 years, basic parse rules, series operations, do/reduce/compose, context!/object!, foundational Red VID layout) bill at $65–$115/hr. Mid-level Rebol engineers (2–4 years, recursive parse dialect rules for deeply nested block! grammars, collect/keep structured output, complex series position management with find/select refinements, context! binding with bind/in/use, Red reactive formula design with react/react?/clear-reactions) bill at $110–$195/hr. Senior Rebol architects (4–8 years, full application design in the values-as-data model, complex parse dialect grammars, Red/System FFI for C library integration, Red VID widget systems with custom draw-based rendering, module! namespace design for large codebases) bill at $160–$295/hr. Monthly retainer ranges: $2,200–$5,200/mo advisory (15–25 hrs), $7,000–$19,000/mo for full Rebol/Red application platform engagements.

What should a Rebol developer retainer agreement include?

A Rebol developer retainer agreement should specify: parse dialect scope (parse rule authorship; into rule design; collect/keep integration; any/some/opt/not/end rule composition; ahead lookahead disambiguation; recursive rules; parse/all vs parse/part modes; parse debugging via trace); series operation scope (head/tail/next/back/at/skip navigation; pick/poke indexed access; append/insert/remove/clear mutation; find/select with refinements; copy/part sub-series extraction; sort/reverse transformation); context and binding scope (make object! context authorship; prototype inheritance; bind/in/use word-to-context wiring; do/load evaluation with context isolation; system/words global context management; module! namespace design); Red language scope (Red reactive formulas with react/react?/clear-reactions; Red VID face hierarchy; Red/System #import/#export FFI); and hour logging format (parse rule name; failure mode; tokens mismatched before/after; Rebol version and platform).

How should Rebol developer retainer hours be logged?

Log each Rebol retainer session with: advisory category (parse dialect rule redesign for nested block! traversal; collect/keep output restructuring; series position management; find/select pipeline restructuring; context! binding with bind/in/use; prototype inheritance chain design; word resolution debugging across do/load evaluation contexts; Red reactive formula authorship; Red VID face hierarchy design; Red/System FFI #import declaration), the specific rule name or series operation and the failure mode (parse rule for nested config blocks failing on 5 cases/day — | choice operator selecting wrong alternative when block content begins with keyword word!; restructured with into [any rule]; parse failures: 5/day → 0), and the before/after metric. Include Rebol version (Rebol2/Rebol3/Red 0.6.x), platform, and whether the fix required parse rule restructuring, context binding, or series traversal strategy changes.