summaryrefslogtreecommitdiff
path: root/ipl/progs/conman.icn
diff options
context:
space:
mode:
Diffstat (limited to 'ipl/progs/conman.icn')
-rw-r--r--ipl/progs/conman.icn427
1 files changed, 427 insertions, 0 deletions
diff --git a/ipl/progs/conman.icn b/ipl/progs/conman.icn
new file mode 100644
index 0000000..01dbb83
--- /dev/null
+++ b/ipl/progs/conman.icn
@@ -0,0 +1,427 @@
+############################################################################
+#
+# File: conman.icn
+#
+# Subject: Program to convert units
+#
+# Author: William E. Drissel
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Conman is a toy I used to teach myself elementary Icon. I
+# once vaguely heard of a program which could respond to queries
+# like "? Volume of the earth in tbsp".
+#
+# The keywords of the language (which are not reserved) are:
+#
+# load
+# save
+# print
+# ? (same as print)
+# list
+# is and are which have the same effect
+#
+# "Load" followed by an optional filename loads definitions of
+# units from a file. If filename is not supplied, it defaults to
+# "conman.sav"
+#
+# "Save" makes a file for "load". Filename defaults to
+# "conman.sav". "Save" appends to an existing file so a user
+# needs to periodically edit his save file to prune it back.
+#
+# "Print" and "?" are used in phrases like:
+#
+# ? 5 minutes in seconds
+#
+# Conman replies:
+#
+# 5 minutes in seconds equals 300
+#
+# List puts up on the screen all the defined units and the
+# corresponding values. Format is same as load/store format.
+#
+# "Is" and "are" are used like this:
+#
+# 100 cm are 1 meter
+#
+# The discovery of is or are causes the preceding token (in
+# this case "cm") to be defined. The load/store format is:
+#
+# unitname "is" value
+#
+# Examples:
+#
+# 8 furlongs is 1 mile
+# furlong is 1 / 8 mile
+#
+# These last two are equivalent. Note spaces before and after
+# "/". Continuing examples:
+#
+# 1 fortnight is 14 days
+# furlong/fortnight is furlong / fortnight
+# inches/hour is inch / hour
+#
+# After this a user might type:
+#
+# ? 1 furlong/fortnight in inches/hour
+#
+# Conman will reply:
+#
+# 1 furlong/fortnight in inches/hour equals 23.57
+#
+# Note: the following feature of Conman: his operators have no
+# precedence so the line above gets the right answer but
+#
+# 1 furlong/fortnight in inches / hour
+#
+# gets the wrong answer. (One definition of a feature is a flaw we're
+# not going to fix).
+#
+############################################################################
+#
+# Program Notes:
+#
+# The procedure, process, parses the user's string to see if it
+# begins with a keyword. If so, it acts accordingly. If not,
+# the user string is fed to isare.
+#
+# Isare attempts to find "is" or "are" in the users string.
+# Failing to, isare feeds the string to conman which can
+# interpret anything. If "is" or "are" is found, the tokens
+# (delimited by blanks) before the "is" or "are" are stacked in
+# foregoing; those after are stacked in subsequent. Then the
+# name to be defined is popped off the foregoing and used as
+# the "index" into a table named values. The corresponding
+# number is computed as eval(subsequent) / eval(foregoing).
+#
+# The procedure, stack, is based on Griswold and Griswold, "The
+# Icon Programming Language", p122.
+#
+# The procedure, eval, unstacks the tokens from a stack one by
+# one until all have been considered. First, the tokens which
+# signify division by the next token are considered and used to
+# set a switch named action. Then depending on action, the
+# token is used to multiply the accumulator or divide it. If
+# eval can make the token into a number, the number is used,
+# failing that the token is looked up in the table named values
+# and the corresponding number is used. Failing both of those,
+# conman gripes to the user and does nothing (in effect
+# multiplying or dividing by 1). Finally, eval returns the
+# number accumulated by the operations with the tokens.
+#
+# Load defaults the filename to conman.sav if the user didn't
+# supply one. Each line read is fed to isare. We will see
+# that save prepares the lines so isare can define the units.
+#
+# Save uses Icon's sort to go thru the table "values". The
+# unit name is the left of a pair and the number stored is the
+# right of the pair. The word " is " is stuck between them so
+# isare will work.
+#
+# Finally, we consider the procedure conman. During initial
+# design, this was perceived to be the largest part of the
+# effort of conman. It is a real tribute to the power of Icon
+# that only one non-trivial line of code is required. The
+# user's string is reproduced then the word "equals" followed
+# the result produced by eval after the user's string is
+# stacked.
+#
+############################################################################
+#
+# Requires: conman.sav
+#
+############################################################################
+#
+# Links: io
+#
+############################################################################
+
+link io
+
+global values, blank, nonblank
+
+procedure main (args)
+ local line
+
+ if map(args[1]) == "-t" then &trace := -1
+
+ init()
+
+ while line := prompt() do {
+ process(line || " ") # pad with a blank to make life easy
+ }
+ windup()
+end
+############################################################################
+#
+# windup
+#
+procedure windup()
+ write(&errout,"windup")
+end
+############################################################################
+#
+# process
+#
+procedure process(str)
+
+ case parse(str) of {
+ "load" : load(str)
+ "save" : save(str)
+ "print" : conman(butfirst(str)) # strip first token
+ "list" : zlist()
+ default : isare(str) # didn't start with a kw, try is or are
+ }
+end
+############################################################################
+#
+# parse
+#
+procedure parse(str)
+ local token
+
+ token := first(str)
+ case token of {
+ "?" : return "print" # only special case at present
+ default : return token
+ }
+end
+############################################################################
+#
+# conman
+#
+# compute and write result - During initial design, this was perceived to
+# require 50 lines of complicated lookup etc.!
+#
+procedure conman(strn)
+
+ write (strn , " equals ", eval(stack(strn, 1, *strn)))
+end
+############################################################################
+#
+# isare - routine to define values - tries to evaluate if not a definition
+#
+# locate is,are - delete
+# backup one word - save, delete
+# compute foregoing
+# compute subsequent
+# store word, subsequent/foregoing in values
+#
+procedure isare(str)
+ local after, before, foregoing, subsequent
+
+# locate blank-delimited is or are - early (?clumsy) Icon code replaced at
+# the suggestion of one of REG's students
+
+ if (str ? (before := tab(find(" is ")) & move(4) &
+ after := \tab(0))) then { } # is
+
+ else if (str ? (before := tab(find(" are ")) & move(5) &
+ after := \tab(0))) then { } # are
+
+ else { # found nothing - try to eval anyhow
+ conman(str)
+ return
+ }
+#
+# here if is or are
+#
+ foregoing := stack(before) # so we can look back one token
+ subsequent := stack(after) # might as well stack this too
+
+ name := singular(pop(foregoing)) # define token before is or are
+#
+# next line so we can support "100 cms are 1 meter"
+#
+ values[name] := eval(subsequent) / eval(foregoing)
+ return
+end
+############################################################################
+#
+# stack - stack tokens - based on IPL section 12.1 p122
+#
+# stack the "words" in str - needs cset nonblank
+#
+procedure stack(str)
+ local i, j, words
+
+ words := [] ; i := 1
+
+ while j := upto(nonblank, str, i) do {
+ i := many(nonblank, str, j)
+ push(words, str[i:j])
+ }
+ return words
+end
+############################################################################
+#
+# eval - evaluate a stack
+#
+# while more remain
+# unstack a token
+# if "in" or "over" or "/", set to divide next time
+# else if number multiply/divide it
+# else if in values, multiply/divide value
+# else gripe and leave accum alone
+#
+procedure eval(stk)
+ local accum, action, token
+
+ accum := 1.0 ; action := "multiply"
+
+ while token := singular(pull(stk)) do {
+
+ if token == ("in" | "over" | "/" )then action := "divide"
+ else if action == "multiply" then {
+
+# write("multiplying by ", token, " ", (real(token) |
+ # real(values[token]) |
+ # "unknown"))
+
+ if not (accum *:= \(real(token) | real(values[token]))) then
+ write (&errout,
+ "Can't evaluate ", token, " - using 1.0 instead")
+ }
+ else if action == "divide" then {
+ action := "multiply"
+ if not (accum /:= \(real(token) | real(values[token]))) then
+ write (&errout,
+ "Can't evaluate ", token, " - using 1.0 instead")
+ }
+ }#........................................ # end of while more tokens
+ return accum
+end
+############################################################################
+#
+# init
+#
+procedure init()
+ write(&errout, "Conman version 1.1, 7/24/87")
+ values := table(&null)
+ nonblank := &ascii -- ' '
+ blank := ' '
+ values["times"] := 1.0
+ values["by"] := 1.0
+ values["of"] := 1.0
+ values["at"] := 1.0
+ values["print"] := 1.0
+ values["?"] := 1.0
+ values["meter"] := 1.0
+ values["kilogram"] := 1.0
+ values["second"] := 1.0
+
+end
+############################################################################
+#
+# prompt
+#
+procedure prompt()
+ return read()
+end
+############################################################################
+#
+# load - loads table from a file - assumes save format compatible
+# with isare
+#
+procedure load(str)
+ local intext, line, filnam
+
+ filnam := (\second(str) | "conman.sav")
+ write (&errout, "Load from ", filnam, ". May take a minute or so.")
+ intext := dopen(filnam,"r") | { write(&errout, "can't open ", filnam)
+ fail}
+ while line := read(intext) do {
+ isare(line || " ") # pad with a blank to make life easy
+ }
+ close(intext)
+ return
+end
+############################################################################
+#
+# save - saves table to file in format compatible with isare
+#
+procedure save(str)
+ local i, outtext, pair, wlist, filnam
+
+ filnam := (\second(str) | "conman.sav")
+ write (&errout, "Save into ", filnam)
+ outtext := open(filnam,"a") | { write(&errout, "can't save to ", filnam)
+ fail}
+ wlist := sort(values)
+ i := 0
+ every pair := !wlist do {
+ write(outtext, pair[1], " is ", pair[2])
+ }
+ close(outtext)
+end
+############################################################################
+#
+# zlist - lists the table
+#
+procedure zlist()
+ local i, pair, wlist
+
+ i := 0
+ wlist := sort(values)
+ every pair := !wlist do {
+ write(&errout, pair[1], " is ", pair[2])
+ }
+end
+############################################################################
+#
+# first - returns first token in a string - needs cset nonblank
+#
+procedure first(s)
+ local stuff
+
+ s? (tab(upto(nonblank)) , (stuff := tab(many(nonblank))))
+ return \stuff
+end
+############################################################################
+#
+# second - returns second token in a string - needs cset nonblank
+#
+procedure second(s)
+ local stuff
+
+ s? (tab(upto(nonblank)) , (tab(many(nonblank)) & tab(upto(nonblank)) &
+ (stuff := tab(many(nonblank)))))
+ return \stuff
+end
+############################################################################
+#
+# butfirst - returns all butfirst token in a string - needs cset nonblank
+#
+procedure butfirst(s)
+ local stuff
+
+ s? (tab(upto(nonblank)) , tab(many(nonblank)) & tab(upto(nonblank)) &
+ (stuff := tab(0)))
+ return \stuff
+end
+############################################################################
+#
+# singular - returns singular of a unit of measure - add special cases in
+# an obvious way. Note: singulars ending in "e" should be handled
+# here also "per second" units which end in "s".
+#
+procedure singular(str)
+ local s
+
+ s := str
+ if s == "fps" then return "fps"
+ if s == "feet" then return "foot"
+ if s == "minutes" then return "minute"
+ if s == "miles" then return "mile"
+#
+## otherwise strip "es" or "s". Slick code by Icon grad student
+#
+ return s? (1(tab(-2), ="es") | 1(tab(-1), ="s" ) | tab(0))
+end
+############################################################################