08 July 2015, 16:21 | #41 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Reserved special characters
Time for more basic stuff
In a previous example we addressed DirOpus and send a command named "Verify" to open a requester. If you quickly browse the list of functions ARexx supplies, you will find a function with exact same name. How does ARexx know? There is a tiny variation in our code that has a big effect on how ARexx interpretes what we wrote. A command is interpreted if it has no adjacent special characters like "." or "(" Moving open parenthesis next to it, turns the command into a function. Set a period close to it and it will be identified as a stem. Wrap it in ' ' and ARexx handles a string. ARexx will check its internal table of symbols before interpreting a symbol as command. ARexx will complain about any syntactic error that does not accidentily fits a correct expression, command, function or statement. Reserved special chars . "dot" used for stems , "comma" glues multiple lines into one statement if you need more space to code () "parentheses" identifies priorization ( "open parenthesis" plus symbol = function ; "semicolon" seperates statements : "colon" identifies a label Some commands require a counterpart to be interpreted correctly: Do ... End Select ... End If ... Then When ... Then What happens if code contains a symbol of same name as function or command? In most cases this will cause an error message that is quite irritating. Or as in example below, nothing happens at all. Think of the following code: Code:
/**/ string = 'This is a string with a number 42' num = Words(string) /* counting words , num contains now value 8 */ If Datatype(Word(string, num)) = NUM , /* check if last word is of numeric type*/ Then Say "We have a number!" "If" conditiona checks against 8 not NUM. Remember, we deal with upper case mostly. Our symbol num is NUM. If you insist on the usage of num, then you have to wrap NUM in single quotes. Code:
If Datatype(Word(string, num)) = 'NUM' Same with commands. If a symbol exists that shares same name with a command used by a host, wrap the command in quotes. Your own functions may have same name as a command, use quotes for the command. See upcoming chapter about function and procedure. Last edited by BigFan; 08 July 2015 at 17:23. |
08 July 2015, 16:33 | #42 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Modularity and functions
Modularity
Do you remember me writing about file endings? .rexx in particular? ARexx searches for files with .rexx first in current directory then in logical drive REXX:. If the file ending is omitted, the file name matches both variants e.g. file name is "wordcount.rexx", searching for "wordcount" matches as well as "wordcount.rexx". How is this related to modularity? Imagine you wrote lots of small scripts for different purposes like : word filter, word count, backup strategy, ports comunication, helper process. All your scripts are named <filename>.rexx and reside in REXX:. Now you can use them as command/function in your newly written code by typing their filename without file ending. Lets assume a script called welcome.rexx is in REXX: Code:
/**/ ... your code welcome ... passed to your external script as well as returning values from scripts. ARexx features following methods to interact with external code: Parsing arguments, using command "Arg" or "Parse" and check their existence with "Arg()" Returning values with command "Return" Copy to Clipboard. The clipboard is available globally for all rexx programs. Rexxsupport.library provides methods to open a rexx message port . It allows you to send commands to a rexx program using "Address <portname> <command>". You have to run this second program first to be up before the caller. Use "WaitforPort" inside caller to avoid timing problems. This lib offers the functions "AllocMem()/FreeMem()". They look similar to Getspace()/ Freespace, but memory allocated this way is public, while Getspace() takes from REXX internal memory pools, which is private. Data stored in allocated memory can be shared with other programs and is not freed automatically on exit. Be careful to not waste mem. Modularity is not restricted to external code. The larger the script grows in complexity, the higher the risk of failure and the less the readability. In short your code becomes weird. This is called "spaghetti code". A simple and recommended programming style is the use of own funtions. Functions To create your own function place a label. A label is a symbol with a colon. Set command "Procedure" after your label. This can be placed anywhere in your function but only once. End function with "Return" e.g. Code:
/**/ ... CheckForPort: Procedure ... your code here ... Return msg The interpreter then stops execution at current line, jumps to the label, executes the code and returns back to the next statement right after the call. In very small scripts this would create some overhead, but in scripts with repetive code sections this will shrink the code and structures it for ease of maintenance. Functions have some advantages: Protection of symbols Your symbols outside your function are save from being overwritten. This feature is on by default. It can be turned on or off for each function block, e.g. Code:
/**/ ... CheckForPort: Procedure Expose i j from your function. Expose has a left to right read order. Stems are translated only as leftmost argument or as sole argument (msg.a becomes msg.21 if a = 21). With " Expose A msg.A ", A becomes 21 while msg.A remains msg.A. Compounds can be exposed all at once using their stem (CUBE. addresses CUBE.X, CUBE.X.Y, CUBE.X.Y.Z etc). Argument parsing upto 15 arguments can be passed, parsed and assigned to variables for functions. Commands use 1 Argument passed to them as a string. Parsing of a string is not restricted by the means of length or number of substrings. Recursion a function can call itself. this results in very dense code. think of factorials. Last edited by BigFan; 12 July 2015 at 10:04. |
09 July 2015, 18:14 | #43 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Creating functions and commands
Funtions using Date()
ARexx offers 2 functions to check for current date and time, and they are named Date() and Time() to no surprise. Date() supports lots of ways to format the output string. DAYS, WEEKS, MONTHS in this year, or in this CENTURY. NORMAL is like '10 Jul 2015'. Use EUROPEAN or US format or SORTED as 20150709. INTERNAL is the days since 01.01.1978. Date() accepts another date as new base. Syntax: Date(option,[date],[I|S]) /* 'I'nternal is default */ Example: Code:
/**/ Say Date('u') /* tell us in US format */ dat0 = Date('e',Date('i')-120) /* Tell the date 3 month ago */ Say dat0 /*Or in half a year */ dat0 = Date('e',Date('i')+180) /* or around 6 month past today*/ Exit 07/09/15 <-- US today 11/03/15 <-- EU - 120 days ago 05/01/16 <-- EU in 180 days These short forms use slashes. If we are going to update a version string with a new date, as a revision bumper f.e., we have to convert this. The function in question is Translate(). This function takes a template string (our date), a string with replacers ('.') and a string with what to be substituted ('/'). Code:
dat0 = Translate(dat0,'.','/') Now we need it bracketed. Simply call Insert() for surgery, with dat0 as injection, a short string of closed parentheses as victim and the optional position to set marks for the syringe. Code:
dat0 = Insert(dat0,'()',1) Voilà! Let's make it a versatile function that can be used in future projects. Use INTERNAL format for this, e.g.: Code:
/* */ Options Results Call ConvDate Date('i') version.date = RESULT Say version.date EXIT /* Date converter */ /* Accepts internal date format and converts it to comply * with version string requirements */ ConvDate: Procedure Arg dat0 If dat0="" Then Return 0 If Verify(dat0,'0123456789') > 0 Then Return 0 /* check for alien characters */ dat0 = Date('e',dat0) /* internal to european */ dat0 = Insert(Translate(dat0,'.','/'),'()',1) /* beautify it */ Return dat0 in "REXX:" Code:
/* Date converter */ /* Accepts internal date format and converts it to comply * with version string requirements */ Arg dat0 If dat0="" Then Return 0 If Verify(dat0,'0123456789') > 0 Then Return 0 dat0 = Date('e',dat0) dat0 = Insert(Translate(dat0,'.','/'),'()',1) Return dat0 Code:
/**/ Options RESULTS ConvDate Date('i') version.date = RESULT Say version.date EXIT Last edited by BigFan; 09 July 2015 at 19:54. |
09 July 2015, 19:34 | #44 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Translate is as powerful as confusing. In our exemple we had just one character
to be replaced with another. That is simple. The routine runs through our table and picks from output what is defined in input according to the position. But Translate is able to work out longer strings and to fill the gap if the output string is shorter than input string. The syntax goes : Translate(template, substitute, indicator, filler) The indicator uses the template to create a position table. This table indicates what character has to be taken as substitute. If there are no more characters in substitute than use the filler for the gaps. Feel clever? What is the output from the code snippet below ? Code:
/**/ Say Translate('123 456 789 80#','it gdC uyo ani','80 971 645 238#','?') Last edited by BigFan; 11 July 2015 at 15:17. |
11 July 2015, 14:48 | #45 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Parsing arguments
Parsing
One of the best features of ARexx is its parser. It is not restricted to command line or function arguments. In fact you may parse anything from one of its input channels. The entire argument is treated as being one long string. If the template has more than one target, the parser looks for substrings seperated by white space. If templatecontains strings, they get temporarily deleted from argument string. The parser then continues with the next substring. Values in [] are optional. Syntax Parse [upper] source template [,template] [,more templates] [.] upper - is optional and translates the input to upper case source - is one of
template - is a list of symbols (uninitialized variables) to assign values to. A template consists of markers (a position) and targets (the variable to extract). ',' - seperates multiple templates when pulling (use without quotes) '.' - is a signal dot that marks the end of line to stop parsing and tokenization. The last value will be taken from input but assigned nowhere (use without quotes) Examples: Argument was "file" Arg typeArg is similar to Parse Upper Arg Argument was "5 6" Arg ii is a string now, can't be used for math i and j are strings of numeric type, useable in arithmetic operations now Arg i j k(k is now an existing but uninitialized symbol, a LITERAL, use Symbol() to check) Argument was "drinks food each $1" Arg d f 'each' '$'pArg is shorthand Parse UPPER Arg. Either use Parse Arg or upper case in template Argument was "Soda £1.50 Coffee £1.50 all prizes incl vat" Parse Arg i j k l Parse Arg i j k l .the remaining substring is not assigned to anything Parse Arg i "£"j k "£"l .currency symbol has been taken from argument string while parsing Argument was "Soda £1.50 Coffee £1.50" <== pay attention, the last gets it all including the white space TIPP: Whenever possible, close the template with a dot to avoid unsolisticated strings or white spaces. Using markers Markers are evaluated left to right in ascending order. If the next marker has lower value than the previous, the parser rewinds to that position. An operator like + or - steps from current position in right(+) or left()- direction. Argument was "Me and Susan had a bad day!" The substring "bad " is hidden from input so -2 jumps to the position of "a" before "bad" not "d" from "bad". Please notice, the input string is not altered in any way. It is still the same. The parser is doing modifications temporarily. You may parse it again with different markers and targets. Multiple templates Multiple templates can be used when pulling from a console. Each template requires a new input. Use comma to seperate templates. Parse Pull last first, street, town This requires the user to send 3 times his input using return key. Assume input was "Clause Santa" "there is no street or road" "why the heck should i tell you" => first="Santa" last="Clause" street="there is no street or road" town="why the heck should i tell you" If we'd put commas in quotes, the parser would have tried to read all in once, leaving street and town uninitialized. Arguments can be parsed in functions by the user manually. To do it yourself see following example Code:
/* Summarize with variable argument length */ Say Sum( 5, 4, 3, 2) Exit Sum:procedure sum=0 Do i=1 to Arg() sum = sum + Arg(i) End Return sum is considered being a command, not a function. Arguments are passed to commands as one string. In our small example, Arg() will report 1 and Arg(1) is "5, 4, 3, 2" if this is fetched from command line. Invoking with :> rx sum 5 4 3 2 Example of erroneous code Code:
/* Summarize with variable argument length */ sum=0 Do i=1 to Arg() /* Arg() is 1*/ sum = sum + Arg(i) /* arithmetic error, sum is a number, Arg(1) is a string */ End Return sum Last edited by BigFan; 12 July 2015 at 10:15. |
12 July 2015, 09:56 | #46 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Code obfuscation
Code obfuscation
This isn't useful at all, but fun anyway Imagine i gave you a code job and it seems too difficult to you. You then simply read the provided example without trying yourself first. I'm fine with that. But i like to see a solution you did yourself so i hide the code slightly. This is highly hypothetical, of course The next example is very short. Would be more impressive on large code blocks. Exercise: Code a script that calculates the factorial of a given integer value. Use recursion to do that. Factorial is multiplication of all integers ranging from 1 to a given value. E.g. !5 is 1*2*3*4*5 is 120. Recursion means, a function is calling itself to iterate. Possible solution with obfuscation Code:
/* * Factorial * using recursion * ;)f(cf = s;f grA;stluser snoitpo ;n grA esraP;erudecorP :cF;s nruter )1-n(cF*n nruteR eslE;1 nruteR nehT 1=n fI */ Arg f . If f<1 Then Do Say "Error!" Say "Usage: rx factorial Val/N (>=1)" Exit End Options RESULTS Pragma(D,"T:") Open(t,"temp.rexx",w) Writeln(t,'/* */') Do L=5 to 7 Writeln(t,reverse(sourceline(L))) End Close(t) f = trunc(f) temp f r = RESULT Say "Fakulty of "f" = "r Address command 'delete >nil: t:temp.rexx' Exit Peeking at the code doesn't help until you run it and analyze the output. Or find another way to reverse the code (watching it in a mirror won't do it ) The minimum value is 1, the maximum is 171. Higher values exceed the limits of numerical representation. The code is fast enough on standard A1200. I have not checked stock A500. Some remarks to the functions used Pragma() can be used to query or set the active directory. Options to Pragma(option,value) are 'D'-irectory, string with new directory as value or without to query current 'P'-riority, changes program runtime priority 'I'-D, the task id of your active rexx code. can be used for unique names 'S'-tack, alters the stack for the program running. Queries without a value. 'W'-orkbench, switch WB requester on/off (file not found, insert disk etc.) *-redirects console iostreams (i never used this) Reverse() is just doing to a string what the name says. Sourceline(), without value can tell the number of lines in your script, a numeric value makes it read the named line. Because the code is hidden in the comments section, we had to put a comment on top of the newly created or the resulting script won't be accepted as a valid arexx script/command. Trunc() is used to truncate the digits, the value becomes integer. The code could be scrambled or encrypted using more advanced routines. To make it more confusing start with hex values. Instead of obfuscating the code, a useful script is to dense the code. Delete comments that are not needed. Remove empty lines. Concatenate statements with semicolons all in one block. The file uses less space on disk then. Tools to do this were once called ARexx-Compilers, though they offered no compiling but a little bit of compression. Last edited by BigFan; 14 July 2015 at 13:14. Reason: typo |
12 July 2015, 10:00 | #47 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Parsing with variable length
Parsing with variable length
It is fairly easy to parse any arguments if they have some sort of identifiers like white space, special or reserved characters (tab, + sign), or are comma seperated. But what it neither start nor length are fixed? Assumption is made, the format has a position code that tells us where to start reading to what length. Everything behind varies from record to record. We could code our own parser but we don't have to. Do parse the following "016014jdklfjadaAdamski-Killercyvyvmmm-" "010031***MelanieThornton-Wonderful Dream5555555" "013030trpuwqMinistry-Everyday Is Halloween07052002" "007030Ministry-Jesus Built My Hotrodbadrip" The trick is, the parser sports variable position markers. This is done by reading first the position and length from defined format, then use this variables again on the record. Remember, you are allowed to parse arguments, pulls and vars as often as you like. First position index 016014 First 3 decimals represent the start, following 3 the length. VALUE is the option to tell the Parser what to parse (record in this case). With is a delimiter for expression with no other meaning. (VALUE expression WITH) Example for parsing Code:
record=Arg(1) /*"016014jdklfjadaAdamski-Killercyvyvmmm-"*/ Parse VALUE record WITH 1 start +3 len +3 =start mp3.entry +len start = 16 len = 14 mp3entry = Adamski-Killer The equal sign qualifies a variable to be a fixed position marker, while +len is a variant. The parser jumps to position stored in "start", reads the next "len" chars and assign it to mp3entry Again: Our starting position is 1 Read next 3 chars and store in "start" Read next 3 chars and store in "len" Interprete "start" as position index (jumps to char 16, "A" of Adamski) Read next "len" chars and store in mp3.entry (position is "r" of Killer) Last step is to parse mp3entry to get artist and title seperately. Parse VAR mp3.entry artist '-' title Code:
/**/ record="016014jdklfjadaAdamski-Killercyvyvmmm-" Parse VALUE record WITH 1 start +3 len +3 =start mp3.entry +len parse var mp3.entry artist '-' title say start say len say mp3entry say artist say title Code:
/**/ record.1="016014jdklfjadaAdamski-Killercyvyvmmm-" record.2="016014jdklfjadaAdamski-Killercyvyvmmm-" record.3="010031***MelanieThornton-Wonderful Dream5555555" record.4="013030trpuwqMinistry-Everyday Is Halloween07052002" record.5="007030Ministry-Jesus Built My Hotrodbadrip" Do i=1 to 5 Parse VALUE record.i WITH 1 start +3 len +3 =start mp3.entry.i +len parse var mp3.entry.i artist.i '-' title.i say mp3.entry.i say artist.i say title.i end |
12 July 2015, 15:11 | #48 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Back to start
All this started with scripts for DirOpus. Time to use our newly gained knowledge. Problem: When interacting with DOPUS, many commands deliver the selected or unselected contant as a single string. Depending on how many files and directories are retrieved, this string becomes very, very long. And to make it worse, it uses blanks for seperating by default. Pathes and names containing blanks cause trouble when reading the result string. They'll be taken as seperate files e.g. "Check o mat" is split into 3 file names. We need a seperator to avoid misinterpretations. But what character is uniqe and not allowed? The mentioned scripts in the starting post use "*" (asterisk) as a seperator. This is problematic, the "*" is a valid char in file names. It is not recommended to use such characters for DOS names, but it is not forbidden. Following characters are not allowed in file names: ":" colon and "/" slash. The list of characters allowed includes: ";.,-_#'*+-°^~$" + " " (blank). The only secure way to identify them as a real seperator and not part of the file name is to use a combination (e.g. ;-) ), but again, someone might have named his files like "MyBirthdayPics;-).ilbm" A single unallowed character might not be unique, f.e. the colon might appear in pathes or device names, but "::" will probably never happen. I suggest "::" as delimiter. This is unallowed in files and pathes. Any path ending with ":" then has 3 colons like "Ram Disk:::". Very easy to identify when you read about parsing before. Let's go: Code:
/**/ Options Results Address DOPUS.1 GetSelectedAll "::" selection = result say selection Do s=1 until selection="" Parse Var selection file.s "::" selection end Do i=1 to s say file.i end 4.Ram Disk:> rx sel Clipboards::ENV::T::check o mat::check o mat.info::say*.not::sel:: Clipboards ENV T check o mat check o mat.info say*.not sel 4.Ram Disk:> The result has no path or devices names. A single ":" will do. Using the parsers abilities is simple and easy to read. Code can be reused and is royalty free. No magic, no fog. And if you have read my examples carefully, you came across my question if a Do While ~Eof() loop can't be done better. In a previous example i deleted last element read to avoid redundant lines in output. Compare to example above, the loop introduces a new option "until". Loops are to be examined in next chapter. |
12 July 2015, 15:57 | #49 | |
Zone Friend
Join Date: Sep 2001
Location: Germany
Posts: 814
|
Quote:
Code:
status 9 <active_lister_number> /* # of selected files */ no_of_files = result do no_of_files getnextselected <active_lister_number> current_file = result /* do something with the file here */ selectfile '"' current_file '" 0 1' /* 0 = deselect, 1 = immediately update display */ end |
|
12 July 2015, 17:32 | #50 |
Registered User
Join Date: Oct 2009
Location: Germany
Posts: 3,322
|
You are using outdated DOpus version. DOpus5 example:
Code:
IF sel = '' THEN f = 'FILES' /* All files */ ELSE f = 'SELFILES' /* Selected files */ 'LISTER QUERY ACTIVE' ; activehandler = result /* Get handle of ACTIVE lister */ 'LISTER QUERY' activehandler f 'STEM' filelist /* Store (selected) files of ACTIVE lister into stem variable */ |
13 July 2015, 14:43 | #51 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
@korodny
sure, getnextselected does it, all written in the examples given in this thread. The chapter is about parsing, i was trying to give and example for that. Maybe i stretched the DirOpus thingy a bit too often. I'm sick of doin version stuff , but it is so easy Except for the creators of that masterpiece below. GetSelectedAll '*', was used in this script and please have a sharp eye on how the result string is split up Code:
/* * $VER: DOpus4-Version 1.2 (27 Jun 2015) by Wizardry and Steamworks * * © 2015 Wizardry and Steamworks * * PROGRAMNAME: * DOpus4-Version * * FUNCTION: * Shows the version of file(s) in a DOpus4. * * USAGE: * ARexx command DOpus4-Version.rexx (from DOpus) * * $HISTORY: * * 27 Jun 2015 : 1.2 : cleanups and fixes * 25 Jun 2015 : 1.1 : remove port, copy file, use random, support spaces (thanks @eab:daxb) * 17 Jun 2015 : 1.0 : initial release * */ /*------- Configuration Variables (change these to suit your setup) --------*/ VCMD = "C:Version" /* Where to find the CBM Version command. */ /*--------------------------------------------------------------------------*/ Opus = Address() /* Get the DOpus address. */ Options RESULTS /* Request results. */ Address value Opus /* Use the DOpus address. */ Busy On /* Set the busy pointer. */ /* Get all the selected items and check if something is actually selected. */ GetSelectedAll '*' /* Get all the selected items. */ Items = RESULT /* Normalize, heh... */ Items = Translate(Translate(Items, '/', ' '), ' ', '*') n = Words(Items) /* Get the number of selected items. */ If n <= 0 Then /* Check if no items were selected. */ Do /* If no items were selected, */ Busy Off /* turn off the busy pointer. */ Exit /* and terminate. */ End /* Set the buttons for continue and abort. */ Status 26 /* Get the value of the Ok button. */ OldOkay = RESULT Status 26 set 'Continue' /* Set the value of the button to continue. */ Status 27 /* Get the value of the Cancel button. */ OldCancel = RESULT Status 27 set 'Abort' /* Set the value of the button to abort. */ /* Loop through the selected items and display their name and version. */ Do i = 1 To n /* For all selected entries... */ Name = Translate(Word(Items, i), ' ', '/') /* Get the name of the entry and translate back. */ ScrollToShow Name /* Scroll to the entry. */ Status 13 i + 1 /* Get the path to the entry. */ Path = RESULT /* Generate temporary files. */ FTMP = wasRandomHexString(Time(SECONDS), Length(Name)) Address COMMAND "Copy >NIL:" '"'Path||Name'"' "T:"FTMP VTMP = wasRandomHexString(Time(SECONDS), Length(Name)) Address COMMAND VCMD "T:"FTMP " > " "T:"VTMP Address COMMAND "Delete >NIL:" "T:"FTMP 'QUIET FORCE' /* Attempt to open the temporary file for reading. */ If Open('output', "T:"VTMP, 'READ') ~= 1 Then Do /* If the temporary file could not be opened, */ Call wasDOpus4DeselectEntry(Name) /* deselect the entry, */ Iterate i /* and continue. */ End Tmp = ReadLN('output') /* Get the output from the temporary file. */ Close('output') /* Close the file. */ Version = Word(Tmp, 2) /* Split the name into a name and a library version. */ /* The assumption is that version strings must contain digits. */ If wasStringContainsDigits(Version) ~= 1 Then /* Check whether the version string contains digits. */ Do /* If the version string does not contain digits, */ Call wasDOpus4DeselectEntry(Name) /* deselect the entry, */ Iterate i /* and continue. */ End Select /* Switch on i */ When i = n Then Notify Name " " Version /* When only one item remains selected just notify. */ Otherwise /* Otherwise, present a chooser whether to continue or abort. */ Do Request Name " " Version /* Show the version. */ CarryOn = RESULT /* Get continue or abort. */ If CarryOn ~= 1 Then /* Check which button was pressed. */ Do /* If the abort button was pressed then */ Call wasDOpus4DeselectEntry(Name) /* deselect the entry, */ Leave /* and abort. */ End End End Address COMMAND "Delete >NIL:" "T:"VTMP 'QUIET FORCE' /* Delete the temporary file. */ Call wasDOpus4DeselectEntry(Name) /* Deselect the entry. */ End /* Restore the continue and abort buttons. */ Status 26 set OldOkay Status 27 set OldCancel Busy Off /* Turn off the busy pointer. */ Exit /* Terminate. */ /*************************************************************************/ /* Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 */ /*************************************************************************/ wasDOpus4DeselectEntry: procedure /* Deselect a selected entry. */ Parse ARG Item GetAll '*' AllItems = RESULT AllItems = Translate(Translate(AllItems, '/', ' '), ' ', '*') n = Words(AllItems) Do i = 1 To n If Item = Translate(Word(AllItems, i), ' ', '/') Then Do SelectEntry i - 1 ||' '|| 0 ||' '|| 1 Return End End Return /*************************************************************************/ /* Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 */ /*************************************************************************/ wasStringContainsDigits: procedure /* True if string contains digits. */ Parse ARG String Do i = 0 To 9 If Pos(i, String) ~= 0 Then Return 1 End Return 0 /*************************************************************************/ /* Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 */ /*************************************************************************/ wasRandomHexString: procedure /* Generates a random hexadecimal string. */ Parse ARG Seed,Size If Size = 0 Then Return '' Random = Random(0, 15, Size + Seed) Return D2X(Random)||wasRandomHexString(Random, Size - 1) DOpus commands GetFiles and GetDirs can't be replaced by GetNextSelected. You have to parse or going a long way . Last edited by BigFan; 13 July 2015 at 19:07. Reason: addendum |
13 July 2015, 14:53 | #52 | |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Quote:
The command params are sent as string. Hex values don't match and the result string does not have any markers then. All as one string with no blanks. Code:
5.Ram Disk:> rx dops ClipboardsENVTdopsupdate2.rexxuser-startup Obviously, using hex values as string wouldn't help either Last edited by BigFan; 13 July 2015 at 19:01. Reason: addedum |
|
13 July 2015, 15:03 | #53 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Loopings
Loops
A flow control to repeate same code is called a "loop". ARexx supplies the Do command with some more or less usefull options. But those option make a big difference to its default behavior. Standard loop is like "For ... Next, While ... wend" in other languages. Code:
/**/ Do i=1 To 10 Say "looping nr "i End because zero indicates nothing to do and aborts the loop. Floating point values are allowed. To do a reverse count, start with high values and set a negative offset. Code:
/**/ Do i=10 To 2 By -2 Say "looping nr "i End Code:
/**/ F=10.5;L=0.0;S=-1.5 Do i=F To L By S Say "looping nr "i End The block is processed till "End". Now the "i" is increased by 1 (this is default) Again "i" is compared to the value after "To". If it not matches, the loop continues. And so on.
"Until" is very "similar" to while. It checks if expression is true and aborts the loop if not. BUT!!! "Until" checks before the counter is increased, "while" checks after the counter is increased. This is very important. Code:
/**/ f = 0 Do i=1 while (f~=7) f = f + 1 end say "f="f "i="i f=0 Do i=1 until (f=7) f = f + 1 end say "f="f "i="i Have a look at our example with file reading. Condition was (While Not EndofFile) We read line by line. The Do block continues as long as the "while" expression is true, that is Eof() is not true. Rebuilding the code with "until" requires to check for Eof() being true. Code:
If Open(infile,filename) Then Do Do i=2 While ~Eof(infile) line.i = Readln(infile) End i = i - 2 End Else Do Echo "Cannot find "filename"!" Exit 20 End Code:
If Open(infile,filename) Then Do Do i=2 Until Eof(infile) line.i = Readln(infile) End i = i - 1 End Else Do Echo "Cannot find "filename"!" Exit 20 End The combination of Eof() and Readln() still results the same. We have to delete any unwanted additional lines or the output gets filled with NULLstrings . Example Code:
/* line counter */ options results if open(fil0,'s:user-startup') then do w=1 while ~eof(fil0) line=readln(fil0) end seek(fil0,0,b) /* rewinds file index to offset 0 from begin = start of file */ do u=1 until eof(fil0) line=readln(fil0) end close(fil0) say "using while lines= "w say "using until lines= "u 5.Ram Disk:> rx line using while lines= 27 using until lines= 26 5.Ram Disk:> What's the cause? Readln() reads all characters till $0a is found . (hex '0a' = decimal '10', signals "Line feed") It stores the string without the trailing '0a'. The last position in a text file is a '0a' normally, so we do not read beyond file bounderies at the last line. Eof() is not true. A new attempt is made to read a line. Now we exceed file length. Because Eof() is true now, the loop breaks. That is one line beyond boundary. "until" fails before increasing the counter, "while" after increase. We get one or two additional lines reported. Readln() NEVER fails. If nothing is read, a NULL string is generated. We can't use this as an indicator, because a text may contain empty lines. |
13 July 2015, 18:54 | #54 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Adding libraries
Libraries
ARexx is a bit limited regarding file system operations or user interaction, and it lacks a graphical user interface. One way to add more functions to ARexx is to use additional libraries. Of course, those libraries have to support ARexx. ARexx comes with 2 libraries, the rexxsyslib.library and rexxsupport.library. There are third party libraries available on aminet. We start with the rexxsupport.library as this offers to us a second way to have more functionality. It enables us to open our own ports and listen silently while staying in the background. To open a library use Addlib(). We have to make sure that the requested library is successfully opened. In other case, script will fail . Check loaded libs with Show() Code:
/**/ Options Results If Show(l,'rexxsupport.library')=0 Then Do If Addlib('rexxsupport.library',0,-30,0)=1 Then Exit End ... Code to run ... code continues. If library is not loaded, Addlib() tries to open it. Syntax Addlib('libname', priority, entry point, version) Priority is a value ranging from -100 to 100. Do not use high priorities, it slows down all other running tasks. Zero should be fine in most cases. Entry point is an address offset to query the library if the requested command is actually available. -30 is often correct, study the library docs if that value differs from default. Version is a major version number that represents the minimum version of the lib to be open. If version is set to 39, any version lower than that causes an error. Use it when necessary or set zero to ignore version. An open library can be closed. Use Remlib(libname) for this purpose. Addlib() opens ports also. Simply call Addlib(name,priority). Entry point and version are not supported in this mode. Rexxsupport.library grants access to the following functions: Allocmem(bytes[,attrib]) - Allocates a block of memory in bytes, default attribute is 'Public' Closeport(portname) - Closes a message port Freemem(address, bytes) - Releases a block of Allocated memory Getarg(message, slot) - Extracts command, function name or string from message pkt Openport(portname) - Creates a public message port Reply(message, rc) - Returns a msg pkt to the sender with a value Showdir(directory[,'A'll|'F'ile|'D'ir][,pad]) - Returns contents of directory as strings of names Showlist(option,name,pad) - works the same way as Show() but has more options Statef(filename) - Returns a string containing file information like size and protection bits Waitpkt(portname) - Waits for a msg pkt from port Options for Showlist() 'A'ssigns and assigned devices 'D'evice drivers 'H'andlers 'I'nterrupts 'L'ibraries 'M'emory list items 'P'orts 'R'esources 'S'emaphores 'T'asks (ready) 'V'olume names 'W'aiting tasks Last edited by BigFan; 14 July 2015 at 12:38. Reason: corrected library call and return code check |
13 July 2015, 19:00 | #55 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Open your Rexx Port
If you get this far reading here, then you should be able to figure out how to use
Showdir() or Showlist(). No big deal. I save time not to explain basic stuff. The most intrigueing functions are to control your own rexx port. This is a complex thing and is best explained with an example code. Example Code:
/* */ /* */ Options Results If ADDLIB('rexxsupport.library',0,-30,0)=0 Then Do port=catch Openport(port) do forever waitpkt(port) msg=getpkt(port) arg=upper(getarg(msg)) if arg=quit then break if arg=version then do reply(msg,0) Address command 'requestchoice Title "Version" Body "Catcher V1.0" OK' end end end Else Do Say "rexxsupport.library not found" Exit End Reply(msg,1) Closeport(catch) Exit If not, find exit. I named the port catch, no quotation marks, we deal with upper case !! After successfully open a port, the code enters an infinite loop. Waitpkt() is systemfriendly and sets our rexx program to sleep, waiting for the first loves first kiss to awaken the beautiful princess (resting in the highest tower guarded by a dragon). eerrrmm. oops If our program receives a message we fetch it with Getpkt() We pick the content (if any) with Getarg() and make it upper case (or we have to find a way to deal with case-sensetivity). We check for two commands, QUIT and VERSION. QUIT forces the code to exit. Version prints a silly version string in a requester. How to control? First, run the example from workbench (add an icon, set rx as command) or use shell command "run" to detach from console. Now the program stays in background with the default RX output window. Open a shell, if not already. Check if our catcher is waiting : rx "say show(p)" CATCH should be listed as available port rx "address catch version" The silly version string shows up in rexx window rx "address catch quit" Commands the program to terminate. All this can be done from inside any other rexx program. If you want to communicate (CATCH should send results to your programm, f.e.) you have to add the code for port creation to your newly written program. The example above lacks error handling. Never forget that almost everything could fail. Better be save than freezed Bug fix: first version checked for success on open library and exits instead of continue Last edited by BigFan; 14 July 2015 at 13:22. Reason: title added, silly bug found |
14 July 2015, 16:21 | #56 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Running from Workbench
Running REXX programs from Workbench.
The shell is a useful interface during development of rexx programs. Results show up in current or additional consoles. You have all of the control over the rexx programs running. But after testing your code thoroughly, you may like to see it working in the background, but on every start from wb the rx output window pops up. A rexx script doesn't need special protection bits. All you need is an icon.
The script starts silently from wb. It is still accessable and listens for any message to come. Rexx offers a few tools to share data with your program or to stop them. See sektion "Signal on ..." and prepare your script for proper error handling. Shell Commands: HI - send a "Halt Interrupt" to all active rexx programs, forcing them to exit immediately. RXSET - send data to rexx clipboard. RXC test="testclip" copies "testclip" to clipboard entry named 'test'. From within a rexx program access it via "dat = Getclip(test)". RXSET without argument lists the clipboard entries in shell window. RXC - closes the REXX resident process after the last rexx program exits. Meanwhile, no new rexx programs can be started, except RexxMast is restarted. Not recommended, but sometimes helpful, if a program calls other script in an infinite loop, otherwise memory is getting low rapidly. WaitforPort - 10 seconds delay. Result code shows an error if named port is not listed. E.g.: "WaitforPort CATCH" after launching "rx catch" from shell or "WaitforPort GOLDED.1" if the editor needs some time to get loaded (slow harddrive, f.e.). |
16 July 2015, 14:22 | #57 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
File examination
With our newly gained knowledge we could examinate files. A simple method is to seek end of file with Seek(). Of course we have to open a file before we set the file cursor to any new position in the file. That is what Seek() does, it moves a file cursor or reports its current position. Seek() can be used in 3 ways Syntax Seek(file, offset, origin) - offset moves the cursor n bytes from origin. Origin is 'B'egin, 'C'urrent or 'E'nd Seek(file, 0, E) jumps to end of file Seek(file, 0, B) rewinds to start Seek(file, 4, B) to read the 5th byte !! Seek(file, 8, C) step another 8 bytes from now (pos 12 or 13th byte in this ex.) Seek(file, 0, C) tells the current index Notice that the offset starts with 0, not 1. The nth byte ist at pos n-1. In our next example we use the rexxreqtools.library (available on aminet) to have a file requester. DOS command 'requestfile' will do, if you don't want use rexxtools. Example Code:
/**/ If Exists('libs:rexxreqtools.library') Then Addlib('rexxreqtools.library',0,-30,0) Else Exit files=rtfilerequest() If Open(infile,files) Then Do size = Seek(infile,0,e) Say "File size =" size "bytes" Say " or" Trunc(size/1024) "kbyte" Close(infile) End Exit Last edited by BigFan; 17 July 2015 at 14:14. Reason: Better file checking |
16 July 2015, 14:23 | #58 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Seek() is very limited. Statef() from rexxsupport.library gives more information to us.
Syntax Statef(file | dir) - returns a string containing file information string looks like "type size block protection days minutes ticks comment" where type is either dir or file. Example Code:
/**/ If Exists('libs:rexxsupport.library') Then Addlib('rexxsupport.library',0,-30,0) Else Exit If Exists('libs:rexxreqtools.library') Then Addlib('rexxreqtools.library',0,-30,0) Else Exit files=rtfilerequest() Parse Value Statef(files) With type size blocks . If type=file then do Say "File size =" size "bytes" Say " or" trunc(size/1024) "kbyte" Say "Size in Blocks" blocks End Else Say "This is a directory." Exit If user selects nothing, return value is empty. Then we parse what Statef() returns. Exercise: Try to parse the date and translate it into a readable form. Last edited by BigFan; 17 July 2015 at 14:12. Reason: formerly written code wasn't fail save |
16 July 2015, 14:27 | #59 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Ascii to hex
In a previous example i told you about the problem using Readln in conjuction
with EoF(). The second function to read from a file is Readch(). This function has less to no problems with file length. We simply tell Readch() the exact numbers of characters to read. Readch() returns a string of same length or less if file boundaries crossed. Stepping only one character gives opportunity to check its value (0 means end of file), makes it easier to check for Eof() than using Readln(). Readch() does not stop on any special character (e.g. $0a , aka line feed). Exercise: Write a little hex file reader. I suggest to make use of the following functions: C2x() Copies() Min() Open() Readch() Statef() Substr() Translate() Xrange() Try first before continue. Example: Code:
/* * $VER: HexReader 1.1 (15.07.15) * written by BigFan */ If Exists('libs:rexxsupport.library') Then Addlib('rexxsupport.library',0,-30,0) Else Exit If Exists('libs:rexxreqtools.library') Then Addlib('rexxreqtools.library',0,-30,0) Else Exit files=rtfilerequest() Parse Value Statef(files) With type size . If size='SIZE' Then Exit /* no file size, no fun */ /* preparing hex code table */ as2 = Xrange('00'x,'1f'x) as3 = Copies(".",32) /* variable init */ chars = "" size = Min(size, 65535) /* string is restricted to 64kB */ Open(infile,files) /* read and concatenate */ Do i=1 To size chars = chars||Readch(infile) End /* apply hex and ascii table */ Do i = 1 To size By 16 hex = C2x(Substr(chars,i,16)) asc = Substr(chars,i,16) tasc = Translate(asc,as3,as2) comb = hex||" "||tasc Writeln(stdout,comb) End Close(infile) Exit Two strings are created, 1st with forbidden characters, 2nd with the replacer We face a new problem here: String length must not exceed 65536 bytes !! The code above does check for it but doesn't deal well with it. After opening the file we concatenate all chars read into one string. This new string is processed in chunks of 16 byte, because hex values have 2 digits we need 32 + 1 + 16 = 49 chars to show it. We read a substring of 16 bytes and convert it to hex values. Same substring again for ascii. Now, Translate() cleans the ascii part from unwanted chars. All values lower than 32 are substituted by a period. Hex string + blank + ascii string are concatenated and written to stdout. This code could be enhanced. Code is sluggish, speed up. Find a way to process files bigger then 64kB (multiple strings). Add file information. Better input handling. Circumvent the 64kB restriction (using allocated memory). Scrolling. (uh, no, that is difficult, you have to create a list view and do your own drawing routines(add graphics.library, poke addresses)) Send data to a file reader or editor. Build a MUI around it with MUIRexx. Add editing features(not serious, ARexx is not good at this, better use another language, ARexx is for interprocess communication). Last edited by BigFan; 17 July 2015 at 14:09. Reason: formerly written code wasn't fail save |
16 July 2015, 15:41 | #60 |
Registered User
Join Date: Feb 2014
Location: Germany
Posts: 261
|
Exercise:
Code is sluggish, speed up. Solution The loop that slows all down is reading all characters one by one. This is a bad idea. Larger files will take a long time to load. As said, Readch() reads a number of characters if we supply size as argument. Delete the loop and replace with chars=readch(infile,size). Much better. |
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
Thread Tools | |
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
AmigaDOS scripting resources | Photon | Coders. System | 26 | 19 March 2018 14:51 |
Very Basic Scripting. Confused. | marduk_kurios | Coders. System | 5 | 06 February 2014 11:13 |
UAE Scripting Layer | FrodeSolheim | support.FS-UAE | 15 | 26 January 2014 15:56 |
C= 64 BASIC as a Scripting Language | Charlie | Retrogaming General Discussion | 2 | 17 November 2008 14:23 |
|
|