Project

General

Profile

Bug #4754

DYNAMIC-FUNCTION parsing fails when first parameter is not an expression

Added by Roger Borrello almost 4 years ago. Updated about 3 years ago.

Status:
Closed
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:
version:

History

#1 Updated by Roger Borrello almost 4 years ago

The second DYNAMIC-FUNCTION call fails to parse:

DEFINE VARIABLE phTarget AS HANDLE     NO-UNDO.
DEFINE VARIABLE hFunc    AS HANDLE     NO-UNDO.

hFunc:BUFFER-VALUE = DYNAMIC-FUNCTION('hFunc-NAME' IN phTarget) NO-ERROR.
hFunc:BUFFER-VALUE = DYNAMIC-FUNCTION(hFunc:NAME IN phTarget) NO-ERROR.

./uast/handle/handle_attr_dyn_func.p:5:53: unexpected token: phTarget
    at com.goldencode.p2j.uast.ProgressParser.widget_qualifier(ProgressParser.java:58653)
    at com.goldencode.p2j.uast.ProgressParser.chained_object_members(ProgressParser.java:22234)
    at com.goldencode.p2j.uast.ProgressParser.un_type(ProgressParser.java:57875)
    at com.goldencode.p2j.uast.ProgressParser.prod_expr(ProgressParser.java:57742)
    at com.goldencode.p2j.uast.ProgressParser.sum_expr(ProgressParser.java:41663)
    at com.goldencode.p2j.uast.ProgressParser.compare_expr(ProgressParser.java:57328)
    at com.goldencode.p2j.uast.ProgressParser.log_not_expr(ProgressParser.java:57180)
    at com.goldencode.p2j.uast.ProgressParser.bitwise_xor_expr(ProgressParser.java:57111)
    at com.goldencode.p2j.uast.ProgressParser.log_and_expr(ProgressParser.java:57050)
    at com.goldencode.p2j.uast.ProgressParser.expr(ProgressParser.java:12178)
    at com.goldencode.p2j.uast.ProgressParser.func_call_parm(ProgressParser.java:61588)
    at com.goldencode.p2j.uast.ProgressParser.dynamic_function_func(ProgressParser.java:59597)
    at com.goldencode.p2j.uast.ProgressParser.primary_expr(ProgressParser.java:58168)
    at com.goldencode.p2j.uast.ProgressParser.chained_object_members(ProgressParser.java:22170)
    at com.goldencode.p2j.uast.ProgressParser.un_type(ProgressParser.java:57875)
    at com.goldencode.p2j.uast.ProgressParser.prod_expr(ProgressParser.java:57742)
    at com.goldencode.p2j.uast.ProgressParser.sum_expr(ProgressParser.java:41663)
    at com.goldencode.p2j.uast.ProgressParser.compare_expr(ProgressParser.java:57328)
    at com.goldencode.p2j.uast.ProgressParser.log_not_expr(ProgressParser.java:57180)
    at com.goldencode.p2j.uast.ProgressParser.bitwise_xor_expr(ProgressParser.java:57111)
    at com.goldencode.p2j.uast.ProgressParser.log_and_expr(ProgressParser.java:57050)
    at com.goldencode.p2j.uast.ProgressParser.expr(ProgressParser.java:12178)
    at com.goldencode.p2j.uast.ProgressParser.un_type(ProgressParser.java:57898)
    at com.goldencode.p2j.uast.ProgressParser.prod_expr(ProgressParser.java:57742)
    at com.goldencode.p2j.uast.ProgressParser.sum_expr(ProgressParser.java:41663)
    at com.goldencode.p2j.uast.ProgressParser.compare_expr(ProgressParser.java:57328)
    at com.goldencode.p2j.uast.ProgressParser.log_not_expr(ProgressParser.java:57180)
    at com.goldencode.p2j.uast.ProgressParser.bitwise_xor_expr(ProgressParser.java:57111)
    at com.goldencode.p2j.uast.ProgressParser.log_and_expr(ProgressParser.java:57050)
    at com.goldencode.p2j.uast.ProgressParser.expr(ProgressParser.java:12178)
    at com.goldencode.p2j.uast.ProgressParser.assignment(ProgressParser.java:8691)
    at com.goldencode.p2j.uast.ProgressParser.single_block(ProgressParser.java:7334)
    at com.goldencode.p2j.uast.ProgressParser.block(ProgressParser.java:7017)
    at com.goldencode.p2j.uast.ProgressParser.external_proc(ProgressParser.java:6944)
    at com.goldencode.p2j.uast.AstGenerator.parse(AstGenerator.java:1571)
    at com.goldencode.p2j.uast.AstGenerator.processFile(AstGenerator.java:996)
    at com.goldencode.p2j.uast.ScanDriver.lambda$scan$0(ScanDriver.java:375)
    at com.goldencode.p2j.uast.ScanDriver.scan(ScanDriver.java:410)
    at com.goldencode.p2j.uast.ScanDriver.scan(ScanDriver.java:248)
    at com.goldencode.p2j.convert.TransformDriver.runScanDriver(TransformDriver.java:369)
    at com.goldencode.p2j.convert.TransformDriver.front(TransformDriver.java:234)
    at com.goldencode.p2j.convert.TransformDriver.executeJob(TransformDriver.java:944)
    at com.goldencode.p2j.convert.ConversionDriver.main(ConversionDriver.java:1025)
