/* Basic ops on symbols.
 */

/*

	Copyright (C) 1991-2003 The National Gallery

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the Free Software Foundation, Inc.,
	51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

 */

/*

	These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

#include "nip4.h"

/* All debug
#define DEBUG
 */

/* Just trace create/destroy.
#define DEBUG_MAKE
 */

/* Time recomputes.
#define DEBUG_TIME
 */

/* Show symbols as we recalc
#define DEBUG_RECALC
 */

/* If DEBUG is on, make sure other debugs are on too.
 */
#ifdef DEBUG
#ifndef DEBUG_MAKE
#define DEBUG_MAKE
#endif
#ifndef DEBUG_TIME
#define DEBUG_TIME
#endif
#ifndef DEBUG_RECALC
#define DEBUG_RECALC
#endif
#endif

/* Our signals.
 */
enum {
	SIG_NEW_VALUE, /* new value for sym->expr */
	SIG_LAST
};

G_DEFINE_TYPE(Symbol, symbol, FILEMODEL_TYPE)

static guint symbol_signals[SIG_LAST] = { 0 };

/* Global symbol - top-level definitions are locals to this symbol.
 */
Symbol *symbol_root = NULL;

/* Set of dirty top-level symbols with no dirty children which do not contain
 * errors. Used to generate next-to-recalc.
 */
static GSList *symbol_leaf_set = NULL;

/* Apply a function to a symbol ... and any locals.
 */
Symbol *
symbol_map_all(Symbol *sym, symbol_map_fn fn, void *a, void *b)
{
	Symbol *res;

	/* Apply to this sym.
	 */
	if ((res = fn(sym, a, b, NULL)))
		return res;

	/* And over any locals of those locals.
	 */
	if (sym->expr && sym->expr->compile &&
		(res = icontainer_map3(ICONTAINER(sym->expr->compile),
			 (icontainer_map3_fn) symbol_map_all,
			 (void *) fn, a, b)))
		return res;

	return NULL;
}

/* Find a symbol's enclosing sym.
 */
Symbol *
symbol_get_parent(Symbol *sym)
{
	if (!ICONTAINER(sym)->parent)
		return NULL;

	return COMPILE(ICONTAINER(sym)->parent)->sym;
}

/* Find the enclosing workspace, if any.
 */
Workspace *
symbol_get_workspace(Symbol *sym)
{
	if (!sym->expr || !sym->expr->row)
		return NULL;

	return row_get_workspace(sym->expr->row);
}

/* Find the enclosing tool, if any.
 */
Tool *
symbol_get_tool(Symbol *sym)
{
	Symbol *i;

	for (i = sym; i && !i->tool; i = symbol_get_parent(i))
		;
	if (i)
		return i->tool;

	return NULL;
}

/* Get the enclosing scope for a sym.
 */
Symbol *
symbol_get_scope(Symbol *sym)
{
	Symbol *i;

	for (i = sym; i && !is_scope(i); i = symbol_get_parent(i))
		;

	return i;
}

/* Get the enclosing top-level for a sym.
 */
Symbol *
symbol_get_top(Symbol *sym)
{
	Symbol *i;

	for (i = sym; i && !is_top(i); i = symbol_get_parent(i))
		;

	return i;
}

/* Return the final definition, if this symbol has multiple RHS.
 */
Symbol *
symbol_get_last(Symbol *sym)
{
	Symbol *last;

	for (last = sym; last; last = last->next_def)
		if (!last->next_def)
			break;

	return last;
}

/* Make a fully-qualified symbol name .. eg fred.jim, given jim. Don't print
 * static scopes.
 */
void
symbol_qualified_name(Symbol *sym, VipsBuf *buf)
{
	Symbol *parent = symbol_get_parent(sym);

	if (parent && !is_scope(parent)) {
		symbol_qualified_name(parent, buf);
		vips_buf_appends(buf, ".");
	}

	vips_buf_appends(buf, IOBJECT(sym)->name);
}

/* Make a symbol name relative to a scope context ... ie. from the point of
 * view of a local of context, what name will find sym.
 */
void
symbol_qualified_name_relative(Symbol *context, Symbol *sym, VipsBuf *buf)
{
	Symbol *parent = symbol_get_parent(sym);

	if (parent && !is_ancestor(context, parent)) {
		symbol_qualified_name_relative(context, parent, buf);
		vips_buf_appends(buf, ".");
	}

	vips_buf_appends(buf, IOBJECT(sym)->name);
}

/* As above, but include stuff about where the symbol is defined, handy for
 * building error messages.
 */
