PDA

View Full Version : Commands not picked up by code sense



Anders Ohrt
10-Nov-2009, 05:48 AM
I have created some custom commands, using #Command. I had hoped that Code Sense would pick them up, which I reported as a bug against 14.0, but 15.1 still does not pick them up.

Dennis Piccioni
10-Nov-2009, 10:26 AM
Hi Anders,

could you remind of where you reported this? I've searched my emails and the forums and can't find the thread. I also can't find a report of this in BugTracker.

Anders Ohrt
10-Nov-2009, 11:54 AM
could you remind of where you reported this? I've searched my emails and the forums and can't find the thread. I also can't find a report of this in BugTracker.

I think it was one of the things I sent directly to Stephen.

Dennis Piccioni
10-Nov-2009, 12:09 PM
Ok, then I don't know the extent of that conversation.

I would consider this as suggestion, if at all, not a bug, since we really have not supported custom commands in a long long time (longer than the 14 years I have worked at DAW).

Anders Ohrt
10-Nov-2009, 12:36 PM
I would consider this as suggestion, if at all, not a bug, since we really have not supported custom commands in a long long time (longer than the 14 years I have worked at DAW).

But, there is nothing in the #command documentation saying it's obsolete. Is it? I don't understand this... I'm not replacing any DAW commands, just creating my own, like this:



//
// ClxClearArray, clears an array.
//
// Usage: ClxClearArray saArray
//
#COMMAND ClxClearArray R .
Move (ResizeArray(!1, 0)) to !1
#ENDCOMMAND // ClxClearArray

John Tuohy
10-Nov-2009, 01:51 PM
Anders,

There are several different issues here.

The lack of code-sense support of custom commands is by design and this was a design decision we spent some time discussing. Supporting custom commands is not easy. It requires a lot more than just adding a new command to the list. The macro command syntax does not provide enough information for code-sense to figure out how to use it. To support the current command set we had to create a mechanism for describing how code-sense should use every command. That mechanism is internal. We did not make this external because it was going to require a lot more work and we felt that it would not be all that useful to developers. It would be hard to configure and we really don't recommend that developers create their own commands.

We have received suggestions that we support code-sense for custom commands and that suggestion is logged. We have no current plans to implement this suggestion.

Your point is valid that we do not mention anywhere in the documentation that the creation of macro commands is either obsolete (it's not) or not recommended (it's not recommended). We need to update our documentation to reflect this. The use of custom commands has never been supported in the product (where "support" means you can ask technical support for assistance). At some point we got it into our heads to document the compiler commands (e.g., #command, #push, #if2type). While our intentions were good perhaps this was not such a wise move. Documenting these commands implies that they are something developers should use. We should have added a caveat saying "we don't recommend that you use this" although at that point someone might ask why we documented them at all. Even if we added these warnings the fact that they are even documented implies that they can be used which easily gets translated to should be used. Actually one of the reasons we chose not to support custom commands in code-sense was that supporting this implies that it is something that developers should use. Someone would contact technical support about a custom command and when told that this is not supported would ask "well why is it supported in code-sense".

As an aside, it was not easy to decide what should be supported in code-sense and what should not. We did not add obsolete commands to code-sense (e.g., Left, Mid) even though you will find them all over everyone's code (including our packages). There are arguments for including and excluding them. At some point you have to consider the trade-offs and make a decision.

-John

Michael Mullan
10-Nov-2009, 02:22 PM
John,

On a related note then, is there a documented way that we can "teach" code sense about the custom commands that we use? and if so is this A: Documented, B:Supported, C: recommended?

I ask because I also have a couple of commands I've defined in my base Library, and they are available in pretty much every program I write.

In truth I don't care if I give this code to another developer who then has to do the research the hard way to use my commands...

My 2c.

John Tuohy
10-Nov-2009, 02:44 PM
There is no way to do this.

-John

Anders Ohrt
10-Nov-2009, 03:20 PM
John,

Thanks for the explanation, that makes more sense. I can agree that commands should be discouraged, just like macros in C should be discouraged, and since I had to figure out some very complex things regarding commands I can see why you don't want to give technical assistance to developers who get trapped here, but there are things that simply cannot be done easily without commands.

For example, we language translate our entire database so vdfquery and errors shows the table names and columns in the native language. In order to do this, we need to register a translation for every column, so we need to get the table number and column number and basically do

Set ColumnDescription iTableNumber iColumnNumber to (Translate("Some column")) The table number is easy, it's just SomeTable.File_Number, but the column number is harder. There is no way to get the column number at compile time, so we would need to have a runtime loop looking at the names, and there would be many ugly hard-coded name-strings, and do something like

Set ColumnDescription SomeTable.File_Number (FindColumnNumber(SomeTable.File_Number, "SomeColumn")) to (Translate("Some column"))And, if we rename the column this silently failes.
With a short command, we are able to do this:

SetColumnDescription iTable.SomeColumn to (Translate("Some column"))We have compile time type-checking, and everything just works great. But, no code sense.

I understand this is a rare practice, and scanning our code we only have 12 commands in total. 10 of them are for this kind of "get the table and column number" and rarely used, but the other two we use very frequently and they are syntactic sugar for arrays. ClxClearArray does a ResizeArray to 0 and ClxArrayAppend appends an element to the end of an array. Both these are one-liners, but we need to write the array name twice and these are _very_ often used constructs in our code, so I created a command as a macro for this. The current array interface is overly verbose (always naming the array twice).

Michael Mullan
10-Nov-2009, 03:28 PM
pity.

Still I did get the token set up in the VisualDataflex.LNG file so it makes my command a pretty blue color :-)