Failure in file '/home/rfb/projects/VirtualBox-VMs/shared/projects/testcases/uast/handle/handle_attr_dyn_func.p':
com.goldencode.ast.AstException: Error processing ./uast/handle/handle_attr_dyn_func.p
    at com.goldencode.p2j.uast.AstGenerator.processFile(AstGenerator.java:1008)
    at com.goldencode.p2j.uast.ScanDriver.lambda$scan$0(ScanDriver.java:375)
    at com.goldencode.p2j.uast.ScanDriver.scan(ScanDriver.java:410)
    at com.goldencode.p2j.uast.ScanDriver.scan(ScanDriver.java:248)
    at com.goldencode.p2j.convert.TransformDriver.runScanDriver(TransformDriver.java:369)
    at com.goldencode.p2j.convert.TransformDriver.front(TransformDriver.java:234)
    at com.goldencode.p2j.convert.TransformDriver.executeJob(TransformDriver.java:944)
    at com.goldencode.p2j.convert.ConversionDriver.main(ConversionDriver.java:1025)
Caused by: java.lang.RuntimeException: Parser encountered 1 errors
    at com.goldencode.p2j.uast.AstGenerator.parse(AstGenerator.java:1640)
    at com.goldencode.p2j.uast.AstGenerator.processFile(AstGenerator.java:996)
    ... 7 more

The fact that the second instance has a handle attribute, instead of an expressions seems to mess up func_call_param:

func_call_parm [boolean builtin]
   {
      boolean isInputFunc = (LA(1) == KW_INPUT &&
                             LA(2) == KW_FRAME &&
                             resolveLvalueCoreType(4, false) != -1);
      boolean classLookup = sym.isClassLookup();
      sym.setClassLookup(false);
   }
   :
      // this should be merged with the parameter rule (this one doesn't create a root
      // node and here we match KW_INPUT as a builtin based on the flag passed in) 
      (
         options { generateAmbigWarnings = false; }
         :
           { !builtin && !isInputFunc }?
           KW_INPUT
         | KW_OUTPUT
         | KW_IN_OUT
      )?
      expr
      (KW_APPEND | KW_BY_VALUE | KW_BY_REF | KW_BIND)*
      {
         sym.setClassLookup(classLookup);
      }
   ;

In debugging, the call to func_call_param for the first DYNAMIC-FUNCTION determines it is an expression. But on the second, does not.

#3 Updated by Roger Borrello almost 4 years ago

Added testcase uast/handle/handle_attr_dyn_func.p

#4 Updated by Greg Shah almost 4 years ago

Roger Borrello wrote:

It is like the IN is never parsed.

No, the IN is seen but the following token is unrecognized.

widget_qualifier
   :
      KW_IN^ 
      (
         options { generateAmbigWarnings = false; }
         :
           frame_reference[false]                       
         | menu_reference

           // used to be browse_reference but that rule was removed and this
           // was consolidated as a recursive call to lvalue
         | { LA(1) == KW_BROWSE }? lvalue
      )
   ;

There is no provision to match a "bare" expression of type handle. It only will match FRAME framename or MENU menuname or SUBMENU submenuname or BROWSE browsename. The lvalue is only used is KW_BROWSE is the next token after KW_IN. We could try removing the contraint (officially this is a "semantic predicate") of { LA(1) == KW_BROWSE }?. This would allow any kind of handle expression (and much more). But this may be wrong if any kind of handle sub-expression can be written here. In that case you could leave the semantic predicate and instead add an alternative for the handle rule after it.