void *
symbol_name_error(Symbol *sym, VipsBuf *buf)
{
	Tool *tool;

	symbol_qualified_name(sym, buf);
	if ((tool = symbol_get_tool(sym)))
		tool_error(tool, buf);

	vips_buf_appends(buf, " ");

	return NULL;
}

/* Handy for error messages ... but nowt else. Return string overwritten on
 * next call.
 */
const char *
symbol_name(Symbol *sym)
{
	static char txt[200];
	static VipsBuf buf = VIPS_BUF_STATIC(txt);

	vips_buf_rewind(&buf);
	symbol_qualified_name(sym, &buf);

	return vips_buf_all(&buf);
}

/* Convenience ... print a qual name to stdout.
 */
void *
symbol_name_print(Symbol *sym)
{
	printf("%s ", symbol_name(sym));

	return NULL;
}

/* Print a symbol's name, including the enclosing static scope. Return value
 * is a pointer to a static buffer :(
 */
const char *
symbol_name_scope(Symbol *sym)
{
	Symbol *scope = symbol_get_scope(sym);

	static char txt[200];
	static VipsBuf buf = VIPS_BUF_STATIC(txt);

	vips_buf_rewind(&buf);
	vips_buf_appends(&buf, IOBJECT(scope)->name);
	vips_buf_appends(&buf, ".");
	symbol_qualified_name_relative(scope, sym, &buf);

	return vips_buf_all(&buf);
}

/* Convenience ... print a qual name to stdout.
 */
void
symbol_name_scope_print(Symbol *sym)
{
	printf("%s", symbol_name_scope(sym));
}

void
symbol_new_value(Symbol *sym)
{
	g_signal_emit(G_OBJECT(sym), symbol_signals[SIG_NEW_VALUE], 0);
}

/* Add a pointer to a patch list.
 */
void *
symbol_patch_add(void **pnt, Symbol *sym)
{
	g_assert(sym->type == SYM_ZOMBIE);

	sym->patch = g_slist_prepend(sym->patch, pnt);

	return NULL;
}

static void
symbol_clear(Symbol *sym)
{
	sym->type = SYM_ZOMBIE;

	sym->patch = NULL;

	sym->expr = NULL;

	sym->base.type = ELEMENT_NOVAL;
	sym->base.ele = (void *) 15; /* handy for debugging */

	sym->dirty = FALSE;
	sym->parents = NULL;

	sym->topchildren = NULL;
	sym->topparents = NULL;
	sym->ndirtychildren = 0;
	sym->leaf = FALSE;

	sym->generated = FALSE;

	sym->tool = NULL;

	sym->builtin = NULL;

	sym->wsr = NULL;

	sym->ws = NULL;
}

/* Initialise root symbol.
 */
Symbol *
symbol_root_init(void)
{
	Symbol *root = SYMBOL(g_object_new(SYMBOL_TYPE, NULL));

	symbol_clear(root);
	iobject_set(IOBJECT(root), "$$ROOT", NULL);
	root->type = SYM_ROOT;
	root->expr = expr_new(root);
	(void) compile_new_local(root->expr);

	symbol_root = symbol_new(root->expr->compile, "root");
	symbol_root->type = SYM_ROOT;
	symbol_root->expr = expr_new(symbol_root);
	(void) compile_new(symbol_root->expr);

	return root;
}

/* Should a symbol be in the leaf set?
 */
static gboolean
symbol_is_leafable(Symbol *sym)
{
	if (is_top(sym) &&
		sym->dirty &&
		sym->expr &&
		!sym->expr->err &&
		sym->ndirtychildren == 0)
		return TRUE;

	return FALSE;
}

#ifdef DEBUG
/* Do a sanity check on a symbol.
 */
void *
symbol_sanity(Symbol *sym)
{
	if (sym->dirty) {
		printf("symbol_sanity: %s dirty, %d dirty children\n",
				symbol_name(sym), sym->ndirtychildren);

	}

	if (is_top(sym)) {
		if (symbol_ndirty(sym) != sym->ndirtychildren)
			error("sanity failure #1 for sym \"%s\"", symbol_name(sym));
	}

	if (symbol_is_leafable(sym) && !sym->leaf)
		error("sanity failure #2 for sym \"%s\"", symbol_name(sym));
	if (!symbol_is_leafable(sym) && sym->leaf)
		error("sanity failure #3 for sym \"%s\"", symbol_name(sym));
	if (sym->leaf && !g_slist_find(symbol_leaf_set, sym))
		error("sanity failure #6 for sym \"%s\"", symbol_name(sym));
	if (!sym->leaf && g_slist_find(symbol_leaf_set, sym))
		error("sanity failure #7 for sym \"%s\"", symbol_name(sym));

	return NULL;
}
#endif /*DEBUG*/

