@@ -1435,7 +1435,7 @@ public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx)
14351435 final String contextFieldId = getFieldId (ctx .fieldContext ());
14361436 this .efxContext .declareContextVariable (variable .name ,
14371437 new FieldContext (contextFieldId , this .symbols .getAbsolutePathOfField (contextFieldId ),
1438- this . symbols . getRelativePathOfField ( contextFieldId , this . efxContext . symbol ()) ));
1438+ path ));
14391439 } else if (ctx .nodeContext () != null ) {
14401440 final String contextNodeId =
14411441 getNodeId (ctx .nodeContext ());
@@ -1793,6 +1793,20 @@ public void exitAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) {
17931793 }
17941794 }
17951795
1796+ @ Override
1797+ public void exitFieldContext (FieldContextContext ctx ) {
1798+ if (ctx .indexer () != null ) {
1799+ NumericExpression index = this .stack .pop (NumericExpression .class );
1800+ final TypedExpression top = this .stack .peekType ();
1801+ if (!(top instanceof PathExpression )) {
1802+ throw TypeMismatchException .cannotConvert (PathExpression .class , top .getClass ());
1803+ }
1804+ PathExpression fieldPath = (PathExpression ) this .stack .pop (top .getClass ());
1805+ this .stack .push (this .script .composeIndexer (fieldPath .asSequence (), index ,
1806+ ScalarPath .fromEfxDataType .get (EfxTypeLattice .toPrimitive (fieldPath .getDataType ()))));
1807+ }
1808+ }
1809+
17961810 @ Override
17971811 public void enterAbsoluteNodeReference (AbsoluteNodeReferenceContext ctx ) {
17981812 if (ctx .Slash () != null ) {
@@ -1956,16 +1970,16 @@ private void resolveAndPushFieldReference(ParserRuleContext ctx, PathExpression
19561970 this .stack .push (result );
19571971 break ;
19581972 case RESOLVE_SCALAR :
1959- if (this .isFieldRepeatableFromCurrentContext (fieldId )) {
1960- throw TypeMismatchException . fieldMayRepeat (ctx , fieldId , this . efxContext . symbol () );
1973+ if (this .fieldMayReturnMultipleValues (fieldId )) {
1974+ throw this . fieldMayReturnMultipleValuesException (ctx , fieldId );
19611975 }
19621976 this .stack .push (result );
19631977 break ;
19641978 case RESOLVE_SEQUENCE :
19651979 this .stack .push (result .asSequence ());
19661980 break ;
19671981 case RESOLVE_EITHER :
1968- if (this .isFieldRepeatableFromCurrentContext (fieldId )) {
1982+ if (this .fieldMayReturnMultipleValues (fieldId )) {
19691983 this .stack .push (result .asSequence ());
19701984 } else {
19711985 this .stack .push (result );
@@ -1975,11 +1989,13 @@ private void resolveAndPushFieldReference(ParserRuleContext ctx, PathExpression
19751989 }
19761990
19771991 /**
1978- * Returns true if a field is repeatable from the current EFX context.
1992+ * Returns true if a field may return multiple values from the current EFX context.
1993+ * This includes fields that are repeatable (from SDK metadata or node hierarchy)
1994+ * and multilingual fields (which have multiple XML elements, one per language).
19791995 * A field is not considered repeatable if it IS the current context (e.g., inside a WITH block
19801996 * on this field, we're referencing the current element being iterated, not the whole sequence).
19811997 */
1982- private boolean isFieldRepeatableFromCurrentContext (String fieldId ) {
1998+ private boolean fieldMayReturnMultipleValues (String fieldId ) {
19831999 if (!this .efxContext .isEmpty ()
19842000 && this .efxContext .peek () != null
19852001 && this .efxContext .peek ().isFieldContext ()
@@ -1989,6 +2005,18 @@ private boolean isFieldRepeatableFromCurrentContext(String fieldId) {
19892005 return this .symbols .isFieldRepeatableFromContext (fieldId , this .getContextNodeId ());
19902006 }
19912007
2008+ /**
2009+ * Creates the appropriate exception for a field that may return multiple values but is used
2010+ * as a scalar. Returns {@link TypeMismatchException#fieldIsMultilingual} for multilingual fields,
2011+ * or {@link TypeMismatchException#fieldMayRepeat} for structurally repeatable fields.
2012+ */
2013+ private TypeMismatchException fieldMayReturnMultipleValuesException (ParserRuleContext ctx , String fieldId ) {
2014+ if (FieldTypes .TEXT_MULTILINGUAL .getName ().equals (this .symbols .getTypeOfField (fieldId ))) {
2015+ return TypeMismatchException .fieldIsMultilingual (ctx , fieldId );
2016+ }
2017+ return TypeMismatchException .fieldMayRepeat (ctx , fieldId , this .efxContext .symbol ());
2018+ }
2019+
19922020 /**
19932021 * Gets the node ID of the current context for use with
19942022 * {@link SymbolResolver#isFieldRepeatableFromContext}.
@@ -2018,12 +2046,11 @@ private String getContextNodeId() {
20182046 */
20192047 @ Override
20202048 public void exitContextFieldSpecifier (ContextFieldSpecifierContext ctx ) {
2021- this .stack .pop (PathExpression .class ); // Discard the PathExpression placed in the stack for
2022- // the context field.
2049+ final PathExpression contextFieldPath = this .stack .pop (PathExpression .class );
20232050 final String contextFieldId = getFieldId (ctx .fieldContext ());
20242051 this .efxContext
20252052 .push (new FieldContext (contextFieldId , this .symbols .getAbsolutePathOfField (contextFieldId ),
2026- this . symbols . getRelativePathOfField ( contextFieldId , this . efxContext . symbol ()) ));
2053+ contextFieldPath ));
20272054 }
20282055
20292056
@@ -2518,10 +2545,6 @@ public void exitEndsWithFunction(EndsWithFunctionContext ctx) {
25182545 @ Override
25192546 public void exitFieldWasWithheldProperty (FieldWasWithheldPropertyContext ctx ) {
25202547 final String fieldId = getFieldId (ctx .fieldMention ());
2521- if (this .isFieldRepeatableFromCurrentContext (fieldId )) {
2522- throw TypeMismatchException .fieldMayRepeat (ctx , fieldId , this .efxContext .symbol ());
2523- }
2524-
25252548 final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
25262549 if (privacyCode == null || privacyCode .isEmpty ()) {
25272550 throw InvalidUsageException .fieldNotWithholdable (ctx , fieldId );
@@ -2533,10 +2556,6 @@ public void exitFieldWasWithheldProperty(FieldWasWithheldPropertyContext ctx) {
25332556 @ Override
25342557 public void exitFieldIsWithheldProperty (FieldIsWithheldPropertyContext ctx ) {
25352558 final String fieldId = getFieldId (ctx .fieldMention ());
2536- if (this .isFieldRepeatableFromCurrentContext (fieldId )) {
2537- throw TypeMismatchException .fieldMayRepeat (ctx , fieldId , this .efxContext .symbol ());
2538- }
2539-
25402559 final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
25412560 if (privacyCode == null || privacyCode .isEmpty ()) {
25422561 throw InvalidUsageException .fieldNotWithholdable (ctx , fieldId );
@@ -2558,10 +2577,6 @@ public void exitFieldIsWithholdableProperty(FieldIsWithholdablePropertyContext c
25582577 @ Override
25592578 public void exitFieldIsDisclosedProperty (FieldIsDisclosedPropertyContext ctx ) {
25602579 final String fieldId = getFieldId (ctx .fieldMention ());
2561- if (this .isFieldRepeatableFromCurrentContext (fieldId )) {
2562- throw TypeMismatchException .fieldMayRepeat (ctx , fieldId , this .efxContext .symbol ());
2563- }
2564-
25652580 final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
25662581 if (privacyCode == null || privacyCode .isEmpty ()) {
25672582 throw InvalidUsageException .fieldNotWithholdable (ctx , fieldId );
@@ -2572,16 +2587,12 @@ public void exitFieldIsDisclosedProperty(FieldIsDisclosedPropertyContext ctx) {
25722587 this .script .composeLogicalAnd (
25732588 this .composeWasWithheldCondition (fieldId , privacyCode ),
25742589 this .script .composeLogicalNot (this .composeStillWithheldCondition (fieldId ))),
2575- this .script .composeLogicalNot (this .composeIsMaskedCondition (ctx , fieldId ))));
2590+ this .script .composeLogicalNot (this .composeIsMaskedCondition (fieldId ))));
25762591 }
25772592
25782593 @ Override
25792594 public void exitFieldIsMaskedProperty (FieldIsMaskedPropertyContext ctx ) {
25802595 final String fieldId = getFieldId (ctx .fieldMention ());
2581- if (this .isFieldRepeatableFromCurrentContext (fieldId )) {
2582- throw TypeMismatchException .fieldMayRepeat (ctx , fieldId , this .efxContext .symbol ());
2583- }
2584-
25852596 final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
25862597 if (privacyCode == null || privacyCode .isEmpty ()) {
25872598 throw InvalidUsageException .fieldNotWithholdable (ctx , fieldId );
@@ -2590,7 +2601,7 @@ public void exitFieldIsMaskedProperty(FieldIsMaskedPropertyContext ctx) {
25902601 // "isMasked" = was withheld AND field value equals the privacy mask
25912602 this .stack .push (this .script .composeLogicalAnd (
25922603 this .composeWasWithheldCondition (fieldId , privacyCode ),
2593- this .composeIsMaskedCondition (ctx , fieldId )));
2604+ this .composeIsMaskedCondition (fieldId )));
25942605 }
25952606
25962607 @ Override
@@ -2604,25 +2615,15 @@ public void exitFieldPrivacyCodeProperty(FieldPrivacyCodePropertyContext ctx) {
26042615 }
26052616
26062617 @ Override
2607- public void exitFieldRawValueProperty ( FieldRawValuePropertyContext ctx ) {
2618+ public void exitRawValueReference ( RawValueReferenceContext ctx ) {
26082619 final TypedExpression top = this .stack .peekType ();
26092620 if (!(top instanceof PathExpression )) {
26102621 throw TypeMismatchException .cannotConvert (PathExpression .class , top .getClass ());
26112622 }
2612- if (top instanceof SequenceExpression ) {
2613- final String fieldId = getFieldId (ctx .fieldReferenceWithVariableContextOverride ()
2614- .reference .reference .reference );
2615- throw TypeMismatchException .fieldMayRepeat (ctx , fieldId , this .efxContext .symbol ());
2616- }
2617- final PathExpression fieldPath = (PathExpression ) this .stack .pop (top .getClass ());
2618- this .stack .push (this .script .composeFieldRawValueReference (fieldPath ));
2619- }
2620-
2621- @ Override
2622- public void exitFieldRawValuePropertySequence (FieldRawValuePropertySequenceContext ctx ) {
2623- final TypedExpression top = this .stack .peekType ();
2624- if (!(top instanceof PathExpression )) {
2625- throw TypeMismatchException .cannotConvert (PathExpression .class , top .getClass ());
2623+ if (top instanceof SequenceExpression
2624+ && this .currentCardinalityResolutionContext () == CardinalityResolutionContext .RESOLVE_SCALAR ) {
2625+ final String fieldId = getFieldId (ctx .fieldContext ());
2626+ throw this .fieldMayReturnMultipleValuesException (ctx , fieldId );
26262627 }
26272628 final PathExpression fieldPath = (PathExpression ) this .stack .pop (top .getClass ());
26282629 this .stack .push (this .script .composeFieldRawValueReference (fieldPath ));
@@ -2660,14 +2661,10 @@ private BooleanExpression composeStillWithheldCondition(String fieldId) {
26602661 BooleanExpression .class );
26612662 }
26622663
2663- private BooleanExpression composeIsMaskedCondition (ParserRuleContext ctx , String fieldId ) {
2664+ private BooleanExpression composeIsMaskedCondition (String fieldId ) {
26642665 final String maskingValue = this .symbols .getPrivacyMask (fieldId );
26652666 final PathExpression fieldValue = this .script .composeFieldValueReference (this .symbols .getRelativePathOfField (fieldId , this .efxContext .symbol ()));
26662667
2667- if (!(fieldValue instanceof ScalarExpression )) {
2668- throw TypeMismatchException .fieldMayRepeat (ctx , fieldId , this .efxContext .symbol ());
2669- }
2670-
26712668 return this .script .composeComparisonOperation (
26722669 TypedExpression .from (fieldValue , ScalarExpression .class ),
26732670 "==" ,
@@ -3165,6 +3162,16 @@ public void exitPreferredLanguageTextFunction(PreferredLanguageTextFunctionConte
31653162 throw InvalidUsageException .templateOnlyFunction (ctx , "preferred-language-text" );
31663163 }
31673164
3165+ @ Override
3166+ public void exitFieldPreferredLanguageProperty (FieldPreferredLanguagePropertyContext ctx ) {
3167+ throw InvalidUsageException .templateOnlyFunction (ctx , ":preferredLanguage" );
3168+ }
3169+
3170+ @ Override
3171+ public void exitFieldPreferredLanguageTextProperty (FieldPreferredLanguageTextPropertyContext ctx ) {
3172+ throw InvalidUsageException .templateOnlyFunction (ctx , ":preferredLanguageText" );
3173+ }
3174+
31683175 @ Override
31693176 public void exitDictionaryLookup (DictionaryLookupContext ctx ) {
31703177 if (this .currentCardinalityResolutionContext () == CardinalityResolutionContext .RESOLVE_SCALAR ) {
0 commit comments