summaryrefslogtreecommitdiff
path: root/ipl/progs/icalc.icn
diff options
context:
space:
mode:
Diffstat (limited to 'ipl/progs/icalc.icn')
-rw-r--r--ipl/progs/icalc.icn477
1 files changed, 477 insertions, 0 deletions
diff --git a/ipl/progs/icalc.icn b/ipl/progs/icalc.icn
new file mode 100644
index 0000000..fa7cacb
--- /dev/null
+++ b/ipl/progs/icalc.icn
@@ -0,0 +1,477 @@
+############################################################################
+#
+# File: icalc.icn
+#
+# Subject: Program to simulate infix desk calculator
+#
+# Author: Stephen B. Wampler
+#
+# Date: January 3, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This is a simple infix calculator with control structures and
+# compound statements. It illustrates a technique that can be
+# easily used in Icon to greatly reduce the performance cost
+# associated with recursive-descent parsing with backtracking.
+# There are numerous improvements and enhancements that can be
+# made.
+#
+# Features include:
+#
+# - integer and real value arithmetic
+# - variables
+# - function calls to Icon functions
+# - strings allowed as function arguments
+# - unary operators:
+# + (absolute value), - (negation)
+# - assignment:
+# :=
+# - binary operators:
+# +,-,*,/,%,^,
+# - relational operators:
+# =, !=, <, <=, >, >=
+# (all return 1 for true and 0 for false)
+# - compound statements in curly braces with semicolon separators
+# - if-then and if-then-else
+# - while-do
+# - limited form of multiline input
+#
+# The grammar at the start of the 'parser' proper provides more
+# details.
+#
+# Normally, the input is processed one line at a time, in calculator
+# fashion. However, compound statements can be continued across
+# line boundaries.
+#
+# Examples:
+#
+# Here is a simple input:
+#
+# {
+# a := 10;
+# while a >= 0 do {
+# write(a);
+# a := a - 1
+# };
+# write("Blastoff")
+# }
+#
+# (execution is delayed until entire compound statement is entered)
+#
+# Another one:
+#
+# write(pi := 3.14159)
+# write(sin(pi/2))
+#
+# (execution done as each line is entered)
+#
+############################################################################
+
+invocable all
+
+ # the types for parse tree nodes:
+
+record trinary(op,first,second,third)
+record binop(op,left,right)
+record unary(op,opnd)
+record id(name)
+record const(value)
+
+ # a global table for holding variable values:
+
+global sym_tab
+
+
+procedure main()
+ local line, sline
+
+ sym_tab := table()
+
+ every line := getbs() do { # a 'line' may be more
+ # than one input line
+ if *(sline := trim(line)) > 0 then { # skip empty lines
+ process(parse(sline))
+ }
+ }
+end
+
+### Input routines...
+
+## getbs - read enough input to ensure that it is
+# balanced with respect to curly braces, allowing
+# compound statements to extend across lines...
+# This can be made considerably more sophisticated,
+# but handles the more common cases.
+#
+procedure getbs()
+static tmp
+ initial tmp := (("" ~== |read()) || " ") | fail
+
+ repeat {
+ while not checkbal(tmp,'{','}') do {
+ if more('}','{',tmp) then break
+ tmp ||:= (("" ~== |read()) || " ") | break
+ }
+ suspend tmp
+ tmp := (("" ~== |read()) || " ") | fail
+ }
+end
+
+## checkbal(s) - quick check to see if s is
+# balanced w.r.t. braces or parens
+#
+procedure checkbal(s,l,r)
+ return (s ? 1(tab(bal(&cset,l,r)),pos(-1)))
+end
+
+## more(c1,c2,s) - succeeds if any prefix of
+# s has more characters in c1 than
+# characters in c2, fails otherwise
+#
+procedure more(c1,c2,s)
+local cnt
+ cnt := 0
+ s ? while (cnt <= 0) & not pos(0) do {
+ (any(c1) & cnt +:= 1) |
+ (any(c2) & cnt -:= 1)
+ move(1)
+ }
+ return cnt >= 0
+end
+
+
+### Parser routines... Implementing an efficient recursive-descent
+### parser with backtracking.
+
+# Parser -- Based on following CFG, but modified to
+# avoid useless backtracking... (see comments
+# preceding procedures 'save' and 'restore')
+
+# Statement ::= Expr | If | While | Compound
+#
+# Compound ::= {Statement_list}
+#
+# Statement_list ::= Statement | Statement ; Statement_list
+#
+# If ::= if Expr then Statement Else
+#
+# Else ::= else Statement | ""
+#
+# While ::= while Expr do Statement
+#
+# Expr ::= R | Id := Expr
+#
+# R ::= X [=,!=,<,>,>=,<=] X | X
+#
+# X ::= T [+-] X | T
+#
+# T ::= F [*/%] T | F
+#
+# F ::= E ^ F | E
+#
+# E ::= L | [+,-] L
+#
+# L ::= Func | Id | Constant | ( Expr ) | String
+#
+# Func ::= Id ( Arglist )
+#
+# Arglist ::= "" | Expr | Expr , arglist
+
+#
+# Note, this version correctly handles left-associativity
+# despite the fact that the above grammar doesn't
+# handle it correctly. (Cannot embed left-associativity
+# into a recursive descent parser!)
+#
+
+procedure parse(s) # must match entire line
+ local tree
+
+ if s ? ((tree := Statement()) & (ws(),pos(0))) then {
+ return tree
+ }
+ write("Syntax error.")
+end
+
+procedure Statement()
+ suspend If() | While() | Compound() | Expr()
+end
+
+procedure Compound()
+ suspend unary("{",2(litmat("{"),Statement_list(),litmat("}")))
+end
+
+procedure Statement_list()
+ local t
+ t := scan()
+ suspend binary(save(Statement,t), litmat(";"), Statement_list()) | restore(t)
+end
+
+procedure If()
+ suspend trinary(keymat("if"),Expr(),2(keymat("then"),Statement()),
+ 2(keymat("else"),Statement())|&null)
+end
+
+procedure While()
+ suspend binary(2(keymat("while"),Expr()),"while",2(keymat("do"),Statement()))
+end
+
+procedure Expr()
+ suspend binary(Id(),litmat(":="),Expr()) | R()
+end
+
+procedure R()
+ local t
+ t := scan()
+ suspend binary(save(X,t),litmat(!["=","!=","<=",">=","<",">"]),X()) |
+ restore(t)
+end
+
+procedure X()
+ local t
+ t := scan()
+ suspend binary(save(T,t),litmat(!"+-"),X()) | restore(t)
+end
+
+procedure T()
+ local t
+ t := scan()
+ suspend binary(save(F,t),litmat(!"*/%"),T()) | restore(t)
+end
+
+procedure F()
+ local t
+ t := scan()
+ suspend binary(save(E,t),litmat("^"),F()) | restore(t)
+end
+
+procedure E()
+ suspend unary(litmat(!"+-"),L()) | L()
+end
+
+procedure L()
+ # keep track of fact expression was parenthesized,
+ # so we don't accidently override the parens when
+ # handling left-associativity
+ suspend Func() | Id() | Const() |
+ unary("(",2(litmat("("), Expr(), litmat(")"))) |
+ String()
+end
+
+procedure Func()
+ suspend binary(Id(),litmat("("),1(Arglist(),litmat(")")))
+end
+
+procedure Arglist()
+ local a
+ a := []
+ suspend (a <- ([Expr()] | [Expr()] ||| 2(litmat(","),Arglist()))) | a
+end
+
+procedure Id()
+ static first, rest
+
+ initial {
+ first := &letters ++ "_"
+ rest := first ++ &digits
+ }
+
+ suspend 2(ws(),id(tab(any(first))||tab(many(rest)) | tab(any(first))))
+end
+
+procedure Const()
+ local t
+
+ t := scan()
+
+ suspend 2(ws(),const((save(digitseq,t)||="."||digitseq()) | restore(t)))
+
+end
+
+procedure digitseq()
+ suspend tab(many(&digits))
+end
+
+procedure String()
+ # can be MUCH smarter, see calc.icn (by Ralph Griswold) for
+ # example of how to do so...
+ suspend 2(litmat("\""),tab(upto('"')),move(1))
+end
+
+procedure litmat(s)
+ suspend 2(ws(),=s)
+end
+
+procedure keymat(key)
+ suspend 2(ws(),key==tab(many(&letters)))
+end
+
+procedure ws()
+ static wsp
+ initial wsp := ' \t'
+ suspend ""|tab(many(wsp))
+end
+
+procedure binary(l,o,r)
+ local lm
+
+ # if operator is left-associative, then alter tree to
+ # reflect that fact, since it isn't parsed that way
+ # (this isn't the most efficient way to do this, but
+ # it is a simple way...)
+
+ if (type(r) == "binop") & samelop(o,r.op) then {
+
+ # ok, have to add node to far left end of chain for r
+
+ # ...do so by first finding leftmost node of chain for r
+ lm := r
+ while (type(lm.left) == "binop") & samelop(o,lm.left.op) do {
+ lm := lm.left
+ }
+
+ # ...add new node as new left-most node in chain
+ lm.left := binop(o,l,lm.left)
+
+ # ...and return original right child as root of tower
+ return r
+ }
+
+ # nothing to do, just return 'normal' tree
+ return binop(o,l,r)
+end
+
+procedure samelop(o1,o2)
+ # both operators are left associative at the same precedence level
+ return (any('+-',o1) & any('+-',o2)) |
+ (any('*/%',o1) & any('*/%',o2))
+end
+
+## Speed up tools for recursive descent parsing...
+#
+# The following two routines make it possible to 'defer'
+# the backtracking into a parsing procedure (at least
+# so far as restoring &pos). This makes it easy to
+# reuse the result of a parsing procedure if needed.
+#
+# For example, the grammar rules:
+#
+# X := T | T + F
+#
+# can be processed as:
+#
+# X := save(T,t) | restore(t) + F
+#
+# The net effect is a very substantial speedup in processing
+# such rules.
+#
+
+record scan(val,pos) # used to avoid repeating a successful scan
+ # (see the use of save() and restore())
+
+# save the current scanning position and result of parsing procedure P
+# and then prevent backtracking into P
+#
+procedure save(P,t)
+ return (t.pos <- &pos, t.val := P())
+end
+
+#
+# if t has in it the saved result of a parsing procedure, then
+# suspend it. if backtracked into reset position back to
+# start of original call to that parsing procedure.
+#
+procedure restore(t)
+ suspend \t.val
+ &pos := \t.pos
+end
+
+### execution of infix expression...
+
+## process -- given an expression tree - walk it to produce a result
+#
+
+ # The only tricky part is in the assignment operator.
+ # Here, since we know the left-hand side is an identifier
+ # We avoid processing it, since process(id(name)) will
+ # return the value of id(name), not it's address.
+
+ # This version just relies upon the icon interpreter to
+ # catch runtime errors. It would be better to catch them
+ # here.
+
+procedure process(t)
+ local a, val
+
+ return case type(t) of {
+ "trinary" : case t.op of { # has to be an 'if'!
+ "if": if process(t.first) ~= 0 then
+ process(t.second)
+ else
+ process(t.third)
+ }
+
+ "binop" : case t.op of {
+ # the relation operators
+ "=" : if process(t.left) = process(t.right) then 1 else 0
+ "!=": if process(t.left) ~= process(t.right) then 1 else 0
+ "<=": if process(t.left) <= process(t.right) then 1 else 0
+ ">=": if process(t.left) >= process(t.right) then 1 else 0
+ "<" : if process(t.left) < process(t.right) then 1 else 0
+ ">" : if process(t.left) > process(t.right) then 1 else 0
+
+ # the arithmetic operators
+ "+" : process(t.left) + process(t.right)
+ "-" : process(t.left) - process(t.right)
+ "*" : process(t.left) * process(t.right)
+ "/" : process(t.left) / process(t.right)
+ "%" : process(t.left) % process(t.right)
+ "^" : process(t.left) ^ process(t.right)
+
+ # assignment
+ ":=": sym_tab[t.left.name] := process(t.right)
+
+ # statements in a statement list
+ ";" : {
+ process(t.left)
+ process(t.right)
+ }
+
+ # while loop
+ "while" : while process(t.left) ~= 0 do
+ process(t.right)
+
+ # function calls
+ "(" : t.left.name ! process(t.right)
+ }
+
+ "unary" : case t.op of {
+ "-" : -process(t.opnd)
+ "+" : if val := process(t.opnd) then
+ return if val < 0 then -val else val
+ # parenthesized expression
+ "(" : process(t.opnd)
+ # compound statement
+ "{" : process(t.opnd)
+ }
+
+ "id" : \sym_tab[t.name] | (write(t.name," is undefined!"),&fail)
+
+ "const" : numeric(t.value)
+
+ "list" : { # argument list for function call
+ # evaluate each argument into a new list
+ a := []
+ every put(a,process(!t))
+ a
+ }
+
+ default: t # anything else (right now, just strings)
+ }
+
+end