############################################################################
#
#	File:     dbg_run.icn
#
#	Subject:  Icon interactive debugging.
#		  Contains an interactive debugging run-time system.
#
#	Author:   Hakan Soderstrom
#
#	Revision: $Revision: 2.21 $
#
###########################################################################
#
# Copyright (c) 1994 Hakan Soderstrom and
# Soderstrom Programvaruverkstad AB, Sweden
# 
# Permission to use, copy, modify, distribute, and sell this software
# and its documentation for any purpose is hereby granted without fee,
# provided that the above copyright notice and this permission notice
# appear in all copies of the software and related documentation.
# 
# THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
# EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
# WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
#
# IN NO EVENT SHALL HAKAN SODERSTROM OR SODERSTROM PROGRAMVARUVERKSTAD
# AB BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL
# DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF THE POSSIBILITY
# OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
###########################################################################
#
# General note: all names are prefixed in an elaborate way in order to
# avoid name collisions with the debugged program.
# The default prefix for all globally visible names is '__dbg_'.
#
# This is the reason why lists are frequently used instead of records
# (whose field names clutter the global name space).
#
###########################################################################

#
#-------- Constants --------
#

# Versions (this program and 'itweak').
$define PROGRAM_VERSION	"$Revision: 2.21 $"

# Components of a breakpoint descriptor (list).
# Breakpoint id (integer).
$define BRKP_ID		1
# Source file (string).
$define BRKP_FILE	2
# File index.
$define BRKP_FIDX	3
# First line number.
$define BRKP_LINE1	4
# Second line number.
$define BRKP_LINE2	5
# Ignore counter (integer).
$define BRKP_IGNORE	6
# Condition for breaking.
$define BRKP_COND	7
# Commands to perform on break.
$define BRKP_DO		8

# Constants for 'the current breakpoint' and 'the last breakpoint'.
$define BRKP_CURRENT	-1
$define BRKP_LAST	-2

# Keywords for the 'clear' command.
# Definitions must match list in compilation procedure.
$define CLEAR_BREAKPOINT	1
$define CLEAR_COND		2
$define CLEAR_DO		3
$define CLEAR_ECHO		4
$define CLEAR_MACRO		5

# Keywords for the 'info' command.
# Definitions must match list in compilation procedure.
$define INFO_BREAKPOINT		1
$define INFO_ECHO		2
$define INFO_FILES		3
$define INFO_GLOBALS		4
$define INFO_LOCALS		5
$define INFO_MACROS		6
$define INFO_TRACE		7
$define INFO_VERSION		8

# Keywords for the 'set' command.
# Definitions must match list in compilation procedure.
$define SET_ECHO		1
$define SET_PRELUDE		2
$define SET_POSTLUDE		3

# Components of a command definition (list).
# Used for built-in commands as well as user-defined macros.
# Unabbreviated command/macro name (string).
$define CMD_NAME	1
# Command code (an integer corresponding to the name).
$define CMD_CODE	2
# Help text (list of string).
$define CMD_HELP	3
# Compilation procedure; null if macro.
$define CMD_COMPILE	4
# Macro definition (list of command instances, list of list).
# Null if built-in command.
$define CMD_MACRO	5
# Executing procedure, if built-in. Null otherwise.
$define CMD_EXEC	6

# Command codes.
$define BREAK_CMD	1
$define CLEAR_CMD	2
$define COMMENT_CMD	3
$define CONDITION_CMD	4
$define DO_CMD		5
$define END_CMD		6
$define EPRINT_CMD	7
$define FAIL_CMD	8
$define FPRINT_CMD	9
$define FRAME_CMD	10
$define GOON_CMD	11
$define HELP_CMD	12
$define INFO_CMD	13
$define IGNORE_CMD	14
$define MACRO_CMD	15
$define NEXT_CMD	16
$define PRINT_CMD	17
$define SET_CMD		18
$define SOURCE_CMD	19
$define STOP_CMD	20
$define TRACE_CMD	21
$define WHERE_CMD	22
$define USERDEF_CMD	23

# Environment variable for defining the input file (must be a string value).
$define DBG_INPUT_ENV	"DBG_INPUT"

# Environment variable for defining the primary output file
# (must be a string value).
$define DBG_OUTPUT_ENV	"DBG_OUTPUT"

# Prefix for debugging run-time global names.
$define DBG_PREFIX	"__dbg_"

# Maximum source nesting levels.
$define MAX_SOURCE_NESTING	12

# File index is obtained by shifting a small integer left a number of
# positions.
$define FIDX_SHIFT	10

# Prompt string to use in initialization mode.
$define INIT_PROMPT	"debug init $ "

# Execution return status.
# Normal return.
$define OK_STATUS	0
# Break the command loop, resume execution.
$define RESUME_STATUS	1
# Break the command loop, terminate the session.
$define STOP_STATUS	2
# Break the command loop, make the current procedure fail.
$define FAIL_STATUS	3

# Index into '__dbg_g_where'.
$define WHERE_FILE	1
$define WHERE_LINE	2
$define WHERE_PROC	3
$define WHERE_BRKP	4
$define WHERE_PRELUDE	5
$define WHERE_POSTLUDE	6

#
#-------- Record types --------
#

#
#-------- Globals --------
#

global __dbg_default_prelude, __dbg_default_postlude
# The source text for the default pre/postlude (single command assumed).

global __dbg_g_automacro
# The 'prelude' and 'postlude' macros.
# List of two components:
# (1) prelude commands,
# (2) postlude commands.
# Both are lists of compiled commands, not complete macros.

global __dbg_g_brkpcnt
# Counter incremented each break.
# Used to identify the file written by 'display' which is used by several
# commands.
# In this way we can check if we have to write the file anew.

global __dbg_g_brkpdef
# Lookup table for breakpoints.
# Entry key is a breakpoint id (integer).
# Entry value is a breakpoint descriptor (list).

global __dbg_g_brlookup
# Lookup table for breakpoints.
# Entry key is a file index or'ed with a line number (integer).
# Entry value is a breakpoint descriptor (list).

global __dbg_g_brkpid
# Id of the latest breakpoint created (integer).

global __dbg_g_cmd
# Table of command and macro definitions.
# Entry key is an unabbreviated command/macro name.
# Entry value is a command descriptor (list).

global __dbg_g_display
# Name of temporary file used by '__dbg_x_opendisplay' and others.

global __dbg_g_fileidx
# Table mapping source file names on (large) integers.
# Entry key is a source file name (string).
# Entry value is a file index (integer).

global __dbg_g_in
# The file through which debugging input is taken.

global __dbg_g_level
# Value of &level for the interrupted procedure.
# Calculated as &level for the breakpoint procedure - 1.

global __dbg_g_local
# Table containing local variables.
# Entry key is variable name (string).
# Entry value is the value of the variable (any type).

global __dbg_g_out1
# Primary file for debugging output.

global __dbg_g_out2, __dbg_g_out2name
# Secondary file for debugging output; used for 'set echo'.
# Null when no echoing is not active.
# The name of this file.

global  __dbg_g_src
# Stack of input files used by the 'source' command (list of file).
# Empty list when no 'source' command is active.

global __dbg_g_trace
# Current trace level (passed to &trace when resuming execution).

global __dbg_g_where
# A list with data about the current breakpoint.
# Contents (symbolic names below):
# (1) Source file name (string).
# (2) Source line number (integer).
# (3) Procedure name (string).
# (4) The breakpoint causing this break (breakpoint descriptor, a list).

global __dbg_g_white
# This program's definition of white space.

# A note on the use of global '__dbg_test' (defined in 'dbg_init.icn').
# The runtime system assigns this variable one of the following values.
# ** Function 'member' for ordinary testing against the breakpoint sets.
# ** Function 'integer' (which is guaranteed to always fail, given a
# set as its first parameter) in the 'nobreak' mode; execution continues
# without break until the program completes.
# ** Integer '2' which causes a break at every intercept point.
# (Returns the second parameter which is the line number.)

#
#-------- Globals for Icon functions used by the debuggin runtime --------
# In an excruciating effort to avoid being hit by bad manners from the
# program under test we use our own variables for Icon functions.

global __dbg_fany, __dbg_fclose, __dbg_fdelete, __dbg_fexit, __dbg_ffind
global __dbg_fgetenv, __dbg_fimage, __dbg_finsert, __dbg_finteger, __dbg_fior
global __dbg_fishift, __dbg_fkey, __dbg_fmany, __dbg_fmatch
global __dbg_fmove, __dbg_fpop, __dbg_fpos, __dbg_fproc, __dbg_fpush
global __dbg_fput, __dbg_fread, __dbg_fremove, __dbg_freverse, __dbg_fright
global __dbg_fsort, __dbg_fstring, __dbg_ftab, __dbg_ftable, __dbg_ftrim
global __dbg_ftype, __dbg_fupto, __dbg_fwrite, __dbg_fwrites

#
#-------------- Expression management globals -----------		
#

global __dbg_ge_message
# Holds message if there is a conflict in expression compilation or
# evaluation

global __dbg_ge_singular
# Value used as default for the local variable table.
# Must be initialized to an empty list (or other suitable value).

#
#-------- Main --------
#

