Utility class that maps the compiler's IR concepts to Slang shader language spellings and helpers. It provides names for intrinsics, operators, types, literals, identifier sanitation, builtin mappings per shader stage, and reserved word lists used when emitting Slang code.
using System.Globalization;
using System.Text;
using Editor.Prism.Compiler.Ir;
using Editor.Prism.Core;
namespace Editor.Prism.Compiler.Backends;
/// <summary>
/// The spelling table that turns Prism's canonical, backend-independent operations into Slang syntax.
/// <para>
/// Everything the <see cref="SlangBackend"/> writes goes through here: intrinsic names, operator
/// symbols, type spellings, literals, identifiers and the per-stage lowering of every
/// <see cref="Core.ShaderStage"/>-dependent <see cref="Compiler.Builtin"/>. Keeping it in one place is
/// what makes the emitted module consistent, and what makes "never emit <c>?:</c> on a vector" a rule
/// the backend cannot accidentally break.
/// </para>
/// </summary>
public static class SlangIntrinsics
{
// ---- well-known names the emitted module and the prelude agree on -------
/// <summary>Name of the single parameter every graphics entry point takes.</summary>
public const string InputParameter = "i";
/// <summary>Name of the pixel entry point's <c>SV_IsFrontFace</c> parameter.</summary>
public const string FrontFaceParameter = "isFrontFace";
/// <summary>Name of the compute entry point's <c>SV_DispatchThreadID</c> parameter.</summary>
public const string DispatchThreadIdParameter = "dispatchThreadId";
/// <summary>Name of the compute entry point's <c>SV_GroupThreadID</c> parameter.</summary>
public const string GroupThreadIdParameter = "groupThreadId";
/// <summary>Name of the compute entry point's <c>SV_GroupID</c> parameter.</summary>
public const string GroupIdParameter = "groupId";
/// <summary>Name of the generated vertex input struct.</summary>
public const string VertexInputStruct = "VsIn";
/// <summary>Name of the generated vertex output / pixel input struct.</summary>
public const string VertexOutputStruct = "VsOut";
/// <summary>Name of the environment parameter block declared by the <c>prism.core</c> prelude.</summary>
public const string EnvironmentBlock = "gPrismEnv";
/// <summary>Prefix given to the locals an entry point prologue declares for builtins.</summary>
public const string LocalPrefix = "prism";
// ---- intrinsics --------------------------------------------------------
/// <summary>
/// The Slang spelling of a canonical intrinsic. Falls back to the HLSL spelling in
/// <see cref="IntrinsicCatalog"/>, because Slang accepts the whole HLSL intrinsic surface.
/// </summary>
public static string Name( Intrinsic id ) => id switch
{
// Slang spells the centroid evaluator the DXC way.
Intrinsic.EvaluateAttributeCentroid => "EvaluateAttributeAtCentroid",
// GetDimensions is an out-parameter method in Slang and cannot appear in an expression,
// so the prelude provides a value-returning wrapper instead.
Intrinsic.TextureSize => "PrismTextureSize",
// Component-wise logic. Never `&&` / `||`, which only short-circuit for scalars.
Intrinsic.AndFn => "and",
Intrinsic.OrFn => "or",
_ => IntrinsicCatalog.Name( id )
};
/// <summary>
/// True when the intrinsic is a method on its first argument — <c>tex.Sample( s, uv )</c> rather
/// than <c>Sample( tex, s, uv )</c>.
/// </summary>
public static bool IsObjectMethod( Intrinsic id ) => id is
Intrinsic.Sample or Intrinsic.SampleLevel or Intrinsic.SampleBias or Intrinsic.SampleGrad or
Intrinsic.SampleCmp or Intrinsic.SampleCmpLevelZero or
Intrinsic.Gather or Intrinsic.GatherRed or Intrinsic.GatherGreen or Intrinsic.GatherBlue or
Intrinsic.GatherAlpha or Intrinsic.GatherCmp or
Intrinsic.Load or
Intrinsic.CalculateLevelOfDetail or Intrinsic.CalculateLevelOfDetailUnclamped;
/// <summary>True when the operation is provided by the emitted <c>prism.core</c> prelude.</summary>
public static bool IsPreludeHelper( Intrinsic id ) => id is Intrinsic.TextureSize;
/// <summary>True when the intrinsic writes through an <c>out</c> parameter and is a statement, not a value.</summary>
public static bool IsVoidResult( Intrinsic id ) => id is
Intrinsic.SinCos or Intrinsic.Clip or
Intrinsic.AllMemoryBarrier or Intrinsic.AllMemoryBarrierWithGroupSync or
Intrinsic.DeviceMemoryBarrier or Intrinsic.DeviceMemoryBarrierWithGroupSync or
Intrinsic.GroupMemoryBarrier or Intrinsic.GroupMemoryBarrierWithGroupSync or
Intrinsic.InterlockedAdd or Intrinsic.InterlockedMin or Intrinsic.InterlockedMax or
Intrinsic.InterlockedAnd or Intrinsic.InterlockedOr or Intrinsic.InterlockedXor or
Intrinsic.InterlockedExchange or Intrinsic.InterlockedCompareExchange or
Intrinsic.InterlockedCompareStore;
// ---- operators ---------------------------------------------------------
/// <summary>The Slang symbol for a binary operator.</summary>
public static string Symbol( BinaryOp op ) => BinaryOps.Symbol( op );
/// <summary>The Slang symbol for a unary operator.</summary>
public static string Symbol( UnaryOp op ) => UnaryOps.Symbol( op );
/// <summary>
/// True when an operator must be written in function form instead of symbol form.
/// <para>
/// <c>&&</c> and <c>||</c> only short-circuit for scalar operands; on a vector Slang
/// evaluates both sides and warns. The core module's <c>and()</c> / <c>or()</c> are the
/// component-wise spellings, so that is what we emit.
/// </para>
/// </summary>
public static bool RequiresFunctionForm( BinaryOp op, ShaderType operandType ) =>
BinaryOps.IsShortCircuit( op ) && !operandType.IsScalar && !operandType.IsVoid;
/// <summary>The function spelling of a short-circuit operator, for component-wise use.</summary>
public static string FunctionForm( BinaryOp op ) => op == BinaryOp.LogicalOr ? "or" : "and";
// ---- types -------------------------------------------------------------
/// <summary>
/// Element type given to a buffer whose element type the IR does not carry. Slang's buffer types
/// are generic with no default, unlike its textures, so a spelling has to be chosen.
/// </summary>
public const string DefaultBufferElement = "float4";
/// <summary>The Slang spelling of a type.</summary>
public static string TypeName( ShaderType type )
{
if ( type.IsObject )
{
switch ( type.Object )
{
case ObjectKind.Buffer:
case ObjectKind.StructuredBuffer:
case ObjectKind.RWBuffer:
case ObjectKind.RWStructuredBuffer:
return $"{ShaderType.ObjectName( type.Object )}<{DefaultBufferElement}>";
}
}
return type.Slang;
}
/// <summary>The Slang interpolation modifier, or an empty string for the default.</summary>
public static string Interpolation( IrInterpolation interpolation ) => interpolation switch
{
IrInterpolation.NoPerspective => "noperspective",
IrInterpolation.NoInterpolation => "nointerpolation",
IrInterpolation.Centroid => "centroid",
IrInterpolation.Sample => "sample",
_ => string.Empty
};
/// <summary>The <c>[shader("...")]</c> attribute for a stage, or null when the stage has none.</summary>
public static string StageAttribute( ShaderStage stage )
{
var name = stage.SlangStage();
return string.IsNullOrEmpty( name ) ? null : $"[shader(\"{name}\")]";
}
// ---- literals ----------------------------------------------------------
/// <summary>Format one component of a literal according to the component type.</summary>
public static string Scalar( double value, ScalarKind kind ) => kind switch
{
ScalarKind.Bool => value != 0 ? "true" : "false",
ScalarKind.Int => ( (long)Math.Clamp( value, int.MinValue, int.MaxValue ) ).ToString( CultureInfo.InvariantCulture ),
ScalarKind.UInt => ( (ulong)Math.Clamp( value, 0, uint.MaxValue ) ).ToString( CultureInfo.InvariantCulture ) + "u",
_ => Real( value )
};
/// <summary>
/// Format a literal of any type. Vectors whose components are all equal collapse to the
/// single-argument constructor, which is both shorter and how a human would write it.
/// </summary>
public static string Literal( ShaderType type, ConstValue value )
{
if ( type.IsVoid ) return "0";
if ( type.IsScalar ) return Scalar( value[0], type.Scalar );
if ( type.IsVector )
{
var components = Math.Clamp( type.Components, 1, 4 );
if ( value.AllEqual( value[0], components ) )
{
return $"{TypeName( type )}( {Scalar( value[0], type.Scalar )} )";
}
var parts = new string[components];
for ( int i = 0; i < components; i++ ) parts[i] = Scalar( value[i], type.Scalar );
return $"{TypeName( type )}( {string.Join( ", ", parts )} )";
}
// A matrix literal cannot be fully represented by four components, so a matrix constant is
// always a broadcast of its first component. The IR builds real matrices with IrConstruct.
if ( type.IsMatrix ) return $"{TypeName( type )}( {Scalar( value[0], type.Scalar )} )";
return $"( {TypeName( type )} )0";
}
/// <summary>Format a floating-point literal so it round-trips and always reads as a float.</summary>
public static string Real( double value )
{
if ( double.IsNaN( value ) ) value = 0;
if ( double.IsPositiveInfinity( value ) ) value = 3.402823466e+38;
if ( double.IsNegativeInfinity( value ) ) value = -3.402823466e+38;
var text = ( (float)value ).ToString( "R", CultureInfo.InvariantCulture );
if ( text.IndexOf( '.' ) < 0 && text.IndexOf( 'E' ) < 0 && text.IndexOf( 'e' ) < 0 )
{
text += ".0";
}
return text;
}
/// <summary>Escape a string so it can appear inside a Slang string literal.</summary>
public static string QuotedString( string value )
{
if ( string.IsNullOrEmpty( value ) ) return "\"\"";
var builder = new StringBuilder( value.Length + 2 );
builder.Append( '"' );
foreach ( var c in value )
{
switch ( c )
{
case '"': builder.Append( "\\\"" ); break;
case '\\': builder.Append( "\\\\" ); break;
case '\r': break;
case '\n': builder.Append( ' ' ); break;
case '\t': builder.Append( ' ' ); break;
default: builder.Append( c ); break;
}
}
builder.Append( '"' );
return builder.ToString();
}
// ---- identifiers -------------------------------------------------------
/// <summary>True when the identifier collides with a Slang keyword or modifier.</summary>
public static bool IsReserved( string identifier ) =>
!string.IsNullOrEmpty( identifier ) && s_reserved.Contains( identifier );
/// <summary>
/// Turn arbitrary text into a legal Slang identifier, preserving as much of the original as
/// possible so the generated module still reads like the graph that produced it.
/// </summary>
public static string SanitizeIdentifier( string name, string fallback = "prismValue" )
{
if ( string.IsNullOrWhiteSpace( name ) ) return fallback;
var builder = new StringBuilder( name.Length );
foreach ( var c in name )
{
if ( char.IsLetterOrDigit( c ) || c == '_' ) builder.Append( c );
else if ( builder.Length > 0 && builder[^1] != '_' ) builder.Append( '_' );
}
while ( builder.Length > 0 && builder[^1] == '_' ) builder.Length--;
if ( builder.Length == 0 ) return fallback;
if ( char.IsDigit( builder[0] ) ) builder.Insert( 0, '_' );
var result = builder.ToString();
return IsReserved( result ) ? result + "_" : result;
}
/// <summary>PascalCase an identifier, dropping the shader-world hungarian prefixes on the way.</summary>
public static string PascalCase( string name )
{
var identifier = SanitizeIdentifier( name, "Value" );
identifier = StripPrefix( identifier );
if ( identifier.Length == 0 ) return "Value";
var builder = new StringBuilder( identifier.Length );
var upper = true;
foreach ( var c in identifier )
{
if ( c == '_' )
{
upper = true;
continue;
}
builder.Append( upper ? char.ToUpperInvariant( c ) : c );
upper = false;
}
if ( builder.Length == 0 ) return "Value";
if ( char.IsDigit( builder[0] ) ) builder.Insert( 0, '_' );
var result = builder.ToString();
return IsReserved( result ) ? result + "_" : result;
}
/// <summary>
/// Normalise an engine field spelling to the Slang-idiomatic name the generated interface structs
/// use. Anything unrecognised passes through untouched, so a struct field the graph invented still
/// resolves against the declaration we copied from the module.
/// </summary>
public static string FieldName( string name )
{
if ( string.IsNullOrEmpty( name ) ) return name;
if ( s_fieldAliases.TryGetValue( name, out var alias ) ) return alias;
return SanitizeIdentifier( name, "Field" );
}
static string StripPrefix( string identifier )
{
// g_flRoughness -> Roughness, g_vTint -> Tint, m_Foo -> Foo.
foreach ( var prefix in s_symbolPrefixes )
{
if ( identifier.Length <= prefix.Length ) continue;
if ( !identifier.StartsWith( prefix, StringComparison.Ordinal ) ) continue;
var tail = identifier[prefix.Length..];
if ( tail.Length > 0 && ( char.IsLetter( tail[0] ) || tail[0] == '_' ) ) return tail.TrimStart( '_' );
}
return identifier;
}
// ---- builtins ----------------------------------------------------------
/// <summary>The name of the prologue local an entry point binds a builtin to.</summary>
public static string BuiltinLocal( Builtin id ) => LocalPrefix + id;
/// <summary>
/// Builtins this one is derived from. The prologue emits dependencies first, so
/// <c>ViewDirection</c> can be written in terms of the already-bound <c>WorldPosition</c> local.
/// </summary>
public static IReadOnlyList<Builtin> Dependencies( Builtin id, ShaderStage stage )
{
if ( stage != ShaderStage.Vertex )
{
return id == Builtin.ViewDirection ? s_dependsWorldPosition : Array.Empty<Builtin>();
}
return id switch
{
Builtin.WorldTangentV => s_dependsTangentFrame,
Builtin.ClipPosition => s_dependsWorldPosition,
Builtin.ScreenUv => s_dependsClipPosition,
Builtin.ViewDirection => s_dependsWorldPosition,
_ => Array.Empty<Builtin>()
};
}
/// <summary>
/// True when a builtin has to travel from the vertex stage to the pixel stage through an
/// interpolator, and therefore becomes a field of the generated <c>VsOut</c> struct.
/// </summary>
public static bool IsInterpolated( Builtin id ) => InterpolantField( id ) is not null;
/// <summary>The <c>VsOut</c> field that carries a builtin, or null when it is not interpolated.</summary>
public static string InterpolantField( Builtin id ) => id switch
{
Builtin.WorldPosition => "WorldPosition",
Builtin.ObjectPosition => "ObjectPosition",
Builtin.WorldNormal => "WorldNormal",
Builtin.ObjectNormal => "ObjectNormal",
Builtin.WorldTangentU => "WorldTangentU",
Builtin.WorldTangentV => "WorldTangentV",
Builtin.ObjectTangentU => "ObjectTangentU",
Builtin.VertexColor => "Color",
Builtin.TexCoord0 => "Uv",
Builtin.TexCoord1 => "Uv2",
Builtin.VertexId => "VertexId",
Builtin.InstanceId => "InstanceId",
_ => null
};
/// <summary>
/// The <c>VsIn</c> attribute a builtin is derived from in the vertex stage, or null when it needs
/// none. A domain whose vertex input does not carry that attribute — a full-screen post-process
/// pass, for instance — binds the builtin to zero instead of naming a field that does not exist.
/// </summary>
public static string VertexInputField( Builtin id ) => id switch
{
Builtin.WorldPosition or Builtin.ObjectPosition or
Builtin.ClipPosition or Builtin.ScreenUv => "Position",
Builtin.WorldNormal or Builtin.ObjectNormal => "Normal",
Builtin.WorldTangentU or Builtin.WorldTangentV or Builtin.ObjectTangentU => "Tangent",
Builtin.VertexColor => "Color",
Builtin.TexCoord0 => "Uv",
Builtin.TexCoord1 => "Uv2",
Builtin.VertexId => "VertexId",
Builtin.InstanceId => "InstanceId",
_ => null
};
/// <summary>How an interpolated builtin's field interpolates across a triangle.</summary>
public static IrInterpolation InterpolantMode( Builtin id ) =>
id is Builtin.VertexId or Builtin.InstanceId ? IrInterpolation.NoInterpolation : IrInterpolation.Linear;
/// <summary>
/// The Slang expression a builtin lowers to in a given stage.
/// <para>
/// Vertex-stage expressions are computed from the <c>VsIn</c> attributes and the environment
/// parameter block; pixel-stage expressions read the interpolated <c>VsOut</c> field. This is the
/// single place that knows a builtin is a different expression in each stage — nodes never do.
/// </para>
/// </summary>
public static string BuiltinExpression( Builtin id, ShaderStage stage )
{
var input = InputParameter;
var frame = EnvironmentBlock + ".Frame";
var obj = EnvironmentBlock + ".Object";
switch ( id )
{
// -- uniform: identical in every stage
case Builtin.ObjectOrigin: return $"{obj}.ObjectOrigin";
case Builtin.ObjectScale: return $"{obj}.ObjectScale";
case Builtin.TintColor: return $"{obj}.TintColor";
case Builtin.ObjectToWorld: return $"{obj}.ObjectToWorld";
case Builtin.WorldToObject: return $"{obj}.WorldToObject";
case Builtin.CameraPosition: return $"{frame}.CameraPosition";
case Builtin.CameraForward: return $"{frame}.CameraForward";
case Builtin.CameraNear: return $"{frame}.CameraNear";
case Builtin.CameraFar: return $"{frame}.CameraFar";
case Builtin.ViewportSize: return $"{frame}.ViewportSize";
case Builtin.ViewportInvSize: return $"{frame}.ViewportInvSize";
case Builtin.ViewportOffset: return $"{frame}.ViewportOffset";
case Builtin.SunDirection: return $"{frame}.SunDirection";
case Builtin.SunColor: return $"{frame}.SunColor";
case Builtin.Time: return $"{frame}.Time";
case Builtin.DeltaTime: return $"{frame}.DeltaTime";
case Builtin.FrameCount: return $"{frame}.FrameCount";
case Builtin.ViewMatrix: return $"{frame}.WorldToView";
case Builtin.ProjectionMatrix: return $"{frame}.ViewToProjection";
case Builtin.ViewProjectionMatrix: return $"{frame}.WorldToProjection";
// -- compute
case Builtin.DispatchThreadId:
return stage == ShaderStage.Compute ? DispatchThreadIdParameter : "uint3( 0 )";
case Builtin.GroupThreadId:
return stage == ShaderStage.Compute ? GroupThreadIdParameter : "uint3( 0 )";
case Builtin.GroupId:
return stage == ShaderStage.Compute ? GroupIdParameter : "uint3( 0 )";
// -- view dependent
case Builtin.ViewDirection:
return $"PrismSafeNormalize( {frame}.CameraPosition - {BuiltinLocal( Builtin.WorldPosition )} )";
}
if ( stage == ShaderStage.Vertex ) return VertexExpression( id, input );
if ( stage == ShaderStage.Pixel ) return PixelExpression( id, input );
return Zero( Builtins.TypeOf( id ) );
}
static string VertexExpression( Builtin id, string input ) => id switch
{
Builtin.WorldPosition => $"PrismObjectToWorldPoint( {input}.Position )",
Builtin.ObjectPosition => $"{input}.Position",
Builtin.WorldNormal => $"PrismObjectToWorldNormal( {input}.Normal )",
Builtin.ObjectNormal => $"{input}.Normal",
Builtin.WorldTangentU => $"PrismObjectToWorldDirection( {input}.Tangent.xyz )",
Builtin.WorldTangentV =>
$"cross( {BuiltinLocal( Builtin.WorldNormal )}, {BuiltinLocal( Builtin.WorldTangentU )} ) * {input}.Tangent.w",
Builtin.ObjectTangentU => $"{input}.Tangent.xyz",
Builtin.VertexColor => $"{input}.Color",
Builtin.TexCoord0 => $"{input}.Uv",
Builtin.TexCoord1 => $"{input}.Uv2",
Builtin.ClipPosition => $"PrismWorldToClip( {BuiltinLocal( Builtin.WorldPosition )} )",
Builtin.ScreenUv => $"PrismScreenUvFromClip( {BuiltinLocal( Builtin.ClipPosition )} )",
Builtin.VertexId => $"{input}.VertexId",
Builtin.InstanceId => $"{input}.InstanceId",
Builtin.IsFrontFace => "true",
_ => Zero( Builtins.TypeOf( id ) )
};
static string PixelExpression( Builtin id, string input ) => id switch
{
Builtin.WorldPosition => $"{input}.WorldPosition",
Builtin.ObjectPosition => $"{input}.ObjectPosition",
Builtin.WorldNormal => $"PrismSafeNormalize( {input}.WorldNormal )",
Builtin.ObjectNormal => $"PrismSafeNormalize( {input}.ObjectNormal )",
Builtin.WorldTangentU => $"PrismSafeNormalize( {input}.WorldTangentU )",
Builtin.WorldTangentV => $"PrismSafeNormalize( {input}.WorldTangentV )",
Builtin.ObjectTangentU => $"{input}.ObjectTangentU",
Builtin.VertexColor => $"{input}.Color",
Builtin.TexCoord0 => $"{input}.Uv",
Builtin.TexCoord1 => $"{input}.Uv2",
Builtin.ClipPosition => $"{input}.Position",
Builtin.ScreenUv => $"{input}.Position.xy * {EnvironmentBlock}.Frame.ViewportInvSize",
Builtin.PixelPosition => $"{input}.Position.xy",
Builtin.FragmentDepth => $"{input}.Position.z",
Builtin.IsFrontFace => FrontFaceParameter,
Builtin.VertexId => $"{input}.VertexId",
Builtin.InstanceId => $"{input}.InstanceId",
_ => Zero( Builtins.TypeOf( id ) )
};
/// <summary>A zero value of a type, used where a builtin has no meaning in the current stage.</summary>
public static string Zero( ShaderType type )
{
if ( type.IsVoid ) return "0";
if ( type.IsScalar ) return Scalar( 0, type.Scalar );
return $"{TypeName( type )}( {Scalar( 0, type.Scalar )} )";
}
static readonly Builtin[] s_dependsWorldPosition = [Builtin.WorldPosition];
static readonly Builtin[] s_dependsClipPosition = [Builtin.WorldPosition, Builtin.ClipPosition];
static readonly Builtin[] s_dependsTangentFrame = [Builtin.WorldNormal, Builtin.WorldTangentU];
static readonly string[] s_symbolPrefixes =
[
"g_fl", "g_v", "g_col", "g_b", "g_n", "g_i", "g_t", "g_m", "g_s", "g_", "m_", "s_", "_"
];
static readonly Dictionary<string, string> s_fieldAliases = new( StringComparer.Ordinal )
{
["vPositionOs"] = "Position",
["vPositionWs"] = "WorldPosition",
["vPositionPs"] = "Position",
["vPositionSs"] = "Position",
["vPositionWithOffsetWs"] = "WorldPosition",
["vNormalOs"] = "Normal",
["vNormalWs"] = "WorldNormal",
["vTangentUOs_flTangentVSign"] = "Tangent",
["vTangentUWs"] = "WorldTangentU",
["vTangentVWs"] = "WorldTangentV",
["vTexCoord"] = "Uv",
["vTextureCoords"] = "Uv",
["vTexCoord2"] = "Uv2",
["vVertexColor"] = "Color",
["vColor"] = "Color",
["vBlendValues"] = "BlendValues",
["nInstanceTransformID"] = "InstanceId",
["nVertexIndex"] = "VertexId",
["vLightmapUVs"] = "LightmapUv"
};
static readonly HashSet<string> s_reserved = new( StringComparer.Ordinal )
{
// control flow
"if", "else", "switch", "case", "default", "return", "try", "throw", "throws", "catch",
"while", "for", "do", "break", "continue", "discard", "defer",
// declarations
"let", "var", "func", "typedef", "typealias", "property", "get", "set",
"class", "struct", "interface", "enum", "extension", "associatedtype",
"namespace", "using", "import", "module", "implementing",
"cbuffer", "tbuffer", "where", "syntax", "semantic", "type_param", "typename",
// modifiers
"static", "const", "extern", "inline", "public", "private", "internal", "protected",
"uniform", "groupshared", "shared", "volatile", "coherent", "restrict",
"readonly", "writeonly", "export", "override", "param", "require",
"row_major", "column_major", "nointerpolation", "noperspective", "linear", "sample",
"centroid", "precise", "in", "out", "inout", "ref", "dyn", "some", "implicit",
"noncopyable", "constexpr", "mutating", "point", "line", "triangle", "lineadj",
"triangleadj", "vertices", "indices", "primitives", "payload", "layout",
// expressions and literals
"as", "is", "this", "This", "sizeof", "alignof", "countof", "each", "expand",
"optional", "nonempty", "true", "false", "nullptr", "none", "no_diff",
// types
"void", "bool", "int", "uint", "half", "float", "double", "string",
"vector", "matrix", "functype", "int8_t", "int16_t", "int32_t", "int64_t",
"uint8_t", "uint16_t", "uint32_t", "uint64_t", "float16_t", "float32_t", "float64_t"
};
}