Project

General

Profile

Feature #2053

create a parameter type lookup facility for built-in functions and handle based methods

Added by Greg Shah about 11 years ago. Updated over 7 years ago.

Status:
Closed
Priority:
Normal
Start date:
02/25/2013
Due date:
% Done:

0%

Estimated time:
16.00 h
billable:
No
vendor_id:
GCD

evl_upd20130307a.zip - The built-ins and methods signature helper drop. (9.94 KB) Eugenie Lyzenko, 03/07/2013 10:04 PM

evl_upd20130308a.zip - Signature helpers for built-ins and handle based methods. (10.2 KB) Eugenie Lyzenko, 03/08/2013 05:07 PM

evl_upd20130309a.zip - Map separation drop for built-ins and methods (10.4 KB) Eugenie Lyzenko, 03/09/2013 05:20 PM

ca_upd20130310a.zip (74.9 KB) Constantin Asofiei, 03/10/2013 10:16 AM

ca_upd20130311b.zip (99.4 KB) Constantin Asofiei, 03/11/2013 06:28 AM

ca_upd20130312e.zip (117 KB) Constantin Asofiei, 03/12/2013 12:48 PM

ca_upd20130312f.zip (117 KB) Constantin Asofiei, 03/12/2013 02:08 PM

ca_upd20130313b.zip (43.5 KB) Constantin Asofiei, 03/13/2013 07:39 AM

ca_upd20130313d.zip (43.5 KB) Constantin Asofiei, 03/13/2013 08:52 AM

ca_upd20130313e.zip (43.5 KB) Constantin Asofiei, 03/13/2013 10:44 AM

ges_upd20130313a.zip (423 KB) Greg Shah, 03/13/2013 09:54 PM

ges_upd20130314a.zip (423 KB) Greg Shah, 03/14/2013 07:57 AM

ges_upd20130314b.zip (423 KB) Greg Shah, 03/14/2013 10:22 AM

ges_upd20130314d.zip (423 KB) Greg Shah, 03/14/2013 12:56 PM

ca_upd20130314a.zip (17 KB) Constantin Asofiei, 03/14/2013 03:14 PM

ca_upd20130314b.zip (17.2 KB) Constantin Asofiei, 03/14/2013 03:59 PM

ca_upd20130315a.zip (51.3 KB) Constantin Asofiei, 03/15/2013 10:24 AM

ca_upd20130315b.zip (187 KB) Constantin Asofiei, 03/15/2013 12:25 PM

ca_upd20130315c.zip (199 KB) Constantin Asofiei, 03/15/2013 03:38 PM

ca_upd20130316a.zip (199 KB) Constantin Asofiei, 03/16/2013 01:40 PM

ges_upd20130317a.zip (370 KB) Greg Shah, 03/17/2013 06:11 PM

ternary_automatic_type_conversion.p Magnifier (6.96 KB) Greg Shah, 03/18/2013 06:49 PM

ternary_really_returns_bdt.p Magnifier (57.9 KB) Greg Shah, 03/20/2013 06:55 PM

ternary_really_returns_bdt_with_buffer_value.p Magnifier (67.6 KB) Greg Shah, 03/21/2013 09:48 AM

ternary_really_returns_bdt_with_dynamic_function.p Magnifier (64.5 KB) Greg Shah, 03/21/2013 09:48 AM

ca_upd20130322d.zip (5.49 KB) Constantin Asofiei, 03/22/2013 07:23 AM

ges_upd20130323a.zip (570 KB) Greg Shah, 03/23/2013 03:04 PM

ges_upd20130323b.zip (575 KB) Greg Shah, 03/23/2013 08:40 PM

ges_upd20130324a.zip (576 KB) Greg Shah, 03/24/2013 12:29 PM

om_upd20130325a.zip (10.7 KB) Ovidiu Maxiniuc, 03/25/2013 04:06 AM

om_upd20130325b.zip (346 KB) Ovidiu Maxiniuc, 03/25/2013 02:34 PM

History

#1 Updated by Greg Shah about 11 years ago

Consider these classes:

package com.goldencode.p2j.convert;

import com.goldencode.p2j.uast.*;
import com.goldencode.p2j.convert.ParmType.*;

/**
 * Encodes the signatures of all Progress built-in functions (FUNC_*) and handle-based
 * methods (METH_*).  Provides methods to inspect these signatures. This is used during
 * conversion to do type analysis of expressions and for other tasks such as knowing the
 * proper type to emit for an unknown literal used as a parameter to a built-in.
 */
public class SignatureHelper
implements ProgressParserTokenTypes
{
   /** Stores the core mapping data by keyword token type. */
   private static final HashMap<Integer, SignDef> map = new HashMap<Integer, SignDef>();

   static
   {
      map.put(KW_CBIT,  new SignDef(FUNC_LOGICAL    , KW_CBIT    , 2, new ParmType[] { CHAR, INT }));
      map.put(KW_U_MSG, new SignDef(FUNC_INT        , KW_U_MSG   , 1, new ParmType[] { NUM }));
      map.put(KW_ABS,   new SignDef(FUNC_POLY       , KW_ABS     , 1, new ParmType[] { NUM }));

      // add the rest of the FUNC_* and METH_* built-ins here!
   }

   /**
    * Lookup the parameter at the specified index position for the function or method
    * specified by keyword.
    *
    * @param    kw
    *           The keyword (oldtype annotation) for this function or method.
    * @param    idx
    *           The zero-based index position of the parameter to lookup.
    *
    * @return   The Java class name of the wrapper class for that parameter or
    *           null if there is no match found for any reason.
*/
public static String parameterTypeName(int kw, int idx) {
String cls = null; SignDef signature = map.get(kw); if (signature != null) {
if (idx >= 0 && idx < signature.parms.length) {
cls = signature.parms[idx].name();
}
} return cls;
} /** * A simple structure to contain each function or method definition.
*/
private static class SignDef {
/** Return type (one of the FUNC_* or METH_* constants). */
int ret = -1; /** Type of the original keyword, stored in the tree as the "oldtype" annotation. */
int kw = -1; /** Parameter list codes from leftmost (0 index) to rightmost (highest index). */
ParmType[] parms = null; /** * Number of parameters that are mandatory (e.g. if there are 5 possible parms and the * last 2 are optional, then this will be 3).
*/
int mand = -1; /** * Construct an instance.
*/
public SignDef(int ret, int kw, int mand, ParmType[] parms) {
this.ret = ret;
this.kw = kw;
this.mand = mand;
this.parms = parms;
}
}
}
package com.goldencode.p2j.convert;

/**                   
 * Encodes the possible parameter types in Progress. This includes a code for each
 * individual wrapper type.  But it also includes codes for the common parent types
 * for use in cases where the parameter can be one of multiple related types (e.g.
 * NUMBERTYPE</action> would indicate that an <code>INTEGER</code>,
 * <code>INT64</code> or <code>DECIMAL</code> could all be used as a parameter).
 */ 
public enum ParmType
{
   CHAR { @Override public String name() { return "character"; } },
   LC { @Override public String name() { return "longchar"; } },
   TEXT { @Override public String name() { return "Text"; } },
   INT { @Override public String name() { return "integer"; } },
   DEC { @Override public String name() { return "decimal"; } },
   INT64 { @Override public String name() { return "int64"; } },
   NUM { @Override public String name() { return "NumberType"; } },
   LOG { @Override public String name() { return "logical"; } },
   DATE { @Override public String name() { return "date"; } },
   DT { @Override public String name() { return "datetime"; } },
   DTTZ { @Override public String name() { return "datetimetz"; } },
   HANDLE { @Override public String name() { return "handle"; } },
   RECID { @Override public String name() { return "recid"; } },
   ROWID { @Override public String name() { return "rowid"; } },
   MEM { @Override public String name() { return "memptr"; } },
   RAW { @Override public String name() { return "raw"; } },
   BIN { @Override public String name() { return "BinaryType"; } },
   BDT { @Override public String name() { return "BaseDataType"; } }
}

#2 Updated by Greg Shah about 11 years ago

  • Subject changed from create a parameter type lookup facility for built-in functions to create a parameter type lookup facility for built-in functions and handle based methods
  • Estimated time changed from 8.00 to 16.00

#3 Updated by Greg Shah about 11 years ago

  • Assignee set to Eugenie Lyzenko
  • Target version set to Milestone 4

This is a high priority for milestone 4 so that we can make sure to generate compilable code. Please implement this ASAP. I have not compiled or tested the code yet, but it should be close. Make sure it does compile and work. But your primary job is to add a SignDef to the map (see the static initializer) for every built-in function (search progress.g for "sym.addBuiltinFunction") and handle-based method (search progress.g for "sym.addAttributeOrMethod"). You will need to test specific cases in the 4GL to see if a superclass can be used (e.g. NUM instead of INT when INT/INT64/DECIMAL can all be used).

If you find some cases that can't be coded, let me know. For example, I suspect we will have cases where an INT/INT64 can be used but not a DECIMAL. If so, then we will need to find a way to code this.

#4 Updated by Eugenie Lyzenko about 11 years ago

Two questions.
1. We need to create separate class file for ParmType? In this case we need to declare ParmType.CHAR instead of CHAR.
2. The method name() can not be overridden because it is final in Enum java class. Can we use toString() instead?

#5 Updated by Greg Shah about 11 years ago

We need to create separate class file for ParmType?

Yes.

In this case we need to declare ParmType.CHAR instead of CHAR.

Didn't the static import statement work? I wasn't sure if it would work with enums or not. If it doesn't work, then yes, qualify the references.

The method name() can not be overridden because it is final in Enum java class. Can we use toString() instead?

Yes.

#6 Updated by Eugenie Lyzenko about 11 years ago

Didn't the static import statement work? I wasn't sure if it would work with enums or not. If it doesn't work, then yes, qualify the references.

OK. The code line in SignatureHelper.java should be:

      map.put(KW_ABS,   new SignDef(FUNC_POLY       , KW_ABS     , 1, new ParmType[] { ParmType.NUM }));

to get compiled for example.

#7 Updated by Eugenie Lyzenko about 11 years ago

Several data type question for this time:
1. If we have multiple versions of the same function with different numbers of different kind of parameters how we declare them? For example:
DATE - all integers
DATE - character to convert to DATE type
DATE - integer number of days
DATE - DATETIME-TZ data type
Do we need 4 different entries in static map declaration?

2. What is the correct data type mapping for DB record? RECID or RAW? RECID is the integer number, can it represent the DB record?
3. If the function can have MEMPTR and RAW the common data type is RAW?
4. If the function can take arbitrary input type we are using BDT, correct?

#8 Updated by Greg Shah about 11 years ago

If we have multiple versions of the same function with different numbers of different kind of parameters how we declare them? For example:

Good question. I had not considered the case where there are multiple signatures. That makes using a map a problem, unless we enhance SignDef or store an array of SignDef instances. How about this approach:

   /**
    * A simple structure to contain each function or method definition.
    */
   private static class FuncDef
   {
      /** Return type (one of the FUNC_* or METH_* constants). */
      int ret = -1;

      /** Type of the original keyword, stored in the tree as the "oldtype" annotation. */
      int kw = -1;

      /** All possible signatures for this entity. */
      SignDef signatures[] = null;

      /**
       * Construct an instance (convenience version).  This form can only be used
       * for functions or methods that don't have multiple conflicting signatures.
       *
       * @param    ret
       *           The return type.
       * @param    kw
       *           The keyword type (from the "oldtype" annotation).
       * @param    mand
       *           Number of parameters that are mandatory (e.g. if there are 5
       *           possible parms and the last 2 are optional, then this will be
       *           3).
       * @param    parms
       *           The specific parameter signature for this function or method.
       */
      public FuncDef(int ret, int kw, int mand, ParmType[] parms)
      {
         this(ret, kw, new SignDef[] { new SignDef(mand, parms) });
      }

      /**
       * Construct an instance.
       *
       * @param    ret
       *           The return type.
       * @param    kw
       *           The keyword type (from the "oldtype" annotation).
       * @param    signatures
       *           An array of signature definitions.
       */
      public FuncDef(int ret, int kw, SignDef[] signatures)
      {
         this.ret        = ret;
         this.kw         = kw;
         this.signatures = signatures;
      }
   }

   /**
    * A simple structure to contain a function or methods parameter signature.
    */
   private static class SignDef
   {
      /** Parameter list codes from leftmost (0 index) to rightmost (highest index). */
      ParmType[] parms = null;

      /**
       * Number of parameters that are mandatory (e.g. if there are 5 possible parms and the
       * last 2 are optional, then this will be 3).
       */
      int mand = -1;

      /**
       * Construct an instance.
       */
      public SignDef(int mand, ParmType[] parms)
      {
         this.mand  = mand;
         this.parms = parms;
      }
   }

What is the correct data type mapping for DB record? RECID or RAW? RECID is the integer number, can it represent the DR record?

How I hate Progress.

You should add a code for RECORD. RECID and ROWID are different.

If the function can have MEMPTR and RAW the common data type is RAW?

No, BinaryType is the parent class for MEMPTR and RAW. So you should use BIN.

If the function can take arbitrary input type we are using BDT, correct?

Yes.

#9 Updated by Eugenie Lyzenko about 11 years ago

How about this approach:
...

The approach is working but we need to modify the getter this way for example:

   /**
    * Lookup the parameter at the specified index position for the function or method
    * specified by keyword.
    *
    * @param    kw
    *           The keyword (oldtype annotation) for this function or method.
    * @param    mand
    *           The numbers of mandatory parameters to search.
    * @param    idx
    *           The zero-based index position of the parameter to lookup.
    *
    * @return   The Java class name of the wrapper class for that parameter or
    *           null if there is no match found for any reason.
    */
   public static String parameterTypeName(int kw, int mand, int idx)
   {
      String cls = null;

      FuncDef function = map.get(kw);

      if (function != null)
      {
         for (int i = 0; i < function.signatures.length; i++)
         {
            // Scan inside functions with same name but different kind of parameters. The last
            // resort is the mandtory number should identify required function exactly.
            if (function.signatures[i].mand == mand)
            {
               if (idx >= 0 && idx < function.signatures[i].parms.length)
               {
                  cls = function.signatures[i].parms[idx].toString();
               }
               break;
            }
         }
      }

      return cls;
   }


The idea is: If we have one function name but different type of parameters, the mandatory amount of parameters will limit the scan area and for remaining versions(when we have only one param of diffrent types) we can choose the closest base data type. What do you think?

#10 Updated by Greg Shah about 11 years ago

Generally, I like it. This will work for some of the cases, but not all cases. Still, it is good enough to go with it for now.

#11 Updated by Constantin Asofiei about 11 years ago

Greg: I don't know the order you plan to have this working, but if documenting each signature will take more time, I think we should build the mechanism of using the signature to emit proper unknown vals (and other usage) first and after that finish documenting each signature. I can provide a list of high-priority signatures (compile errors from the logs), which we can do first.

I ask this because I've seen that sometime other compile errors appear after one is fixed, as they are 'hidden'.

#12 Updated by Greg Shah about 11 years ago

Eugenie: how much more time do you expect this to take?

Constantin: We can certainly go ahead with the infrastructure to use the facility. I planned this to be exposed through the ExpressionConversionWorker, so dummy interfaces can be provided for now and the rule-set changes can be implemented.

#13 Updated by Constantin Asofiei about 11 years ago

Constantin: We can certainly go ahead with the infrastructure to use the facility. I planned this to be exposed through the ExpressionConversionWorker, so dummy interfaces can be provided for now and the rule-set changes can be implemented.

What I mean is provide full support for the facility first and finish documenting the signatures second. This way, we can get past bottlenecks in the folders faster.

#14 Updated by Eugenie Lyzenko about 11 years ago

Eugenie: how much more time do you expect this to take?

It is > 100 items left in functions. I guess this is ~10 hours of pure work. Plus attributes and methods(it can be estimated as 5 minutes per map.put entry). I think if we have the first priority list - we can implement it first. Then add the remaining in a next drop.

#15 Updated by Greg Shah about 11 years ago

OK, get as much done tonight as possible. Then create an update zip and upload it here.

Constantin will be able to look at it in the morning. He can integrate it with ECW and build the necessary facilities to use it. He will also provide the priority list so that you can get those done first tomorrow (and re-upload the results).

#16 Updated by Eugenie Lyzenko about 11 years ago

OK.

The new datatype under questions:
1. If parameter is widget we need to add something: WID mapping to GenerigWidget?
2. Arrays are BIN? Or create new one?

#17 Updated by Eugenie Lyzenko about 11 years ago

  • File evl_upd20130306a.zip added

The update has been uploaded. All built-in functions are there except so called "fake" web-speed ones. No methods for handle objects are added.

#18 Updated by Eugenie Lyzenko about 11 years ago

Greg,

I'm working on the handle based methods adding. It's avout 300 lines. So I'm expecting to finish today-tomorrow. The qestion. If something does not have parameters the line should be like this?

map.put(KW_ACC_CHG, new FuncDef(METH_LOGICAL        , KW_ACC_CHG   , 0, null));

#19 Updated by Greg Shah about 11 years ago

If parameter is widget we need to add something: WID mapping to GenerigWidget?

Yes, that is fine.

Arrays are BIN? Or create new one?

No, not BIN. Which cases need an array besides EXTENT?

For that one, make a code of BDT_ARRAY since it takes a BaseDataType[].

If something does not have parameters the line should be like this?

map.put(KW_ACC_CHG, new FuncDef(METH_LOGICAL        , KW_ACC_CHG   , 0, null));

Yes. That will be fine.

#20 Updated by Eugenie Lyzenko about 11 years ago

Found potential issue in progress.g:
According to Progress doc CONFIG-NAME is an attribute, not a method. But we define it as method:
sym.addAttributeOrMethod( KW_CFG_NAME, METH_CHAR );

This is Progress document error?

#21 Updated by Greg Shah about 11 years ago

No, CONFIG-NAME is supposed to be an attribute. It is a bug in our parser. I have some other parser changes I'm making, so I will make the fix here. Good catch.

#22 Updated by Eugenie Lyzenko about 11 years ago

  • File deleted (evl_upd20130306a.zip)

#23 Updated by Eugenie Lyzenko about 11 years ago

