/* * misc_helpers.sli * * This file is part of NEST. * * Copyright (C) 2004 The NEST Initiative * * NEST is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * NEST is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NEST. If not, see <http://www.gnu.org/licenses/>. * */ /* A library of handy SLI-routines */ %---------------------------------------------------------------------------- /* BeginDocumentation Name: message - Display an information message Synopsis: (Message) Priority message -> - Priority (From) (Message) message -> where "Priority" may be any positive integer, or one of the following: M_DEBUG, M_STATUS, M_INFO, M_WARNING, M_ERROR, M_FATAL Description: Display a message, if priority is higher than current verbosity-level. message uses the message mechanism which is provided by the SLIInterpreter class. The output consists of a time-stamp (date and time), the name of the calling function, the priority level and the message text. % month dd hh:mm::ss from[pl] messagetext Where month - abbreviated month (Jan-Dec) dd - day in month (01-31) hh - hour (00-23) mm - minute (00-61) ss - seconds (00-61) from - name of the caller, defaults to (SLI) in the short form pl - priority level of the message The time-stamp is useful during long-running simulations. All output is sent to the stream defined in M_OUT which is set to cout by default. The verbosity level can be controlled by the functions verbosity and setverbosity. For compatibility, the function shutupto is still supported as alias to setverbosity. Parameters: In : (MyMessage: Message to display Priority : Priority level of message (integer). Opt: (From) : Name of the caller of class of message. Examples: Short form: 1. (Still alive) M_DEBUG message issues something like % Jul 21 16:23:12 SLI[5] Still alive 1. (Something happened) M_STATUS message issues something like % Jul 21 16:23:12 SLI[5] Still alive 3. (Your last action had no effect) M_INFO message issues something like % Jul 21 16:24:12 SLI[10] Your last action had no effect 4. (Result may be invalid) M_WARNING message issues something like % Jul 21 16:24:24 SLI[20] Result may be invalid 5. (This should never happen!) M_ERROR message issues something like % Jul 21 16:24:36 SLI[30] This should never happen! 6. (It's all broken, let's go home.) M_FATAL message issues something like % Jul 21 16:24:36 SLI[40] It's all broken, let's go home. Long form: 1. M_DEBUG (DEBUGMESSAGE) (This should never happen!) message issues something like % Jul 21 16:24:36 DEBUGMESSAGE[30] This should never happen! Depending on the verbosity-level, the output may be suppressed. Bugs: Author: R Kupper, M.O. Gewaltig, FirstVersion: Apr 9 1999 Remarks: M_* values are defined in system dictionary. You may introduce new priority levels in the user dictionary if convenient. Standard values are: M_ALL = 0 M_DEBUG = 5 M_STATUS = 7 M_INFO = 10 M_WARNING = 20 M_ERROR = 30 M_FATAL = 40 M_QUIET = 100 Standard output: M_OUT = cout SeeAlso: setverbosity */ % Define constants and variables used by message/shutupto systemdict begin %this is unnessessary, if misc_helpers.sli is called from sli-init.sli % but that's not sure, and it won't harm anyway... %Define error levels: /M_ALL 0 def /M_DEBUG 5 def /M_STATUS 7 def /M_INFO 10 def /M_WARNING 20 def /M_ERROR 30 def /M_FATAL 40 def /M_QUIET 100 def end %systemdict /message trie [/stringtype /integertype] { exch (SLI) exch message_ } bind addtotrie [ /integertype /stringtype /stringtype] { message_ } bind addtotrie def %---------------------------------------------------------------------------- /* BeginDocumentation Name: setverbosity - Set verbosity level for message shutupto - Set quiet level for message Synopsis: VerbosityLevel setverbosity -> - M_DEBUG setverbosity -> - M_STATUS setverbosity -> - M_INFO setverbosity -> - M_WARNING setverbosity -> - M_ERROR setverbosity -> - M_FATAL setverbosity -> - Description: Set verbosity level for message. Only messages of priority equal to or greater than the specified level will be issued by the "message" command. Parameters: VerbosityLevel: The new verbosity-level for message-display (see "message"). You may use any poitive integer here. For conveniency, there exist five predifined error levels in systemdict: M_ALL=0, display all messages M_DEBUG=5, display debugging messages and above M_STATUS=7, display status messages and above M_INFO=10, display information messages and above M_WARNING=20, display warning messages and above M_ERROR=30, display error messages and above M_FATAL=40, display failure messages and above M_QUIET=100, suppress all messages Thus, by calling "M_WARNING=20 setverbosity" you indicate that you are interested in seeing error messages and more important messages only. Side Effects: Change verbosity level of the SLI interpreter. Examples: 1. M_INFO setverbosity % Don't display any debugging- or status-messages, only informational and % above. 2. M_ERROR setverbosity % Do inform me about severe errors only. Bugs: Author: R Kupper FirstVersion: Apr 9 1999, Modified by Marc-Oliver Gewaltig, July 21 1999 Modified by Ruediger Kupper, March 19 2003 Remarks: M_* values are defined in system dictionary. You may introduce new priority levels in the user dictionary if convenient. Standard values are: M_ALL = 0 M_DEBUG = 5 M_STATUS = 7 M_INFO = 10 M_WARNING = 20 M_ERROR = 30 M_FATAL = 40 M_QUIET = 100 Standard output: M_OUT = cout SeeAlso: message, verbosity */ /shutupto trie [/integertype] /setverbosity_i load addtotrie def /* %---------------------------------------------------------------------------- /* BeginDocumentation Name: trim - Delete leading/trailing elements in a container. Synopsis: MyStr Character Flag={0|1|2} trim -> TrimmedStr MyArr Element Flag={0|1|2} trim -> TrimmedArr Description: The trim function returns a copy of MyStr (MyArr) with leading and/or trailing Characters (Elements) removed. Parameters: In : MyStr(string): The String to be strimmed MyArr(array): The array to be trimmed Optional In: Character(string): The character to remove Element(any) : The element to remove Flag(int) : A value that controls the action of trim. If Flag is zero, trailing elements are removed. Leading elements are removed if it is equal to 1. Both are removed if it is equal to 2. Out: TrimmedStr(string)/TrimmedArr(array): a copy of MyStr (MyArr) with leading and/or trailing Characters (Elements) removed. Examples: (--hello-you---) (-) 0 trim -> (hello-you---) (--hello-you---) (-) 1 trim -> (--hello-you) (--hello-you---) (-) 2 trim -> (hello-you) Bugs: array-type-trie doesn't work (why?) Author: R Kupper FirstVersion: Apr 10 1999 Remarks: Inspired by IDL's STRTRIM-Function. If Flag doesn't equal 0, 1 or 2, it defaults to 0 and a warning message is issued If trim is called on an empty container, a debugging message is issued Set shutup-level to hide messages! (see shutup) Procedure: a. Flag=0: Scan string for first non-occurance of character, then delete string up to this position. b. Flag=1 executes a. on the reversed string c. Flag=3 executes a. and b. SeeAlso: reverse, message */ % This function will work for strings AND for arrays, as search does: /trim_sa {% stack: str char flag dup dup 0 lt exch 2 gt or {(trim: WARNING - Flag not 0,1,2. Assuming 0.) M_WARNING message pop 0} if %stack: str char flag 2 index size 0 eq %stack: str char flag str {true|false} %check for empty str: {% str is the empty string %stack: str char flag str (trim: DEBUG-INFO - Trim was called on an empty container. No action.) M_DEBUG message 3 npop %stack: str } {% str is not empty % stack: str char flag str pop % stack: str char flag dup 0 eq % stack: str char flag {true|false} {%flag=0, stack: str char 0 pop %stack: str char exch dup %stack: char str str size exch %stack: char str size str size 1 sub %stack: char str size str (size-1) 0 exch 1 exch %stack: char str size str 0 1 (size-1) { %loop entry - stack: char str size str i exch 1 index %stack: char str size i str i get %stack: char str size i str[i] 4 index %stack: char str size i str[i] char eq %stack: char str size i {true|false} { %str[i] equals char. Do nothing. %stack: char str size i pop 1 index %stack: char str size str } { %str[i] is first character unequal to char: %leave i on the stack and exit for-loop %stack: char str size i exch pop 1 index %stack: char str i str exit % exit for-loop. } ifelse } for %at this point, the stack will be % char str i str, if no-char was found at position i % char str size str, if no-char was NOT found at all (i.e. str is a string of chars) % so all we have to do, is erase up to the given position: pop 0 exch %stack: char str 0 {i|size} erase %stack: char resultstr ,where resultstr may be the empty string. exch pop %stack: resultstr % READY! }%end: flag=0 { 1 eq % stack: str char {true|false} {%flag=1, stack: str char exch reverse exch 0 trim_sa % execute trim 0 on reversed string reverse }%end: flag=1 {%flag=2, stack: str char exch 1 index %stack: char str char 0 trim_sa %stack: char newstr exch 1 trim_sa % 2 is a combination of 0 and 1 }%end: flag=2 ifelse } ifelse } % end: str was not the empty string ifelse } bind def /trim trie [/stringtype /stringtype /integertype] { exch 0 get exch trim_sa } bind addtotrie %if character is given as string, change to ASCII-code [/stringtype /integertype /integertype] /trim_sa load addtotrie %string, ASCII-code, flag [/arraytype /anytype /integertype] /trim_sa load addtotrie def %---------------------------------------------------------------------------- /* BeginDocumentation Name: breakup - Break a string or an array at given Substrings or SubArrays, or to given lengths. Synopsis: MyStr BreakStr breakup -> StrArr MyArr BreakArr breakup -> ArrArr MyStr SecLen breakup -> StrArr MyArr SecLen breakup -> ArrArr Description: Break a string or an array at given Substrings or SubArrays, or to sections of given length. In the latter case, the last section may be shorter, if the section length does not evenly divide the string/array length. Parameters: In : MyStr(string)/MyArr(array): String/Array to break up. BreakStr(string)/BreakArr(array): Substring/Subarray defining break points. SecLen(int): Length of sections to form Out: StrArr(array of strings)/ArrArr(array of arrays): array of partial strings/arrays (see example below) Examples: 1. (home/kupper/synod) (/) breakup -> [(home) (kupper) (synod)] 2. (Theresnospaceinhere) ( ) breakup -> [(Theresnospaceinhere)] 3. [23 5 0 17 0] [0] breakup -> [ [23 5] [17] [] ] 4. [23 5 0 17 0] [5 0] breakup -> [ [23] [17 0] ] 5. (abcdef) 3 breakup -> [(abc) (def)] 6. (abcdef) 4 breakup -> [(abcd) (ef)] 7 [1 2 3 4] 2 breakup -> [[1 2] [3 4]] Bugs: Author: R Kupper, H E Plesser FirstVersion: Apr 10 1999 Remarks: Inspired by IDL's STRBREAK-function Procedure: Recursive call of "search" SeeAlso: trim */ % This function will work for strings AND for arrays, as search does: /breakup_sa { % stack: str be array 3 1 roll % stack: array str be search % break at first occurence of breaking element % stack: array strend be {strbegin true|false} { % element found 4 -1 roll exch % stack: strend be array strbegi append %stack: strend be array breakup_sa % RECURSION! } { %element not found append %that's it! } ifelse } bind def /breakup_sa_i { /seclen Set empty { 1 arraystore } { /strarr Set [ 0 strarr length 1 sub seclen ] { seclen strarr rollu getinterval } Table } ifelse } bind def /breakup trie [/stringtype /stringtype] { [] breakup_sa } addtotrie [/arraytype /arraytype ] { [] breakup_sa } addtotrie [/stringtype /integertype] { breakup_sa_i } addtotrie [/arraytype /integertype] { breakup_sa_i } addtotrie def %---------------------------------------------------------------------------- /* BeginDocumentation Name: reverse - Reverse a string or array. Synopsis: MyStr reverse -> rtSyM MyArr reverse -> rrAyM Description: Reverse a string or array. Parameters: In : MyStr(string)/MyArr(array) Out: rtSyM(string)/rrAyM(array): The reverse of MyStr/MyArr Examples: (Madam I'm Adam) reverse -> (madA m'I madaM) (Lagerregal) reverse -> (lagerregaL) (Aide-moi o media) reverse -> (aidem o iom-ediA) [1 2 3] reverse -> [3 2 1] Bugs: Author: R Kupper FirstVersion: Apr 11 1999 Remarks: There is no "forallindexed" for strings. why? Apr 13 1999: -Name changed from "revert" to "reverse" on Apr 13 1999.- -Name "revert" will still be valid, but will issue a warning message.- String-Handling has been changed to a call to the C++-coded function "Revert". The command "Reverse" can also be used for arrays. SeeAlso: trim */ % This function will work for strings AND for arrays, as search does: /reverse_sa { %stack: str / arr container %stack: str () / arr [] 1 index %stack: str () str size exch pop %stack: str () size reserve %reserve enough space in new container %stack: str () exch %stack: () str { %stack: () str[i] prepend } forall } bind def /reverse trie [/stringtype] /reverse_sa load addtotrie %There already is a C++-coded version called "Reverse" for Arrays: %[/arraytype] /reverse_sa load addtotrie [/arraytype] /Reverse load addtotrie def %---------------------------------------------------------------------------- /revert { (WARNING: "revert"-function has been ranamed to "reverse" on Apr 13 1999.\n Change call to "reverse" to avoid this warning message!) M_WARNING message reverse } def /* BeginDocumentation Name: searchif - check wether a substring is contained within a string Synopsis: string1 string2 searchif -> bool array1 array2 searchif -> bool Description: calls search and removes the substrings/-arrays generated thereby. Neither string/array will be conserved (!), bool contains success or failure. Parameters: string1/array1: The object where to search into. string2/array2: The subobject to look for. bool is true on success, otherwise false Examples: (Hello world!) (Hello) searchif -> true [1 3 2 5 2] [3 2] searchif -> true Bugs: Does not have optimal performance; search creates subobjects which are not needed for serachif. Author: Hehl FirstVersion: April 15, 1999 Remarks: SeeAlso: search */ /searchif_sa { exch dup 3 -1 roll % save string1 search % search { 4 npop true % success: do some clean up } { pop pop false % failure: somewhat less to cleanup } ifelse } bind def /searchif trie [/stringtype /stringtype] /searchif_sa load addtotrie [/arraytype /arraytype] /searchif_sa load addtotrie def /* BeginDocumentation Name: oldgetline - "old", ignorant version of getline Synopsis: istream getline -> istream string Description: Reads a string from the supplied stream. If an error occured during the read process, an empty string is returned. This function is for compatibility. Eventually, all occurences of oldgetline should convert to the new, more save version of getline. Diagnostics: NO ERRORS are raised! Author: Marc-Oliver Gewaltig FirstVersion: July 9 1999 Remarks: SeeAlso: getline, readline */ /oldgetline { getline not { () } if } bind def %---------------------------------------------------------------------------- /* BeginDocumentation Name: ignore - ignore any waiting data on an istream Synopsis: istream ignore -> istream Description: "ignore" reads input form the given istream line by line and discards it. Parameters: In : istream: the stream to ignore data in. Out: istream: " " " " " " Diagnostics: Examples: cin ignore myfifo ignore Bugs: - Author: R Kupper FirstVersion: 1999. New version using setNONBLOCK: Oct 22 1999 Remarks: After a call to "ignore", the state of the stream is always "good", and blocking I/O is selected for that stream (O_NONBLOCK==false). SeeAlso: available */ /ignore trie [/istreamtype] { true setNONBLOCK % select non-blocking I/O {% read until EOF getline not {exit} if % read it pop % forget it } loop iclear % reset streamstate to good false setNONBLOCK % select blocking I/O } bind addtotrie def %---------------------------------------------------------------------------- /* BeginDocumentation Name: CyclicValue - project a cyclic value onto it's norm interval (e.g. angle on [0,360)) Synopsis: value [b1 b2] CyclicValue -> normvalue value b1 b2 CyclicValue -> normvalue See below for the meaning of b1, b2! Description: For a given value and a given norm interval, "CyclicValue" returns the value's norm equivalent. This is useful for all values with a cyclic definition, such as angles. The output is always of type double, regardless of the input type. Alternatives: Function CyclicValue_d_d_d if you use it with three doubles (interval = double1 - double2), CyclicValue_d_a if you use it with double and array (both undocumented) -> behaviour and synopsis are the same. Parameters: In : value: value in (-oo, oo) b1, b2: norm interval. This interval is half-open: [.), or (.], depending on the following rules: b1 must not equal b2. b1 always denotes the closed end of the interval. b2 always denotes the open end of the interval. If b1<b2, the norm interval used is [b1,b2). If b1>b2, the norm interval used is (b2,b1]. See below for examples. Out: The value's norm equivalent in the interval [b1, b2). The output is always of type double, regardless of the input type. Diagnostics: b1 must not equal b2. This is not checked for efficency reasons! If b1=b2, code will break with /DivisionByZero during execution. Examples: %% project angle in degrees onto [0,360): -3601 [0 360] CyclicValue -> 359.0 %% project angle in radians onto [-pi,pi): 23.0 [Pi -1 mul Pi] CyclicValue -> -2.13274 %% this demonstrates the handling of open/closed interval ends: %% project number onto [1,4): 1 [1 4] CyclicValue -> 1 2 [1 4] CyclicValue -> 2 3 [1 4] CyclicValue -> 3 4 [1 4] CyclicValue -> 1 %% project number onto (1,4]: 1 [4 1] CyclicValue -> 4 2 [4 1] CyclicValue -> 2 3 [4 1] CyclicValue -> 3 4 [4 1] CyclicValue -> 4 Author: Ruediger Kupper FirstVersion: 13.3.2003 Remarks: Variant *_d_d_d is fastest. Prefer this variant over *_d_a in time critical applications. Inspired by IDL/NASE commandfunction cyclic_value(). SeeAlso: mod, floor */ %% Shall be equivalent to the following IDL code: %% %%Function Cyclic_Value, val, int %% min = int[0] %% max = int[1] %% interval = double(max-min) %% return, val - floor(double(val-min)/interval)*interval %%End /CyclicValue_d_d_d { %stack: value min max 1 pick sub_dd %stack: value min interval %%interval=max-min dup_ rolld %stack: value interval interval min 3 pick %stack: value interval interval min value exch sub_dd exch_ div_dd floor_d mul_dd sub_dd } bind def /CyclicValue_d_a { %stack: value_d [min max] {double} Map arrayload pop %stack: value_d min_d max_d CyclicValue_d_d_d } bind def /CyclicValue [/doubletype /doubletype /doubletype] /CyclicValue_d_d_d load def /CyclicValue [/doubletype /doubletype /integertype] {double_i CyclicValue_d_d_d} bind def /CyclicValue [/doubletype /integertype /doubletype] {rollu double_i rolld CyclicValue_d_d_d} bind def /CyclicValue [/integertype /doubletype /doubletype] {rolld double_i rollu CyclicValue_d_d_d} bind def /CyclicValue [/doubletype /integertype /integertype] {double_i rollu double_i rolld CyclicValue_d_d_d} bind def /CyclicValue [/integertype /integertype /doubletype] {rolld double_i rolld double_i rolld CyclicValue_d_d_d} bind def /CyclicValue [/integertype /doubletype /integertype] {double_i rolld double_i rollu CyclicValue_d_d_d} bind def /CyclicValue [/integertype /integertype /integertype] {double_i rolld double_i rolld double_i rolld CyclicValue_d_d_d} bind def /CyclicValue [/integertype /arraytype] {exch_ double_i exch CyclicValue_d_a} bind def /CyclicValue [/doubletype /arraytype] /CyclicValue_d_a load def /* BeginDocumentation Name: SLIFunctionWrapper - Define a SLI function with lots of comfort. Synopsis: /funcname [/trietypes] [/variablenames] {procedure} SLIFunctionWrapper -> FunctionResult /funcname [/trietypes /variablenames] {procedure} SLIFunctionWrapper -> FunctionResult Description: SLIFunctionWrapper provides a very convenient way to define and manage a SLI function. Much like the command "def", it has a way to define the parameter list of the function. In addition, before the actual routine is called, its parameters will be popped from the stack and assigned to names, which then are provided to the routine in a local dictionary. Furthermore, SLIFunctionWrapper rids the user from the need to restore the operand stack before raising an error, the routine will take care of that. All this is achieved by putting the actual user routine inside a wrapper routine, which is assigned to the given name. The wrapper routine will perform all sorts of bureaucracy, then call the actual routine, and clean up the stack if necessary afterwards. The function's definition resembles the ones in C++. Please see examples below for demonstrations. IMPORTANT: Please see remarks below for a SET OF RULES THE USER ROUTINE MUST ADHERE TO! Parameters: /funcname - The function name (will be assigned to the wrpper function). [/trietypes /variablenames] - List of parameters and their names. This may either be two separate arrays of literals, the first one defining the variable types, the second one their names, or a single array of literals, consisting of pairs of type and name. See examples below. The resulting call looks pretty much like a function definition in C++. {procedure} - The actual procedure to be called from the wrapper. Examples: The following defines a function called "myfunc", taking two arguments of type integer and double. The function will have a type trie for parameter typechacking, as well as a local dictionary. The arguments will be known as "x" and "y" inside the local dictionary of the function. /myfunc [/integertype /x /doubletype /y] { (see how my local dictionary looks like:) = who } SLIFunctionWrapper The parameterlist could have also been specified like this: /myfunc [/integertype /doubletype] [/x /y] {...} SLIFunctionWrapper This defines functions expecting no parameters: /f [] {...} SLIFunctionWrapper /f2 [] [] {...} SLIFunctionWrapper The routine can litter is local dictionary as it likes. The dictionary will automatically be closed when the routine ends: /f [] { /this 23 def /will 0.0 def /be 1 def /forgotten -3 def } SLIFunctionWrapper If the routine raises an error, the local dictionary is closed, and the stack will be automatically restored to the condition it was before the function call: /f [] { /myvar 0.0 def % push rubbish on the stack: 1 2 3 4 5 6 7 % now raise an error: funcname /Error raiseerror } SLIFunctionWrapper SLI ] (a) (b) f Error: /Error in f SLI [2] pstack (b) (a) If a nested routine causes an error, the stack is NOT restored, and the local dictionary is NOT closed: /f [] { /myvar 0.0 def % push rubbish on the stack: 1 2 3 4 5 6 7 % now cause an error: 0 0 div } SLIFunctionWrapper SLI ] (a) (b) f Error: /DivisionByZero in div_ii SLI [12] who -------------------------------------------------- Name Type Value -------------------------------------------------- myvar doubletype 0 -------------------------------------------------- Total number of entries: 1 Diagnostics: Several messages of priority M_DEBUG are issued to let the programmer know what happens. They may be turned off using "setverbosity". Author: Ruediger Kupper FirstVersion: 21.3.2003 Remarks: Rules the user routine MUST adhere to: The user routine must not leave any "mark" on the stack if it fails. The user routine must not make any assumptions on the contents of the stack, that lie deeper than the level at which it took control The user routine must not modify any contents of the operand stack, that lie deeper than the level at which it took control. The user routine must restore the dictionary stack to the state it was when it took control, before it ends or fails. The user routine must not re-define the variable /funcname. When raising an error, /funcname must be specified as the routine name (i.e., the name that was specified in the call to SLIFunctionWrapper). No "bind" is performed on the procedure. If namebinding is wanted, the user has to call "bind" explicitely before passing the procedure. The type-specifyer-array(s) may be empty, indicating that the routine does not expect any parameters. The following names are known by the user routine, being defined in an enclosing dictionary (not the routine's local dictionary): /funcname - The wrapper's name, as specified in the call to SLIFunctionWrapper. /localdict - The user routine's local dictionary. It is already open when the routine takes control, and shall not be closed by the user rotuine. /n_params - Number of parameters to the user routine. /rvarnames - Array containing the names of the local variables defined in the local dictionary, in reversed order. /function - The user routine object. Convenience comes at a cost. The wrapper routine causes considerable overhead. Do not use this technique for routines that will be used at time-critical positions, e.g. inside loops. Availability: SLI2.0 References: [1] Ruediger Kupper, SLI library management, HRI-EU Report 06/05, Honda Research Institute Europe GmbH, 2006. SeeAlso: def, raiseerror, setverbosity, SFWdumpparameters */ %% this is the version that takes separate arrays for variable types and names: /SLIFunctionWrapper [/literaltype /arraytype /arraytype /proceduretype] { %stack: /functionname [trietypes] [variablenames] {procedure} << >> begin %use local variables for SLIFunctionWrapper /function Set reverse /rvarnames Set %% we need them in reversed order. /trietypes Set /funcname Set trietypes length /n_params Set %consistency check (length): rvarnames length n_params neq { M_ERROR (SLIFunctionWrapper) (Trietype array and variable name array must be of same size.) message end %use local variables for SLIFunctionWrapper /SLIFunctionWrapper /DimensionMismatch raiseerror } if %consistency check (types) trietypes { type /literaltype neq { M_ERROR (SLIFunctionWrapper) (Trietype array must contain literals only.) message end %use local variables for SLIFunctionWrapper /SLIFunctionWrapper /TypeMismatch raiseerror } if } forall rvarnames { type /literaltype neq { M_ERROR (SLIFunctionWrapper) (Variable name array must contain literals only.) message end %use local variables for SLIFunctionWrapper /SLIFunctionWrapper /TypeMismatch raiseerror } if } forall %end consistency checks % we now will define the wrapper routine that % actually calls the user routine M_DEBUG (SLIFunctionWrapper) (Defining wrapper ') funcname cvs join ('.) join message funcname trietypes %stack: funcname trietypes % The following will be the function that is actually bound % to the given name. We need a way to "hard code" % the parameters that need to be know inside this procedure, but % which are only known here, in SLIFunctionWrapper. % The way to do this is to use "append" in order to construct the % procedure. { %stack: var1 .. varn % we use local variables in the wrapper: << >> begin } % now the trick to hard-code the parameters: /funcname load append {/funcname Set} join /n_params load append {/n_params Set} join /rvarnames load append {/rvarnames Set} join /function load cvlit append {cvx /function Set} join % now the rest of the routine, which will be joined afterwards. It % can now use the parameters "n_params", "rvarnames" and "function". { %stack: var1 .. varn % we store the parameters in "our" dictionary, % in order to have the originals in case of an error: n_params arraystore /params Set % we push a mark mark % we push the parameters again params arrayload pop %stack: mark var1 .. varn % we give the user routine a "private" dictionary % it will be known in the wrapper's dictionary by the name /localdict /localdict << >> def localdict begin % in this private dictionary, % we read the variables off the stack: % (rvarnames contains the desired variable names in reverse order) rvarnames {Set} forall %stack: mark % now call the user function, in a stopped context: /function load stopped { % the function stopped! %stack: mark lots-of-rubbish /name-of-routine-that-caused-error % each error handler has to do this: errordict /newerror false put % There are two possibilities: % 1. The error was raised by "our" routine. % In this case, we need to restore the stack. % 2. The error was unexpectedly raised in a routine % that was called from "our" routine. % In this case, the current state of the stack, % as well as the local variables should stay as % they are, to be inspected by the user. %stack: mark lots-of-rubbish /name-of-routine-that-caused-error funcname eq {% error was raised by "our" routine: % close its private dictionary end M_DEBUG (SLIFunctionWrapper) (User routine ') funcname cvs join (' stopped.) join message M_DEBUG (SLIFunctionWrapper) (Reason: The routine raised an error.) message M_DEBUG (SLIFunctionWrapper) (Action: Restoring the stack.) message %stack: mark lots-of-rubbish % throw away all the rubbish it left, including our mark. counttomark 1 add_ii npop %stack: % restores the stack to the state before calling the % function. params arrayload pop %stack: var1 .. varn % close local variable dict of wrapper: end % "It is now safe to turn off the computer." M_DEBUG (SLIFunctionWrapper) (Stack restored. Re-raising the error.) message raiseagain } { % error was raised by another routine: M_WARNING (SLIFunctionWrapper) (User routine ') funcname cvs join (' stopped.) join message M_WARNING (SLIFunctionWrapper) (Reason: A nested routine raised an error.) message M_WARNING (SLIFunctionWrapper) (Action: Leaving stack and variable context unchanged for inspection.) message M_DEBUG (SLIFunctionWrapper) (Re-raising the error.) message M_ERROR (SLIFunctionWrapper) (In function ') funcname cvs join (':) join message raiseagain } ifelse } { % the function operated normally. %stack: mark valuable-results % close its private dictionary end M_DEBUG (SLIFunctionWrapper) (User routine ') funcname cvs join (' exited normally.) join message %stack: mark valuable-results % remove the mark from the stack counttomark 1 add_ii -1 roll pop %stack: valuable-results % close local variable dict of wrapper: end % "It is now safe to turn off the computer." } ifelse } join %stack: functionname typetries {wrapperroutine} end %local variables for SLIFunctionWrapper, %because function shall be defined in the outer dict. bind def %okay, we defined the wrapper routine, that's it. } bind def %% this is the version that takes one array of combined variable types and names: /SLIFunctionWrapper [/literaltype /arraytype /proceduretype] {%stack: /functionname [type1 name1 ... typen namen] {procedure} % first handle the special case of an empty array: 1 index length 0 eq { % parameter list is empty [] exch % stack: /functionname [] [] {procedure} SLIFunctionWrapper } { % parameter list is not empty: %stack: /functionname [type1 name1 ... typen namen] {procedure} rollu %stack: {procedure} /functionname [/type1 /name1 ... /typen /namen] %consistency check (number of elements) size dup 2 div 2 mul neq { M_ERROR (SLIFunctionWrapper) (Single variable array must consist of pairs: /variabletype /variablename.) message /SLIFunctionWrapper /DimensionMismatch raiseerror } if %stack: {procedure} /functionname [/type1 /name1 ... /typen /namen] %consistency check (types) dup { type /literaltype neq { M_ERROR (SLIFunctionWrapper) (Variable names and types must be literals only.) message /SLIFunctionWrapper /TypeMismatch raiseerror } if } forall %stack: {procedure} /functionname [/type1 /name1 .. /typen /namen] % end consistency checks % form separate name/type-arrays from the combined one and call the other form of SLIFunctionWrapper: %stack: {procedure} /functionname [/type1 /name1 .. /typen /namen] dup size 1 sub [ 0 rolld 2 ] %stack: {procedure} /functionname [/type1 /name1 .. /typen /namen] [/type1 /name1 .. /typen /namen] [0 size-1 2] Range get %stack: {procedure} /functionname [/type1 /name1 .. /typen /namen] [/type1 .. /typen] exch size 1 sub [ 1 rolld 2 ] %stack: {procedure} /functionname [/type1 .. /typen] [/type1 /name1 .. /typen /namen] [1 size-1 2] Range get %stack: {procedure} /functionname [/type1 .. /typen] [/var1 .. /varn] 4 -1 roll %stack: /functionname [/type1 .. /typen] [/var1 .. /varn] {procedure} SLIFunctionWrapper } ifelse } bind def /* BeginDocumentation Name: SFWdumpparameters - Dump parameters of a SLIFunctionWrapper routine. Synopsis: SWFdumpparameters -> - Description: Debugging aid for SLIFunctionWrapper. This routine can be called from any routine defined using SLIFunctionWrapper. It dumps all parameters and their values to the standard output. Examples: /myfunc [/integertype /doubletype] [/x /y] { (hello) = SFW_dumpparameters (hello) = } SLIFunctionWrapper SLI ] 1 1.0 myfunc hello Dumping parameters for SLIFunctionWrapper routine /myfunc y = 1.000000e+00 x = 1 hello Diagnostics: Issues a warning, if no enclosing SLIFunctionWrapper was found. Author: R. Kupper FirstVersion: 6-Dec-2006 References: [1] Ruediger Kupper, SLI library management, HRI-EU Report 06/05, Honda Research Institute Europe GmbH, 2006. SeeAlso: SLIFunctionWrapper */ /SFWdumpparameters { /funcname lookup not { M_WARNING (SFWdumpparameters) (No enclosing SLIFunctionWrapper was found.) message } { pop (Dumping parameters for SLIFunctionWrapper routine ) =only funcname == rvarnames {dup ( ) =only =only ( = ) =only load ==} forall } ifelse } def /* BeginDocumentation Name: pageoutput - Display the output of a procedure through "page". Synopsis: {procedure} pageoutput -> result_of_procedure Description: "pageoutput" executes the procedure that is passed as an argument. It captures any printed output that the procedure generates and displays it via the "page" command (see documentation of "page"). "pageoutput" redirects SLI's standard output at the level of the file descriptor. This is a very low-level operation, and captures *all* output that the SLI process sends to stdout. This includes any output from SLI commands, from the simulation kernel, and all error messages that go to stdout. If the procedure raises an error, the error message is included in the paged output, and the file descriptors are restored, so that subsequent output goes to the SLI prompt again. Examples: {(This is my output.) =} pageoutput {MemoryInfo} pageoutput {whos} pageoutput {mydir info} pageoutput Bugs: "pageoutput" creates a temporary file that is passed to the "page" command. The pager may be executed in a subprocess of SLI. Since we do not know how long it takes the pager process to open the file, we cannot delete it right after calling "page". Hence, the temporary file remains forever. Currently it is unclear how the temporary file can be removed at a later time. This bug is filed as ticket #262. Diagnostics: Any error messages that the procedure raises go to the paged output. In case of errors, the file descriptors are correctly restored. Author: R Kupper FirstVersion: 22-jul-2008 SeeAlso: page */ /pageoutput[/proceduretype /p] { % create two temporary files for writing: /fname tmpnam def /f fname (w) file def /gname tmpnam def /g gname (w) file def % f will be the temporary storage for the cout file descriptor. % duplicate systemdict::cout to f. % (Subsequent writes to f would write to standard output, but we will not do this). systemdict /cout get f dup2 % now duplicate g to systemdict::cout. % Subsequent writes to standard output (even from the C-level) will go to file g. g systemdict /cout get dup2 % now call the procedure. We catch all errors to restore the standard output in any case! {p exec} stopped {handleerror} if % if there was an error, call the normal handler f systemdict /cout get dup2 % restore previous destination of systemdict::cout f close % close file f fname DeleteFile pop % we don't need it any more g close % close file g gname page % show its contents %gname DeleteFile pop } SLIFunctionWrapper /* %% NOTE: There must be a carriage return after the last line in each file, i.e., here: */