#ifdef DEBUG
/* Test the leaf set for sanity.
 */
void
symbol_leaf_set_sanity(void)
{
	slist_map(symbol_leaf_set, (SListMapFn) symbol_sanity, NULL);
	if (symbol_root->expr->compile)
		icontainer_map(ICONTAINER(symbol_root->expr->compile),
			(icontainer_map_fn) symbol_sanity, NULL, NULL);

	/* Commented out to reduce spam
	printf("Leaf set: ");
	slist_map(symbol_leaf_set, (SListMapFn) dump_tiny, NULL);
	printf("\n");
	 */
}
#endif /*DEBUG*/

#ifdef DEBUG_RECALC
void *
symbol_dirty_print(Symbol *sym, int *indent)
{
	/*
	if (g_str_equal(IOBJECT(sym)->name, "main")) {
		printf("checking main!!\n");
		int indent = 0;
		dump_symbol(sym, &indent);
	}
	 */

	if (sym->dirty) {
		printf("%*c", *indent, ' ');
		printf("%s dirty, %d dirty children\n",
			symbol_name_scope(sym), sym->ndirtychildren);

		printf("%*c", *indent + 2, ' ');
		printf("(");
		for (GSList *p = sym->topchildren; p; p = p->next) {
			Link *link = (Link *) (p->data);

			if (link->child->dirty)
				printf("*");
			printf("%s, ", symbol_name_scope(link->child));
		}
		printf(")\n");
	}

	*indent += 2;

	if (sym->expr &&
		sym->expr->compile)
		icontainer_map(ICONTAINER(sym->expr->compile),
			(icontainer_map_fn) symbol_dirty_print, indent, NULL);

	*indent -= 2;

	return NULL;
}

static void
symbol_dirties(void)
{
	int indent = 0;

	printf("symbol_dirties:\n");
	icontainer_map(ICONTAINER(symbol_root->expr->compile),
		(icontainer_map_fn) symbol_dirty_print, &indent, NULL);
}
#endif /*DEBUG_RECALC*/

/* Strip a symbol down, ready for redefinition.
 */
void *
symbol_strip(Symbol *sym)
{
#ifdef DEBUG_MAKE
	printf("symbol_strip: ");
	symbol_name_print(sym);
	printf("\n");
#endif /*DEBUG_MAKE*/

	/* Anything that refers to us will need a recomp.
	 */
	if (is_top(sym))
		symbol_dirty_intrans(sym, link_serial_new());

	/* Clean out old exprinfo.
	 */
	icontainer_map(ICONTAINER(sym),
		(icontainer_map_fn) expr_strip, NULL, NULL);

	/* Free any top-links we made.
	 */
	(void) slist_map(sym->topchildren, (SListMapFn) link_destroy, NULL);

	/* Can free the patch list. We should not have to resolve off this
	 * name again.
	 */
	VIPS_FREEF(g_slist_free, sym->patch);

	/* Workspaceroot? Unlink from wsr.
	 */
	if (sym->wsr) {
		sym->wsr->sym = NULL;
		sym->wsr = NULL;
	}

	/* Workspace? Unlink from ws.
	 */
	if (sym->ws) {
		sym->ws->sym = NULL;
		sym->ws = NULL;
	}

	sym->next_def = NULL;

	/* It's a ZOMBIE now.
	 */
	sym->type = SYM_ZOMBIE;

#ifdef DEBUG
	symbol_sanity(sym);
#endif /*DEBUG*/

	return NULL;
}

static void *
symbol_made_error_clear(Link *link)
{
	expr_error_clear(link->parent->expr);

	return NULL;
}

/* Finish creating a symbol. Sequence is: symbol_new(), specialise ZOMBIE
 * into a particular symbol type, symbol_made(). Do any final tidying up.
 */
void
symbol_made(Symbol *sym)
{
#ifdef DEBUG_MAKE
	printf("symbol_made: ");
	symbol_name_print(sym);
	printf("\n");
#endif /*DEBUG_MAKE*/

	if (is_top(sym)) {
		/* Remake all top-level dependencies.
		 */
		(void) symbol_link_build(sym);

		/* Clear error on every symbol that refs us, then mark dirty.
		 * This lets us replace refed-to syms cleanly.
		 */
		slist_map(sym->topparents,
			(SListMapFn) symbol_made_error_clear, NULL);

		/* Real dirrrrty.
		 */
		if (sym->expr)
			expr_dirty(sym->expr, link_serial_new());
	}

#ifdef DEBUG
	int indent = 0;
	dump_symbol(sym, &indent);
#endif /*DEBUG*/
}