The handle methods has been added. There are the methods we do not define in progress.g file:
ADD-PARENT-ID-RELATION
CREATE-LIKE-SEQUENTIAL??
CURRENT-QUERY??
GET-CLIENT
GET-COLUMN
GET-MESSAGE-TYPE
GET-ROW
INITIALIZE
MARK-NEW
MARK-ROW-STATE
READ-JSON
SERIALIZE-ROW
TENANT-ID
TENANT-NAME
WRITE-JSON

Also the code is still not formatted to confirm 98 column restriction. And I have another questions:
1. Do we handle CLOB data type?
2. Ho we will declare the methods with arbitrary numbers of parameters? BDT_ARRAY is OK?
3. NOT ENTERED function is planned to be supported in conversion?
4. What about webspeed built-in functions? Do we need to add them too?

I have deleted yesterday file because wrong directory tree inside.

#24 Updated by Greg Shah about 11 years ago

There are the methods we do not define in progress.g file:

These are from v11 which we don't support yet.

Do we handle CLOB data type?

Can you provide an example? A CLOB can only be a field in the database, so I guess the method must take a reference to a CLOB field?

Ho we will declare the methods with arbitrary numbers of parameters? BDT_ARRAY is OK?

No. Please give examples.

NOT ENTERED function is planned to be supported in conversion?

Yes, we support it. It is a FUNC_LOGICAL with an oldtype == NOT_ENTERED.

What about webspeed built-in functions? Do we need to add them too?

No.

#25 Updated by Eugenie Lyzenko about 11 years ago

Can you provide an example? A CLOB can only be a field in the database, so I guess the method must take a reference to a CLOB field?

The are three points of usage I've found:
1. GET-CODEPAGE (large-char-object), large-char-object is LONGCHAR or CLOB field
2. GET-COLLATION (clob-field), clob-field is CLOB field.
3. IS-COLUMN-CODEPAGE (field), field is CLOB field.

No. Please give examples.

For example the method:
SET-BUFFERS (buffer, [buffer]...), buffer is the handle to the buffer or character expression evaluating the name of the buffer.

Another question:
1.The method
SET-NODE (x-noderef) takes the variable of the XNodereference type. We need the special type, HANDLE is not OK? Something like XNODEREF mapping th XNodeRef?

2. The function RECORD-LENGTH (buffer) takes the DB buffer with record. The data type is RECORD for such a cases?

#26 Updated by Greg Shah about 11 years ago

Add CLOB (which is really only the CLOB field case) and LARGE_CHAR_OBJECT which is either LONGCHAR or CLOB.

SET-BUFFERS (buffer, [buffer]...), buffer is the handle to the buffer or character expression evaluating the name of the buffer.

Since this can be any number (and mixture?) of handle or character parms, let's add a new BDT_VARARGS code.

SET-NODE (x-noderef) takes the variable of the XNodereference type. We need the special type, HANDLE is not OK? Something like XNODEREF mapping th XNodeRef?

This is just a handle. The documentation is unclear, but there is no way to obtain or reference the resource directly. Everything is handle based.

The function RECORD-LENGTH (buffer) takes the DB buffer with record. The data type is RECORD for such a cases?

Yes.

#27 Updated by Eugenie Lyzenko about 11 years ago

The remaining questions:
The functions defined in progress.g but not in 4GL reference documentation:

      sym.addBuiltinFunction("lower"                , FUNC_CHAR       , true );
      sym.addBuiltinFunction("upper"                , FUNC_CHAR       , true );

Undocumented functions?

The Temptable buffer is the RECORD data type? Example:

REJECTED(ttbuffer)

function.

#28 Updated by Eugenie Lyzenko about 11 years ago

Greg,

I'm preparing the final drop to upload and requesting for ability not to follow the 98 column restriction for static map code. I have tried to format according this rule and the result decrease readability. I'm offering to have one line per one entry with space justification. What do you think? Like this:

...
map.put(KW_ACCUM,    new FuncDef(FUNC_POLY    , KW_ACCUM    , 2, new ParmType[]...));
map.put(KW_ADD_INVL, new FuncDef(FUNC_POLY    , KW_ADD_INVL , 3, new ParmType[]...));
...

I'm affraid otherwise it will be difficult to modify/control this table.

#29 Updated by Greg Shah about 11 years ago

Undocumented functions?

Yes, these are undocumented.

It is our understanding that LOWER is the same thing as LC and UPPER is the same thing as CAPS. As far as I can tell these are just different names that call the same function inside the 4GL. I think they were added to match common functions used in SQL.

The Temptable buffer is the RECORD data type?

Yes.

I'm preparing the final drop to upload and requesting for ability not to follow the 98 column restriction for static map code.

That is fine. Please put a comment in there that we have deliberately gone past the line limit because it is believed to be much more readable.

#30 Updated by Eugenie Lyzenko about 11 years ago

The first complete drop to review has been uploaded.

PS: Forgot to note. The file SignatureHelper.java contains the code not confitming the 98 column source code restriction. This is done to keep acceptable visivility of the big table like block.

#31 Updated by Constantin Asofiei about 11 years ago

The fact that a function can have multiple signatures troubles us at conversion time, as if we don't offer a choice to not specify the number of mandatory parameters, we will need to hardcode for each function the number of mandatory parameters and provide it to the conversion rules. As I see, there are only these cases with multiple signatures:

KW_DATE
KW_DATETIME
KW_DATE_TZ
KW_INSERT
KW_REPLACE

I prefer to explicitly handle them at conversion time (i.e. determine the number of mandatory parameters and any other info required for them), and add a SignatureHelper.parameterTypeName(int kw, int idx) which can be called for all other normal cases.

Greg: you mentioned in a comment we will expose this through ExpressionConversionWorker. I don't understand what else is needed beside adding the ExpressionConversionWorker.ExpressionHelper.parameterTypeName APIs which in turn call SignatureHelper.

#32 Updated by Constantin Asofiei about 11 years ago

Eugenie: KW_FILL appears twice in the map.

#33 Updated by Constantin Asofiei about 11 years ago

About KW_DATETIME:
  • how can we disambiguate the signature, if the number of mandatory parameter is the same (1) in two cases?
  • I see that the DATETIME function can have a maximum of 1, 2 or 7 parameter. In the case of 7 parameters, there are 5 mandatory according to docs. In SignatureHelper, you mentioned 4 in this case, where IMO should have been 5 mandatory parameters.

For KW_REPLACE: I see that you've specified two signatures for it, but according to 10.2B docs, it has 3 mandatory parameters, not 2.

Can you tell me which version of progress is specified in the documentation you've used to build the SignatureHelper database ?

About ParmType: I see that ParmType.TEXT is used when character and longchar can be used for the param, but note that this class is abstract, and it can't be instantiated. In case a Text parameter is needed, then we should default to character if both character and longchar are supported.

#34 Updated by Constantin Asofiei about 11 years ago

There is another case which is not treated properly. When IF ternary operator has an ? on the else or then branch, as in:

ch = if ch = ? then ? else ?.

then we will not be able to disambiguate the ?. Note that the AST in this case looks like:
   assignment [ASSIGNMENT] @0:0
      = [ASSIGN] @2:4
         ch [VAR_CHAR] @2:1
         expression [EXPRESSION] @0:0
            if [FUNC_POLY] @2:6
               = [EQUALS] @2:12
                  ch [VAR_CHAR] @2:9
                  ? [UNKNOWN_VAL] @2:14
               ? [UNKNOWN_VAL] @2:21
               ? [UNKNOWN_VAL] @2:28

As you see, the condition, then and else branches are on the same level.

Greg: I think we need to handle this case explicitly in the TRPL rules, to wrap only the first parameter (the condition).

#35 Updated by Constantin Asofiei about 11 years ago

Another case of the KW_DYN_FUNC: the handle parameter is optional. I think this should also be explicitly treated by the TRPL rules, together with KW_SUPER, as the varargs these functions accept can't be disambiguated based on the function name, but based on other info.

#36 Updated by Greg Shah about 11 years ago

Some thoughts:

The fact that a function can have multiple signatures troubles us at conversion time, as if we don't offer a choice to not specify the number of mandatory parameters, we will need to hardcode for each function the number of mandatory parameters and provide it to the conversion rules.

My plan:

1. Make sure that the common case (a single signature) can be easily determined from the keyword and parameter index.

2. For the multiple signature case, we would add the known signature as we have it. So we would build up an encoding of the signature with all the types that we know. If some of the types in the code can be determined without context analysis, then the number and types of the parameters in use may be enough information to detect the right signature and thus the type. We still pass the keyword and parm index, but also we pass some kind of encoded signature of what we know:

keyword = KW_DATE
index we are looking up = 1
parm_array = INT, _, INT

The _ is an unknown parm type, in this case it is also the parm we are looking up. But you can imagine that there can be more than one _ parm in our array.

About ParmType: I see that ParmType.TEXT is used when character and longchar can be used for the param, but note that this class is abstract, and it can't be instantiated. In case a Text parameter is needed, then we should default to character if both character and longchar are supported.

I expected that the ECW helper code would use these ParmType values to determine the proper wrapper (or even a non-wrapper type like Buffer...). I don't want the SignatureHelper to know the details of that. Let's limit the SignatureHelper to determining the proper signature and returning the right value for the index.

I think this should also be explicitly treated by the TRPL rules, together with KW_SUPER, as the varargs these functions accept can't be disambiguated based on the function name, but based on other info.

Actually, I think the ECW already handles KW_SUPER fully. It searches up the tree to find the containing function and it returns that function's return type. As far as I understand it, that is good.

In regard to KW_DYN_FUNC, what do you suggest? We already do quite a bit of context analysis, but also some even more important work in TRPL. The ECW already supports the casttype annotation that is there. Is it not always available early enough? Please help me understand the problem.

#37 Updated by Constantin Asofiei about 11 years ago

Actually, I think the ECW already handles KW_SUPER fully. It searches up the tree to find the containing function and it returns that function's return type. As far as I understand it, that is good.

In regard to KW_DYN_FUNC, what do you suggest? We already do quite a bit of context analysis, but also some even more important work in TRPL. The ECW already supports the casttype annotation that is there. Is it not always available early enough? Please help me understand the problem.

When I mentioned KW_SUPER and KW_DYN_FUNC I didn't refer to their return type, but to their parameters. If unknown is passed as in:

super(?). 
dynamic-function("foo", ?).

then this unknown literals can't be disambiguated by the ECW, as it needs info about the user-defined function signature targeted by these calls. So, the TRPL rules should not invoke ECW in these cases, as ECW doesn't know anything about them.

#38 Updated by Greg Shah about 11 years ago

OK. But we could handle these cases too.

1. SUPER is especially easy to handle, because we can use the same technique to read up to the containing function def to get at its parameter list.

2. The DYNAMIC-FUNCTION case is tougher, but we already gather all the function names + return types for reference. It would not be hard to add the parameter data and use that for lookups.

#39 Updated by Greg Shah about 11 years ago

Actually, after thinking about it some more, is there a need to ever wrap the parameters to DYNAMIC-FUNCTION? They will be passed through as an array of BDT, right? I think we can hide any morphing inside the runtime.

#40 Updated by Constantin Asofiei about 11 years ago

Greg Shah wrote:

Actually, after thinking about it some more, is there a need to ever wrap the parameters to DYNAMIC-FUNCTION? They will be passed through as an array of BDT, right? I think we can hide any morphing inside the runtime.

No, the parameters are passed using varargs - see the ControlFlow's runSuper and invokeDynamicFunction APIs. And I would prefer to do the work at conversion time, then let the runtime do the work on each and every call...

#41 Updated by Eugenie Lyzenko about 11 years ago

For KW_REPLACE: I see that you've specified two signatures for it, but according to 10.2B docs, it has 3 mandatory parameters, not 2.

I have specified 3 mandatory for REPLACE as function and 2 or 3 depending on the parameters types when it is used as method.

Can you tell me which version of progress is specified in the documentation you've used to build the SignatureHelper database ?

11.0

#42 Updated by Eugenie Lyzenko about 11 years ago

Eugenie: KW_FILL appears twice in the map.

Yes, one for Built-in function, other for method. They both have different return value type and different parameters set. We need to implement the approach to separate them because I think this is not the case when we have single function with different set of parameters.

#43 Updated by Constantin Asofiei about 11 years ago

Eugenie Lyzenko wrote:

Eugenie: KW_FILL appears twice in the map.

Yes, one for Built-in function, other for method. They both have different return value type and different parameters set. We need to implement the approach to separate them because I think this is not the case when we have single function with different set of parameters.

Considering that the map uses token type as key and both these cases have the same token type, then you need to merge them, as the map can hold only one value per key (or use different token types for each case). Currently, only the last KW_FILL will survive in the map. Please check if there are other cases of such collisions.

#44 Updated by Greg Shah about 11 years ago

This also means we will have to pass the token type (FUNC_*/METH_*) in addition to the keyword.

#45 Updated by Eugenie Lyzenko about 11 years ago

This also means we will have to pass the token type (FUNC_*/METH_*) in addition to the keyword.

Yes, we can implement the approach:
1. To have two different maps, one for functions, one for methods.
2. Put all functions to map_func, methods to map_meth.
3. Change the parameterTypeName () this way, type is either FUNC_* or METH_*:

public static String parameterTypeName(int kw, int type, int mand, int idx)
{
...
   FuncDef function = null;

   if (type > BEGIN_FUNCTYPES && type < END_FUNCTYPES)
   {
      function = map_func.get(kw);
   }
   else if (type > BEGIN_METH && type < END_METH)
   {
      function = map_meth.get(kw);
   }
   else
   {
      Some action for unknown type
   }
...

#46 Updated by Greg Shah about 11 years ago

Yes, go with that approach.

#47 Updated by Eugenie Lyzenko about 11 years ago

New drop uploaded. The function and methods are separated in two different maps.

#48 Updated by Constantin Asofiei about 11 years ago

Constantin Asofiei wrote:

Greg Shah wrote:

Actually, after thinking about it some more, is there a need to ever wrap the parameters to DYNAMIC-FUNCTION? They will be passed through as an array of BDT, right? I think we can hide any morphing inside the runtime.

No, the parameters are passed using varargs - see the ControlFlow's runSuper and invokeDynamicFunction APIs. And I would prefer to do the work at conversion time, then let the runtime do the work on each and every call...

Actually, we can go ahead and ignore KW_SUPER and KW_DYN_FUNC's varargs, as the runtime already casts each parameter to the expected type.

#49 Updated by Constantin Asofiei about 11 years ago

Eugenie: I think KW_SET_BUF should have a BDT_VARARGS instead of BDT_ARRAY, because as I see from the docs, it can receive a variable number of parameters. Also, as I understand, BDT_ARRAY maps the 4GL's extent type.

#50 Updated by Constantin Asofiei about 11 years ago

Attached update contains:
  1. ParmType - added needed APIs for the enum
  2. SignatureHelper - fixed the definition for KW_IF, KW_DYN_FUNCT, KW_SET_BUF. Also, improved the APIs
  3. ExpressionConversionWorker - first pass for the parameterTypeName tool. Greg: let me know if you see this integrated deeper in the ECW's internals.
  4. base_structure.xml - support for wrapping unknown vals and also wrapping literals for functions/methods registered in the common-progress.rule's needs_wrapped_literals function.

I'm putting this through conversion regression testing + server folder testing just to see how it behaves.

#51 Updated by Constantin Asofiei about 11 years ago

And the update for note 50.

#52 Updated by Eugenie Lyzenko about 11 years ago

And the update for note 50.

Constantin, how you compiled the update? I put it on top of the current bzr code and what I have:

compile:
    [javac] Compiling 1110 source files to /home/eugenie/timco/p2j/build/classes
    [javac] /home/eugenie/timco/p2j/src/com/goldencode/p2j/convert/ExpressionConversionWorker.java:1860: inconvertible types
    [javac] found   : java.lang.Object
    [javac] required: long
    [javac]                int kw = (int) ((long) ast.getAnnotation("oldtype"));
    [javac]                                                        ^
    [javac] /home/eugenie/timco/p2j/src/com/goldencode/p2j/convert/ExpressionConversionWorker.java:1941: inconvertible types
    [javac] found   : java.lang.Object
    [javac] required: long
    [javac]          int kw = (int) ((long) ast.getAnnotation("oldtype"));
    [javac]                                                  ^
    [javac] Note: Some input files use unchecked or unsafe operations.
    [javac] Note: Recompile with -Xlint:unchecked for details.
    [javac] 2 errors

Is the line:

int kw = (int) ((long) ast.getAnnotation("oldtype"));

correct?

#53 Updated by Constantin Asofiei about 11 years ago

Eugenie: what Java are you using ? I haven't got any error on my system or devsrv01.

#54 Updated by Eugenie Lyzenko about 11 years ago

Eugenie: what Java are you using ? I haven't got any error on my system or devsrv01.

1.6.X

The long 64 bit can not be converted to 32 bit int without losses.

What is working on my system:

int kw = ((java.lang.Long) ast.getAnnotation("oldtype")).intValue();

This at least compiled.

#55 Updated by Constantin Asofiei about 11 years ago

Yes, that was my fix too for Java 1.6. And I don't think there will be losses, as the token type doesn't need 32 bits (at least for now).

#56 Updated by Constantin Asofiei about 11 years ago

This update contains some fixes in ECW.parameterTypeName and base_structure.xml, plus BUFFER-COPY/COMPARE (from Eric) and ADD-NEW-FIELD new API changes.
PS: plus some unknown conversion changes in literals.rules (related to SUPER calls and attribute set to unknown).

#57 Updated by Eugenie Lyzenko about 11 years ago

Yes, that was my fix too for Java 1.6. And I don't think there will be losses, as the token type doesn't need 32 bits (at least for now).

I see, the upper 32-bit of the 64-bit long is not important in this case for now. And I think we need to provide some explicit code to extract lower 32-bit part from 64-bit integer.

#58 Updated by Greg Shah about 11 years ago

The case automatically assigns the lower 32-bits of the long to the int. Since oldtype can never be > 2GB, this is safe. We do need to continue coding everything so that it can compile in both Java 6 and Java 7, until TIMCO and all of our other systems are shifted to Java 7 (and we have some production time running on Java 7 without problems).

#59 Updated by Greg Shah about 11 years ago

Code Review:

1. It seems to me that ParmType.typesMatch() should implement a wider range of matching behavior. For example, if CHAR or LONGCHAR are the candidates and TEXT is the expected parameter, then it is a match.

2. Why does the simple form of SignatureHelper.getSignature() make a copy of the parameter array when the other forms do not?

3. Should the SignatureHelper.lookupSignature() processing only try for an inexact match on signatures where mand < signatures[i].length? In other words, it is only valid to try for an inexact match when there are optional parameters in a signature.

4. Since expressionType() can return null, it is possible that the ptypes[] created by ECW.resolveSignature() and passed through to SignatureHelper.getSignature/lookupSignature will contain nulls. These methods are not null safe.

5. I think the while loop added to base_structure.xml seems too aggressive. This will match non-lock type literals (actually, unknown_val is tested for twice since the "literals" function will match it too) anywhere in a procedure, not just as a parameter. When it is outside a parameter definition, then it will search all the way up to the root and then bail out. It will also match literals that are deeply nested inside a sub-expression. These should not be matched. It seems that we could do a search up to find the first non-LPARENS parent node and then match more deterministically, to tighten this up.

6. In regard to the "should this be more tightly integrated with ECW?" question. I was originally thinking that we would not use the parameterTypeName() directly from TRPL code. Instead, I planned to augment the analyzeContext() to properly determine the expression type for not known parameters (we would include new processing for UNKNOWN_VAL when it is passed as a parameter to a builtin func or method). My thinking was that this would be a more generic solution and would enhance the overall context analysis processing. I can do that work if you prefer.

#60 Updated by Constantin Asofiei about 11 years ago

1. It seems to me that ParmType.typesMatch() should implement a wider range of matching behavior. For example, if CHAR or LONGCHAR are the candidates and TEXT is the expected parameter, then it is a match.

The return in this method uses a expectedClass.isAssignableFrom(candidateClass) which takes care of this problem. That's why I've added the P2J wrapper class to each ParmType enum constant.

2. Why does the simple form of SignatureHelper.getSignature() make a copy of the parameter array when the other forms do not?

That was added in case when only a certain prefix of the signature was needed (and in the end the approach was not good), but that call now always searches for exact matches, so it can be removed.

3. Should the SignatureHelper.lookupSignature() processing only try for an inexact match on signatures where mand < signatures[i].length? In other words, it is only valid to try for an inexact match when there are optional parameters in a signature.

Hm... yes, you are correct, we can skip signatures which do not allow optional parameters.

4. Since expressionType() can return null, it is possible that the ptypes[] created by ECW.resolveSignature() and passed through to SignatureHelper.getSignature/lookupSignature will contain nulls. These methods are not null safe.

In these cases where ptypes[i] is null, I think it default to BaseDataType, right?

5. I think the while loop added to base_structure.xml seems too aggressive. This will match non-lock type literals (actually, unknown_val is tested for twice since the "literals" function will match it too) anywhere in a procedure, not just as a parameter. When it is outside a parameter definition, then it will search all the way up to the root and then bail out. It will also match literals that are deeply nested inside a sub-expression. These should not be matched. It seems that we could do a search up to find the first non-LPARENS parent node and then match more deterministically, to tighten this up.

I know is too aggressive. And LPARENS can't be used, as function/method calls don't wrap their parameters in LPARENS. Example for h:buffer-copy(?, ?, ?, ?).:

   assignment [ASSIGNMENT] @0:0
      : [COLON] @47:2
         h [VAR_HANDLE] @47:1
         buffer-copy [METH_LOGICAL] @47:3
            expression [EXPRESSION] @0:0
               ? [UNKNOWN_VAL] @47:15
            expression [EXPRESSION] @0:0
               ? [UNKNOWN_VAL] @47:18
            expression [EXPRESSION] @0:0
               ? [UNKNOWN_VAL] @47:21
            expression [EXPRESSION] @0:0
               ? [UNKNOWN_VAL] @47:24

More, in some cases the parameter is enclosed in an EXPRESSION node, in others in a PARAMETER node, in others in a KW_IN node, and in others is the direct child of the function/method call. But, as I think about it, will be enough to look at most two levels up. And if I think more about it, as in the end we will need to wrap _POLY undetermined expressions using the parameter type (as in trim(h:buffer-value) calls), and in these cases we can't match just on _POLY, I think we should stop on a child of a function/method call and test the child - is it unknown ? is it literal and the function needs it wrapped ? is it poly ? And we might be able to get rid of these tests, if the work will be moved in analyzeContext: if it returns null, BaseDataType or unknown, then we can go ahead and wrap it.

6. In regard to the "should this be more tightly integrated with ECW?" question. I was originally thinking that we would not use the parameterTypeName() directly from TRPL code. Instead, I planned to augment the analyzeContext() to properly determine the expression type for not known parameters (we would include new processing for UNKNOWN_VAL when it is passed as a parameter to a builtin func or method). My thinking was that this would be a more generic solution and would enhance the overall context analysis processing. I can do that work if you prefer.

Yes, please do.

#61 Updated by Greg Shah about 11 years ago

In these cases where ptypes[i] is null, I think it default to BaseDataType, right?

Are you asking what the type should be if expressionType() returns null? Yes, BDT is the best guess. But I can also imagine some of the "special cases" like RECORD or WIDGET... where their expression type would definitely not be BDT and where the ECW will have no idea what the type should be. Likewise, there are always features added to our support which are imperfectly supported by ECW. SYS_HANDLE was a good example. The null protection is important for this reason.

And LPARENS can't be used, as function/method calls don't wrap their parameters in LPARENS. Example for h:buffer-copy(?, ?, ?, ?).:

It is possible in the 4GL, but I think we must remove the excess LPARENS:

def var i as int.

i = length(((((("some_text_literal")))))).
message i.

the AST:

        <ast col="0" id="386547056659" line="0" text="expression" type="EXPRESSION">
          <ast col="5" id="386547056660" line="3" text="length" type="FUNC_INT">
            <annotation datatype="java.lang.Long" key="oldtype" value="1568"/>
            <annotation datatype="java.lang.Boolean" key="builtin" value="true"/>
            <annotation datatype="java.lang.Boolean" key="returnsunknown" value="true"/>
            <ast col="17" id="386547056667" line="3" text="&quot;some_text_literal&quot;" type="STRING"/>
          </ast>
        </ast>

is it poly ?

The changes I am working on relate to the wrapping of indeterminate results to new logical(BDT) for IF statement, WHEN clauses, logical operators and the 1st child of the IF function (e.g. the condition of the ternary operator).

BUT, the way I am approaching this provides a useful tool for the indeterminate _POLY cases (those _POLY cases where we would otherwise have to use context analysis to determine the type). I am making the 2nd parameter to expressionType() a boolean "infer" flag. When true, we implement context analysis for the indeterminate _POLY types (for now it is only: DB_REF_NON_STATIC, DYNAMIC-FUNCTION and BUFFER-VALUE). But if you pass false for infer, then context analysis for those 3 cases is disabled and you will get back a "BaseDataType" as the class. This can be used to know when to wrap.

And we might be able to get rid of these tests, if the work will be moved in analyzeContext: if it returns null, BaseDataType or unknown, then we can go ahead and wrap it.

Yes, exactly right.

Yes, please do.

OK. Please make the final changes to the code and get it tested and checked in. Then I will merge my changes on top and look to integrate the signature stuff into the rest of the ECW. Then we can figure out the next steps for the indeterminate _POLY cases.

#62 Updated by Greg Shah about 11 years ago

Do we really need BDT.castLogical()? Or can we handle this using new logical(BDT)? If we can avoid the castLogical() then we can implement a common solution for wrapping an indeterminate _POLY return value. I am already implementing that generic solution for the logical cases, BUT in every place I put that code I have to exclude the DYNAMIC-FUNCTION case, because it processes things differently (implementing the castLogical() in builtin_functions.rules).

#63 Updated by Constantin Asofiei about 11 years ago

Yes, we can get rid of castLogical and use new logical() instead.

I have an update for which I still need to make some small changes (i.e. skip nodes like KW_FIRST for a CAN-FIND all), and I think I will finish it today.

#64 Updated by Greg Shah about 11 years ago

OK, I'll fold that into my changes. It will make the result much cleaner.

In regard to your changes, that is great! The sooner the better, since I need to build mine on yours and I want to finish all of it today.

#65 Updated by Constantin Asofiei about 11 years ago

I don't recall seeing "attributes set to unknown"-related errors, but I think we should consider handling the attribute assign to unknown/_POLY cases too. i.e. h:error = ?. fails on compile with my current changes, as it is converted to handle.unwrapError(h).setError(new unknown());, instead of handle.unwrapError(h).setError(new logical());

#66 Updated by Greg Shah about 11 years ago

That makes sense. Are you going to add that?

In a related note: my changes are ready, but I want to test them with the DYNAMIC-FUNCTION cases. Do you have good testcases for me to try?

Finally, what do you think your timing will be? If it is going to take a while, I will regression test twice to clear any Majic errors now. If it is close, then I will merge first and avoid the extra regression testing.

#67 Updated by Constantin Asofiei about 11 years ago

That makes sense. Are you going to add that?

No, I don't want to add this now.

In a related note: my changes are ready, but I want to test them with the DYNAMIC-FUNCTION cases. Do you have good testcases for me to try?

See the handle_dyn_func* cases from #1920 (they are in the repo too).

Finally, what do you think your timing will be? If it is going to take a while, I will regression test twice to clear any Majic errors now. If it is close, then I will merge first and avoid the extra regression testing.

MAJIC build was OK with this update (but silly me, I forgot to compare the sources before running conversion again, as I had a DYNAMIC-FUNCTION-related change exposed by server folders). Anyway, I'm running MAJIC conversion testing again and server folders are also converting again. I'll check the sources too this time, and I'll let you know what happens.

If you have a better idea to for my base_structure.xml changes, please go ahead. What I've done was to land on the child of a function/method call and work with that child. But note that not every child of a function/method call needs to be tested (i.e. the AGGREGATE phrase from a ACCUM function call, the KW_FIRST/RECORD_PHRASE children of a CAN-FIND function call, etc), so I had to put some limits on which functions will be used, as current ECW version will fail if such nodes were passed. More, seems like ECW still has some problems when the passed AST has a SYS_HANDLE, NO_LOCK_LITERAL, or other nodes, so for now I had to limit the last fallback part of the base_structure.xml changes (at line 359) to be executed only if a _POLY/DB_REF_NON_STATIC node is in the expression (the descendant calls are expensive, I know :().

I will use this version to clear all the _POLY-related changes error from the server build logs, as it seems stable.

#68 Updated by Greg Shah about 11 years ago

The code looks fine. I have changes in the assignments.rules and I prefer if you don't include that file (it is just whitespace changes).

In regard to the base_structure changes:

This comment seems out of place:

            <!-- this is a node which converts as a function parameter. for now, ignore KW_TABLE,
                 KW_BUFFER, BUFFER and TABLE nodes -->

More, seems like ECW still has some problems when the passed AST has a SYS_HANDLE, NO_LOCK_LITERAL, or other nodes, so for now I had to limit the last fallback part of the base_structure.xml changes (at line 359) to be executed only if a _POLY/DB_REF_NON_STATIC node is in the expression (the descendant calls are expensive, I know :().

I have fixed the SYS_HANDLE in my version. I can easily fix the lock literals too. Can you provide a list of other problem items? I will fix them and we can shift to using expressionType().

#69 Updated by Constantin Asofiei about 11 years ago

  • File ca_upd20130312f.zip added

The code looks fine. I have changes in the assignments.rules and I prefer if you don't include that file (it is just whitespace changes).

OK, I'll remove it, but please include the format fix; it bugs me when I see unformatted code (my pattern matching neurons get upset <g>).

In regard to the base_structure changes:

This comment seems out of place:

Yes, is out of place, as it was added before the test on the last fallback rule.

I have fixed the SYS_HANDLE in my version. I can easily fix the lock literals too. Can you provide a list of other problem items? I will fix them and we can shift to using expressionType().

Beside the TABLE, BUFFER, WORK_TABLE, TEMP_TABLE, KW_BUFFER and KW_TABLE nodes, I don't know of any other nodes (I got tired of building an "ignore tokens" rule as the server folders kept failing on conversion, so I added the checks on the last fallback rule). What I can do is remove the last fallback checks (the _POLY-related checks ), adjust the ECW to log a message instead of throwing exception, and run the full server folder conversion again (but I will do this tomorrow, I don't want to stop the server folder conversion again).

#70 Updated by Constantin Asofiei about 11 years ago

PS: the conversion regression testing has passed. The only changes are related to:

def var d as dec.
d = decimal(?).

which now converts to d.assign(new decimal(new character())) instead of d.assign(new decimal()). But this is OK, as the signature for the decimal function is character.

#71 Updated by Greg Shah about 11 years ago

OK, please go ahead and check it in. You can leave the assignments.rules there. I will deal with the merge.

What I can do is remove the last fallback checks (the _POLY-related checks ), adjust the ECW to log a message instead of throwing exception, and run the full server folder conversion again (but I will do this tomorrow, I don't want to stop the server folder conversion again).

Yes, this will be very helpful! Thanks.

#72 Updated by Constantin Asofiei about 11 years ago

  • File deleted (ca_upd20130312f.zip)

#73 Updated by Constantin Asofiei about 11 years ago

Attached was committed to bzr revision 10282.

#74 Updated by Constantin Asofiei about 11 years ago

Greg, in the server project there are failures related to the fact that _POLY is used as an operand. The failed operators currently are + and BEGINS. Does your ECW changes handle these too?

#75 Updated by Constantin Asofiei about 11 years ago

The attached update fixes this:
  1. wrapping of handle for a RUN ... IN DYNAMIC-FUNCTION call
  2. varargs signature problem + SUBSTITUTE signature
  3. never wrap the modes parameter for the ControlFlowOPs.invoke*WithMode calls.

I'm putting this through conversion regression testing.

Cases that still need to be solved:

def temp-table tt1 field f1 as int.
def var ch as char.
def var k as int.
def var dd as dec.

ch = h:buffer-compare(h, 'case-sensitive':u, ch, entry(k, ch, chr(1))).
dd = dd + dynamic-function("bar").
if ch begins dynamic-function("foo") then message "bla".
if not h:buffer-value then message "bla".

tt1.f1 = if dynamic-function("bar") then dynamic-function("func1") else 0.

#76 Updated by Constantin Asofiei about 11 years ago

And the update...

#77 Updated by Greg Shah about 11 years ago

I think there should be some code like this in the example:

def var h as handle.
h = tt1:handle.

I will look into these cases.

The code changes are fine.

#78 Updated by Constantin Asofiei about 11 years ago

Greg Shah wrote:

I think there should be some code like this in the example:

The code should be h = temp-table tt1:handle, but for conversion-related tests, I don't necessarily care for the 4GL code to be runnable in terms of "on attribute/method access, the handle should contain the correct resource", as 4GL doesn't care at compile-time what resource the handle refers when the attr/mthd is invoked.

#79 Updated by Constantin Asofiei about 11 years ago

Had a problem, forgot to initialize the ref var in base_structure.xml's parent.type == prog.kw_run run (without this, the ref would leak). I'm running conversion regression testing again.

#80 Updated by Greg Shah about 11 years ago

Here is the code that is generated by my current implementation:

ch.assign(new character(), handle.unwrapBuffer(h).bufferCompare(h, "case-sensitive", ch, entry(k, ch, chr(1))));

I think the only issue here is where the new character() wrapper is emitted.

dd.assign(plus(dd, ControlFlowOps.invokeDynamicFunction("bar")));
if (_begins(ch, ControlFlowOps.invokeDynamicFunction("foo")))
{
   message("bla");
}

If I understand correctly, we need to wrap the operator parms.

if (_not(new logical(handle.unwrapBufferField(h).value())))
{
   message("bla");
}

I think this one is working now (with my current code changes).

tt1.setF1(new integer((new logical(new logical(new logical(ControlFlowOps.invokeDynamicFunction("bar"))))).booleanValue() ? new integer(new integer(ControlFlowOps.invokeDynamicFunction("func1"))) : new integer(0)));

We seem to be heavily over-wrapping this case. It is hard to know where to begin with this one.

#81 Updated by Constantin Asofiei about 11 years ago

I think the only issue here is where the new character() wrapper is emitted.

I think you are right.

If I understand correctly, we need to wrap the operator parms.

Correct, all operator parms should be handled.

I think this one is working now (with my current code changes).

Your version is way better than what is currently emitting (castLogical() in the then branch is emitted wrong):

tt1.setF1((ControlFlowOps.invokeDynamicFunction("bar").castLogical()).booleanValue() ? ControlFlowOps.invokeDynamicFunction("func1").castLogical() : new integer(0));

#82 Updated by Greg Shah about 11 years ago

In regard to the removal of the castLogical(): currently we bypass the emit of the .class instance in this case (when the logical_test function returns true). What is the idea there? Isn't it still a good idea to always emit the type of the function being called if we know it?

I don't want to break that code, but basically we now detect those same logical_test cases, but we do it in other locations (e.g. in expressions.rules or operators.rules). I have also added some cases (e.g. WHEN clauses). All those locations implement generic logical wrapping of results for the indeterminate _POLY nodes when used in these logical test (control flow or operator) use cases. When I check these locations, I disable context analysis, so that the _POLY nodes will return BDT instead of the inferred type.

#83 Updated by Constantin Asofiei about 11 years ago

In regard to the removal of the castLogical(): currently we bypass the emit of the .class instance in this case (when the logical_test function returns true). What is the idea there? Isn't it still a good idea to always emit the type of the function being called if we know it?

I didn't want to emit the .class parameter for the type of the function, because in this case (and others, like in assignments) we don't care if the function's return type can be determined or not. In this case, we care only of wrapping the returned value in a logical instance.

#84 Updated by Greg Shah about 11 years ago

How about this: if these cases are already known to return logical, then I will emit the logical.class and not wrap it. In all other cases, I will not emit the .class and I will wrap it.

#85 Updated by Constantin Asofiei about 11 years ago

OK, good idea.

#86 Updated by Greg Shah about 11 years ago

I don't fully understand the purpose of the "ignorecast" annotation:

            <!-- If involved in logical test, do not emit special APIs, 
                 castLogical() will be emitted later on. The .class reference
                 will be emitted only if not involved in a logical test. -->
            <action>emitDynFunCls = false</action>

            <rule>isNote("casttype") and !isNote("ignorecast")
               <action>putNote("ignorecast", true)</action>   <----------------- this not protected by logical_test

               <rule>!evalLib("logical_test", copy)
                  <action>emitDynFunCls = true</action>
               </rule>
            </rule>

I don't understand why if casttype is there and ignorecast is not, that ignorecast is always added. It seems to cause casting to be disabled, but that means that casting is always disabled, right?

#87 Updated by Greg Shah about 11 years ago

I guess what is happening is that casttype is really meant to be used for the .class emit on a DYNAMIC-FUNCTION invocation (and it is also used in ECW). But the same annotation is used for non DYNAMIC-FUNCTION built-ins and then it is honored. Do I understand correctly?

#88 Updated by Constantin Asofiei about 11 years ago

Yes, you understand correctly. The idea here was this: for the KW_DYN_FUNC node, we don't need to the ControlFlowOps.invokeDynamicFunction* call with a Java cast node (this is what "casttype" does in builtin_functions.rules, emits a Java cast node for other builtins). From the line you mention, the emitDynFunCls variable is in charge of emitting the .class reference.

More, the !isNote("ignorecast") test is needed because annotations/functions might decide that the dynamic-function call doesn't need its type set.

#89 Updated by Constantin Asofiei about 11 years ago

This is the good version, committed to bzr revision 10284.

LE: it passed conversion regression testing.

#90 Updated by Greg Shah about 11 years ago

What I can do is remove the last fallback checks (the _POLY-related checks ), adjust the ECW to log a message instead of throwing exception, and run the full server folder conversion again (but I will do this tomorrow, I don't want to stop the server folder conversion again).

When do you think you will have this available?

I have already been going through the parser to try to identify things to add. But there is too much there to do at once. A better list from the server code will be very helpful.

#91 Updated by Constantin Asofiei about 11 years ago

When do you think you will have this available?

The server conversion is in the Base Structure phase, with a few folders remaining. I'll have the list in the next 30min-1h.

#92 Updated by Constantin Asofiei about 11 years ago

The complete list is this (logged from where the ECW.expressionType would throw exception otherwise):

Unknown node type AGGREGATE
Unknown node type ATTR_RECID
Unknown node type ATTR_ROWID
Unknown node type BUFFER
Unknown node type EXCLUSIVE_LOCK_LITERAL
Unknown node type KW_BUFFER
Unknown node type KW_FIRST
Unknown node type KW_FRAME
Unknown node type KW_INPUT
Unknown node type KW_LAST
Unknown node type NO_LOCK_LITERAL
Unknown node type QUERY
Unknown node type RECORD_PHRASE
Unknown node type STREAM
Unknown node type SYS_HANDLE
Unknown node type TABLE
Unknown node type TEMP_TABLE
Unknown node type WORK_TABLE

Do you need me to get the function for each case? (there are warnings for this in the logs, which I can use to identify)

#93 Updated by Greg Shah about 11 years ago

Constantin: this case:

ch.assign(new character(), handle.unwrapBuffer(h).bufferCompare(h, "case-sensitive", ch, entry(k, ch, chr(1))));

is caused by the fact that the METH_LOGICAL node for KW_BUF_COMP does not have a peerid. This means that the normal peering support is broken when the code in literals.rules creates the wrapper constructor. I started to work my way through the maze that is the methods_attributes.rules but I think this one would be best handled by you.

I have fixed all the other problems from note 75 except for the over-wrapping case. I am looking at that next.

#94 Updated by Greg Shah about 11 years ago

This update (hopefully) resolves everything left on this task except for the issue mentioned in note 93. Here is a summary:

1. Wrap of logical tests for all indeterminate _POLY cases.
2. Wrap parameters to operators when the operand type is different than expected.
3. Wrap assignment of BDT to fields.
4. Major improvements in ECW to handle a much larger range of expression typing and many "near expression" nodes that can appear in expressions but are not really expression elements.
5. Integration of the parameterTypeName() functionality into expressionType() and replacement in the base_structure.xml call sites.
6. implicit_input analysis support (untested).
7. Wrap of unknown value literal when assigned to an attribute.

This is in conversion testing now. It is such a big change, I am worried there will be some breakage.

#95 Updated by Greg Shah about 11 years ago

I have fixed some issues (found in my manual testing) with the update.

The conversion testing didn't ever complete. In fact, I was running without my changes and it failed due to some very disturbing issues on devsrv01 (it looked like filesystem issues). I re-ran conversion this morning (without my changes) and it has succeeded now. I am now converting using the changes.

Constantin: would you please do a code review of this update?

#96 Updated by Constantin Asofiei about 11 years ago

Constantin: would you please do a code review of this update?

I'm looking at it now.

#97 Updated by Greg Shah about 11 years ago

Here is the final version, with some fixes necessary to convert Majic. Please see #2091 for a Majic change that must be applied.

Please note that the following diffs are generated.

diff -r generated/20130314a/src/aero/timco/majic/gl/PeriodsProgram.java generated/20130314b/src/aero/timco/majic/gl/PeriodsProgram.java
550c550
<          return isNotEqual(fFindFrame.getScreenValue(fFindFrame.widgetStartMo()), "");
---
>          return isNotEqual(fFindFrame.getStartMo(), "");
564c564
<          return isNotEqual(fFindFrame.getScreenValue(fFindFrame.widgetStartYr()), "");
---
>          return isNotEqual(fFindFrame.getStartYr(), "");
diff -r generated/20130314a/src/aero/timco/majic/job/OtanalysisR.java generated/20130314b/src/aero/timco/majic/job/OtanalysisR.java
1580c1580
<          return concat("INVALID SO #.  PLEASE ENTER SO # >= ", valueOf(fInputsFrame.getScreenValue(fInputsFrame.widgetSCoNum())));
---
>          return concat("INVALID SO #.  PLEASE ENTER SO # >= ", valueOf(fInputsFrame.getSCoNum()));
1608c1608
<          return concat("INVALID EMPLOYEE #.  PLEASE ENTER EMPLOYEE # >= ", valueOf(fInputsFrame.getScreenValue(fInputsFrame.widgetSEmpNum())));
---
>          return concat("INVALID EMPLOYEE #.  PLEASE ENTER EMPLOYEE # >= ", valueOf(fInputsFrame.getSEmpNum()));
1636c1636
<          return concat("INVALID DATE.  PLEASE ENTER DATE >= ", valueOf(fInputsFrame.getScreenValue(fInputsFrame.widgetSDate())));
---
>          return concat("INVALID DATE.  PLEASE ENTER DATE >= ", valueOf(fInputsFrame.getSDate()));
diff -r generated/20130314a/src/aero/timco/majic/po/BPoitem.java generated/20130314b/src/aero/timco/majic/po/BPoitem.java
1009c1009
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), "");
---
>                                              return isEqual(fListFrame.getPurchaseOrderNumber(), "");
1155c1155
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), "");
---
>                                                 return isEqual(fListFrame.getPurchaseOrderNumber(), "");
1851c1851
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getPurchaseOrderNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/po/BVend.java generated/20130314b/src/aero/timco/majic/po/BVend.java
941c941
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetVendorNumber()), "");
---
>                                              return isEqual(fListFrame.getVendorNumber(), "");
1071c1071
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetVendorNumber()), "");
---
>                                                 return isEqual(fListFrame.getVendorNumber(), "");
1712c1712
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetVendorNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getVendorNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/so/BJob.java generated/20130314b/src/aero/timco/majic/so/BJob.java
1040c1040
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetTimcoOperation()), "");
---
>                                              return isEqual(fListFrame.getTimcoOperation(), "");
1201c1201
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetTimcoOperation()), "");
---
>                                                 return isEqual(fListFrame.getTimcoOperation(), "");
1912c1912
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetTimcoOperation()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getTimcoOperation(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/so/BJrte.java generated/20130314b/src/aero/timco/majic/so/BJrte.java
1048c1048
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                              return isEqual(fListFrame.getCustomerOrderNumber(), "");
1218c1218
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                                 return isEqual(fListFrame.getCustomerOrderNumber(), "");
1910c1910
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getCustomerOrderNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/so/BSochkoff.java generated/20130314b/src/aero/timco/majic/so/BSochkoff.java
932c932
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                              return isEqual(fListFrame.getCustomerOrderNumber(), "");
1057c1057
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                                 return isEqual(fListFrame.getCustomerOrderNumber(), "");
1693c1693
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getCustomerOrderNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/so/SPoper.java generated/20130314b/src/aero/timco/majic/so/SPoper.java
1322c1322
<                                                 message(concat("How Malfunctioned Code ", fOperFrame.getScreenValue(fOperFrame.widgetTHowMal()), new character(" does not exists in Master")));
---
>                                                 message(plus(concat("How Malfunctioned Code ", fOperFrame.getTHowMal()), new character(" does not exists in Master")));
diff -r generated/20130314a/src/aero/timco/majic/train/BTrn.java generated/20130314b/src/aero/timco/majic/train/BTrn.java
958c958
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), "");
---
>                                              return isEqual(fListFrame.getEmployeeNumber(), "");
1099c1099
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), "");
---
>                                                 return isEqual(fListFrame.getEmployeeNumber(), "");
1751c1751
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getEmployeeNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/AcctLst.java generated/20130314b/src/aero/timco/majic/util/AcctLst.java
588c588
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetAccount()), "");
---
>                                              return isEqual(fListFrame.getAccount(), "");
677c677
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetAccount()), "");
---
>                                                 return isEqual(fListFrame.getAccount(), "");
959c959
<                                                       if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetAccount()), ""), matchesList("!yes,!true,*", "")))
---
>                                                       if (_and(isEqual(fListFrame.getAccount(), ""), matchesList("!yes,!true,*", "")))
diff -r generated/20130314a/src/aero/timco/majic/util/BAcct.java generated/20130314b/src/aero/timco/majic/util/BAcct.java
949c949
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetAccount()), "");
---
>                                              return isEqual(fListFrame.getAccount(), "");
1083c1083
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetAccount()), "");
---
>                                                 return isEqual(fListFrame.getAccount(), "");
1728c1728
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetAccount()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getAccount(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BCall.java generated/20130314b/src/aero/timco/majic/util/BCall.java
936c936
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), "");
---
>                                              return isEqual(fListFrame.getEmployeeNumber(), "");
1063c1063
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), "");
---
>                                                 return isEqual(fListFrame.getEmployeeNumber(), "");
1701c1701
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getEmployeeNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BCo.java generated/20130314b/src/aero/timco/majic/util/BCo.java
937c937
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                              return isEqual(fListFrame.getCustomerOrderNumber(), "");
1065c1065
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                                 return isEqual(fListFrame.getCustomerOrderNumber(), "");
1704c1704
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getCustomerOrderNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BCtype.java generated/20130314b/src/aero/timco/majic/util/BCtype.java
1023c1023
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetContractType()), "");
---
>                                              return isEqual(fListFrame.getContractType(), "");
1167c1167
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetContractType()), "");
---
>                                                 return isEqual(fListFrame.getContractType(), "");
1840c1840
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetContractType()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getContractType(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BCust.java generated/20130314b/src/aero/timco/majic/util/BCust.java
941c941
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerNumber()), "");
---
>                                              return isEqual(fListFrame.getCustomerNumber(), "");
1069c1069
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerNumber()), "");
---
>                                                 return isEqual(fListFrame.getCustomerNumber(), "");
1708c1708
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getCustomerNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BDrsn.java generated/20130314b/src/aero/timco/majic/util/BDrsn.java
1007c1007
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetReason()), "");
---
>                                              return isEqual(fListFrame.getReason(), "");
1150c1150
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetReason()), "");
---
>                                                 return isEqual(fListFrame.getReason(), "");
1843c1843
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetReason()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getReason(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BEmp.java generated/20130314b/src/aero/timco/majic/util/BEmp.java
947c947
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), "");
---
>                                              return isEqual(fListFrame.getEmployeeNumber(), "");
1077c1077
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), "");
---
>                                                 return isEqual(fListFrame.getEmployeeNumber(), "");
1718c1718
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetEmployeeNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getEmployeeNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BHow.java generated/20130314b/src/aero/timco/majic/util/BHow.java
921c921
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetHowMal()), "");
---
>                                              return isEqual(fListFrame.getHowMal(), "");
1046c1046
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetHowMal()), "");
---
>                                                 return isEqual(fListFrame.getHowMal(), "");
1682c1682
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetHowMal()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getHowMal(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BJob.java generated/20130314b/src/aero/timco/majic/util/BJob.java
939c939
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetJob()), "");
---
>                                              return isEqual(fListFrame.getJob(), "");
1067c1067
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetJob()), "");
---
>                                                 return isEqual(fListFrame.getJob(), "");
1706c1706
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetJob()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getJob(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BPck.java generated/20130314b/src/aero/timco/majic/util/BPck.java
933c933
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetPackNumber()), "");
---
>                                              return isEqual(fListFrame.getPackNumber(), "");
1061c1061
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetPackNumber()), "");
---
>                                                 return isEqual(fListFrame.getPackNumber(), "");
1700c1700
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetPackNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getPackNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BPo.java generated/20130314b/src/aero/timco/majic/util/BPo.java
943c943
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), "");
---
>                                              return isEqual(fListFrame.getPurchaseOrderNumber(), "");
1071c1071
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), "");
---
>                                                 return isEqual(fListFrame.getPurchaseOrderNumber(), "");
1710c1710
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getPurchaseOrderNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BSft.java generated/20130314b/src/aero/timco/majic/util/BSft.java
932c932
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetShift()), "");
---
>                                              return isEqual(fListFrame.getShift(), "");
1057c1057
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetShift()), "");
---
>                                                 return isEqual(fListFrame.getShift(), "");
1693c1693
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetShift()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getShift(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BShto.java generated/20130314b/src/aero/timco/majic/util/BShto.java
940c940
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetDropShipNo()), "");
---
>                                              return isEqual(fListFrame.getDropShipNo(), "");
1067c1067
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetDropShipNo()), "");
---
>                                                 return isEqual(fListFrame.getDropShipNo(), "");
1705c1705
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetDropShipNo()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getDropShipNo(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BSite.java generated/20130314b/src/aero/timco/majic/util/BSite.java
938c938
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetSiteNumber()), "");
---
>                                              return isEqual(fListFrame.getSiteNumber(), "");
1065c1065
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetSiteNumber()), "");
---
>                                                 return isEqual(fListFrame.getSiteNumber(), "");
1703c1703
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetSiteNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getSiteNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/BSreq.java generated/20130314b/src/aero/timco/majic/util/BSreq.java
944c944
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), "");
---
>                                              return isEqual(fListFrame.getPurchaseOrderNumber(), "");
1073c1073
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), "");
---
>                                                 return isEqual(fListFrame.getPurchaseOrderNumber(), "");
1713c1713
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetPurchaseOrderNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getPurchaseOrderNumber(), ""), matchesList("!yes,!true,*", "yes")))
diff -r generated/20130314a/src/aero/timco/majic/util/CoeLst.java generated/20130314b/src/aero/timco/majic/util/CoeLst.java
610c610
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                              return isEqual(fListFrame.getCustomerOrderNumber(), "");
699c699
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                                 return isEqual(fListFrame.getCustomerOrderNumber(), "");
998c998
<                                                       if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), ""), matchesList("!yes,!true,*", "")))
---
>                                                       if (_and(isEqual(fListFrame.getCustomerOrderNumber(), ""), matchesList("!yes,!true,*", "")))
diff -r generated/20130314a/src/aero/timco/majic/util/CoLst.java generated/20130314b/src/aero/timco/majic/util/CoLst.java
631c631
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                              return isEqual(fListFrame.getCustomerOrderNumber(), "");
723c723
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), "");
---
>                                                 return isEqual(fListFrame.getCustomerOrderNumber(), "");
1034c1034
<                                                       if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetCustomerOrderNumber()), ""), matchesList("!yes,!true,*", "")))
---
>                                                       if (_and(isEqual(fListFrame.getCustomerOrderNumber(), ""), matchesList("!yes,!true,*", "")))
diff -r generated/20130314a/src/aero/timco/majic/util/DstLst.java generated/20130314b/src/aero/timco/majic/util/DstLst.java
567c567
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetDropShipNo()), "");
---
>                                              return isEqual(fListFrame.getDropShipNo(), "");
649c649
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetDropShipNo()), "");
---
>                                                 return isEqual(fListFrame.getDropShipNo(), "");
907c907
<                                                       if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetDropShipNo()), ""), matchesList("!yes,!true,*", "")))
---
>                                                       if (_and(isEqual(fListFrame.getDropShipNo(), ""), matchesList("!yes,!true,*", "")))
diff -r generated/20130314a/src/aero/timco/majic/util/JobLst.java generated/20130314b/src/aero/timco/majic/util/JobLst.java
627c627
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetJob()), "");
---
>                                              return isEqual(fListFrame.getJob(), "");
719c719
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetJob()), "");
---
>                                                 return isEqual(fListFrame.getJob(), "");
1004c1004
<                                                       if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetJob()), ""), matchesList("!yes,!true,*", "")))
---
>                                                       if (_and(isEqual(fListFrame.getJob(), ""), matchesList("!yes,!true,*", "")))
diff -r generated/20130314a/src/aero/timco/majic/util/Misreq.java generated/20130314b/src/aero/timco/majic/util/Misreq.java
947c947
<                                              return isEqual(fListFrame.getScreenValue(fListFrame.widgetReqNumber()), "");
---
>                                              return isEqual(fListFrame.getReqNumber(), "");
1077c1077
<                                                 return isEqual(fListFrame.getScreenValue(fListFrame.widgetReqNumber()), "");
---
>                                                 return isEqual(fListFrame.getReqNumber(), "");
1718c1718
<                                                             if (_and(isEqual(fListFrame.getScreenValue(fListFrame.widgetReqNumber()), ""), matchesList("!yes,!true,*", "yes")))
---
>                                                             if (_and(isEqual(fListFrame.getReqNumber(), ""), matchesList("!yes,!true,*", "yes")))

