############################################################################ # # File: echo.icn # # Subject: Procedure to perform "variable interpolation" a la Perl # # Authors: Charles L Hethcoat III and Carl Sturtivant # # Date: February 9, 2010 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # echo() substitutes global variables for occurrences of $name in &subject, # and writes the result to standard output. # ############################################################################ # # Background: # # String "interpolation", as used in Perl, Tcl, Bash, and so on, # involves a special notation used within a string that causes the # value of a variable to be inserted into the string at runtime. A # common notation for this is a dollar sign, e. g. "The price is # $price pfennig." If a variable named "price" has the value 10, then # on output the string becomes "The price is 10 pfennig." # # Interpolation is lacking in Icon, so we must use the fussier syntax # of an Icon write() procedure: write("The price is ", price, # "pfennig."). Here is a slightly more complex example, assuming # variables `price' = 10, `article' == "thimble", and `currency' == # "pfennig": # # write("The price of a ", article, " is ", price, " ", currency, ".") # # This can be annoying and error-prone if we must use many such # strings in a program. # # The echo() procedure provides a very nice solution for Icon # programmers. Compare the preceding write() call to this: # # "The price of a $article is $price $currency" ? echo() # # Is this not much simpler? Both examples will print out the string # # "The price of a thimble is 10 pfennig." # # but interpolation with echo() greatly reduces the low-level # syntactic requirements (and reduces the number of characters to type # from 68 to 54). It is much easier to write, read, and check. If # many such lines of code are needed, the difference adds up. # Consider, for example, how this would pay off if your program needs # to generate hundreds of lines of HTML or PostScript. # ############################################################################ # # Usage: # # A string to # be printed with interpolated values should be set up in a scanning # environment, using echo() as the scanning procedure, as in # "foo$variable" ? echo(). Here is an actual example for testing: # # link echo # global month, day, year # # procedure main() # month := "February" # day := 30 # year := 2010 # "Free beer on $month $day, $year." ? echo() # end # # Assuming echo.icn has been compiled with the -c option beforehand, # compiling, linking, and running this program produces the string # "Free beer on February 30, 2010." on standard output. # ############################################################################ # # Notes: # # Since there is no way for any Icon procedure to discover the values of # any another procedure's local variables, all variables to be used via # the echo() procedure must be global. This restriction ought not to be # too serious for smaller programs, or even longer ones if they are of # simple construction. But it may be a limitation for sophisticated # Icon programming projects. You will have to be the judge. # # If x is a global variable with value 10, # # "x" ? echo() prints "x" # "$x" ? echo() prints "10" # "$$x" ? echo() prints "$x" # "$$$x" ? echo() prints "$10" # "$$$$x" ? echo() prints "$$x" # "$$$$$x" ? echo() prints "$$10" # # and so on. The rule is: take dollar signs off in pairs from the # left. Each pair prints ONE literal dollar sign on the output. # # If there were an odd number of dollar signs to begin with, then one # will be left over, and this will print the value of the variable (10). # # If there were an even number to begin with, then none are left, and a # literal "x" is printed. # # There is an extended notation that helps disambiguate some usage # scenarios. Here are some examples: # # "${x}" is the same as $x. # "${x}:" is the same as $x:. # "${x}${y}" is the same as $x$y. # # However, "${x}avier" is NOT the same as $xavier! Can you see why? # # You may use any variable names you like. There are no restrictions. # echo() uses no global variable names of its own, but receives the # string it interpolates in a string scanning environment. # ############################################################################ # # Using echo() on a larger scale , with input from a generator: # # global time, date, save, wombats # # link echo # # procedure main() # time := &clock # date := &date # save := ?100000 # wombats := 22 # "It is now $time on $date and you have savings of $$$save." | # "The number of wombats is $wombats." | # "It is now ${time} on ${date} and you have ${wombats} wombats." | # "There is no global variable named \"$foo\"." | # "This does not work: It is now ${&clock}." | # "" | # "The previous input line printed an empty output line." ? echo() # end # # Because echo() always fails (in the Icon sense), evaluation of # # a | b | c | d ? echo() # # will group as # # (a | b | c | d) ? echo() # # because of operator precedence, and the left-hand expression produces # _a_ first, which is assigned to &subject. Then echo() is evaluated -- # and fails. This makes the whole expression fail, so Icon backtracks # to the first expression, resumes its evaluation to produce its second # value b, which is assigned to &subject and then echo() is called, # which fails, and so forth, until all possibilities are exhausted. # ############################################################################ # # Taking input from a template file: # # You can create a template file (with $-strings in it) and use an Icon # program to read it and write it out to standard output. Your main # Icon program will supply the needed variable values for the $-strings # in the template. # # As an example, suppose your program will generate a hundred business # cards for you as a PostScript file. You have a template file named # template.ps with $-strings such as $firstname, $lastname, $address, # $companyname, and so on --- all embedded in it at the proper places. # Your main program will read this template and substitute the actual # name and address information. # # This is one way your program can read template.ps and pass it to # echo(): # # ... # firstname := "Joe" # lastname := "Smith" # # ... etc. ... # reads("template.ps",1000000) ? echo() # ... # # When this is run, your customized business cards appear on standard # output. # ############################################################################ # # This trick relies upon concatenation having a higher precedence # than alternation: # # "................" || # "................" || # "................" | # "................" || # "................" | # "................" || # "................" ? echo() # # This prints out three messages, one specified on three lines, one on # two, and one on two. The alternations fix the newlines provided at the # end of each message by echo(). # # &subject is the empty string if it's unassigned. So echo() called # without ? will under those circumstances print a blank line. # ############################################################################ procedure echo() #: interpolate variables and print $define idchars 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_' while writes(tab(find("$")) ) do { move(1) writes( ="$" | variable(tab(many(idchars)) | 2( ="{", tab(find("}")), ="}" ) ) ) | tab(many(idchars)) | ( ="{" & tab(find("}")) & ="}" ) } write(tab(0)) $undef idchars end