@@ -320,10 +320,37 @@ protected virtual MatchTextExpression ParseTextMatch(string operatorName)
320320 EatText ( operatorName ) ;
321321 EatSingleCharacterToken ( TokenKind . OpenParen ) ;
322322
323+ QueryExpression matchTarget = ParseTextMatchLeftTerm ( ) ;
324+
325+ EatSingleCharacterToken ( TokenKind . Comma ) ;
326+
327+ ConstantValueConverter constantValueConverter = GetConstantValueConverterForType ( typeof ( string ) ) ;
328+ LiteralConstantExpression constant = ParseConstant ( constantValueConverter ) ;
329+
330+ EatSingleCharacterToken ( TokenKind . CloseParen ) ;
331+
332+ var matchKind = Enum . Parse < TextMatchKind > ( operatorName . Pascalize ( ) ) ;
333+ return new MatchTextExpression ( matchTarget , constant , matchKind ) ;
334+ }
335+
336+ private QueryExpression ParseTextMatchLeftTerm ( )
337+ {
338+ if ( TokenStack . TryPeek ( out Token ? nextToken ) && nextToken is { Kind : TokenKind . Text } && IsFunction ( nextToken . Value ! ) )
339+ {
340+ FunctionExpression targetFunction = ParseFunction ( ) ;
341+
342+ if ( targetFunction . ReturnType != typeof ( string ) )
343+ {
344+ throw new QueryParseException ( "Function that returns type 'String' expected." , nextToken . Position ) ;
345+ }
346+
347+ return targetFunction ;
348+ }
349+
323350 int chainStartPosition = GetNextTokenPositionOrEnd ( ) ;
324351
325- ResourceFieldChainExpression targetAttributeChain =
326- ParseFieldChain ( BuiltInPatterns . ToOneChainEndingInAttribute , FieldChainPatternMatchOptions . None , ResourceTypeInScope , null ) ;
352+ ResourceFieldChainExpression targetAttributeChain = ParseFieldChain ( BuiltInPatterns . ToOneChainEndingInAttribute , FieldChainPatternMatchOptions . None ,
353+ ResourceTypeInScope , null ) ;
327354
328355 var targetAttribute = ( AttrAttribute ) targetAttributeChain . Fields [ ^ 1 ] ;
329356
@@ -333,32 +360,21 @@ protected virtual MatchTextExpression ParseTextMatch(string operatorName)
333360 throw new QueryParseException ( "Attribute of type 'String' expected." , position ) ;
334361 }
335362
336- EatSingleCharacterToken ( TokenKind . Comma ) ;
337-
338- ConstantValueConverter constantValueConverter = GetConstantValueConverterForAttribute ( targetAttribute ) ;
339- LiteralConstantExpression constant = ParseConstant ( constantValueConverter ) ;
340-
341- EatSingleCharacterToken ( TokenKind . CloseParen ) ;
342-
343- var matchKind = Enum . Parse < TextMatchKind > ( operatorName . Pascalize ( ) ) ;
344- return new MatchTextExpression ( targetAttributeChain , constant , matchKind ) ;
363+ return targetAttributeChain ;
345364 }
346365
347366 protected virtual AnyExpression ParseAny ( )
348367 {
349368 EatText ( Keywords . Any ) ;
350369 EatSingleCharacterToken ( TokenKind . OpenParen ) ;
351370
352- ResourceFieldChainExpression targetAttributeChain =
353- ParseFieldChain ( BuiltInPatterns . ToOneChainEndingInAttribute , FieldChainPatternMatchOptions . None , ResourceTypeInScope , null ) ;
354-
355- var targetAttribute = ( AttrAttribute ) targetAttributeChain . Fields [ ^ 1 ] ;
371+ ( QueryExpression matchTarget , Func < ConstantValueConverter > constantValueConverterFactory ) = ParseAnyLeftTerm ( ) ;
356372
357373 EatSingleCharacterToken ( TokenKind . Comma ) ;
358374
359375 ImmutableHashSet < LiteralConstantExpression > . Builder constantsBuilder = ImmutableHashSet . CreateBuilder < LiteralConstantExpression > ( ) ;
360376
361- ConstantValueConverter constantValueConverter = GetConstantValueConverterForAttribute ( targetAttribute ) ;
377+ ConstantValueConverter constantValueConverter = constantValueConverterFactory ( ) ;
362378 LiteralConstantExpression constant = ParseConstant ( constantValueConverter ) ;
363379 constantsBuilder . Add ( constant ) ;
364380
@@ -374,7 +390,26 @@ protected virtual AnyExpression ParseAny()
374390
375391 IImmutableSet < LiteralConstantExpression > constantSet = constantsBuilder . ToImmutable ( ) ;
376392
377- return new AnyExpression ( targetAttributeChain , constantSet ) ;
393+ return new AnyExpression ( matchTarget , constantSet ) ;
394+ }
395+
396+ private ( QueryExpression matchTarget , Func < ConstantValueConverter > constantValueConverterFactory ) ParseAnyLeftTerm ( )
397+ {
398+ if ( TokenStack . TryPeek ( out Token ? nextToken ) && nextToken is { Kind : TokenKind . Text } && IsFunction ( nextToken . Value ! ) )
399+ {
400+ FunctionExpression targetFunction = ParseFunction ( ) ;
401+
402+ Func < ConstantValueConverter > functionConverterFactory = ( ) => GetConstantValueConverterForType ( targetFunction . ReturnType ) ;
403+ return ( targetFunction , functionConverterFactory ) ;
404+ }
405+
406+ ResourceFieldChainExpression targetAttributeChain =
407+ ParseFieldChain ( BuiltInPatterns . ToOneChainEndingInAttribute , FieldChainPatternMatchOptions . None , ResourceTypeInScope , null ) ;
408+
409+ var targetAttribute = ( AttrAttribute ) targetAttributeChain . Fields [ ^ 1 ] ;
410+
411+ Func < ConstantValueConverter > attributeConverterFactory = ( ) => GetConstantValueConverterForAttribute ( targetAttribute ) ;
412+ return ( targetAttributeChain , attributeConverterFactory ) ;
378413 }
379414
380415 protected virtual HasExpression ParseHas ( )
0 commit comments