A lexer for s&box .shader VFX files. It extends an HLSL lexer to recognize top-level VFX blocks (HEADER, MODES, FEATURES, COMMON, VS, PS, etc.), map blocks to shader stages, and classify identifiers differently depending on current VFX block context.
using Editor.Prism.Core;
using Editor.Prism.Text.LanguageDb;
namespace Editor.Prism.Text.Lexer;
/// <summary>
/// The top-level blocks of a VFX <c>.shader</c> file. The numeric values are the 1-based index into
/// <see cref="SboxSymbols.BlockNames"/> and are what <see cref="LexFlags.Block"/> stores, so they
/// must not be reordered.
/// </summary>
public enum VfxBlock
{
/// <summary>File scope — outside every block. This is where the <c>struct VertexInput</c> and
/// <c>struct PixelInput</c> declarations live.</summary>
None = 0,
/// <summary>The <c>HEADER</c> metadata block.</summary>
Header = 1,
/// <summary>The <c>MODES</c> render-pass block.</summary>
Modes = 2,
/// <summary>The <c>FEATURES</c> permutation block.</summary>
Features = 3,
/// <summary>The <c>COMMON</c> block, shared HLSL.</summary>
Common = 4,
/// <summary>The <c>VS</c> vertex program.</summary>
Vertex = 5,
/// <summary>The <c>PS</c> pixel program.</summary>
Pixel = 6,
/// <summary>The <c>GS</c> geometry program.</summary>
Geometry = 7,
/// <summary>The <c>HS</c> hull program. The engine's parser throws on this block.</summary>
Hull = 8,
/// <summary>The <c>DS</c> domain program. The engine's parser throws on this block.</summary>
Domain = 9,
/// <summary>The <c>CS</c> compute program.</summary>
Compute = 10,
/// <summary>The <c>PS_RENDER_STATE</c> block.</summary>
PixelRenderState = 11,
/// <summary>The <c>RTX</c> ray-tracing program.</summary>
RayTracing = 12
}
/// <summary>
/// The lexer for s&box <c>.shader</c> files. A <c>.shader</c> is a small block language wrapping
/// HLSL, so this derives from <see cref="HlslLexer"/> and the embedded HLSL inside <c>COMMON</c>,
/// <c>VS</c>, <c>PS</c>, <c>GS</c> and <c>CS</c> is lexed by exactly the same scanner — including the
/// <c>< … ></c> annotation grammar, which the base lexer already understands because
/// <c>common_samplers.fxc</c> and friends use it in plain <c>.hlsl</c> files too.
///
/// What this class adds is the block structure: the block name is recognised only at file scope, the
/// brace that follows it opens the block, and <c>HEADER</c>, <c>MODES</c> and <c>FEATURES</c> get
/// their own word classes because their contents are declarations rather than code.
/// </summary>
public sealed class VfxLexer : HlslLexer
{
/// <inheritdoc/>
public override string Language => "vfx";
/// <inheritdoc/>
protected override LanguageDefinition Definition => LanguageDefinition.Vfx;
/// <summary>Which block a lexer state sits inside. <see cref="VfxBlock.None"/> means file scope.</summary>
public static VfxBlock BlockOf( LexState state )
{
var index = LexFlags.Block( state.Flags );
return index is >= 0 and <= (int)VfxBlock.RayTracing ? (VfxBlock)index : VfxBlock.None;
}
/// <summary>True when the state sits inside a block whose body is HLSL rather than declarations.</summary>
public static bool IsCodeBlock( LexState state ) => IsCodeBlock( BlockOf( state ) );
/// <summary>True when the block's body is HLSL rather than VFX declarations.</summary>
public static bool IsCodeBlock( VfxBlock block ) => block >= VfxBlock.Common;
/// <summary>
/// The shader stage a block compiles to, or <see cref="ShaderStage.None"/> for the metadata
/// blocks and for the two programs the engine rejects.
/// </summary>
public static ShaderStage StageOf( VfxBlock block )
{
switch ( block )
{
case VfxBlock.Vertex: return ShaderStage.Vertex;
case VfxBlock.Pixel: return ShaderStage.Pixel;
case VfxBlock.PixelRenderState: return ShaderStage.Pixel;
case VfxBlock.Geometry: return ShaderStage.Geometry;
case VfxBlock.Compute: return ShaderStage.Compute;
default: return ShaderStage.None;
}
}
/// <inheritdoc/>
protected override TokenKind ClassifyIdentifier( string word, IdentifierContext ctx )
{
if ( ctx.AfterDot || ctx.AfterScope || ctx.InAttribute )
return base.ClassifyIdentifier( word, ctx );
var block = (VfxBlock)LexFlags.Block( ctx.Flags );
// The metadata blocks are declaration languages of their own.
if ( !ctx.InAnnotation && !ctx.InDirective )
{
switch ( block )
{
case VfxBlock.Header:
if ( SboxSymbols.TryGet( word, out var key ) && key.Kind == SboxSymbolKind.HeaderKey )
return TokenKind.Keyword;
break;
case VfxBlock.Modes:
if ( SboxSymbols.IsModeFunction( word ) )
return TokenKind.FunctionName;
break;
case VfxBlock.Features:
if ( SboxSymbols.IsComboDeclaration( word ) )
return TokenKind.Keyword;
break;
}
}
return base.ClassifyIdentifier( word, ctx );
}
}