summaryrefslogtreecommitdiff
path: root/ipl/packs/ibpag2/iiglrpar.lib
diff options
context:
space:
mode:
Diffstat (limited to 'ipl/packs/ibpag2/iiglrpar.lib')
-rw-r--r--ipl/packs/ibpag2/iiglrpar.lib946
1 files changed, 946 insertions, 0 deletions
diff --git a/ipl/packs/ibpag2/iiglrpar.lib b/ipl/packs/ibpag2/iiglrpar.lib
new file mode 100644
index 0000000..059b0bf
--- /dev/null
+++ b/ipl/packs/ibpag2/iiglrpar.lib
@@ -0,0 +1,946 @@
+############################################################################
+#
+# Name: iiglrpar.lib
+#
+# Title: Quasi-GLR parser code
+#
+# Author: Richard L. Goerwitz
+#
+# Version: 1.20
+#
+############################################################################
+#
+# This file contains quasi-GLR parser code for use by Ibpag2's
+# output. See below on what I mean by "quasi-GLR." Entry point is
+# iiparse(infile, fail_on_error). Infile is the stream from which
+# input is to be taken. Infile is passed as argument 1 to the
+# user-supplied lexical analyzer, iilex_module() (where _module is
+# the string supplied with the -m option to Ibpag2). If
+# fail_on_error is nonnull, the parser, iiparse, will fail on errors,
+# rather than abort. Iiparse() returns the top element on its value
+# stack on a successful parse (which can be handy).
+#
+# Iilex_module() must suspend integers for tokens and may also set
+# iilval_module to the actual string values. Tokens -2, -1, and 0
+# are reserved. -2 is epsilon, and -1 is error. 0 is EOF, and is
+# automatically appended to the token stream when iilex_module, the
+# tokenizer, fails. These values should not normally be returned by
+# the analyzer. In general, it is a good idea to $include
+# iilex_module from your Ibpag2 source files, so that it can use the
+# symbolic %token names declared in the original Ibpag2 source file.
+# As implied above ("suspend"), iilex_module must be a generator,
+# failing on EOF.
+#
+# If desired, you may include your own error-handling routine. It
+# must be called iiparse_module (where _module is once again the
+# module name supplied to ibpag2 via the -m option). The global
+# variable line_number_module is automatically defined below, so a
+# typical arrangement would be for the lexical analyzer to initialize
+# line_number_module to 0, and increment by 1 for each line read.
+# The error handler, iierror_module() can then display this variable.
+# Note that the error handler should accept a single string argument
+# (set by iiparse to describe the token on the input stream when the
+# error was encountered).
+#
+# I label this parser "GLR" because it does support multiple parallel
+# parsers (like GLR parsers are supposed to). I use the qualifier
+# "quasi," though, because it does not use a graph-structured stack.
+# Instead it copies both value and state stacks (in fact, the whole
+# parser environment) when creating new automata to handle
+# alternative parse paths. Slower, yes. But it enables the user to
+# use almost precisely the action and input format that is used for
+# the standard parser.
+#
+# Note that iiparse(), as implemented here, may suspend multiple
+# results. So be sure to call it in some context where multiple
+# results can be used (e.g. every parse := iiparse(&input, 1), or the
+# like). Note also that when new parser "edges" get created, a
+# rather cumbersome recursive copy routine is used. Sorry, but it's
+# necessary to prevent unintended side-effects.
+#
+############################################################################
+#
+# The algorithm:
+#
+# A = list of active parsers needing action lookup
+# S = list of parsers to be shifted
+# R = list of parsers to be reduced
+# B = list of parsers that "choked"
+#
+# for every token on the input stream
+# begin
+# until length of R = 0 and length of A = 0
+# begin
+# - pop successive parsers off of A, and placing them in S,
+# R, or B, depending on parse table directives; suspend a
+# result for each parser that has reached an accepting
+# state
+# - pop successive parsers off of R, reducing them, and
+# placing them back in A; perform the action code
+# associated with each reduction
+# end
+# - pop successive parsers off of S, shifting them, and placing
+# them back in A; mark recovering parsers as recovered when
+# they have successfully shifted three tokens
+# if length of A = 0 and token not = EOF
+# then
+# - initiate error recovery on the parsers in B, i.e. for
+# each parser in B that is not already recovering, pop its
+# stack until error (-1) can legally be shifted, then shift
+# error, mark the parser as recovering from an error, and
+# place it back in A; if the parser is already recovering,
+# discard the current token
+# else
+# - clobber the parsers in B
+# end
+# end
+#
+# Note that when a given active parser in A is being classified
+# as needing a reduction, shift, suspension, or entry into the error
+# list (B), more than one action may apply due to ambiguity in the
+# grammar. At such points, the parser environment is duplicated,
+# once for each alternative pathway, and each of the new parsers is
+# then entered into the appropriate list (R or S; if accept is an
+# alternative, the classification routine suspends).
+#
+# Note also that when performing the action code associated with
+# reductions, parsers may be reclassified as erroneous, accepting,
+# etc. via "semantic" directives like IIERROR and IIACCEPT. See the
+# README file. Multiple-result action code will cause new parser
+# threads to be created, just as ambiguities in the grammar do within
+# the classification routine above.
+#
+#############################################################################
+#
+# See also: ibpag2.icn, iiparse.icn
+#
+############################################################################
+
+$$line 119 "iiglrpar.lib"
+
+$$ifndef IIDEBUG
+ $$define $iidebug 1
+ $$define show_new_forest 1
+$$endif # not IIDEBUG
+
+# These defines are output by Ibpag2 ahead of time (with the module
+# name appended, if need be):
+#
+# IIERROR
+# IIACCEPT
+# iiprune - GLR mode only
+# iiisolate - GLR mode only
+# iierrok
+# iiclearin
+
+# Parser environment + lookahead and pending action field.
+#
+record $ib_pe(state_stack, value_stack, action, errors,
+ recover_shifts, discards, clearin)
+
+# Warning! If you change the name of the value stack, change it also
+# in ibreader.icn, procedure write_action_as_procedure().
+#
+global $iilval, $line_number, $state_stack, $value_stack,
+ $iidirective, $ttbl, $errors, $discard_token
+
+#
+# iiparse: file x anything -> ?s (a generator)
+# (stream, fail_on_error) -> ?
+#
+# Where stream is an open file, where fail_on_error is a switch
+# that (if nonnull) tells the iiparse to fail, rather than abort,
+# on error, and where ?s represent the user-defined results of a
+# completed parse of file, from the current location up to the
+# point where the parser executes an "accept" action. Note that
+# iiparse, as implemented here, is a generator.
+#
+procedure $iiparse(stream, fail_on_error)
+
+ local token, next_token, actives, reducers, shifters, barfers
+ #global ttbl, errors
+ static atbl
+ initial {
+ $iidirective := ""
+ atbl := $atbl_insertion_point
+ $ttbl := $ttbl_insertion_point
+ $$line 166 "iiglrpar.lib"
+ \$iilex | stop("no iilex tokenizer defined")
+ }
+
+ actives := [ $ib_pe([1], [], &null, 0) ]
+ $state_stack := actives[1].state_stack
+ $value_stack := actives[1].value_stack
+ $errors := actives[1].errors
+ reducers := list()
+ shifters := list()
+ # I get tired of bland error code. We'll call the list of
+ # parsers in an error state "barfers" :-).
+ barfers := list()
+
+ next_token := create $iilex(stream, fail_on_error) | 0
+
+ token := @next_token
+ #
+ # After this ^, new tokens are read in near the end of the repeat
+ # loop. None is read in on an error, since then we will try again
+ # on the token that caused the error.
+ #
+ repeat {
+ until *actives = *reducers = 0
+ do {
+
+ # Prune out parsers that are doing the same thing as some
+ # other parser.
+ #
+ $$ifdef AUTO_PRUNE
+ auto_prune(actives)
+ $$endif
+
+ # Suspends $value_stack[1] on accept actions. Otherwise,
+ # puts parsers that need shifting into the shifters list,
+ # parsers that need reducing into the reducers list, and
+ # error-state parsers into the barfers list. Creates new
+ # parser environments as needed.
+ #
+ suspend $ib_action(atbl, token, actives, shifters,
+ reducers, barfers)
+
+ # Perform reductions. If instructed via the iiaccept
+ # macro, simulate an accept action, and suspend with a
+ # result.
+ #
+ suspend $perform_reductions(token, actives, shifters,
+ reducers, barfers)
+ }
+
+ # Shift token for every parser in the shifters list. This
+ # will create a bunch of new active parsers.
+ #
+ $perform_shifts(token, actives, shifters)
+ #
+ # If we get to here and have no actives, and we're not at the
+ # end of the input stream, then we are at an error impasse.
+ # Do formal error recovery.
+ #
+ if *actives = 0 & token ~=== 0 then {
+ suspend $perform_barfs(atbl, token, actives, barfers,fail_on_error)
+ #
+ # Perform_barfs sets discard_token if recovery was
+ # unsuccessful on the last token, and it needs discarding.
+ #
+ if \$discard_token := &null then
+ token := @next_token | break
+ #
+ # If there *still* aren't any active parsers, we've
+ # reached an impasse (or there are no error productions).
+ # Abort.
+ #
+ if *actives = 0 then {
+ if \fail_on_error then fail
+ else stop()
+ }
+ }
+ else {
+ #
+ # Parsers in an error state should be weeded out, since if
+ # we get to here, we have some valid parsers still going.
+ # I.e. only use them if there are *no* actives (see above).
+ #
+ $$ifdef IIDEBUG
+ write(&errout, "+++ pruning ", *barfers, " erroneous parsers")
+ while parser := pop(barfers)
+ do $iidebug("p", token, &null, parser)
+ $$else
+ while pop(barfers)
+ $$endif #IIDEBUG
+ #
+ # Get the next token. Only do this if we have active
+ # parsers not recovering from an error, i.e., if we're here.
+ #
+ token := @next_token | break
+ }
+ }
+
+end
+
+
+#
+# ib_action
+#
+procedure $ib_action(atbl, token, actives, shifters, reducers,
+ barfers)
+
+ local a, act, num, parser, new_parser
+
+ # While there is an active parser, take it off the actives list,
+ # and...
+ while parser := pop(actives) do {
+
+ # ...check for a valid action (if none, then there is an
+ # error; put it into the barfers list).
+ #
+ if a := \ (\atbl[token])[parser.state_stack[1]]
+ then {
+ a ? {
+ # Keep track of how many actions we've seen.
+ num := 0
+
+ # Snip off successive actions. If there's no
+ # ambiguity, there will be only one action, & no
+ # additional parser environments will be created.
+ #
+ while {
+ $$ifdef COMPRESSED_TABLES
+ # "\x80" is the accept action; uncompress_action
+ # does its own move()ing
+ act := $uncompress_action()
+ $$else
+ act := ="a" | {
+ tab(any('sr')) || tab(upto('.<')) ||
+ ((="<" || tab(find(">")+1)) | =".") ||
+ tab(many(&digits))
+ }
+ $$endif #COMPRESSED TABLES
+ }
+ do {
+ # New parser environment only needed for num > 1.
+ #
+ if (num +:= 1) > 1 then {
+ new_parser := $fullcopy(parser)
+ show_new_forest("=== table conflict; new parser",
+ actives, shifters, reducers, barfers, new_parser)
+ }
+ else new_parser := parser
+ new_parser.action := act
+
+ # Classify the action as s, r, or a, and place i
+ # the appropriate list (or suspend a result if a).
+ #
+ case act[1] of {
+ "s" : put(shifters, new_parser)
+ "r" : put(reducers, new_parser)
+ "a" : {
+ $iidebug("a", token, ruleno, parser)
+ suspend parser.value_stack[1]
+ }
+ }
+ }
+ }
+ }
+ else {
+ #
+ # Error. Parser will get garbage collected before another
+ # token is read from iilex, unless the parsers all fail -
+ # in which case, error recovery will be tried.
+ #
+ $iidebug("e", token, &null, parser)
+ put(barfers, parser)
+ }
+ }
+
+end
+
+
+#
+# perform_reductions
+#
+procedure $perform_reductions(token, actives, shifters, reducers, barfers)
+
+ local parser, ruleno, newsym, rhsize, arglist, result, num,
+ new_parser, tmp, p
+ static gtbl
+ initial {
+ gtbl := $gtbl_insertion_point
+ $$line 336 "iiglrpar.lib"
+ }
+
+ while parser := get(reducers)
+ do {
+
+ # Set up global state and value stacks, so that the action
+ # code can access them.
+ #
+ $state_stack := parser.state_stack
+ $value_stack := parser.value_stack
+ $errors := parser.errors
+
+ # Finally, perform the given action:
+ #
+ parser.action ? {
+ #
+ # Reduce action format, e.g. r1<S>2 = reduce by rule 1
+ # (LHS = S, RHS length = 2).
+ #
+ move(1)
+ ruleno := integer(1(tab(find("<")), move(1)))
+ newsym := 1(tab(find(">")), move(1))
+ rhsize := integer(tab(many(&digits)))
+ arglist := []
+ every 1 to rhsize do {
+ pop($state_stack)
+ push(arglist, pop($value_stack))
+ }
+ # Gtbl is "backwards," i.e. token first, state second.
+ # The value produced is the "goto" state.
+ #
+ push($state_stack, gtbl[newsym][$state_stack[1]])
+ #
+ # The actions are in procedures having the same name as
+ # the number of their rule, bracketed by underscores, &
+ # followed by the current module name. If there is such a
+ # procedure associated with the current reduce action,
+ # call it.
+ #
+ if func := proc("_" || ruleno || "_" || $module)
+ then {
+ num := 0
+ #
+ # For every valid result from the action code for the
+ # current reduction, create a new parser if need be
+ # (i.e. if num > 1), and check iidirective. Push the
+ # result onto the stack of the new parser & put the
+ # new parser into the actives list.
+ #
+ every result := func!arglist do {
+ # For all but the first result, create a new parser.
+ if (num +:= 1) > 1 then {
+ new_parser := $fullcopy(parser)
+ pop(new_parser.value_stack) # take off pushed result
+ show_new_forest("=== multi-result action; new parser",
+ actives, shifters, reducers, barfers, new_parser)
+ }
+ else new_parser := parser
+ #
+ # IIERROR, IIACCEPT, iierrok, iiisolate, and iiprune
+ # are all implemented using a search through a global
+ # iidirective variable; see the $defines described
+ # above.
+ #
+ tmp := $iidirective
+ $iidirective := ""
+ if *tmp > 0 then {
+ if find("clearin", tmp) then {
+ # see perform_shifts() below
+ new_parser.clearin := 1
+ }
+ if find("error", tmp) then {
+ $iidebug("e", token, ruleno, new_parser)
+ put(barfers, new_parser)
+ next
+ }
+ if find("errok", tmp) then {
+ new_parser.recover_shifts := &null
+ new_parser.discards := 0
+ }
+ if find("prune", tmp) then {
+ # Garden path.
+ $iidebug("p", token, ruleno, new_parser)
+ break next
+ }
+ if find("isolate", tmp) then {
+ # Prune all but the current parser.
+ $$ifdef IIDEBUG
+ write(&errout, "+++ isolating by pruning")
+ while p := pop(actives) do
+ $iidebug("p", token, ruleno, p)
+ while p := pop(reducers) do
+ $iidebug("p", token, ruleno, p)
+ while p := pop(shifters) do
+ $iidebug("p", token, ruleno, p)
+ while p := pop(barfers) do
+ $iidebug("p", token, ruleno, p)
+ $$else
+ while pop(actives)
+ while pop(reducers)
+ while pop(shifters)
+ while pop(barfers)
+ $$endif #IIDEBUG
+ push(new_parser.value_stack, result)
+ $iidebug("r", token, ruleno, new_parser)
+ put(actives, new_parser)
+ break next
+ }
+ if find("accept", tmp) then {
+ $iidebug("a", token, ruleno, new_parser)
+ suspend result
+ next
+ }
+ }
+ #
+ # Push result onto the new parser thread's value
+ # stack.
+ #
+ push(new_parser.value_stack, result)
+ $iidebug("r", token, ruleno, new_parser)
+ put(actives, new_parser)
+ #
+ # Action code must have the stack in its original
+ # form. So restore the stack's old form before
+ # going back to the action code.
+ #
+ if num = 1 then
+ $value_stack := parser.value_stack[2:0]
+ }
+ #
+ # If the action code for this rule failed, push &null.
+ # But first check $iidirective.
+ #
+ if num = 0 then {
+ #
+ # Same $iidirective code as above repeated
+ # (inelegantly) because it accesses too many
+ # variables to be easily isolated.
+ #
+ tmp := $iidirective
+ $iidirective := ""
+ if *tmp > 0 then {
+ if find("clearin", tmp) then {
+ # see perform_shifts() below
+ parser.clearin := 1
+ }
+ if find("error", tmp) then {
+ $iidebug("e", token, ruleno, parser)
+ put(barfers, parser)
+ next
+ }
+ if find("errok", tmp) then {
+ parser.recover_shifts := &null
+ parser.discards := 0
+ }
+ if find("prune", tmp) then {
+ # Garden path.
+ $iidebug("p", token, ruleno, parser)
+ next # go back to enclosing while pop...
+ }
+ if find("isolate", tmp) then {
+ # Prune all but the current parser.
+ $$ifdef IIDEBUG
+ write(&errout, "+++ isolating by pruning")
+ while p := pop(actives) do
+ $iidebug("p", token, ruleno, p)
+ while p := pop(reducers) do
+ $iidebug("p", token, ruleno, p)
+ while p := pop(shifters) do
+ $iidebug("p", token, ruleno, p)
+ while p := pop(barfers) do
+ $iidebug("p", token, ruleno, p)
+ $$else
+ while pop(actives)
+ while pop(reducers)
+ while pop(shifters)
+ while pop(barfers)
+ $$endif #IIDEBUG
+ }
+ if find("accept", tmp) then {
+ $iidebug("a", token, ruleno, parser)
+ suspend arglist[-1] | &null
+ next
+ }
+ }
+ # Finally, push the result!
+ result := arglist[-1] | &null
+ push(parser.value_stack, result)
+ $iidebug("r", token, ruleno, parser)
+ put(actives, parser)
+ }
+ }
+ # If there is no action code for this rule...
+ else {
+ # ...push the value of the last RHS arg.
+ # For 0-length e-productions, push &null.
+ result := arglist[-1] | &null
+ push(parser.value_stack, result)
+ $iidebug("r", token, ruleno, parser)
+ put(actives, parser)
+ }
+ }
+ }
+
+end
+
+
+#
+# perform_shifts
+#
+procedure $perform_shifts(token, actives, shifters)
+
+ local parser, ruleno
+
+ *shifters = 0 & fail
+
+ while parser := pop(shifters) do {
+ #
+ # One of the iidirectives is iiclearin, i.e. clear the input
+ # token and try again on the next token.
+ #
+ \parser.clearin := &null & {
+ put(actives, parser)
+ next
+ }
+ parser.action ? {
+ #
+ # Shift action format, e.g. s2.1 = shift and go to state 2
+ # by rule 1.
+ #
+ move(1)
+ push(parser.state_stack, integer(tab(find("."))))
+ push(parser.value_stack, $iilval)
+ ="."; ruleno := integer(tab(many(&digits)))
+ pos(0) | stop("malformed action: ", act)
+ #
+ # If, while recovering, we can manage to shift 3 tokens,
+ # then we consider ourselves resynchronized. Don't count
+ # the error token (-1).
+ #
+ if token ~= -1 then {
+ if \parser.recover_shifts +:= 1 then {
+ # 3 shifts make a successful recovery
+ if parser.recover_shifts > 4 then {
+ parser.recover_shifts := &null
+ parser.discards := 0
+ }
+ }
+ }
+ $iidebug("s", token, ruleno, parser)
+ }
+ put(actives, parser)
+ }
+
+ return
+
+end
+
+
+#
+# perform_barfs
+#
+procedure $perform_barfs(atbl, token, actives, barfers, fail_on_error)
+
+ #
+ # Note how this procedure has its own local reducers and shifters
+ # list. These are *not* passed from the parent environment!
+ #
+ local parser, count, reducers, shifters, recoverers
+
+ # To hold the list of parsers that need to shift error (-1).
+ recoverers := list()
+
+ count := 0
+ while parser := pop(barfers) do {
+ count +:= 1
+ if \parser.recover_shifts := 0 then {
+ #
+ # If we're already in an error state, discard the
+ # current token, and increment the number of discards
+ # we have made. 500 is too many; abort.
+ #
+ if (parser.discards +:= 1) > 500 then {
+ if proc($iierror)
+ then $iierror("fatal error: can't resynchronize")
+ else write(&errout, "fatal error: can't resynchronize")
+ if \fail_on_error then fail
+ else stop()
+ }
+ # try again on this one with the next token
+ put(actives, parser)
+ } else {
+ parser.errors +:= 1 # error count for this parser
+ parser.discards := parser.recover_shifts := 0
+ # If this is our first erroneous parser, print a message.
+ if count = 1 then {
+ if proc($iierror)
+ then $iierror(image(\$ttbl[token]) | image(token))
+ else write(&errout, "parse error")
+ }
+ #
+ # If error appears in a RHS, pop states until we get to a
+ # spot where error (-1) is a valid lookahead token:
+ #
+ if \$ttbl[-1] then {
+ until *parser.state_stack = 0 do {
+ if \atbl[-1][parser.state_stack[1]] then {
+ put(recoverers, parser)
+ break next
+ } else pop(parser.state_stack) & pop(parser.value_stack)
+ }
+ }
+ # If we get past here, the stack is now empty or there
+ # are no error productions. Abandon this parser.
+ $iidebug("p", token, &null, parser)
+ }
+ }
+
+ # Parsers still recovering are in the actives list; those that
+ # need to shift error (-1) are in the recoverers list. The
+ # following turns recoverers into actives:
+ #
+ if *recoverers > 0 then {
+ reducers := list() # a scratch list
+ shifters := list() # ditto
+ until *recoverers = *reducers = 0 do {
+ $$ifdef AUTO_PRUNE
+ auto_prune(actives)
+ $$endif
+ suspend $ib_action(atbl, -1, recoverers, shifters,
+ reducers, barfers)
+ suspend $perform_reductions(-1, recoverers, shifters,
+ reducers, barfers)
+ }
+ $perform_shifts(-1, recoverers, shifters)
+ every put(actives, !recoverers)
+ }
+ #
+ # If there were no recoverers, we've already shifted the error
+ # token, and are discarding tokens from the input stream. Note
+ # that if one parser was recovering, they *all* should be
+ # recovering, since if one was not recovering, it the erroneous
+ # parsers should all have been discarded by the calling proc.
+ #
+ else
+ $discard_token := 1
+
+end
+
+
+$$ifdef IIDEBUG
+
+record production(LHS, RHS, POS, LOOK, no, prec, assoc)
+#
+# iidebug
+#
+procedure $iidebug(action, token, ruleno, parser)
+
+ local p, t, state
+ static rule_list
+ initial {
+ rule_list := $rule_list_insertion_point
+ $$line 693 "iiglrpar.lib"
+ }
+
+ write(&errout, "--- In parser ", image(parser), ":")
+ case action of {
+ "a" : writes(&errout, "accepting ") &
+ state := parser.state_stack[1]
+ "e" : writes(&errout, "***ERROR***\n") &
+ write(&errout, "recover shifts = ",
+ parser.recover_shifts) &
+ write(&errout, "discarded tokens = ",
+ parser.discards) &
+ writes(&errout, "error action ") &
+ state := parser.state_stack[1]
+ "p" : writes(&errout, "***PRUNING***\n") &
+ writes(&errout, "prune action ") &
+ state := parser.state_stack[1]
+ "r" : writes(&errout, "reducing ") &
+ state := parser.state_stack[2]
+ "s" : writes(&errout, "shifting ") &
+ state := parser.state_stack[2]
+ default : stop("malformed action argument to iidebug")
+ }
+
+ t := image(token) || (" (" || (\$ttbl[token] | "unknown") || ")")
+ writes(&errout, "on lookahead ", t, ", in state ", state)
+ if \ruleno then {
+ (p := !rule_list).no === ruleno &
+ write(&errout, "; rule ", $production_2_string(p, $ttbl))
+ }
+ # for errors, ruleno is null
+ else write(&errout)
+
+ write(&errout, " state stack now: ")
+ every write(&errout, "\t", image(!parser.state_stack))
+ write(&errout, " value stack now: ")
+ if *parser.value_stack > 0
+ then every write(&errout, "\t", image(!parser.value_stack))
+ else write(&errout, "\t(empty)")
+
+ return
+
+end
+
+
+#
+# production_2_string: production record -> string
+# p -> s
+#
+# Stringizes an image of the LHS and RHS of production p in
+# human-readable form.
+#
+procedure $production_2_string(p, ibtoktbl)
+
+ local s, m, t
+
+ s := image(p.LHS) || " -> "
+ every m := !p.RHS do {
+ if t := \ (\ibtoktbl)[m]
+ then s ||:= t || " "
+ else s ||:= image(m) || " "
+ }
+ # if the POS field is nonnull, print it
+ s ||:= "(POS = " || image(\p.POS) || ") "
+ # if the LOOK field is nonnull, print it, too
+ s ||:= "lookahead = " || image(\p.LOOK)
+
+ return trim(s)
+
+end
+
+#
+# show_new_forest
+#
+procedure show_new_forest(msg, actives, shifters, reducers, barfers, parser)
+ write(&errout, msg)
+ write(&errout, " List of active parsers:")
+ every write(&errout, "\t", image(!actives))
+ every write(&errout, "\t", image(!shifters))
+ every write(&errout, "\t", image(!reducers))
+ every write(&errout, "\t", image(!barfers), " (error)")
+ write(&errout, "\tnew -> ", image(parser))
+end
+$$endif # IIDEBUG
+
+
+$$ifdef COMPRESSED_TABLES
+
+#
+# uncompress_action
+#
+procedure $uncompress_action()
+
+ local next_chunk, full_action
+
+ next_chunk := create ord(!&subject[&pos:0])
+ case $in_ib_bits(next_chunk, 2) of {
+ 0: {
+ full_action := "s"
+ full_action ||:= $in_ib_bits(next_chunk, 11)
+ full_action ||:= "."
+ full_action ||:= $in_ib_bits(next_chunk, 11)
+ move(3)
+ }
+ 1: {
+ full_action := "r"
+ full_action ||:= $in_ib_bits(next_chunk, 11)
+ full_action ||:= "<"
+ full_action ||:= $in_ib_bits(next_chunk, 11)
+ full_action ||:= ">"
+ full_action ||:= $in_ib_bits(next_chunk, 8)
+ move(4)
+ }
+ 2: {
+ full_action := "a"
+ move(1)
+ }
+ } | fail
+
+ return full_action
+
+end
+
+
+#
+# in_ib_bits: like inbits (IPL), but with coexpression for file
+#
+procedure $in_ib_bits(next_chunk, len)
+
+ local i, byte, old_byte_mask
+ static old_byte, old_len, byte_length
+ initial {
+ old_byte := old_len := 0
+ byte_length := 8
+ }
+
+ old_byte_mask := (0 < 2^old_len - 1) | 0
+ old_byte := iand(old_byte, old_byte_mask)
+ i := ishift(old_byte, len-old_len)
+
+ len -:= (len > old_len) | {
+ old_len -:= len
+ return i
+ }
+
+ while byte := @next_chunk do {
+ i := ior(i, ishift(byte, len-byte_length))
+ len -:= (len > byte_length) | {
+ old_len := byte_length-len
+ old_byte := byte
+ return i
+ }
+ }
+
+end
+
+$$endif # COMPRESSED_TABLES
+
+#
+# fullcopy: make full recursive copy of object obj
+#
+procedure $fullcopy(obj)
+
+ local retval, i, k
+
+ case type(obj) of {
+ "co-expression" : return obj
+ "cset" : return obj
+ "file" : return obj
+ "integer" : return obj
+ "list" : {
+ retval := list(*obj)
+ every i := 1 to *obj do
+ retval[i] := $fullcopy(obj[i])
+ return retval
+ }
+ "null" : return &null
+ "procedure" : return obj
+ "real" : return obj
+ "set" : {
+ retval := set()
+ every insert(retval, $fullcopy(!obj))
+ return retval
+ }
+ "string" : return obj
+ "table" : {
+ retval := table(obj[[]])
+ every k := key(obj) do
+ insert(retval, $fullcopy(k), $fullcopy(obj[k]))
+ return retval
+ }
+ # probably a record; if not, we're dealing with a new
+ # version of Icon or a nonstandard implementation, and
+ # we're screwed
+ default : {
+ retval := copy(obj)
+ every i := 1 to *obj do
+ retval[i] := $fullcopy(obj[i])
+ return retval
+ }
+ }
+
+end
+
+
+$$ifdef AUTO_PRUNE
+procedure auto_prune(actives)
+
+ new_actives := []
+ while parser1 := pop(actives) do {
+ every parser2 := actives[j := 1 to *actives] do {
+ parser1.state_stack[1] = parser2.state_stack[1] | next
+ *parser1.value_stack = *parser2.value_stack | next
+ every i := 1 to *parser1.value_stack do {
+ parser1.value_stack[i] === parser2.value_stack[i] |
+ break next
+ }
+ if parser1.errors < parser2.errors then
+ actives[j] := parser1
+ break next
+ }
+ put(new_actives, parser1)
+ }
+
+ every put(actives, !new_actives)
+ return &null
+
+end
+$$endif # AUTO_PRUNE