Feature #4349
add support for 4GL ENUM
100%
Related issues
History
#1 Updated by Constantin Asofiei over 4 years ago
- Related to Feature #3751: implement support for OO 4GL and structured error handling added
#2 Updated by Constantin Asofiei over 4 years ago
- Tracker changed from Bug to Feature
We need to add conversion (I'm not sure, but 4069a may have some fixes related to this) and runtime support for OpenEdge ENUM. This will require refactoring any skeleton (openedge or .NET) which defines an ENUM - currently, these are emulated by static properties.
#3 Updated by Greg Shah over 4 years ago
- Related to Feature #4373: finish core OO 4GL support added
#4 Updated by Marian Edu about 4 years ago
Hi Greg, we need FlagsEnum
for reflection so tried to implement something for this but there seems this is not yet supported in the conversion - the parser works just fine, the AstGenerator
on the other hand seems to be missing any support for enums. There is a TODO line but that's about all it is, those using OO in 4GL are often using enums other than just the built-in ones. I have a draft implementation for Enum
and FlagsEnum
that I've refactored so maybe the generator could start using that or whatever version you are thinking of, for now we will probably just go ahead and work with the built-in ones.
#5 Updated by Greg Shah about 4 years ago
4GL Behavior/Functionality¶
- Enums are implicitly final subclasses of
Progress.Lang.Enum
(regular enums) orProgress.Lang.FlagsEnum
(flags enums). You cannot subclass from an enum.- Using reflection you can prove the final flag is true and the parent class for regular enums is
Progress.Lang.Enum
. Seeoo/enums/non_flag_enums_are_final_classes_with_superclass_progress_lang_enum.p
. - Useing reflection you can prove the final flag is true and the parent class for flags enums is
Progress.Lang.FlagsEnum
. Seeoo/enums/flag_enums_are_final_classes_with_superclass_progress_lang_flagsenum.p
. - Using an
INHERITS oo.enums.Ordinals
in anENUM
statement causes compile error** Unable to understand after -- "enum oo.enums.". (247)
(seeoo/enums/EnumsAreImplicitlyFinal1.cls.does_not_compile
). - Using an
INHERITS oo.enums.Ordinals
in aCLASS
statement causes compile errorCannot inherit from 'oo.enums.Ordinals'. It is an enum. (17968)
(seeoo/enums/EnumsAreImplicitlyFinal2.cls.does_not_compile
). - Using an
INHERITS oo.enums.Ordinals
in anINTERFACE
statement causes compile errorCannot inherit from 'oo.enums.Ordinals'. It is a class. (16471)
(seeoo/enums/EnumsAreImplicitlyFinal3.cls.does_not_compile
).
- Using reflection you can prove the final flag is true and the parent class for regular enums is
- Enum definition structure and restrictions.
- Each enum definition does need to be in its own
.cls
file.- An enum statement must be in a
.cls
file. It causes the compile failureEnum statement may only be used in files with '.cls' extension. (17950)
. Seeoo/enums/enum_stmt_must_be_inside_cls_file.p.does_not_compile
. - 2 enum statements cannot be in the same
.cls
file. It causes the compile failureEND ENUM statement already encountered. Cannot compile more statements. (17974)
. Seeoo/enums/EnumsMustBeOnTheirOwn1.cls.does_not_compile
. - A class statement and enum statement cannot be in the same
.cls
file. It causes the compile failureEND CLASS statement was already encountered. Cannot compile more statements. (12981)
. Seeoo/enums/EnumsMustBeOnTheirOwn1.cls.does_not_compile
. - An interface statement and enum statement cannot be in the same
.cls
file. It causes the compile failureEND INTERFACE statement was already encountered. Cannot compile more statements. (12982)
. Seeoo/enums/EnumsMustBeOnTheirOwn1.cls.does_not_compile
. - An enum statement cannot be defined inside another enum statement. It causes the compile failure
Already defining an ENUM. Cannot compile a CLASS, INTERFACE or another ENUM statement. (17965)
. Seeoo/enums/EnumsMustBeOnTheirOwn1.cls.does_not_compile
. - An enum statement cannot be defined inside an interface statement. It causes the compile failure
Already defining an INTERFACE. Cannot compile an ENUM statement nested within an INTERFACE statement. (17962)
. Seeoo/enums/EnumsMustBeOnTheirOwn1.cls.does_not_compile
. - An enum statement cannot be defined inside a class statement. It causes the compile failure
Already defining a CLASS. Cannot compile an ENUM statement. (17963)
. Seeoo/enums/EnumsMustBeOnTheirOwn1.cls.does_not_compile
.
- An enum statement must be in a
- Each enum definition cannot have any members except those defined in the
DEFINE ENUM
.- Variables, properties, temp-tables and widgets cannot be defined inside an enum statement. It causes the compile failure
Only specific types of members may be defined in an enum file. (17977)
. Seeoo/enums/EnumsCannotHaveDataMembersOrMethods.cls.does_not_compile
. - Methods cannot be defined inside an enum statement. It causes the compile failure
A method may only be defined in a class or an interface file. (12909)
. Seeoo/enums/EnumsCannotHaveDataMembersOrMethods.cls.does_not_compile
.
- Variables, properties, temp-tables and widgets cannot be defined inside an enum statement. It causes the compile failure
DEFINE ENUM
- Can not appear in a procedure file. It causes the compile failure
Only an enum type may define enum members. (17976)
. Seeoo/enums/define_enum_must_be_inside_enum_stmt.p.does_not_compile
. - Can not appear in a class. It causes the compile failure
Only an enum type may define enum members. (17976)
. Seeoo/enums/DefineEnumMustBeInsideEnumStmt.cls.does_not_compile
. - Can not appear in an interface. It causes the compile failure
Only datasets, temp-tables, and properties may be defined in an interface. (13149)
. Seeoo/enums/DefineEnumMustBeInsideEnumStmt2.cls.does_not_compile
. - You can indeed specify any number of
DEFINE ENUM
statements inside a singleENUM
statement. The result appears to always be the same as if all members were defined in a singleDEFINE ENUM
statement. The order of the multiple statements matters in just the same way that the member definitions would work if each of the members were defined in the same order in a singleDEFINE ENUM
statement. Seeoo/enums/MultipleDefineEnum[1-4].cls
andoo/enums/MultipleDefineFlagsEnum[1-11].cls
. - Definition Behaviors
- Number of Members
- All enum members seem to store their value in an
int64
value. For flag enums, sometimes the value also operates as a set of 64 bits. This limits the number of possible unique members since there is limited space for the value storage. - A single-bit enum is one where only one of the 64 bits is set and all others are cleared. This can be defined implicitly or explicitly with the correct literal.
- A multi-bit enum is one where 2 or more of the 64 bits are set and all others are cleared. This can be defined explicitly with the correct literal or with a comma-separated list of previously defined member names (making an alias).
- An alias is an enum whose value is defined using one or more names of previously defined enums. If more than one name is specified, it must be a comma-separated list. If one name is used, then this aliased enum will have a value that is the same as the referenced member (which means it can be a single-bit or multi-bit member). If a list of names is used, then the value is the same as if each of these members was bitwise OR'd together. Unless all of these names are the same single-bit value, this should generate a multi-bit value.
- A duplicate of an enum is really just an alias for that same enum value but it will appear separately in lists of values and lists of names. This can be defined implicity, explicitly using an alias or explicitly using a numeric literal.
- All enum members seem to store their value in an
- Regular Enums
- The value of a regular enum is treated as a regular
int64
value. As such, it can have any value that a can be stored. - As a consequence, regular enums can have more than 64 non-aliased members. No specific number of members has been found. See
oo/enums/MoreThan64RegularEnumsOK.cls
.
- The value of a regular enum is treated as a regular
- Flag Enums
- See
oo/enums/MoreThan64FlagsIsRightOut*.cls.does_not_compile
,oo/enums/AliasesAllowMoreThan64Flags.cls
,oo/enums/MultibitAllowMoreThan64Flags.cls
andoo/flags/InitializersMustBeSigned64Bits.cls.does_not_compile
. - Only 64 single-bit members are allowed (bits 0 through 63). These bits can be defined implicitly or explicitly with a numeric literal. Aliases do not create a unique bit flag since they refer to one or more already defined bits.
- Trying to define any member using the 65th (or higher) bit (implicitly or explicitly) will cause a compile failure.
- If one tries to create an implicitly calculated member (one defined without an initializer, immediately following any member which has bit 63 set), the compile error
The value for enum member 'm2' is too large. (18218)
will occur. - If one tries to explicitly define a value using a hexadecimal literal that is larger than 64-bits, the compile error
Hexidecimal input greater than 16 digits is not supported. (14342)
will occur. The actual value does not matter, even if that value would fit into anint64
. For example, even0x00000000000000000000
will fail. - If one tries to explicitly define a value using a numeric literal that is larger than a signed 64-bit value, the compile errors
** Value too large for integer. (78)
andInvalid value specified for enum member '<whatever>'. (18215)
are caused. - These last two issues are broader than having only 64 single bit values since they can occur anytime one tries to define a member with this "too large" incorrect input.
- If one tries to create an implicitly calculated member (one defined without an initializer, immediately following any member which has bit 63 set), the compile error
- It is possible to have more than 64 members of a flags enum, but all of the extra members must be one of the following:
- A multi-bit member, defined via a numeric literal or via alias (single name or list of names).
- A duplicate of a single-bit member, defined via a numeric literal or via alias.
- Any number of members can have the value 0 (no bits set).
- See
- Names
- Enum names have two representations:
- The symbols as defined statically in the 4GL source code.
- The text as rendered using
enum:ToString()
orSTRING(enum)
.
- Symbol Name
- The enum name as referenced in 4GL code is always the case-insensitive symbol defined statically for the member in the
DEFINE ENUM
statement. - This same name can be used in a character expression for certain lookup methods (e.g.
enum_class:GetEnum("<enum_symbol_name")
).
- The enum name as referenced in 4GL code is always the case-insensitive symbol defined statically for the member in the
- Text Rendering
- By default, the text rendering of an enum member
e
(as reported bye:ToString()
andSTRING(e)
) is the exact text of the member as written in the original 4GL source code. - Exceptions
- Alias Initializers
- If an alias is used which references a single enum name, then that referenced enum instances text representation will be used instead of the default. In
DefineEnum15
, the text representation of bothfour
andten
istWO
. - A comma-separated alias list (which can only be used for flags enums), will cause a multibit enum to be created as a pattern but only the value will be used, the text representation will NOT be overridden. This means that the text representation will default to the symbol name. In
DefineFlagsEnum18
, the text representation of enumthree
isthree
even though the alias is defined astwo, one
.
- If an alias is used which references a single enum name, then that referenced enum instances text representation will be used instead of the default. In
- Duplicates
- If an enum definition is a duplicate of an existing enum value, the text representation is copied from the existing enum instead of using the default symbol.
- This occurs for the explicit numeric literal case. In
DefineFlagsEnum12
, the text representation ofnine
will beEIGHT
. - This occurs for the implicit value case (where an implicitly calculated value duplicates an existing value). In
DefineFlagsEnum18
, the text representation is@one@ for botheight
andten
enum members.
- Alias Initializers
- Enum member names are case-preserving when rendered as a character value, so the name of the second member of
oo.enums.DefineEnum15
istWO
even though it can be referenced on a case-insensitive basis (oo.enums.DefineEnum15:two
).
- By default, the text rendering of an enum member
- There is no difference in member name processing between regular enums and flag enums, except for the behavior of comma-separated aliases above.
- Enum names have two representations:
- Values
- Explicit
- An explicit initializer can be specified after the enum member name using
enum_member_name = initializer_literal
syntax. More than one initializer_literal is not allowed for a single enum member. - Any (negative, 0 and/or positive)
int64
orinteger
literal (which is base-10) can be used as the initializer for either regular or flag enums. Seeoo/enums/DefineEnum*.cls
,oo/enums/DefineFlagsEnum*.cls
andoo/enums/NegativeInitializersInFlags1.cls
. - With flag enums an explicit negative initializer followed by an implicitly initialized member will cause a compile failure (in this example the implicit member name is
two
)The value for enum member 'two' is too large. (18218)
. Seeoo/enums/NegativeInitializersInFlags?.cls.does_not_compile
. Presumably, this is an artifact of the compiler's "next power of two" algorithm, which will find the most significant bit (bit 63) already set for any negative number and thus it cannot set the non-existent bit 64. - Only non-negative hexadecimal (base-16) literals up to 16 hex digits in size can be used to initialize regular and flag enum values. This means that everything from
0x00
to0x7FFFFFFFFFFFFFFF
(max 64-bit signed integer, same as 9223372036854775807) are valid. Seeoo/enums/DefineFlagsEnumHexLiteral1.cls
. - Any usage of a hexadecimal form which has bit 63 (most significant bit, also the sign bit) set will cause two compile errors
** Value too large for integer. (78)
andInvalid value specified for enum member '<member_name>'. (18215)
. This means that everything from0x8000000000000000
(min 64-bit signed integer, only the leftmost bit set, which is also -9223372036854775808) to0xFFFFFFFFFFFFFFFF
(-1) are invalid. Seeoo/enums/DefineFlagsEnumHexLiteral2.cls.does_not_compile
. This seems to be a problem (bug?) with how the 4GL compiler converts hex literals intoint64
values. I guess it is treating the value as an unsigned 64-bit integer and choking on it. The workaround is simply to use the base-10 representation of the same number. - Any value may specify 0 bits, 1 bit or any number of bits set.
- Invalid Initializers
- Decimal usage causes a compile error
Invalid value specified for enum member 'something'. (18215)
. Seeoo/enums/CannotInitializeUsingDecimals.cls.does_not_compile
. - Character usage causes a compile error
** Unable to understand after -- "def". (247)
. Seeoo/enums/CannotInitializeUsingCharacter.cls.does_not_compile
. - Expressions (even ones with only literals) cause a compile error
** Unable to understand after -- "something = 1". (247)
. Seeoo/enums/CannotInitializeUsingExpressions.cls.does_not_compile
. - Using a hexadecimal literal that is larger than 64-bits, causes the compile error
Hexidecimal input greater than 16 digits is not supported. (14342)
. The actual value does not matter, even if that value would fit into anint64
. For example, even0x00000000000000000000
will fail. - Using a numeric literal that is larger than a signed 64-bit value, the compile errors
** Value too large for integer. (78)
andInvalid value specified for enum member '<whatever>'. (18215)
are caused. Seeoo/enums/InitializersMustBeSigned64Bits.cls.does_not_compile
. - Using a comma-separated list of numeric literals causes the compile failure
** Unable to understand after -- "enum whatevs = 9". (247)
. Seeoo/enums/MultipleLiteralsNotAllowed.cls.does_not_compile
.
- Decimal usage causes a compile error
- References to other previously defined members of the same enum are a form of explicit initialization. See "Aliases" below for syntax and rules.
- An explicit initializer can be specified after the enum member name using
- Implicit
- An implicit initializer is any enum name which is not followed by an initializer (a literal or an alias).
- The compiler calculates a value based on the value of the last member defined (the member defined just prior to the implicit member). It does not matter if that last enum got its value implicitly or explicitly.
- The highest value of all prior enum members is irrelevant. Only the value of the most recently defined enum determines the implicit member's value.
- Regular Enums
- If the member is the first member of the enum, then the implicit value will be 1.
- For the 2nd and later members of the enum, the implicitly calculated value will be 1 more than the last member's value.
- This means that if the last member's value is 1 then the calculated value will be 2, last member's value 0 then the calculated value will be 1, last member's value is -5 the calculated value will be -4.
- By setting an explicit value in the middle of otherwise implicitly calculated list has the effect of "resetting" the next implicitly calculated value.
- It does not matter how the last member's value was set (through implicit or explicit initializers or aliases), the implicit value will be 1 more than that value.
- Even if the resulting value is a duplicate of an existing member, it will still be used.
- Flag Enums
- Each implicit value set will have 1 bit set and 63 bits cleared.
- If the member is the first member of the enum, then the implicit value will be bit 0 set (value 0x01).
- For the 2nd and later members of the enum, the last enum member's value is inspected. The implicitly calculated bit to be set will be the next bit after the last member's highest set bit. It does not matter if there are multiple bits set, the highest bit will determine what bit will be set for the implicit calc.
- For last value 0x00, 0x01 will bet set. For last value 0x01, 0x02 will be set. For 0x02 or 0x03, 0x04 will be set. For 0x04 through 0x07, bit 0x08 will be set. For 0x08 through 0x0f, bit 0x10 will be set. And so forth.
- By setting an explicit value in the middle of otherwise implicitly calculated list has the effect of "resetting" the next implicitly calculated bit.
- It does not matter how the last member's value was set (through implicit or explicit initializers or aliases), the implicit value will be next next higher bit than that value's highest set bit.
- Even if the resulting value is a duplicate of an existing member, it will still be used.
- Duplicates
- It is OK to specify (implicitly or explicitly) a value which is the same as an existing enum member.
- This can happen by referencing an existing enum using its name or by setting the same value.
- There can be any number of duplicates.
- When the name of a duplicate is referenced as an initializer, it just makes another enum with the same value (another duplicate).
- Aliases
- The name or names of previous enum members can be referenced as an initializer. The syntax is
new_enum_member_name = previous_enum_member [, previous_enum_member ...]
. - For regular enums, only one
previous_enum_member
can be referenced. This is the equivalent of copying the named enum's value into this new member. - For flag enums, either one
previous_enum_member
or a comma-separated list ofprevious_enum_member
names can be used. If it is a singleprevious_enum_member
, then that enum's value is copied into this new member. If it is list ofprevious_enum_member
, the new enum will have the values of each of the enums bitwise OR'd into its resulting value. - Single
previous_enum_member
references create duplicate enum members, a kind of alias to the referenced member. - Lists of
previous_enum_member
references create new multi-bit members that may or may not fully duplicate the other members (depending on their definitions). At a minimum, every input enum will partially overlap with the resulting enum. - Attempting to reference an enum name that is not yet defined causes the compile error
Could not find enum member 'three'. (18217)
. Seeoo/enums/ForwardAliasReferencesFail.cls.does_not_compile
. - Attempting to mix numeric literals and aliases in the same initializer, causes the compile failure
** Unable to understand after -- "three = one,". (247)
. Seeoo/enums/MixingAliasesAndLiterals.cls.does_not_compile
. - Multiple comma-separated aliases cannot be used in regular enums. Doing so creates a compile failure:
Can only specify multiple values when the enum has the FLAGS option. (18216)
. Seeoo/enums/MultipleAliasesOnlyWorksInFlagsEnums.cls.does_not_compile
.
- The name or names of previous enum members can be referenced as an initializer. The syntax is
- The values of members do not need to be in ascending or descending order. They can be defined in any order. Only a sequence of uninterrupted implicit definitions will inherently be in ascending order.* Life Cycle
- It is OK to intermix explicit and implicit definitions in any way (all explicit, all implicit, explicit at beginning and/or middle and/or end with implicit in between... whatever). The key is that the implicit values will always follow the rules above, which are completely determined by the value of the most recently defined member.
- Explicit
- Number of Members
- Can not appear in a procedure file. It causes the compile failure
- Instantiation
- Each named member of an enum class is the equivalent of
static final
. They exist without any explicit instantiation on the part of the 4Gl programmer. The can be referenced at any time without any initialization needed. - The
NEW
operator cannot be used with an enum. For example,new oo.enums.Ordinals()
causes the compile failureCannot use NEW statement with 'oo.enums.Ordinals' because it is an enum. (17970)
. Seeoo/enums/new_operator_cannot_be_used_with_enum.p.does_not_compile
. - The
DYNAMIC-NEW
operator cannot be used with an enum. For example,dynamic-new ch-var()
(wherech-var
is the string"oo.enums.Ordinals"
) causes the runtime error (error-status:error
istrue
)Cannot NEW class oo.enums.Ordinals because it is an interface or enum. (17971)
. Seeoo/enums/dynamic_new_cannot_be_used_with_enum.p
. - The
Progress.Lang.Class:NEW()
method cannot be used with an enum. For example, assumingcls = GET-CLASS(oo.enums.Ordinals).
thencls:new()
causes the runtime error (error-status:error
istrue
)Cannot NEW class oo.enums.Ordinals because it is an interface or enum. (17971)
. Seeoo/enums/class_new_method_cannot_be_used_with_enum.p
. - The only known way to "create" a new instance of an enum is with the return value from bitwise operators and/or the bitwise helper methods. This only works for flags enums. See "Bitwise Operations" below.
- Each named member of an enum class is the equivalent of
- Enum instances do not appear in the
SESSION:FIRST-OBJECT
/SESSION:LAST-OBJECT
chain. Seeoo/enums/enums_are_not_in_the_session_object_chain.p
. - Calling
NEXT-SIBLING()
orPREV-SIBLING()
on an enum instance returns unknown value. Seeoo/enums/enums_are_not_in_the_session_object_chain.p
. DELETE OBJECT
silently returns with no change when executed on an enum instance. The enum instance remains available. It doesn't matter if the reference passed is a static referenceoo.enums.Ordinals:first
or if it is a variable of type enum. The statement appears to be a NOP. Seeoo/enums/delete_object_silently_ignores_enums.p
.VALID-OBJECT(enum_instance)
returns true, before and afterDELETE OBJECT
. Seeoo/enums/delete_object_silently_ignores_enums.p
.- Use of
Clone()
on a regular enum or flags enum will cause the runtime warningClone method is undefined for the class. (13444)
. Seeoo/enums/clone_method_undefined_for_enums.p
.
- Each enum definition does need to be in its own
- Casting
CAST()
works as one would expect for enums (same as classes).- If the type of the
parm
parameter toCAST(parm, typename)
is incompatible (not assignable) withtypename
, then a compile failure** Incompatible data types in expression or assignment. (223)
will be generated. Seeoo/enums/cast_treats_enums_as_expected.p.does_not_compile
. - If the return type (
typename
) ofCAST(parm, typename)
is incompatible with the assignment or containing sub-expression type, then a compile failure** Incompatible data types in expression or assignment. (223)
will be generated. Seeoo/enums/cast_treats_enums_as_expected.p.does_not_compile
. - If all types match, then
CAST()
works as expected. Seeoo/enums/cast_and_dynamic_cast_with_enum_and_flags_enum.p
.
- If the type of the
DYNAMIC-CAST()
- If the type of the
parm
parameter toDYNAMIC-CAST(parm, typeexpr)
is incompatible withtypeexpr
, then a runtime warning (error-status:error
isfalse
but an error message/number is recorded in silent error mode or an alter-box is shown if not in silent error mode) occursInvalid cast from oo.enums.Ordinals to oo.enums.Permissions. (12869)
. Seeoo/enums/cast_and_dynamic_cast_with_enum_and_flags_enum.p
. - If the return type (
typeexpr
) ofDYNAMIC-CAST(parm, typename)
is a valid type but is incompatible with the assignment or containing sub-expression type, then a runtime errorA variable of class 'oo.enums.Ordinals' cannot be assigned to a variable of class 'oo.enums.Permissions'. (13448)
will be generated. Seeoo/enums/cast_and_dynamic_cast_with_enum_and_flags_enum.p
. - If the return type (
typeexpr
) ofDYNAMIC-CAST(parm, typename)
is NOT a valid type, then a runtime warning (error-status:error
isfalse
but an error message/number is recorded in silent error mode or an alter-box is shown if not in silent error mode)CAST to 'oo.enums.InvalidName' not allowed. CAST target must be a user-defined type. (12900)
. Seeoo/enums/cast_and_dynamic_cast_with_enum_and_flags_enum.p
. - If all types match, then
DYNAMIC-CAST()
works as expected. Seeoo/enums/cast_and_dynamic_cast_with_enum_and_flags_enum.p
.
- If the type of the
- Enum Instance Lookup
- There are 6 ways to lookup an arbitrary enum instance
- By
int64
enum value:<class_reference>:GetEnum(int64)
Progress.Lang.Enum:ToObject(<fully_qualified_class_name_char_expr>, int64)
DYNAMIC-ENUM(<fully_qualified_class_name_char_expr>, int64)
- By
character
enum name:<class_reference>:GetEnum(character)
Progress.Lang.Enum:ToObject(<fully_qualified_class_name_char_expr>, character)
DYNAMIC-ENUM(<fully_qualified_class_name_char_expr>, character)
- By
- For all cases below, passing unknown value as one of the parameters causes a runtime error as follows:
oo.enums.Ordinals:GetEnum(int64)
results inInvalid value specified for oo.enums.Ordinals:GetEnum (15246)
oo.enums.Ordinals:GetEnum(character)
results inInvalid value specified for oo.enums.Ordinals:GetEnum (15246)
Progress.Lang.Enum:ToObject(valid_class, ?)
results inInvalid value specified for parameter 'value' of method "ToObject". (18193)
Progress.Lang.Enum:ToObject(?, int64)
results inInvalid value specified for parameter 'enumTypeName' of method "ToObject". (18193)
Progress.Lang.Enum:ToObject(?, character)
results inInvalid value specified for parameter 'enumTypeName' of method "ToObject". (18193)
DYNAMIC-ENUM(valid_class, ?)
results inCould not evaluate 'member-name-or-value' parameter for DYNAMIC-ENUM. (18007)
DYNAMIC-ENUM(?, int64)
results inCould not evaluate 'enum-type-name' parameter for DYNAMIC-ENUM. (18007)
DYNAMIC-ENUM(?, character)
results inCould not evaluate 'enum-type-name' parameter for DYNAMIC-ENUM. (18007)
- Both
GetEnum()
static methods are implicitly added by the compiler. They returns the same type as the class used for the lookup. - Given the same enum class and the same
character
orint64
parameter, the behavior of bothToObject
andDYNAMIC-ENUM()
is identical toGetEnum()
with these exceptions:- Unknown value generates a different error (see above).
- There is an extra step to lookup the referenced class. The first parameter of
ToObject
/DYNAMIC-ENUM()
is a character expression which references a valid fully qualified class name that can be found via the PROPATH. If that class cannot be found (because it is not properly qualified or because it just does not exist), then a runtime errorFailed to find enum type 'whatever'. (18109)
will be raised (in this case, we passed "whatever" as the first parameter).
- Regular Enum Results
GetEnum(int64)
static method,Progress.Lang.Enum:ToObject(class_char_expr, int64)
,DYNAMIC-ENUM(class_char_expr, int64)
- See
oo/enums/getenum_int_value_method.p
andoo/enums/dynamic_enum_int_value.p
. - Passing an exact match to an existing enum value will return that associated enum.
- Passing any other non-unknown integer causes a runtime error (for example using -1 on an enum where -1 is not a valid value)
Could not get enum of type 'oo.enums.Ordinals' for value -1. (18207)
.
- See
GetEnum(character)
static method,Progress.Lang.Enum:ToObject(class_char_expr, character)
,DYNAMIC-ENUM(class_char_expr, character)
- See
oo/enums/getenum_string_value_method.p
andoo/enums/dynamic_enum_string_value.p
. - Passing an exact match to an existing enum name will return that associated enum. No abbreviations of enum names are possible.
- Optionally one can pass leading and/or trailing whitespace ("second", " second", " second ", "second " are all equivalent). Any leading/trailing whitepace is ignored for purposes of matching.
- Passing any other non-unknown and non-comma-separated character value (including empty string) causes a runtime error (for example using "one" on an enum where "one" is not a valid name)
Could not find 'one' in enum 'oo.enums.Ordinals'. (18008)
. - Passing a comma-separated character value causes a runtime error (for example using "first,third")
Comma separated list of names 'first,third' not valid for non-flag enum 'oo.enums.Ordinals'. (18017)
.
- See
- Flag Enum Results
- Flag enums act as bitfields some of the time and as
int64
numbers at other times. The behavior makes sense the internal representation of the flags is in a signed 64-bit integer value encoded using two's complement. This is also consistent with theint64
implementation in the 4GL. As a result, one must be prepared for strangeness when dealing with negative numbers and flag enums. Please remember that in this form of representation, the most significant bit (bit 63) is the sign bit. If bit 63 is set, then the number is negative. The other thing is that small negative numbers tend to set all the bits on their left (e.g. -1 is 0xFFFFFFFFFFFFFFFFF and -2 is 0xFFFFFFFFFFFFFFFE). This means that using the 64th flag can cause weirdness and bit flag matching can be very strange. GetEnum(int64)
static method,Progress.Lang.Enum:ToObject(class_char_expr, int64)
,DYNAMIC-ENUM(class_char_expr, int64)
- See
oo/enums/getenum_int_value_method_for_flag_enum.p
,oo/enums/dynamic_enum_int_value_for_flag_enum.p
,oo/enums/flag_enums_with_positive_values_lookup_*.p
andoo/enums/flag_enums_with_negative_values_lookup_*.p
. - Passing an exact match to an existing
int64
enum value will return that associated enum. - If there is no exact
int64
match, then "bitflag matches" can still occur. First, the "maximal bitfield" will be calculated. This is the same as taking each pre-defined enum and OR'ing it with all the others. In this mode, if all the set bits (binary 1) in the givenint64
input value are also set in the maximal bitfield, then a new enum instance is created with that exactint64
input value will be created. - Bitflag matches can have real names. The name associated with the instance will be a comma separated list of all flags which are fully present in the enum OR "" if no single flag is fully present. As occurs with
STRING()
(see below), if the only flags present are the exact match to an aliased compound set of flags (seeoo.enums.Permissions
and theread
which is 0x04,write
which is 0x08 andreadwrite
which isread OR write
or 0x0C) then the name will have the compound alias instead of the individual flags (e.g. passing 12 or 0x0C will return an enum with the name "readwrite" instead of "read,write"). - Non-exact bitflag matching is complicated by some quirks in regard to negative numbers:
- If no enum flag is explicitly or implicitly defined as a negative number (bit 63 is not set), then all negative numbers will fail to match. This makes sense since all negative numbers will have the sign bit set and no such bitfield match can occur when bit 63 is not set. Besides setting that bit explicitly (by using a negative number for a flag), it is also possible to implicitly set that bit by defining 64 separate (non-aliased) flags and letting the compiler calculate the next highest power of 2. If a flag has bit 62 set and the next flag has an implicit value, then the next power of two will set bit 63. See
oo.enums.DefineFlagsEnum33
for an example. - If at least one enum flag is defined as a negative number between -1 and -32768 (inclusive), a special lookup mode is enabled. The two's complement notation means that for numbers greater than or equal to -32768, at least the most significant 49 bits will all be set. -1 is 'extra special' because its encoding is 0xFFFFFFFFFFFFFFFF which means that all bits are set. In this special matching mode any input value (positive or negative) which is a subset of the set bits will be returned as a valid enum with the same value as input (see above for the name rules). It is as if the input value is being treated as just a bitfield instead of a signed
int64
, so the sign extension bits are considered active for bitfield matching. For example, if there is a valid flag of -10, the input -9223372036854775808 (which is 0x8000000000000000 and has binary1000
in most significant nibble, i.e. only bit 63/the sign bit is set) will yield a valid enum even though the only valid negative enum is a number that is >= -32768. - This special lookup mode is also activated if there is any negative number AND the maximal bitfield in in this range of -1 and -32768 (inclusive). This can be seen in
oo/enums/flag_enums_with_negative_values_lookup_13.p
where it usesDefineFlagsEnum33.cls
in which all 64 flag bits are implicitly created. It is only the lasta64
enum def that is a "negative number" but it is -9223372036854775808 which normally would not trigger the quirk. However, in this case the maximal bitfield is -1 and that triggers the special lookup mode. - If the only negative numbers as flags are values less than -32768 OR the maximal bitfield is not in that range, then this special lookup mode is not used. For example, if there is no valid flag of >= -32768 but there is a flag -4611686018427387904 (0xC000000000000000 or
1100
in most significant nibble, which is both bit 63 and bit 62 set), then the input -9223372036854775808 (which is 0x8000000000000000 and has binary1000
in most significant nibble, i.e. only bit 63/the sign bit is set) will NOT yield a valid enum even though the bit 63 is actually set in at least one of the enums. In other words, this is an exception to the bitfield matching depending on the state of the negative numbers usage.
- If no enum flag is explicitly or implicitly defined as a negative number (bit 63 is not set), then all negative numbers will fail to match. This makes sense since all negative numbers will have the sign bit set and no such bitfield match can occur when bit 63 is not set. Besides setting that bit explicitly (by using a negative number for a flag), it is also possible to implicitly set that bit by defining 64 separate (non-aliased) flags and letting the compiler calculate the next highest power of 2. If a flag has bit 62 set and the next flag has an implicit value, then the next power of two will set bit 63. See
- Passing any
int64
value in which at least one set bit is not matched by any possible flag in the enum causes a runtime error (in this example we are trying to obtain the value 128 in oo.enums.Permissions)Could not get enum of type 'oo.enums.Permissions' for value 128. (18207)
.
- See
GetEnum(character)
static method,Progress.Lang.Enum:ToObject(class_char_expr, character)
,DYNAMIC-ENUM(class_char_expr, character)
- See
oo/enums/getenum_string_value_method_for_flag_enum.p
,oo/enums/dynamic_enum_string_value_for_flag_enum.p
,oo/enums/flag_enums_with_positive_values_lookup_*.p
andoo/enums/flag_enums_with_negative_values_lookup_*.p
. - There is no bitfield matching with string/character lookups.
- Lookup of flag enums from a string can have a single flag name or a comma separated list of multiple flag names.
- Each name must be an exact match to an existing enum name. For each flag name in the list, that associated enum will be OR'd into the enum that is returned.
- Names may not be abbreviated.
- Optionally each name in the input list can have leading and/or trailing whitespace ("read", " read", " read ", "read " are all equivalent). Any leading/trailing whitepace is ignored for purposes of matching.
- If the list includes any other non-unknown character value (including empty string) causes a runtime error (for example using "rea" on an enum where "one" is not a valid name)
Could not find 'one' in enum 'oo.enums.Ordinals'. (18008)
. This means that foroo.enums.Permissions
, "rea", "", "reade", "read,wri" and "read,,write" are all invalid inputs that generate the runtime error 18808. - The enum will report a name that is a comma separated list of the enum names, in the order of definition. It is rationalized from the OR'd enum instead of being defined from the input string.
- See
- Flag enums act as bitfields some of the time and as
- There are 6 ways to lookup an arbitrary enum instance
- Reflection-Based Enum Inspection
- See
oo/enums/reflection_getenum_methods_for_enum.p
,oo/enums/reflection_getenum_methods_for_flag_enum.p
,oo/enums/reflection_getenum_methods_for_enum_values_and_names_ordering.p
. - All of the following are static methods which can only be called on the
Progress.Lang.Class
instance associated with a given enum or flag enum instance. For example, an instance obtained usingGET-CLASS(oo.enums.Ordinals)
. - You cannot call these methods directly on a
Progress.Lang.Enum
orProgress.Lang.FlagsEnum
. The methods can only be called on a sub-class ofProgress.Lang.Enum
orProgress.Lang.FlagsEnum
. Calling it on ANY other class or interface, includingProgress.Lang.Class
(non-enum class) itself or onProgress.Lang.Error
(non-enum interface) will generate the same runtime errorClass 'Progress.Lang.Class' is not an enum type. (18110)
. Seeoo/enums/reflection_getenum_methods_only_work_on_enum_types.p
. GetEnumValues()
- This returns the comma separated list of all enum definition values.
- Each value is a base-10 integer no matter how it was defined (e.g.
0x0C
will be reported as 12). - The definition order of the enum is ignored.
- For regular enums, the values are sorted smallest to largest. This means negative values (if present) are on the left, followed by 0 and positive values in ascending order. For
oo.enum.DefineEnum13
, the result will be"-1000,-5,3,9,11,49,54,700,702,754"
even though the definition order is quite different. - For flag enums, the values are sorted 0 to the largest positive number and then if there are any negative values from smallest to largest negative number. This means negative values (if present) are on the left, followed by 0 and positive values in ascending order. For
oo.enums.DefineFlagsEnum21
, the result will be"0,0,1,-5,-1"
even though the definition order is quite different. - Duplicate values (enum instances with different names but the same values) will cause those values to appear as many times as they were defined. For
oo.enums.DefineEnum15
, the result will be"0,1,2,2,3,3,4,4,4,4"
.
GetEnumNames()
- This returns the comma separated list of all enum definition names.
- Although the text rendering of the enum "name" is sometimes copied from a different enum instance (see the
Names
section ofDEFINE ENUM
above), the returned list of this method always uses the 4GL source code symbol name. This may be different from what is seen as the text representation of the enum. - The definition order of the enum is ignored.
- Given the list of values as returned by
GetEnumValues()
, the corresponding names will be substituted in for each value. This means that the each enum's corresponding name and value will have the same index postion in the returned lists forGetEnumValues()
andGetEnumNames()
. These lists can be used to map bidirectionally. - For regular enum
oo.enum.DefineEnum13
, the result will be"five,nine,four,ten,three,two,one,seven,eight,six"
even though the definition order is quite different. - For flag enum
oo.enums.DefineFlagsEnum21
, the result will bezero,nil,one,neg-five,neg-one
even though the definition order is quite different. Please note that thenil
enum instance will render its name aszero
(e.g. usingToString()
) but the correct symbol name will appear inside theGetEnumNames()
output. - For duplicate values (
zero
andnil
have the same 0 value), the sorting appears to match the order of definition in theDEFINE ENUM
.
GetEnumValue(character)
- Regular Enums
- Passing an exact match to an existing enum name will return the
int64
value of that associated enum. No abbreviations of enum names are possible. - One CANNOT pass leading and/or trailing whitespace (" second", " second ", "second " are NOT equivalent to "second"). This is different from how the
GetEnum
methods work. - Passing any non-unknown, non-exact match character value (including empty string or a comma-separated value) causes unknown value to be returned. This means that for
oo.enums.Ordinals
, "fir", "first ", "" and "first,third" all return unknown value.
- Passing an exact match to an existing enum name will return the
- Flag Enums
- Lookup of flag enums from a string can have a single flag name or a comma separated list of multiple flag names.
- Each name must be exact match to an existing enum name. For each flag name in the list, that associated enum will be OR'd into the enum whose value is then returned.
- Names may not be abbreviated.
- Optionally each name in the input list can have leading and/or trailing whitespace ("read", " read", " read ", "read " are all equivalent). Any leading/trailing whitepace is ignored for purposes of matching.
- If the list includes any other character value (including empty string) then unknown value is returned. This means that for
oo.enums.Permissions
, "rea", "", "reade", "read,wri" and "read,,write" are all invalid inputs that return unknown.
- If unknown value is passed as an argument, this runtime error is generated:
Invalid value specified for Progress.Lang.Class:GetEnumValue (15246).
.
- Regular Enums
GetEnumName(int64)
- Regular Enums
- Passing an exact match to an existing enum value will return the name for that associated enum.
- Passing any other non-unknown integer returns unknown value.
- For
oo.enums.Ordinals
, passing 1 will return "first" and passing 0 will return unknown value. - Duplicate values (enum instances with different names but the same values) will return the name of the first enum (in the order of definition in
DEFINE ENUM
) that has that value.
- Flag Enums
- Passing an exact match to an existing
int64
enum value will return the name for that associated enum. Foroo.enums.Permissions
, the value 4 will return a name of "read". Since it is an exact match, only that enum name is returned even though the 0x04 bit is also set in other enum definitions. In this case, the result will NOT be a comma-separated list. - Compound (i.e. an enum member that is aliased to multiple other flags) members are treated as a unique member that can be matched exactly using their "combined"
int64
value. Foroo.enums.Permissions
, the value 12 will report a name of"readwrite"
instead of"read,write"
(which each are present as part of the compound flag). - If there is no exact
int64
match, then a "bitflag matches" can still occur, generating a possible comma-separated list of results. The name is created by iterating through each defined enum member (in the order they appear inDEFINE ENUM
). For each member, this algorithm is used(<input_int64_value> BITWISE_AND <member_under_test>) EQ <member_under_test>
(it checks if all bits in the member under test are set in the inputint64
). For each case where this test is true, that member's name will be added to the comma-separated output string. - Compound members are not matched in this bitflag matching phase. For
oo.enums.Permissions
, passing 13 or 0x0D will return the name "create,read,write" instead of "create,readwrite". - The two's complement encoding of
int64
means that negative numbers will always have bit 63 set and will often have many bits set. For example, if -1 is the value, then the encoding is 0xFFFFFFFFFFFFFFFF which means that all non-compound (and non-zero) enum members will match. Foroo.enums.Permissions
, an input value of -1 will yield a name of "create,delete,read,write,exec". Note howdenied
(0x00) and the compoundreadwrite
(0x0C@) are not present. - For exact matches and for bitflag matches, duplicate values (enum instances with different names but the same values) will return a single name of first defined enum (in the order of definition in
DEFINE ENUM
) that has that value. - Any input value for which there is no enum which can have all its corresponding bits set, will cause unknown value to be returned. For
oo.enums.Permissions
, an input value of 128 will not allow any member to be matched so unknown will be returned. Foroo.enums.DefineFlagsEnum21
, passing either 128 (0x80) or -9223372036854775808 (MIN 64-BIT SIGNED INT, 0x8000000000000000) will cause no enum members (0, 0, 1, -5. -1) to have all their bits set in the input value so the result is unknown.
- Passing an exact match to an existing
- If unknown value is passed as an argument, this runtime error is generated:
Invalid value specified for Progress.Lang.Class:GetEnumValue (15246).
.
- Regular Enums
- See
- ASSIGNMENT
- Only the same exact enum or flag enum type (e.g.
oo.enums.Ordinals
) can be assigned to a variable of typeoo.enums.Ordinals
. - Although enum members all have an
int64
value, no numeric types can be assigned to an enum. Enums are truly treated as specific data types like an OO class instance. - A
CAST()
orDYNAMIC-CAST()
can be used to assign an expression of the proper superclass which is actually an instance of the same exact specific class. Ifmy-prog-lang-enum
is a var of typeProgress.Lang.Enum
which actually holds an instance ofoo.enums.Ordinals
, themy-ordinal = CAST(my-prog-lang-enum, oo.enums.Ordinals).
will work correctly. - Any mismatch in types will cause a compile failure
** Incompatible data types in expression or assignment. (223)
. Seeoo/enums/assign_enum_requires_exact_type_match.p.does_not_compile
. - The members of an enum cannot be assigned (they are treated as implicitly final members). Trying to assign to
oo.enums.Ordinals:first
causes a compile errorCannot update first because it is read-only. (17979)
. Seeoo/enums/enum_members_cannot_be_assigned.p.does_not_compile
.
- Only the same exact enum or flag enum type (e.g.
- CASE Statement
- See
oo/enums/enum_case_stmt.p
,oo/enums/flag_enum_case_stmt.p
andoo/enums/getenum_int_value_method_for_flag_enum.p
. - The statement starts with
CASE <enum_expression>:
.<enum_expression>
must be an expression of regular enum or flag enum type. - Each
WHEN <enum_member_expression> THEN
clause is a possible match to a specific enum member.<enum_member_expression>
can be a hardcoded enum member reference likeoo.enums.Ordinals:first
but it can also be any valid expression that returns an enum of the same exact type. For example, a user-defined function callfirst-ordinal()
which returns aoo.enums.Ordinals
instance. This is especially necessary for flag enums, where one will often want to use bitwise operations to specify an exact list of flags that are to be matched, likeoo.enums.Permissions:create OR oo.enums.Permissions:read OR oo.enums.Permissions:exec
oroo.enums.Permissions:create OR oo.enums.Permissions:random XOR (oo.enums.Permissions:delete OR oo.enums.Permissions:read)
. - For both kinds of enums, the first
WHEN
clause detected as a match will have itsTHEN
block executed. - It is OK to have duplicate conditions in subsequent
WHEN
clauses, but these cannot be matched. - If no match is found the
OTHERWISE
block will execute (if it is present). - For both kinds of enums, the matching is based on the member's
int64
value. Only an exact match will select the correspondingWHEN
clause. With flag enums, this means that only the same exact bit pattern can be matched. There is no subset matching going on. - It is valid to use the
OR WHEN <enum_member_expression>
construct with both types of enums. Please note that the meaning ofwhen oo.enums.Permissions:write or when oo.enums.Permissions:exec then
is very different thanwhen oo.enums.Permissions:write or oo.enums.Permissions:exec then
. The first matcheswrite
orexec
individually but will NOT match if both bits are set. The second ONLY matches if both bits are set. - The following WHEN clauses all generate a compile failure
Incompatible datatypes in CASE and a WHEN branch expression. (3536)
. Seeoo/enums/mismatching_when_types_in_enum_case_stmt.p.does_not_compile
andoo/enums/mismatching_when_types_in_flags_enum_case_stmt.p.does_not_compile
.- Referencing a different enum type (the
CASE
expression is of typeoo.enums.Ordinals
but there is aWHEN oo.enums.Permissions:create THEN
. - Hexadecimal literals.
integer
orint64
literals.character
literals.
- Referencing a different enum type (the
- See
- Bitwise Operations
- See
oo/enums/bitwise_operators_cannot_have_enums_and_number_operands.p.does_not_compile
,oo/enums/bitwise_operators_cannot_have_flags_enums_and_number_operands.p.does_not_compile
,oo/enums/bitwise_operations_unknown_value.p
,oo/enums/test_flag_enum_bitwise_ops.p
andoo/enums/test_flag_enum_bitwise_ops_including_non_positive_numbers.p
andoo/enums/test_flag_enum_bitwise_ops_non_intersecting_bitsets.p
. - Bitwise operations only operate on flag enums. They cannot be used with numeric data and they cannot be used with regular enums.
- There are 3 binary operators (
OR
,XOR
,AND
), 1 unary operator (NOT
), 3 methods which are implicitly added to each subclass ofProgress.Lang.FlagsEnum
(SetFlag()
,ToggleFlag()
,UnsetFlag()
) and 1 methodIsFlagSet()
that is implemented directly inProgress.Lang.FlagsEnum
. - In the 4GL, the 3 implicitly added methods cannot be placed in
Progress.Lang.FlagsEnum
because they both receive as a parameter and have a return type which is the specific type of enum that is the child ofProgress.Lang.FlagsEnum
. This means that if you call one of these methods on flags enum A and pass it an instance of flags enum B, then this compile error will occurParameter 1 for METHOD <methodname> is not type compatible with its definition. (12905)
. This 12905 will also occur if the parameter is of typeProgress.Lang.FlagsEnum
even if it really is the correct subclass type. Trying to assign the return value from one of these methods to flags enum A when the method is called on flags enum B, will cause the compile error** Incompatible data types in expression or assignment. (223)
. Seeoo/enums/implicit_flags_enum_methods_are_type_specific.p.does_not_compile
. - Except for the
IsFlagSet()
, all the operators and the other methods return a new instance of the same enum type of the operand(s). The compiler enforced strict type matching in all of these cases, so this result is consistent. If the result of a bitwise operation is an exact match to the value of an existing (predefined) enum instance, then that instance will be returned. If the result is NOT an exact match then a new enum will be created which is a combination of existing enums or which is a completely unique value that is not directly represented by any existing enum. This means that it must be possible to create new instances at runtime with unique calculated values. - Unknown Value
- If any operand or both of the operands to the binary bitwise operators is unknown value, this runtime error is raised
Cannot perform bitwise operation with the unknown value. (18210)
. This holds true for the unary NOT bitwise operator. - If the parameter to
SetFlag
,ToggleFlag
orUnsetFlag
is unknown value, this runtime error is raisedInvalid value specified for <enum_class>:<method> (15246)
. - If the parameter to
IsFlagSet
is unknown value, this runtime error is raisedCould not check flags because the specified enum instance is invalid. (18205)
. - If the referent for
SetFlag
,ToggleFlag
,UnsetFlag
orIsFlagSet
is unknown value, this runtime error is raisedInvalid handle. Not initialized or points to a deleted object. (3135)
.
- If any operand or both of the operands to the binary bitwise operators is unknown value, this runtime error is raised
- Between all these operators and methods, there are 6 types of bitwise operations:
OR
operator/SetFlag()
implicit method- Given the same inputs, both
op1 OR op2
andop1:SetFlag(op2)
will generate the same output. - The result is a true bitwise OR of the operands. All bits that are set in each operand will also be set in the output enum, regardless of whether it is set in the other operand.
- There are no ordering considerations of the operands. Passing the same operands in switched order will yield the same result.
- There is no special behavior for negative values, other than the fact that the two's complement notation means that the sign bit will be set AND for negative numbers near zero, it is likely that most of the bits are set (e.g. -1 is 0xFFFFFFFFFFFFFFFF). For
oo.enums.DefineFlagsEnum20
- With unique single-bit enums as input, the result will be a multi-bit enum that is a superset of the two input enums. For
oo.enums.Permissions
,create OR delete
(0x01 and 0x02 values respectively) will yield an enum with a value of 0x03 and a name of "create,delete". - If either operand has a value of 0, the result will be equivalent to the other operand.
- Given the same inputs, both
XOR
operator/ToggleFlag()
implicit method- Given the same inputs, both
op1 XOR op2
andop1:ToggleFlag(op2)
will generate the same output. - The result is a true bitwise XOR of the operands. All bits that are set differently in each operand will be set in the output enum.
- There are no ordering considerations of the operands. Passing the same operands in switched order will yield the same result.
- There is no special behavior for negative values, other than the fact that the two's complement notation means that the sign bit will be set AND for negative numbers near zero, it is likely that most of the bits are set (e.g. -1 is 0xFFFFFFFFFFFFFFFF).
- With unique single-bit enums as input, the result will be a multi-bit enum that is a superset of the two input enums. For
oo.enums.Permissions
,create XOR delete
(0x01 and 0x02 values respectively) will yield an enum with a value of 0x03 and a name of "create,delete". - If the bit sets are disjoint (no set bits in common), the result will be a multi-bit enum that is a superset of the two input enums. For
oo.enums.Permissions
,create XOR readwrite
(0x01 and 0x0A values respectively) will yield an enum with a value of 0x0B and a name of "create,read,write". - Any overlap in set bits will be set to 0 in the result. For
oo.enums.Permissions
,read XOR readwrite
(0x04 and 0x0A values respectively) will yield an enum with a value of 0x08 and a name of "write". - Repeatedly XOR'ing the same 2nd operand into a given enum result will have the effect of toggling the 2nd operand's bits on and off.
- If either operand has a value of 0, the result will be equivalent to the other operand.
- XOR'ing two enums that have the same value will result in an enum with the value 0.
- Given the same inputs, both
AND
operator- The result is a true bitwise AND of the operands. All bits that are set the same in both operands will be set in the output enum.
- There are no ordering considerations of the operands. Passing the same operands in switched order will yield the same result.
- There is no special behavior for negative values, other than the fact that the two's complement notation means that the sign bit will be set AND for negative numbers near zero, it is likely that most of the bits are set (e.g. -1 is 0xFFFFFFFFFFFFFFFF).
- With unique single-bit enums as input, the result will be an enum with value 0. For
oo.enums.Permissions
,create AND delete
(0x01 and 0x02 values respectively) will yield an enum with a value of 0x00 and a name of "denied". - If the bit sets are disjoint (no set bits in common), the result will be an enum with value 0. For
oo.enums.Permissions
,create AND readwrite
(0x01 and 0x0A values respectively) will yield an enum with a value of 0x00 and a name of "denied". - Any overlap in set bits will be set to 1 in the result. For
oo.enums.Permissions
,read AND readwrite
(0x04 and 0x0A values respectively) will yield an enum with a value of 0x04 and a name of "read". - If one enum is a subset of the other, the resulting enum will be the same as the subset. For
oo.enums.DefineFlagsEnum20
,neg-five AND neg-one
(0xFFFFFFFFFFFFFFFB and 0xFFFFFFFFFFFFFFFF) will yield an enum with value -5 and nameneg-five
. - If either operand has a value of 0, the result will be an enum of value 0.
- AND'ing two enums that have the same value will result in an enum with that same value.
NOT
operator- Traditional bitwise NOT is an inversion of all bit state. For example, the in C or Java the inverse of 0x0000000000000000 is 0xFFFFFFFFFFFFFFFF and the inverse of 0x0000000000000006 (0110 in least signficant nibble) is 0xFFFFFFFFFFFFFFF9 (1001 in least signficant nibble). In the 4GL the result is not a true bitwise NOT. The result depends upon the maximal set of bits which can possibly be set. This "maximal bitfield" can be calculated by taking each defined enum and OR'ing it with all the others. This becomes a "mask" that limits the inversion of bits to only those in this set. All others will be cleared.
- If the maximal bitfield contains all 64 bits, then the bitwise NOT acts the same as the traditional inversion. For example, in
oo.enums.DefineFlagsEnum34
theNOT zero
yields theneg-one
enum (value -1 or 0xFFFFFFFFFFFFFFFF) andNOT four
yieldsneg-five
enum (value -5 or 0xFFFFFFFFFFFFFFFB which has 1011 in the least significant nibble). - This behavior changes when maximal bitfield contains a subset of all 64 bits. For example, in
oo.enums.Permissions
the maximal bitfield is 0x000000000000003F (00111111 in least significant byte). In this case,NOT denied
(denied
has value 0) yields an enum with value 0x3F (62 in base-10) and name "create,delete,read,write,exec" (note that the bit at 0x20 comes from therandom
member and has no single bit name on its own so it does not appear in the name). Similarly,NOT exec
yields an enum with value 0x2F (00101111 in least significant byte) and name "create,delete,read,write". - This means that the algorithm for 4GL bitwise NOT is
((INVERT enum_member) AND maximal_bitfield)
whereINVERT
is traditional bitwise NOT (full inversion of bit state),AND
is traditional "bitwise AND" andmaximal_bitfield
is the previously described mask for this operation.
UnsetFlag()
implicit method- Where
op1
andop2
are both members of the same flag enum, the use ofop1:UnsetFlag(op2)
clears all set bits inop2
which are also set inop1
and returns the resulting enum. - This is consistent with the expression
op1 AND (NOT op2)
. The 4GL version of bitwise NOT yields the same result as the traditional bitwise NOT in this use case. - The order of operands is important here. Switching the order is not sure to yield the same results.
- Unsetting the same enum from itself will yield an enum with no bits set. For
oo.enums.Permissions
,create:UnsetFlag(create)
results in an enum which is equivalent todenied
(value 0 and name "denied"). - Unsetting a single bit unique enum from a different single bit unique enum will return the
op1
enum (no bits changed). Foroo.enums.Permissions
,create:UnsetFlag(delete)
results in an enum which is equivalent tocreate
. - If
op1
andop2
are disjoint sets of bits, then the result will always be equivalent toop1
since there is not subset of common bits to clear. - Only if there is an overlap of set bits, will a change be made. For
oo.enums.Permissions
,readwrite:UnsetFlag(write)
results in an enum which is equivalent toread
. - Arbitrary subsets of the maximal bitfield are possible. For
oo.enums.Permissions
,random:UnsetFlag(readwrite)
(random
has value 0x26 which is "delete,read" and an unspecified/unnamed 0x20 bit;readwrite
is 0x0C or 0x04 + 0x08) yields an enum with value 0x22 and name "delete". - No special behavior occurs for negative values, except it is more likely to yield unusual results. For
oo.enums.DefineFlagsEnum20
,neg-one:UnsetFlag(one)
will result in an enum with value -2 and name "". This makes sense since 0xFFFFFFFFFFFFFFFF AND NOT 0x0000000000000001 should yield 0xFFFFFFFFFFFFFFFE.
- Where
IsFlagSet()
method- The code related to checking this method's behavior can be seen in the
bitwise-enum
function ofcommon/enum_helpers.i
. op1:IsFlagSet(op2)
returnstrue
only if all set bits ofop2
are also set inop1
. If any one of the bits ofop2
are not set inop1
, then the result will befalse
.- It doesn't matter if
op1 EQ op2
or ifop1
is a superset ofop2
. Both cases will cause atrue
result. - The result is consistent with the algorithm
(op1 AND op2) EQ op2
. - If the
op2
parameter toop1:IsFlagSet(op2)
has a value of 0, then the method invocation will generate the runtime errorCannot check flags if the value for the specified enum instance is zero. (18113)
.
- The code related to checking this method's behavior can be seen in the
- All of the following cannot be used as an operand for any bitwise operator. Use will generate a compile failure
** Incompatible data types in expression or assignment. (223)
.integer
,int64
ordecimal
values (as lvalues, unknown value, complex sub-expressions or any form of literal)- Subclasses of
Progress.Lang.Enum
as one operand of a binary operator. - Two different subclasses of
Progress.Lang.FlagEnum
operands of a binary operator. - Attempting to assign different subclasses of
Progress.Lang.FlagEnum
to one another.
- If subclasses of
Progress.Lang.Enum
are the operand tonot
or are both operands for a binary operator, then this pair of compile failures is generated:'oo.enums.Ordinals' is not a flags enum type. (18211)
and** Incompatible data types in expression or assignment. (223)
.
- See
- Comparison Operations
- See
oo/enums/comparison_operators_cannot_compare_enums_against_numbers.p.does_not_compile
,oo/enums/comparison_operators_cannot_compare_flags_enums_against_numbers.p.does_not_compile
,oo/enums/test_enum_comparisons.p
,oo/enums/test_enum_comparisons_including_non_positive_numbers.p
,oo/enums/test_flag_enum_comparisons.p
,oo/enums/test_flag_enum_comparisons_including_non_positive_numbers.p
. - In the following truth table, the
op1
andop2
are the enum values as represented by theint64(enum)
function. These values may optionally be set to unknown value (?). - When
op1
is unknown, theCompareTo()
andEquals()
methods for enums will cause the runtime errorInvalid handle. Not initialized or points to a deleted object. (3135)
. These are the only known deviations from the results documented in the truth table below. - Truth Table
- The following all cause this compile failure
** Incompatible data types in expression or assignment. (223)
.- Mixing different types of enums. Two regular enums of different types (
oo.enums.Ordinals
vsoo.enums.DefineEnum2
) cannot be compared against each other. Two flag enums of different types (oo.enums.Permissions
vsoo.enums.DefineFlagsEnum20
) cannot be compared against each other. Regular enums cannot be compared to flag enums. Only two enums of the exact same type can be compared. - No form of numeric data (
integer
,int64
ordecimal
) can be used in comparison to an enum. This is true for any type of expression including literals and non-literals with unknown value.
- Mixing different types of enums. Two regular enums of different types (
- See
- String Rendering
- See the function
check-enum-worker
incommon/enum_helpers.i
. - Pre-Defined Enums
- See the Names section of the
DEFINE ENUM
above for basic details of how each pre-defined enum's name matches the original text (in a case-preserving manner). - Pre-defined enums can be obtained by static name reference (e.g.
oo.enums.Ordinals:first
) OR by some reflective lookup which has the same exactint64
value (e.g.oo.enums.Ordinals:GetEnum(1)
oroo.enums.Ordinals:GetEnum("first")
). - Both regular enums and flags enums can obtained this way.
- See the Names section of the
- Dynamically Created Enums
- Flag enums allow dynamic creation through:
- Bitwise operations.
GetEnum(int64)
reflective lookupGetEnum(character)
reflective lookup using a comma-separated list
- Dynamic creation allows enums that have multiple values set or even arbitrary values which don't match any single predefined enum.
- These enums do not have a any dedicated 4GL code which defines the name.
- Instead, the name is calculated using this algorithm:
- If there is only a single enum's bit(s) present in the enum, then that enum's name will be the name.
- If there enum has bits set for more than one enum, the name associated with the instance will be a comma separated list of all flags which are fully present in the enum. This list will be in the order of enum's source code definition.
- If the only flags present are the exact match to an aliased compound set of flags (see
oo.enums.Permissions
and theread
which is 0x04,write
which is 0x08 andreadwrite
which isread OR write
or 0x0C) then the name will have the compound alias instead of the individual flags (e.g. passing 12 or 0x0C will return an enum with the name "readwrite" instead of "read,write"). - Empty string is returned if no single enum member's bits are fully present.
- Flag enums allow dynamic creation through:
STRING()
andToString()
- The results of both of these are always the same.
- This name will be returned as noted above for the given enum (pre-defined or dynamically created).
- The contents of the character value will be the enum's name, with all case preserved.
STRING(oo.enums.Ordinals:first)
will reportfirst
STRING(oo.enums.DefineEnum15:two)
will reporttWO
STRING(oo.enums.Permissions:exec OR oo.enums.Permissions:readwrite)
will reportread,write,exec
STRING(oo.enums.Permissions:read OR oo.enums.Permissions:write)
will reportreadwrite
STRING(oo.enums.DefineFlagsEnum20:one XOR oo.enums.DefineFlagsEnum20:neg-one)
will report a character with no contents (the value is -2 and this corresponds to no single flag member in that enum).STRING(oo.enums.DefineFlagsEnum34:neg-ten OR oo.enums.DefineFlagsEnum34:one)
will reportone,four
(the value is -9 and the bits for 1 and 4 are both set).
QUOTER()
- This name will be returned as noted above for the given enum (pre-defined or dynamically created).
- The contents of the character value will an opening double quote character, then the enum's name(s) (with all case preserved) and then a closing double quote character.
QUOTER(oo.enums.Ordinals:first)
will report"first"
QUOTER(oo.enums.DefineEnum15:two)
will report"tWO"
QUOTER(oo.enums.Permissions:exec OR oo.enums.Permissions:readwrite)
will report"read,write,exec"
QUOTER(oo.enums.Permissions:read OR oo.enums.Permissions:write)
will report"readwrite"
QUOTER(oo.enums.DefineFlagsEnum20:one XOR oo.enums.DefineFlagsEnum20:neg-one)
will report""
(the value is -2 and this corresponds to no single flag member in that enum).QUOTER(oo.enums.DefineFlagsEnum34:neg-ten OR oo.enums.DefineFlagsEnum34:one)
will report"one,four"
(the value is -9 and the bits for 1 and 4 are both set).
- See the function
- Numeric Type Conversion
- See the function
check-enum-worker
incommon/enum_helpers.i
. INT64(enum_member)
returns the member's value, unchanged.INTEGER(enum_member)
- If the member's
int64
value is less than -2147483648 or greater than 2147483647, a runtime warning (error-status:error is false)Value <whatever> too large to fit in INTEGER. (15747)
. - Otherwise the member's value is returned as an integer.
- If the member's
DECIMAL(enum_member)
returns the member's value as a decimal.
- See the function
- I/O Usage
- See
oo/enums/put_enum_output.p
,oo/enums/put_flag_enum_output.p
andoo/enums/export_enum_output.p.does_not_compile
. PUT
- The
PUT
statement allows direct reference to enum members. For example,put <enum_member_expression> format "x(50)" skip.
. <enum_member_expression>
may be a any expression of an enum type.- The result for that field is the same output as
STRING(<enum_member_expression>)
or<enum_member_expression>:ToString()
.
- The
- The
EXPORT
statement cannot directly reference enum type expressions. Any attempt will generate a compile errorUse the INTEGER or STRING function to see user defined object variables. (13464)
. - No other I/O statements are known to have direct enum reference support, but the
STRING()
orINT64()
functions can be used to create a non-enum exression for output.
- See
#6 Updated by Greg Shah about 4 years ago
I have a draft implementation for Enum and FlagsEnum that I've refactored so maybe the generator could start using that
I need to put these in place as a first step and then work on the conversion of the ENUM
/DEFINE ENUM
statements. From there I will work on the rest of the features noted in #4349-5. Each feature includes a range of conversion and runtime code.
I'd like to start with your versions of Enum
and FlagsEnum
. Are your changes already in 4384b?
Either we can push your changes from 4384b into 4231b and then reset 4384b as a copy of 4231b. Or I'll take your versions of Enum
and FlagsEnum
, augment them and put them in 4231b/4384b.
Let me know what you want to do. I'd like to start on these today if you have your versions ready.
#7 Updated by Marian Edu about 4 years ago
Greg Shah wrote:
I have a draft implementation for Enum and FlagsEnum that I've refactored so maybe the generator could start using that
I need to put these in place as a first step and then work on the conversion of the
ENUM
/DEFINE ENUM
statements. From there I will work on the rest of the features noted in #4349-5. Each feature includes a range of conversion and runtime code.I'd like to start with your versions of
Enum
andFlagsEnum
. Are your changes already in 4384b?Either we can push your changes from 4384b into 4231b and then reset 4384b as a copy of 4231b. Or I'll take your versions of
Enum
andFlagsEnum
, augment them and put them in 4231b/4384b.Let me know what you want to do. I'd like to start on these today if you have your versions ready.
Enum
, FlagsEnum
and some built-in enums are already in 4384b... I thought about using an inline java enum in those classes to make it easier to 'define' the members but thought it's better to stop and wait for you on that one.
The enum classes should be defined as final
indeed, maybe we can prevent those from being deleted and not show in session objects list somehow. The automatic assignment of the value if not specified seems to be straightforward and I see you already have tests for that but since your conversion works on code that compile on 4GL I would not bother much about compile time validations :)
#8 Updated by Greg Shah about 4 years ago
maybe we can prevent those from being deleted and not show in session objects list somehow
Yes, as noted above this is how it must work.
I would not bother much about compile time validations :)
Exactly right!
#9 Updated by Constantin Asofiei about 4 years ago
A quick note, the enum values should be emitted like this:
public static ContextLocal<object<? extends CustomEnum> enumValue1 = new ContextLocal<>() { protected object<? extends CustomEnum> initialValue() { return // code to create the enum; } };
As an enum value is actually compatible with all OO methods (toString
, comparison, etc), we need it to be an instance local to the FWD's context, and behave as other object
instances.
#10 Updated by Constantin Asofiei about 4 years ago
4231b rev 11498 contains conversion-related fixes for 4GL ENUM. This was done by chasing down INTERFACE_DEF, KW_INTERFAC, CLASS_DEF, KW_CLASS, and making the proper adjustments to include ENUM_DEF or KW_ENUM, accordingly.
The callgraph parts are not complete.
#11 Updated by Greg Shah about 4 years ago
The first set of runtime changes will be to get the parent classes (Progress.Lang.Enum
and Progress.Lang.FlagsEnum
) integrated with the proper life-cycle behavior. This will include changes to common OO runtime code to properly tolerate/process enums.
Enums have the following inheritance and life cycle behaviors:
- Enums are final.
- Enums are not in the
SESSION:FIRST-OBJECT
/SESSION:LAST-OBJECT
chain. - Calling
enum_instance:NEXT-SIBLING()
/enum_instance:PREV-SIBLING()
returns unknown value. VALID-OBJECT(enum_instance)
is always true, even afterDELETE OBJECT
.DELETE OBJECT
is a NOP.- There are no constructors, destructors or members of an enum, except those enum instances that are part of the
DEFINE ENUM
statement. - No way to explicitly
NEW
,Class:NEW()
orDYNAMIC-NEW()
.
Because of this, I think we can simplify much of the processing for Progress.Lang.Enum
, Progress.Lang.FlagsEnum
and all subclasses. The idea is to use a simpler Java implementation. In particular, I plan to short circuit much of the normal life cycle processing (e.g. for things like isValid()
and delete()
), add error handling and to completely avoid ever using ObjectResource
for enum instances.
I don't understand why we need to use ContextLocal
for the predefined enum instances. They are truly static, are immutable and cannot be different by context. For cases where the BaseObject
methods are invalid, I was going to override them.
- Add
_BaseObject_
default methodboolean isTracked() { return true; }
- Remove
*_constructor__
method*_constructor__static__
method*_execute__
method
- Override
boolean isTracked() { return false; }
(this will allow runtime methods to determine if a quick exit is needed to avoid all the tracking/reference counting/deletion; we won't need reflection and we don't need to hard code knowledge of the enum class itself into this locations)BaseObject.getNextSibling()
/BaseObject.getPrevSibling()
to return unknown
- Modify
ObjectOps
isValid()
delete()
newDynamicInstance()
newInstanceInternal()
increment()
decrement()
hasDestructors()
defaultStaticConstructor()
getConstructorMethod()
getExecuteMethods()
getExecuteMethod()
object
isUnknown()
isValid()
My intuition suggests that the following also need changes, but the changes are not clear to me:
ObjectOps
loadClassInt()
- I think theSourceNameMapper.registerLegacyClass()
may still be needed but the rest seems unnecessarygetStaticInstance()
getStaticClass()
getClassInstance()
asResource()
handle
fromString()
fromResourceId()
ControlFlowOps
initializeLegacyObject()
- I think we can bypass calling this inObjectOps.loadClassInt()
initializeLegacyClass()
- I think we can bypass calling this inObjectOps.newInstanceInternal()
invokeLegacyMethod()
executeDestructor()
- never called ifObjectResource
is never used
Constantin: Besides the question about why ContextLocal
is needed instead of a real static
implementation of enum members, I definitely need feedback on these other changes. The section at the bottom is especially in question but if you see other issues with my plan I would like to know.
#12 Updated by Greg Shah about 4 years ago
- Status changed from New to WIP
- Assignee set to Greg Shah
- Start date deleted (
10/15/2019)
#13 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
A quick note, the enum values should be emitted like this:
[...]As an enum value is actually compatible with all OO methods (
toString
, comparison, etc), we need it to be an instance local to the FWD's context, and behave as otherobject
instances.
This was the first attempt since you said ContextLocal is to be used instead of real static members, there were two problems with that:
- first is you can't instantiate the same object class inside the ContextLocal initialValue
method cause at that point the class is not registered yet. ObjectOps.newInstance
will give you a null back.
- all enum instances needs to have GetEnum
methods that returns that specific enum type not just Enum
, so there must be a list of valid enum values unless we write a big switch/case :(
I think if we use a private constructor and don't use ObjectOps
at all to create the enum members but simply override isValid
and isUnknown
things are pretty much solved. Delete
should be a no-op if the reference is an Enum, if the object is unknown it still throws error like now but if the ref
is an enum is should simply return.
#14 Updated by Constantin Asofiei almost 4 years ago
Marian Edu wrote:
I think if we use a private constructor and don't use
ObjectOps
at all to create the enum members but simply overrideisValid
andisUnknown
things are pretty much solved.Delete
should be a no-op if the reference is an Enum, if the object is unknown it still throws error like now but if theref
is an enum is should simply return.
The converted members need to be object
instances in FWD (with a ref to the actual enum value). And this can be pass around to any other object
var which is compatible (even progress.lang.object
, right?). So we can't override stuff in object
, we need to handle the enum case explicitly.
Greg: as these are truly immutable, then yes, we can avoid ContextLocal
. Otherwise, I need to review your notes.
#15 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
The converted members need to be
object
instances in FWD (with a ref to the actual enum value). And this can be pass around to any otherobject
var which is compatible (evenprogress.lang.object
, right?). So we can't override stuff inobject
, we need to handle the enum case explicitly.
Yes, those are still object
and the reference is a valid Enum value. Using a private constructor with new object(ref) and avoid going through the ObjectOps.newInstance all together (plus remove execute and 4gl 'constructors') seems to work. No need to override stuff in object
, still ObjectOps
needs to be made aware of Enums - isValid
, increment
, decrement
and delete
needed to be updated for that.
Greg: as these are truly immutable, then yes, we can avoid
ContextLocal
. Otherwise, I need to review your notes.
Since those can't be deleted I don't see why you need ContextLocal
, it's safe to let all clients use the same enum values and beside I don't think ContextLocal can work with an object that doesn't participate to the regular lifecycle.
#16 Updated by Greg Shah almost 4 years ago
Good. So far, your feedback is completely consistent with my plan.
Constantin: I still need your thoughts on the section "My intuition suggests that the following also need changes, but the changes are not clear to me:" in #4349-11.
#17 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
Good. So far, your feedback is completely consistent with my plan.
I've couldn't find a solution for using a Java enum though, one option for the GetEnum
methods would be to use reflection to get all members... for some reason this doesn't appeal me much :(
I can push my changes to 4384b if you want, just need to update existing enums and flagenums.
#18 Updated by Constantin Asofiei almost 4 years ago
- I assume there will be a
BaseEnum
super-class? - we need to override
toLegacyString
andlegacyEquals
too (inBaseEnum
) - what about
clone
? - we need to emit the
getEnum()
static methods in the converted enum Java code, but these should delegate the call to some helper inBaseEnum
- you are right, no other c'tor, d'tor or execute method are needed in the generated Java code for an enum
- we don't need to track the associated 'legacy class' or 'legacy instance' for the enum (as we do for the non-enum code), but:
- in
ObjectOps
all usage ofstaticReferents
,referents2static
should never be reached - although I think this is already true forstaticReferents
, as this is used for non-enum usage only, we should protect for it (or at least log something) - same as the other cases you mention in for
ObjectOps
, they should never happen, but we should protect against it - ControlFlowOps is involved only in case of dynamic calls for an enum method - in such cases, this code needs to be protected. I'm not sure yet of the implications (as it will need an 'class surrogate' to emulate the 'persistent program' for the static method). As we don't have a 'context-local surrogate' for the enum class, this needs to be changed so that it can work.
BlockManager.checkJavaCall
should avoid theObjectOps.loadClass
call, and also do not emitObjectOps.loadClass
for enum methods. But this can be protected directly atObjectOps.loadClass
(and related APIs), too.
- in
- for the other ObjectOps APIs you mention in the review section, I'm not sure all need to be protected, as some may be not be reached because of the caller code changes. Testing will show.
- for the first section, I aggree with the approach
I want to continue with the enum conversion; do you have a design how the converted enum code will look like? My approach would be, for an enum class like:
enum CustomEnum: define enum enum-value-1 enum-value-2 enum-value-3. end enum.
to emit this:
public class CustomEnum extends BaseEnum { public static final object<? extends CustomEnum> enumValue1 = BaseEnum.createEnumValue(CustomEnum.class, 1, "enum-value-1"); public static final object<? extends CustomEnum> enumValue2 = BaseEnum.createEnumValue(CustomEnum.class, 2, "enum-value-2"); public static final object<? extends CustomEnum> enumValue3 = BaseEnum.createEnumValue(CustomEnum.class, 3, "enum-value-3"); public static object<? extends CustomEnum> getEnum(int64 idx) { return BaseEnum.getEnum(CustomEnum.class, idx); } public static object<? extends CustomEnum> getEnum(character enumValue) { return BaseEnum.getEnum(CustomEnum.class, enumValue); } }
(plus the associated
LegacySignature
).#19 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
- I assume there will be a
BaseEnum
super-class?
Why another BaseEnum
class, we already have lang.Enum
mapped to Progress.Lang.Enum
. The only thing different from 4GL is that this class is not final but again this can be handled in LegacyClass
as a special case. If we add another class then the hierarchy will be different compared with 4GL one.
I think right now the Enum
class holds references to all enum members (hash map on enum class) so it's basically the same thing as you describe unless I'm wrong and it isn't :(
#20 Updated by Constantin Asofiei almost 4 years ago
Marian Edu wrote:
I think right now the
Enum
class holds references to all enum members (hash map on enum class) so it's basically the same thing as you describe unless I'm wrong and it isn't :(
Sorry, I forgot about that. Yes, looks like there is no need for another base class, and just add the createEnumValue
and getEnum
to Enum.java
.
OTOH, looking at that code, I can't shake a feeling that whenever we rely on something which requires the this-procedure
, we will have problems... for example, we can't use BlockManager.function
in Enum.java
without changes in ProcedureManager
to resolve and push the 'shared enum instance' or the 'shared enum class'.
#21 Updated by Greg Shah almost 4 years ago
I've couldn't find a solution for using a Java enum though
Correct, we cannot use Java enums. The result must be a subclass of the our equivalent to the legacy base class (Progress.Lang.Enum
or Progress.Lang.FlagsEnum
).
one option for the GetEnum methods would be to use reflection to get all members... for some reason this doesn't appeal me much :(
Agreed. I'm going to have internal data structures to store the pre-defined members.
I can push my changes to 4384b if you want, just need to update existing enums and flagenums.
Yes, please do.
we need to override toLegacyString and legacyEquals too (in BaseEnum)
Yes, my notes above are just about the life cycle issues. I have many other changes needed to fully implement all the helper methods, operators, CASE statement etc...
what about clone?
I'll need to check it with a test. It probably just returns this
.
we need to emit the getEnum() static methods in the converted enum Java code, but these should delegate the call to some helper in BaseEnum
Yes. There are other methods like this (SetFlag
, ToggleFlag
and UnsetFlag
) too.
The only thing different from 4GL is that this class is not final but again this can be handled in LegacyClass as a special case. If we add another class then the hierarchy will be different compared with 4GL one.
This is a good point.
I think right now the Enum class holds references to all enum members (hash map on enum class) so it's basically the same thing as you describe unless I'm wrong and it isn't :(
Yes, except the definition order is actually important in some cases so I may change this a little.
I want to continue with the enum conversion; do you have a design how the converted enum code will look like?
...
to emit this:
Yes, this has some parts as I envision. But we also need some other things:
- the class itself should be
final
- it needs a
private
constructor so that instances cannot be created - there needs to be an
abstract
non-static "factory" method in the base class (something likeEnum create(long, String)
) which will actually construct the subclass; this must be implemented in the generated class; this will be used in the common code for the bitwise operators etc... which can return new instances of the same class as the operands - the data structure that holds the list of predefined enums is better in the generated class; otherwise all enums will be mixed together and the operations might be quite slow plus they would need special logic to ignore instances of the other enums
- my initial idea on the data structure was to use an array, but perhaps collections are better; I haven't figured this out yet
- I was expecting to use a helper in the base class (like your
createEnumValue
) but was thinking to maintain the data structure(s) in a static initializer in the generated code
I was going to work on the conversion now, but if you want to take it I won't argue. :)
I can't shake a feeling that whenever we rely on something which requires the this-procedure, we will have problems... for example, we can't use BlockManager.function in Enum.java without changes in ProcedureManager to resolve and push the 'shared enum instance' or the 'shared enum class'.
I don't think that the BlockManager
stuff is needed at all. I was planning that all of the methods in the enum hierarchy (emitted or inherited) can be implemented as a standard FWD runtime approach with no extra blocks.
#22 Updated by Greg Shah almost 4 years ago
Constantin: If you want to continue with the conversion, do let me know. I would then focus on the runtime parts.
Marian: Let me know when you have your latest enum changes in 4384b. I'll pull them and use them as the basis for the versions in 4231b.
#23 Updated by Constantin Asofiei almost 4 years ago
Greg Shah wrote:
Constantin: If you want to continue with the conversion, do let me know. I would then focus on the runtime parts.
Yes, I'll take the conversion. Do you have a comprehensive list of all the static methods which need to be added and their signatures? Or toggleFlag
, unsetFlag
, setFlag
and getEnum
are all we need?
the data structure that holds the list of predefined enums is better in the generated class; otherwise all enums will be mixed together and the operations might be quite slow plus they would need special logic to ignore instances of the other enums
I assume this will be exposed via a getEnumValues
abstract method (or others similar, for example to get by index and/or name) in the Enum
super-class? The idea with the static class initializer is nice; the createEnumValue
helper was meant to have a single point for creating these. I'm thinking about the skeleton classes, too, to make it easier to write those. I don't want to duplicate too much code by hand in the skeletons.
my initial idea on the data structure was to use an array, but perhaps collections are better; I haven't figured this out yet
We need lookup both via index and (lowercased) name. So a hashmap and an array should be enough.
#24 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
Marian: Let me know when you have your latest enum changes in 4384b. I'll pull them and use them as the basis for the versions in 4231b.
Committed revision 11468 in 4384b, an example of Enum is oo/reflect/AccessMode
and oo/reflect/Flags
for FlagsEnum.
#25 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
Yes, I'll take the conversion. Do you have a comprehensive list of all the static methods which need to be added and their signatures? Or
toggleFlag
,unsetFlag
,setFlag
andgetEnum
are all we need?
toggleFlag
, setFlag
, unsetFlag
and isFlagSet
are all implemented in FlagsEnum
, no need to add those to all instances, those must extend FlagsEnum
anyway.
We need lookup both via index and (lowercased) name. So a hashmap and an array should be enough.
Array could work if we add all members at once (we know the size), I've used a list with createEnum
.
#26 Updated by Constantin Asofiei almost 4 years ago
Marian Edu wrote:
Constantin Asofiei wrote:
Yes, I'll take the conversion. Do you have a comprehensive list of all the static methods which need to be added and their signatures? Or
toggleFlag
,unsetFlag
,setFlag
andgetEnum
are all we need?
toggleFlag
,setFlag
,unsetFlag
andisFlagSet
are all implemented inFlagsEnum
, no need to add those to all instances, those must extendFlagsEnum
anyway.
How do you define an enum to extend FlagsEnum
in 4GL?
Greg: I don't see the enum tests on our testcases project.
#27 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
How do you define an enum to extend
FlagsEnum
in 4GL?
By using the flags
option...
enum <EnumName> flags: end. <pre> > > Greg: I don't see the enum tests on our testcases project. I've found those in oo/enums.
#28 Updated by Greg Shah almost 4 years ago
Or toggleFlag, unsetFlag, setFlag and getEnum are all we need?
This should be the list, but the toggleFlag, unsetFlag, setFlag methods are not static. They are instance methods.
I assume this will be exposed via a getEnumValues abstract method (or others similar, for example to get by index and/or name) in the Enum super-class?
Our various helper methods/implementation methods in the base classes will use the data structures directly. The only external access is from Progress.Lang.Class
but we will add helpers for this access.
I'm thinking about the skeleton classes, too, to make it easier to write those. I don't want to duplicate too much code by hand in the skeletons.
I was assuming that we just use the standard 4GL enum syntax for skeletons. It should be enough.
We need lookup both via index and (lowercased) name. So a hashmap and an array should be enough.
I don't think we need indexed lookup, but we do need to do an ordered (in the order of definition) search by value. We definitely need name and value lookups.
Yes, I'll take the conversion.
Cool. The majority of this will be easy to implement in templates, with differences for regular vs flags enums. Please note that the tricky part is in the implicit value calculations, the aliasing and the duplication of values. All of it is documented above.
toggleFlag, setFlag, unsetFlag and isFlagSet are all implemented in FlagsEnum, no need to add those to all instances, those must extend FlagsEnum anyway.
For IsFlagSet
, this is clear.
I was thinking about this, for toggleFlag, setFlag, unsetFlag don't we need to cast the result to the right sub-class type? The core logic can be in the parent class but we may need a shim layer in the generated enum. I was thinking about class parameters/generics as a possible solution but I'm not sure that really does it.
Greg: I don't see the enum tests on our testcases project.
It is in the new/good testcases, not in old/bad/uast.
#29 Updated by Constantin Asofiei almost 4 years ago
Greg Shah wrote:
I don't think we need indexed lookup, but we do need to do an ordered (in the order of definition) search by value. We definitely need name and value lookups.
I mean the GetEnum(idx)
- isn't this something like 'get the Nth defined enum'?
It is in the new/good testcases, not in old/bad/uast.
Thanks, found them.
#30 Updated by Constantin Asofiei almost 4 years ago
Constantin Asofiei wrote:
Greg Shah wrote:
I don't think we need indexed lookup, but we do need to do an ordered (in the order of definition) search by value. We definitely need name and value lookups.
I mean the
GetEnum(idx)
- isn't this something like 'get the Nth defined enum'?
OK, I understand, I see the syntax now.
#31 Updated by Constantin Asofiei almost 4 years ago
Can we rename p2j.oo.lang.Enum
to p2j.oo.lang.LegacyEnum
? To avoid java conflicts...
#32 Updated by Greg Shah almost 4 years ago
Can we rename
p2j.oo.lang.Enum
top2j.oo.lang.LegacyEnum
? To avoid java conflicts...
Yes.
#33 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
Greg Shah wrote:
I don't think we need indexed lookup, but we do need to do an ordered (in the order of definition) search by value. We definitely need name and value lookups.
I mean the
GetEnum(idx)
- isn't this something like 'get the Nth defined enum'?
That has nothing to do with the order, it's the actual value used there. If no explicit value is set, then indeed is the same as defined order but this is caused by the way 'default' values are defined by the compiler so this is something to be handled during conversion..
#34 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
Can we rename
p2j.oo.lang.Enum
top2j.oo.lang.LegacyEnum
? To avoid java conflicts...Yes.
Right, think we need to do the same for 'Map'... damn, missed that :(
#35 Updated by Constantin Asofiei almost 4 years ago
oo/enums
? I have some weird errors:
oo.enums.Ordinals
andDefineEnum3
should haveflags
, astest_enum_comparisons.p
and another program usesToggleFlag
,setFlag
, etc with them- there is a
{common/enum_helpers.i}
line innon_flag_enums_are_final_classes_with_superclass_progress_lang_enum.p
andnon_flag_enums_are_final_classes_with_superclass_progress_lang_enum.p
oo/enums/MultibitMembersAllowMoreThan64Flags.cls
hasoo.enums.MultibitAllowMoreThan64Flags
instead ofoo.enums.MultibitMembersAllowMoreThan64Flags
#36 Updated by Greg Shah almost 4 years ago
Fixed in rev 559. These were mostly cases where I had tested the code and then later changed the include file without fixing up all locations.
#37 Updated by Constantin Asofiei almost 4 years ago
Greg, I've added stubs for ObjectOps.newDynamicEnum
and we also need a ObjectOps.registerEnum(Class extends LegacyEnum>)
to register an enum (this will be called from the static c'tor).
#38 Updated by Constantin Asofiei almost 4 years ago
Greg, my plan is for something like this:
class CustomEnum: define enum one two. define enum three = two four = 1234. end.
to generate:
public final class CustomEnum extends LegacyEnum { private static java.util.Map<Long, CustomEnum> byValue = new java.util.LinkedHashMap<>(); private static java.util.Map<String, CustomEnum> byName = new java.util.LinkedHashMap<>(); public static final object<? extends com.goldencode.testcases.CustomEnum> one; public static final object<? extends com.goldencode.testcases.CustomEnum> two; public static final object<? extends com.goldencode.testcases.CustomEnum> three; public static final object<? extends com.goldencode.testcases.CustomEnum> four; static { one = createEnum(new CustomEnum(), "one", byValue, byName); // first in list two = createEnum(new CustomEnum(), "two", "one", byValue, byName); // increment from previous value three = createEnum(new CustomEnum(), "three", "two", true, byValue, byName); // alias four = createEnum(new CustomEnum(), "four", 1234, byValue, byName); // explicit value } private CustomEnum() { } public static object<CustomEnum> getEnum(int64 val) { return getEnum(CustomEnum.class, byValue, val); } public static object<CustomEnum> getEnum(character val) { return getEnum(CustomEnum.class, byName, val); } }The four cases are:
- first in list - just assign to 1
createEnum
with 2nd and 3rd argument aString
- increment the value using the previously defined enum as specified by the 3rd argumentcreateEnum
with 2nd and 3rd argument aString
, and a 4thtrue
- assign the exact value for the specified alias- assign an explicit value
The conversion rules will take care of linking these together, and the runtime will just make sure to compute everything correctly. I don't want to add code in the conversion to compute the exact value.
The two byValue
and byName
maps will always hold all the enum values.
#39 Updated by Greg Shah almost 4 years ago
I just checked in new tests for Clone()
(it doesn't work) and Progress.Lang.Enum:ToObject()
(works like GetEnum()
and DYNAMIC-ENUM()
). See revision 561. I also updated the details in #4349-3.
#40 Updated by Greg Shah almost 4 years ago
Overall, I like the approach.
The conversion rules will take care of linking these together, and the runtime will just make sure to compute everything correctly. I don't want to add code in the conversion to compute the exact value.
I don't like the idea that there is so much implicit 4GL processing left behind. This makes the resulting code more fragile because people editing it later won't always understand the rules.
Regardless of this, for createEnum with 2nd and 3rd argument a String
, I don't want to pass the previously defined name. If we are already relying upon the first implicit item to come first, then we can also rely upon the next implicit ones to be in order. In that case, then the implicit ones are always just defined using the most recently added enum.
This means that first in list
and createEnum with 2nd and 3rd argument a String
would both look like createEnum(new CustomEnum(), "member_name", byValue, byName);
.
And then the createEnum with 2nd and 3rd argument a String, and a 4th true
would actually be createEnum(new CustomEnum(), "new_member_name", "old_member_name", byValue, byName);
.
The two byValue and byName maps will always hold all the enum values.
I think we need to provide some abstract methods to return these maps. Otherwise we can't implement the necessary logic in the parent classes. Although that does not help for the static methods (see below), it can be a cleaner result for things like createEnum()
and the bitwise operations.
For the static methods (GetEnumValues()
, GetEnumNames()
, GetEnumValue()
, GetEnumName()
, Progress.Lang.Enum:ToObject()
and DYNAMIC-ENUM()
) the problem will need reflection to resolve.
#41 Updated by Constantin Asofiei almost 4 years ago
Greg Shah wrote:
This means that
first in list
andcreateEnum with 2nd and 3rd argument a String
would both look likecreateEnum(new CustomEnum(), "member_name", byValue, byName);
.And then the
createEnum with 2nd and 3rd argument a String, and a 4th true
would actually becreateEnum(new CustomEnum(), "new_member_name", "old_member_name", byValue, byName);
.
OK, I'll change it. You are right, it makes sense.
The two byValue and byName maps will always hold all the enum values.
I think we need to provide some abstract methods to return these maps. Otherwise we can't implement the necessary logic in the parent classes. Although that does not help for the static methods (see below), it can be a cleaner result for things like
createEnum()
and the bitwise operations.
Well... actually I've made some changes so that in the static c'tor there is this call (as we need to know what enums have been registered so far):
registerEnum(CustomEnum.class, "CustomEnum", byValue, byName);
and
LegacyEnum
will take care of registering the maps. And this allows simple calls for createEnum
and getEnum
, to avoid passing the maps. Runtime will just interrogate the registry (a simple map get, should be fast for IdentityHashMap
).
BTW, the runtime must rely on SourceNameMapper
to resolve a enum class, if the first ever access is via reflection.
#42 Updated by Constantin Asofiei almost 4 years ago
Does someone have the FWD equivalent for FlagsEnum
?
#43 Updated by Greg Shah almost 4 years ago
Good, I like it.
Does someone have the FWD equivalent for FlagsEnum ?
These are checked in to 4384b. I will copy Enum and FlagsEnum to 4231b. I haven't made edits yet.
#44 Updated by Constantin Asofiei almost 4 years ago
Greg, the first pass at DEFINE ENUM conversion is in 4231b rev 11503. Please review.
The oo/enums
tests convert, but FlagsEnum
, bitwise operands, GET-CLASS
and some other parts are not supported (so the code doesn't fully compile). What is relevant for non-flags enum compiles.
Note that I've changed another two cases of throwException
to printfln
, to not abend (for builtin functions and 'invalid operands' case).
#45 Updated by Greg Shah almost 4 years ago
I was just about to check in all the LegacyEnum, FlagsEnum, and the fixups of all the existing enum subclasses. It looks like we duplicated work on that. :(
#46 Updated by Greg Shah almost 4 years ago
From Marian:
One thing I wanted to mention about enums is those can be used in switch/case statements, also would be nice to make it easier to work with in Java... if we can't get some inline enum in place will probably continue with `equals`.
#47 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
From Marian:
One thing I wanted to mention about enums is those can be used in switch/case statements, also would be nice to make it easier to work with in Java... if we can't get some inline enum in place will probably continue with `equals`.
Yeah, I mean not only for supporting case
/when
statement on conversion but also actually using the LegacyEnums
in plain java using switch
.
#48 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
The two
byValue
andbyName
maps will always hold all the enum values.
Constantin, do you think two maps are needed to handle say about 5-10 enum members that one might use? Those will be used for GetEnum
and dynamic access but this is probably about 1% of the code out there, not the most common use of enums imho.
It could work better for flags as there could be more combinations only that there it will be useless cause the order of flags is not relevant, aka this will return the same enum member:
GetEnum('input,append,byreference,output,bind') GetEnum('input,output,append,byreference,bind')
#49 Updated by Greg Shah almost 4 years ago
One thing I wanted to mention about enums is those can be used in switch/case statements, also would be nice to make it easier to work with in Java... if we can't get some inline enum in place will probably continue with `equals`.
We plan to support all the features of enum including conversion of CASE
statements. However, due to the compatibility requirements (especially the need to maintain the 4GL inheritance hierarchy), we are not planning to use real Java enums. As you note, this causes a limitation in how we use the results in a Java switch
.
Please note that we already have a mis-match between switch
and CASE
. In the 4GL, WHEN
clauses can be complex expressions (they do not need to be constants) while in Java each case
must be a compile-time constant or an enum (which is technically not always considered a compile-time constant). The key point here is that an expression which is not constant cannot be placed as one of the alternatives. Java objects (except for enums and constants like String literals) cannot be used. There are some other differences, but these are the big ones. Any such usage needs to convert to if
/else if
/else
.
We could define a real Java enum in addition to the planned Java class + static members approach documented above. This would have to duplicate each member as a kind of "shadow" or "mirror" to the Java class. This would be error prone (it must be maintained in parallel with any changes to the Java class) and confusing since they would have to be different entities. Since they can't be substituted for each other, they would only work for specific use cases and the developer would have to understand the meaning. I think these drawbacks are too significant. I don't plan this approach.
The plan is to convert as if
/else if
/else
, unless someone has a better idea.
#50 Updated by Greg Shah almost 4 years ago
Constantin, do you think two maps are needed to handle say about 5-10 enum members that one might use? Those will be used for GetEnum and dynamic access but this is probably about 1% of the code out there, not the most common use of enums imho.
You are right that we could eliminate one map. But it would make some method implementations much more complicated. I prefer to take the extra map overhead in order to get the cleaner code. As you say, enums are not so common so there is not much of a memory hit especially since the maps are static.
#51 Updated by Constantin Asofiei almost 4 years ago
I've forgot to add the LegacySignature to the GetEnum method.
I don t plan to continue with enum conversion for now.
get-class
was added, but not yet commited. Will do later today.
#52 Updated by Greg Shah almost 4 years ago
I've checked in the first pass at the base classes. They are unfinished.
I've moved the creation of the data structures (byValue
, byName
and a new one called enums
) into LegacyEnum
. I want to have control over the structures (and be able to change them later) and there is no advantage to letting them be created in every subclass. Also, this reduces the generated code. I've made the changes to conversion to match.
The registerEnum()
call changed too. Of course, it must be executed in the static initializer before the createEnum()
calls.
I will continue with this tomorrow. Everything builds but I haven't tested any of it.
#53 Updated by Greg Shah almost 4 years ago
4231b revision 11511 is the next pass at enum runtime and conversion, still unfinished. Registration has been modified to pass the private 2-arg constructor as a lambda. This allows the create methods and future flags enum bitwise processing to have a callback for instantiation. There is no need for reflection or for the subclass to pass uninitialized instances. It also allows the state of the enum to be final.
#54 Updated by Greg Shah almost 4 years ago
Consider this code:
throw new LegacyErrorException( SysError.newInstance(String.format("Invalid value specified for %s:GetEnum", ObjectOps.getLegacyName(enumClass)), 15246));
LegacyErrorException
extends ErrorConditionException
which is the equivalent or the ERROR
condition in the 4GL. But this code bypasses the ERROR-STATUS
processing (recording error/warning state when NO-ERROR
is present. The proper processing for ERROR-STATUS
is done by the ErrorManager
.
Although we may need to make modifications to ErrorManager
better integrate with the OO 4GL exceptions (e.g. SysError
and related), I think we need to be using the ErrorManager
for these cases.
Am I missing something? Perhaps this is part of the reason that some incorrect behavior has been seen with NO-ERROR
?
Please note that the code above will result in tracked objects (e.g. in the session object chain) being created for the exceptions. If this is necessary, then it will need some attention as well in ErrorManager
.
#55 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
Consider this code:
[...]
LegacyErrorException
extendsErrorConditionException
which is the equivalent or theERROR
condition in the 4GL. But this code bypasses theERROR-STATUS
processing (recording error/warning state whenNO-ERROR
is present. The proper processing forERROR-STATUS
is done by theErrorManager
.
What we used, and seemed to work fine for our tests, is using undoThrow
from BlockManager
as in:
undoThrow(SysError.newInstance(String.format("Invalid value specified for %s:GetEnum", ObjectOps.getLegacyName(enumClass)), 15246));
#56 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
Please note that the code above will result in tracked objects (e.g. in the session object chain) being created for the exceptions. If this is necessary, then it will need some attention as well in
ErrorManager
.
This is correct, the error pop up the stack till is being catch
and if is not deleted in that catch
block it will linger in the session objects chain - eventually will be garbage collected at some point. It was recommended to delete error objects inside catch
block, before they changed the recommendation to not delete anything anymore and let the GC handle everything :)
A sample testcase in error_handling/test_object_chain.p
.
#57 Updated by Constantin Asofiei almost 4 years ago
Greg, regarding the static c'tor problem: I think the LegacyEnum.createEnumWorker
should return null
if Configuration.isRuntimeConfig()
returns false
- this indicates that we are not within the FWD server, and are doing conversion, which doesn't need the actual enum instance.
The other issue I mentioned about the object
for enum needing to be detached completed from context-local state still stands. I think simple use-case is to have a main
somewhere which sets runtime-config to true and just calls Enum.class
on the legacy enums.
#58 Updated by Constantin Asofiei almost 4 years ago
For the infinite recursion - the issue was that WidgetPool
was calling TM.getTransactionHelper()
and PM.getProcedureHelper()
before it was created, and these two depend back on it. I have a fix for it, to set these two fields in locate()
.
The "bad" news: it no longer fails at all when creating an enum instance, so it will no longer be obvious that it relies on context-local state.
#59 Updated by Constantin Asofiei almost 4 years ago
For FlagsEnum.cls
- see ClassDefinition.registerEnumMethods
.
Here, we need to add the proper signatures for all flags-related methods, which exist at the child class. There are these in FlagsEnum.cls
:
/* this is mocked here but in the 4GL it is implicitly added to the child enum class */ /* and the type is coded to that enum */ method public Progress.Lang.FlagsEnum SetFlag(input flag as Progress.Lang.FlagsEnum): end method. /* this is mocked here but in the 4GL it is implicitly added to the child enum class */ /* and the type is coded to that enum */ method public Progress.Lang.FlagsEnum ToggleFlag(input flag as Progress.Lang.FlagsEnum): end method. /* this is mocked here but in the 4GL it is implicitly added to the child enum class */ /* and the type is coded to that enum */ method public Progress.Lang.FlagsEnum UnsetFlag(input flag as Progress.Lang.FlagsEnum): end method.
The registered signatures need to have the proper FlagsEnum sub-type for the parameter and return type.
Also, the templates which generate the getEnum
Java method need to have LegacySignature
added, so these can be found via reflection.
#60 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
For
FlagsEnum.cls
- seeClassDefinition.registerEnumMethods
.Here, we need to add the proper signatures for all flags-related methods, which exist at the child class. There are these in
FlagsEnum.cls
:
Constantin, while is true in 4GL the type for return and input parameters of those methods is the actual FlagEnum at hand and not the base class those methods will just call the one in super and since you probably don't need to worry about code completion/check syntax in FWD I don't see much value in generating those in all flags enums. If needed we can fix that for reflection although I doubt anyone is using reflection on enums to check the signature of those methods :)
#61 Updated by Constantin Asofiei almost 4 years ago
Marian Edu wrote:
Constantin Asofiei wrote:
For
FlagsEnum.cls
- seeClassDefinition.registerEnumMethods
.Here, we need to add the proper signatures for all flags-related methods, which exist at the child class. There are these in
FlagsEnum.cls
:Constantin, while is true in 4GL the type for return and input parameters of those methods is the actual FlagEnum at hand and not the base class those methods will just call the one in super and since you probably don't need to worry about code completion/check syntax in FWD I don't see much value in generating those in all flags enums. If needed we can fix that for reflection although I doubt anyone is using reflection on enums to check the signature of those methods :)
Calling the one in super is fine. The problem is the return type - if the return type is the actual enum type, and not FlagsEnum, then you can chain these together. And this makes it mandatory to have the exact definitions both at parser time, and at conversion. Otherwise, chained calls will not work.
#62 Updated by Constantin Asofiei almost 4 years ago
Constantin Asofiei wrote:
Calling the one in super is fine. The problem is the return type - if the return type is the actual enum type, and not FlagsEnum, then you can chain these together. And this makes it mandatory to have the exact definitions both at parser time, and at conversion. Otherwise, chained calls will not work.
Looks like there is nothing specific to the custom flags enum definition which can be chained via instance method calls. Am I right? The enum members can only be accessed via the class name (statically).
If the above is true, then there is no need to inject these at the flags enum in FWD, and to add the signatures with the correct datatypes.
Another concern is: how about reflection? If you need to specify the actual enum name to get the i.e. ToggleFlag
method via reflection, then FWD will need changes at the runtime to properly resolve the method from the FlagsEnum
super-class.
#63 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
Constantin Asofiei wrote:
Calling the one in super is fine. The problem is the return type - if the return type is the actual enum type, and not FlagsEnum, then you can chain these together. And this makes it mandatory to have the exact definitions both at parser time, and at conversion. Otherwise, chained calls will not work.
Looks like there is nothing specific to the custom flags enum definition which can be chained via instance method calls. Am I right? The enum members can only be accessed via the class name (statically).
If the above is true, then there is no need to inject these at the flags enum in FWD, and to add the signatures with the correct datatypes.
Yes, this is correct. You could add the signatures if you want though - only methods SetFlag
, UnsetFlag
and ToggleFlag
are defined at each enum instance. IsFlagSet
is at FlagsEnum
level although given the input parameter one would have expected to also be at enum instance level.
ToString
, GetValue
, CompareTo
are from Enum
base class
Another concern is: how about reflection? If you need to specify the actual enum name to get the i.e.
ToggleFlag
method via reflection, then FWD will need changes at the runtime to properly resolve the method from theFlagsEnum
super-class.
It's all 'fake' on 4GL side as well, no static methods are visible in neither specific enum nor P.L.Enum or P.L.FlagsEnum. Enum members does not show as 'properties' although it behaves like a static ones so we will have to handle enums as a specific case in reflection anyway.
Ah, I had to fix the LegacyEnum
not to return null in createEnumWorker
and increment the value from the last one found if not specified otherwise it was making enums with the same value. And also the members in HashAlgorithmEnum
needed to be lowercase.
#64 Updated by Greg Shah almost 4 years ago
Calling the one in super is fine. The problem is the return type - if the return type is the actual enum type, and not FlagsEnum, then you can chain these together. And this makes it mandatory to have the exact definitions both at parser time, and at conversion. Otherwise, chained calls will not work.
Looks like there is nothing specific to the custom flags enum definition which can be chained via instance method calls. Am I right? The enum members can only be accessed via the class name (statically).
If the above is true, then there is no need to inject these at the flags enum in FWD, and to add the signatures with the correct datatypes.
Yes, this is correct. You could add the signatures if you want though - only methods
SetFlag
,UnsetFlag
andToggleFlag
are defined at each enum instance.IsFlagSet
is atFlagsEnum
level although given the input parameter one would have expected to also be at enum instance level.
I'm planning to emit specific versions of these in each flags enum subclass. They will call the worker at the parent level and are just there to implement the correct signatures/typing.
ToString
,GetValue
,CompareTo
are fromEnum
base class
Yes, only the GetEnum
needs this treatment. I have to fix the annotations there.
Another concern is: how about reflection? If you need to specify the actual enum name to get the i.e.
ToggleFlag
method via reflection, then FWD will need changes at the runtime to properly resolve the method from theFlagsEnum
super-class.It's all 'fake' on 4GL side as well, no static methods are visible in neither specific enum nor P.L.Enum or P.L.FlagsEnum. Enum members does not show as 'properties' although it behaves like a static ones so we will have to handle enums as a specific case in reflection anyway.
Only the subclasses will have the annotations and the workers will be protected
. I think this should yield the same results.
Ah, I had to fix the
LegacyEnum
not to return null increateEnumWorker
and increment the value from the last one found if not specified otherwise it was making enums with the same value. And also the members inHashAlgorithmEnum
needed to be lowercase.
I've fixed these issues in my code. Thanks!
#65 Updated by Greg Shah almost 4 years ago
I'm planning to emit specific versions of these in each flags enum subclass. They will call the worker at the parent level and are just there to implement the correct signatures/typing.
To be clear, the reason I plan this is so that we can get full compatibility. I don't want to leave something partial here and then have to find/remember it and fix it later. The extra cost in generated code and conversion effort is minimal.
#66 Updated by Greg Shah almost 4 years ago
Constantin, some questions:
1. Do I need to add the @LegacySignature
annotation to the converted enum members?
@LegacySignature(type = Type.VARIABLE, name = "my-legacy-enum-name") public static final object<? extends AccessMode> myLegacyEnumName;
2. I get a very large number of warnings for the implicit methods (2 GetEnum
variants for regular enums and the IsFlagSet
/SetFlag
/ToggleFlag
/UnsetFlag
for flags enums). This doesn't seem to have any real negative affect, the converted enum classes are correct and the code that references these methods seems OK too. But the logs are full of these warnings.
./oo/enums/DefineEnum1.cls WARNING: Can't locate converted method for getenum(KW_INPUT int64) in com.goldencode.testcases.oo.enums.DefineEnum1 WARNING: Can't locate converted method for getenum(KW_INPUT character) in com.goldencode.testcases.oo.enums.DefineEnum1 ... ./oo/enums/bitwise_operations_unknown_value.p WARNING: Can't locate converted method for progress.lang.flagsenum() in com.goldencode.p2j.oo.lang.FlagsEnum WARNING: Can't locate converted method for unsetflag(KW_INPUT object<? extends progress.lang.flagsenum>) in com.goldencode.p2j.oo.lang.FlagsEnum WARNING: Can't locate converted method for setflag(KW_INPUT object<? extends progress.lang.flagsenum>) in com.goldencode.p2j.oo.lang.FlagsEnum WARNING: Can't locate converted method for toggleflag(KW_INPUT object<? extends progress.lang.flagsenum>) in com.goldencode.p2j.oo.lang.FlagsEnum
#67 Updated by Constantin Asofiei almost 4 years ago
Greg Shah wrote:
1. Do I need to add the
@LegacySignature
annotation to the converted enum members?
As I understand, we have a separate registry for these in the LegacyEnum class, which will be used for resolving them via reflection. If this is correct, we don't need the LegacySignature
for enum members, as these will not be really used.
2. I get a very large number of warnings for the implicit methods (2
GetEnum
variants for regular enums and theIsFlagSet
/SetFlag
/ToggleFlag
/UnsetFlag
for flags enums). This doesn't seem to have any real negative affect, the converted enum classes are correct and the code that references these methods seems OK too. But the logs are full of these warnings.
I think this can be solved in ClassDefinition.registerGetEnumMethods
- add the javaname to javaMethodNames
map, like:
String legacySignature1 = SymbolResolver.calculateLegacySignature(mdat1.name, mdat1.signature); javaMethodNames.put(legacySignature1, "getEnum"); String legacySignature2 = SymbolResolver.calculateLegacySignature(mdat2.name, mdat2.signature); javaMethodNames.put(legacySignature2, "getEnum");
I assume you added unsetFlag
, setFlag
and toggleFlag
to registerGetEnumMethods
, too?
#68 Updated by Marian Edu almost 4 years ago
There is an issue with using an Enum
's GetEnum
method passed as input parameter to another method. That fails during conversion, the method loadConvertedClass
in SystemResolver
being called with null
as filename for whatever reason.
[java] EXPRESSION EXECUTION ERROR: [java] --------------------------- [java] nameinfo = loadConvertedClass(fname, false) [java] ^ { Can not find class associated with null } [java] --------------------------- [java] ERROR: [java] com.goldencode.p2j.pattern.TreeWalkException: ERROR! Active Rule: [java] ----------------------- [java] RULE REPORT [java] ----------------------- [java] Rule Type : WALK [java] Source AST: [ GetHash ] BLOCK/INNER_BLOCK/BLOCK/ASSIGNMENT/EXPRESSION/OBJECT_INVOCATION/OO_METH_RAW/ @16:17 {17179869350} [java] Copy AST : [ GetHash ] BLOCK/INNER_BLOCK/BLOCK/ASSIGNMENT/EXPRESSION/OBJECT_INVOCATION/OO_METH_RAW/ @16:17 {17179869350} [java] Condition : nameinfo = loadConvertedClass(fname, false) [java] Loop : false [java] --- END RULE REPORT --- [java] at com.goldencode.p2j.pattern.PatternEngine.run(PatternEngine.java:1070) [java] at com.goldencode.p2j.convert.TransformDriver.processTrees(TransformDriver.java:569) [java] at com.goldencode.p2j.convert.ConversionDriver.back(ConversionDriver.java:573) [java] at com.goldencode.p2j.convert.TransformDriver.executeJob(TransformDriver.java:956) [java] at com.goldencode.p2j.convert.ConversionDriver.main(ConversionDriver.java:1025) [java] Caused by: com.goldencode.expr.ExpressionException: Expression execution error @1:12 [OO_METH_RAW id=17179869350] [java] at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:275) [java] at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:210) [java] at com.goldencode.p2j.pattern.PatternEngine.apply(PatternEngine.java:1633) [java] at com.goldencode.p2j.pattern.PatternEngine.processAst(PatternEngine.java:1531) [java] at com.goldencode.p2j.pattern.PatternEngine.processAst(PatternEngine.java:1479) [java] at com.goldencode.p2j.pattern.PatternEngine.run(PatternEngine.java:1034) [java] ... 4 more [java] Caused by: com.goldencode.expr.ExpressionException: Expression execution error @1:12 [java] at com.goldencode.expr.Expression.execute(Expression.java:484) [java] at com.goldencode.p2j.pattern.Rule.apply(Rule.java:497) [java] at com.goldencode.p2j.pattern.Rule.executeActions(Rule.java:745) [java] at com.goldencode.p2j.pattern.Rule.coreProcessing(Rule.java:712) [java] at com.goldencode.p2j.pattern.Rule.apply(Rule.java:534) [java] at com.goldencode.p2j.pattern.Rule.executeActions(Rule.java:745) [java] at com.goldencode.p2j.pattern.Rule.coreProcessing(Rule.java:712) [java] at com.goldencode.p2j.pattern.Rule.apply(Rule.java:534) [java] at com.goldencode.p2j.pattern.Rule.executeActions(Rule.java:745) [java] at com.goldencode.p2j.pattern.Rule.coreProcessing(Rule.java:712) [java] at com.goldencode.p2j.pattern.Rule.apply(Rule.java:534) [java] at com.goldencode.p2j.pattern.RuleContainer.apply(RuleContainer.java:585) [java] at com.goldencode.p2j.pattern.RuleSet.apply(RuleSet.java:98) [java] at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:262) [java] ... 9 more [java] Caused by: java.lang.ClassNotFoundException: Can not find class associated with null [java] at com.goldencode.p2j.uast.SymbolResolver.loadConvertedClass(SymbolResolver.java:1854)
A test case in oo/enums/test_enum_input_param.p, using an intermediate variable solves the issue. First I've thought it could be because there were no qualified
set in getEnum
methods in HashAlgorithmEnum
but adding that do not seem to help.
#69 Updated by Greg Shah almost 4 years ago
4231b revision 11564 is the first full implementation of enum support. Conversion is working well and has been tested. Runtime has all features fully implemented but has not been tested at all. In particular, all features documented in discussed #4349-5, #4349-11 (excluding the ControlFlowOps
changes) and #4349-18 are all implemented. This includes the expected changes to ObjectOps
and the enum-related reflection implementations in LegacyClass
. I did not touch the GET-CLASS()
implementation.
I also did NOT implement anything related to the error handling concerns in #4602-9. I think there are definitely changes needed there and I still have open questions to be discussed there. Regardless, I don't think that these error issues will be a problem for the enum tests so I have not worked on the error stuff.
I've tested everything in the testcases oo/enums/
directory, including oo/enums/test_enum_input_param.p
. Everything converts and compiles successfully. My checks on the resultng code show it to be as expected, though I did not check every file manually.
I will post next to show examples of how various things convert. One important note: in the end, due to the limitations of Java generics (type erasure and/or constraints on type inference), there was no compile time advantage to emitting the SetFlag
/ToggleFlag
/UnsetFlag
methods into the converted flags enum classes. Instead, I implemented these in FlagsEnum
directly using generics and the is an added runtime type check to ensure different flags enum types can't intermix. This would have been needed anyway, since the emitted code would have required casting to make it compile. In other words, emitting the code into the FlagsEnum
subclasses will only compile by explicitly casting the instances, which breaks the idea is of compile-time type safety. Lacking that advantage, it is much better to avoid the extra emitted code, thus leaving the converted implementation much smaller.
As part of this, I copied or updated the following Java code from 4384b rev 11565.
modified src/com/goldencode/p2j/oo/core/HashAlgorithmEnum.java modified src/com/goldencode/p2j/oo/logging/LogLevelEnum.java modified src/com/goldencode/p2j/oo/net/UriEncodingTypeEnum.java modified src/com/goldencode/p2j/oo/reflect/AccessMode.java modified src/com/goldencode/p2j/oo/reflect/DataType.java added src/com/goldencode/p2j/oo/reflect/Event.java added src/com/goldencode/p2j/oo/reflect/Flags.java renamed src/com/goldencode/p2j/oo/reflect/Method.java => src/com/goldencode/p2j/oo/reflect/Legacy4GLMethod.java added src/com/goldencode/p2j/oo/reflect/LegacyConstructor.java added src/com/goldencode/p2j/oo/reflect/Parameter.java added src/com/goldencode/p2j/oo/reflect/ParameterMode.java added src/com/goldencode/p2j/oo/reflect/Property.java added src/com/goldencode/p2j/oo/reflect/Variable.java
Although the HashAlgorithmEnum
and UriEncodingTypeEnum
have been modified in later 4384b versions, I think the versions in 4231b are correct. In addition, I made many corrections to the code formatting etc... in the versions checked into 4231b so these should be versions that remain after 4384b is rebased from 4231b.
I think it is probably a good time to pull the recent changes from 4384b into 4231b and reset 4384b.
Marian: from there, your team should be able to move ahead with enums pretty easily. My only concern is that I've not yet tested the runtime and I would be very surprised if there are no bugs. So if that is going to get in the way, then we may need to wait to reset 4384b until after any runtime fixes are made.
Constantin: I would like you to implement any changes needed for ControlFlowOps
, as was discussed in #4349-11. I really am not clear on what is needed.
#70 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
Marian: from there, your team should be able to move ahead with enums pretty easily. My only concern is that I've not yet tested the runtime and I would be very surprised if there are no bugs. So if that is going to get in the way, then we may need to wait to reset 4384b until after any runtime fixes are made.
Greg, the current version we have is missing implementation of getEnum
methods so we already have limited runtime support, we should probably do the rebase and with that we can run some tests and see if we find anything.
#71 Updated by Greg Shah almost 4 years ago
Marian: Is 4384b missing any changes from your team?
Constantin: Please merge the 4384b changes into 4231b, avoiding the list of files from #4349-69. Then 4384b can be reset from 4231b.
#72 Updated by Constantin Asofiei almost 4 years ago
#73 Updated by Greg Shah almost 4 years ago
Is the #4349-69 list of files meant to be 'dead' in 4384b, and their current 4231b version survive?
Yes, exactly this.
#74 Updated by Greg Shah almost 4 years ago
Implementation of the built-in OO class Progress.Util.EnumHelper
is not needed because it is only used for .NET enums which won't exist in the converted system. (This is just a note to remember that idea.)
#75 Updated by Greg Shah almost 4 years ago
It seems like ObjectOps.WorkArea.legacyNames
and ObjectOps.WorkArea.name2cls
are both truly static data. It seems that the only reason these would need to be context-local is if we were to support the same fully qualified legacy class name mapped to more than 1 converted class. Such an approach would only work based on dynamic propath differences at runtime. But I think we would need to make other changes to support that idea. And it is a very bad practice, so I wasn't planning to support it.
Anyway, by moving those two data structures into true static data, it can make it possible to lookup the enum superclasses without a constructor registration. I could change the code to register them in every context as soon as the first enum class is loaded but it would be a bit messy that way.
Constantin: What is your opinion?
#76 Updated by Constantin Asofiei almost 4 years ago
Greg Shah wrote:
It seems like
ObjectOps.WorkArea.legacyNames
andObjectOps.WorkArea.name2cls
are both truly static data. It seems that the only reason these would need to be context-local is if we were to support the same fully qualified legacy class name mapped to more than 1 converted class. Such an approach would only work based on dynamic propath differences at runtime. But I think we would need to make other changes to support that idea. And it is a very bad practice, so I wasn't planning to support it.
Yes, we can't support duck-typing without getting rid of the 'true inheritance' in the conversion. We would need to implement our own 'dynamic inheritance' and polymorphism, to make that part work.
I'm OK with making themAnyway, by moving those two data structures into true static data, it can make it possible to lookup the enum superclasses without a constructor registration. I could change the code to register them in every context as soon as the first enum class is loaded but it would be a bit messy that way.
- the changes takes care of the concurrent access to these maps.
- the context-local class initialization (the 'static associated c'tor' of the converted class) is still executed properly. You need to take care to not short-circuit this when, for example, class Foo (with a static c'tor) was registered in
legacyNames
orname2cls
by context A, and context B wants to initialize it, too (don't forget that the static c'tor code in 4GL is always context-local and needs to be called by each context when first accessing that class).
#77 Updated by Greg Shah almost 4 years ago
- % Done changed from 0 to 100
- Status changed from WIP to Review
4231b rev 11585 completes the enum implementation. With the fixes in this revision, all of the oo/enums/*.p
test programs convert, compile and run successfully. This includes all functionality documented in this task except for anything in ControlFlowOps
. It is not clear if the CFS stuff is actually needed.
The other thing that is not done is serialization, but I would plan for that to be done when OO serialization is implemented in general (#4658).
Constantin: Please review.
#78 Updated by Constantin Asofiei almost 4 years ago
ObjectOps.registerClass
- now the method is a no-op, theregisterClass(clsName, cls);
call is not longer there.
Otherwise, the changes look good.
#79 Updated by Greg Shah almost 4 years ago
ObjectOps.registerClass
- now the method is a no-op, theregisterClass(clsName, cls);
call is not longer there.
registerClass(clsName, cls);
is used heavily in om.goldencode.p2j.oo.*
as well as from object.initialize()
.
I'm removing the one in object.initialize()
since it is not needed (the getLegacyName()
does it internally). But the other ones are still used. If they can be removed, then we should do so.
#80 Updated by Constantin Asofiei almost 4 years ago
Greg, I meant the public static void registerClass(Class extends _BaseObject_> cls, boolean hierarchy)
. I can't find any calls for this anymore, but Marian found at least a case which required it. And its body is missing the registerClass(clsName, cls);
call.
#81 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
ObjectOps.registerClass
- now the method is a no-op, theregisterClass(clsName, cls);
call is not longer there.
registerClass(clsName, cls);
is used heavily inom.goldencode.p2j.oo.*
as well as fromobject.initialize()
.I'm removing the one in
object.initialize()
since it is not needed (thegetLegacyName()
does it internally). But the other ones are still used. If they can be removed, then we should do so.
The usage in p2j.oo
was mainly in "builders" and to assert is type, for that getLegacyClass
that takes a character/string parameter was used along with static string properties for various class/interface names used. Since resolveClass
doesn't do much of resolving unless the class was already registered someone must have thought registerClass
is the cure. After all the generated code does a lot of registerClass
as much as I can tell. The real issue is probably with resolveClass
, one should be able to get the class for name if that exists in PROPATH, regardless if it was registered or not before.
#82 Updated by Marian Edu almost 4 years ago
Constantin Asofiei wrote:
Greg, I meant the
public static void registerClass(Class extends _BaseObject_> cls, boolean hierarchy)
. I can't find any calls for this anymore, but Marian found at least a case which required it. And its body is missing theregisterClass(clsName, cls);
call.
A new method is used instead - getAssignableFrom
that does not walk through the whole hierarchy but stop as soon as it finds the class/interface we're interested in. The use case was for resolving method signature when the qualified
parameter used in LegacySignature
was a class/interface that was not already registered - ControlFlow.resolveLegacyEntry
. In our case the input parameter was defined as an interface but an actual implementation was passed and there were no variable defined of that type (as suggested by Constantin that should have taken care of registration).
Again, if resolveClass
is fixed then there will be no need of doing all that manually.
#83 Updated by Marian Edu almost 4 years ago
Greg Shah wrote:
ObjectOps.registerClass
- now the method is a no-op, theregisterClass(clsName, cls);
call is not longer there.
registerClass(clsName, cls);
is used heavily inom.goldencode.p2j.oo.*
as well as fromobject.initialize()
.I'm removing the one in
object.initialize()
since it is not needed (thegetLegacyName()
does it internally). But the other ones are still used. If they can be removed, then we should do so.
Removed all use of ObjectOps.registerClass
from p2j.oo
package, it's now only used from within ObjectOps
and object
ctor as far as I can see. As it is now the call in ObjectOps.getLegacyClass
is a nop since resolveClass
will never resolve anything that was not already registered and it it was then what is the point of calling it again.
The resolveClass
needs to be completed to search through propath as for external procedures, there is one catch here where the class name in FWD is different as of that in 4GL so that mapping should be considered first before looking through the file system.
#84 Updated by Greg Shah almost 4 years ago
- Status changed from Review to Closed
Task branch 4231b has been merged to trunk as revision 11347.
#85 Updated by Marian Edu almost 3 years ago
Greg Shah wrote:
Task branch 4231b has been merged to trunk as revision 11347.
One issue there with flag enums, logical operators (and, or, not, xor) should return the same enum type so most probably the same processing as for getEnum
methods should be taken for those - aka, take those out of FlagsEnum
and implement them in each flags enum class. Or maybe use cast during conversion but either way the result should of that specific enum type not FlagsEnum
as it is now cause the generated code does not compile as-is.
Simple test case in oo/enums/test_flag_enum_bitwise_ops.p
:
oo/enums/Permissions.cls oo/enums/FlagParams.cls oo/enums/test_flag_enum_bitwise_ops.p
#86 Updated by Greg Shah almost 3 years ago
One issue there with flag enums, logical operators (and, or, not, xor) should return the same enum type so most probably the same processing as for
getEnum
methods should be taken for those - aka, take those out ofFlagsEnum
and implement them in each flags enum class. Or maybe use cast during conversion but either way the result should of that specific enum type notFlagsEnum
as it is now cause the generated code does not compile as-is.
This is resolved in 3821c revision 12684. I inserted a cast for the return value of all bitwise operators.