All of these cases are about:

1. Detection of string concatenation and the subsequent use of getScreenValue() instead of BDT. This should be OK.
2. Detection of incompatible types in the comparison operators (which take BDT). A standalone testcase:

def var i as int.

update i with frame f0.

message input i eq "".

No matter how you run this, the result is always "no". You can just press F1 (with no edits) OR if you enter any number (including 0), but the comparison is always false in Progress.

I am not sure of the implication of our changes, but I am worried about this one. What do you think?

#98 Updated by Constantin Asofiei about 11 years ago

2. Detection of incompatible types in the comparison operators (which take BDT). A standalone testcase:

def var i as int.

update i with frame f0.

message input i eq "".

If you declare the variable like def var i as int format ">>>". and just press F1, then you will get yes (this is because the content of the widget is really empty). Note that when the widget is non-character, our frame.get#Widget# calls will not return what is actually in the buffer. In your example, if the var is formatted as I mentioned, our widget value getter call will return 0, and not the empty string. So, for these cases of non-char widgets the getScreenValue is mandatory, but we may get away with it if the var's format does not allow the user to delete all the content of the widget (i.e. without the format I mentioned, char 0 will always be shown, the user can't get rid of it).

#99 Updated by Greg Shah about 11 years ago

Questions:

1. The new way is more correct?
2. The old way may be working (or may not) depending on the format of the var?
3. We have a bug in our getter methods? Or is there some good reason for them to work that way?

