NAME
List::Parseable - routines to work with lists containing a simple language
DESCRIPTION
This module allows you to treat a list (which can be expressed as an actual perl list, or as a string which will be parsed to form a list) as a simple program which returns a value. This allows you to do several tasks that I run into frequently.
A task that occurs often is to have a config file or a data file which is read in by the program. Most of the time, the data stored in these files is fully defined at the time it is read in, but occasionally, the data that you want is more complex, and is best determined at run time.
One obvious way to do this is to build extra logic into the program that understands the data stored in the file and which can do additional operations such as supplying missing values, checking data validity, etc., but often, building a knowledge of the exact format of the data file is not desired. It leads to added complexity in the program, and usually, the types of checks and manipulations that are done are very repetitive.
This module can be used to bypass some of these issues.
- Creating complex values
-
If you need to set a variable in a config file or a data file to some value which can only be determined at runtime, or which is best defined as some definition based on other values is the config file, the value can be set to a list which is interpreted using this module. That way, the actual value can be determined at runtime using a simple program.
- Supplying missing data
-
Defaults for missing values can be supplied.
- Describing valid data
-
If you want to describe validity checks for data or config files, this can be done using this module. A piece of data can be defined as well as a flag which will be evaluated based on the value currently in the config file. If the data is invalid, the flag will be set accordingly.
A description of the data can be written as a list which, when evaluated, will return true if a piece of data meets the validity requirements described there.
All of this could be done by nesting actual perl code and running eval on it, but it is usually desirable to do things in a much safer way. Also, it is rarely necessary to have the full power (and complexity) of the perl language in this case.
ROUTINES
- new
-
use List::Parseable; $obj = new List::Parseable;
Creates a new List::Parseable object.
- version
-
$version = $obj->version();
Check the module version.
- list, string
-
$obj->list(NAME,LIST); $obj->string(NAME,STRING);
The list function takes the arguments and stores them in the object under the given NAME.
The string function takes a single string argument and converts it to a list (string parsing rules are described below).
- eval
-
$val = $obj->eval(NAME);
This must be called after the list or string method is used to store a list under the given name. It parses the list using the list parsing rules described below.
- errors
-
$obj->errors(OPTION,OPTION,...)
When a list is used in any of the operations described below, some of the elements may not be valid for that operation. This option tells how to handle these errors. Allowed values for OPTION are:
exit : the program halts with an error return : the routine returns an empty set or element (depending on type of return value) ignore : the value is ignored (removed from the list) and the operation continues
NOTE: exactly one of the above options may be given. It defaults to "ignore".
In addition, the following options may be included:
stderr : send a warning about invalid elements to stderr stdout : send a warning about invalid elements to stdout both : sends wanring message to both stdout and stderr quiet : never send warnings
The default is quiet.
- vars
-
$obj->vars(HASH);
This takes a hash of the form VAR => VAL where each VAR is the name of a variable, and each VAL is either a scalar or a list reference (which may be nested list references and scalars).
It stores these in the object for use in the "getvar" and other variable operations described below.
LIST PARSING RULES
List parsing consists of two steps.
First, every element is examined. If it is a scalar, it is left untouched duiring the first step. If it is a list reference, the list of values is first parsed using the same rules as the parent list. In this way, nested lists are parsed to any level. As an example of this, the list:
(count a b)
would evaluate to a scalar "2" after the second step (since a list who's first element is "count" evaluates to the number of elements in the list), so the nested structure:
(foo (count a b) 3 (count x))
is identical to:
(foo 2 3 1)
after the first step is complete on the main list.
For the second step of parsing, the list is examined again. It must consist of zero or more operations (each of which is one of the strings described below) followed by zero or more arguments, each of which can be either a list reference or a scalar. The types of arguments allowed depend on the operation.
Arguments start with the first element which is not a known operation. Alternately, if one of the elements is "--", that element signals the end of the operations and the start of the arguments (but is otherwise ignored).
For example, if "foo" and "bar" are known operations, then
(foo bar a b)
has two operations and two arguments. This is equivalent to:
(foo bar -- a b)
The list:
(foo -- bar a b)
has one operation and three arguments since "bar" is not treated as an operation in this case.
Only the first occurence of "--" are treated this way, and only if it follows a set of operations. For example:
(foo a -- bar b)
contains one operation (foo) and 4 arguments (a, --, bar, b).
If no operation is included, it defaults to the "scalar" operation, so
(scalar a b)
(a b)
are equivalent.
If a list includes multiple operations, they are handled one at a time, starting with the right most. For example:
(foo bar a b)
is equivalent to:
(foo (bar a b))
STRING PARSING RULES
When parsing a string, sets of list delimiters are checked for. Valid list delimeters are:
parenthese ()
brackets []
braces {}
The list delimiters may be separated from the list elements by whitespace, but this is optional except in cases where the first element in the list begins with a punctuation mark. In this case, the list delimiter must be followed by space. Any string that starts with a punctuation mark which immediately follows the left list delimiter is treated as an element delimiter.`q
Elements in a list are typically separated by spaces, but including an element delimiter can change this. An element delimiter is a character or string attached to the left list delimiter. The element delimiter MUST start some punctuation mark, but it is not allowed to start with "\" which is treated specially.
For example, the list (a b c) can be written in the following ways:
'( a b c )'
'(a b c)'
'[ a b c ]'
'{: a:b:c }'
Any combination of list delimiters can be used to create nested lists:
'(a (b c) [: d:e ] )'
There is currently no "quoting" mechanism, so there is no way to include the element delimiter in an element, so if an element can contain a space, some other element delimiter must be used. In other words, to make a list: ("this", "is a", "special list"), use:
'(: this:is a:special list)'
In order to include any list delimiter in an element, the left list delimiter must be followed immediately with an "\". It may then be followed by any element delimiter. Everything up to the closing list delimiter is treated as part of the list of elements. No nested lists can be created. For example:
'( (a) (\ x ] ) [\: b:) ] )'
is the list:
( [ 'a' ],
[ 'x', ']' ],
[ 'b', ')' ] )
In order to include a parentheses (either left or right) in a list, use one of the other list delimiters with the "\" option.
KNOWN OPERATIONS
In the following operations, lists of elements may be either scalars or list references, but in most cases, some types of elements may not be valid. In the event of an invalid element, the behavior is dictated by the results of the "errors" method.
The following basic operations are known:
- (scalar ELE0 ELE1 ...), (list ELE0 ELE1 ...)
-
These two operations determine how to treat the results of the parsing. Most operations take a list of scalars as arguments, but some take multiple lists. These require that the "list" operation be used.
For example:
(foo (scalar a b) (list c d))
after parsing all of the sublists is eqivalent to:
(foo a b [ c d ])
where [ c d ] is a list reference.
All element types are allowed.
The following operations take a list and return a scalar based on the list:
- (count ELE0 ELE1 ...)
-
The count operation counts the number of arguments and returns it.
(count a b) => 2 (count (list a b) c) => 2
All element types are allowed.
- (countval VAL ELE0 ELE1 ...)
-
This returns the number of times VAL appears in the list. VAL must be a scalar.
(countval a a b a) => 2
All elements should be scalars.
- (minval ELE0 ELE1 ...), (maxval ELE0 ELE1 ...)
-
This returns the numerical value who's value is the least or greatest.
(minval 5 7 8) => 5 (maxval 5 7 8) => 8
All elements should be numeric scalars.
- (nth N ELE0 ELE1 ...)
-
This returns the Nth element of the list. Elements are numbered 0 to M or -(M+1) to -1.
The first element must be an integer or nothing is returned. All element types are allowed for the remaining arguments.
- (case TEST0 VAL0 ... TESTN VALN [DEFAULT_VAL])
-
All TEST elements must be scalars or nothing is returned. Values can be any type.
Tests are evaluated, one at a time, and the first one that evaluates as true provides the return value.
If no test is true, the default value is returned. If no default is provided, nothing is returned.
- (indexval VAL ELE0 ELE1 ...), (rindexval VAL ELE0 ELE1 ...)
-
This returns the index of the first/last occurence of VAL in the list or -1 if it doesn't appear.
VAL must be a scalar. All other elements should be scalars.
- (join ELE0 ELE1 ...), (join delim DEL ELE0 ELE1 ...)
-
This joins all elements into a single string. By default, a space is used, but this can be overridden by including the "delim" word as the first element in the list followed by the delimiter. DEL can be the keywork "_null_" which means to join them with no delimiter, "_space_" to join them with a space, or "_nl_" to join them with a newline, or "_tab_" to join with a tab.
DEL must be scalar. All others should be scalars.
- ( + ELE0 ELE1 ...), ( * ELE0 ELE1 ...)
-
These return the result of adding or multiplying all of the elements.
All elements should be numbers.
- ( - ELE0 ELE1 ), ( / ELE0 ELE1 )
-
These perform the subtraction (ELE0 - ELE1) or division (ELE0/ELE1). All elements must be numbers, and in the division case, ELE1 must not be zero.
The following returns true or false (1 or 0) based on the list:
- (mintrue N ELE0 ELE1 ...), (maxtrue N ELE0 ELE1 ...)
-
Returns true if at least (or at most) N of the elements evaluate to true.
All elements should be scalars.
- (minfalse N ELE0 ELE1 ...), (maxfalse N ELE0 ELE1 ...)
-
Similar to mintrue/maxtrue but tests for false values.
All elements should be scalars.
- (numtrue N ELE0 ELE1 ...), (numfalse N ELE0 ELE1 ...)
-
Returns true if exactly N of the elements evaluate to true (or false).
All elements should be scalars.
- (and ELE0 ELE1 ...)
-
Returns true if all elements evaluate to true.
All elements should be scalars.
- (or ELE0 ELE1 ...)
-
Returns true if any element evaluates to true.
All elements should be scalars.
- (not ELE0 ELE1 ...)
-
Returns true if all elements evaluate to false.
All elements should be scalars.
- (member VAL ELE0 ELE1 ...)
-
Returns true if any element in the list is equal to the value.
All elements should be scalars. The first element MUST be a scalar or nothing can be returned.
- (absent VAL ELE0 ELE1 ...)
-
Returns true if no element in the list is equal to the value.
All elements should be scalars. The first element MUST be a scalar or nothing can be returned.
- ( ELE0 ELE1 ); also >= == <= < !=>
-
This compares ELE0 and ELE1 numerically. It returns true if ELE0 is greater than ELE1. The other common mathematcial operations: >=, =, <=, <, != are also available.
Note that space is required after the opening list delimiter in order to not confuse them with element delimiters.
Exactly two numerical elements are required in all cases.
- ( gt ELE0 ELE1 ); also ge eq le lt ne
-
This compares ELE0 and ELE1 alphabetically. It returns true if ELE0 is greater than ELE1. The other common string operations: ge, eq, le, lt, ne are also available.
Exactly two scalar elements are required in all cases.
- (if TEST), (if TEST VAL1), (if TEST VAL1 VAL2)
-
This checks to see if TEST evaluates to true. If it is true, it returns VAL1 if it is included or true otherwise. If it is false, it returns VAL2 if itis include or false otherwise.
TEST must be a scalar. The other values can be any type.
- (is_equal LIST0 LIST1)
-
This takes two list references (which should contain only scalars) and checks to make sure that the elements are equal (order is ignored). If they are, true is returned. Otherwise, false is.
If either argument is not a list reference, or if either list contains non-scalars, nothing is returned.
- (not_equal LIST0 LIST1)
-
Similar to is_equal, but returns true if the two lists are different.
- (iff ELE0 ELE1 ...)
-
This returns true if all elements are true or all are false. It returns false if they are a mixture of true and false.
- (range NUM X Y); also rangeL rangeR rangeLR
-
These check to make sure that NUM is in the range X to Y. All three must be numeric, and X must be less than (or equal) to Y.
It returns true in the following cases:
range X <= NUM <= Y rangeL X < NUM <= Y rangeR X <= NUM < Y rangeLR X < NUM < Y
and false otherwise.
The following manipulate a list.
- (flatten ELE0 ELE1 ...)
-
This takes all elements (which may be scalars or nested list references) and returns a flat list with all of the elements from any level.
All element types are allowed.
- (union ELE0 ELE1 ...)
-
This combines all of the members of all of the elements (which may be scalars or nested lists) into a single list of elements. Only the top level is flattened. So:
(union a [b] [ [c], [d] ]) => (a b [c] [d])
All element types are allowed.
- (sort ELE0 ELE1 ...)
-
This returns the list sorted alphabetically. The list is flattened first.
All elements should be scalars.
- (sort_by_method METHOD LIST ARG1 ARG2 ...)
-
This returns the list sorted by the method given. The method can be any method in the Sort::DataTypes module. LIST must be a list produced by the (list ELE0 ELE1 ...) operation.
The first element (METHOD) must be a valid method and LIST must be a list reference or nothing can be returned. Other arguments must be valid for the sort method, but are not checked in advance.
- (unique ELE0 ELE1 ...)
-
This removes duplicate elements and returns a list of unique elements.
All elements should be scalars.
- (compact ELE0 ELE1 ...)
-
This removes all empty ("") elements and returns a flat list of all remaining elements.
All elements should be scalars.
- (true ELE0 ELE1 ...)
-
This removes all elements that evaluate to false and returns a flat list of all remaining elements.
All elements should be scalars.
- (pop ELE0 ELE1 ...), (shift ELE0 ELE1 ...)
-
This removes the last/first element from the list and returns the resulting list.
All element types are allowed.
- (pad LENGTH ELE0 ELE1 ...)
-
This takes a list of elements and pads it to the right with spaces until it is LENGTH characters long. If LENGTH is negative, it will pad to the left.
The first argument must be an integer or nothing will be returned. All others should be scalars.
- (padchar LENGTH CHAR ELE0 ELE1 ...)
-
This is identical to (pad ...) but it will pad with an arbitrary character (the 2nd argument).
The first arguement must be an integer and the 2nd argument must be a single character or nothing will be returned. Other elements should be scalars.
- (column N LIST0 LIST1 ...)
-
This returns a list of the Nth element of each of the listrefs. All arguments (except for the first) must be listrefs.
- (reverse ELE0 ELE1 ...)
-
This returns the reverse of the list.
All element types are allowed.
- (rotate N ELE0 ELE1 ...)
-
This rotates the list of element N times. If N is positive, a single rotation is to remove the first element and put it on the end. If N is negative, a single rotation is to remove the last element and move it to the first.
N must be an integer or nothing is returned. All element types are allowed for the list.
- (delete VAL ELE0 ELE1 ...)
-
This removes all occurences of VAL from the list.
VAL must be a scalar or nothing is returned. All other elements should be scalars.
- (clear ELE0 ELE1 ...)
-
This clears all elements from the list and returns an empty list.
- (append STRING ELE0 ELE1 ...), (prepend STRING ELE0 ELE1 ...)
-
These append or prepend a string to all elements in the list.
The first element must be a scalar. All others should be scalars.
- (splice LIST N LEN [ELE0 ELE1 ...])
-
This deletes LEN elements from LIST starting at element N and inserts the ELE elements in their place.
LIST must be a list reference, N and LEN must be integers (LEN must be zero or positive). The remaining elements can be any type.
- (slice N LEN ELE0 ELE1 ...)
-
This returns the slice of the list of elements starting with the Nth element and including LEN number of elements.
N and LEN must be integers (LEN must be zero or positive). The remaining elements can be any type.
- (fill LIST N LEN VAL)
-
This sets elements in LIST. The first argument is required, and must be a list reference. All other arguments are optional. Elements are numbered 0 to M or -(M+1) to -1. It is valid to refer to elements with an index greater than M (these are elements which will be added on to the right of the list), or less than -(M+1) (these are elements which will be added to the left.
If N is given, it must be an integer. This is the index of the first element of the list to change.
If LEN is given, it must be an integer. This is the number of elements to change starting at the Nth element and moving right. If it is negative, it is the number of elements to change starting with the Nth element and moving left. If LEN is zero,the list is unmodified.
If VAL is set, all elements to be changed will be set to it. Otherwise, they will be set to "".
The elements to be set depend on N and LEN (and M). The following table describes the changes for cases where LEN is not given. L refers to an index to the left of the list, R refers to an index to the right of the list, and C refers to an index in the list.
N === A If both N and LEN are omitted, the entire list is filled with "". L This adds blank elements to the left of the list out to (and including) the Lth element. Existing elements are unmodified. C This sets from Cth element to the end of the list to "". R This adds blank elements to the right of the list out to (and including) the Rth element. Existing elements are unmodified.
For cases where LEN is given, N and LEN explicitly define the start and stop elements to modify. Either or both elements can be to the left of the list or to the right of the list.
All elements in the (N,LEN) range are set to the value. If LEN goes past the end of the list, additional elements are added and set to that value. If N points to elements before the list, additional elements are added to the left.
One possible confusion is when the operation is used to fill elements which are outside of the list entirely. For example if LIST contains:
(a a a)
and N is 4, LEN is 2, VAL is b, the resulting list is:
(a a a "" b b)
(so an empty element was added to get the list out to the portion that was being set).
- (difference LIST0 LIST1), (d_difference LIST0 LIST1)
-
These take two lists and removes all elements in the second list from the first (and return the new list). The difference between the two is how duplicate entries are handled. In the first call, all duplicates are removed. In the second call, only one instance per element in the second list is removed.
(difference [list a a b c] [list a]) => (b c) (d_difference [list a a b c] [list a]) => (a b c)
- (intersection LIST0 LIST1), (d_intersection LIST0 LIST1)
-
This takes two lists and finds the intersection of the two. The intersection are the elements that are in both lists. In the first call, duplicates are ignored returning only a single instance of the intersection. In the second call, duplicates may be included in the intersection.
(intersection [list a a b c] [list a a a b]) => (a b) (d_intersection [list a a b c] [list a a a b]) => (a a b)
- (symdiff LIST0 LIST1), (d_symdiff LIST0 LIST1)
-
This takes the symmetric difference between two lists. The symmetric difference are elements that are in either list but not both. Again, duplicates are allowed in the second call.
(symdiff [list a a b c] [list a a a b]) => (c) (d_symdiff [list a a b c] [list a a a b]) => (a c)
The following variable operations are known:
- (getvar VAR)
-
This returns the value of a variable named VAR. VAR must be a valid variable name or nothing is returned.
- (setvar VAR VAL)
-
This sets the variable VAR to the given value (which may be a list or a scalar).
Returns VAL.
- (default VAR VAL)
-
This sets the values of VAR to VAL unless VAR already has a value.
Returns the value of VAR.
- (unsetvar VAR)
-
Removes VAR from the defined variables.
Returns nothing.
- (shiftvar VAR), (popvar VAR)
-
These shift or pop a value from the variable. Nothing is returned if the operation is not valid. Otherwise, the shifted/popped value is returned.
- (unshiftvar VAR VAL), (pushvar VAR VAL)
-
These add a new element to the start or end of the given variable. If the variable refers to a scalar, it will be converted to a list.
Nothing is returned.
EXAMPLES
- Reading a config file
-
A simple config file might contain a two types of lines. The first type would be lines which actually set a config variable to a value:
Var = Val
and the second type would be lines which use this module to set more complex values:
Var : ( ... )
You might read the file, one line at a time, in the following way:
use List::Parseable; $lp = new List::Parseable; %CONFIG = (); @lines = ...; # @lines contains the lines from the config file foreach $line (@lines) { if ($line =~ /^\s*(\S+)\s*=\s*(.*?)\s*$/) { set_value($1,$2); } elsif ($line =~ /^\s*(\S+)\s*:\s*(.*?)\s*$/) { parse_value($1,$2); } } # This stores a value in a variable. The value is stored in both # a global config hash and in the List::Parseable object so that # the config values can be used in setting complex values. # sub set_value { my($var,$val) = @_; $::CONFIG{$var} = $val; $lp->vars($var,$val); } # Set a complex variable using List::Parseable. # sub parse_value { my($var,$str) = @_; $lp->string("curr",$str); my ($val) = $lp->eval("curr"); set_value($var,$val); }
- Setting a complex value
-
If you have a config file with three values: ValA, ValB, and Ave, and you want Ave to be the average of ValA and ValB, but you don't want to build this into the program that reads the data, you could have the following (using the program in the example for reading a config file):
ValA = 7 ValB = 9 Ave : ( / ( + (getvar ValA) (getvar ValB) ) 2 )
- Supplying a missing value
-
If you have a config file and you want to provide defaults for missing values, you can do the following:
ValA = suppliedA ValA : (default ValA defaultA) ValB : (default ValB defaultB)
This will result in the ValA being 'suppliedA' and ValB being 'defaultB'.
BACKWARDS INCOMPATABILITIES
- 1.01 no longer uses < as list delimiters>
-
In order to simplify the use of the <, <=, >, and >= operators, the <> symbols will no longer be used as list delimiters.
BUGS AND QUESTIONS
If you find a bug in this module, please send it directly to me (see the AUTHOR section below). Alternately, you can submit it on CPAN. This can be done at the following URL:
http://rt.cpan.org/Public/Dist/Display.html?Name=List-Parseable
Please do not use other means to report bugs (such as usenet newsgroups, or forums for a specific OS or linux distribution) as it is impossible for me to keep up with all of them.
When filing a bug report, please include the following information:
- *
-
The version of the module you are using. You can get this by using the script:
use List::Parseable; $obj = new List::Parseable; print $obj->version(),"\n";
- *
-
The output from "perl -V"
If you have a problem using the module that perhaps isn't a bug (can't figure out the syntax, etc.), you're in the right place. Go right back to the top of this manual and start reading. If this still doesn't answer your question, mail me directly.
KNOWN PROBLEMS
None at this point.
LICENSE
This script is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
AUTHOR
Sullivan Beck (sbeck@cpan.org)