static void *
symbol_not_defined_sub(Link *link, VipsBuf *buf)
{
	vips_buf_appends(buf, "\n\t");
	symbol_name_error(link->parent, buf);

	return NULL;
}

/* Make a "not defined" error message. Can be called before symbol is removed,
 * so don't assume it's a ZOMBIE.
 */
void
symbol_not_defined(Symbol *sym)
{
	char txt[1024];
	VipsBuf buf = VIPS_BUF_STATIC(txt);

	error_top(_("Not found"));
	vips_buf_appendf(&buf, _("symbol %s is not defined"), symbol_name(sym));
	if (sym->topparents) {
		vips_buf_appends(&buf, "\n");
		vips_buf_appendf(&buf, _("%s is referred to by"), symbol_name(sym));
		slist_map2(sym->topparents,
			(SListMap2Fn) symbol_not_defined_sub, &buf, NULL);
	}
	error_sub("%s", vips_buf_all(&buf));
}

/* Compile refers to sym, which is going ... mark compile as containing an
 * error.
 */
static void *
symbol_destroy_error(Compile *compile, Symbol *sym)
{
	// don't mess up the error buffer
	error_push();

	// not for placeholders or zombies ... they get removed during parse
	// and should not set error states
	if (sym->type != SYM_ZOMBIE &&
		!sym->placeholder) {
		symbol_not_defined(sym);
		compile_error_set(compile);
	}

	error_pop();

	return NULL;
}

static void
symbol_dispose(GObject *gobject)
{
	Symbol *sym;

	g_return_if_fail(gobject != NULL);
	g_return_if_fail(IS_SYMBOL(gobject));

	sym = SYMBOL(gobject);

#ifdef DEBUG_MAKE
	printf("symbol_dispose: ");
	symbol_name_print(sym);
	printf("(%p)\n", sym);
#endif /*DEBUG_MAKE*/

	/* Does our parent ref us as next_def? Clear it.
	 */
	if (sym->expr &&
		sym->expr->compile) {
		Compile *parent_compile = compile_get_parent(sym->expr->compile);

		if (parent_compile &&
			parent_compile->sym->next_def == sym)
			parent_compile->sym->next_def = NULL;
	}

	/* Clear state.
	 */
	if (is_top(sym)) {
		/* All stuff that depends on this sym is now dirty.
		 */
		symbol_dirty_intrans(sym, link_serial_new());

		/* This will knock this sym off the leaf set as well.
		 */
		symbol_dirty_clear(sym);
	}

	/* Any Compile which refers to us must have errors.
	 */
	(void) slist_map(sym->parents,
		(SListMapFn) symbol_destroy_error, sym);

	/* Strip it down.
	 */
	(void) symbol_strip(sym);
	IDESTROY(sym->tool);

	/* Remove links from any expr which refer to us.
	 */
	(void) slist_map(sym->parents, (SListMapFn) compile_link_break, sym);

	/* No other syms should have toplinks to us.
	 */
	(void) slist_map(sym->topparents, (SListMapFn) link_destroy, NULL);

	/* Unregister value with GC.
	 */
	reduce_unregister(sym);

	/* Free other stuff.
	 */
	sym->type = SYM_ZOMBIE;

	g_assert(!sym->tool);
	g_assert(!sym->parents);
	g_assert(!sym->topparents);
	g_assert(!sym->topchildren);

	VIPS_FREEF(g_slist_free, sym->patch);
	VIPS_FREEF(g_slist_free, sym->parents);

	G_OBJECT_CLASS(symbol_parent_class)->dispose(gobject);
}

static void
symbol_changed(iObject *iobject)
{
	Symbol *sym = SYMBOL(iobject);

	/* If we have a tool, signal changed on that as well.
	 */
	if (sym->tool)
		iobject_changed(IOBJECT(sym->tool));

	IOBJECT_CLASS(symbol_parent_class)->changed(iobject);
}

static void
symbol_real_new_value(Symbol *symbol)
{
}

