// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Threading;
using EpicGames.Core;
using EpicGames.UHT.Tables;
using EpicGames.UHT.Tokenizer;
using EpicGames.UHT.Utils;
namespace EpicGames.UHT.Parsers
{
///
/// Class responsible for parsing specifiers and the field meta data. To reduce allocations, one specifier parser is shared between all objects in
/// a given header file. This makes the Action pattern being used a bit more obtuse, but it does help performance by reducing the allocations fairly
/// significantly.
///
public class UhtSpecifierParser : IUhtMessageExtraContext
{
struct DeferredSpecifier
{
public UhtSpecifier _specifier;
public object? _value;
}
///
/// For a given header file, we share a common specifier parser to reduce the number of allocations.
/// Before the parser can be reused, the ParseDeferred method must be called to dispatch that list.
///
private static readonly ThreadLocal s_tls = new(() => null);
private static readonly List> s_emptyKVPValues = new();
private UhtSpecifierContext _specifierContext;
private IUhtTokenReader _tokenReader;
private UhtSpecifierTable _table;
private StringView _context;
private StringView _currentSpecifier = new();
private List _deferredSpecifiers = new();
private bool _isParsingFieldMetaData = false;
private List>? _currentKVPValues = null;
private List? _currentStringValues = null;
private int _umetaElementsParsed = 0;
private readonly Action _parseAction;
private readonly Action _parseFieldMetaDataAction;
private readonly Action _parseKVPValueAction;
private readonly Action _parseStringViewListAction;
///
/// Get the cached specifier parser
///
/// Specifier context
/// User facing context
/// Specifier table
/// Specifier parser
public static UhtSpecifierParser GetThreadInstance(UhtSpecifierContext specifierContext, StringView context, UhtSpecifierTable table)
{
if (s_tls.Value == null)
{
s_tls.Value = new UhtSpecifierParser(specifierContext, context, table);
}
else
{
s_tls.Value.Reset(specifierContext, context, table);
}
return s_tls.Value;
}
///
/// Construct a new specifier parser
///
/// Specifier context
/// User facing context added to messages
/// Specifier table
public UhtSpecifierParser(UhtSpecifierContext specifierContext, StringView context, UhtSpecifierTable table)
{
_specifierContext = specifierContext;
_tokenReader = specifierContext.TokenReader;
_context = context;
_table = table;
_parseAction = ParseInternal;
_parseFieldMetaDataAction = ParseFieldMetaDataInternal;
_parseKVPValueAction = ParseKVPValueInternal;
_parseStringViewListAction = ParseStringViewListInternal;
}
///
/// Reset an existing parser to parse a new specifier block
///
/// Specifier context
/// User facing context added to messages
/// Specifier table
public void Reset(UhtSpecifierContext specifierContext, StringView context, UhtSpecifierTable table)
{
_specifierContext = specifierContext;
_tokenReader = specifierContext.TokenReader;
_context = context;
_table = table;
_deferredSpecifiers.Clear();
}
///
/// Perform the specify parsing
///
/// The parser
public UhtSpecifierParser ParseSpecifiers()
{
_isParsingFieldMetaData = false;
_specifierContext.MetaData.LineNumber = _tokenReader.InputLine;
using UhtMessageContext tokenContext = new(this);
_tokenReader.RequireList('(', ')', ',', false, _parseAction);
return this;
}
///
/// Parse field meta data
///
/// Specifier parser
public UhtSpecifierParser ParseFieldMetaData()
{
_tokenReader = _specifierContext.TokenReader;
_isParsingFieldMetaData = true;
using UhtMessageContext tokenContext = new(this);
if (_tokenReader.TryOptional("UMETA"))
{
_umetaElementsParsed = 0;
_tokenReader.RequireList('(', ')', ',', false, _parseFieldMetaDataAction);
if (_umetaElementsParsed == 0)
{
_tokenReader.LogError($"No metadata specified while parsing {UhtMessage.FormatContext(this)}");
}
}
return this;
}
///
/// Parse any deferred specifiers
///
public void ParseDeferred()
{
foreach (DeferredSpecifier deferred in _deferredSpecifiers)
{
Dispatch(deferred._specifier, deferred._value);
}
_deferredSpecifiers.Clear();
}
#region IMessageExtraContext implementation
///
public IEnumerable