#100 Updated by Constantin Asofiei about 11 years ago

1. The new way is more correct?
2. The old way may be working (or may not) depending on the format of the var?

Unless I read the diff wrong, the new way is the way of eliminating the getScreenValue and using the widget getter. If this is the case and getScreenValue is eliminated, to me it looks like the new way is wrong, especially in tests like INPUT i eq "".

3. We have a bug in our getter methods? Or is there some good reason for them to work that way?

The getter as in frame.getI() will return a type depending on widget value. I.e. for integer widgets, will return the value zero, even if what is actually on the screen for this widget is the empty string. This is correct in terms of the duality of the INPUT function, which can return char values plus values of widget's data type.

#101 Updated by Greg Shah about 11 years ago

Unless I read the diff wrong, the new way is the way of eliminating the getScreenValue and using the widget getter. If this is the case and getScreenValue is eliminated, to me it looks like the new way is wrong, especially in tests like INPUT i eq "".

You're right. I was not looking carefully enough at the diff. I had assumed (bad idea) that the change was one that came from my "improvements" to the type analysis and coercion. Instead, I have regressed it!

OK, I'll fix it. Thanks for thinking about this more clearly than I did.

#102 Updated by Constantin Asofiei about 11 years ago

Review results:
1. This comment in base_structure.xml:379

            <!-- all other cases - check if the type of the parameter's expression can be 
                 determined. do this only if this is a _POLY case - this is needed because 
                 currently ECW fails if the passed expression contains SYS_HANDLE, 
                 NO_LOCK_LITERAL, etc nodes. -->

should be:
            <!-- all other cases - check if the type of the parameter's expression can be 
                 determined. -->

2. in expressions.rules:308 - I'm not sure why you are thinking to remove the WHEN. Can you share the case you have in mind?

3. literals.rules:402 - the func_poly is needed to allow conversion of unknown literals to new unknown, when passed to DYNAMIc-FUNCTION/sUPER calls. Without this, the following will not convert properly:

dynamic-function("foo", ?, ?, ?).
super(?, ?, ?).

I've just tested and it seems in converted code for the super(?, ?, ?) the unknown literals are not emitted, so we still have an issue here. The dynamic-function call was converted OK.

I was warried that the removal of the ref.type == prog.func_poly etc tests in base_structure.xml would wrap the parameter expressions with determined type too, but I've checked some simple examples and it seems to be good.

#103 Updated by Greg Shah about 11 years ago

Good feedback.

2. in expressions.rules:308 - I'm not sure why you are thinking to remove the WHEN. Can you share the case you have in mind?

I'm thinking that this code:

ENABLE i WHEN true WITH FRAME f0.

is the equivalent of this:

ENABLE i WITH FRAME f0.

And this:

ENABLE j i WHEN false WITH FRAME f0.

is the equivalent to this:

ENABLE j WITH FRAME f0.

Either way, it seems some simplification of the results could occur. But I wasn't going to spend time on it. So I just put the note there for future reference.

I've just tested and it seems in converted code for the super(?, ?, ?) the unknown literals are not emitted, so we still have an issue here.

What should this case look like when emitted properly? It emits like this right now:

returnNormal((integer) ControlFlowOps.runSuper("bogus"));

I'm not sure where the parameters have gone/why they didn't emit.

#104 Updated by Constantin Asofiei about 11 years ago

2. in expressions.rules:308 - I'm not sure why you are thinking to remove the WHEN. Can you share the case you have in mind?

I'm thinking that this code:

Ok, I understand now, I was thinking of the WHEN in a CASE ... WHEN construct. Your notes make sense.

I'm not sure where the parameters have gone/why they didn't emit.

The parameter should be as (yes, unknown is OK here):

returnNormal((integer) ControlFlowOps.runSuper("bogus", new unknown(), new unknown(), new unknown()));

I can look at this, together with note 93.

#105 Updated by Greg Shah about 11 years ago

This version fixes both the regression in Majic (hopefully in all cases) as well as the super(?, ?, ?) case. It resolves the other notes in the code review and it is merged to bzr revision 10285.

I am putting this through conversion testing now.

#106 Updated by Constantin Asofiei about 11 years ago

About note 93. See these two ASTs:

   assignment [ASSIGNMENT] @0:0
      : [COLON] @6:2
         h [VAR_HANDLE] @6:1
         buffer-compare [METH_LOGICAL] @6:3
            expression [EXPRESSION] @0:0
               'bar' [STRING] @6:18
   assignment [ASSIGNMENT] @0:0
      = [ASSIGN] @7:3
         l [VAR_LOGICAL] @7:1
         expression [EXPRESSION] @0:0
            : [COLON] @7:6
               h [VAR_HANDLE] @7:5
               buffer-compare [METH_LOGICAL] @7:7
                  'bar' [STRING] @7:22

