@@ -22,8 +22,7 @@ public static void GenerateDefinitions(DirectoryInfo inputDirectory, DirectoryIn
2222 GenerateYSLSFilesForUnity ( inputDirectory , outputDirectory ) ;
2323 break ;
2424 case "Godot-gd" :
25- Log . Error ( "At this stage ysc only supports generating YSLS files for csharp projects" ) ;
26- System . Environment . Exit ( 1 ) ;
25+ GenerateYSLSFilesForGodotGDScript ( inputDirectory , outputDirectory ) ;
2726 break ;
2827 case "Godot-csharp" :
2928 GenerateYSLSFilesForGodot ( inputDirectory , outputDirectory ) ;
@@ -204,6 +203,197 @@ private static void GenerateYSLSFilesForGodot(DirectoryInfo inputDirectory, Dire
204203 } ;
205204 GenerateYSLSFilesForCSharp ( inputDirectory , outputDirectory , required , emptyList , emptyList ) ;
206205 }
206+
207+ private static void GenerateYSLSFilesForGodotGDScript ( DirectoryInfo inputDirectory , DirectoryInfo outputDirectory )
208+ {
209+ Log . Info ( $ "Scanning GDScript files in { inputDirectory . FullName } ") ;
210+
211+ var gdFiles = inputDirectory . GetFiles ( "*.gd" , SearchOption . AllDirectories ) ;
212+ Log . Info ( $ "Found { gdFiles . Length } .gd files") ;
213+
214+ var commands = new List < string > ( ) ;
215+ var functions = new List < string > ( ) ;
216+
217+ foreach ( var file in gdFiles )
218+ {
219+ var lines = File . ReadAllLines ( file . FullName ) ;
220+ var relativePath = Path . GetRelativePath ( inputDirectory . FullName , file . FullName ) ;
221+
222+ for ( int i = 0 ; i < lines . Length ; i ++ )
223+ {
224+ var line = lines [ i ] . Trim ( ) ;
225+
226+ string ? prefix = null ;
227+ bool isFunction = false ;
228+
229+ if ( line . StartsWith ( "func _yarn_command_" ) )
230+ {
231+ prefix = "_yarn_command_" ;
232+ }
233+ else if ( line . StartsWith ( "func _yarn_function_" ) )
234+ {
235+ prefix = "_yarn_function_" ;
236+ isFunction = true ;
237+ }
238+
239+ if ( prefix == null ) continue ;
240+
241+ // Handle multi-line signatures by joining lines until we find ')'
242+ var fullLine = line ;
243+ while ( ! fullLine . Contains ( ')' ) && i + 1 < lines . Length )
244+ {
245+ i ++ ;
246+ fullLine += " " + lines [ i ] . Trim ( ) ;
247+ }
248+
249+ var parsed = ParseGDScriptAction ( fullLine , prefix , isFunction , relativePath , i ) ;
250+ if ( parsed != null )
251+ {
252+ ( isFunction ? functions : commands ) . Add ( parsed ) ;
253+ }
254+ }
255+ }
256+
257+ if ( commands . Count == 0 && functions . Count == 0 )
258+ {
259+ Log . Info ( " 😴 No Yarn commands or functions found in GDScript files" ) ;
260+ return ;
261+ }
262+
263+ var ysls = "{" +
264+ @"""version"":2," +
265+ $@ """commands"":[{ string . Join ( "," , commands ) } ]," +
266+ $@ """functions"":[{ string . Join ( "," , functions ) } ]" +
267+ "}" ;
268+
269+ if ( ! outputDirectory . Exists )
270+ {
271+ outputDirectory . Create ( ) ;
272+ }
273+
274+ var outputPath = Path . Combine ( outputDirectory . FullName , "GDScript.ysls.json" ) ;
275+ File . WriteAllText ( outputPath , ysls ) ;
276+ Log . Info ( $ " 😎 Found { commands . Count } commands and { functions . Count } functions") ;
277+ Log . Info ( $ " 😎 Wrote { outputPath } ") ;
278+ }
279+
280+ private static string ? ParseGDScriptAction ( string line , string prefix , bool isFunction , string fileName , int lineNumber )
281+ {
282+ var afterFunc = line . Substring ( "func " . Length ) ;
283+ var parenStart = afterFunc . IndexOf ( '(' ) ;
284+ if ( parenStart < 0 ) return null ;
285+
286+ var methodName = afterFunc . Substring ( 0 , parenStart ) ;
287+ var yarnName = methodName . Substring ( prefix . Length ) ;
288+
289+ var parenEnd = afterFunc . IndexOf ( ')' ) ;
290+ if ( parenEnd < 0 ) return null ;
291+
292+ var paramString = afterFunc . Substring ( parenStart + 1 , parenEnd - parenStart - 1 ) . Trim ( ) ;
293+ var parameters = ParseGDScriptParams ( paramString ) ;
294+
295+ // Extract return type from -> annotation
296+ string ? returnTypeStr = null ;
297+ var arrowIndex = afterFunc . IndexOf ( "->" , parenEnd ) ;
298+ if ( arrowIndex >= 0 )
299+ {
300+ returnTypeStr = afterFunc . Substring ( arrowIndex + 2 ) . Trim ( ) . TrimEnd ( ':' ) . Trim ( ) ;
301+ }
302+
303+ var result = new Dictionary < string , object ? >
304+ {
305+ [ "yarnName" ] = yarnName ,
306+ [ "definitionName" ] = methodName ,
307+ [ "fileName" ] = fileName ,
308+ [ "language" ] = "gdscript" ,
309+ [ "containsErrors" ] = false ,
310+ [ "parameters" ] = parameters ,
311+ [ "location" ] = new Dictionary < string , object >
312+ {
313+ [ "start" ] = new Dictionary < string , int > { [ "line" ] = lineNumber , [ "character" ] = 0 } ,
314+ [ "end" ] = new Dictionary < string , int > { [ "line" ] = lineNumber , [ "character" ] = line . Length }
315+ }
316+ } ;
317+
318+ if ( isFunction )
319+ {
320+ var yarnType = returnTypeStr != null ? MapGDScriptTypeToYarn ( returnTypeStr ) : "any" ;
321+ result [ "return" ] = new Dictionary < string , string > { [ "type" ] = yarnType } ;
322+ }
323+ else
324+ {
325+ result [ "async" ] = returnTypeStr ? . Equals ( "Signal" , StringComparison . OrdinalIgnoreCase ) == true ;
326+ }
327+
328+ return System . Text . Json . JsonSerializer . Serialize ( result ) ;
329+ }
330+
331+ private static List < Dictionary < string , object ? > > ParseGDScriptParams ( string paramString )
332+ {
333+ var result = new List < Dictionary < string , object ? > > ( ) ;
334+ if ( string . IsNullOrWhiteSpace ( paramString ) ) return result ;
335+
336+ var parts = paramString . Split ( ',' ) ;
337+ foreach ( var part in parts )
338+ {
339+ var trimmed = part . Trim ( ) ;
340+ if ( string . IsNullOrEmpty ( trimmed ) ) continue ;
341+
342+ string name ;
343+ string type = "any" ;
344+ string ? defaultValue = null ;
345+
346+ // Check for default value: param := value or param = value
347+ var eqIndex = trimmed . IndexOf ( '=' ) ;
348+ string beforeDefault = trimmed ;
349+ if ( eqIndex >= 0 )
350+ {
351+ // Handle := (typed default) and = (untyped default)
352+ if ( eqIndex > 0 && trimmed [ eqIndex - 1 ] == ':' )
353+ {
354+ beforeDefault = trimmed . Substring ( 0 , eqIndex - 1 ) . Trim ( ) ;
355+ }
356+ else
357+ {
358+ beforeDefault = trimmed . Substring ( 0 , eqIndex ) . Trim ( ) ;
359+ }
360+ defaultValue = trimmed . Substring ( eqIndex + 1 ) . Trim ( ) ;
361+ }
362+
363+ // Check for type annotation: param: Type
364+ var colonIndex = beforeDefault . IndexOf ( ':' ) ;
365+ if ( colonIndex >= 0 )
366+ {
367+ name = beforeDefault . Substring ( 0 , colonIndex ) . Trim ( ) ;
368+ var gdType = beforeDefault . Substring ( colonIndex + 1 ) . Trim ( ) ;
369+ type = MapGDScriptTypeToYarn ( gdType ) ;
370+ }
371+ else
372+ {
373+ name = beforeDefault ;
374+ }
375+
376+ var param = new Dictionary < string , object ? > { [ "name" ] = name , [ "type" ] = type } ;
377+ if ( defaultValue != null )
378+ {
379+ param [ "defaultValue" ] = defaultValue ;
380+ }
381+ result . Add ( param ) ;
382+ }
383+
384+ return result ;
385+ }
386+
387+ private static string MapGDScriptTypeToYarn ( string gdType )
388+ {
389+ return gdType . ToLowerInvariant ( ) switch
390+ {
391+ "string" or "stringname" => "string" ,
392+ "int" or "float" => "number" ,
393+ "bool" => "bool" ,
394+ _ => "any" ,
395+ } ;
396+ }
207397 }
208398
209399 public interface ILogger
0 commit comments