mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-21 04:40:00 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
229 lines
5.6 KiB
C#
229 lines
5.6 KiB
C#
using Sandbox.Engine;
|
|
|
|
namespace Sandbox.UI;
|
|
|
|
internal static partial class StyleParser
|
|
{
|
|
[ThreadStatic]
|
|
static int IncludeLoops = 0;
|
|
|
|
public static StyleSheet ParseSheet( string content, string filename = "none", IEnumerable<(string, string)> variables = null )
|
|
{
|
|
IncludeLoops = 0;
|
|
|
|
StyleSheet sheet = new();
|
|
sheet.AddVariables( variables );
|
|
|
|
ParseToSheet( content, filename, sheet );
|
|
|
|
return sheet;
|
|
}
|
|
|
|
private static void ParseToSheet( string content, string filename, StyleSheet sheet )
|
|
{
|
|
IncludeLoops++;
|
|
|
|
filename ??= "none";
|
|
filename = filename.NormalizeFilename();
|
|
|
|
sheet.AddFilename( filename );
|
|
|
|
content = StripComments( content );
|
|
|
|
var p = new Parse( content, filename );
|
|
while ( !p.IsEnd )
|
|
{
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
|
|
if ( p.IsEnd )
|
|
break;
|
|
|
|
if ( ParseVariable( ref p, sheet ) )
|
|
continue;
|
|
|
|
if ( ParseKeyframes( ref p, sheet ) )
|
|
continue;
|
|
|
|
if ( ParseImport( ref p, sheet, filename ) )
|
|
continue;
|
|
|
|
var selector = p.ReadUntilOrEnd( "{;$@" );
|
|
|
|
if ( selector is null )
|
|
throw new System.Exception( $"Parse Error, expected class name {p.FileAndLine}" );
|
|
|
|
if ( p.IsEnd ) throw new System.Exception( $"Parse Error, unexpected end {p.FileAndLine}" );
|
|
|
|
if ( p.Current != '{' ) throw new System.Exception( $"Parse Error, unexpected character {p.Current} {p.FileAndLine}" );
|
|
|
|
if ( p.Current == '{' )
|
|
{
|
|
ReadStyleBlock( ref p, selector, sheet, null );
|
|
}
|
|
}
|
|
|
|
IncludeLoops--;
|
|
}
|
|
|
|
private static bool ParseVariable( ref Parse p, StyleSheet sheet )
|
|
{
|
|
if ( p.Current != '$' )
|
|
return false;
|
|
|
|
// We want the key with the $
|
|
(string key, string value) = p.ReadKeyValue();
|
|
|
|
bool isDefault = value.EndsWith( "!default", StringComparison.OrdinalIgnoreCase );
|
|
if ( isDefault )
|
|
{
|
|
value = value[..^8].Trim();
|
|
}
|
|
|
|
// Console.WriteLine( $"Found [{key}] = [{value}] ({isDefault})" );
|
|
|
|
sheet.SetVariable( key, value, isDefault );
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void TryImport( StyleSheet sheet, string filename, string includeFileAndLine )
|
|
{
|
|
if ( !GlobalContext.Current.FileMount.FileExists( filename ) )
|
|
throw new System.Exception( $"Missing import {filename} ({includeFileAndLine})" );
|
|
|
|
var text = GlobalContext.Current.FileMount.ReadAllText( filename );
|
|
ParseToSheet( text, filename, sheet );
|
|
}
|
|
|
|
private static bool ParseImport( ref Parse p, StyleSheet sheet, string filename )
|
|
{
|
|
if ( p.Current != '@' )
|
|
return false;
|
|
|
|
var word = p.ReadWord( " ", true );
|
|
|
|
if ( string.IsNullOrWhiteSpace( word ) )
|
|
throw new System.Exception( $"Expected word after @ {p.FileAndLine}" );
|
|
|
|
if ( word == "@import" )
|
|
{
|
|
if ( IncludeLoops > 10 )
|
|
throw new System.Exception( $"Possible infinite @import loop {p.FileAndLine}" );
|
|
|
|
var thisRoot = System.IO.Path.GetDirectoryName( filename );
|
|
var files = p.ReadUntilOrEnd( ";" );
|
|
|
|
if ( string.IsNullOrWhiteSpace( files ) )
|
|
throw new System.Exception( $"Expected files then ; after @import {p.FileAndLine}" );
|
|
|
|
// files could be
|
|
// 1. "file", "file", "file"
|
|
// 2. "file"
|
|
// 3. 'file'
|
|
|
|
foreach ( var file in files.Split( ',', StringSplitOptions.RemoveEmptyEntries ) )
|
|
{
|
|
var cleanFile = file.Trim( ' ', '\"', '\'' );
|
|
if ( cleanFile.StartsWith( "./" ) ) cleanFile = cleanFile.Substring( 2 );
|
|
|
|
while ( cleanFile.StartsWith( "../" ) || cleanFile.StartsWith( "..\\" ) )
|
|
{
|
|
thisRoot = System.IO.Path.GetDirectoryName( thisRoot );
|
|
cleanFile = cleanFile.Substring( 3 );
|
|
}
|
|
|
|
// if no extension clean it up as an include
|
|
if ( !System.IO.Path.HasExtension( cleanFile ) ) cleanFile = $"_{cleanFile}.scss";
|
|
|
|
// try to find file in local directory, if not found then fall back
|
|
var localPath = System.IO.Path.Combine( thisRoot, cleanFile ).ToLower();
|
|
if ( !GlobalContext.Current.FileMount.FileExists( localPath ) )
|
|
{
|
|
localPath = cleanFile.ToLower();
|
|
}
|
|
|
|
TryImport( sheet, localPath, p.FileAndLine );
|
|
}
|
|
|
|
if ( p.Is( ';' ) )
|
|
p.Pointer++;
|
|
|
|
return true;
|
|
}
|
|
|
|
throw new System.Exception( $"Unknown rule {word} {p.FileAndLine}" );
|
|
}
|
|
|
|
private static bool ParseKeyframes( ref Parse p, StyleSheet sheet )
|
|
{
|
|
var keyframe = KeyFrames.Parse( ref p );
|
|
if ( keyframe == null )
|
|
return false;
|
|
|
|
sheet.AddKeyFrames( keyframe );
|
|
return true;
|
|
}
|
|
|
|
static void ReadStyleBlock( ref Parse p, string selectors, StyleSheet sheet, StyleBlock parentNode )
|
|
{
|
|
if ( p.Current != '{' )
|
|
throw new System.Exception( $"Block doesn't start with {{ {p.FileAndLine}" );
|
|
|
|
p.Pointer++;
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
|
|
var node = new StyleBlock();
|
|
node.LoadOrder = sheet.Nodes.Count();
|
|
node.FileName = p.FileName;
|
|
node.AbsolutePath = GlobalContext.Current.FileMount?.GetFullPath( p.FileName );
|
|
node.FileLine = p.CurrentLine;
|
|
node.SetSelector( selectors, parentNode );
|
|
|
|
var styles = new Styles();
|
|
|
|
while ( !p.IsEnd )
|
|
{
|
|
var content = p.ReadUntilOrEnd( ";{}" );
|
|
if ( content is null ) throw new System.Exception( $"Parse Error, expected class name {p.FileAndLine}" );
|
|
|
|
if ( p.Current == '{' )
|
|
{
|
|
ReadStyleBlock( ref p, content, sheet, node );
|
|
continue;
|
|
}
|
|
|
|
if ( p.Current == ';' )
|
|
{
|
|
try
|
|
{
|
|
content = sheet.ReplaceVariables( content );
|
|
}
|
|
catch ( System.Exception e )
|
|
{
|
|
throw new System.Exception( $"{e.Message} {p.FileAndLine}" );
|
|
}
|
|
|
|
styles.SetInternal( content, p.FileName, p.CurrentLine );
|
|
p.Pointer++;
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
}
|
|
|
|
if ( p.Current == '}' )
|
|
{
|
|
p.Pointer++;
|
|
node.Styles = styles;
|
|
|
|
// Only add this node if it's not empty
|
|
if ( !node.IsEmpty )
|
|
{
|
|
sheet.Nodes.Add( node );
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new System.Exception( $"Unexpected end of block {p.FileAndLine}" );
|
|
}
|
|
}
|