for these two statements (I know they don't make 4GL sens, but is simple to understand where the problem is):
h:buffer-compare('bar').
l = h:buffer-compare('bar').

In each case, a peerid is put at the STRING node. In the first case, the literal parameter is wrapped in an EXPRESSION node, which is used as the peer for the constructor node. In the second case, there is no parent EXPRESSION node for the literal parameter, so the constructor node can't hook itself in the proper place.

To me, this is again an issue related to the EXPRESSION node problem... Not sure how to dance around it yet.

#107 Updated by Constantin Asofiei about 11 years ago

LE: I see the problem, is related to the fact that when move_params is called by methods_attributes.rules in the second case, the STRING node in the .p.ast has a peerid for the STRING node in the .p.jast, not for the c'tor. Adding this code after jref2 is determined in move_params:

               <!-- check if the jref2 has a CONTRUCTOR parent with no peerid -->
               <rule>nref.type != prog.expression          and
                     jref2.parent.type == java.constructor and
                     !jref2.parent.isAnnotation("peerid")
                  <action>jref2 = jref2.parent</action> 
               </rule>

solves this issue, but it feels like addressing the symptom, not the problem. I'll see if the constructor node can link itself with the literal, in case the EXPRESSION is missing.

#108 Updated by Greg Shah about 11 years ago

The peerid that is added at the STRING node is done after any wrapping would have to take place. I am more concerned that the METH_LOGICAL has no peerid set. When we try to wrap the STRING node in literals.rules, we have no place to attach it because the closestPeerId finds a peer node that is associated with a point higher up the tree. Thus the new character() attaches to the same level as the METH_LOGICAL. The METH_LOGICAL is always there. The EXPRESSION is not always there. So it is optimal if the METH_LOGICAL has the peerid, even if it is a "temporary" one.

#109 Updated by Constantin Asofiei about 11 years ago

Hmm... I see now what you mean. Note that in methods_attributes.rules, when we convert a METH_ node, the this in the ascent-rules references the COLON node, not the METH_ node. And AFAIK createPeerAst sets the peerid for the current processed node (the this node), right?

More, I think this is because we handle the METH_LOGICAL node on the ascent rules, and at the time all its parameter children are processed, the COLON node (for the METH_ call) can't have a peerid as it wasn't processed yet.

#110 Updated by Greg Shah about 11 years ago

And AFAIK createPeerAst sets the peerid for the current processed node (the this node), right?

Yes.

I think this is because we handle the METH_LOGICAL node on the ascent rules, and at the time all its parameter children are processed, the COLON node (for the METH_ call) can't have a peerid as it wasn't processed yet.

Yes, I think you have it right.

#111 Updated by Constantin Asofiei about 11 years ago

With this in mind, note 93 looks like an effect of what we've discussed in the last 3 notes. I'll put the fix at note 107 through conversion regression testing, as I don't see another way to fix this.

#112 Updated by Greg Shah about 11 years ago

The ges_upd20130314d.zip passed conversion testing and checked in as bzr revision 10287.

#113 Updated by Greg Shah about 11 years ago

Remind me again: how much of the inversion problem is due to the fact that we use statics in handle for unwrapping instead of instance methods? I know you have told me before but I seem to have a mental block on the issue.

#114 Updated by Constantin Asofiei about 11 years ago

None at all. To make it generate instance method calls, only a few changes are needed in methods_attributes.rules (to emit a java.method_call instead of java.static_method_call in two places, where handle.unwrap is emitted plus to change the jref2.text.startsWith("handle.unwrap") to jref2.text.startsWith("unwrap") (or change the test to use annotations, to not get confused with some other method names). The other change is in handle.java, as all the handle.unwrap* methods need to be changed from static to instance and a last one is in convert/dereference.rules needs to change from handle.unwrapDereferenceable to unwrapDereferenceable.

The only problem is I need to double-check the DB_REF_NON_STATIC tests, to be sure everything converts nicely (plus my handle chaining tests).

#115 Updated by Constantin Asofiei about 11 years ago

The attached update has passed conversion regression testing.

#116 Updated by Constantin Asofiei about 11 years ago

Greg,

I've started per-folder conversion of the server project and I got this regression:

def var h as handle.
put unformatted "<" + h:label + ">" + h:buffer-value + ",".

gets me this PutExpression:
   private class PutExpr0
   extends GenericExpression
   {
      public BaseDataType resolve()
      {
         return concat(new character("<", handle.unwrapCommonField(h).getLabel(), ">", new character(handle.unwrapBufferField(h).value()), ","));
      }
   }

#117 Updated by Greg Shah about 11 years ago

In regard to your update in note 115, check it in and distribute it.

In regard to note 116, I don't see where the problem is. What is wrong?

#118 Updated by Constantin Asofiei about 11 years ago

The problem is that it wraps all parameters passed to the concat API call, as in concat(new character("<", handle.unwrapCommonField(h).getLabel(), ">", new character(handle.unwrapBufferField(h).value()), ",")); - see how the character c'tor gets a parameter list...

#119 Updated by Constantin Asofiei about 11 years ago

In regard to your update in note 115, check it in and distribute it.

Committed to bzr revision 10288.

#120 Updated by Constantin Asofiei about 11 years ago

Urgh... I should have waited 5 more minutes. My ca_upd20130314a.zip update regresses this test, during conversion (LE: of the server folders):

def var h as handle.
def var ch as char.
if not h:buffer-field(ch):buffer-value  then ch = "u" + ch.

#121 Updated by Greg Shah about 11 years ago

In regard to note 116, I wonder where the extra wrapping is coming from. The h:label case is an ATTR_CHAR so there is no ambiguity there nor is there any conflict with the containing operator.

I will look into this one.

#122 Updated by Constantin Asofiei about 11 years ago

This update fixes case at note 120. I'm putting this back into conversion regression testing and into server folder conversion, and I'd like to release it after the server folders have finished (just to make sure is really looks stable).

#123 Updated by Greg Shah about 11 years ago

I don't see a problem with this, but honestly, I don't really fully understand the code in move_params. If it works, check it in.

#124 Updated by Constantin Asofiei about 11 years ago

The JAST at the time of the move_params is called, for the h:buffer-field(ch):buffer-value part of the note 120's test, looks like this:

                            <ast col="0" id="3874060501040" line="0" text="logical" type="CONSTRUCTOR">
                              <ast col="0" id="3874060501042" line="0" text="ch" type="REFERENCE">
                                <annotation datatype="java.lang.Long" key="peerid" value="3869765533754"/>
                              </ast>
                              <ast col="0" id="3874060501043" line="0" text="value" type="METHOD_CALL">
                                <annotation datatype="java.lang.Long" key="peerid" value="3869765533752"/>
                                <ast col="0" id="3874060501045" line="0" text="handle.unwrapBufferField" type="STATIC_METHOD_CALL">
                                  <annotation datatype="java.lang.Long" key="peerid" value="3869765533749"/>
                                  <ast col="0" id="3874060501046" line="0" text="bufferField" type="METHOD_CALL">
                                    <annotation datatype="java.lang.Long" key="peerid" value="3869765533749"/>
                                    <ast col="0" id="3874060501044" line="0" text="handle.unwrapBuffer" type="STATIC_METHOD_CALL">
                                      <ast col="0" id="3874060501041" line="0" text="h" type="REFERENCE">
                                        <annotation datatype="java.lang.Long" key="peerid" value="3869765533750"/>
                                      </ast>
                                    </ast>
                                  </ast>
                                </ast>
                              </ast>
                            </ast>

The move_params code just wants to fix the JAST and put every param in its place, and I'm slowly starting to hate the code I've added in methods_attributes.rules. I really need to find some time and check if the AST for chaining cases can't be fixed, to let everything emit naturally.

#125 Updated by Constantin Asofiei about 11 years ago

PS: sorry, the JAST is the wrong one, I'll update the previous note shortly.

#126 Updated by Greg Shah about 11 years ago

Your recent update also causes this issue:

./buffer_value_context_analysis.p
ERROR:
java.lang.RuntimeException: ERROR!  Active Rule:
-----------------------
      RULE REPORT      
-----------------------
Rule Type :   WALK
Source AST:  [ : ] BLOCK/STATEMENT/KW_MSG/CONTENT_ARRAY/EXPRESSION/PLUS/COLON/ @12:24 {12884901990}
Copy AST  :  [ : ] BLOCK/STATEMENT/KW_MSG/CONTENT_ARRAY/EXPRESSION/PLUS/COLON/ @12:24 {12884901990}
Condition :  jref1.graft(jref2)
Loop      :  false
--- END RULE REPORT ---

        at com.goldencode.p2j.pattern.PatternEngine.run(PatternEngine.java:732)
        at com.goldencode.p2j.convert.ConversionDriver.processTrees(ConversionDriver.java:948)
        at com.goldencode.p2j.convert.ConversionDriver.back(ConversionDriver.java:852)
        at com.goldencode.p2j.convert.ConversionDriver.main(ConversionDriver.java:1729)
Caused by: java.lang.StackOverflowError
        at com.goldencode.ast.AnnotatedAst.fixups(AnnotatedAst.java:2879)
        at com.goldencode.ast.AnnotatedAst.fixups(AnnotatedAst.java:2879)
        at com.goldencode.ast.AnnotatedAst.fixups(AnnotatedAst.java:2879)
        at com.goldencode.ast.AnnotatedAst.fixups(AnnotatedAst.java:2879)
        at com.goldencode.ast.AnnotatedAst.fixups(AnnotatedAst.java:2879)
        at com.goldencode.ast.AnnotatedAst.fixups(AnnotatedAst.java:2879)

I think it is now the time to fix the chaining properly. Can you point me to the task where we were previously discussing the chaining and how the AST was inverted?

#127 Updated by Constantin Asofiei about 11 years ago

Do you mean 14a or 14b? The taks is #1920.

#128 Updated by Greg Shah about 11 years ago

14a causes the issue. I don't have the 14b applied. The testcase is checked in as well.

But if we are going to fix this properly, then this code will go away. I'll look at #1920 and and parser to see what can be done there. Then we will have to consider the downstream affects of any tree changes.

#129 Updated by Constantin Asofiei about 11 years ago

Greg Shah wrote:

14a causes the issue. I don't have the 14b applied. The testcase is checked in as well.

With 14b, the conversion error is gone.

But if we are going to fix this properly, then this code will go away.

What about this: if server folders go through with no more chaining-related problems, then we release 14b, to allow server folders to convert (plus your testcase).

I'll look at #1920 and and parser to see what can be done there. Then we will have to consider the downstream affects of any tree changes.

I don't know how easy the parser will be able to do the job, as the COLON should act as an operator, and the tree needs to be reversed, to match how the associated JAST needs to look. Should I check if it would be easier to fix the AST in post-parse-fixups or the parser is best?

#130 Updated by Greg Shah about 11 years ago

OK. Test your change and check it in if it is OK.

The parser is the best solution for this case. Yes, COLON (and DB_REF_NON_STATIC) need to act like binary operators. They would have to have very high precedence (probably just below LPARENS). I am looking at it further to make sure I know what has to be done.

#131 Updated by Constantin Asofiei about 11 years ago

Greg Shah wrote:

In regard to note 116, I wonder where the extra wrapping is coming from. The h:label case is an ATTR_CHAR so there is no ambiguity there nor is there any conflict with the containing operator.

I will look into this one.

I took a quick look at how the ASTs look, just to check if is again a COLON problem, and it doesn't seem so. The constructor's peerid is the PLUS operator the prog.plus node's peerid is the constructor node; see this case, for put unformatted h:buffer-value + ",".:
  1. the progress AST:
                <ast col="54" id="3869765533750" line="6" text="+" type="PLUS">
                  <annotation datatype="java.lang.Long" key="peerid" value="3874060501036"/>
                  <ast col="40" id="3869765533753" line="6" text=":" type="COLON">
                    <annotation datatype="java.lang.Long" key="peerid" value="3874060501039"/>
                    <ast col="39" id="3869765533754" line="6" text="h" type="VAR_HANDLE">
                      <annotation datatype="java.lang.Long" key="oldtype" value="2341"/>
                      <annotation datatype="java.lang.Long" key="refid" value="3869765533731"/>
                      <annotation datatype="java.lang.Long" key="peerid" value="3874060501037"/>
                    </ast>
                    <ast col="41" id="3869765533756" line="6" text="buffer-value" type="ATTR_POLY">
                      <annotation datatype="java.lang.Long" key="oldtype" value="978"/>
                    </ast>
                  </ast>
                  <ast col="56" id="3869765533758" line="6" text="&quot;,&quot;" type="STRING">
                    <annotation datatype="java.lang.Long" key="peerid" value="3874060501040"/>
                  </ast>
                </ast>
    
  2. the Java AST:
                        <ast col="0" id="3874060501035" line="0" text="concat" type="STATIC_METHOD_CALL">
                          <annotation datatype="java.lang.Long" key="peerid" value="3869765533750"/>
                          <ast col="0" id="3874060501036" line="0" text="character" type="CONSTRUCTOR">
                            <ast col="0" id="3874060501039" line="0" text="value" type="METHOD_CALL">
                              <annotation datatype="java.lang.Long" key="peerid" value="3869765533753"/>
                              <ast col="0" id="3874060501038" line="0" text="handle.unwrapBufferField" type="STATIC_METHOD_CALL">
                                <ast col="0" id="3874060501037" line="0" text="h" type="REFERENCE">
                                  <annotation datatype="java.lang.Long" key="peerid" value="3869765533754"/>
                                </ast>
                              </ast>
                            </ast>
                            <ast col="0" id="3874060501040" line="0" text="," type="STRING">
                              <annotation datatype="java.lang.Long" key="peerid" value="3869765533758"/>
                            </ast>
                          </ast>
                        </ast>
    

#132 Updated by Greg Shah about 11 years ago

I've been looking at the chaining problem. We have a model that is already pretty close. The OO features provide chaining too (also using COLON) and we implemented that in a better way (as an operator but probably not 100% correctly). But I am also finding some bugs in our OO implementation which I am trying to work out so that I can see how the chaining works in the current approach.

Anyway, I will continue with this tomorrow. Unfortunately, I will be travelling and will only have limited access to Internet/email. Please go ahead and try to resolve the note 116 case. Also, please do check in your 14b fix if/when it is OK.

#133 Updated by Constantin Asofiei about 11 years ago

Also, please do check in your 14b fix if/when it is OK.

Committed to bzr revision 10290, as the server folders converted fully (and conversion regression testing has passed).

#134 Updated by Constantin Asofiei about 11 years ago

This update fixes:
  1. HANDLE attribute passed to method/function calls as a parameter (needs to be wrapped in a handle). For dynamic cases or RUN statements, the runtime will take care of the wrapping (fixed in ControlFlowOps).
  2. literals.rules: The concat operator doesn't need to wrap its first parameter. (fixed the problem where the new constructor(val1, val2, val_poly) was emitted).
  3. literals.rules: The right operand needs c'tor only if the "needed" var is not-null (this is OK as DynamicOps APIs will accept BDT for it) - fixed a case where a new null(poly) was emitted, in case of _poly = _poly + _poly.
  4. in ArrayAssigner, added missing APIs to allow extent handles to be assigned resources returned by the HANDLE attribute.

#135 Updated by Constantin Asofiei about 11 years ago

Another weird issue which needs to be handled by the ECW. Following code is valid 4GL:

def temp-table tt1 field f1 as int field f2 as char.
function func0 returns char. end.

def var ch as char.
create tt1.

tt1.f1 = (if ch <> ? then dynamic-function("func0") else 0).
tt1.f2 = (if ch <> ? then dynamic-function("func0") else 0).

In this case, current ECW decides that func0 returns character so it will force operand 3 to be wrapped in a character too. But in this case, I think the IF "inherits" the _POLY flavor from its children, and 4GL treats the IF as a _POLY.

I think the ECW needs to be changed so that for the KW_IF's then and else operands, it will not search the DYNAMIC-FUNCTION's return type.

#136 Updated by Constantin Asofiei about 11 years ago

Another case of character c'tor derail:

def var ch as char.
def var h as handle.
if h:buffer-value begins ch then message "bla".

This one might be related to the unnatural conversion of COLON node, so I'll postpone it for a little.

#137 Updated by Constantin Asofiei about 11 years ago

Attached update is on top of 15a.zip and adds:
  1. field assignment, when the field is date/datetime/datetimetz, to allow upcasting from date to datetime/datetimetz and datetime to datetimetz
  2. Fixed unwrapper for KW_DEL_R_L.
  3. Always wrap the first parameter of QUOTER function
  4. Added missing INDEX-INFORMATION API.
  5. Added missing API for STRING-VALUE.
  6. Added BlockManager.returnNormal(WrappedResource) API, to allow return temp-table tt1:handle.-like statements.

Will see how it acts in conversion regression testing and server folder conversion and if OK, I'll release it.

#138 Updated by Constantin Asofiei about 11 years ago

Another update, built on top of 15b:
  1. fix a NPE during conversion
  2. fix the MINIMUM/MAXIMUM functions, when used with char literals (TextOps was emitted as a wrapper, instead of character).

#139 Updated by Constantin Asofiei about 11 years ago

Another case which does not convert properly:

def var i as int.
def var j as int.
if not (if l then (j <= i)
             else (j <> i)) then return.

Conversion decides that the then/else tests need to return a boolean instead of a logical:
            if (_not(((l).booleanValue() ? _isLessThanOrEqual(j, i) : _isNotEqual(j, i))))
            {
               returnNormal();
            }

#140 Updated by Greg Shah about 11 years ago

Code Review (for all of the updates including 15c):

I am generally fine with the changes. There are some formatting issues in the persist files but that is not major.

The only real issue I see is that the ECW change is not right. The simpleExpressionType() method is only ever expected to translate types into default classnames. Notice that the result of simpleExpressionType() is not immediately returned. We then drop into the main expressionType() logic and can override the special cases. The idea is that all special case logic is excluded from the simpleExpressionType() code.

      // first pass (may be null)
      String jcls = simpleExpressionType(type);

      // some of the simple cases may need additional processing and this also
      // handles the nodes that cannot be determined by type alone 
      switch (type)

Go ahead and rework that file. If you find the comment to be insufficient to properly explain this (or the javadoc on simpleExpressionType()), please enhance them to make it more clear.

Then get this through conversion testing and check it in.

#141 Updated by Greg Shah about 11 years ago

I will take notes 135 and 139.

#142 Updated by Constantin Asofiei about 11 years ago

I had the impression that this was fixed:

function func0 returns int(input i as int). end
def var h as handle.

func0(h:buffer-value()). /* first param needs to be wrapped as a integer */

I'm working on it (in a separate update).

#143 Updated by Constantin Asofiei about 11 years ago

This update changes the ECW (by moving the ATTR_HANDLE test out of the simpleExpressionType) and the javadocs (I touched only the new added code).
Conversion regression testing has passed.

#144 Updated by Greg Shah about 11 years ago

Looks good. Check it in and distribute it.

#145 Updated by Constantin Asofiei about 11 years ago

16a.zip was committed to bzr revision 10292.

#146 Updated by Constantin Asofiei about 11 years ago

Update for note 142 is at #2068, note 133

#147 Updated by Greg Shah about 11 years ago

This set of changes resolves the problems in note 135 (literals.rules, ECW) and 139 (common-progress.rules). I also have added some parse-time changes to fix OO support which I was using to investigate the COLON chaining issues. This is being conversion tested now. The ECW change could cause breakage in ternary IF processing.

#148 Updated by Greg Shah about 11 years ago

My changes do not work properly. Interestingly, even Majic has some pretty crazy combinations like this:

def var counter as int.
def var num as int.

num = if counter <> ? then counter else "N/A".

Interestingly, this will compile and run. The THEN branch is OK, but if the ELSE branch ever executes, you get a runtime exception. However, this always works:

num = if counter <> ? then counter else "14".

integer and int53 are the only types to have this behavior, although it is common for some "related" classes to be allowed in teh ELSE. I have explored these combinations and the attached testcase is the result.

I am putting it here to make it easy to read:

def var i as int init 14.
def var d as dec init 30.0.
def var i64 as int64 init 9.
def var da as date init today.
def var dt1 as datetime init ?. 
def var dtz1 as datetime-tz init ?.
def var dt2 as datetime init now. 
def var dtz2 as datetime-tz init now.
def var bool as log init true.
def var bool2 as log init false.
def var h as handle.
def var h2 as handle.
def var ch1 as char init "99".
def var ch2 as char init "99".
def var lc1 as longchar init "99999".
def var lc2 as longchar init "99999".
def var r1 as recid.
def var re1 as recid.
def var r2 as rowid.
def var ro2 as rowid.
def var m1 as memptr.
def var mp1 as memptr.
def var m2 as raw.
def var mr2 as raw.

def var bogus as int init ?.

/*
 for integer/int64 op1, the op2 is not allowed to be:
 - non-unknown datetime (but unknown is fine)
 - non-unknown datetime-tz (but unknown is fine)
 - longchar
 - memptr
 - raw
*/

i = if bogus <> ? then i else ?.
i = if bogus <> ? then i else d.
i = if bogus <> ? then i else i64.
i = if bogus <> ? then i else 42.1.
i = if bogus <> ? then i else da.   /* equivalent to the julian day number */
i = if bogus <> ? then i else dt1.  /* if init to a real value, there is a runtime failure */
i = if bogus <> ? then i else dtz1. /* if init to a real value, there is a runtime failure */
i = if bogus <> ? then i else 01/01/2000.
i = if bogus <> ? then i else bool.  /* equivalent to int 1 */
i = if bogus <> ? then i else true.  /* equivalent to int 1 */
i = if bogus <> ? then i else false. /* equivalent to int 0 */
i = if bogus <> ? then i else h.
i = if bogus <> ? then i else ch1.    /* must be a valid number in the text, else a runtime error */
i = if bogus <> ? then i else "14".  /* must be a valid number in the text, else a runtime error */
i = if bogus <> ? then i else r1.
i = if bogus <> ? then i else r2.
i64 = if bogus <> ? then i64 else ?.
i64 = if bogus <> ? then i64 else d.
i64 = if bogus <> ? then i64 else i.
i64 = if bogus <> ? then i64 else 42.1.
i64 = if bogus <> ? then i64 else 42.
i64 = if bogus <> ? then i64 else da.   /* equivalent to the julian day number */
i64 = if bogus <> ? then i64 else dt1.  /* if init to a real value, there is a runtime failure */
i64 = if bogus <> ? then i64 else dtz1. /* if init to a real value, there is a runtime failure */
i64 = if bogus <> ? then i64 else 01/01/2000.
i64 = if bogus <> ? then i64 else bool.  /* equivalent to int 1 */
i64 = if bogus <> ? then i64 else true.  /* equivalent to int 1 */
i64 = if bogus <> ? then i64 else false. /* equivalent to int 0 */
i64 = if bogus <> ? then i64 else h.
i64 = if bogus <> ? then i64 else ch1.   /* must be a valid number in the text, else a runtime error */
i64 = if bogus <> ? then i64 else "14". /* must be a valid number in the text, else a runtime error */
i64 = if bogus <> ? then i64 else r1.
i64 = if bogus <> ? then i64 else r2.

/* for decimal op1, the op2 must be numeric or the unknown literal */

d = if bogus <> ? then d else ?.
d = if bogus <> ? then d else i.
d = if bogus <> ? then d else i64.
d = if bogus <> ? then d else 42.

/* for date/datetime/datetime-tz op1, the op2 must be one of the date types or the unknown literal */
/* even if the character expression is a properly formatted date, they can't be used */

da = if bogus <> ? then da else ?.
da = if bogus <> ? then da else dt1.
da = if bogus <> ? then da else dt2.
da = if bogus <> ? then da else dtz1.
da = if bogus <> ? then da else dtz2.
da = if bogus <> ? then da else 01/01/2000.
dt1 = if bogus <> ? then dt1 else ?.
dt1 = if bogus <> ? then dt1 else da.
dt1 = if bogus <> ? then dt1 else dtz1.
dt1 = if bogus <> ? then dt1 else dtz2.
dt1 = if bogus <> ? then dt1 else 01/01/2000.
dtz1 = if bogus <> ? then dtz1 else ?.
dtz1 = if bogus <> ? then dtz1 else da.
dtz1 = if bogus <> ? then dtz1 else dt1.
dtz1 = if bogus <> ? then dtz1 else dt2.
dtz1 = if bogus <> ? then dtz1 else 01/01/2000.

/* for logical op1, the op2 must be logical or the unknown literal */
/* even if the character expression is a properly formatted logical, they can't be used */

bool = if bogus <> ? then bool else ?.
bool = if bogus <> ? then bool else bool2.
bool = if bogus <> ? then bool else true.
bool = if bogus <> ? then bool else false.

/* for handle op1, the op2 must be handle or the unknown literal */

h = if bogus <> ? then h else ?.
h = if bogus <> ? then h else h2.

/* for recid op1, the op2 must be recid or the unknown literal */
/* rowid is not allowed! */

r1 = if bogus <> ? then r1 else ?.
r1 = if bogus <> ? then r1 else re1.

/* for rowid op1, the op2 must be rowid or the unknown literal */
/* recid is not allowed! */

r2 = if bogus <> ? then r2 else ?.
r2 = if bogus <> ? then r2 else ro2.

/* for memptr op1, the op2 must be memptr or the unknown literal */
/* raw is not allowed! */

m1 = if bogus <> ? then m1 else ?.
m1 = if bogus <> ? then m1 else mp1.

/* for raw op1, the op2 must be raw or the unknown literal */
/* memptr is not allowed! */

m2 = if bogus <> ? then m2 else ?.
m2 = if bogus <> ? then m2 else mr2.

/* for character op1, the op2 must be character, longchar or the unknown literal */

ch1 = if bogus <> ? then ch1 else ?.
ch1 = if bogus <> ? then ch1 else ch2.
ch1 = if bogus <> ? then ch1 else lc1.
ch1 = if bogus <> ? then ch1 else "yep".

/* for longchar op1, the op2 must be character, longchar or the unknown literal */

lc1 = if bogus <> ? then lc1 else ?.
lc1 = if bogus <> ? then lc1 else ch1.
lc1 = if bogus <> ? then lc1 else lc2.
lc1 = if bogus <> ? then lc1 else "yep".

/* unknown literal as op1, takes the type of op2 */
/* unknown literal cannot be both op1 and op2 */

i = if bogus <> ? then ? else i.
i = if bogus <> ? then ? else i64.
i = if bogus <> ? then ? else 42.
i = if bogus <> ? then ? else 42.1.
i = if bogus <> ? then ? else d.
i64 = if bogus <> ? then ? else i.
i64 = if bogus <> ? then ? else i64.
i64 = if bogus <> ? then ? else 42.
i64 = if bogus <> ? then ? else 42.1.
i64 = if bogus <> ? then ? else d.
d = if bogus <> ? then ? else i.
d = if bogus <> ? then ? else i64.
d = if bogus <> ? then ? else 42.
d = if bogus <> ? then ? else 42.1.
d = if bogus <> ? then ? else d.
da = if bogus <> ? then ? else da.
da = if bogus <> ? then ? else dt1.
da = if bogus <> ? then ? else dtz1.
dt1 = if bogus <> ? then ? else dt2.
dt1 = if bogus <> ? then ? else da.
dt1 = if bogus <> ? then ? else dtz1.
dtz1 = if bogus <> ? then ? else dtz2.
dtz1 = if bogus <> ? then ? else da.
dtz1 = if bogus <> ? then ? else dt1.
da = if bogus <> ? then ? else 01/01/2000.
dt1 = if bogus <> ? then ? else 01/01/2000.
dtz1 = if bogus <> ? then ? else 01/01/2000.
bool = if bogus <> ? then ? else bool2.
bool = if bogus <> ? then ? else true.
bool = if bogus <> ? then ? else false.
h = if bogus <> ? then ? else h2.
ch1 = if bogus <> ? then ? else ch2.
ch1 = if bogus <> ? then ? else lc1.
ch1 = if bogus <> ? then ? else "14".
lc1 = if bogus <> ? then ? else ch1.
lc1 = if bogus <> ? then ? else lc2.
lc1 = if bogus <> ? then ? else "14".
r1 = if bogus <> ? then ? else r1.
r2 = if bogus <> ? then ? else r2.
m1 = if bogus <> ? then ? else m1.
m2 = if bogus <> ? then ? else m2.

Now I have to re-implement my ECW changes to encode these possibilities. AND I have to find a new way to handle the THEN branch POLY case. I probably need to write more testcases to understand those scenarios.

#149 Updated by Greg Shah about 11 years ago

OK, this testcase is close to being done. It shows the really insane variety of possibilities with the truly dynamic _POLY cases.

def temp-table tt1 field fi as int
                   field fd as dec
                   field fi64 as int64
                   field fda as date
                   field fdt as datetime
                   field fdtz as datetime-tz
                   field fbool as log
                   field fh as handle
                   field fch as char
                   field fr1 as recid
                   field fr2 as rowid
                   field fm2 as raw
                   field na as char.

function bothIndeterminate returns int (input bogus as int) forward.
function op1Indeterminate returns int (input bogus as int) forward.
function op2Indeterminate returns int (input bogus as int) forward.

def var i as int init 14.
def var d as dec init 30.0.
def var i64 as int64 init 9.
def var da as date init today.
def var dt as datetime init ?. 
def var dtz as datetime-tz init ?.
def var bool as log init true.
def var h as handle.
def var h2 as handle.
def var ch as char init "99".
def var lch as longchar init "99999".
def var r1 as recid.
def var r2 as rowid.
def var m1 as memptr.
def var ptr as memptr.
def var m2 as raw.

def var bypass-runtime-error as logical init false.

create tt1.

set-size(ptr) = 128.

h = buffer tt1:handle.

bothIndeterminate(?).
bothIndeterminate(14).
op1Indeterminate(?).
op1Indeterminate(14).
op2Indeterminate(?).
op2Indeterminate(14).

function bothIndeterminate returns int (input bogus as int):

   message "bothIndeterminate(" + (if bogus = ? then "?" else string(bogus)) + ")".

   /* both operands may be non-typable */
   /* all data types can be assigned but there can be runtime errors depending on the contents of the field */
   /* the example here is the use of a character field and "" empty string works for most types except logical and memptr */
   /* the type of the field is not known at compile time in the 4GL, failures occur at runtime */

   tt1.na = "".

   i         = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   dt        = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fdt   = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   dtz       = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fdtz  = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.

   /* both of these will generate a runtime error if the input string is not a valid logical */
   tt1.na = "yes".
   bool      = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value. 
   tt1.fbool = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.na = "".

   h2        = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   lch       = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   r1        = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fr1   = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   r2        = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fr2   = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.

   /* always a runtime error with a character field, so we use raw here, but assigned var will become broken */
   m1        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else h:buffer-field("fm2"):buffer-value.

   m2        = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.
   tt1.fm2   = if bogus <> ? then h:buffer-field("na"):buffer-value else h:buffer-field("na"):buffer-value.

   return bogus.
end.

function op1Indeterminate returns int (input bogus as int):

   message "op1Indeterminate(" + (if bogus = ? then "?" else string(bogus)) + ")".

   /* when op1 is non-typable, op2 will depend on the type being assigned to */

   tt1.na = "".
   set-size(ptr) = 128.
   put-long(ptr, 1) = 30.

   /* integer/int64 assignment can have op2 as int, int64, dec, handle, recid, memptr (runtime */
   /* error whether or not it is initialized) or unknown value literal */

   i         = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   i         = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   i         = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   i         = if bogus <> ? then h:buffer-field("na"):buffer-value else d.
   i         = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   i         = if bogus <> ? then h:buffer-field("na"):buffer-value else h.
   i         = if bogus <> ? then h:buffer-field("na"):buffer-value else r1.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      i         = if bogus <> ? then h:buffer-field("na"):buffer-value else ptr.

   tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else d.
   tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else h.
   tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else r1.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fi    = if bogus <> ? then h:buffer-field("na"):buffer-value else ptr.

   i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else d.
   i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else h.
   i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else r1.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      i64       = if bogus <> ? then h:buffer-field("na"):buffer-value else ptr.

   tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else d.
   tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else h.
   tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else r1.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fi64  = if bogus <> ? then h:buffer-field("na"):buffer-value else ptr.

   /* decimal assignment can have op2 as int, int64, dec or unknown value literal */

   d         = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   d         = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   d         = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   d         = if bogus <> ? then h:buffer-field("na"):buffer-value else d.
   d         = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   tt1.fd    = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   tt1.fd    = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   tt1.fd    = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   tt1.fd    = if bogus <> ? then h:buffer-field("na"):buffer-value else d.
   tt1.fd    = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.

   /* date/datetime/datetime-tz assignment can have op2 as date, datetime, datetime-tz or unknown value literal */
   /* date can also have int, int64 as op2 */
   /* datetime/datetime-tz can compile with int, int64 as op2, but they generate runtime errors */  

   da        = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   da        = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   da        = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   da        = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   da        = if bogus <> ? then h:buffer-field("na"):buffer-value else da.
   da        = if bogus <> ? then h:buffer-field("na"):buffer-value else dt.
   da        = if bogus <> ? then h:buffer-field("na"):buffer-value else dtz.
   tt1.fda   = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   tt1.fda   = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   tt1.fda   = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   tt1.fda   = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   tt1.fda   = if bogus <> ? then h:buffer-field("na"):buffer-value else da.
   tt1.fda   = if bogus <> ? then h:buffer-field("na"):buffer-value else dt.
   tt1.fda   = if bogus <> ? then h:buffer-field("na"):buffer-value else dtz.
   dt        = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      dt        = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
      dt        = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
      dt        = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   end.

   dt        = if bogus <> ? then h:buffer-field("na"):buffer-value else da.
   dt        = if bogus <> ? then h:buffer-field("na"):buffer-value else dt.
   dt        = if bogus <> ? then h:buffer-field("na"):buffer-value else dtz.
   tt1.fdt   = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fdt   = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
      tt1.fdt   = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
      tt1.fdt   = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   end.

   tt1.fdt   = if bogus <> ? then h:buffer-field("na"):buffer-value else da.
   tt1.fdt   = if bogus <> ? then h:buffer-field("na"):buffer-value else dt.
   tt1.fdt   = if bogus <> ? then h:buffer-field("na"):buffer-value else dtz.
   dtz       = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      dtz       = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
      dtz       = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
      dtz       = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   end.

   dtz       = if bogus <> ? then h:buffer-field("na"):buffer-value else da.
   dtz       = if bogus <> ? then h:buffer-field("na"):buffer-value else dt.
   dtz       = if bogus <> ? then h:buffer-field("na"):buffer-value else dtz.
   tt1.fdtz  = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fdtz  = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
      tt1.fdtz  = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
      tt1.fdtz  = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   end.

   tt1.fdtz  = if bogus <> ? then h:buffer-field("na"):buffer-value else da.
   tt1.fdtz  = if bogus <> ? then h:buffer-field("na"):buffer-value else dt.
   tt1.fdtz  = if bogus <> ? then h:buffer-field("na"):buffer-value else dtz.

   /* logical assignment can have op2 as logical, integer, int64 or unknown value literal */

   tt1.na = "yes".
   bool      = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   bool      = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   bool      = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   bool      = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   bool      = if bogus <> ? then h:buffer-field("na"):buffer-value else bool.
   tt1.fbool = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   tt1.fbool = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   tt1.fbool = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   tt1.fbool = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   tt1.fbool = if bogus <> ? then h:buffer-field("na"):buffer-value else bool.
   tt1.na = "".

   /* handle assignment can have op2 as handle, integer, int64 or unknown value literal */

   h2        = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   h2        = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   h2        = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   h2        = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   h2        = if bogus <> ? then h:buffer-field("na"):buffer-value else h.
   tt1.fh    = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   tt1.fh    = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   tt1.fh    = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   tt1.fh    = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   tt1.fh    = if bogus <> ? then h:buffer-field("na"):buffer-value else h.

   ch        = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   ch        = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   ch        = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   ch        = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   ch        = if bogus <> ? then h:buffer-field("na"):buffer-value else ch.
   ch        = if bogus <> ? then h:buffer-field("na"):buffer-value else lch.
   tt1.fch   = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.
   tt1.fch   = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
   tt1.fch   = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
   tt1.fch   = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   tt1.fch   = if bogus <> ? then h:buffer-field("na"):buffer-value else ch.
   tt1.fch   = if bogus <> ? then h:buffer-field("na"):buffer-value else lch.

   lch       = if bogus <> ? then h:buffer-field("na"):buffer-value else ?.

   /* compiles but creates a runtime error */
   if bypass-runtime-error then
   do:   
      lch       = if bogus <> ? then h:buffer-field("na"):buffer-value else 0.
      lch       = if bogus <> ? then h:buffer-field("na"):buffer-value else i.
      lch       = if bogus <> ? then h:buffer-field("na"):buffer-value else i64.
   end.

   lch       = if bogus <> ? then h:buffer-field("na"):buffer-value else ch.
   lch       = if bogus <> ? then h:buffer-field("na"):buffer-value else lch.

   /* compiles but creates a runtime error */
   if bypass-runtime-error then
   do:
      m1        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else ?.
      m1        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else 0.
      m1        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else i.
      m1        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else i64.
   end.

   m1        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else ptr.
   m1        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else m2.

   /* compiles but creates a runtime error */
   if bypass-runtime-error then
   do:
      m2        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else ?.
      m2        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else 0.
      m2        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else i.
      m2        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else i64.
   end.

   m2        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else ptr.
   m2        = if bogus <> ? then h:buffer-field("fm2"):buffer-value else m2.

   tt1.fm2   = if bogus <> ? then h:buffer-field("fm2"):buffer-value else ?.

   /* compiles but creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fm2   = if bogus <> ? then h:buffer-field("fm2"):buffer-value else 0.
      tt1.fm2   = if bogus <> ? then h:buffer-field("fm2"):buffer-value else i.
      tt1.fm2   = if bogus <> ? then h:buffer-field("fm2"):buffer-value else i64.
   end.

   tt1.fm2   = if bogus <> ? then h:buffer-field("fm2"):buffer-value else ptr.
   tt1.fm2   = if bogus <> ? then h:buffer-field("fm2"):buffer-value else m2.

   r1        = if bogus <> ? then h:buffer-field("fr1"):buffer-value else ?.
   r1        = if bogus <> ? then h:buffer-field("fr1"):buffer-value else 0.
   r1        = if bogus <> ? then h:buffer-field("fr1"):buffer-value else i.
   r1        = if bogus <> ? then h:buffer-field("fr1"):buffer-value else i64.
   r1        = if bogus <> ? then h:buffer-field("fr1"):buffer-value else r1.

   tt1.fr1   = if bogus <> ? then h:buffer-field("fr1"):buffer-value else ?.
   tt1.fr1   = if bogus <> ? then h:buffer-field("fr1"):buffer-value else 0.
   tt1.fr1   = if bogus <> ? then h:buffer-field("fr1"):buffer-value else i.
   tt1.fr1   = if bogus <> ? then h:buffer-field("fr1"):buffer-value else i64.
   tt1.fr1   = if bogus <> ? then h:buffer-field("fr1"):buffer-value else r1.

   r2        = if bogus <> ? then h:buffer-field("fr2"):buffer-value else ?.

   /* compiles but creates a runtime error */
   if bypass-runtime-error then
   do:
      r2        = if bogus <> ? then h:buffer-field("fr2"):buffer-value else 0.
      r2        = if bogus <> ? then h:buffer-field("fr2"):buffer-value else i.
      r2        = if bogus <> ? then h:buffer-field("fr2"):buffer-value else i64.
   end.

   r2        = if bogus <> ? then h:buffer-field("fr2"):buffer-value else r2.

   tt1.fr2   = if bogus <> ? then h:buffer-field("fr2"):buffer-value else ?.

   /* compiles but creates a runtime error */
   if bypass-runtime-error then
   do:
      tt1.fr2   = if bogus <> ? then h:buffer-field("fr2"):buffer-value else 0.
      tt1.fr2   = if bogus <> ? then h:buffer-field("fr2"):buffer-value else i.
      tt1.fr2   = if bogus <> ? then h:buffer-field("fr2"):buffer-value else i64.
   end.

   tt1.fr2   = if bogus <> ? then h:buffer-field("fr2"):buffer-value else r2.

   return bogus.
end.

function op2Indeterminate returns int (input bogus as int):

   message "op2Indeterminate(" + (if bogus = ? then "?" else string(bogus)) + ")".

   /* when op2 is non-typable, op1 can be anything but there can be runtime errors depending in the types and contents */
   /* some types just can't be assigned without runtime errors */

   i         = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      i         = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   i         = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   i         = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      i         = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      i         = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      i         = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   tt1.fi    = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fi    = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   tt1.fi    = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   tt1.fi    = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fi    = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fi    = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fi    = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   i64       = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      i64       = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   i64       = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   i64       = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      i64       = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      i64       = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      i64       = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   tt1.fi64  = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fi64  = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   tt1.fi64  = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   tt1.fi64  = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fi64  = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fi64  = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fi64  = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   d         = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      d         = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   d         = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   d         = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      d         = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      d         = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      d         = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   tt1.fd    = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fd    = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   tt1.fd    = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   tt1.fd    = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fd    = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fd    = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fd    = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   da        = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      da        = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   da        = if bogus <> ? then ""    else h:buffer-field("na"):buffer-value.
   da        = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      da        = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      da        = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      da        = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   tt1.fda   = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fda   = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   tt1.fda   = if bogus <> ? then ""    else h:buffer-field("na"):buffer-value.
   tt1.fda   = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fda   = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fda   = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fda   = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   end.

   dt        = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   dt        = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   dt        = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      dt        = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   end.

   dt        = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      dt        = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   dt        = if bogus <> ? then ""    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      dt        = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      dt        = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   end.

   tt1.fdt   = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fdt   = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fdt   = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fdt   = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   end.

   tt1.fdt   = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fdt   = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   tt1.fdt   = if bogus <> ? then ""    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fdt   = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fdt   = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   end.

   dtz       = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   dtz       = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   dtz       = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      dtz       = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   end.

   dtz       = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      dtz       = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   dtz       = if bogus <> ? then ""    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      dtz       = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      dtz       = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   end.

   tt1.fdtz  = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fdtz  = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fdtz  = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fdtz  = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   end.

   tt1.fdtz  = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fdtz  = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   tt1.fdtz  = if bogus <> ? then ""    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fdtz  = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fdtz  = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   tt1.na = "yes".
   ch = "yes".
   bool      = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      bool      = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   bool      = if bogus <> ? then "yes" else h:buffer-field("na"):buffer-value.
   bool      = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      bool      = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      bool      = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      bool      = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.
   tt1.fbool = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      tt1.fbool = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   tt1.fbool = if bogus <> ? then "yes" else h:buffer-field("na"):buffer-value.
   tt1.fbool = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fbool = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fbool = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fbool = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.
   tt1.na = "".
   ch = "".

   h2        = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      h2        = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.

   h2        = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   h2        = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      h2        = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      h2        = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      h2        = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   tt1.fh    = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fh    = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.
   end.

   tt1.fh    = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   tt1.fh    = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fh    = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fh    = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fh    = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   ch        = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   ch        = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      ch        = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      ch        = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      ch        = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   tt1.fch   = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.
   tt1.fch   = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      tt1.fch   = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      tt1.fch   = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      tt1.fch   = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.

      lch       = if bogus <> ? then 0     else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then i     else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then d     else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then i64   else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then da    else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then dt    else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then dtz   else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then bool  else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then true  else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then false else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then h     else h:buffer-field("na"):buffer-value.
   end.

   lch       = if bogus <> ? then ch    else h:buffer-field("na"):buffer-value.
   lch       = if bogus <> ? then lch   else h:buffer-field("na"):buffer-value.
   lch       = if bogus <> ? then "14"  else h:buffer-field("na"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:
      lch       = if bogus <> ? then r1    else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then r2    else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then m1    else h:buffer-field("na"):buffer-value.
      lch       = if bogus <> ? then m2    else h:buffer-field("na"):buffer-value.
   end.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      m1        = if bogus <> ? then 0     else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then i     else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then d     else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then i64   else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then da    else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then dt    else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then dtz   else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then bool  else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then true  else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then false else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then h     else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then ch    else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then lch   else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then "14"  else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then r1    else h:buffer-field("fm2"):buffer-value.
      m1        = if bogus <> ? then r2    else h:buffer-field("fm2"):buffer-value.
   end.

   m1        = if bogus <> ? then ptr   else h:buffer-field("fm2"):buffer-value.
   m1        = if bogus <> ? then m2    else h:buffer-field("fm2"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      m2        = if bogus <> ? then 0     else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then i     else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then d     else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then i64   else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then da    else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then dt    else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then dtz   else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then bool  else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then true  else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then false else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then h     else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then ch    else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then lch   else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then "14"  else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then r1    else h:buffer-field("fm2"):buffer-value.
      m2        = if bogus <> ? then r2    else h:buffer-field("fm2"):buffer-value.
   end.

   m2        = if bogus <> ? then ptr   else h:buffer-field("fm2"):buffer-value.
   m2        = if bogus <> ? then m2    else h:buffer-field("fm2"):buffer-value.

   r1        = if bogus <> ? then 0     else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then i     else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then d     else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then i64   else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then da    else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then dt    else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then dtz   else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then bool  else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then true  else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then false else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then h     else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then ch    else h:buffer-field("fr1"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      r1        = if bogus <> ? then lch   else h:buffer-field("fr1"):buffer-value.

   r1        = if bogus <> ? then "14"  else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then r1    else h:buffer-field("fr1"):buffer-value.
   r1        = if bogus <> ? then r2    else h:buffer-field("fr1"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      r1        = if bogus <> ? then ptr   else h:buffer-field("fr1"):buffer-value.
      r1        = if bogus <> ? then m2    else h:buffer-field("fr1"):buffer-value.
   end.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:
      r2        = if bogus <> ? then 0     else h:buffer-field("fr2"):buffer-value.
      r2        = if bogus <> ? then i     else h:buffer-field("fr2"):buffer-value.
      r2        = if bogus <> ? then d     else h:buffer-field("fr2"):buffer-value.
      r2        = if bogus <> ? then i64   else h:buffer-field("fr2"):buffer-value.
      r2        = if bogus <> ? then da    else h:buffer-field("fr2"):buffer-value.
   end.

   r2        = if bogus <> ? then dt    else h:buffer-field("fr2"):buffer-value.
   r2        = if bogus <> ? then dtz   else h:buffer-field("fr2"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:
      r2        = if bogus <> ? then bool  else h:buffer-field("fr2"):buffer-value.
      r2        = if bogus <> ? then true  else h:buffer-field("fr2"):buffer-value.
      r2        = if bogus <> ? then false else h:buffer-field("fr2"):buffer-value.
      r2        = if bogus <> ? then h     else h:buffer-field("fr2"):buffer-value.
   end.

   r2        = if bogus <> ? then ch    else h:buffer-field("fr2"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
      r2        = if bogus <> ? then lch   else h:buffer-field("fr2"):buffer-value.

   r2        = if bogus <> ? then "14"  else h:buffer-field("fr2"):buffer-value.
   r2        = if bogus <> ? then r1    else h:buffer-field("fr2"):buffer-value.
   r2        = if bogus <> ? then r2    else h:buffer-field("fr2"):buffer-value.

   /* compiles but always creates a runtime error */
   if bypass-runtime-error then
   do:   
      r2        = if bogus <> ? then ptr   else h:buffer-field("fr2"):buffer-value.
      r2        = if bogus <> ? then m2    else h:buffer-field("fr2"):buffer-value.
   end.

   return bogus.
end.

#150 Updated by Greg Shah about 11 years ago

Here is the final version of the indeterminate cases. I have done 2 equivalent versions: one using buffer-value and one with dynamic-function. The result is identical and there is no difference between these cases. In both cases, the use of these features causes the 4GL to change its behavior and the 4GL simply does not seem to know the type of these expressions (at least in ternary cases) at compile time. Things that can compile do often cause runtime errors. I wrote the testcases so that they could compile and run, bypassing the runtime failures with an IF statement where needed.

Note that there is a very complex set of behaviors that depend on:

1. The type to which the result is being assigned.
2. Which operand is indeterminate (or if both are indeterminate).
3. If one operand is properly typed, its type can change things.

Next I will be implementing the matching conversion behavior.

#151 Updated by Ovidiu Maxiniuc about 11 years ago

While working on #2068 note 187, I encountered the following case:

someDate = DATE(VhFieldHandle:BUFFER-VALUE(customer.unique2)).

which converted OK:
someDate.assign(new date(handle.unwrapBufferField(vhFieldHandle).value(customer.getUnique2())));

Then I choose to replace with the following:
someDate = DATE(bf::unique2).

This form failed:
someDate.assign(new date(new BaseDataType(handle.unwrapDereferenceable(vhFieldHandle).dereference("unique2"))));

as BaseDataType is of course, abstract. Looking into the SignatureHelper you can see that the only declared form of KW_DATE with only one parameter is a ParmType.BDT and P2J tries to wrap the argument.

#152 Updated by Constantin Asofiei about 11 years ago

I think I found the problem for the BEGINS operator problem if h:buffer-value begins ch then message "blah".. In operators.rules, when accessing the first child in descent-rules, it set the peerid at the parent and not at the first child.

This is going through conversion testing now.

#153 Updated by Greg Shah about 11 years ago

It is good. Check it in and distribute it if it passes testing.

#154 Updated by Constantin Asofiei about 11 years ago

ca_upd20130322d.zip committed to bzr revision 10312.

#155 Updated by Constantin Asofiei about 11 years ago

Greg, have you touched this case:

def var h as handle.

function foo returns char.
end.

h:query-prepare(dynamic-function('foo')).

As the function's return type can be determined plus the fact that the ignorecast annotation is set to true for the dynamic-function call, the converted code does not have the dynamic-function('foo') wrapped in a new character and also the character.class parameter is not emitted, for the ControlFlowOps.invokeDynamicFunction call.

What is doing wrong I think is related to the fact that the ignorecast annotation is set, when it should have not been set.

#156 Updated by Greg Shah about 11 years ago

No, I haven't looked at this case.

#157 Updated by Constantin Asofiei about 11 years ago

There is a problem in ECW/base_structure. For a case like:

def var ch as char.

function foo returns char.
end.

message substring(ch, dynamic-function("foo"), dynamic-function("foo")).

it doesn't force the dyn-funct parameters to be wrapped, as it knows the return type. But, in this case, we need to wrap it, as the type is not numeric.

If think for such cases ECW should return null, if the function's return type and the parameter's type are not compatible.

#158 Updated by Greg Shah about 11 years ago

If think for such cases ECW should return null, if the function's return type and the parameter's type are not compatible.

It is hard to do this inside the ECW. The idea we have been using so far is to call expressionType() twice, once with the 2nd parm false and another time with the 2nd parm true. The idea is that by disabling context analysis, when null or BDT is returned, that can be compared to the result with context analysis and the proper action determined by the TRPL code (which is much more aware of each specific case).

As part of my changes I am probably going to be moving the dynamic-function processing related to casttype into the block that is bypassed by infer == false. This will mean that there are many more cases where we treat dynamic-function as a BDT type. I think this is part of the solution for your case too.

#159 Updated by Constantin Asofiei about 11 years ago

OK, I think the case at note 155 here is related to 157. I'll wait for your changes before continuing.

#160 Updated by Constantin Asofiei about 11 years ago

Ovidiu: please post here all the cases related to LENGTH function which are not converting properly (the cases when the second parameter is not wrapped).

#161 Updated by Greg Shah about 11 years ago

Here is the proposed update to resolve notes 135 and 139. This resolves all known cases of these problems.

I will conversion test this now.

#162 Updated by Greg Shah about 11 years ago

In regard to note 155, the same testcase is a bit different after my changes in note 161. Here is the output:

         public void body()
         {
            ;
            handle.unwrapQuery(h).prepare(new character(ControlFlowOps.invokeDynamicFunction("foo")));
         }

I don't know where the extra ";" comes from, but I don't think that is from my changes.

Anyway, the code now wraps the output and still does not emit the character.class. I will look into this, we I think we want the character.class to emit and to avoid the extra wrapping.

#163 Updated by Constantin Asofiei about 11 years ago

The extra ';' might be related to the comments changes. For query-prepare (and dynamic-function as parameter in general), please test the case when the function's return type is determined to be i.e. integer and the parameter is character - in this case, wrapping is needed.

#164 Updated by Constantin Asofiei about 11 years ago

About note 157: please see the handle_dyn_func6.p and handle_dyn_func5b.p (the substring issue at note 157 is from here) testcases for some cases which IIRC used to work and now are regressed (not by your latest update, but by some previous changes).

#165 Updated by Constantin Asofiei about 11 years ago

The update looks OK, I can't find any obvious flaws in the logic.

#166 Updated by Greg Shah about 11 years ago

Part of the issue with note 155 is the over-aggressive setting of ignorecast in annotations/functions.rules:

      <!-- check if this node is on the first child (i.e. on the left side) of
           function call assigment - possible only when the function is not
           really in an assignment call, is just a plain function call;
           if yes, no cast is needed -->
      <rule>evalLib("user_funcs") or
            (this.type == prog.func_poly and
             (getNoteLong("oldtype") == prog.kw_dyn_func or
              getNoteLong("oldtype") == prog.kw_super))

         <action>ref = getAncestorOfType(prog.assignment)</action>
         <rule>ref != null and parent.type == prog.expression
            <action>ref = ref.getFirstChild()</action>
            <rule>ref.type == prog.assign
               <action>ref = ref.getFirstChild()</action>
            </rule>
            <rule>ref.isAncestorOf(0, copy)
               <action>putNote("ignorecast", true)</action>
            </rule>
         </rule>
      </rule>

This code sets ignorecast even if something is deeply nested inside the sub-expression. As far as I understand, we only want it in the case where it is essentially the top-level node.

In this case (which we do not want to match), the tree looks like:

    <ast col="0" id="360777252880" line="0" text="assignment" type="ASSIGNMENT">
      <ast col="2" id="360777252881" line="6" text=":" type="COLON">
        <annotation datatype="java.lang.Long" key="peerid" value="365072220208"/>
        <ast col="1" id="360777252882" line="6" text="h" type="VAR_HANDLE">
          <annotation datatype="java.lang.Long" key="oldtype" value="2340"/>
          <annotation datatype="java.lang.Long" key="refid" value="360777252867"/>
          <annotation datatype="java.lang.Long" key="peerid" value="365072220202"/>
        </ast>
        <ast col="3" id="360777252884" line="6" text="query-prepare" type="METH_LOGICAL">
          <annotation datatype="java.lang.Long" key="oldtype" value="1900"/>
          <ast col="0" id="360777252885" line="0" text="expression" type="EXPRESSION">
            <annotation datatype="java.lang.Boolean" key="wrap_parameter" value="true"/>
            <annotation datatype="java.lang.String" key="classname" value="character"/>
            <annotation datatype="java.lang.Long" key="peerid" value="365072220204"/>
            <ast col="17" id="360777252886" line="6" text="dynamic-function" type="FUNC_POLY">
              <annotation datatype="java.lang.Long" key="oldtype" value="542"/>
              <annotation datatype="java.lang.Boolean" key="builtin" value="true"/>
              <annotation datatype="java.lang.Boolean" key="returnsunknown" value="true"/>
              <annotation datatype="java.lang.Boolean" key="ignorecast" value="true"/>
              <annotation datatype="java.lang.String" key="casttype" value="character"/>
              <annotation datatype="java.lang.String" key="param_modes" value=""/>
              <annotation datatype="java.lang.Long" key="peerid" value="365072220205"/>
              <ast col="34" id="360777252887" line="6" text="'foo'" type="STRING">
                <annotation datatype="java.lang.Long" key="peerid" value="365072220206"/>
              </ast>
            </ast>
          </ast>
        </ast>
      </ast>
    </ast>

But as far as I understand it, we really only care about these cases:

foo().
dynamic-function("foo").
super().

These would generate ASTs like this:

    <ast col="0" id="360777252880" line="0" text="assignment" type="ASSIGNMENT">
      <ast col="0" id="360777252881" line="0" text="expression" type="EXPRESSION">
        <annotation datatype="java.lang.Long" key="peerid" value="365072220202"/>
        <ast col="1" id="360777252882" line="6" text="foo" type="FUNC_CHAR">
          <annotation datatype="java.lang.Long" key="oldtype" value="2340"/>
          <annotation datatype="java.lang.Long" key="refid" value="360777252877"/>
          <annotation datatype="java.lang.Boolean" key="ignorecast" value="true"/>
          <annotation datatype="java.lang.String" key="casttype" value="character"/>
          <annotation datatype="java.lang.Long" key="peerid" value="365072220203"/>
          <annotation datatype="java.lang.Boolean" key="wrap" value="true"/>
        </ast>
      </ast>
    </ast>
    <ast col="0" id="360777252884" line="0" text="assignment" type="ASSIGNMENT">
      <ast col="0" id="360777252885" line="0" text="expression" type="EXPRESSION">
        <annotation datatype="java.lang.Long" key="peerid" value="365072220204"/>
        <ast col="1" id="360777252886" line="7" text="dynamic-function" type="FUNC_POLY">
          <annotation datatype="java.lang.Long" key="oldtype" value="542"/>
          <annotation datatype="java.lang.Boolean" key="builtin" value="true"/>
          <annotation datatype="java.lang.Boolean" key="returnsunknown" value="true"/>
          <annotation datatype="java.lang.Boolean" key="ignorecast" value="true"/>
          <annotation datatype="java.lang.String" key="casttype" value="character"/>
          <annotation datatype="java.lang.String" key="param_modes" value=""/>
          <annotation datatype="java.lang.Long" key="peerid" value="365072220205"/>
          <ast col="18" id="360777252888" line="7" text="&quot;foo&quot;" type="STRING">
            <annotation datatype="java.lang.Long" key="peerid" value="365072220207"/>
          </ast>
        </ast>
      </ast>
    </ast>

As far as I know, we only need to do this:

      <!-- check if this node is on the first child (i.e. on the left side) of
           function call assigment - possible only when the function is not
           really in an assignment call, is just a plain function call;
           if yes, no cast is needed -->
      <rule>evalLib("user_funcs") or
            (this.type == prog.func_poly and
             (getNoteLong("oldtype") == prog.kw_dyn_func or
              getNoteLong("oldtype") == prog.kw_super))

         <rule>upPath("ASSIGNMENT/EXPRESSION")
            <action>putNote("ignorecast", true)</action>
         </rule>
      </rule>

I can't really tell what this code was originally meant to do:

            <rule>ref.type == prog.assign
               <action>ref = ref.getFirstChild()</action>
            </rule>

Please help me understand if I am missing some cases here.

#167 Updated by Constantin Asofiei about 11 years ago

You are correct, the approach you mentioned from functions.rules matches the not-needed cases too.

I can't really tell what this code was originally meant to do:

I think that was added as a caution just in case the ASSIGN node is emitted for this pseudo-assignment case. The presented approach is OK.

#168 Updated by Greg Shah about 11 years ago

The changes in note 161 did cause some regressions in Majic. I have fixed all but one of them. As soon as that one is done, I will go back through testing.

#169 Updated by Greg Shah about 11 years ago

This update fixes the regressions in Majic (although there is still 1 change that is expected but will need to be checked out tomorrow). It also fixes the following issues:

2053 note 151 improper BDT c'tor/wrapping
2053 note 155 ignorecast issue
2068 note 187.5 query-prepare with dynamic-func
2068 note 187.6 date wrapping BDT

The last remaining known issue is note 157 (mismatching types need wrapping). I'll look at that tomorrow. I am conversion testing this update right now.

#170 Updated by Constantin Asofiei about 11 years ago

This case is regressed with GES 0323b (from the server folders):

def var ch as char.
def var h as handle.
ch = ch + (if valid-handle(h) then h:buffer-value else '') + '<'. 

converts to:
            ch.assign(DynamicOps.plus(concat(ch, ((h.isValid()).booleanValue() ? handle.unwrapBufferField(h).value() : new character(""))), "<"));

As now it converts to DynamicOps.plus, the string literal needs to be wrapped (or new API version added).

#171 Updated by Greg Shah about 11 years ago

Some unexpected changes were found in regression testing. I suspect these are wrong.

diff -r generated/20130323a/src/aero/timco/majic/dan/TrendR.java generated/20130323c/src/aero/timco/majic/dan/TrendR.java
626c626
<                            new Element((_isEqual(((integer) accumTotal0.getResult()), 0) ? new decimal(0) : multiply(divide(((integer) accumTotal0.getResult()), ttParent.getCntCard()), 100)), "zz9.999%", fcosumFrame.widgetIntHold())
---
>                            new Element((_isEqual(((integer) accumTotal0.getResult()), 0) ? new integer(0) : multiply(divide(((integer) accumTotal0.getResult()), ttParent.getCntCard()), 100)), "zz9.999%", fcosumFrame.widgetIntHold())
817c817
<                new Element((_isEqual(tot, 0) ? new decimal(0) : multiply(divide(tot, ttParent.getCntCard()), 100)), fcosumFrame.widgetExpr73())
---
>                new Element((_isEqual(tot, 0) ? new integer(0) : multiply(divide(tot, ttParent.getCntCard()), 100)), fcosumFrame.widgetExpr73())

diff -r generated/20130323a/src/aero/timco/majic/dc/Dc42R.java generated/20130323c/src/aero/timco/majic/dc/Dc42R.java
720c720
<                                  new Element((_isNotEqual((accumCount0.getResult(byExpr4)), 0) ? divide(((decimal) accumTotal0.getResult(byExpr4)), (accumCount0.getResult(byE
xpr4))) : new decimal(0)), fBody80Frame.widgetCrewAvg()),
---
>                                  new Element((_isNotEqual((accumCount0.getResult(byExpr4)), 0) ? divide(((decimal) accumTotal0.getResult(byExpr4)), (accumCount0.getResult(byExpr4))) : new integer(0)), fBody80Frame.widgetCrewAvg()),
739c739
<                                  new Element((_isNotEqual((accumCount0.getResult(byExpr4)), 0) ? divide(((decimal) accumTotal0.getResult(byExpr4)), (accumCount0.getResult(byExpr4))) : new decimal(0)), fBody132Frame.widgetCrewAvg()),
---
>                                  new Element((_isNotEqual((accumCount0.getResult(byExpr4)), 0) ? divide(((decimal) accumTotal0.getResult(byExpr4)), (accumCount0.getResult(byExpr4))) : new integer(0)), fBody132Frame.widgetCrewAvg()),

I will also resolve the other regression.

#172 Updated by Greg Shah about 11 years ago

This version clears all the regressions and also fixes note 157. The changes in Majic listed in note 171 are believed to be OK. Taking out the change that causes those will cause other changes that are less desirable. The bottom line is that the DISPLAY statement has handling for BDT values and this should not cause a problem. But we will know better once regression testing is run. We will runtime regression test this update separately from all others.

This is running through conversion testing right now. If it passes (with the expected changes) then I will check this in.

#173 Updated by Greg Shah about 11 years ago

And this is the update.

#174 Updated by Greg Shah about 11 years ago

Passed conversion regression testing and is checked int obzr as revision 10327. Runtime regression testing will be run independently.

#175 Updated by Greg Shah about 11 years ago

  • Status changed from New to Closed

#176 Updated by Ovidiu Maxiniuc about 11 years ago

This is a small signature fix for the following P4GL functions:

DATETIME ( month, day, year, hours, minutes [ , seconds [ , milliseconds ] ] )
DATETIME-TZ ( month, day, year, hours, minutes [ , seconds [ , milliseconds [ , timezone-exp ] ] ] )

Every other test cases that failed the conversion, with latest bzr sources are converted fine now.

#177 Updated by Greg Shah about 11 years ago

This looks good. I will conversion regression test it now.

#178 Updated by Ovidiu Maxiniuc about 11 years ago

Greg, this is one question for you.
Consider this 4GL code:

MESSAGE hSocket:READ(sreadBuff, 1, hSocket:GET-BYTES-AVAILABLE(), READ-AVAILABLE).

P2J will convert is as:
message(new integer()handle.unwrapSocket(hSocket).read(sreadBuff, 1, handle.unwrapSocket(hSocket).getBytesAvailable(), Socket.READ_AVAILABLE));

This is because the new integer() is emitted before the java.methoad_call (message). At first I thought that the cast belongs to literal 1 - the 2nd parameter, but digging deeply, I found out that in fact the node that P2J tries to wrap is the last one: READ-AVAILABLE. This is marked wrap_parameter with integer. However, I think READ-AVAILABLE & READ-EXACT-NUM should be treated as a literal, the same way NO-LOCK, SHARE-LOCK, EXCLUSIVE-LOCK and NO-WAIT are (and detected in lock_type_literals).
Maybe it's a good idea to check progress.g for other cases of such literals and add them to literals function from common-progress.rules ?

#179 Updated by Greg Shah about 11 years ago

Yes, I agree. Let's implement all of the compiler constants now and do it right. Please create a new function in common-progress called compiler_constants. It should call the lock_type_literals function but can also add the other constants too. Then you will have to fixup most (but not all) calling locations to change lock_type_literals to compiler_constants.

#180 Updated by Greg Shah about 11 years ago

om_upd20130325a.zip has passed testing. Please check it in and distribute it.

#181 Updated by Ovidiu Maxiniuc about 11 years ago

This is the fix for the note 178 above, please review.
If some other categories of compiler are found, the constants would be added to a new function and the added to compiler_constants.
For the READ-AVAILABLE and READ_EXACT_NUM I had to go deep to the progress.g.

#182 Updated by Greg Shah about 11 years ago

I am fine with the code changes. I will conversion test these now.

#183 Updated by Greg Shah about 11 years ago

om_upd20130325b.zip passed conversion testing. You can check in and distribute the update.

#184 Updated by Ovidiu Maxiniuc about 11 years ago

The updates om_upd20130325a.zip and om_upd20130325b.zip were committed to bzr (rev 10330, and rev. 10332) and distributed by mail.

#185 Updated by Greg Shah over 7 years ago

  • Target version changed from Milestone 4 to Conversion Support for Server Features

Also available in: Atom PDF