1+ /// <summary>
2+ /// Parses and evaluates preprocessor expressions, handling known and unknown symbols.
3+ /// </summary>
4+ public class ExpressionParser ( string expression , HashSet < string > definedSymbols , HashSet < string > knownSymbols )
5+ {
6+ int pos ;
7+
8+ public TriState Evaluate ( )
9+ {
10+ pos = 0 ;
11+ return ParseOr ( ) ;
12+ }
13+
14+ public string Simplify ( )
15+ {
16+ pos = 0 ;
17+ return SimplifyOr ( ) ;
18+ }
19+
20+ void SkipWhitespace ( )
21+ {
22+ while ( pos < expression . Length && char . IsWhiteSpace ( expression [ pos ] ) )
23+ {
24+ pos ++ ;
25+ }
26+ }
27+
28+ TriState ParseOr ( )
29+ {
30+ var left = ParseAnd ( ) ;
31+ SkipWhitespace ( ) ;
32+
33+ while ( pos < expression . Length - 1 && expression . Substring ( pos , 2 ) == "||" )
34+ {
35+ pos += 2 ;
36+ var right = ParseAnd ( ) ;
37+ left = OrTriState ( left , right ) ;
38+ }
39+
40+ return left ;
41+ }
42+
43+ TriState ParseAnd ( )
44+ {
45+ var left = ParseUnary ( ) ;
46+ SkipWhitespace ( ) ;
47+
48+ while ( pos < expression . Length - 1 && expression . Substring ( pos , 2 ) == "&&" )
49+ {
50+ pos += 2 ;
51+ var right = ParseUnary ( ) ;
52+ left = AndTriState ( left , right ) ;
53+ }
54+
55+ return left ;
56+ }
57+
58+ TriState ParseUnary ( )
59+ {
60+ SkipWhitespace ( ) ;
61+
62+ if ( pos < expression . Length && expression [ pos ] == '!' )
63+ {
64+ pos ++ ;
65+ var inner = ParseUnary ( ) ;
66+ return NotTriState ( inner ) ;
67+ }
68+
69+ return ParsePrimary ( ) ;
70+ }
71+
72+ TriState ParsePrimary ( )
73+ {
74+ SkipWhitespace ( ) ;
75+
76+ if ( pos < expression . Length && expression [ pos ] == '(' )
77+ {
78+ pos ++ ;
79+ var result = ParseOr ( ) ;
80+ SkipWhitespace ( ) ;
81+ if ( pos < expression . Length && expression [ pos ] == ')' )
82+ {
83+ pos ++ ;
84+ }
85+
86+ return result ;
87+ }
88+
89+ // Parse identifier
90+ var start = pos ;
91+ while ( pos < expression . Length && ( char . IsLetterOrDigit ( expression [ pos ] ) || expression [ pos ] == '_' ) )
92+ {
93+ pos ++ ;
94+ }
95+
96+ var identifier = expression . Substring ( start , pos - start ) ;
97+ SkipWhitespace ( ) ;
98+
99+ if ( string . Equals ( identifier , "true" , StringComparison . OrdinalIgnoreCase ) )
100+ {
101+ return TriState . True ;
102+ }
103+
104+ if ( string . Equals ( identifier , "false" , StringComparison . OrdinalIgnoreCase ) )
105+ {
106+ return TriState . False ;
107+ }
108+
109+ // Check if this is a known symbol
110+ if ( knownSymbols . Contains ( identifier ) )
111+ {
112+ return definedSymbols . Contains ( identifier ) ? TriState . True : TriState . False ;
113+ }
114+
115+ // Unknown symbol
116+ return TriState . Unknown ;
117+ }
118+
119+ // Simplification methods
120+ string SimplifyOr ( )
121+ {
122+ var left = SimplifyAnd ( ) ;
123+ SkipWhitespace ( ) ;
124+ var parts = new List < string > { left } ;
125+
126+ while ( pos < expression . Length - 1 && expression . Substring ( pos , 2 ) == "||" )
127+ {
128+ pos += 2 ;
129+ var right = SimplifyAnd ( ) ;
130+ parts . Add ( right ) ;
131+ }
132+
133+ // Simplify: if any part is "true", result is "true"
134+ // If any part is "false", remove it
135+ var filtered = parts . Where ( p => p != "false" ) . ToList ( ) ;
136+ if ( filtered . Any ( p => p == "true" ) )
137+ {
138+ return "true" ;
139+ }
140+
141+ if ( filtered . Count == 0 )
142+ {
143+ return "false" ;
144+ }
145+
146+ if ( filtered . Count == 1 )
147+ {
148+ return filtered [ 0 ] ;
149+ }
150+
151+ return string . Join ( " || " , filtered ) ;
152+ }
153+
154+ string SimplifyAnd ( )
155+ {
156+ var left = SimplifyUnary ( ) ;
157+ SkipWhitespace ( ) ;
158+ var parts = new List < string > { left } ;
159+
160+ while ( pos < expression . Length - 1 && expression . Substring ( pos , 2 ) == "&&" )
161+ {
162+ pos += 2 ;
163+ var right = SimplifyUnary ( ) ;
164+ parts . Add ( right ) ;
165+ }
166+
167+ // Simplify: if any part is "false", result is "false"
168+ // If any part is "true", remove it
169+ if ( parts . Any ( p => p == "false" ) )
170+ {
171+ return "false" ;
172+ }
173+
174+ var filtered = parts . Where ( p => p != "true" ) . ToList ( ) ;
175+ if ( filtered . Count == 0 )
176+ {
177+ return "true" ;
178+ }
179+
180+ if ( filtered . Count == 1 )
181+ {
182+ return filtered [ 0 ] ;
183+ }
184+
185+ return string . Join ( " && " , filtered ) ;
186+ }
187+
188+ string SimplifyUnary ( )
189+ {
190+ SkipWhitespace ( ) ;
191+
192+ if ( pos < expression . Length && expression [ pos ] == '!' )
193+ {
194+ pos ++ ;
195+ var inner = SimplifyUnary ( ) ;
196+ if ( inner == "true" )
197+ {
198+ return "false" ;
199+ }
200+
201+ if ( inner == "false" )
202+ {
203+ return "true" ;
204+ }
205+
206+ // Check if inner starts with ! and simplify double negation
207+ if ( inner . StartsWith ( '!' ) )
208+ {
209+ return inner [ 1 ..] ;
210+ }
211+
212+ return $ "!{ inner } ";
213+ }
214+
215+ return SimplifyPrimary ( ) ;
216+ }
217+
218+ string SimplifyPrimary ( )
219+ {
220+ SkipWhitespace ( ) ;
221+
222+ if ( pos < expression . Length && expression [ pos ] == '(' )
223+ {
224+ pos ++ ;
225+ var result = SimplifyOr ( ) ;
226+ SkipWhitespace ( ) ;
227+ if ( pos < expression . Length && expression [ pos ] == ')' )
228+ {
229+ pos ++ ;
230+ }
231+
232+ // Only keep parens if needed (contains || and was inside an && context)
233+ if ( result . Contains ( "||" ) && ! result . StartsWith ( '(' ) )
234+ {
235+ return $ "({ result } )";
236+ }
237+
238+ return result ;
239+ }
240+
241+ // Parse identifier
242+ var start = pos ;
243+ while ( pos < expression . Length && ( char . IsLetterOrDigit ( expression [ pos ] ) || expression [ pos ] == '_' ) )
244+ {
245+ pos ++ ;
246+ }
247+
248+ var identifier = expression [ start ..pos ] ;
249+ SkipWhitespace ( ) ;
250+
251+ if ( string . Equals ( identifier , "true" , StringComparison . OrdinalIgnoreCase ) )
252+ {
253+ return "true" ;
254+ }
255+
256+ if ( string . Equals ( identifier , "false" , StringComparison . OrdinalIgnoreCase ) )
257+ {
258+ return "false" ;
259+ }
260+
261+ // Check if this is a known symbol - replace with true/false
262+ if ( knownSymbols . Contains ( identifier ) )
263+ {
264+ return definedSymbols . Contains ( identifier ) ? "true" : "false" ;
265+ }
266+
267+ // Unknown symbol - keep as-is
268+ return identifier ;
269+ }
270+
271+ static TriState AndTriState ( TriState left , TriState right )
272+ {
273+ if ( left == TriState . False || right == TriState . False )
274+ {
275+ return TriState . False ;
276+ }
277+
278+ if ( left == TriState . True && right == TriState . True )
279+ {
280+ return TriState . True ;
281+ }
282+
283+ return TriState . Unknown ;
284+ }
285+
286+ static TriState OrTriState ( TriState left , TriState right )
287+ {
288+ if ( left == TriState . True || right == TriState . True )
289+ {
290+ return TriState . True ;
291+ }
292+
293+ if ( left == TriState . False && right == TriState . False )
294+ {
295+ return TriState . False ;
296+ }
297+
298+ return TriState . Unknown ;
299+ }
300+
301+ static TriState NotTriState ( TriState value ) =>
302+ value switch
303+ {
304+ TriState . True => TriState . False ,
305+ TriState . False => TriState . True ,
306+ _ => TriState . Unknown
307+ } ;
308+ }
0 commit comments