############################################################################
#
#	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