procedure __dbg_proc (file, line, proc_name, var_name, var_val[])
# This procedure is invoked a first time during initialization with parameters
# all null.
# Then it is called every time we hit a breakpoint during a debugging session.
# The parameters define the breakpoint, as follows,
# 'file': source file name (string).
# 'line': source line number (integer).
# 'proc_name': name of the current procedure (string).
# 'var_name': names of variables local to the current procedure
# (list of string).
# The list is sorted alphabetically.
# 'Local' variables include parameters and static variables.
# 'var_val': The current values of the local variables (list).
# The values occur in the same order as the names in 'var_name'.
# NOTE: In order not to affect the logic of the debugged program this
# procedure MUST FAIL.
# If it returns anything the current procedure will fail immediately.
local bdescr, cond, cmd, idx, tfname
	# Save trace level; turn tracing off.
	__dbg_g_trace := &trace
	&trace := 0

	if \file then { # Not the first-time invocation from "dbg_init".
		# Increment the global breakpoint counter.
		__dbg_g_brkpcnt +:= 1

		# Compute the procedure nesting level.
		__dbg_g_level := &level - 1

		# Begin setting up the 'where' structure.
		__dbg_g_where := [file, line, proc_name, &null]

		# We get here either because of a 'next', or because we hit a
		# breakpoint.
		# If we break because of a 'next' we should not treat this as
		# a breakpoint, even if there is one on this source line.
		if __dbg_test === member then {
			# This is a breakpoint; get it.
			if bdescr := __dbg_g_brlookup[__dbg_fior (__dbg_g_fileidx[file],
						line)] then {
				# Check ignore count.
				((bdescr[BRKP_IGNORE] -:= 1) = -1) | fail
				bdescr[BRKP_IGNORE] := 0
				}
			else
				__dbg_io_cfl ("Mysterious break: %1 (%2:%3).",
					proc_name, file, line)
			}
		else {  # Break caused by 'next'.
			# By convention treated as breakpoint number 0.
			bdescr := __dbg_g_brkpdef[0]
			# Check ignore count.
			((bdescr[BRKP_IGNORE] -:= 1) = -1) | fail
			bdescr[BRKP_IGNORE] := 0
			}
		__dbg_g_where[WHERE_BRKP] := bdescr

		# Create table of locals.
		__dbg_g_local := __dbg_ftable (__dbg_ge_singular)
		every idx := 1 to *var_name do
			__dbg_g_local[var_name[idx]] := var_val[idx]

		# Evaluate the condition of the breakpoint, if any.
		if cond := \(bdescr)[BRKP_COND] then {
			idx := 0
			__dbg_e_eval (cond[1]) & (idx +:= 1)
			# Check for conflict.
			# Make sure we don't resume in such case.
			__dbg_io_cfl ("[%1] condition '%2'\n   %3",
				bdescr[BRKP_ID], cond[2], \__dbg_ge_message) &
				(idx +:= 1)
			(idx > 0) | fail
			}

		# Reset the test procedure (effective if this is a 'next' break).
		__dbg_test := member

		# The first command to execute is the macro attached to the
		# breakpoint, if any; otherwise the prelude.
		cmd := (\(\bdescr)[BRKP_DO] | __dbg_g_automacro[1])
		}
	else {	# Initialize global variables for Icon functions.
		__dbg_func_init ()
		# Initialize breakpoint globals.
		__dbg_g_brkpcnt := 0
		__dbg_g_brkpdef := __dbg_ftable ()
		__dbg_g_brlookup := __dbg_ftable ()
		__dbg_g_brkpid := 0

		# Compute the procedure nesting level.
		__dbg_g_level := &level - 2

		# Create breakpoint number 0, used for 'next' breaks.
		__dbg_g_brkpdef[0] := [0, "*any*", 0, 0, 0, 0, , ]

		# Display file name.
		__dbg_g_display := "_DBG" || &clock[4:6] || &clock[7:0] || ".tmp"

		# More globals.
		__dbg_g_src := []
		__dbg_g_white := ' \t'
		__dbg_ge_singular := []

		# Create file index table.
		idx := -1
		__dbg_g_fileidx := __dbg_ftable ()
		every __dbg_g_fileidx[key(__dbg_file_map)] :=
			__dbg_fishift ((idx +:= 1), FIDX_SHIFT)

		# Open input and output files.
		if tfname := __dbg_fgetenv (DBG_INPUT_ENV) then
			__dbg_g_in := __dbg_x_openfile (tfname)
		(/__dbg_g_in := &input) | __dbg_fpush (__dbg_g_src, &input)

		if tfname := __dbg_fgetenv (DBG_OUTPUT_ENV) then
			__dbg_g_out1 := __dbg_x_openfile (tfname, 1)
		/__dbg_g_out1 := &errout

		# Initialize command definitions.
		__dbg_cmd_init ()

		# Set up the breakpoint data structure.
		# This is not a breakpoint; the following keeps some commands from
		# crashing.
		__dbg_g_local := __dbg_ftable ()
		__dbg_g_where := [&null, 0, "main", &null]
		__dbg_default_prelude :=
		"fprint \"[%1] %2 (%3:%4)\\n\";&bp;&proc;&file;&line"
		__dbg_default_postlude := ""
		__dbg_g_automacro := [[__dbg_c_compile (__dbg_default_prelude)],
			[]]
		cmd := []
		}

	# Command processing.
	repeat {
		case __dbg_c_interp (cmd) of {
		RESUME_STATUS: break
		STOP_STATUS: {
			__dbg_fremove (__dbg_g_display)
			__dbg_io_note ("Debug session terminates.")
			__dbg_fexit (0)
			}
		}
		# Get input until it compiles OK.
		repeat {
			(*__dbg_g_src > 0) | __dbg_fwrites ("$ ")
			if cmd := [__dbg_c_compile (__dbg_io_getline ())] then
				break
			}
		}
	# Run the postlude, if any; status discarded.
	__dbg_c_interp (__dbg_g_automacro[2])
	&trace := __dbg_g_trace
end

#
#-------- Command processing procedures --------
#

procedure __dbg_c_compile (str, macro_def)
# Compiles a command.
# 'str' must be a command to compile (string).
# 'macro_def' must be non-null to indicate a macro is being defined.
# RETURNS a command instance (list), or
# FAILS on conflict.
local cmd, keywd
	str ? {
		__dbg_ftab (__dbg_fmany (__dbg_g_white))
		keywd := __dbg_ftab (__dbg_fupto (__dbg_g_white) | 0)
		if *keywd = 0 then # empty line treated as comment
			return [__dbg_cx_NOOP, COMMENT_CMD]
		__dbg_ftab (__dbg_fmany (__dbg_g_white))
		(cmd := __dbg_c_findcmd (keywd)) | fail
		return cmd[CMD_COMPILE] (cmd, macro_def)
		}
end

procedure __dbg_c_brkpt (not_zero)
# Extracts a breakpoint id from a command.
# A breakpoint id is either an integer, or one of the special forms
# '.' (current), '$' (last defined).
# 'not_zero' may be non-null to indicate that breakpoint number zero
# is not accepted.
# RETURNS a breakpoint identifier (integer) on success;
# FAILS with a suitable conflict message otherwise.
local id, res
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	(res := (__dbg_finteger (__dbg_ftab (__dbg_fmany (&digits))) |
		2(id := =".", BRKP_CURRENT) |
		2(id := ="$", BRKP_LAST))) | {
		__dbg_io_cfl ("Breakpoint id (integer, '.', '$') expected.")
		fail
		}
	(res > 0) | /not_zero | {
		__dbg_io_cfl ("Breakpoint number 0 not accepted here.")
		fail
		}
	return res
end

procedure __dbg_c_interp (clist)
# Command interpreter.
# 'clist' must be a list of command instances.
# The interpreter may call itself indirectly through commands.
# RETURNS a status code, or
# FAILS on conflict, abandoning its command list.
local cmd, code
	every cmd := !clist do {
		(code := cmd[1]!cmd) | fail
		(code = OK_STATUS) | return code
		}
	return OK_STATUS
end

procedure __dbg_c_findcmd (keywd)
# Finds a command descriptor given a keyword.
# 'keywd' must be a command keyword candidate, possibly abbreviated (string).
# RETURNS a command definition, or
# FAILS with a message on conflict.
local count, cmd, mstr, sep, try
	count := 0
	sep := mstr := ""
	every __dbg_fmatch (keywd, (try := !__dbg_g_cmd)[CMD_NAME], 1, 0) do {
		cmd := try
		count +:= 1
		mstr ||:= sep || cmd[CMD_NAME]
		sep := ", "
		}
	case count of {
	0: {
		__dbg_io_cfl ("%1: unrecognized command.", keywd)
		fail
		}
	1: return cmd
	default : {
		__dbg_io_cfl ("'%1': ambiguous (matches %2).", keywd, mstr)
		fail
		}
	}
end

procedure __dbg_c_findkey (keywd, keylist)
# Finds a command descriptor given a keyword.
# 'keywd' must be a keyword candidate, possibly abbreviated (string).
# 'keylist' must be a list of available keywords.
# RETURNS an integer index into 'keylist', or
# FAILS with a message on conflict.
local count, cmd, idx, mstr, sep
	count := 0
	sep := mstr := ""
	every __dbg_fmatch (keywd, keylist[idx := 1 to *keylist], 1, 0) do {
		count +:= 1
		mstr ||:= sep || keylist[cmd := idx]
		sep := ", "
		}
	case count of {
	0: {
		__dbg_io_cfl ("%1: unrecognized keyword.", keywd)
		fail
		}
	1: return cmd
	default : {
		__dbg_io_cfl ("'%1': ambiguous (matches %2).", keywd, mstr)
		fail
		}
	}
end

procedure __dbg_c_mcompile (fname)
# Compiles a macro.
# 'fname' must contain a file name (string) if the macro definition should
# be read from a file; otherwise null.
# If 'fname' is defined and can be opened, a null value is pushed on the file
# stack before the file, as a mark.
# RETURNS a macro, i.e. a list of compiled commands -- on success.
# FAILS if a conflict arises during the macro definition.
local cfl_count, cmd, f, line, macro
	cfl_count := 0
	macro := []
	if \fname then {
		if f := __dbg_x_openfile (fname) then {
			__dbg_fpush (__dbg_g_src, __dbg_g_in)
			__dbg_fpush (__dbg_g_src, &null)
			__dbg_g_in := f
			}
		else
			fail
		}
	repeat {
		(*__dbg_g_src > 0) | __dbg_fwrites ("> ")
		(line := __dbg_io_getline ()) | break
		if cmd := __dbg_c_compile (line, 1) then {
			if cmd[CMD_CODE] = END_CMD then
				break
			else
				__dbg_fput (macro, cmd)
			}
		 else
			cfl_count +:= 1
		(cfl_count < 30) | break
		}
	/__dbg_g_in := __dbg_fpop (__dbg_g_src)
	if cfl_count = 0 then
		return macro
	else {
		__dbg_io_note ("The definition did not take effect.")
		fail
		}
end

procedure __dbg_c_msource ()
# Checks if the source of a macro is a file.
# RETURNS a file name if there is a '<' followed by a file name.
# RETURNS null if there is nothing but white space.
# FAILS with a message on conflict.
local fname
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	if ="<" then {
		__dbg_ftab (__dbg_fmany (__dbg_g_white))
		if __dbg_fpos (0) then {
			__dbg_io_cfl ("File name expected.")
			fail
			}
		fname := __dbg_ftrim (__dbg_ftab (0))
		}
	return fname
end