Michael.

Anders Ohrt
10-Nov-2009, 03:49 PM
I understand this is a rare practice, and scanning our code we only have 12 commands in total. 10 of them are for this kind of "get the table and column number" and rarely used

I just realized that when I wrote those commands, a couple of years ago, it was the only solution. But, since then we've actually switched to generating our own customer FD files, which contains the original DAW FD content, and then some "extra candy", like

Define Table.Column.ColumnNumber for 42I can add

Define Table.Column.TableNumberAndColumnNumber for 47 42and then pass the two integers as one.


Set ColumnDescription Table.Column.TableNumberAndColumnNumber to (Translate("Some column"))

Procedure Set ColumnDescription Integer iTableNumber Integer iColumnNumber String sTranslation
...
That still leave the two array commands, though. I could create procedures that took the arrays as byref's, but the whole point was to make it short and unbreakable and with a byref it would just be a matter of time before someone forgot the & before the array... I assume adding new commands is on the list of forbidden things at DAW?

Dennis Piccioni
10-Nov-2009, 03:55 PM
Hi Anders,

well, as John stated, it's not recommended or supported, but if it works for you and you have a backup plan, it's up to you.

Gregg Finney
10-Nov-2009, 09:41 PM
John,
Code-sense still works on the Append command though.

Gregg Finney

Garret Mott
10-Nov-2009, 10:34 PM
John,
Code-sense still works on the Append command though.

Gregg Finney

Ssshhhh! I like that it still works - as the old way is much easier to use.... :p

Ola Eldoy
11-Nov-2009, 03:06 AM
As long as there is code out in the wild that uses obsolete or not-recommended practices, it certainly is helpful to have documentation available to make such code at least theoretically decipherable. When such documentation also gives recommendations on usage (or perhaps preferred non-usage) that is even better.

So please keep the documentation :)

Peter Crook
11-Nov-2009, 04:15 AM
Anders

Your reply flags up what has always seemed to me to be a big hole in DataFlex - the inability to get a field number without using a literal string (which doesn't get flagged by the compiler if it's invalid). Has DAW any plans to address this?

Sonny Falk
11-Nov-2009, 12:40 PM
Anders

Your reply flags up what has always seemed to me to be a big hole in DataFlex - the inability to get a field number without using a literal string (which doesn't get flagged by the compiler if it's invalid). Has DAW any plans to address this?

Actually, there's a very simple and fully supported technique for just that purpose, the File_Field and Field keywords that you use all the time with DDs for example.



Procedure Foo Integer iFile Integer iField
Showln "iFile=" iFile " iField=" iField
End_Procedure

Procedure TestIt
Send Foo File_Field Customer.Name
End_Procedure

Anders Ohrt
11-Nov-2009, 12:52 PM
Actually, there's a very simple and fully supported technique for just that purpose, the File_Field and Field keywords that you use all the time with DDs for example.


I heard about the File_Field keyword in some older news postings a long time ago, but understood it as needing some magic support in the procedure so work. I didn't realize it was generic. That's great, thanks.

John Tuohy
11-Nov-2009, 01:06 PM
John,
Code-sense still works on the Append command though.

Gregg Finney

Actually it's not an obsolete command.

-John