static void
symbol_class_init(SymbolClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	iObjectClass *iobject_class = (iObjectClass *) class;

	gobject_class->dispose = symbol_dispose;

	iobject_class->changed = symbol_changed;

	symbol_signals[SIG_NEW_VALUE] = g_signal_new("new_value",
		G_OBJECT_CLASS_TYPE(gobject_class),
		G_SIGNAL_RUN_FIRST,
		G_STRUCT_OFFSET(SymbolClass, new_value),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	class->new_value = symbol_real_new_value;
}

static void
symbol_init(Symbol *sym)
{
	symbol_clear(sym);

#ifdef DEBUG_MAKE
	printf("symbol_init: (%p)\n", sym);
#endif /*DEBUG_MAKE*/
}

/* Make a new symbol on an expr. If it's already there and a ZOMBIE, just
 * return it. If it's not a ZOMBIE, turn it into one. Otherwise make and
 * link on a new symbol.
 */
Symbol *
symbol_new(Compile *compile, const char *name)
{
	Symbol *sym;

	if ((sym = compile_lookup(compile, name))) {
		if (sym->type != SYM_ZOMBIE)
			/* Already exists: strip it down.
			 */
			(void) symbol_strip(sym);

#ifdef DEBUG_MAKE
		printf("symbol_new: redefining ");
		symbol_name_print(sym);
		printf("(%p)\n", sym);
#endif /*DEBUG_MAKE*/
	}
	else {
		sym = SYMBOL(g_object_new(SYMBOL_TYPE, NULL));
		iobject_set(IOBJECT(sym), name, NULL);
		icontainer_child_add(ICONTAINER(compile),
			ICONTAINER(sym), -1);

#ifdef DEBUG_MAKE
		printf("symbol_new: creating ");
		symbol_name_print(sym);
		printf("(%p)\n", sym);
#endif /*DEBUG_MAKE*/
	}

	return sym;
}

gboolean
symbol_rename(Symbol *sym, const char *new_name)
{
	Compile *compile = COMPILE(ICONTAINER(sym)->parent);
	Symbol *old_sym;

	if (strcmp(IOBJECT(sym)->name, new_name) == 0)
		return TRUE;

	if ((old_sym = compile_lookup(compile, new_name))) {
		error_top(_("Name clash"));
		error_sub(_("can't rename %s \"%s\" as \"%s\", "
					"the name is already in use"),
			decode_SymbolType_user(sym->type),
			IOBJECT(sym)->name,
			new_name);
		return FALSE;
	}

	/* Everything that depends on us will break.
	 */
	symbol_dirty_intrans(sym, link_serial_new());

	g_object_ref(sym);

	icontainer_child_remove(ICONTAINER(sym));
	iobject_set(IOBJECT(sym), new_name, NULL);
	icontainer_child_add(ICONTAINER(compile), ICONTAINER(sym),
		ICONTAINER(sym)->pos);

	g_object_unref(sym);

	return TRUE;
}

extern Toolkit *current_kit;

/* Can we add a new def to a sym?
 */
static gboolean
symbol_can_add_def(Symbol *sym)
{
	/* The sym is in current_kit? it must be in the same parse unit, so a new
	 * def is OK
	 */
	if (sym->type == SYM_VALUE &&
		sym->tool &&
		sym->tool->kit == current_kit)
		return TRUE;

	/* Is this a local def? A second def is also OK, since we must still be
	 * parsing.
	 */
	if (sym->type == SYM_VALUE &&
		!is_top(sym))
		return TRUE;

	return FALSE;
}

/* Name in defining occurrence.
 *
 * If this is a redefinition of an existing def in this kit, add it as a local
 * of the existing def and tag for codegen. Consider repeated param names,
 * param names clashing with locals.
 *
 * We make a ZOMBIE: our caller should turn it into a blank user definition, a
 * parameter etc.
 */
Symbol *
symbol_new_defining(Compile *compile, const char *name)
{
	static int symbol_def_id = 0;

	Symbol *sym;

	/* Block definition of "root" anywhere ... too confusing.
	 */
	if (strcmp(name, IOBJECT(symbol_root)->name) == 0)
		nip2yyerror(_("Attempt to redefine root symbol \"%s\"."), name);

	/* Is this a redefinition of an existing symbol in this scope?
	 */
	if ((sym = compile_lookup(compile, name))) {
		if (sym->type == SYM_ZOMBIE) {
			/* This is the definition for a previously referenced
			 * symbol. Just return the ZOMBIE we made.
			 */
		}
		else if (symbol_can_add_def(sym)) {
			/* This is a new def for an existing def, either a local or a
			 * top-level.
			 *
			 * This new def should be attached as a local of that first def,
			 * and the whole thing needs tagging for a codegen pass.
			 */
			char name_id[256];
			Symbol *new_def;

			g_snprintf(name_id, 256, "$$%s_def%d", name, symbol_def_id++);
			new_def = symbol_new(sym->expr->compile, name_id);
			new_def->generated = TRUE;

			// append to the set of defs
			symbol_get_last(sym)->next_def = new_def;

			// sym has multiple defs and will need a codegen pass
			sym->needs_codegen = TRUE;

			sym = new_def;
		}
		else {
			/* Eg. redef of parameter, workspace, an existing def in
			 * another kit.
			 */
			char txt[200];
			VipsBuf buf = VIPS_BUF_STATIC(txt);

			vips_buf_appendf(&buf, _("can't redefine %s \"%s\""),
				decode_SymbolType_user(sym->type), name);

			if (sym->tool &&
				sym->tool->kit) {
				vips_buf_appendf(&buf, ", ");
				vips_buf_appendf(&buf, _("previous definition was %s:%d"),
					FILEMODEL(sym->tool->kit)->filename, sym->tool->lineno);
			}
			vips_buf_appendf(&buf, ".");

			nip2yyerror("%s", vips_buf_all(&buf));
			/*NOTREACHED*/
		}

		/* This is a defining occurrence ... move to the end of the
		 * traverse order.
		 */
		icontainer_child_move(ICONTAINER(sym), -1);
	}
	else
		sym = symbol_new(compile, name);

#ifdef DEBUG_MAKE
	printf("symbol_new_defining: ");
	symbol_name_print(sym);
	printf("(%p)\n", sym);
#endif /*DEBUG_MAKE*/

	return sym;
}

/* Make a reference to a symbol. Look on the local table for the name - if
 * it's not there, make a ZOMBIE. Note that ZOMBIEs etc. need patch lists
 * attached to them for all pointers to them we make. Responsibility of
 * caller!
 */
Symbol *
symbol_new_reference(Compile *compile, const char *name)
{
	Symbol *sym = compile_lookup(compile, name);

	if (!sym)
		sym = symbol_new(compile, name);

	/* Note the new dependency.
	 */
	compile_link_make(compile, sym);

#ifdef DEBUG_MAKE
	printf("symbol_new_reference: ");
	symbol_name_print(sym);
	printf("\n");
#endif /*DEBUG_MAKE*/

	return sym;
}

/* Compile refers to child ... break link.
 */
void *
symbol_link_break(Symbol *child, Compile *compile)
{
	compile_link_break(compile, child);

	return NULL;
}

/* Specialise into a user definition.
 */
gboolean
symbol_user_init(Symbol *sym)
{
	g_assert(sym->type == SYM_ZOMBIE);

	sym->type = SYM_VALUE;
	reduce_register(sym);
	if (!sym->expr)
		sym->expr = expr_new(sym);

	/* We don't symbol_made() yet, wait until we have finished building
	 * sym->expr.
	 */

	return TRUE;
}

/* Specialise into a parameter on an expression.
 */
gboolean
symbol_parameter_init(Symbol *sym)
{
	Compile *parent = COMPILE(ICONTAINER(sym)->parent);

	g_assert(sym->type == SYM_ZOMBIE);

	sym->type = SYM_PARAM;
	parent->param = g_slist_append(parent->param, sym);
	parent->nparam = g_slist_length(parent->param);
	symbol_made(sym);

	return TRUE;
}

/* Specialise into a builtin parameter (eg. "this").
 */
gboolean
symbol_parameter_builtin_init(Symbol *sym)
{
	g_assert(sym->type == SYM_ZOMBIE);

	sym->type = SYM_PARAM;
	symbol_made(sym);

	return TRUE;
}

/* Get the next dirty leaf symbol.
 */
static Symbol *
symbol_leaf_next(void)
{
	return symbol_leaf_set ? (Symbol *) symbol_leaf_set->data : NULL;
}

/* Are there symbols we can recalculate? Used to display "Calculating ..."
 * status.
 */
gboolean
symbol_busy(void)
{
	return symbol_leaf_set != NULL;
}

/* Set leaf state.
 */
static void
symbol_set_leaf(Symbol *sym, gboolean leaf)
{
	if (sym->leaf != leaf) {
		gboolean changed;

		sym->leaf = leaf;

		changed = FALSE;
		if (leaf) {
			if (!symbol_leaf_set)
				changed = TRUE;

			symbol_leaf_set = g_slist_prepend(symbol_leaf_set, sym);
		}
		else {
			g_assert(symbol_leaf_set);

			symbol_leaf_set = g_slist_remove(symbol_leaf_set, sym);

			if (!symbol_leaf_set)
				changed = TRUE;
		}

		if (changed)
			iobject_changed(IOBJECT(reduce_context->heap));

		if (sym->expr && sym->expr->row)
			iobject_changed(IOBJECT(sym->expr->row));
	}
}

/* State of a symbol has changed ... update!
 */
void
symbol_state_change(Symbol *sym)
{
	g_assert(sym->ndirtychildren >= 0);

	/* Used to do more ... now we just set leaf.
	 */
	symbol_set_leaf(sym, symbol_is_leafable(sym));
}

/* Recalculate a symbol. We know we are dirty and have no dirty ancestors.
 */
static gboolean
symbol_recalculate_sub(Symbol *sym)
{
	gboolean result = TRUE;

#ifdef DEBUG_TIME
    static GTimer *timer = NULL;

    if (!timer)
        timer = g_timer_new();
    g_timer_reset(timer);
#endif /*DEBUG_TIME*/

	g_assert(is_value(sym));

	if (sym->expr->row) {
		/* This is the root of a display ... use that recomp
		 * mechanism.
		 */
		row_recomp(sym->expr->row);

		/* Stuff may have been removed.
		 */
		if (sym->expr &&
			sym->expr->row &&
			sym->expr->row->err)
			result = FALSE;
	}
	else if (sym->expr->compile->nparam == 0) {
		/* No params: this ought to have a value.
		 */
		if (!reduce_regenerate(sym->expr, &sym->expr->root))
			result = FALSE;
	}
	else
		heap_noval_new(NULL, &sym->expr->root);

#ifdef DEBUG_TIME
    printf("symbol_recalculate_sub: ");
    symbol_name_scope_print(sym);
    printf(" %g\n", g_timer_elapsed(timer, NULL));
#endif /*DEBUG_TIME*/

	return result;
}

/* Note the name of the last thing we calced here, for progress to display.
 */
static char symbol_last_calc_txt[256];
static VipsBuf symbol_last_calc_buf = VIPS_BUF_STATIC(symbol_last_calc_txt);

static void
symbol_note_calc_name(Symbol *sym)
{
	Symbol *scope = symbol_get_scope(sym);
	VipsBuf *buf = &symbol_last_calc_buf;

	vips_buf_rewind(buf);
	vips_buf_appends(buf, IOBJECT(scope)->name);
	vips_buf_appends(buf, ".");
	symbol_qualified_name_relative(scope, sym, buf);
}

const char *
symbol_get_last_calc(void)
{
	return vips_buf_all(&symbol_last_calc_buf);
}

/* We can get called recursively .. eg. we do an tiffload(), that
 * pops a progress box, that triggers idle, that tries to recalc a
 * leaf again.
 */
static gboolean symbol_running = FALSE;

/* Recalc a symbol with error checks. This can succeed, stop with a timeout,
 * or fail with an error.
 */
static void *
symbol_recalculate_leaf_sub(Symbol *sym)
{
#ifdef DEBUG_RECALC
	printf("symbol_recalculate_leaf_sub: %s\n", symbol_name_scope(sym));

	/* We can symbol_recalculate_leaf_sub() syms which are not dirty.
	 *
	 * Both these asserts can fail with regular code, just here for debugging.
	 *
	 * Test 2 will fail for mutual top-level recursion.
	 *
	 */
	g_assert(!sym->dirty || symbol_is_leafable(sym));
	g_assert(symbol_ndirty(sym) == 0);
#endif /*DEBUG_RECALC*/

	error_clear();
	if (sym->expr &&
		sym->expr->err) {
		expr_error_get(sym->expr);

#ifdef DEBUG_RECALC
		printf("\t(error: previous error)\n");
#endif /*DEBUG_RECALC*/

		return sym;
	}

	if (!sym->dirty)
		return NULL;
	if (!is_value(sym)) {
		symbol_dirty_clear(sym);
		return NULL;
	}
	if (symbol_running)
		return NULL;

	reduce_context->heap->filled = FALSE;
	symbol_running = TRUE;
	symbol_note_calc_name(sym);
	progress_begin();

	// false for error, true for success or timeout
	gboolean result = symbol_recalculate_sub(sym);

	symbol_running = FALSE;
	progress_end();

	if (!result ||
		reduce_context->heap->filled) {
		if (sym->expr)
			expr_error_set(sym->expr);

#ifdef DEBUG_RECALC
		if (sym->expr)
			printf("\t(error: %s %s)\n",
				sym->expr->error_top, sym->expr->error_sub);
		printf("\t(error: in %s)\n", symbol_name_scope(sym));
#endif /*DEBUG_RECALC*/

		return sym;
	}

	/* Any dirty children? If not, we've cleaned this sym and it can come
	 * off the leaf set.
	 */
	if (!sym->ndirtychildren) {
		symbol_dirty_clear(sym);
		if (sym->expr) {
			expr_new_value(sym->expr);

#ifdef DEBUG_RECALC
			printf("\tsuccess: ");
			graph_pointer(&sym->expr->root);
#endif /*DEBUG_RECALC*/
		}
	}
#ifdef DEBUG_RECALC
	else {
		printf("\t(found dirty children)\n");
		int indent = 0;
		dump_symbol(sym, &indent);
	}
#endif /*DEBUG_RECALC*/

	return NULL;
}

/* Recalculate a symbol. FALSE if no symbols can be recalced.
 */
static gboolean
symbol_recalculate_leaf(void)
{
	gboolean recalculated;
	Symbol *sym;

	recalculated = FALSE;

	/* Mark any classmodels dirty that have updates ready.
	 */
	classmodel_dirty_updated();

#ifdef DEBUG_RECALC
	printf("symbol_recalculate_leaves: Leaf set: ");
	slist_map(symbol_leaf_set, (SListMapFn) dump_tiny, NULL);
	printf("\n");
	symbol_dirties();
#endif /*DEBUG_RECALC*/

	/* Grab stuff off the leaf set.
	 */
	if ((sym = symbol_leaf_next())) {
		/* Should be dirty with no dirty children. Unless it's a
		 * function, in which case dirty kids are OK.
		 *
		 * Can contain an error, in which case this recomp will just take it
		 * off the dirty list.
		 */
		g_assert(sym->dirty);
		g_assert(is_top(sym));
		g_assert(symbol_ndirty(sym) == 0 || is_value(sym));

		/* Found a symbol!
		 */
		(void) symbol_recalculate_leaf_sub(sym);

		/* We can get errors during backtracking, don't report here.
		 */

		/* Queue a GC.
		 */
		(void) heap_gc_request(reduce_context->heap);

		/* We have made some progress.
		 */
		recalculated = TRUE;
	}

	return recalculated;
}

/* Our idle recomp callback.
 */
static gint symbol_idle_id = 0;

static gboolean
symbol_recalculate_idle(void *user_data)
{
#ifdef DEBUG_RECALC
	printf("symbol_recalculate_idle:\n");
#endif /*DEBUG_RECALC*/

	if (symbol_running)
		/* We've been run from a nested main loop, perhaps from the
		 * progress bar. Just run again and perhaps next time we'll be
		 * back in the top-level main loop.
		 */
		return TRUE;

	if (mainwindow_auto_recalc)
		while (symbol_recalculate_leaf())
			;

	if (!symbol_leaf_next()) {
#ifdef DEBUG_RECALC
		printf("symbol_recalculate_idle: bg recalc done\n");
#endif /*DEBUG_RECALC*/

		symbol_idle_id = 0;
		progress_end();

		return FALSE;
	}
	else
		return TRUE;
}

/* Recalculate ... either nudge the idle recomp, or in batch mode, do a recomp
 * right now.
 */
void
symbol_recalculate_all_force(gboolean now)
{
#ifdef DEBUG
	icontainer_map(ICONTAINER(symbol_root->expr->compile),
		(icontainer_map_fn) symbol_sanity, NULL, NULL);
#endif /*DEBUG*/

	/* In case we're called directly.
	 */
	(void) view_scan_all();

	if (symbol_running)
		/* Do nothing.
		 */
		;
	else if (main_option_batch ||
		now) {
		progress_begin();

		while (symbol_recalculate_leaf())
			;

		progress_end();
	}
	else if (!symbol_idle_id) {
#ifdef DEBUG_RECALC
		printf("symbol_recalculate_all_force: starting bg recalc ...\n");
#endif /*DEBUG_RECALC*/

		progress_begin();
		symbol_idle_id = g_idle_add(symbol_recalculate_idle, NULL);
	}
}

/* Recalculate the symbol table.
 */
void
symbol_recalculate_all(void)
{
	// it'll pop back if there's still an error
	workspace_clear_error_all();

	/* Do a scan, even if we don't recomp. We need to pick up edits before
	 * views get refreshed.
	 */
	(void) view_scan_all();

	if (mainwindow_auto_recalc)
		symbol_recalculate_all_force(FALSE);
}

/* Recalc a symbol ... with error checks.
 */
gboolean
symbol_recalculate_check(Symbol *sym)
{
	gboolean result;

	result = symbol_recalculate_leaf_sub(sym) == NULL;

	return result;
}
