Editor/Prism/Text/Lexer/SlangLexer.cs

A lexer class for the Slang shading language that extends an HLSL lexer. It swaps in Slang-specific language definition and classifies identifiers with extra rules for module introducers (module, import, implementing, __include, __import) and interface-like names starting with 'I' followed by an uppercase letter and then a lowercase letter.

Reflection
using Editor.Prism.Text.LanguageDb;

namespace Editor.Prism.Text.Lexer;

/// <summary>
/// The Slang lexer. Slang is a superset of HLSL, so the whole scanner is inherited from
/// <see cref="HlslLexer"/>; this class swaps in the Slang word tables and adds the deltas that need
/// context the tables cannot express — module names after <c>module</c>, <c>import</c>,
/// <c>implementing</c> and <c>__include</c>, and the fact that s&amp;box's <c>system.fxc</c> symbols
/// and its <c>&lt; … &gt;</c> annotation grammar are not part of the language.
/// </summary>
public sealed class SlangLexer : HlslLexer
{
	/// <inheritdoc/>
	public override string Language => "slang";

	/// <inheritdoc/>
	protected override LanguageDefinition Definition => SlangLanguage.Definition;

	/// <inheritdoc/>
	protected override TokenKind ClassifyIdentifier( string word, IdentifierContext ctx )
	{
		// `module scene;`, `import mathlib;`, `implementing scene;`, `__include helpers;`
		if ( !ctx.AfterDot && !ctx.AfterScope && IsModuleIntroducer( ctx.PreviousWord ) )
			return TokenKind.IncludePath;

		// An interface name in a generic constraint reads far better as a type.
		if ( !ctx.AfterDot && !ctx.AfterScope && !ctx.BeforeParen && LooksLikeInterface( word ) )
			return TokenKind.UserType;

		return base.ClassifyIdentifier( word, ctx );
	}

	private static bool IsModuleIntroducer( string word ) =>
		word is "module" or "import" or "implementing" or "__include" or "__import";

	private static bool LooksLikeInterface( string word )
	{
		// The Slang core module and every convention in the wild name interfaces `IFoo`.
		if ( word.Length < 3 || word[0] != 'I' )
			return false;

		if ( !char.IsUpper( word[1] ) )
			return false;

		for ( var i = 2; i < word.Length; i++ )
		{
			if ( char.IsLower( word[i] ) )
				return true;
		}

		return false;
	}
}