John Tuohy
11-Nov-2009, 01:10 PM
I heard about the File_Field keyword in some older news postings a long time ago, but understood it as needing some magic support in the procedure so work. I didn't realize it was generic. That's great, thanks.

This should work with any message parameter. We also have the commands Get_FieldNumber and Get_FileNumber.

-John

Peter Crook
12-Nov-2009, 04:59 AM
This should work with any message parameter. We also have the commands Get_FieldNumber and Get_FileNumber.

-John
Hurray!! Just what I was looking for! Sorry to have raised this unnecessarily. Maybe a link in the help from Field_Map would have helped. And would you ever use Field_Map when you could more easily and safely use Get_FieldNumber?

BTW What does DF_FIELD_NUMBER do and how do you use it? The Help is unhelpful. Is it
Get_Attribute DF_FIELD_NUMBER file.field to iField In which case when would you use this and when Get_Fieldnumber? The Help for Get_Attribute (Example - Index Attribute) seems to suggest that its usage would be:
Get_Attribute DF_FIELD_NUMBER iFile iField to iField which seems somewhat pointless.

Vincent Oorsprong
12-Nov-2009, 05:06 AM
Peter,

Get_FieldNumber is a command that at compile time takes the fieldnumber from the passed file.field reference and stores that in a variable. Use as:


Get_FieldNumber Customer.Name to iField
Get_Field_Value Customer.File_Number iField to sValue

The API attribute DF_FIELD_NUMBER is a different thing. It is a pretty useless attribute...

Anders Ohrt
12-Nov-2009, 05:19 AM
Actually, there's a very simple and fully supported technique for just that purpose, the File_Field and Field keywords that you use all the time with DDs for example.


I was just about to start implementing this, but noticed I need to names often, not the numbers. There is no was to get the names at compile this, except via a command, is there?

Like this:


Send Stop_Box (SFormat("Error in field %1.%2", File_Name Customer, File_Field_Name Customer.Name))

// Want output "Error in field Customer.Name"

Peter Crook
12-Nov-2009, 05:20 AM
Peter,

Get_FieldNumber is a command that at compile time takes the fieldnumber from the passed file.field reference and stores that in a variable. Use as:


Get_FieldNumber Customer.Name to iField
Get_Field_Value Customer.File_Number iField to sValue
Hi Vincent
I understood that. I just wondered if there were any circumstances in which the use of Field_Map would be better.


The API attribute DF_FIELD_NUMBER is a different thing. It is a pretty useless attribute......is this because you have to pass it the field number as a parameter?

Anders Ohrt
12-Nov-2009, 05:48 AM
The API attribute DF_FIELD_NUMBER is a different thing. It is a pretty useless attribute...

I think it's just there for completions sake. Given a field number, you can get any attribute that field has. One of the fields attributes is it's number. Unnecessary, but it completes the API.

Focus
12-Nov-2009, 06:15 AM
As a side note I've found Field_map to be a very "expensive" command time wise. I think it might even read the FD or something everytime you call it. So I make up my own lookup array at program start

Vincent Oorsprong
12-Nov-2009, 06:49 AM
Peter,

Field_Map command enumerates the columns of a table at runtime and when the name of a column matches with the passed column name argument the column number is returned. Get_FieldNumber is a compile time solution. Performance wise is the Get_FieldNumber more efficient in the same situation.

Vincent Oorsprong
12-Nov-2009, 06:55 AM
Anders,

If you can access a DDO for the table and column number you can use the Field_Label function.

Anders Ohrt
12-Nov-2009, 07:20 AM
If you can access a DDO for the table and column number you can use the Field_Label function.

I don't have access to that, no. I want a way given nothing else than the Table.Column token.

Anders Ohrt
12-Nov-2009, 07:23 AM
And would you ever use Field_Map when you could more easily and safely use Get_FieldNumber?

If you programmatically change the DB, or access a foreign DB, you cannot use Get_FieldNumber but Field_Map would work.

Peter Crook
12-Nov-2009, 08:01 AM
Would Get_Attribute DF_FILE_LOGICAL_NAME and Get_Attribute DF_FIELD_NAME (after a Get_FieldNumber) do it?

Anders Ohrt
12-Nov-2009, 08:16 AM
Would Get_Attribute DF_FILE_LOGICAL_NAME and Get_Attribute DF_FIELD_NAME (after a Get_FieldNumber) do it?

No, I want to get the at name compile time, that's runtime. And, it's 3 extra lines.

