Friday, 15 June 2012

Undocumented feature - interrupt - part five


Continuing from part four where we looked at "4", "5" and "6"...


7 - Parent directory


This sets $result to be the parent directory of the file path that you specify, like this...


  interrupt(7,"C:\temp\test\")
  parent = $result



In this case $result would be set to "C:\temp\".  You could do this with string manipulation, but you need to be careful that you check to see if the last character is a delimiter or not...

  filepath = "C:\temp\test\"
  if ( $scan(filepath,"/") < 1 ) ;calculate delimiter
    del = "\"
  else
    del = "/"
  endif
  pos = $length(filepath)
  if ( filepath[pos] = del ) ;remove last character if delimiter
    filepath = filepath[1:pos-1] 
  endif
  pos = $rscan(filepath,del) ;find last delimiter
  filepath = filepath[1,pos] ;truncate string

As you can see, this isn't very concise either, and uses $rscan as well as $scan, so it's not likely to perform very well.  As with "6", I'll test with and without the code which calculates the delimiter, over 2,000,000 iterations...

  • interrupt = 00:05.16, 00:05.20, 00:05.09 (just over 5 seconds)
  • alternative = 00:17.74, 00:17.52, 00:17.67 (over 17 seconds)
  • without $scan = 00:15.62, 00:15.39, 00:15.41 (over 15 seconds)

Looking at these times, I'm struggling to argue for using the alternative!  I'm thinking I must be doing something wrong, so I'm going to try and refactor my alternative, without $rscan.  

I've just tried using $replace to convert the file path into a list, remove the last item (or two) and then convert it back again - this took slightly longer!  I'm going to have to give up with this for now, but I hope to tackle this again.


8 - Unknown


I haven't found any indication of what functionality this might be, but it does behave differently to "1" and "4", which are also unknown.  In this case it always returns a $status of 0 and $result is set to the string that you pass in.  I've tried all sorts of weird and wonderful things without getting anywhere, I'm giving up on this one for now too.



9 - Unknown


I haven't found any indication of what functionality this might be either.  This also behaves differently, as it always returns a $status of 0 but $result remains unaffected. 


Summary: Numeric code "7" could be very useful for finding the parent directory quickly, but "8" and "9" remain unknown.



Wednesday, 13 June 2012

Undocumented feature - interrupt - part four


Continuing from part three where we looked at "2" and "3"...


4 - Unknown


I haven't found any indication of what functionality this might be, but it does behave differently to "1", which was also unknown.  In this case it always returns a $status of 0 and $result is a blank string.  I tried folders, subfolders, filenames and a blank string, with no difference in behaviour.  I'm giving up, for now.



5 - Working directory


As discussed in part one, using numeric code "5" returns the working directory...

  interrupt(5,"")
  working = $result

The alternative is to use $ldir...

  working = $ldir()

The alternative is more maintainable, safer and also performs better.


6 - Concatenate file path

This takes the file path which is currently in $result and then concatenates the path you pass in, automatically adding the folder delimiters where necessary.  Here's some example code...


  filepath = "C:\temp"
  $result filepath
  interrupt(6,"test")
  filepath = $result



This will then populate $result with "C:\temp\test\".  As you can see, I had excluded the delimiter from the end of both the original $result value and the value I passed it, but the interrupt command fixed that.  Again, this is another string manipulation example, I came up with something like this...



  filepath = "C:\temp" 
  if ( $scan(filepath,"/") < 1 ) ;calculate delimiter
    del = "\"
  else
    del = "/"
  endif
  pos = $length(filepath)
  if ( filepath[pos] != del ) ;check filepath ends with delimiter
    filepath = "%%filepath%%del%%%"
  endif
  filepath = "%%filepath%%%test%%del%%%" ;concatenate filepath



As you can see, this isn't very concise, primarily because I'm accounting for Windows and Unix environments again, meaning that I have to first calculate the "del" local variable.  You could of course use $concat instead of using substitution, like I have.


I tested these two methods over 2,000,000 iterations with the following results...

  • interrupt = 00:06.67, 00:06.75, 00:06.72 (almost 7 seconds)
  • alternative = 00:12.02, 00:12.00, 00:12.04 (about 12 seconds)
  • without $scan = 00:09.04, 00:09.78, 00:09.74 (almost 10 seconds)

I added a third option which was the alternative but with a hardcoded delimiter instead of calculating it, so the performance can be improved by having a global register that holds this.  Still, in this case the interrupt command is better for performance and is less lines of code.  

Summary: Numeric code "4" remains unknown for now, "5" has been replaced by a better alternative and "6" seems to still be useful, although I think I'd still opt for string manipulation which I know that I can control over an undocumented feature.


Tuesday, 12 June 2012

Undocumented feature - interrupt - part three

Continuing from part two where we looked at "0" and "1"...



2 - Extract filename


This seems to extract the filename part of a full path, removing the directory structure.  Here's some example code for you...



interrupt(2,"C:\temp\test.txt")
filename = $result

In this case "filename" will be populated with "text.txt".  This does seem to be a simple case of string manipulation, but if you support both Windows and Unix filesystems (which the company I work for do, and I expect a lot of people will if they are doing any web development) then maybe it's not so simple...



filename = "C:\temp\test.txt"
pos = $rscan(filename,"\";for Windows
if ( pos < 1 )
  pos = $rscan(filename,"/";for Unix
endif
filename = filename[pos+1]



So this requires a numeric local variable and a few extra lines of code to achieve.  I tested for 2,000,000 iterations...



  • interrupt = 00:09.35, 00:09.30, 00:09.33 (over 9 seconds)
  • $rscan (Windows first) = 00:14.72, 00:14.75, 00:14.66 (under 15 seconds)
  • $rscan (Unix first) = 00:16.38, 00:16.41, 00:16.31 (over 16 seconds)

Unfortunately $rscan is a pretty slow function, but as they could be multiple folder delimiters in the string, I don't see how it can be avoided.  You have to choose in your code whether to put Windows or Unix first, unless you have a global setting somewhere which tells you which the application is running on, in which case you could limit this to a single $rscan.  

Whilst interrupt does perform better in this case, I would say that it is more than likely negligible, given the number of iterations.  Therefore, I'd choose to use the alternative, as I know that I can rely on it working in future.

3 - Extract file path


This seems to extract the file path part of a full path, removing the filename.  Here's an example...



interrupt(3,"C:\temp\test.txt")
filepath = $result

In this case "filepath" will be populated with "C:\temp\".  The alternative to this is therefore going to consist of a similar bit of string manipulation...

filepath = "C:\temp\test.txt"
pos = $rscan(filepath,"\";for Windows
if ( pos < 1 )
  pos = $rscan(filepath,"/";for Unix
endif
filepath = filepath[1:pos]


So there's no need to measure the performance of this, the different will be very similar.

Summary: Numeric codes "2" and "3" do seem to be useful and perform better than the alternatives.  However, the performance difference is probably not enough to consider using the interrupt command, as it will be less maintainable and risky moving forwards.  I'll continue in the next part.

Monday, 11 June 2012

Undocumented feature - interrupt - part two

Carrying on from part one, I want to look at the functionality available using the undocumented interrupt command and see if I can find alternatives, preferably documented ones. So let's go through the known numeric codes...


0 - File information


Always a good place to start.  In this case you pass in the filename in the second parameter and $result is populated with a list of information about the file.  If the file does not exist the $status will be -1, so it could also be used for this check, but assuming it does exist then it will be 0.  


In the information on PUUU, $result will return the following list of properties...

  • Path
  • DiskDir
  • Name
  • Type
  • Version
  • Attrib
  • RecSize
  • Filesize

Here's some example code for you...

  interrupt(0,"test.txt")
  if $status < 0 )
    ;file doesn't exist
  else
    list = $result
    name = $item("Name",list)
    type = $item("Type",list)
    vers = $item("Version",list)
    attr = $item("Attrib",list)
  endif

You may notice that I am only referencing a subset of the properties in this example.  This because I've tested this in Uniface 9.5 and I only get these properties in the list.  This is the problem with undocumented features.  Assuming the information on PUUU was accurate when tested in Uniface 7.2, this functionality has changed and some properties are no longer being returned.  I hope no one was relying on them!

So what if we didn't have the interrupt command, could we still access this information?  My first though was "yes", using $fileproperties or $lfileproperties, which are well documented.  This has the following properties (or "topics")...

  • FILETYPE
  • FILESIZE
  • FULLPATH
  • CREATIONDATE
  • MODIFICATIONDATE
  • ACCESSDATE
  • FILEATTRIBUTES
  • COMPRESSEDSIZE
  • CHECKSUM
  • METHOD
  • ZIPFILENAME

Unfortunately when I compare the output of the two, I don't really get any correlation at all.  For my "test.txt" file...

  • Name = "test"
  • Type = "txt"
  • Version = ""
  • Attrib = "UUU"

I have no idea what the attributes "UUU" means, but the topic FILEATTRIBUTES returns a much more sensible "A", meaning that the file is archived and not hidden or system, etc.  Also version is blank, so I don't know if that genuinely is the version, or whether this property is no longer being populated correctly, making it very hard to investigate.

When it comes to name and type, these seem to be a simple case of cutting up the filename, rather than actually being file properties in themselves.  I could compare the performance of the string manipulation over the call to interrupt, but I think in this case I'm going to accept that this command doesn't return any useful information, and disregard it.


1 - Unknown


I have not found any indication of what functionality this might be, but given the incremental nature of the numeric code I have to assume it does something.  My first thought was that it might be the same as "0" but for folders instead of files, unfortunately it always returns a $status of -1 and $result is unaffected.  I tried folders, subfolders, filenames and a blank string, with no difference in behaviour.  I'm giving up, for now.


Summary: Numeric codes "0" and "1" don't seem to be useful.  I'll continue in the next part.

Sunday, 10 June 2012

Undocumented feature - interrupt - part one

This is an undocumented feature that has been known about and used for a while, including a good guide on PUUU.  However, Compuware have documented a number of undocumented features in Uniface 9.5 and interrupt is not one of them.  This makes me think that there must be alternative ways of doing most of these things, or at least that's what I hope to determine whilst writing this post.


The most frequent usage, as far as I'm aware, is getting the working directory...


  interrupt(5,"")
  working = $result


The way that interrupt works is that you pass in a numeric code in the first parameter which determines the functionality, and then the second parameter can be used to pass a value in if needed.  The output is then place in $result for you to do with what you need.  Of course, this example is no longer needed as there is now $ldir which returns the same thing...

  working = $ldir()

So clearly the new version is documented and therefore supported, it's also more concise code and easier to maintain.  So once again a familiar question pops into my head... Which performs best?  I tested both of them over 2,000,000 iterations...

  • interrupt = 00:07.31, 00:07.34, 00:07.36 (just over 7 seconds)
  • $ldir = 00:06.11, 00:06.09, 00:06.04 (just over 6 seconds)

So as you can see, $ldir also performs fractionally better.  Given the number of iterations, it's pretty negligible, but on top of all the other reasons, I'm convinced that I should be using it over the old interrupt method.

In the next part I will be going through each of the known interrupt codes, trying to find alternatives that are documented, and testing the performance where appropriate.

Wednesday, 6 June 2012

Undocumented feature - filebox filter

I was working with Uniface's filebox command today, allowing a user to select a file using the Windows selection dialog.  This is a handy feature and very simple to use, it's fairly well documented, but there is one thing missing.


The easiest way to trigger this selection dialog is like this...



  filebox





The "All File (*.*)" option is added by default, and as far as I'm aware this cannot be removed.  However, it is normally useful to filter the files which appear, especially if it is being used to select a particular type of file.  You can add a filter like this...


  filebox "*.txt"






Notice that the title of the filter is built from the file extension in your filter - very clever.  You can even add multiple filters, like this...



  filebox "my*.txt·;*.txt"





However, In this example I have two different filters, but because they have the same file extension, the default titles are the same.  Well here's the undocumented bit; changing the title is very easy to do, like this...



  filebox "my*.txt=My text files·;*.txt=Text files"





Much better!  This allows you to create more user-friendly dialogs with helpfully named filters.

Summary: It is easy to rename the filters in a filebox selection dialog, if you know how.

Saturday, 2 June 2012

Validating a numeric string

Today I wanted to validate a numeric string, in the format "YYYYMMDD".  I wasn't particularly worried about checking that there were only 12 months in the year, 28-31 days in the month, or whether the year was in a sensible range, I just wanted to make sure that I'd found a string of 8 numeric digits.


So my first thought was to check the length using $length, an easy place to start...



  if $length(str) = 8 )
    ;good start
  endif



Next I went on to think about checking it was numeric, $number being the first function that popped into my head which seemed relevant...



  if $length(str) = 8 & $number(str) = str )
    ;pretty good
  endif



But then I remember that this matches even if the string contains spaces, which I didn't want (nor do I think is correct, personally), so I had to add a check for that...



  if $length(str) = 8 & $number(str) = str & $scan(str," ") = 0 )
    ;even better
  endif



And then I thought about other "numeric" characters that were allowed.  Depending on your language settings, this could include plus (+), minus (-), fullstop (.) and comma (,) - there may be more as well. 


It was at this point that I thought I must be doing something wrong, this clearly wasn't the best way of doing this.  


Then I remember a suggestion a colleague of mine had made to me when I started this blog - syntax strings.  It's very easy to use a syntax string to pattern match for numbers...



  if ( $length(str) = 8 & str = '#*' )
    ;much better
  endif


I won't go into the details of syntax strings as this is well documented, but suffice to say that a hash (#) means a numeric digit (0-9) and star (*) means any number of them (0-n).  Combined with my length check, this seemed right.  

But then it occurred to me that I could improve this even further...


 if ( str = '########' )
    ;perfect
  endif


In this case I have specified that I require 8 numeric digits, which is exactly what I wanted to check for.  Obviously using a syntax string here has simplified my code and made it much more readable, it's clear to another developer what I'm trying to achieve.

Now usually I'd have settled for the code that was using $length, $number$ and $scan, which would have sufficed and seemed pretty logical as I was writing it.  But noticing how much more elegant this code was got me thinking... How much better does this code perform?  Surely better than all those function calls!

I took the last 3 of my code blocks and ran them 2,000,000 times, giving the following results...

  • $length+$number+$scan = 00:07.70, 00:07.70, 00:07.65 (almost 8 seconds)
  • $length+syntax string = 00:06.33, 00:06.25, 00:06.27 (over 6 seconds)
  • Just the syntax string = 00:05.00, 00:05.09, 00:05.00 (about 5 seconds)


As you can see not only is this code more elegant and therefore more maintainable, but it also performs better.  Given then difference is only a second or two over 2 million iterations, it's not really much of a consideration by itself, but they say every little helps!

Thanks to Dave W for the inspiration which led to this post.

Summary: Don't always jump to using Uniface functions for validation and other checks, it can often be simpler and more efficient to use syntax strings to perform pattern matches.