procedure __dbg_x_brkpt (id)
# RETURNS a breakpoint descriptor, given a breakpoint id ('id', integer).
# FAILS with a diagnostic message on conflict.
local bdescr
	bdescr := case id of {
	BRKP_CURRENT:	\__dbg_g_where[WHERE_BRKP] |
			(__dbg_io_cfl ("No current breakpoint."), &null)
	BRKP_LAST:	\__dbg_g_brkpdef[__dbg_g_brkpid] |
		(__dbg_io_cfl ("Breakpoint [%1] undefined.", __dbg_g_brkpid),
			&null)
	default:	\__dbg_g_brkpdef[id] |
			(__dbg_io_cfl ("Breakpoint [%1] undefined.", id), &null)
	}
	return \bdescr
end

procedure __dbg_x_dispglob (f, pat)
# Essentially performs the 'info globals' command.
# 'f' must be a display file open for input.
# 'pat' must be a substring that variable names must contain.
local fchanged, line, word
static func
initial {
	func := set ()
	# A set containing all function names.
	every insert (func, function ())
	}
	fchanged := []
	until __dbg_fread (f) == "global identifiers:"
	repeat {
		(line := __dbg_fread (f)) | break
		word := []
		line ? repeat {
			__dbg_ftab (__dbg_fmany (__dbg_g_white))
			if __dbg_fpos (0) then
				break
			__dbg_fput (word, __dbg_ftab (__dbg_fupto (__dbg_g_white) | 0))
			}
		__dbg_fmatch (DBG_PREFIX, word[1]) | (word[1] == word[-1]) |
		if __dbg_ffind (pat, word[1]) then
			__dbg_io_info ("%1", word[1])

		# Check if function name has been used for other things.
		if member (func, word[1]) then {
			(word[-2] == "function" & word[-1] == word[1]) |
			put (fchanged, word[1])
			}
		}
	if *fchanged > 0 then {
		__dbg_io_note ("The following global(s) no longer hold their usual Icon functions:")
		every __dbg_io_wrline ("  " || !fchanged)
		}
end

procedure __dbg_x_dispinit (f)
# Reads the display file, skipping over lines caused by the debugger.
# 'f' must be the display file, open for input.
# RETURNS the first 'significant' line.
# NOTE that you must take care of the 'co-expression' line before calling
# this procedure.
local line
	until __dbg_fmatch (DBG_PREFIX, line := __dbg_fread (f))
	while line[1] == " " | __dbg_fmatch (DBG_PREFIX, line) do
		line := __dbg_fread (f)
	return line
end

procedure __dbg_x_lbreak (bdescr)
# Lists the nominal definition of a breakpoint.
# 'bdescr' may be a breakpoint descriptor, or null.
# If null all breakpoints are listed.
local bd, blist, cond, dodef, tmplist
	(blist := [\bdescr]) | {
		tmplist := __dbg_fsort (__dbg_g_brkpdef)
		blist := []
		every __dbg_fput (blist, (!tmplist)[2])
		}
	every bd := !blist do {
		dodef := if \bd[BRKP_DO] then "  DO defined" else ""
		__dbg_io_info ("[%1] %2 %3:%4%5", bd[BRKP_ID], bd[BRKP_FILE],
			bd[BRKP_LINE1], bd[BRKP_LINE2], dodef)
		if cond := \bd[BRKP_COND] then
			__dbg_io_info ("   CONDITION: %1", cond[2])
		}
end

procedure __dbg_x_openfile (fname, output, quiet)
# Opens a file.
# 'fname' must be the name of the file to open.
# 'output' must be non-null if the file is to be opened for output.
# 'quiet' must be non-null to prevent a conflict from generating a message.
# RETURNS an open file on success;
# FAILS with a message otherwise, unless 'quiet' is set.
# FAILS silently if 'quiet' is set.
local f, mode, modestr
	if \output then {
		mode := "w"
		modestr := "output"
		}
	else {
		mode := "r"
		modestr := "input"
		}
	(f := open (fname, mode)) | (\quiet & fail) |
	__dbg_io_cfl ("Cannot open '%1' for %2.", fname, modestr)
	return \f
end

procedure __dbg_x_opendisplay ()
# Opens the display file for reading; writes it first, if necessary.
# RETURNS a file open for input on success.
# FAILS with a message on conflict.
local f, res
	if f := __dbg_x_openfile (__dbg_g_display,, 1) then {
		if __dbg_finteger (__dbg_fread (f)) = __dbg_g_brkpcnt then
			res := f
		else
			__dbg_fclose (f)
		}
	\res | {
		(f := __dbg_x_openfile (__dbg_g_display, 1)) | fail
		__dbg_fwrite (f, __dbg_g_brkpcnt)
		display (, f)
		__dbg_fclose (f)
		(f := __dbg_x_openfile (__dbg_g_display)) | fail
		__dbg_fread (f) # Throw away breakpoint counter.
		res := f
		}
	return res
end

#-------- Command compilation procedures --------
# 'macro_def' must be non-null to indicate that a macro is being defined.
# The command compilation procedures must return a list representing the
# compiled command, or fail on conflict.
# When they are invoked the keyword and any following white space has been
# parsed.


procedure __dbg_cc_break (cmd, macro_def)
local fidx, fname, line1, line2
	__dbg_fany (&digits) | (fname := __dbg_ftab (__dbg_fupto (__dbg_g_white))) | {
		__dbg_io_cfl ("File name and/or line number expected.")
		fail
		}

	# Get file name.
	if \fname then {
		(fidx := \__dbg_g_fileidx[fname]) | {
			__dbg_io_cfl ("File name '%1' not recognized.", fname)
			fail
			}
		}
	else if fname := \__dbg_g_where[WHERE_FILE] then
		fidx := __dbg_g_fileidx[fname]
	else { # init mode
		__dbg_io_cfl ("File name required.")
		fail
		}

	# Get line number(s).
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	(line1 := __dbg_finteger (__dbg_ftab (__dbg_fmany (&digits)))) | {
		__dbg_io_cfl ("Line number expected.")
		fail
		}
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	if =":" then {
		__dbg_ftab (__dbg_fmany (__dbg_g_white))
			(line2 := __dbg_finteger (__dbg_ftab (__dbg_fmany (&digits)))) | {
				__dbg_io_cfl ("Line number expected.")
				fail
				}
		}
	else
		line2 := line1
	(line1 <= line2 < 1000000) | {
		__dbg_io_cfl ("Weird line number.")
		fail
		}

	# Create an almost finished breakpoint descriptor (id is missing).
	return [cmd[CMD_EXEC], cmd[CMD_CODE], [ , fname, fidx, line1, line2, 0, ,]]
end

procedure __dbg_cc_clear (cmd, macro_def)
# A compound command.
local keyidx, parm
static ckey
initial ckey := ["breakpoint", "condition", "do", "echo", "macro"]
	(keyidx := __dbg_c_findkey (__dbg_ftab (__dbg_fupto (__dbg_g_white) | 0), ckey)) |
		fail
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	case keyidx of {
	CLEAR_BREAKPOINT:
		(parm := __dbg_c_brkpt (1)) | fail
	(CLEAR_COND | CLEAR_DO):
		(parm := __dbg_c_brkpt ()) | fail
	CLEAR_MACRO:
		(parm := __dbg_e_idf ()) | {
			__dbg_io_cfl ("Macro name expected.")
			fail
			}
	}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], ckey, keyidx, parm]
end

procedure __dbg_cc_condition (cmd, macro_def)
local brkpt, expr
	(brkpt := __dbg_c_brkpt ()) | fail
	# This makes the expression cleaner, but not necessary.
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	(expr := __dbg_e_compile (__dbg_ftab (0))) | {
		__dbg_io_cfl (__dbg_ge_message)
		fail
		}
	(*expr = 1) | __dbg_io_note ("Last %1 expressions ignored.", *expr - 1)
	return [cmd[CMD_EXEC], cmd[CMD_CODE], brkpt, expr[1]]
end

procedure __dbg_cc_do (cmd, macro_def)
local brkpt, fname
	/macro_def | {
		__dbg_io_cfl ("Sorry, nested macros not accepted.")
		fail
		}
	(brkpt := __dbg_c_brkpt ()) | fail
	(fname := __dbg_c_msource ()) | fail
	return [cmd[CMD_EXEC], cmd[CMD_CODE], brkpt, fname]
end

procedure __dbg_cc_end (cmd, macro_def)
	\macro_def | {
		__dbg_io_cfl ("'end' out of context.")
		fail
		}
	return [cmd[CMD_EXEC], cmd[CMD_CODE]]
end

procedure __dbg_cc_eprint (cmd, macro_def)
local expr
	(expr := __dbg_e_compile (__dbg_ftab (0))) | {
		__dbg_io_cfl (__dbg_ge_message)
		fail
		}
	(*expr = 1) | __dbg_io_note ("Last %1 expressions ignored.", *expr - 1)
	return [cmd[CMD_EXEC], cmd[CMD_CODE], expr[1]]
end

procedure __dbg_cc_frame (cmd, macro_def)
local frame_no
	__dbg_fpos (0) | (frame_no := __dbg_finteger (__dbg_ftab (__dbg_fmany (&digits ++ '-')))) | {
		__dbg_io_cfl ("Frame number expected.")
		fail
		}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], frame_no]
end

procedure __dbg_cc_goon (cmd, macro_def)
local opt
	__dbg_fpos (0) | __dbg_fmatch (opt := __dbg_ftab (__dbg_fmany (&lcase)), "nobreak", 1, 0) | {
		__dbg_io_cfl ("Expected 'nobreak', found '%1'.", opt)
		fail
		}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], opt]
end

procedure __dbg_cc_help (cmd, macro_def)
local keywd
	__dbg_fpos (0) | (keywd := __dbg_ftab (__dbg_fmany (&lcase))) | {
		__dbg_io_cfl ("Command keyword expected.")
		fail
		}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], keywd]
end

procedure __dbg_cc_ignore (cmd, macro_def)
local brkpt, count
	(brkpt := __dbg_c_brkpt ()) | fail
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	(count := __dbg_finteger (__dbg_ftab (__dbg_fmany (&digits ++ '+-')))) | {
		__dbg_io_cfl ("Integer ignore count expected.")
		fail
		}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], brkpt, count]
end

procedure __dbg_cc_info (cmd, macro_def)
# A compound command.
local keyidx, parm
static ckey
initial ckey := ["breakpoint", "echo", "files", "globals", "locals", "macros",
	"trace", "version"]
	(keyidx := __dbg_c_findkey (__dbg_ftab (__dbg_fupto (__dbg_g_white) | 0), ckey)) |
		fail
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	if keyidx = INFO_BREAKPOINT then
		__dbg_fpos (0) | (parm := __dbg_c_brkpt ()) | fail
	else if keyidx = INFO_GLOBALS then
		__dbg_fpos (0) | (parm := __dbg_ftab (__dbg_fupto (__dbg_g_white) | 0))
	return [cmd[CMD_EXEC], cmd[CMD_CODE], ckey, keyidx, parm]
