0.00%
Search · Index

Weblog Page

Filtered by date 2007-07-24, 1 - 4 of 4 Postings (all, summary)

Solutions

Created by Anett Szabo, last modified by Anett Szabo 24 Jul 2007, at 04:14 PM

V.1 / 1

 

 

V.1 / 2

 

# K :: alpha beta -> alpha
proc K {x y} {
return $x
} 

 

 

V.3 / 1

 

 

V.3 / 2  

# incrlist :: [num] -> [num]
proc incrlist {L} {
set k [list]
set i 0
while {$i<[llength $L]} {
lappend k [ expr [lindex $L $i]+1 ]
incr i
}
return $k
}

  

 or

   

 

 

V.3/ 3

# strlenlist :: [str] -> [num]
proc strlenlist {L} {
set k {}
foreach i ${L} {
lappend k [string length $i]
}
return $k
}

 

 

V.3 /4 

proc sumlist {mylist} {
set result 0
foreach element $mylist {
set result [expr $result + $element]
}
return $result
}

 

 

V.3/5

 

proc multlist {mylist} {
set result 1

foreach element $mylist {

set result [expr $result * $element]

}
return $result
}

 

 

V.3 / 6

proc catlist {L} {
return [join $L ""]
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Tcl for Web Nerds Introduction

Created by Hal Abelson, Philip Greenspun, and Lydia Sandon, last modified by Anett Szabo 24 Jul 2007, at 04:05 PM

Evaluation and quoting


Each line of Tcl is interpreted as a separate command:
procedure_name arg1 arg2 arg3
Arguments are evaluated in sequence. The resulting values are then passed to procedure_name, which is assumed to be a system- or user-defined procedure. Tcl assumes that you're mostly dealing with strings and therefore stands some of the conventions of standard programming languages on their heads. For example, you might think that set foo bar would result in Tcl complaining about an undefined variable (bar). But actually what happens is that Tcl sets the variable foo to the character string "bar":
> tclsh
% set foo bar
bar
% set foo
bar
%

The first command line illustrates that the set command returns the new value that was set (in this case, the character string "bar"), which is the result printed by the interpreter. The second command line uses the set command again, but this time with only one argument. This actually gets the value of the variable foo. Notice that you don't have to declare variables before using them.

By analogy with Scheme, you'd think that we could just type the command line $foo and have Tcl return and print the value. This won't work in Tcl, however, which assumes that every command line invokes a procedure. This is why we need to explicity use set or puts.

Does this mean that you never need to use string quotes when you've got string literals in your program? No. In Tcl, the double quote is a grouping mechanism. If your string literal contains any spaces, which would otherwise be interpreted as argument separators, you need to group the tokens with double quotes:

% set the_truth Lisp is the world's best computer language
wrong # args: should be "set varName ?newValue?"
% set the_truth "Lisp is the world's best computer language"
Lisp is the world's best computer language
In the first command above, the Tcl interpreter saw that we were attempting to call set with seven arguments. In the second command, we grouped all the words in our string literal with the double quotes and therefore the Tcl interpreter saw only two arguments to set. Note a stylistic point here: multi-word variable names are all-lowercase with underscores separating the words. This makes our Tcl code very compatible with relational database management systems where underscore is a legal character in a column name.

In this example, we invoked the Unix command "tclsh" to start the Tcl interpreter from the Unix shell. Later on we'll see how to use Tcl in other ways:

  • writing a program file and evaluating it
  • embedding Tcl commands in Web pages to create dynamic pages
  • extending the behavior of the Web server with Tcl programs
For now, let's stick with typing interactively at the shell. You can keep evaluating Tcl commands at the % prompt until you exit the Tcl shell by evaluating exit.

To indicate a literal string that contains a space, you can wrap the string in double quotes. Quoting like this does not prevent the interpreter from evaluating procedure calls and variables inside the strings:

% set checking_account_balance [expr {25 + 34 + 86}]
145
% puts "your bank balance is $checking_account_balance dollars"
your bank balance is 145 dollars
% puts "ten times your balance is [expr {10 * $checking_account_balance}] dollars"
ten times your balance is 1450 dollars
The interpreter looks for dollar signs and square brackets within quoted strings. This is known as variable interpolation What if you need to include a dollar sign or a square bracket? One approach is to escape with backslash:
% puts "your bank balance is \$$checking_account_balance"
your bank balance is $145
% puts "your bank balance is \$$checking_account_balance \[pretty sad\]"
your bank balance is $145 [pretty sad]

If we don't need Tcl to evaluate variables and procedure calls inside a string, we can use braces for grouping rather than double quotes:

% puts {your bank balance is $checking_account_balance dollars}
your bank balance is $checking_account_balance dollars
% puts {ten times your balance is [expr {10 * $checking_account_balance}] dollars}
ten times your balance is [expr {10 * $checking_account_balance}] dollars
Throughout the rest of this book you'll see hundreds of examples of braces being used as a grouping character for Tcl code. For example, when defining a procedure or using control structure commands, conditional code is grouped using braces.

Keep it all on one line!

The good news is that Tcl does not suffer from cancer of the semicolon. The bad news is that any Tcl procedure call or command must be on one line from the interpreter's point of view. Suppose that you want to split up
% set a_very_long_variable_name "a very long value of some sort..."
If you want to have newlines within the double quotes, that's just fine:
% set a_very_long_variable_name "a very long value of some sort... 
with a few embedded newlines
makes for rather bad poetry"
a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry
%
It also works to do it with braces
% set a_very_long_variable_name {a very long value of some sort... 
with a few embedded newlines
makes for rather bad poetry}
a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry
%
but if you were to try
set a_very_long_variable_name 
"a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry"
Tcl would interpret this as two separate commands, the first a call to set to find the existing value of a_very_long_variable_name and the second a call to the procedure named "a very long value...":
can't read "a_very_long_variable_name": no such variable
invalid command name "a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry"
If you want to continue a Tcl command on a second line, it is possible to use the backslash to escape the newline that would otherwise terminate the command:
% set a_very_long_variable_name \
"a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry"
a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry
%
Note that this looks good as code but the end-result is probably not what you'd want. The second and third lines of our poem contain seven spaces at the beginning of each line. You probably want to do something like this:
% set a_very_long_variable_name "a very long value of some sort... 
with a few embedded newlines
makes for rather bad poetry"

 

 

Case Sensitivity, Poisonous Unix Heritage, and Naming Conventions

The great case-sensitivity winter descended upon humankind in 1970 with the Unix operating system.
% set MyAge 36
36
% set YearsToExpectedDeath [expr {80-$Myage}]
can't read "Myage": no such variable
%
Variables and procedure names in Tcl are case-sensitive. We consider it very bad programming style to depend on this, though. For example, you shouldn't simultaneously use the variables Username and username and rely on the computer to keep them separate; the computer will succeed but humans maintaining the program in the future will fail. So use lowercase all the time with underscores to separate words!

 

Procedures 

One of the keys to making a large software system reliable and maintainable is procedural abstraction. The idea is to take a complex operation and encapsulate it into a function that other programmers can call without worrying about how it works.

To define a procedures in Tcl use the following syntax:

proc name { list_of_arguments } {
body_expressions
}

This creates a procedure with the name "name." Tcl has a global environment for procedure names, i.e., there can be only one procedure called "foobar" in a Tcl system.

The next part of the syntax is the set of arguments, delimited by a set of curly braces. Each argument value is then mapped into the procedure body, which is also delimited by curly braces. As before, each statement of the procedure body can be separated by a semi-colon or a newline (or both). Here's an example, taken from the calendar widget component of the ArsDigita Community System:

proc calendar_convert_julian_to_ansi { date } {
set db [ns_db gethandle subquery]
# make Oracle do all the real work
set output [database_to_tcl_string $db \
"select trunc(to_date('$date', 'J')) from dual"]
ns_db releasehandle $db
return $output
}

As  you can see the variable "date" is set in the context of the procedure so you can address the value with "$date".

Here's the factorial procedure in Tcl:

% #this is good old recursive factorial
% proc factorial {number} {
if { $number == 0 } {
return 1
} else {
return [expr {$number * [factorial [expr {$number - 1}]]}]
}
}
% factorial 10
3628800
At first glance, you might think that you've had to learn some new syntax here. In fact, the Tcl procedure-creation procedure is called like any other. The three arguments to proc are procedure_name arglist body. The creation command is able to extend over several lines not because the interpreter recognizes something special about proc but because we've used braces to group blocks of code. Similarly the if statement within the procedure uses braces to group its arguments so that they are all on one line as far as the interpreter is concerned.

As the example illustrates, we can use the standard base-case-plus-recursion programming style in Tcl. Our factorial procedure checks to see if the number is 0 (the base case). If so, it returns 1. Otherwise, it computes factorial of the number minus 1 and returns the result multiplied by the number. The # character signals a comment.

Examine the following example:

% set checking_account_balance [expr {25 + 34 + 86}]
145
% puts "\nYour checking balance is \$$checking_account_balance.
If you're so smart, why aren't you rich like Bill Gates?
He probably has \$[factorial $checking_account_balance] by now."


Your checking balance is $145.
If you're so smart, why aren't you rich like Bill Gates?
He probably has $0 by now.
There are a few things to observe here:
  • The "\n" at the beginning of the quoted string argument to puts resulted in an extra newline in front of the output.
  • The newline after balance. did not terminate the puts command. The string quotes group all three lines together into a single argument.
  • The [factorial... ] procedure call was evaluated but resulted in an output of 0. This isn't a bug in the evaluation of quoted strings, but rather a limitation of the Tcl language itself:
    % factorial 145
    0

 


 Exercises

 

 1. Write the identity function (the I combinator), which simply returns its argument (any type of argument) unchanged:
# I :: alpha -> alpha
proc I {x} {...}
Examples:
I 12
=> 12
I foo
=> foo
I {string length abracadabra}
=> {string length abracadabra}

Why is this a useful function?

Answer

 

 2. Write the K combinator, which takes two arguments and always returns its first argument, unchanged:
# K :: alpha beta -> alpha
proc K {x y} {...}
Examples:
K 0 456
=> 0
K foo 1
=> foo

Why is this a useful function?

Answer

 

---

based on Tcl for Web Nerds 

List Operations

Created by Anett Szabo, last modified by Anett Szabo 24 Jul 2007, at 01:19 PM

A Tcl list holds a sequence of elements, each of which can be a number, a string, or another list. Let's look at the commands for constructing a list:
% # create an empty list using the list command  
% set user_preferences [list]
% # verify that we've created a 0-item list
% llength $user_preferences
0
% lappend user_preferences "hiking"
hiking
% lappend user_preferences "biking"
hiking biking
% lappend user_preferences "whale watching"
hiking biking {whale watching}
% llength $user_preferences
3
At this point, the variable user_preferences is a three-element list. We can pull individual items out with lindex:
% lindex $user_preferences 0
hiking
% lindex $user_preferences 1
biking
% lindex $user_preferences 2
whale watching
% lindex $user_preferences 3
% lindex $user_preferences 5

Note, that  lindex list 0  gives the first element of the list! (Indexing is 0-based and lindex will return the empty string rather than an error if you supply an out-of-range index.)

When producing a page for a user, we'd be more likely to be interested in searching the list. The command lsearch returns the index of the list element matching a query argument or -1 if unsuccessful:

if { [lsearch -exact $user_preferences "hiking"] != -1 } {
# look for new articles related to hiking
}

 

Concat

Suppose that User A marries User B. You want to combine their preferences into a household_preferences variable using the concat command:

% # use the multiple-argument form of list to create an N-element
% # list with one procedure call
% set spouse_preferences [list "programming" "computer games" "slashdot"]
programming {computer games} slashdot
% set household_preferences [concat $user_preferences $spouse_preferences]
hiking biking {whale watching} programming {computer games} slashdot
% llength $household_preferences
6

 

Split and Join

Suppose we have a file called addressees.txt with information about people, one person to a line. Suppose each of these lines contains, among other information, an email address which we assume we can recognize by the presence of an at-sign (@). The following program extracts all the email addresses and joins them together, separated by commas and spaces, to form a string called spam_address that we can use as the Bcc: field of an email message, to spam them all:

# open the file for reading
set addressees_stream [open "~/addressees.txt" r]

# read entire file into a variable
set contents_of_file [read $addressees_stream]

close $addressees_stream

# split the contents on newlines
set list_of_lines [split $contents_of_file "\n"]

# loop through the lines
foreach line $list_of_lines {
if { [regexp {([^ ]*@[^ ]*)} $line one_address] } {
lappend all_addresses $one_address
}
}

# use the join command to mush the list together
set bcc_line_for_mailer [join $all_addresses ", "]
Some things to observe here:
  • We've used the foreach operator (see the chapter on control structure) to iterate over the list formed by splitting the file at newline characters.
  • We use pattern matching to extract the email address from each line. The pattern here specifies "stuff that doesn't contain a space, followed by at-sign, followed by stuff that doesn't contain a space." (See the explanation of regexp in the chapter on pattern matching.)
  • The iteration keeps lappending to all_addresses, but this variable is never initialized. lappend treats an unbound list variable the same as an empty list.



 

Reference: List operations

  • list arg1 arg2 ...
    Construct and return a list the arguments. Akin to (list arg1 arg2...) in Scheme.
    set foo [list 1 2 [list 3 4 5]] ==> 1 2 {3 4 5}
    or   
    set foo {1 2 {3 4 5}}==> 1 2 {3 4 5}
  • lset varName ?index...? newValue
    Gives you the opportunity to insert elements at a given position and work with positions within a list in general.

    .
     

  • lindex list i
    Returns the ith element from list; starts at index 0.

    llength $foo ==> 1

  • llength list
    Returns the number of elements in list.
    llength $foo ==> 3

  • lrange list i j
    Returns the ith through jth elements from list.
    lrange $foo 1 2 ==> 2 {3 4 5}

  • lappend listVar arg arg...
    Append elements to the value of listVar and reset listVar to the new list. Please note that listVar is the name of a variable and not the value, i.e., you should not put a $ in front of it (the same way that set works.
    lappend foo [list 6 7] ==> 1 2 {3 4 5} {6 7}
    set foo ==> 1 2 {3 4 5} {6 7}

  • linsert list index arg arg...
    Insert elements into list before the element at position index. Returns a new list.
    linsert $foo 0 0  ==> 0 1 2 {3 4 5} {6 7}
    
  • lreplace list i j arg arg...
    Replace elements i through j of list with the args. Returns a new list and leaves the original list unmodified.
    lreplace $foo 3 4 3 4 5 6 7 ==> 0 1 2 3 4 5 6 7
    set foo ==> 1 2 {3 4 5} {6 7}
    
  • lsearch mode list value
    Return the index of the element in list that matches the value according to the mode, which is -exact, -glob, or -regexp. -glob is the default. Return -1 if not found.
    set community_colleges [list "caltech" "cmu" "rpi"]
    lsearch -exact $community_colleges "caltech" ==> 0 lsearch -exact $community_colleges "harvard" ==> -1

  • lsort switches list
    Sort elements of the list according to the switches: -ascii, -integer, -real, -increasing, -decreasing, -command command. Returns a new list.
    set my_friends [list "herschel" "schlomo" "mendel"]
    set my_sorted_friends [lsort -decreasing $my_friends] ==> schlomo mendel herschel
  • concat arg arg...
    Join multiple lists together into one list.
    set my_wifes_friends [list "biff" "christine" "clarissa"]
    concat $my_wifes_friends $my_friends ==> biff christine clarissa herschel schlomo mendel

  • join list joinString
    Merge the elements of list to produce a string, where the original list elements are separated by joinString. Returns the resulting string. Note:  if you want to merge without separating the elements, just put "" . 
    set foo_string [join $foo ":"] ==> 0:1:2:3:4:5:6:7

  • split string splitChars
    Split a string to produce a list, using (and discarding) the characters in splitChars as the places where to split. Returns the resulting list.
    set my_ip_address 18.1.2.3 ==> 18.1.2.3
    set ip_elements [split $my_ip_address "."] ==> four element list,
    with values 18 1 2 3

 


 Exercises

 

 1. Write the iota function, which takes a numeric argument n and returns a list of numbers of length n which are the numbers from 0 to n-1.

# iota :: num -> [num]
proc iota {n} {...}

Examples:

iota 3
=> 0 1 2
iota 20
=> 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Answer

 

 2. Write the function incrlist, which takes one argument, a list of numbers, and returns a list of equal length as a result, in which each element is the successor to the corresponding element of the argument list.

# incrlist :: [num] -> [num]
proc incrlist {L} {...}
incrlist {34 987 1 567 -23 8}
=> 35 988 2 568 -22 9
incrlist [iota 12]
=> 1 2 3 4 5 6 7 8 9 10 11 12

Hint: Arithmetic is not done directly by the Tcl interpreter. It is done by calling the C library using the expr command on arthmetic expressions.

Does your function work for the empty list?

Answer

 

 3. Write the function strlenlist, which takes one argument, a list of strings, and returns a list of equal length as a result, in which each element is the string length of the corresponding element of the argument list.

# strlenlist :: [str] -> [num]
proc strlenlist {L} {...}
strlenlist {34 987 1 567 -23 8}
=> 2 3 1 3 3 1
strlenlist {foo bar antidisestablishmentarianism}
=> 3 3 28

Does your function work for the empty list? 

Answer

 

 4. Write sumlist, which takes one argument, a list of numbers, and returns, as result, a single number: the sum of the numbers in the argument list.

# sumlist :: [num] -> num
proc sumlist {L} {...}
sumlist [iota 3]
=> 3
sumlist {34 987 1 567 -23 8}
=> 1574

Answer

 

 5. Write multlist, which takes one argument, a list of numbers, and returns, as result, a single number: the product of the numbers in the argument list.

# multlist :: [num] -> num
proc multlist {L} {...}
multlist [iota 3]
=> 0
multlist [incrlist [iota 3]]
=> 3
multlist {34 987 1 567 -23 8}
=> 793928272

Answer

 

 6. Write catlist, which takes one argument, a list of strings, and returns, as result, a single string: the concatenation of the strings in the argument list.

# catlist :: [str] -> str
proc catlist {L} {...}
catlist [iota 3]
=> 012
catlist [incrlist [iota 3]]
=> 123
list {foo bar antidisestablishmentarianism}
=> foobarantidisestablishmentarianism

Answer

 


 

 

 

 

 

Data abstraction with lists

The Tcl shell's output is giving you an ugly insight into the internal representation of lists as strings, with elements being separated by spaces and grouped with braces. There is no reason to rely on how Tcl represents lists or even think about it. Practice data abstraction by using Tcl lists as your underlying storage mechanism but define constructor and accessor procedures that the rest of your source code invokes. You won't find a huge amount of this being done in Web development because the really important data structures tend to be RDBMS tables, but here's an example of how it might work, taken from http://photo.net/philg/careers/four-random-people.tcl. We're building a list of lists. Each sublist contains all the information on a single historical figure. Method A is quick and dirty:

set einstein [list "A. Einstein" "Patent Office Clerk" "Formulated Theory of Relativity."]

set mill [list "John Stuart Mill" "English Youth" "Was able to read Greek and Latin at age 3."]

# let's build the big list of lists
set average_folks [list $einstein $mill ...]

# let's pull out Einstein's title
set einsteins_title [lindex $einstein 1]

Method B uses data abstraction:

proc define_person {name title accomplishment} {
return [list $name $title $accomplishment]
}

proc person_name {person} {
return [lindex $person 0]
}

proc person_title {person} {
return [lindex $person 1]
}

proc person_accomplishment {person} {
return [lindex $person 2]
}

% set einstein [define_person "A. Einstein" "Patent Office Clerk" "Formulated Theory of Relativity."]
{A. Einstein} {Patent Office Clerk} {Formulated Theory of Relativity.}
% set einsteins_title [person_title $einstein]
Patent Office Clerk

Data abstraction will make building and maintaining a large system much easier. As noted above, however, the stateless nature of HTTP means that any information you want kept around from page to page must be kept by the RDBMS. SQL already forces you to refer to data by table name and column name rather than positionally.

---

based on Tcl for Web Nerds 

Pattern matching

Created by Anett Szabo, last modified by Anett Szabo 24 Jul 2007, at 11:56 AM

 

Pattern matching is important across a wide variety of Web programming tasks but most notably when looking for exceptions in user-entered data and when trying to parse information out of non-cooperating Web sites.

Tcl's pattern matching facilities test whether a given string matches a specified pattern. Patterns are described using a syntax known as regular expressions. For example, the pattern expression consisting of a single period matches any character. The pattern a..a matches any four-character string whose first and last characters are both a.

The regexp command takes a pattern, a string, and an optional match variable. It tests whether the string matches the pattern, returns 1 if there is a match and zero otherwise, and sets the match variable to the part of the string that matched the pattern:

% set something candelabra
candelabra

% regexp a..a $something match
1

% set match
abra
Patterns can also contain subpatterns (delimited by parentheses) and denote repetition. A star denotes zero or more occurrences of a pattern, so a(.*)a matches any string of at least two characters that begins and ends with the character a. Whatever has matched the subpattern between the a's will get put into the first subvariable:
% set something candelabra
candelabra

% regexp a(.*)a $something match
1

% set match
andelabra
Note that Tcl regexp by default behaves in a greedy fashion. There are three alternative substrings of "candelabra" that match the regexp a(.*)a: "andelabra", "andela", and "abra". Tcl chose the longest substring. This is very painful when trying to pull HTML pages apart:
% set simple_case "Normal folks might say <i>et cetera</i>"
Normal folks might say <i>et cetera</i>
% regexp {<i>(.+)</i>} $simple_case match italicized_phrase
1

% set italicized_phrase
et cetera

% set some_html "Pedants say <i>sui generis</i> and <i>ipso facto</i>"
Pedants say <i>sui generis</i> and <i>ipso facto</i>
% regexp {<i>(.+)</i>} $some_html match italicized_phrase
1

% set italicized_phrase
sui generis</i> and <i>ipso facto
What you want is a non-greedy regexp, a standard feature of Perl and an option in Tcl 8.1 and later versions (see http://www.scriptics.com/services/support/howto/regexp81.html).

Lisp systems in the 1970s included elegant ways of returning all possibilities when there were multiple matches for an expression. Java libraries, Perl, and Tcl demonstrate the progress of the field of computer science by ignoring these superior systems of decades past.

 

Matching Cookies From the Browser

A common problem in Web development is pulling information out of cookies that come from the client. The cookie spec at http://home.netscape.com/newsref/std/cookie_spec.html mandates that multiple cookies be separated by semicolons. So you look for "the cookie name that you've been using" followed by an equals sign and them slurp up anything that follows that isn't a semicolon. Here is how the ArsDigita Community System looks for the value of the last_visit cookie:

regexp {last_visit=([^;]+)} $cookie match last_visit
Note the square brackets inside the regexp. The Tcl interpreter isn't trying to call a procedure because the entire regexp has been grouped with braces rather than double quotes. Square brackets denote a range of acceptable characters:
  • [A-Z] would match any uppercase character
  • [ABC] would match any of first three characters in the alphabet (uppercase only)
  • [^ABC] would match any character other than the first three uppercase characters in the alphabet, i.e., the ^ reverses the sense of the brackets
The plus sign after the [^;] says "one or more characters that meets the preceding spec", i.e., "one or more characters that isn't a semicolon". It is distinguished from * in that there must be at least one character for a match.

If successful, the regexp command above will set the match variable with the complete matching string, starting from "last_visit=". Our code doesn't make any use of this variable but only looks at the subvar last_visit that would also have been set.

Pages that use this cookie expect an integer and this code failed in one case where a user edited his cookies file and corrupted it so that his browser was sending several thousands bytes of garbage after the "last_visit=". A better approach might have been to limit the match to digits:

regexp {last_visit=([0-9]+)} $cookie match last_visit

 

Matching Into Multiple Variables

More generally regexp allows multiple pattern variables. The pattern variables after the first are set to the substrings that matched the subpatterns. Here is an example of matching a credit card expiration date entered by a user:

% set date_typed_by_user "06/02"
06/02

% regexp {([0-9][0-9])/([0-9][0-9])} $date_typed_by_user match month year
1

% set month
06

% set year
02
%
Each pair of parentheses corresponds to a subpattern variable.

 

Full Syntax


The most general form of regexp includes optional flags as well as multiple match variables:

regexp [flags] pattern data matched_result var1 var2 ...
The various flags are
  • -nocase
    uppercase characters in the data are bashed down to lower for case-insensitive matching (make sure that your pattern is all lowercase!)
  • -indices
    the returned values of the regexp contain the indices delimiting the matched substring, rather than the strings themselves.
  • If your pattern begins with a -, put a -- flag at the end of your flags
Regular expression syntax is:
  • .
    matches any character.
  • *
    matches zero or more instances of the previous pattern item.
  • +
    matches one or more instances of the previous pattern item.
  • ?
    matches zero or one instances of the previous pattern item.
  • |
    disjunction, e.g., (a|b) matches an a or a b
  • ( )
    groups a sub-pattern.
  • [ ]
    delimits a set of characters. ASCII Ranges are specified using hyphens, e.g., [A-z] matches any character from uppercase A through lowercase z (i.e., any alphabetic character). If the first character in the set is ^, this complements the set, e.g., [^A-z] matches any non-alphabetic character.
  • ^
    Matches only when the pattern appears at the beginning of the string. The ^ must appear at the beginning of the pattern expression.
  • $
    Matches only when the pattern appears at the end of the string. The $ must appear last in the pattern expression.

More: http://www.tcl.tk/man/tcl8.4/TclCmd/regexp.htm

 

Matching with substitution

It's common in Web programming to create strings by substitution. Tcl's regsub command performs substitution based on a pattern:

regsub [flags] pattern data replacements var
matches the pattern against the data. If the match succeeds, the variable named var is set to data, with various parts modified, as specified by replacements. If the match fails, var is simply set to data. The value returned by regsub is the number of replacements performed.

The flag -all specifies that every occurrence of the pattern should be replaced. Otherwise only the first occurrence is replaced. Other flags include -nocase and -- as with regexp

Here's an example from the banner ideas module of the ArsDigita Community System (see http://photo.net/doc/bannerideas.html). The goal is that each banner idea contain a linked thumbnail image. To facilitate cutting and pasting of the image html, we don't require that the publisher include uniform subtags within the IMG. However, we use regexp to clean up:

# turn "<img align=right hspace=5" into "<img align=left border=0 hspace=8"
regsub -nocase {align=[^ ]+} $picture_html "" without_align
regsub -nocase {hspace=[^ ]+} $without_align "" without_hspace
regsub -nocase {<img} $without_hspace {<img align=left border=0 hspace=8} final_photo_html

In the example above, <replacements> specified the literal characters ''. Other replacement directives include:

  • & inserts the string that matched the pattern
  • The backslashed numbers \1 through \9 inserts the strings that matched the corresponding sub-patterns in the pattern.
Here's another web example, which parses HTML, and replaces the comments (delineated in HTML by <!-- and -->) by the comment text, enclosed in parentheses.
% proc extract_comment_text {html} {
regsub -all {<!--([^-]*)-->} $html {(\1)} with_exposed_comments
return $with_exposed_comments
}

% extract_comment_text {<!--insert the price below-->
We give the same low price to everyone: $219.99
<!--make sure to query out discount if this is one of our big customers-->}
(insert the price below)
We give the same low price to everyone: $219.99
(make sure to query out discount if this is one of our big customers)

More: http://www.tcl.tk/man/tcl8.4/TclCmd/regsub.htm


String match

Tcl provides an alternative matching mechanism that is simpler for users to understand than regular expressions. The Tcl command string match uses "GLOB-style" matching. Here is the syntax:

string match pattern data
It returns 1 if there is a match and 0 otherwise. The only pattern elements permitted here are ?, which matches any single character; *, which matches any sequence; and [], which delimits a set of characters or a range. This differs from regexp in that the pattern must match the entire string supplied:
% regexp "foo" "foobar"
1

% string match "foo" "foobar"
0

% # here's what we need to do to make the string match
% # work like the regexp
% string match "*foo*" foobar
1
Here's an example of the character range system in use:
string match {*[0-9]*} $text

returns 1 if text contains at least one digit and 0 otherwise.

More: http://www.tcl.tk/man/tcl8.4/TclCmd/string.htm

 

 


Exercises

1. 

  • Write a procedure which takes a string and makes sure that the result contains an "@" sign
  • Extend the procedure to make sure that only letters, numbers are allowed before the "@" sign
  • Extend the procedure to check that after the @ sign comes a valid domain (hint, look at 2.) A valid domain contains of at least one "." and only letters after the last ".". so malte.cognovis.de is a valid domain, cognovis.d1e is not.
  • Extend the procedure to return "Welcome foo, member of bar.com" if the string is "foo@bar.com"
  • Extend the procedure to return "Welcome OpenACS member foo" if the string is like "foo@openacs.org" meaning, the e-mail ends with openacs.org
  • Check against the valid domain again. This time make use of the ad_locales table installed in your local copy of OpenACS. To make this work you will have to use the OpenACS Shell.
    • Get a list of all countries from the table ad_locales. Choose the language column for this. The command to extract this is "db_list".
    • If your list contains the language "ca" more than once, make sure to limit it to one "ca" only. Make sure this works for others as well.
    • As ".com" ".org" and ".net" are also valid domain ending append them to the list.
    • Make sure that the domain ends on any language defined in your list you created. So automotive.ca works but automotive.eu does not (and yes, I know that .eu is now a valid domain :-)).


Answer


2.

  1. Search at amazon.com for your favorite book. Copy the URL until you see the "/ref..." part, e.g. http://www.amazon.com/4-Hour-Workweek-Escape-Live-Anywhere/dp/0307353133
  2. In the OpenACS shell use "ad_httpget" to retrieve the URL you copied. Look at the api doc for the syntax.
  3. Use regexp to find the price of the book in the html source returned to you by ad_httpget
  4. Return the price of the book.

 

Answer

---

based on Tcl for Web Nerds