handle   
   :
      h:chained_object_members 
      {
         #h.getType() == VAR_HANDLE        ||
         #h.getType() == FIELD_HANDLE      ||
         #h.getType() == FUNC_HANDLE       ||
         #h.getType() == COLON             ||
         #h.getType() == OBJECT_INVOCATION ||
         #h.getType() == OO_METH_HANDLE    ||
         #h.getType() == SYS_HANDLE        ||
         #h.getType() == VAR_COM_HANDLE    ||
         #h.getType() == FIELD_COM_HANDLE  ||
         #h.getType() == FUNC_COM_HANDLE   ||
         #h.getType() == OO_METH_COM_HANDLE
      }?
   ;

#5 Updated by Greg Shah almost 4 years ago

From Roger:

Why would widget_qualifier be parsing, instead of dynamic_function_func

dynamic_function_func   
   :
      d:KW_DYN_FUNC^
      {
         #d.setType(FUNC_POLY);
      } 
      lparens
      (
         func_call_parm[false] (in_procedure_clause)?
         (comma func_call_parm[false])*
      )
      rparens
      {
         // save the original token type that rewriting erased
         ##.putAnnotation("oldtype", new Long(KW_DYN_FUNC));

         // write our function-specific annotations
         sym.annotateFunction(#d.getText(), ##, false);            
      }
   ;


and in_procedure_clause?
/**   
 * Matches the IN keyword and the required following expression
 * that resolves to the procedure handle construct in which the internal
 * procedure is to be run.
 * <p>
 * Used by {@link #run_stmt}, {@link #dynamic_function_func}, {@link #event_proc_clause}, 
 * {@link #subscribe_stmt} and {@link #unsubscribe_stmt}, the subtree is rooted by the keyword 
 * (which is the reason for using a separate rule for something so simple).
*/
in_procedure_clause
:
KW_IN^ expr
;

#6 Updated by Greg Shah almost 4 years ago

Because the 2nd case is not supposed to match an in_procedure_clause. The 2nd usage is an attribute that is being qualified by a widget handle. Did you try my original suggestion for changing widget_qualifier?

#7 Updated by Roger Borrello almost 4 years ago

Greg Shah wrote:

Because the 2nd case is not supposed to match an in_procedure_clause. The 2nd usage is an attribute that is being qualified by a widget handle. Did you try my original suggestion for changing widget_qualifier?

I tried both ways:

widget_qualifier
   :
      KW_IN^ 
      (
         options { generateAmbigWarnings = false; }
         :
           frame_reference[false]                       
         | menu_reference

           // used to be browse_reference but that rule was removed and this
           // was consolidated as a recursive call to lvalue
         | lvalue
      )
   ;

and
widget_qualifier
   :
      KW_IN^ 
      (
         options { generateAmbigWarnings = false; }
         :
           frame_reference[false]                       
         | menu_reference

           // used to be browse_reference but that rule was removed and this
           // was consolidated as a recursive call to lvalue
         | { LA(1) == KW_BROWSE }? lvalue
         | handle
      )
   ;

Both worked and generated equivalent ast files.

#8 Updated by Constantin Asofiei almost 4 years ago

What's the generated Java code for this call? The dynamic_function_func rule checks for func_call_parm[false] (in_procedure_clause)?, which has:

in_procedure_clause   
   :
      KW_IN^ expr
   ;

I don't think func_call_parm should resolve the IN clause via the widget_qualifier. widget_qualifier needs to check expressions like ... IN <frame-reference> (or other kind of reference, not handle). There is no syntax AFAIK which can have a widget qualifier with a handle on the right-side of IN.

#9 Updated by Roger Borrello almost 4 years ago

The 4GL in the ADM2 panel.p is:

  DO iFunc = 1 TO hBuffer:NUM-FIELDS.
    hFunc = hBuffer:BUFFER-FIELD(iFunc).
    ghTargetProcedure = TARGET-PROCEDURE. 
    hFunc:BUFFER-VALUE = DYNAMIC-FUNCTION(hFunc:NAME IN phTarget) NO-ERROR.
  END.

and the converted code is:
         doTo("loopLabel10", toClause2, new Block((Body) () -> 
         {
            hFunc.assign(hBuffer.unwrapBuffer().bufferField(iFunc));
            ghTargetProcedure.assign(targetProcedure());
            silent(() -> hFunc.unwrapBufferField().changeValue(ControlFlowOps.invokeDynamicFunctionIn(hFunc.unwrap().name(), phTarget_1)));
         }));

I was a little confused that Greg had pointed out the change was in widget_qualifier, but that certainly worked. The large ChUI regression test passed with the 2nd version of his recommended changes, and the first is running now.

#10 Updated by Constantin Asofiei almost 4 years ago

hFunc:NAME is a character, which in the DYNAMIC-FUNCTION's syntax for the first argument (function-name[ IN proc-handle]) is the target function name. IN phTarget is the persistent program handle.

My opinion is that the parser should no go down the widget_qualifier path - if we change widget_qualifier to accept IN handle, then we are adding to the 4GL syntax...

#11 Updated by Greg Shah almost 4 years ago

If you are sure that this is procedure handle syntax instead of widget qualifier syntax, then we may need a different approach. However, the solution is messy.

The widget:attribute IN frame_or_menu_or_browse is valid 4GL syntax. The parser predicts this match in preference to the in_procedure_clause because func_call_parm uses expr and expr will "see" the KW_IN before it exits back into dynamic_function_func where it could match to in_procedure_clause. More specifically, in chained_object_members, we match the COLON followed by attribute_or_method and this is where we optionally match widget_qualifier.

To avoid this, we must know that we are processing the first parameter of the dynamic_function_func somewhere further up the stack.

1. Add a member variable inDynFunc to the parser class.

2. Modify dynamic_function_func like this:

      d:KW_DYN_FUNC^
      {
         #d.setType(FUNC_POLY);
         inDynFunc = true;
      } 
      lparens
      (
         func_call_parm[false] { inDynFunc = false; } (in_procedure_clause)?
         (comma func_call_parm[false])*
      )
      rparens

3. Modify chained_object_members like this:

              | am:attribute_or_method  { refnode = #am; }
                (
                    // this is needed for constructs such as var:ATTR IN FRAME frame
                    { !inDynFunc }?
                    widget_qualifier
                  | // empty alternative
                )

#12 Updated by Roger Borrello almost 4 years ago

Why is the 1st parameter of KW_DYN_FUNC handled the same as the other parameters, when it is the function-name, not a parameter? Wouldn't that allow for parsing separate from func_call_parm? Something more like:

dynamic_function_func   
   :
      d:KW_DYN_FUNC^
      {
         #d.setType(FUNC_POLY);
      } 
      lparens
      (
         func_call_name (in_procedure_clause)?   <-- new method
         (comma func_call_parm[false])*
      )
      rparens
      {
         // save the original token type that rewriting erased
         ##.putAnnotation("oldtype", new Long(KW_DYN_FUNC));

         // write our function-specific annotations
         sym.annotateFunction(#d.getText(), ##, false);            
      }
   ;

The func_call_name would just parse the handle?

#13 Updated by Greg Shah almost 4 years ago

The first parameter can be an arbitrarily complex sub-expression like something + func(handle:blah:blah:method(), object-stuff:meth()) + if not trash then yatta else another:stuff:whatever():more-stuff. That matches with expr.

#14 Updated by Roger Borrello almost 4 years ago

Greg Shah wrote:

The first parameter can be an arbitrarily complex sub-expression like something + func(handle:blah:blah:method(), object-stuff:meth()) + if not trash then yatta else another:stuff:whatever():more-stuff. That matches with expr.

But why does it need to be handled using func_call_parm? It's not the same as a function call parameter.

#15 Updated by Greg Shah almost 4 years ago

It's not the same as a function call parameter.

In what way?

We know that the parameter must be an expr. That is the same as a function call parameter. The only restriction is that the result must be a character expression, but that does not exclude anything of consequence. I don't see what you are trying to achieve.

Did you try my suggestion?

#16 Updated by Roger Borrello almost 4 years ago

Greg Shah wrote:

It's not the same as a function call parameter.

In what way?

Well, my ignorance was showing. It certainly is a function call parameter, but is a very particular one.

We know that the parameter must be an expr. That is the same as a function call parameter. The only restriction is that the result must be a character expression, but that does not exclude anything of consequence. I don't see what you are trying to achieve.

Did you try my suggestion?

Yes, and it works very well! Can I check it in?

#17 Updated by Greg Shah almost 4 years ago

Can I check it in?

Yes.

#18 Updated by Roger Borrello almost 4 years ago

3821c-11399 ready for review.

#19 Updated by Greg Shah almost 4 years ago

  • Assignee set to Roger Borrello
  • Status changed from New to Test
  • % Done changed from 0 to 100

I'm fine with the change.

#20 Updated by Roger Borrello about 3 years ago

Was this waiting for the large ChUI regression to complete in order to be closed?

#21 Updated by Greg Shah about 3 years ago

No, DYNAMIC-FUNCTION() is not used in that code. If you confirm all is well, I will close it.

#22 Updated by Roger Borrello about 3 years ago

All is well... at least as far as this task is concerned.

#23 Updated by Greg Shah about 3 years ago

  • Status changed from Test to Closed

Also available in: Atom PDF