Revision history for Perl extension Text::Fab.

0.01		  Fri Sep  5 07:50:43 2025
	- original version; created by h2xs 1.23 with options
		-Xn Text::Fab

	A lot of technical work on the stub implementation, documentation and the test suite was passed to Gemini 2.5 Pro
		(with 32K thinking budget + very delicate system instructions).

	The implementation supports the most basic parts of processing pipeline only.  Only the pure-config__* subsystem is
	covered by the test suite.

0.02	Reworked with Gemini 3.8 Flash Medium (and one iteration with High) in a pure-chat in AI Studio; no system instructions.
	The only target was to make the test suite to match the docs (and fix the implementation accordingly).
	Only the updates to the docs checked by a human.
	The model claims that all the APIs (except Control Flow, Loops, Caching, Root Directives) are implemented and covered by
	the test suite.

0.03	Continue reworking with Gemini 3.8 Flash Medium in a pure-chat in AI Studio; no system instructions.
	Restore the original comments in the code.  Add new comments likewise.
	Fix a mismatch of the semantic of preserved blinding lists with what is written in the docs.
	Fix the scalar set/append with multiple arguments (only one allowed), allow string "offset" for hashes.
	Implement priority-based group__postpone; support insertion of undo actions from undo actions.
	Even if errors happen, all the postponed actions are performed.

	  t/10-corner-cases.t		New file

0.04    Better coverage:

	  t/11-coverage-expansion.t	New file

0.05    POD: Preview of the architecture on a (contrived) example.  (Might not map 100% to the current implementation.)
		Completeness of Directive Parser:
	Added handlers for #prepend, #pop, and #create_section, wiring them to cfg__prepend, cfg__pop, and out__create_section.
	Added target section tracking stack ($fab->{target_stack}) to support #end_section (as shown in the SYNOPSIS), restoring
	  the target section to the enclosing context.
	Extended #emb parsing to support qualified namespace:basename syntax (e.g., #emb ChapterLayout:title and #emb _main:title).
	Standardized default directive syntax errors to include E_SYNTAX_ERROR while maintaining compatibility with _handle_error.
		Prefix Call Stack Lifecycle & Prefix-Aware Types:
	Implemented call_stack__push($prefix, [$filter_name]) and call_stack__pop(), with underflow protection for the base prefix
	  'Fab/'.
	Updated _get_key_type to resolve hash_keys and list_keys using cfg__get_prefixed_scan (without redundant fallbacks),
	  enabling localized prefix-scoped type definitions.
		Introspection of Inclusion State:
	Initialized and maintained $self->{config}{'opened_includes'} alongside 'opened_groups' inside include__text, cleanly
	  exposing the include path chain.

	  Makefile.PL		put
	    use 5.022000;
            TEST_REQUIRES => {
                'Test::Exception' => 0,	## Present (?) since Perl 5.18 (which bundles MakeMaker 6.90 >= 6.64)
            },

	Better coverage:

	  t/12-parser-and-prefix-lifecycle.t New file

