1+ using System . Globalization ;
2+
3+ namespace Darkages . CommandSystem ;
4+
5+ internal static class ArgumentParser
6+ {
7+ public static bool TryParse (
8+ CommandDefinition command ,
9+ ReadOnlySpan < char > input ,
10+ Token [ ] tokens ,
11+ int argsStart ,
12+ int argsCount ,
13+ ParsedArgs parsed ,
14+ Action < object ? , string > onError ,
15+ object ? ctx )
16+ {
17+ var specs = command . ArgSpecs ;
18+ var values = parsed . Values ;
19+
20+ // Too many args?
21+ if ( argsCount > specs . Length )
22+ {
23+ onError ( ctx , $ "Too many arguments. Usage: { Usage ( command ) } ") ;
24+ return false ;
25+ }
26+
27+ for ( int i = 0 ; i < specs . Length ; i ++ )
28+ {
29+ var spec = specs [ i ] ;
30+
31+ if ( i >= argsCount )
32+ {
33+ if ( spec . Optional )
34+ {
35+ if ( spec . Default is not null )
36+ {
37+ if ( ! TryCoerce ( spec , spec . Default . AsSpan ( ) , out values [ i ] , onError , ctx ) )
38+ return false ;
39+ }
40+ continue ;
41+ }
42+
43+ onError ( ctx , $ "Missing required argument '{ spec . Name } '. Usage: { Usage ( command ) } ") ;
44+ return false ;
45+ }
46+
47+ var raw = tokens [ argsStart + i ] . Slice ( input ) ;
48+ raw = Tokenizer . Unquote ( raw ) ;
49+
50+ if ( spec . Validator is not null && ! spec . Validator ( raw ) )
51+ {
52+ onError ( ctx , $ "Argument '{ spec . Name } ' is invalid.") ;
53+ return false ;
54+ }
55+
56+ if ( ! TryCoerce ( spec , raw , out values [ i ] , onError , ctx ) )
57+ return false ;
58+ }
59+
60+ return true ;
61+ }
62+
63+ private static bool TryCoerce (
64+ ArgSpec spec ,
65+ ReadOnlySpan < char > raw ,
66+ out ArgValue value ,
67+ Action < object ? , string > onError ,
68+ object ? ctx )
69+ {
70+ switch ( spec . Kind )
71+ {
72+ case ArgKind . String :
73+ value = ArgValue . FromString ( raw . ToString ( ) ) ;
74+ return true ;
75+
76+ case ArgKind . Int32 :
77+ if ( int . TryParse ( raw , NumberStyles . Integer , CultureInfo . InvariantCulture , out var i32 ) )
78+ {
79+ value = ArgValue . FromInt32 ( i32 ) ;
80+ return true ;
81+ }
82+ onError ( ctx , $ "Argument '{ spec . Name } ' must be an integer.") ;
83+ value = default ;
84+ return false ;
85+
86+ case ArgKind . Int64 :
87+ if ( long . TryParse ( raw , NumberStyles . Integer , CultureInfo . InvariantCulture , out var i64 ) )
88+ {
89+ value = ArgValue . FromInt64 ( i64 ) ;
90+ return true ;
91+ }
92+ onError ( ctx , $ "Argument '{ spec . Name } ' must be a 64-bit integer.") ;
93+ value = default ;
94+ return false ;
95+
96+ case ArgKind . Double :
97+ if ( double . TryParse ( raw , NumberStyles . Float , CultureInfo . InvariantCulture , out var dbl ) )
98+ {
99+ value = ArgValue . FromDouble ( dbl ) ;
100+ return true ;
101+ }
102+ onError ( ctx , $ "Argument '{ spec . Name } ' must be a number.") ;
103+ value = default ;
104+ return false ;
105+
106+ default :
107+ value = ArgValue . FromString ( raw . ToString ( ) ) ;
108+ return true ;
109+ }
110+ }
111+
112+ private static string Usage ( CommandDefinition cmd )
113+ {
114+ var alias = cmd . Aliases . Length > 0 ? cmd . Aliases [ 0 ] : cmd . Name ;
115+
116+ if ( cmd . ArgSpecs . Length == 0 )
117+ return alias ;
118+
119+ var parts = new string [ cmd . ArgSpecs . Length ] ;
120+ for ( int i = 0 ; i < cmd . ArgSpecs . Length ; i ++ )
121+ {
122+ var a = cmd . ArgSpecs [ i ] ;
123+ parts [ i ] = a . Optional ? $ "[{ a . Name } ]" : $ "<{ a . Name } >";
124+ }
125+
126+ return $ "{ alias } { string . Join ( ' ' , parts ) } ";
127+ }
128+ }
0 commit comments