The reason is we have places where we need to pass the names in a type safe manner (so hard coding them is not an option and it must be compile time), but since it's very many identical lines I want one call per line. Using Get_Attribute would mean 4 lines, which lowers readability. I first had a command to do this:



CmdLogChanges Customer.CustomerNo
CmdLogChanges Customer.Name
[...]
Since they are not recommended, I now switched to using our pre-generated package:



Send LogChanges Customer.TableName Customer.CustomerNo.ColumnName
Send LogChanges Customer.Name.TableName Customer.Name.ColumnName
[...]


But this relies on the custom generated packages, which is ugly.

Todd Forsberg
12-Nov-2009, 08:20 AM
It reads the TAG file. If your TAG file is missing, the the Field_Map fails.

Dennis Piccioni
12-Nov-2009, 09:53 AM
I would wrap the ugly 4 commands you need to get this in a function with a useful name and use it inline.

Dennis Piccioni
12-Nov-2009, 09:54 AM
That's absolutely it, it's there for sake of completeness.

Dennis Piccioni
12-Nov-2009, 09:55 AM
Hi Peter,

I'm going to update the notes and links for these commands to make sure they point developers reading the doc in the right direction.

Larry R Pint
12-Nov-2009, 11:51 AM
Hi Vincent
I understood that. I just wondered if there were any circumstances in which the use of Field_Map would be better.
...is this because you have to pass it the field number as a parameter?

One case where field_map was the best solution I could come up with:

Most of the tables in my SQL database have 4 fields used to storing the created by user and date/time and the updated by user and date/time. I created a custom DD class that uses the field_map command in the end_construct_object procedure (with ignore error of error 4100) to set properties that contain the field numbers. In the creating and updating procedures I use these properties to update the field values as necessary. I don't believe this could be done in this generic way (in a DD class) with get_fieldnumber.



Procedure Construct_Object
Forward Send Construct_Object
// Define new Properties:
Property Integer piUDateColumn 0
Property Integer piUUserColumn 0
Property Integer piCDateColumn 0
Property Integer piCUserColumn 0
// Create child objects:
// Set property values:
End_Procedure

Procedure End_Construct_Object
Forward Send End_Construct_Object
// Add your code that needs to be executed at the end of the object construction here:
Integer iMyField iMyFile
Get Main_File to iMyFile
Send Ignore_Error of Error_Object_ID 4100
Field_Map iMyFile "UpdateStamp" to iMyField
Set piUDateColumn to iMyField
Field_Map iMyFile "CreateStamp" to iMyField
Set piCDateColumn to iMyField
Field_Map iMyFile "UpdateUserKey" to iMyField
Set piUUserColumn to iMyField
Field_Map iMyFile "CreateUserKey" to iMyField
Set piCUserColumn to iMyField
Send Trap_Error of Error_Object_ID 4100
End_Procedure

Procedure Update
Integer iMyFile iUser iDateField iUserField
DateTime dtStamp
Forward Send Update
Get Main_File to iMyFile
Get piUDateColumn to iDateField
Get piUUserColumn to iUserField
If (iDateField > 0) Begin
Move (CurrentDateTime()) to dtStamp
Set_Field_Value iMyFile iDateField to dtStamp
End
If (iUserField > 0) Begin
Get piUserID of oApplication to iUser // piUser is a property of the application object
Set_Field_Value iMyFile iUserField to iUser
End
End_Procedure // Update
// Creating:
Procedure Creating
Integer iMyFile iUser iDateField iUserField
DateTime dtStamp
Forward Send Creating
Get Main_File to iMyFile
Get piCDateColumn to iDateField
Get piCUserColumn to iUserField
If (iDateField > 0) Begin
Move (CurrentDateTime()) to dtStamp
Set_Field_Value iMyFile iDateField to dtStamp
End
If (iUserField > 0) Begin
Get piUserID of oApplication to iUser // piUser is a property of the application object
Set_Field_Value iMyFile iUserField to iUser
End
End_Procedure // Creating

Anders Ohrt
12-Nov-2009, 02:23 PM
I would wrap the ugly 4 commands you need to get this in a function with a useful name and use it inline.

That would solve the type safety part, and the readability part, but still be a runtime solution when I asked for a compile time solution.

Peter Crook
13-Nov-2009, 08:45 AM
I like that. I had done something similar in 2.x using #IFDEF but hadn't worked out how to do it with DDs.