using Godot; using Rokojori; using System.Collections.Generic; using System; using System.Reflection; using System.Text.RegularExpressions; using System.Linq; namespace Rokojori.DocGenerator { public class ShaderDocGenerator { public static readonly string GDShaderExtension = ".gdshader"; public static readonly string GDShaderIncludeExtension = ".gdshaderinc"; ClassDocInfo info = new ClassDocInfo(); List _lexerEvents; LexerList tokens; public ClassDocInfo Create( string filePath ) { var fileContent = FilesSync.LoadUTF8( filePath ); _lexerEvents = GDShaderLexer.Lex( fileContent ); tokens = LexerList.Create( _lexerEvents ); if ( filePath.EndsWith( GDShaderIncludeExtension ) ) { info.definitionType = "GDShaderInclude"; } else { info.definitionType = "GDShader"; } info.name = RegexUtility.TrimToLastPathFragment( filePath ).ReplaceEnd( GDShaderIncludeExtension, "" ).ReplaceEnd( GDShaderExtension, "" ); GetIncludes(); GetUniforms(); GetBlocks(); return info; } void GetIncludes() { var includePrexix = "#include "; RJLog.Log( "Processing includes:", tokens.GetAll( LexerMatcherLibrary.CInstructionMatcher ).events.Count ); tokens.ForEach( LexerMatcherLibrary.CInstructionMatcher, ( t )=> { var match = t.match; RJLog.Log( "Processing include:", match ); if ( ! match.StartsWith( includePrexix ) ) { RJLog.Log( "Processing include:", match ); return; } var path = match.ReplaceStart( includePrexix, "" ); path = path.Substring( 1, path.Length - 2 ); var name = RegexUtility.TrimToLastPathFragment( path ).TrimSuffix( ".gdshaderinc"); var memberInfo = MemberInfo.Create( "ShaderInclude" ); memberInfo.name = name; memberInfo.path = path; memberInfo.dataType = "GDShaderInclude"; info.memberInfos.Add( memberInfo ); } ); } void GetUniforms() { var sequences = tokens.FindSequences( ( int tokenIndex, bool inSequence ) => { var token = tokens.events[ tokenIndex ]; if ( ! inSequence ) { if ( token.Is( GDShaderLexer.UsageMatcher ) ) { return Trillean.True; } return Trillean.Any; } else { return TrilleanLogic.FromBool( ! token.Is( LexerMatcherLibrary.OperatorMatcher, ";" ) ); } } ); // RJLog.Log( "Uniforms/Varyings:", sequences.Count ); sequences.ForEach( ( s )=> { if ( s == null ) { return; } var filtered = s.Filter( s => ! s.IsAnyOf( GDShaderLexer.ignore ) ); // RJLog.Log( "'" + filtered.Map( s => s.match ).Join( "', '") + "'" ); if ( filtered.Count < 3 ) { return; } var first = filtered[ 0 ]; var isUniform = first.MatchIs( "uniform" ); var memberInfo = isUniform ? MemberInfo.CreateUniform() : MemberInfo.CreateVarying(); memberInfo.dataType = filtered[ 1 ].match; memberInfo.name = filtered[ 2 ].match; // LINE INFO info.memberInfos.Add( memberInfo ); } ); } void GetBlocks() { var blocks = tokens.GetBlocks(); if ( blocks == null ) { return; } // RJLog.Log( "Num blocks:", blocks.Count ); blocks.ForEach( b => ProcessBlock( b ) ); } void ProcessBlock( RangeI blockRange ) { var blockStart = blockRange.min; var firstTokenBeforeResult = tokens.ReverseFind( blockStart - 1, ( t ) => { // RJLog.Log( "Token type:", t ); if ( t.IsAnyOf( GDShaderLexer.ignore ) ) { return LexerEvent.FindResultType.KeepSearching; } if ( ! ( t.Is( LexerMatcherLibrary.BracketMatcher, ")" ) || t.Is( LexerMatcherLibrary.CwordMatcher ) ) ) { // RJLog.Log( "Invalid token type:", t ); return LexerEvent.FindResultType.Error; } return LexerEvent.FindResultType.Found; } ); if ( ! firstTokenBeforeResult.found ) { // RJLog.Log( "No first token before block found!" ); return; } var firstToken = _lexerEvents[ firstTokenBeforeResult.index ]; var isMethod = firstToken.Is( LexerMatcherLibrary.BracketMatcher, ")" ); if ( isMethod ) { var closeBracketResult = firstTokenBeforeResult; var openBracketResult = tokens.ReverseFindOpeningBracket( closeBracketResult.index ); if ( ! openBracketResult.found ) { return; } var nameToken = tokens.ReverseFind( openBracketResult.index, ( t ) => { return t.Is( LexerMatcherLibrary.CFunctionMatcher ) ? LexerEvent.FindResultType.Found : LexerEvent.FindResultType.KeepSearching; } ); if ( ! nameToken.found ) { return; } var typeToken = tokens.ReverseFind( nameToken.index - 1, ( t ) => { return t.Is( LexerMatcherLibrary.CwordMatcher ) ? LexerEvent.FindResultType.Found : LexerEvent.FindResultType.KeepSearching; } ); var memberInfo = MemberInfo.CreateMethod(); memberInfo.name = tokens.events[ nameToken.index ].match; memberInfo.dataType = tokens.events[ typeToken.index ].match; var parameters = tokens.Seperate( openBracketResult.index + 1, closeBracketResult.index - 1, GDShaderLexer.ignore ); parameters.ForEach( ( p )=> { var pValues = tokens.Range( p ).Filter( t => ! t.IsAnyOf( GDShaderLexer.ignore ) ); var parameterType = new ParameterType(); parameterType.name = pValues.events.ReverseAt( 0 ).match; parameterType.type = pValues.events.ReverseAt( 1 ).match; if ( pValues.events.Count > 2 ) { parameterType.modifiers.Add( pValues.events.ReverseAt( 2 ).match ); } memberInfo.parameters.Add( parameterType ); } ); info.memberInfos.Add( memberInfo ); // RJLog.Log( "Method found!", memberInfo.memberType, memberInfo.name ); } else { // RJLog.Log( "No method found!", firstToken ); } } } }