end

procedure __dbg_cc_macro (cmd, macro_def)
local fname, idf
	/macro_def | {
		__dbg_io_cfl ("Sorry, nested macros not accepted.")
		fail
		}
	(idf := __dbg_ftab (__dbg_fmany (&lcase))) | {
		__dbg_io_cfl ("Macro name expected.")
		fail
		}
	(fname := __dbg_c_msource ()) | fail
	return [cmd[CMD_EXEC], cmd[CMD_CODE], idf, fname]
end

procedure __dbg_cc_next (cmd, macro_def)
local count
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	__dbg_fpos (0) | (count := __dbg_finteger (__dbg_ftab (__dbg_fmany (&digits ++ '+-')))) | {
		__dbg_io_cfl ("Integer ignore count expected.")
		fail
		}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], count]
end

procedure __dbg_cc_print (cmd, macro_def)
# Used to compile 'fprint' and 'print'.
local expr
	(expr := __dbg_e_compile (__dbg_ftab (0))) | {
		__dbg_io_cfl (__dbg_ge_message)
		fail
		}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], expr]
end

procedure __dbg_cc_set (cmd, macro_def)
# A compound command.
local keyidx, parm
static ckey
initial ckey := ["echo", "prelude", "postlude"]
	(keyidx := __dbg_c_findkey (__dbg_ftab (__dbg_fupto (__dbg_g_white) | 0), ckey)) |
		fail
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	case keyidx of {
	SET_ECHO: {
		parm := __dbg_ftrim (__dbg_ftab (__dbg_fupto (__dbg_g_white) | 0))
		(*parm > 0) | {
			__dbg_io_cfl ("File name expected.")
			fail
			}
		}
	(SET_PRELUDE | SET_POSTLUDE):
		(parm := __dbg_c_msource ()) | fail
	}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], ckey, keyidx, parm]
end

procedure __dbg_cc_source (cmd, macro_def)
# The 'source' command is different from other commands, because it is not
# really compiled; it takes effect immediately.
# In contrast to macro compilation, no null marker is pushed on the file stack.
# RETURNS a dummy 'source' command.
local f, fname, res
	__dbg_ftab (__dbg_fmany (__dbg_g_white))
	if __dbg_fpos (0) then
		__dbg_io_cfl ("File name expected.")
	else {
		fname := __dbg_ftrim (__dbg_ftab (0))
		if *__dbg_g_src >= MAX_SOURCE_NESTING then
			__dbg_io_cfl ("%1: Too deeply nested 'source' file.", fname)
		else if f := __dbg_x_openfile (fname) then {
			__dbg_fpush (__dbg_g_src, __dbg_g_in)
			__dbg_g_in := f
			res := [cmd[CMD_EXEC], cmd[CMD_CODE], fname]
			}
		}
	return \res
end

procedure __dbg_cc_trace (cmd, macro_def)
local tlevel
	(tlevel := __dbg_finteger (__dbg_ftab (__dbg_fmany (&digits ++ '+-')))) | {
		__dbg_io_cfl ("Integer value expected.")
		fail
		}
	return [cmd[CMD_EXEC], cmd[CMD_CODE], \tlevel]
end

procedure __dbg_cc_SIMPLE (cmd, macro_def)
# Used to compile all keyword-only commands, including macros.
	return [cmd[CMD_EXEC], cmd[CMD_CODE], cmd[CMD_MACRO]]
end

#-------- Command executing procedures --------
# The first parameter of these procedures is the procedure itself.
# (Not a very interesting parameter.)
# The command executing procedures must return a return code on success.
# Return codes are defined among the symbolic constants.
# The procedures must fail on conflict.


procedure __dbg_cx_break (proced, ccode, brkp)
local id, bpset, fidx, line1, line2
	# Add the breakpoint id to the descriptor.
	brkp[BRKP_ID] := id := (__dbg_g_brkpid +:= 1)
	__dbg_io_wrline ("[" || id || "]")
	# Make sure we can find the breakpint descriptor, given its id.
	__dbg_g_brkpdef[id] := brkp
	# Install the breakpoint lines in the lookup table.
	fidx := brkp[BRKP_FIDX]
	line1 := brkp[BRKP_LINE1]
	line2 := brkp[BRKP_LINE2]
	every __dbg_g_brlookup[__dbg_fior (fidx, line1 to line2)] := brkp
	# Add the line numbers to the breakpoint set.
	bpset := __dbg_file_map[brkp[BRKP_FILE]]
	every __dbg_finsert (bpset, line1 to line2)
	return OK_STATUS
end

procedure __dbg_cx_clear (proced, ccode, ckey, keyidx, parm)
# 'ckey' will be a list containing all the possible keywords to 'clear'.
# 'keyidx' is an index into that list, indicating a subcommand.
local bdescr, bpset, cmd, fidx, lcode, line, line1, line2
	if keyidx = (CLEAR_BREAKPOINT | CLEAR_COND | CLEAR_DO) then
		(bdescr := __dbg_x_brkpt (parm)) | fail
	else if keyidx = CLEAR_MACRO then
		(cmd := __dbg_c_findcmd (parm)) | fail
	case keyidx of {
	CLEAR_BREAKPOINT: {
		__dbg_fdelete (__dbg_g_brkpdef, bdescr[BRKP_ID])
		fidx := bdescr[BRKP_FIDX]
		line1 := bdescr[BRKP_LINE1]
		line2 := bdescr[BRKP_LINE2]
		bpset := __dbg_file_map[bdescr[BRKP_FILE]]
		# The range of lines once defined for the breakpoint might
		# have been overwritten by later breakpoints.
		every lcode := __dbg_fior (fidx, line := line1 to line2) do {
			if __dbg_g_brlookup[lcode] === bdescr then {
				__dbg_fdelete (__dbg_g_brlookup, lcode)
				__dbg_fdelete (bpset, line)
				}
			}
		}
	CLEAR_COND:	bdescr[BRKP_COND] := &null
	CLEAR_DO:	bdescr[BRKP_DO] := &null
	CLEAR_ECHO: {
		__dbg_fclose (\__dbg_g_out2)
		__dbg_g_out2 := &null
		}
	CLEAR_MACRO: {
		(cmd := __dbg_c_findcmd (parm)) | fail
		__dbg_fdelete (__dbg_g_cmd, cmd[CMD_NAME])
		}
	}
	return OK_STATUS
end

procedure __dbg_cx_condition (proced, ccode, brkpt, expr)
local bdescr
	(bdescr := __dbg_x_brkpt (brkpt)) | fail
	bdescr[BRKP_COND] := expr
	return OK_STATUS
end

procedure __dbg_cx_do (proced, ccode, brkpt, fname)
local bdescr
	(bdescr := __dbg_x_brkpt (brkpt)) | fail
	(bdescr[BRKP_DO] := __dbg_c_mcompile (fname)) | fail
	return OK_STATUS
end

procedure __dbg_cx_eprint (proced, ccode, expr)
local count, val
	__dbg_io_wrline ("{" || expr[2] || "}")
	count := 0
	every val := __dbg_fimage (__dbg_e_eval (expr[1])) do {
		if __dbg_io_cfl (\__dbg_ge_message) then
			fail
		else
			__dbg_io_wrline ("" || __dbg_fright ((count +:= 1), 3) ||
				": " || val)
		}
	return OK_STATUS
end

procedure __dbg_cx_fprint (proced, ccode, elist)
# 'elist' must be a list on the format returned by '__dbg_e_compile'.
local expr, fmt, idx, sval, val
	val := []
	every expr := !elist do {
		__dbg_fput (val, __dbg_e_eval (expr[1]) | "&fail")
		if __dbg_io_cfl (\__dbg_ge_message) then
			fail
		}
	(fmt := __dbg_fstring (val[1])) | {
		__dbg_io_cfl ("Expected format string; got '%1'.", __dbg_fimage (val[1]))
		fail
		}
	sval := []
	every idx := 2 to *val do {
		__dbg_fput (sval, __dbg_fstring (val[idx])) | {
		__dbg_io_cfl ("Expression not string-convertible: {%1} %2",
			elist[idx][2], __dbg_fimage (val[idx]))
			fail
			}
		}
	__dbg_io_wrstr (__dbg_x_subst (fmt, sval))
	return OK_STATUS
end

procedure __dbg_cx_frame (proced, ccode, frame_spec)
local f, frame_no, idx, line
	frame_no := if \frame_spec then {
		if frame_spec < 0 then __dbg_g_level + frame_spec else frame_spec
		} else __dbg_g_level
	(1 <= frame_no <= __dbg_g_level) | {
		__dbg_io_cfl ("Invalid frame number.")
		fail
		}
	(f := __dbg_x_opendisplay ()) | fail
	line := __dbg_x_dispinit (f)
	idx := __dbg_g_level
	while idx > frame_no do {
		repeat if (line := __dbg_fread (f))[1] ~== " " then
				break
		idx -:= 1
		}
	__dbg_io_info ("(%1) %2", frame_no, line)
	repeat {
		if (line := __dbg_fread (f))[1] ~== " " then
			break
		line ? {
			__dbg_ftab (__dbg_fmany (__dbg_g_white))
			=DBG_PREFIX | __dbg_io_info ("%1", line, *line > 0)
			}
		}
	__dbg_fclose (f)
	return OK_STATUS
end

procedure __dbg_cx_goon (proced, ccode, nobreak)
	if \nobreak then {
		__dbg_test := integer
		__dbg_fremove (__dbg_g_display)
		}
	return RESUME_STATUS
end

procedure __dbg_cx_help (proced, ccode, keywd)
# 'keywd' will be an identifier if the command had a keyword.
local cmd, hstr
	if cmd := __dbg_c_findcmd (\keywd) then {
		if hstr := \cmd[CMD_HELP] then
			__dbg_io_wrline (hstr)
		else
			__dbg_io_note ("No help available for '%1'.", cmd[CMD_NAME])
		}
	else
