001package gudusoft.gsqlparser.pp2.render;
002
003import gudusoft.gsqlparser.pp2.Pp2FormatOptions;
004import gudusoft.gsqlparser.pp2.region.RegionParseOutcome;
005import gudusoft.gsqlparser.pp2.token.Pp2TokenStream;
006
007/**
008 * Strategy interface for rendering a single {@link RegionParseOutcome} to
009 * its final text. Implementations are dispatched by
010 * {@code gudusoft.gsqlparser.pp2.engine.Pp2Engine} (S16) and form the
011 * three-tier renderer pipeline of plan §5.2:
012 *
013 * <ul>
014 *   <li><b>{@link GuardedAstDelegate}</b> (S13) — wraps
015 *       {@code FormatterFactory.pp(parser, opt)} and runs a
016 *       {@code TokenEquivalence} guard on the output. Used when the engine
017 *       has an AST for the region.</li>
018 *   <li><b>{@code ConservativeTokenRenderer}</b> (S14, Phase-2 MVP) — emits
019 *       tokens with light spacing. The minimum-viable last-resort that
020 *       guarantees content preservation.</li>
021 *   <li><b>{@code LexicalIslandRenderer}</b> (S31, Phase-3 quality) —
022 *       full Java port of the Delphi TSQLion pipeline. Replaces the
023 *       conservative renderer for AST_ERROR regions once Phase 3 ships;
024 *       conservative remains as the last-resort safety net.</li>
025 * </ul>
026 *
027 * <h2>Fall-through contract</h2>
028 *
029 * <p>Implementations <b>must not throw for any valid, non-null inputs</b>.
030 * Argument-validity assertions ({@link NullPointerException} on null
031 * {@code outcome} / {@code opts}) are the only exceptions allowed — they
032 * signal a caller bug, not a recoverable rendering failure. On any
033 * <i>recoverable</i> failure (parser exception, guard violation, internal
034 * renderer error) the implementation must return {@code null} so the
035 * engine can route the region to the next renderer in the chain.
036 *
037 * <p><b>{@code null} is the only fall-through sentinel.</b> An empty
038 * {@code String} ({@code ""}) is a valid rendering of a legitimately empty
039 * region (trivia at end-of-input, an explicitly-empty user fragment) and
040 * must <i>not</i> be re-interpreted as a fall-through by the engine.
041 *
042 * <p>Diagnostic messages should be emitted via
043 * {@link gudusoft.gsqlparser.pp.logger.PPLogger}; structured
044 * {@code FormatDiagnostic} entries are attached by the engine (S16), not
045 * the renderer.
046 *
047 * <p>Plan reference: §5.2, §7.3/S13, §10.2.
048 */
049public interface RegionRenderer {
050
051    /**
052     * Render the outcome's region to its final text.
053     *
054     * @param outcome non-null parse outcome for the region. Implementations
055     *                may assume {@link RegionParseOutcome#getRange()} and
056     *                {@link RegionParseOutcome#getParsedSql()} are non-null.
057     *                {@link RegionParseOutcome#getParser()} is non-null only
058     *                for {@link RegionParseOutcome.Status#AST_OK AST_OK}
059     *                outcomes.
060     * @param stream  the engine's full {@code Pp2TokenStream} for the whole
061     *                input script (token-based renderers slice into it via
062     *                the outcome's range token indices)
063     * @param opts    non-null pp2 options
064     * @return the formatted text for this region, or {@code null} (the
065     *         only sentinel) to signal fall-through to the next renderer
066     *         in the chain. An empty string is a valid rendering, not a
067     *         fall-through.
068     */
069    String render(RegionParseOutcome outcome,
070                  Pp2TokenStream stream,
071                  Pp2FormatOptions opts);
072
073    /**
074     * Identifier mirrored on {@link gudusoft.gsqlparser.pp2.RendererId} so
075     * {@code Pp2FormatResult.Region.getRendererId()} reflects which strategy
076     * actually produced the text for the region.
077     */
078    gudusoft.gsqlparser.pp2.RendererId id();
079}