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}