__dbg_io_wrline ("Available commands: (all keywords may be abbreviated)\n_
break		(set breakpoint)\n_
clear		(clear breakpoint or debugger parameter)\n_
condition	(attach condition to breakpoint)\n_
do		(attach macro to breakpoint)\n_
end		(terminate macro definition)\n_
eprint		(print every value from expression)\n_
fprint		(formatted print)\n_
frame		(inspect procedure call chain)\n_
goon		(resume execution)\n_
help		(print explanatory text)\n_
ignore		(set ignore counter on breakpoint)\n_
info		(print information about breakpoint or debugger parameter)\n_
macro		(define new command)\n_
next		(resume execution, break on every line)\n_
print		(print expressions)\n_
set		(set a debugger parameter)\n_
source		(read debugging commands from file)\n_
stop		(terminate program and debugging session)\n_
trace		(set value of Icon &trace)\n_
where		(print procedure call chain)\n\n_
An expression may be formed from a large subset of Icon operators; integer,\n_
string, list literals; locals from the current procedure, and globals.\n_
Procedure/function invocation, subscripting, record field reference is\n_
supported. Several keywords are also included.\n\n_
New/altered keywords,\n_
\	&bp, &breakpoint	current breakpoint id (integer)\n_
\	&file			current breakpoint source file name (string)\n_
\	&line			current breakpoint line number (integer)\n_
\	&proc			current breakpoint procedure name (string)")
	return OK_STATUS
end

procedure __dbg_cx_ignore (proced, ccode, brkpt, count)
local bdescr
	(bdescr := __dbg_x_brkpt (brkpt)) | fail
	bdescr[BRKP_IGNORE] := count
	return OK_STATUS
end

procedure __dbg_cx_info (proced, ccode, ckey, keyidx, parm)
# 'ckey' will be a list containing all the possible keywords to 'info'.
# 'keyidx' is an index into that list, indicating a subcommand.
local cmd, bdescr, f, nlist, version
	case keyidx of {
	INFO_BREAKPOINT:
		if \parm then {
			(bdescr := __dbg_x_brkpt (parm)) | fail
			__dbg_x_lbreak (bdescr)
			}
		else
			__dbg_x_lbreak ()
	INFO_ECHO:
		if \__dbg_g_out2 then
			__dbg_io_info ("Echo file: %1.", __dbg_g_out2name)
		else
			__dbg_io_info ("No echo file.")
	INFO_FILES: {
		nlist := []
		every __dbg_fput (nlist, __dbg_fkey (__dbg_file_map))
		nlist := __dbg_fsort (nlist)
		__dbg_io_info ("Tweaked source files in this program:")
		every __dbg_io_info ("   %1", !nlist)
		}
	INFO_GLOBALS: {
		(f := __dbg_x_opendisplay ()) | fail
		if \parm then
			__dbg_x_dispglob (f, parm)
		else
			__dbg_x_dispglob (f, "")
		__dbg_fclose (f)
		}
	INFO_LOCALS: {
		nlist := []
		every __dbg_fput (nlist, __dbg_fkey (__dbg_g_local))
		nlist := __dbg_fsort (nlist)
		__dbg_io_info ("Local identifiers in the current procedure:",
			*nlist > 0)
		every __dbg_io_info ("   %1", !nlist)
		}
	INFO_MACROS: {
		nlist := []
		every \(cmd := !__dbg_g_cmd)[CMD_MACRO] do
			__dbg_fput (nlist, cmd[CMD_NAME])
		nlist := __dbg_fsort (nlist)
		__dbg_io_info ("Currently defined macros:", *nlist > 0)
		every __dbg_io_info ("   %1", !nlist)
		}
	INFO_TRACE:
		__dbg_io_info ("Current trace level: %1.", __dbg_g_trace)
	INFO_VERSION: {
		version := (PROGRAM_VERSION ? (__dbg_ftab (__dbg_fupto (&digits)),
			__dbg_ftab (__dbg_fmany (&digits++'.'))))
		__dbg_io_info ("Program tweaked by itweak version %1.\n_
		This is runtime version %2.", __dbg_itweak_ver, version)
		}
	}
	return OK_STATUS
end

procedure __dbg_cx_macro (proced, ccode, idf, fname)
# Executes a 'macro' statement (not the resulting macro).
# 'fname' contains a file name (string) if the macro definition should be
# read from a file; otherwise null.
# SIDE EFFECT: Adds a command definition to '__dbg_g_cmd' on success.
local count, macro, mstr, sep, try
	count := 0
	mlist := []
	# Macro name must not be an abbreviation of an existing command.
	every __dbg_fmatch (idf, try := (!__dbg_g_cmd)[CMD_NAME], 1, 0) do {
		count +:= 1
		__dbg_fput (mlist, try)
		}
	# Check that no existing command is an abbreviation of macro name.
	every __dbg_fmatch (try := (!__dbg_g_cmd)[CMD_NAME], idf, 1, 0) do {
		count +:= 1
		(try == !mlist) | __dbg_fput (mlist, try)
		}
	(count = 0) | {
		mstr := sep := ""
		every mstr ||:= sep || !mlist do
			sep := ", "
		__dbg_io_cfl ("'%1' clashes with existing command (%2).", idf, mstr)
		fail
		}
	(macro := __dbg_c_mcompile (fname)) | fail
	__dbg_g_cmd[idf] := [idf, USERDEF_CMD, , __dbg_cc_SIMPLE, macro, __dbg_cx_userdef]
	return OK_STATUS
end

procedure __dbg_cx_next (proced, ccode, count)
# 'count' may be an ignore count.
	__dbg_g_brkpdef[0][BRKP_IGNORE] := \count
	__dbg_test := 2
	return RESUME_STATUS
end

procedure __dbg_cx_print (proced, ccode, elist)
# 'elist' must be a list on the format returned by '__dbg_e_compile'.
local expr, val
	every expr := !elist do {
		val := (__dbg_fimage (__dbg_e_eval (expr[1])) | "&fail")
		if __dbg_io_cfl (\__dbg_ge_message) then
			fail
		else
			__dbg_io_wrline ("{" || expr[2] || "} " || val)
		}
	return OK_STATUS
end

procedure __dbg_cx_set (proced, ccode, ckey, keyidx, parm)
# 'ckey' will be a list containing all the possible keywords to 'set'.
# 'keyidx' is an index into that list, indicating a subcommand.
	case keyidx of {
	SET_ECHO: {
		(__dbg_g_out2 := __dbg_x_openfile (parm, 1)) | fail
		__dbg_g_out2name := parm
		}
	SET_PRELUDE:
		(__dbg_g_automacro[1] := __dbg_c_mcompile (parm)) | fail
	SET_POSTLUDE:
		(__dbg_g_automacro[2] := __dbg_c_mcompile (parm)) | fail
	}
	return OK_STATUS
end

procedure __dbg_cx_stop (proced, ccode)
	return STOP_STATUS
end

procedure __dbg_cx_trace (proced, ccode, tlevel)
	__dbg_g_trace := tlevel
	return OK_STATUS
end

procedure __dbg_cx_where (proced, ccode)
local f, idf, idx, line
	(f := __dbg_x_opendisplay ()) | fail
	__dbg_io_info ("Current call stack in %1:", __dbg_fread (f))
	idx := __dbg_g_level
	line := __dbg_x_dispinit (f)
	repeat {
		idf := (line ? __dbg_ftab (__dbg_fupto (__dbg_g_white)))
		if idf == "global" then
			break
		if *idf > 0 then {
			__dbg_io_info ("(%1) %2", idx, idf)
			idx -:= 1
			}
		(line := __dbg_fread (f)) | break # Sanity.
		}
	__dbg_fclose (f)
	return OK_STATUS
end

procedure __dbg_cx_userdef (proced, ccode, macro)
	return __dbg_c_interp (macro)
end

procedure __dbg_cx_NOOP (proced, ccode)
	return OK_STATUS
end

#
#-------- General-purpose procedures --------
#

procedure __dbg_x_fld_adj (str)
# Part of 'subst' format string parsing.
# 'str' must be a parameter string identified by the beginning part of a
# placeholder ('%n').
# This procedure checks if the placeholder contains a fixed field width
# specifier.
# A fixed field specifier begins with '<' or '>' and continues with the field
# width expressed as a decimal literal.
# RETURNS 'str' possibly inserted in a fixed width field.
local just, init_p, res, wid
static fwf
initial fwf := '<>'
	init_p := &pos
	if (just := if ="<" then left else if =">" then right) &
		(wid := __dbg_finteger (__dbg_ftab (__dbg_fmany (&digits)))) then
		res := just (str, wid)
	else {
		res := str
		&pos := init_p
		}
	return res
end

procedure __dbg_x_subst (msg, parm)
# Substitutes parameters in a message template.
# 'msg' must be a message template (string).
# 'parm' must be a list of parameters (list of string-convertible), or null.
# It may also be a string.
local esc, res, sub
static p_digit
initial p_digit := '123456789'
	\parm | return msg
	parm := [__dbg_fstring (parm)]
	res := ""
	msg ? until __dbg_fpos (0) do {
		res ||:= __dbg_ftab (__dbg_fupto ('%\\') | 0)
		if ="%" then res ||:= {
			if __dbg_fany (p_digit) then {
				sub := (\parm[__dbg_finteger (__dbg_fmove (1))] | "")
				__dbg_x_fld_adj (sub)	
				}
			else if __dbg_fany ('%') then
				__dbg_fmove (1)
			else ""
			}
		else if ="\\" then res ||:= case esc := __dbg_fmove (1) of {
			"n": "\n"
			"t": "\t"
			default: esc
			}
		}
	return res
end

#
#-------- Input/Output procedures --------
#

procedure __dbg_io_cfl (format, parm[])
# Writes a conflict message to debugging output.
# 'format' must be a format string.
# 'parm' must be string-convertibles to insert into placeholders in the
# format string, if any.
# RETURNS 1 (i.e. always succeeds).
	__dbg_io_wrline ("[debug CONFLICT] " || __dbg_x_subst (format, parm))
	return 1
end

procedure __dbg_io_getline ()
# RETURNS the next line from debugging input, or
# FAILS on end of file.
local line
	(line := __dbg_fread (__dbg_g_in)) | {
		__dbg_fclose (__dbg_g_in)
		# Check for a macro definition marker.
		\(__dbg_g_in := __dbg_fpop (__dbg_g_src)) | fail
		if *__dbg_g_src > 0 then
			return __dbg_io_getline ()
		}
	__dbg_fwrite (\__dbg_g_out2, "$ ", \line)
	return \line
end

procedure __dbg_io_info (format, parm[])
# Writes an info message to debugging output.
# 'format' must be a format string.
# 'parm' must be string-convertibles to insert into placeholders in the
# format string, if any.
	__dbg_io_wrline (__dbg_x_subst (format, parm))
end

procedure __dbg_io_note (format, parm[])
# Writes a note to debugging output.
# 'format' must be a format string.
# 'parm' must be string-convertibles to insert into placeholders in the
# format string, if any.
	__dbg_io_wrline ("[debug NOTE] " || __dbg_x_subst (format, parm))
end

procedure __dbg_io_wrline (line)
# Writes a string and a newline to debugging output.
# 'line' must be the string to write.
# It may contains additional newlines.
	__dbg_fwrite (__dbg_g_out1, line)
	__dbg_fwrite (\__dbg_g_out2, line)
end

procedure __dbg_io_wrstr (line)
# Writes a string without a newline to debugging output.
# 'line' must be the string to write.
# It may contains additional newlines.
	__dbg_fwrites (__dbg_g_out1, line)
	__dbg_fwrites (\__dbg_g_out2, line)
end

#
#-------- Function initialization ---------
#
procedure __dbg_func_init ()
	__dbg_fany	:= any
	__dbg_fclose	:= close
	__dbg_fdelete	:= delete
	__dbg_fexit	:= exit
	__dbg_ffind	:= find
	__dbg_fgetenv	:= getenv
	__dbg_fimage	:= image
	__dbg_finsert	:= insert
	__dbg_finteger	:= integer
	__dbg_fior	:= ior
	__dbg_fishift	:= ishift
	__dbg_fkey	:= key
	__dbg_fmany	:= many
	__dbg_fmatch	:= match
	__dbg_fmove	:= move
	__dbg_fpop	:= pop
	__dbg_fpos	:= pos
	__dbg_fproc	:= proc
	__dbg_fpush	:= push
	__dbg_fput	:= put
	__dbg_fread	:= read
	__dbg_fremove	:= remove
	__dbg_freverse	:= reverse
	__dbg_fright	:= right
	__dbg_fsort	:= sort
	__dbg_fstring	:= string
	__dbg_ftab	:= tab
	__dbg_ftable	:= table
	__dbg_ftrim	:= trim
	__dbg_ftype	:= type
	__dbg_fupto	:= upto
	__dbg_fwrite	:= write
	__dbg_fwrites	:= writes
end

#
#-------- Command initialization ---------
#

procedure __dbg_cmd_init ()
# Initialize command definitions.
	__dbg_g_cmd := __dbg_ftable ()
### break
		__dbg_g_cmd["break"] := ["break", BREAK_CMD,
"	break [file] [line [: line]]\n_
Sets a breakpoint on a line or a range of lines. The file name (if present)\n_
must be one of the tweaked files (cf. the 'info files' command). If omitted\n_
the file of the current breakpoint is assumed. The identity of the new\n_
breakpoint (an integer) is displayed. It may be used in other commands.\n_
Besides an integer there are two other ways to identify a breakpoint,\n_
\	.	(dot) the current breakpoint,\n_
\	$	(dollar) the last breakpoint defined by a 'break' command.\n_
Breakpoint 0 (zero) is special; see the 'next' command.\n\n_
As a rule a breakpoint takes effect AFTER the breakpointed line has been\n_
executed. If two breakpoints are defined on the same line, only the latest\n_
is in effect.",
__dbg_cc_break, , __dbg_cx_break]
### clear
		__dbg_g_cmd["clear"] := ["clear", CLEAR_CMD,
"	clear breakpoint brkpt\n_
Deletes breakpoint identified by 'brkpt'.\n_
\	clear condition brkpt\n_
Removes condition from breakpoint 'brkpt'. The breakpoint becomes\n_
unconditional.\n_
\	clear do brkpt\n_
Removes commands associated with breakpoint 'brkpt'.\n_
\	clear echo\n_
Stops output to echo file.\n_
\	clear macro name\n_
Removes macro identified by 'name'.",
__dbg_cc_clear, , __dbg_cx_clear]
### comment
		__dbg_g_cmd["#"] := ["#", COMMENT_CMD,
"	# comment text\n_
A line beginning with '#' is ignored.",
__dbg_cc_SIMPLE, , __dbg_cx_NOOP]
### condition
		__dbg_g_cmd["condition"] := ["condition", CONDITION_CMD,
"	condition brkpt expr\n_
Attaches a condition to breakpoint 'brkpt'. The expression 'expr' must\n_
succeed for a break to occur.",
__dbg_cc_condition, , __dbg_cx_condition]
### do
		__dbg_g_cmd["do"] := ["do", DO_CMD,
"	do brkpt [<filename]\n_
Attaches commands to the breakpoint identified by 'brkpt'. The commands\n_
are entered interactively (terminate with 'end'), or are read from a file.",
__dbg_cc_do, , __dbg_cx_do]
### end
		__dbg_g_cmd["end"] := ["end", END_CMD,
"	end\n_
Terminates a macro definition.",
__dbg_cc_end, , __dbg_cx_NOOP]
### eprint
		__dbg_g_cmd["eprint"] := ["eprint", EPRINT_CMD,
"	eprint expr\n_
Prints image of every value generated by expression 'expr'.",
__dbg_cc_eprint, , __dbg_cx_eprint]
### fprint
		__dbg_g_cmd["fprint"] := ["fprint", FPRINT_CMD,
"	fprint format-expr {; expr}\n_
Formatted print. The first expression must evaluate to a format string,\n_
possibly containing placeholders (%1, %2, etc). The result of evaluating\n_
remaining expressions will be substituted for the placeholders. You must\n_
make sure their values are string-convertible (the 'image' function is\n_
available). Insert '\\n' in format string to obtain newline.",
__dbg_cc_print, , __dbg_cx_fprint]
### frame
		__dbg_g_cmd["frame"] := ["frame", FRAME_CMD,
"	frame [n]\n_
Shows a call frame. 'n' may be an integer frame number (obtained from\n_
the 'where' command), or may be omitted. Omitted frame number = current\n_
procedure. Negative frame number is relative to the current procedure.\n_
The command prints the image of all local variables.",
__dbg_cc_frame, , __dbg_cx_frame]
### goon
		__dbg_g_cmd["goon"] := ["goon", GOON_CMD,
"	goon [nobreak]\n_
Resumes execution. With 'nobreak': lets the program run to completion\n_
without breaking.",
__dbg_cc_goon, , __dbg_cx_goon]
### help
		__dbg_g_cmd["help"] := ["help", HELP_CMD,
"	help [command]\n_
Displays information. Prints short command description if command keyword\n_
is included. Otherwise prints list of available commands.",
__dbg_cc_help, , __dbg_cx_help]
### ignore
		__dbg_g_cmd["ignore"] := ["ignore", IGNORE_CMD,
"	ignore brkpt count\n_
Sets the ignore counter of breakpoint 'brkpt'. 'count' may be a positive\n_
or negative integer. It replaces the previous ignore counter value.\n_
A breakpoint with a non-zero ignore count does not cause a break, but the\n_
ignore count is decremented by 1.",
__dbg_cc_ignore, , __dbg_cx_ignore]
### info
		__dbg_g_cmd["info"] := ["info", INFO_CMD,
"	info breakpoint [brkpt]\n_
Prints info about breakpoint identified by 'brkpt', or about all\n_
breakpoints if 'brkpt' is omitted.\n_
\	info echo\n_
Prints the current 'echo' file name, if any.\n_
\	info files\n_
Prints names of source files with tweaked ucode in this program.\n_
\	info globals [substr]\n_
Prints names of global variables. The optional substring limits output\n_
to global names containing this substring.\n_
\	info locals\n_
Prints names of all local variables in current procedure.\n_
\	info macros\n_
Prints names of all currently defined macros.\n_
\	info trace\n_
Prints the current value of &trace.\n_
\	info version\n_
Prints itweak and runtime versions.",
__dbg_cc_info, , __dbg_cx_info]
### macro
		__dbg_g_cmd["macro"] := ["macro", MACRO_CMD,
"	macro name\n_
Creates a new command called 'name'. The command will consist of\n_
subsequent lines, up to a line containing 'end'.\n_
\	macro name <filename\n_
As above, but macro definition read from a file. 'end' command optional.",
__dbg_cc_macro, , __dbg_cx_macro]
### next
		__dbg_g_cmd["next"] := ["next", NEXT_CMD,
"	next [count]\n_
Resumes execution as if a breakpoint were defined on every line. An\n_
ignore count may be included (see the 'ignore' command). A break\n_
caused by 'next' is considered breakpoint 0 (zero), even if an\n_
ordinary breakpoint is in effect on the same line. The 'condition',\n_
'do', 'info' commands accept 0 as a breakpoint number.",
__dbg_cc_next, , __dbg_cx_next]
### print
		__dbg_g_cmd["print"] := ["print", PRINT_CMD,
"	print expr {; expr}\n_
Evaluates and print image of expression(s). Only the first value from\n_
each expression is printed. '&fail' printed if an expression fails.",
__dbg_cc_print, , __dbg_cx_print]
### set
		__dbg_g_cmd["set"] := ["set", SET_CMD,
"	set echo filename\n_
Starts echoing output to a file.\n_
\	set prelude [<file]\n_
Defines a macro to be exeucted at breaks. The default prelude is\n_
\	fprint \"[%1] %2 (%3:%4)\\n\";&bp;&proc;&file;&line\n_
It prints breakpoint number, procedure name, source file name, and\n_
line number.\n_
\	set postlude [<file]\n_
Defines a macro to be executed when resuming execution. The default\n_
postlude does nothing.",
__dbg_cc_set, , __dbg_cx_set]
### source
		__dbg_g_cmd["source"] := ["source", SOURCE_CMD,
"	source filename\n_
Reads commands from a file. Takes effect immediately when used in a macro\n_
definition.",
__dbg_cc_source, , __dbg_cx_NOOP]
### stop
		__dbg_g_cmd["stop"] := ["stop", STOP_CMD,
"	stop\n_
Stops the program and terminates the debugging session.",
__dbg_cc_SIMPLE, , __dbg_cx_stop]
### trace
		__dbg_g_cmd["trace"] := ["trace", TRACE_CMD,
"	trace count\n_
Sets the value of the Icon trace counter (&trace) to 'count'.",
__dbg_cc_trace, , __dbg_cx_trace]
### where
		__dbg_g_cmd["where"] := ["where", WHERE_CMD,
"	where\n_
Prints the call chain leading up to the current procedure.\n_
Displays frame numbers which may be used by the 'frame' command.",
__dbg_cc_SIMPLE, , __dbg_cx_where]
end

############### EXPRESSIONS ##############################
#
# Parses a fair subset of Icon expressions.
# Compiles them into a linear post-fix representation.
# Evaluates.
# Somewhat adapted to the debugging environment, but
# generally useful with small modifications.
#
##########################################################

#
#-------------- Expression management constants ----------
#

$define IDENT_T		1
$define INTEGER_T	2
$define STRING_T	3
$define SPECIAL_T	4
$define FIELD_T		5
$define LIST_T		6
$define EXPR_T		8
$define ELIST_T		9
$define UNOP_T		10
$define BINOP_T		11
$define TEROP_T		12
$define INVOKE_T	13

$define NOTN_OP		901
$define ISN_OP		902
$define SIZ_OP		903
$define BNG_OP		904
$define NEG_OP		905

$define ALT_OP		1501
$define CNJ_OP		1401
# N -- numerical comparison.
$define NEQ_OP		1301
$define NNE_OP		1302
$define NLE_OP		1303
$define NLT_OP		1304
$define NGE_OP		1305
$define NGT_OP		1306
# L -- lexical comparison.
$define LLT_OP		1307
$define LLE_OP		1308
$define LEQ_OP		1309
$define LNE_OP		1310
$define LGE_OP		1311
$define LGT_OP		1312
$define EQ_OP		1313
$define NE_OP		1314
$define ADD_OP		1201
$define SUBTR_OP	1202
$define UNION_OP	1203
$define DIFF_OP		1204
$define CAT_OP		1101
$define LCAT_OP		1102
$define MUL_OP		1001
$define DIV_OP		1002
$define REM_OP		1003
$define ISCT_OP		1004
$define EXP_OP		1001
$define INVOKE_OP	801
$define SSC_OP		802
$define PART_OP		803
$define FLD_OP		804

$define CLOCK_SP	1
$define CURRENT_SP	2
$define DATE_SP		3
$define DATELINE_SP	4
$define POS_SP		5
$define REGIONS_SP	6
$define SOURCE_SP	7
$define STORAGE_SP	8
$define SUBJECT_SP	9
$define VERSION_SP	10

$define BREAK_SP	101
$define FILE_SP		102
$define LEVEL_SP	103
$define LINE_SP		104
$define PROC_SP		105
$define TRACE_SP	106

#
#-------------- Expression parsing ----------------------		
#

procedure __dbg_e_compile (str)
# Compiles one or more expressions separated by a semicolon.
# 'str' must be the candidate expression (string).
# RETURNS a list of lists where each sublist has the following components:
# (1) The compiled expression in postfix representation (list).
# This representation can be used with the '__dbg_e_eval' procedure.
# (2) The expression source string.
# FAILS on conflict.
# SIDE EFFECT: Assigns a message (string) to '__dbg_ge_message' on conflict;
# assigns &null otherwise.
local elist, res1, res2, pos1, pos2
	elist := []
	# Parse the expression(s).
	str ? repeat {
		pos1 := &pos
		(res1 := 1(__dbg_e_expr(), pos2:= &pos, __dbg_e_ws (),
			(__dbg_fpos (0) | __dbg_fany (';')))) | {
			__dbg_ge_message := "Expression syntax error."
			fail
			}
		# Linearize, convert to postfix.
		__dbg_ge_message := &null
		res2 := []
		__dbg_e_ecode (res1, res2)
		# Check for conflict.
		/__dbg_ge_message | fail
		__dbg_fput (elist, [res2, str[pos1:pos2]])
		if __dbg_fpos (0) then
			break
		else {
			__dbg_fmove (1)
			__dbg_e_ws ()
			}
		}
	return elist
end

procedure __dbg_e_expr()
	__dbg_ftab (__dbg_fmany (' \t'))
	suspend [__dbg_e_term()] |
 		([__dbg_e_term(), __dbg_e_bin()] ||| __dbg_e_expr())
end

procedure __dbg_e_term()
	__dbg_ftab (__dbg_fmany (' \t'))
	suspend [__dbg_e_factor()] |
		[__dbg_e_factor(), __dbg_e_form()] |
		[__dbg_e_un(), __dbg_e_factor()] |
		[__dbg_e_un(), __dbg_e_factor(), __dbg_e_form()]
end
	
procedure __dbg_e_form()
	__dbg_ftab (__dbg_fmany (' \t'))
	suspend 2(=".", [FLD_OP, [FIELD_T, __dbg_e_idf()]]) |
		2(="[", [SSC_OP, __dbg_e_expr()], ="]") |
		2(="(", [INVOKE_OP, __dbg_e_elist()], =")") |
		2(="[", [PART_OP, __dbg_e_expr(),
			3(__dbg_e_ws(), =":", __dbg_e_expr())], ="]") |
 		(2(=".", [FLD_OP, [FIELD_T, __dbg_e_idf()]]) ||| __dbg_e_form()) |
		(2(="[", [SSC_OP, __dbg_e_expr()], ="]") ||| __dbg_e_form()) |
		(2(="(", [INVOKE_OP, __dbg_e_elist()], =")") ||| __dbg_e_form()) |
		(2(="[", [PART_OP, __dbg_e_expr(),
			3(__dbg_e_ws(), =":", __dbg_e_expr())], ="]") |||
			__dbg_e_form())
end

procedure __dbg_e_elist()
	__dbg_ftab (__dbg_fmany (' \t'))
	suspend [] |
		[__dbg_e_expr()] |
		[__dbg_e_expr()] ||| 3(__dbg_e_ws(), =",", __dbg_e_elist())
end

procedure __dbg_e_factor()
	__dbg_ftab (__dbg_fmany (' \t'))
	suspend [IDENT_T, __dbg_e_idf()] |
		[INTEGER_T, __dbg_e_ilit()] |
		[STRING_T, __dbg_e_slit()] |
		[SPECIAL_T, (="&", __dbg_e_idf())] |
		2(="(", [EXPR_T, __dbg_e_expr()], __dbg_e_ws(), =")") |
		2(="[", [LIST_T, __dbg_e_elist()], __dbg_e_ws(), ="]")
end

procedure __dbg_e_idf()
static char1, char2
initial {
	char1 := &ucase ++ &lcase ++ '_'
	char2 := char1 ++ &digits
	}
	suspend __dbg_ftab (__dbg_fmany (char1)) || (__dbg_ftab (__dbg_fmany (char2)) | "")
end

procedure __dbg_e_ilit()
	suspend __dbg_ftab (__dbg_fmany (&digits))
end

procedure __dbg_e_strend()
static signal, nonsignal
initial {
	signal := '\"\\'
	nonsignal := ~signal
	}
	suspend 2(="\"", "") |
		1(__dbg_e_stresc(), ="\"") |
		(__dbg_e_stresc() || __dbg_ftab (__dbg_fmany (nonsignal)) || __dbg_e_strend()) |
		(__dbg_e_stresc() || __dbg_e_strend())
end

procedure __dbg_e_stresc()
	suspend (="\\n", "\n") |
		(="\\t", "\t") |
		(="\\r", "\r") |
		(="\\", __dbg_fmove (1))
end

procedure __dbg_e_slit()
static signal, nonsignal
initial {
	signal := '\"\\'
	nonsignal := ~signal
	}
	suspend 2(="\"",
		(__dbg_ftab (__dbg_fmany (nonsignal)) || __dbg_e_strend()) | __dbg_e_strend())
end

procedure __dbg_e_un()
# Sequence of unary operators.
# Always succeeds.
# NOTE: Assumes no space between operators.
static unop
initial unop := '\\/*!-'
	__dbg_ftab (__dbg_fmany (' \t'))
	suspend [UNOP_T, __dbg_ftab (__dbg_fmany (unop))]
end

procedure __dbg_e_bin()
# Binary operators.
static optab
initial {
	# Table of operators.
	# Operators are coded as decimal integers where the hundreds
	# digit defines precedence.
	optab := table()
	optab["|"] :=		ALT_OP
	optab["&"] :=		CNJ_OP
	optab["="] :=		NEQ_OP
	optab["~="] :=		NNE_OP
	optab["<="] :=		NLE_OP
	optab["<"] :=		NLT_OP
	optab[">="] :=		NGE_OP
	optab[">"] :=		NGT_OP
	optab["<<"] :=		LLT_OP
	optab["<<="] :=		LLE_OP
	optab["=="] :=		LEQ_OP
	optab["~=="] :=		LNE_OP
	optab[">>="] :=		LGE_OP
	optab[">>"] :=		LGT_OP
	optab["==="] :=		EQ_OP
	optab["~==="] :=	NE_OP
	optab["+"] :=		ADD_OP
	optab["-"] :=		SUBTR_OP
	optab["++"] :=		UNION_OP
	optab["--"] :=		DIFF_OP
	optab["||"] :=		CAT_OP
	optab["|||"] :=		LCAT_OP
	optab["*"] :=		MUL_OP
	optab["/"] :=		DIV_OP
	optab["%"] :=		REM_OP
	optab["**"] :=		ISCT_OP
	optab["^"] :=		EXP_OP
	}
	__dbg_ftab (__dbg_fmany (' \t'))
	suspend \optab[__dbg_fmove (3)] |
		\optab[__dbg_fmove (2)] |
		\optab[__dbg_fmove (1)] |
		\optab[=("~===")]
end

procedure __dbg_e_ws()
# Removes optional white space.
# The point is that it always succeeds.
	__dbg_ftab (__dbg_fmany (' \t'))
	return 1
end

#-------------- Linearization ----------------------		

procedure __dbg_e_ecode (ex, res)
# 'Evaluates' the list resulting from pattern matching.
# Produces a single list with everything in postfix order.
# 'ex' must be an expression in the form that '__dbg_e_compile' generates.
# 'res' must be an (empty) list where the expression elements are to
# be inserted.
# Always FAILS.
# SIDE EFFECT: Adds elements to 'res'.
# Assigns a message string to '__dbg_ge_message' on conflict.
local opnd, oprt, op_stack
	if *ex = 1 then
		__dbg_e_tcode (ex[1], res)
	else {
		op_stack := []
		opnd := create !ex
		__dbg_e_tcode (@opnd, res)
		while oprt := @opnd do {
			while (op_stack[1]/100) <= (oprt/100) do
				__dbg_fput (res, __dbg_e_proc ([BINOP_T, __dbg_fpop (op_stack),]))
			__dbg_fpush (op_stack, oprt)
			__dbg_e_tcode (@opnd, res)
			}
		while __dbg_fput (res, __dbg_e_proc ([BINOP_T, __dbg_fpop (op_stack),]))
		}
end

procedure __dbg_e_tcode (tm, res)
# Disentangles a term.
local comp, unary
static special, unop
initial {
	special := __dbg_ftable ()
	# The 'normal' keywords.
	special["clock"] :=	CLOCK_SP
	special["current"] :=	CURRENT_SP
	special["date"] :=	DATE_SP
	special["dateline"] :=	DATELINE_SP
	special["pos"] :=	POS_SP
	special["regions"] :=	REGIONS_SP
	special["source"] :=	SOURCE_SP
	special["storage"] :=	STORAGE_SP
	special["subject"] :=	SUBJECT_SP
	special["trace"] :=	TRACE_SP
	special["version"] :=	VERSION_SP
	
	# The special keywords.
	special["bp"] :=BREAK_SP
	special["breakpoint"] :=BREAK_SP
	special["file"]	:=	FILE_SP
	special["level"] :=	LEVEL_SP
	special["line"] :=	LINE_SP
	special["proc"] :=	PROC_SP

	unop := __dbg_ftable ()
	unop["\\"] :=	NOTN_OP
	unop["/"] :=	ISN_OP
	unop["*"] :=	SIZ_OP
	unop["!"] :=	BNG_OP
	unop["-"] :=	NEG_OP
	}
	every comp := !tm do case comp[1] of {
	UNOP_T:	unary := comp	# Save for later.
	INTEGER_T: {
		comp[2] := __dbg_finteger (comp[2])
		__dbg_fput (res, comp)
		}
	SPECIAL_T: {
		if comp[2] := \special[comp[2]] then
			__dbg_fput (res, comp)
		else
			__dbg_ge_message := "'" || comp[2] ||
				"': unrecognized special identifier."
		}
	EXPR_T:		__dbg_e_ecode (comp[2], res)
	LIST_T:	{
		every __dbg_e_ecode (!comp[2], res)
		__dbg_fput (res, [LIST_T, *comp[2]])
		}
	(FLD_OP | SSC_OP | INVOKE_OP | PART_OP) :
		__dbg_e_fcode (comp, res)
	default:	__dbg_fput (res, comp)
	# This includes: IDENT_T, STRING_T
	}
	every __dbg_fput (res, __dbg_e_proc ([UNOP_T, unop[!__dbg_freverse ((\unary)[2])],]))
end

procedure __dbg_e_fcode (fm, res)
# Disentangles a form.
# The operators have the same precedence; stack not needed.
local comp, opnd, oprt
	comp := create !fm
	while oprt := @comp do {
		opnd := @comp	# There is at least one operand.
		case oprt of {
		FLD_OP:	{
			__dbg_fput (res, opnd)
			__dbg_fput (res, [BINOP_T, oprt, __dbg_e_field])
		}
		SSC_OP:	{
			__dbg_e_ecode (opnd, res)
			__dbg_fput (res, [BINOP_T, oprt, __dbg_fproc ("[]", 2)])
		}
		INVOKE_OP: {
			every __dbg_e_ecode (!opnd, res)
			__dbg_fput (res, [INVOKE_T, *opnd])
		}
		PART_OP: {
			__dbg_e_ecode (opnd, res)
			__dbg_e_ecode (@comp, res)
			__dbg_fput (res, [TEROP_T, oprt, __dbg_fproc ("[:]", 3)])
		}
		default: __dbg_ge_message := __dbg_fimage (oprt) || ": weird operator."
		}
		}
end

procedure __dbg_e_proc (op_d)
# 'op_d' must be an operator descriptor (list(3)).
# RETURNS the descriptor with the 3rd component filled in by a
# procedure/function.
static opt
initial {
	opt := __dbg_ftable ()
	opt[NOTN_OP] :=		__dbg_fproc ("\\", 1)
	opt[ISN_OP] :=		__dbg_fproc ("/", 1)
	opt[SIZ_OP] :=		__dbg_fproc ("*", 1)
	opt[BNG_OP] :=		__dbg_fproc ("!", 1)
	opt[NEG_OP] :=		__dbg_fproc ("-", 1)
	opt[ALT_OP] :=		__dbg_e_alt
	opt[CNJ_OP] :=		__dbg_e_cnj
	opt[NEQ_OP] :=		__dbg_fproc ("=", 2)
	opt[NNE_OP] :=		__dbg_fproc ("~=", 2)
	opt[NLE_OP] :=		__dbg_fproc ("<=", 2)
	opt[NLT_OP] :=		__dbg_fproc ("<", 2)
	opt[NGE_OP] :=		__dbg_fproc (">=", 2)
	opt[NGT_OP] :=		__dbg_fproc (">", 2)
	opt[LLT_OP] :=		__dbg_fproc ("<<", 2)
	opt[LLE_OP] :=		__dbg_fproc ("<<=", 2)
	opt[LEQ_OP] :=		__dbg_fproc ("==", 2)
	opt[LNE_OP] :=		__dbg_fproc ("~==", 2)
	opt[LGE_OP] :=		__dbg_fproc (">>=", 2)
	opt[LGT_OP] :=		__dbg_fproc (">>", 2)
	opt[EQ_OP] :=		__dbg_fproc ("===", 2)
	opt[NE_OP] :=		__dbg_fproc ("~===", 2)
	opt[ADD_OP] :=		__dbg_fproc ("+", 2)
	opt[SUBTR_OP] :=	__dbg_fproc ("-", 2)
	opt[UNION_OP] :=	__dbg_fproc ("++", 2)
	opt[DIFF_OP] :=		__dbg_fproc ("--", 2)
	opt[CAT_OP] :=		__dbg_fproc ("||", 2)
	opt[LCAT_OP] :=		__dbg_fproc ("|||", 2)
	opt[MUL_OP] :=		__dbg_fproc ("*", 2)
	opt[DIV_OP] :=		__dbg_fproc ("/", 2)
	opt[REM_OP] :=		__dbg_fproc ("%", 2)
	opt[ISCT_OP] :=		__dbg_fproc ("**", 2)
	opt[EXP_OP] :=		__dbg_fproc ("^", 2)
	opt[SSC_OP] :=		__dbg_fproc ("[]", 2)
	opt[PART_OP] :=		__dbg_fproc ("[:]", 2)
	opt[FLD_OP] :=		__dbg_e_field
	}
	op_d[3] := opt[op_d[2]]
	return op_d
end

#-------------- Evaluation ----------------------		

procedure __dbg_e_eval (expr)
# Evaluates a compiled expression.
# 'expr' must be an expression using the representation created by
# '__dbg_e_compile' (list).
# GENERATES all expression values.
# SIDE EFFECT: Assigns a message (string) to '__dbg_ge_message' on conflict;
# assigns &null otherwise.
local val
	__dbg_ge_message := &null
	&error := -1
	every val := __dbg_e_eval1 (expr, []) do {
		&error := 0
		suspend val
		__dbg_ge_message := &null
		&error := -1
		}
	if &error < -1 then
		__dbg_ge_message := "Error number " || &errornumber || ": " ||
		&errortext || "." ||
		(("\nOffending value: " || __dbg_fimage (\&errorvalue) || ".") | "")
	&error := 0
end

procedure __dbg_e_alt (opnd1, opnd2)
# Our version of alternation.
	suspend (opnd1 | opnd2)
end

procedure __dbg_e_cnj (opnd1, opnd2)
# Our version of conjunction.
	suspend (opnd1 & opnd2)
end

procedure __dbg_e_field (opnd1, opnd2)
# Record field access.
# Any better way to determine if a value is a record of any type?
static builtin
initial {
	builtin := __dbg_ftable ()
	builtin["co-expression"] := 1
	builtin["cset"] := 1
	builtin["file"] := 1
	builtin["integer"] := 1
	builtin["list"] := 1
	builtin["null"] := 1
	builtin["procedure"] := 1
	builtin["real"] := 1
	builtin["set"] := 1
	builtin["string"] := 1
	builtin["table"] := 1
	}
	if \builtin[__dbg_ftype (opnd1)] then {
		__dbg_ge_message := "Record expected; found " || __dbg_fimage (opnd1)
		fail
		}
	suspend opnd1[opnd2]
end

procedure __dbg_e_ident (idf)
# Evaluates an identifier.
local val
	(val := ((__dbg_ge_singular ~=== __dbg_g_local[idf]) | variable (idf))) | {
		__dbg_ge_message := "Identifier '" || idf || "' not visible."
		fail
		}
	suspend val
end

procedure __dbg_e_special (sp_code)
# Evaluates a special identifier.
	suspend case sp_code of {
	# Regular Icon keyword variables.
	CLOCK_SP:	&clock
	CURRENT_SP:	&current
	DATE_SP:	&date
	DATELINE_SP:	&dateline
	POS_SP:		&pos
	REGIONS_SP:	&regions
	SOURCE_SP:	&source
	STORAGE_SP:	&storage
	SUBJECT_SP:	&subject
	VERSION_SP:	&version
	# Special keywords.
	BREAK_SP:	(\__dbg_g_where[WHERE_BRKP])[BRKP_ID]
	FILE_SP:	__dbg_g_where[WHERE_FILE]
	LEVEL_SP:	__dbg_g_level
	LINE_SP:	__dbg_g_where[WHERE_LINE]
	PROC_SP:	__dbg_g_where[WHERE_PROC]
	TRACE_SP:	__dbg_g_trace
	default: {
		__dbg_ge_message := __dbg_fimage (sp_code) ||
			": weird special identifier code."
		fail
		}
	}
end

procedure __dbg_e_eval1 (expr, stack)
# Evaluates an expression.
# 'stack' must be the current evaluation stack (list).
# The procedure is recursive; the initial invocation must supply an
# empty list.
local comp
	(comp := expr[1]) | while suspend __dbg_fpop (stack) | fail
	suspend __dbg_e_eval1 (expr[2:0], case comp[1] of {
	IDENT_T:	stack ||| [__dbg_e_ident (comp[2])]
	SPECIAL_T:	stack ||| [__dbg_e_special (comp[2])]
	LIST_T:		stack[1:-comp[2]] ||| [stack[-comp[2]:0]]
	UNOP_T:		stack[1:-1] ||| [comp[3](stack[-1])]
	BINOP_T:	stack[1:-2] ||| [comp[3]!stack[-2:0]]
	TEROP_T:	stack[1:-3] ||| [comp[3]!stack[-3:0]]
	INVOKE_T:	stack[1:-(comp[2]+1)] |||
				[stack[-(comp[2]+1)]!stack[-comp[2]:0]]
	default:	stack ||| [comp[2]]
	})
end