0.06  2026-10-22
    [ Architecture & Callbacks ]
    - Canonical engine callbacks moved to 'Fab/callbacks_bundle/dbg0'.
    - 'Fab/callbacks' now accepts a bundle name string (e.g. 'dbg0') in addition
      to direct hashrefs; resolved and propagated during initialization and scoping.
    - Eliminated ad-hoc fallback guessing chains (//) across callback retrieval;
      operational callbacks now resolve uniformly via cfg__get_prefixed_scan().
    - Added support for scoped callback bundle switches via #start_group[callbacks=...]
      and promise-local overrides via #emb[callbacks=...].

    [ Embeds, Parameter Passing & Lenses ]
    - Modernized #emb directive grammar to #emb[<opts>] <recipe> [<args...>].
      Supported options in [...] include aim=..., in=..., with_blinder=...,
      callbacks=..., and resolve_args=....
    - Primitive out__embed() now takes ($recipe_basename, $recipe_ns, \@args, \%opts).
    - Parameterized recipes: section arguments passed to a recipe are bound to
      $ARGV0, $ARGV1, etc. in the recipe body via an internal call frame stack.
    - Implemented lens affection massaging with deterministic static joining
      (_fmt_lens_join) formatting candidate lenses as (A)/B and matching against
      registered transitions in 'Fab/lens_affects'.
    - Embed resolution now traverses the namespace DAG from the massaged virtual
      root while respecting blinders and dynamic argument bindings.

    [ Input & Inclusion ]
    - Extended #include and include__text() to support container targets:
      'section' and 'configkey'.
    - Enforced strict arity validation across all inclusion modes:
      * 'filename' and 'string' strictly require exactly 1 target argument.
      * 'section' and 'configkey' strictly require 2 arguments: position 2 is
        the joiner list key, and position 3 is the target container.
      Arity mismatches now immediately halt with E_SYNTAX_ERROR (directives)
      or E_TYPE_MISMATCH (Perl API).
    - Section and configkey inclusions reuse the cyclic delimiter engine
      (out__get_joined / cfg__get_joined).

    [ Snapshot & Undo Engine ]
    - Added a compatibility hook for FreezeThaw::copyContents treating compiled
      CODE references as atomic/immutable values, preventing dclone() crashes
      when configuration contains duplicated or shared code references across
      snapshots and groups.

    [ Documentation & Tests ]
    - Updated POD to reflect modern #emb, #include, lens, and callback semantics
      while preserving design rationale and marking planned subsystems as {???}.
    - Modernized test suite (t/01 through t/12) to exercise strict arity,
      parameterized recipes, lens affections, and container inclusion.

0.0601	Update to requiring 1.00 of FreezeThaw — instead of patching!

0.07 - 2026-10-23
* Recursion & Cycle Detection. Parameterized #emb resolution in assemble()
  now differentiates invocations by their active argument signatures rather
  than unconditionally blocking repeated "$ns:$basename" identifiers along
  the active branch. Added the 'Fab/max_recursion_depth' configuration limit
  (default 1000) to trap runaway cycles while permitting deep recursive recipe
  decomposition.
* Configuration & Typing. Corrected the meta-configuration classification of
  'Fab/list_keys' in new() to be registered under 'Fab/hash_keys' rather than
  listing itself as a list key, preventing cfg__set() from clobbering the
  internal hash into an array reference and causing dereference crashes in
  _get_key_type().
* Inclusion & Options Forwarding. Normalized include__text() across all modes
  ('filename', 'string', 'section', 'configkey') to peel off and accept an
  optional trailing options hashref without triggering E_TYPE_MISMATCH arity
  errors. Updated the default directive parser to pass bracketed directive
  options (#include[opts] ...) through to include__text().
* Test Suite. Added t/50_peano.t to stress-test deep recursive stack frames,
  lexical call-frame isolation, and nested promise resolution by modeling pure
  unary Peano arithmetic (addition, multiplication, non-negative subtraction,
  and integer division with remainder) strictly on top of add1/pred primitives.

  t/50_peano.t		Peano arithmetic (honestly implemented in terms of incr, decr and is0)
  t/51_rpn.t		RPN calculator for embedded expressions
  t/52_infix.t		Infix notation calculator
  t/53_at-end-paragraph_vars.t	Same, with ${var} accessing the value of the variable at the end of the current paragraph

0.08 - 2026-10-??
* Reworked with Claude (claude-opus-5-5), driven by a review checklist; all
  changes to the docs audited.
* Name Resolution. Incompatible: unqualified recipes (#emb x, or out__embed()
  with an undefined namespace) are resolved late, during assembly: from the
  lens vertex if the promise sets aim=, otherwise starting from the namespace
  in which the enclosing section was requested (which may be a child of the
  namespace where the section's text was found). The resolution order
  (depth-first through the parents, in the declared order) is documented.
* Operands. Operands of a promise are bound when it is expanded, in the
  caller's context: NS:name starts at NS, an unqualified name at the caller's
  namespace, and $ARGVn passes on the caller's own operand, so recipes can
  forward their operands. Stopgap: when a promise switches the lens (aim=),
  every operand must be tagged old=... or new=... (E_UNTAGGED_OPERAND).
* Prefix-scoped Settings. Fab's own settings (list_keys, hash_keys,
  lens_affects, inheritance_graph, group_flavors, group_nesting, blinders/*,
  callbacks, callbacks_bundle/*, hash_key_sort_order, max_recursion_depth)
  are looked up under each prefix of Fab/call_stack_prefixes, the most recent
  first; NS__set_parents() writes under the current prefix.
* Errors. Incompatible: E_UNDEFINED_PARENT is raised during assembly, when the
  search reaches a parent with neither sections nor parents; #set_parents
  accepts parents declared before they exist. New codes: E_UNKNOWN_BLINDER
  (was silently ignored), E_UNTAGGED_OPERAND. The permitted_joiners_modulo
  error now starts with E_TYPE_MISMATCH.
* Directives. Incompatible: #target_section / #create_section take any
  non-space names (so lens namespaces like LaTeX/as_TOC work), and extra words
  are E_SYNTAX_ERROR. Incompatible: the count of #pop / cfg__pop() must be a
  non-negative integer, and 0 pops nothing (it used to empty the list).
  Incompatible: the legacy alias asiffrom= of aim= is removed.
* Lenses. Incompatible: the lens stack starts empty (it used to start with
  _main).
* Documentation. The SYNOPSIS runs with the default callbacks, and the
  not-yet-implemented constructs are kept at its end, marked as such. New:
  the syntax of the default directives, an example of partial undo, and the
  callback signatures in PLACEHOLDERS. Planned features (#emb[callbacks=...],
  uplevel directives, the #set_root errors) are marked [Planned]. Fixed a POD
  syntax error.
* Makefile.PL: MIN_PERL_VERSION 5.022; FreezeThaw 1.00 is required.
* Tests. Review items still open are TODO tests named after the item.

  t/13-directives-synopsis.t	SYNOPSIS extracted from the POD, run and compared; default directive syntax
  t/14-config-and-groups.t	Configuration, call stack, callbacks and groups
  t/15-assembly.t		Resolution order, late resolution, operands, lenses
  t/16-dist.t			Prerequisites and POD syntax
  t/17-coverage-gaps.t		Documented behaviour not exercised before

0.09 - 2026-10-25
* Joining. cfg__get_joined() on a hash with a single joiner printed an array
  reference between the pairs; that joiner is now used there too.
  Incompatible: permitted_joiners_modulo works as documented: the permitted
  remainders of the number of emitted joiners modulo scalar @joiners (a list,
  or one remainder), e.g. [0] for "joiners come in pairs".
* Output targets. Incompatible: the output targets are one stack in the
  configuration, Fab/output_target_stack (its last entry is the current
  target), so groups restore it like any other key. out__target_section()
  pushes, like #target_section; the new out__end_section() pops, like
  #end_section.
* Direct output. With an empty target stack, text goes directly to
  Fab/output_handle (default STDOUT). Fab/output_postpone decides when output
  is postponed into _main:body: at_first_promise (the default, so
  include__text() of a file without directives works like cat), at_start
  (incompatible: the only behaviour up to 0.08), or error (a promise in direct
  output dies with the new E_PROMISE_IN_DIRECT_OUTPUT). The new out__flush()
  prints the postponed output.
* Input. A blank line at the start of a chunk (at the start of the input, or
  after a directive line) is no longer dropped.
* TODO. Reading input from a filehandle; detecting the end of a job.
* Tests. t/50_peano.t does all its arithmetic through Fab's assembly. Tests of
  deferred directive syntax are removed. Tests which inspect _main:body after
  plain text set Fab/output_postpone to at_start.

  t/18-output.t		Direct and postponed output, the target stack

0.10 - 2026-10-26
	All tests REALLY pass now (no postponement, no TODO).
* Groups. #start_group checks Fab/group_nesting/FLAVOR/allowed_parents also at
  top level, where the enclosing flavor is '' (as inside an unnamed group).
  Incompatible: a flavor whose allowed_parents doesn't list '' can no longer
  be opened at top level.
* Groups. Configuration changes made by the options of #start_group (e.g.
  [callbacks=...]) are always undone at #end_group, whatever the flavor
  restores: by an undo action at priority 1000.0, after the configuration is
  restored at 0.0. An unknown bundle in [callbacks=...] dies before the group
  is opened.
* Callbacks. The new setting Fab/callbacks_scope says from where on a switch
  of the callbacks (e.g. #start_group[callbacks=...]) is used: nested (the
  default, the behaviour so far: only input read later, e.g. included files)
  or next_line (from the line after the directive).
* Call stack. Incompatible: call_stack__push() requires a filter name, so
  Fab/call_stack_filter_name stays paired with Fab/call_stack_prefixes.
* Joining. Incompatible: a hash is joined as the flat list key1, value1, key2,
  value2, ..., cycling through all the joiners, as documented;
  permitted_joiners_modulo counts all of these joiners.
* TODO. An engine for streaming to STDOUT: output held back by a promise may
  continue once an authority (a self-declared name, overridable by others)
  declares the promise finished.
* Tests. No TODO tests are left in t/14-config-and-groups.t.

0.11 - 2026-10-26
* Groups/includes. The configuration restored at #end_group no longer brings
  back a stale opened_includes: when a group is opened before #include and
  ended inside the file, the file stays listed (before, it vanished from the
  list); when it is opened inside a file and ended after it, the closed file
  is no longer listed (before, it stayed there to the end of the run).
* Includes. Incompatible: a file that is still being read may be included
  again (the check by file name, E_CYCLIC_INCLUDE, is removed: it forbade
  inclusions that end, e.g. behind a guard, after a change of a configuration
  key that the file reads, or read with other callbacks active, and missed a
  key or a section that includes itself). Instead, the new setting
  Fab/max_include_depth (default 200) limits nested include__text() calls of
  all kinds; one more halts with E_INCLUDE_TOO_DEEP, naming the file open
  most often.
* Tests. New usage cases t/54_m4.t (m4) and t/55_cpp.t (a C preprocessor,
  agnostic of C syntax), written as callbacks; their expected outputs are
  those of GNU m4 1.4.19 and GNU cpp 13.3. TODO parts: putting text in front
  of the rest of the input, and output without the chunk preprocessor.
* POD. Two new MISS entries in TODO: input__prepend() and out__text().
* Control flow. The control API is implemented (it was [Planned]):
  control__start(), control__set_skipping(), control__end(),
  control__set_pointer(), control__define_invalidation_pointer(),
  control__jump_to(), and the new control__skipping() and
  control__loop_counter(). Every input stream (a process_string() call) has
  its own control stack; groups do not restore it; Fab/control__loop_indices
  lists the indices of its loop-like frames (those with a label). In a
  skipped part the chunk preprocessor is not called, and the default parser
  executes no directive. The interleaver and the directive parser get two
  more arguments: the skipping state and the replay flag. Errors:
  E_MISMATCHED_CONTROL_END, E_DANGLING_CONTROL_END, E_UNCLOSED_CONTROL_BLOCK,
  E_POINTER_NOT_FOUND, E_BLOCK_LABEL_NOT_FOUND, and the new
  E_CONTROL_OUTSIDE_DIRECTIVE.
  Differences from the plan: every included text (not only a file) has its
  own stack; the stack is kept outside the configuration; a jump without a
  label goes to the innermost loop-like frame (not the top frame); a jump to
  the start of a frame makes the directive there re-enter it: the first
  control__start() with the frame's type and label continues it instead of
  pushing a new frame, and control__reentering() names it; a pointer at the
  end of a directive is recorded after the directive parser returns; frames
  above a frame searching for a pointer skip on entry; a frame is loop-like
  when a label is given; control__skipping(), control__reentering() and
  control__loop_counter() are new.
* Control flow. New setting Fab/control__loop_limits (a list, default
  [1e300]): entry D is the most jumps back in one instance of a loop at loop
  depth D (counting the loops of including inputs too); the last entry counts
  for all greater depths. One more halts with E_LOOP_LIMIT.
* Tests. New t/19-control-flow.t (a parser with #if/#elif/#else/#endif,
  #while, #repeat, #break, #continue, labels); in t/55_cpp.t the variant using
  the control API is no longer TODO.
* Tests. t/55_cpp.t: the engine leaves a macro name unexpanded ("painted")
  only while that macro's own replacement is being read, as C11 6.10.3.4 and
  GCC do (before, every token an expansion produced kept the name, so calls
  delayed by EMPTY() and forced by EVAL were never made). New tests: the
  examples of the Habr article "Проклятый огонь, или магия препроцессора C"
  (haqreu, 2024; a decrement, a branch, a test for zero, a delayed recursive
  call, a countdown), each with the steps of expansion that make its output.
* Control flow. control__jump_to() with a label that no open block has
  searches forward for a block with this label that starts directly inside
  the current block (or at the top level of the input), as the plan's
  E_BLOCK_LABEL_NOT_FOUND implied; it halts with E_BLOCK_LABEL_NOT_FOUND only
  if the enclosing block (or the input) ends first. (0.11-pre4 halted at
  once.) New blocks started while a search is on skip on entry.
* Tests. t/55_cpp.t: the engine supports __VA_OPT__ (C23). New tests: examples
  of P. Fultz ("Is the C preprocessor Turing complete?", 2012, and the Cloak
  wiki: DEFER/OBSTRUCT, NOT/BOOL/IF/WHEN, IS_PAREN, REPEAT, WHILE, comparing
  names by painted tokens, FOREVER counting the scans) and of h4x0r.org
  ("Recursive macros in C, demystified", 2025: counting arguments with MAP).
  t/19-control-flow.t: #grid, one directive starting two loops, and
  control__reentering().
* Tests. t/55_cpp.t: the examples of J. Heathcote, "C Pre-Processor Magic"
  (jhnet): IF_ELSE, BOOL, deferred calls, recursion, MAP with HAS_ARGS.
* Tests. t/55_cpp.t: the Habr article's bonus (X1(0) doubling 64 times) at
  depth 8 through the cpp engine, and as 64 sections with promises (190
  entries; assembling one level gives 2^8 zeros).
