// Native DCG translation: the translator and its bifs.
//
// This is a cell -> cell rewrite of what library/dcgs.pl does with =..,
// append/3 and subsumes_term/2. Nothing here creates a query, prints a
// term, or re-parses one: the output is built in a plain cell arena and
// blitted into final storage by the caller.
//
// Layer 1 (everything above the bif table) never throws and never
// touches the query heap. It records error *intent* in the dcg_ctx and
// returns DCG_ERROR; the caller decides whether that becomes a parser
// error (consult time, later phases) or throw_error() (runtime, here).
// That split exists because compile-time expansion may not raise an
// error at a different moment than the runtime would - see section 5.3
// of docs/native-dcg-design.md.
//
// The one concession to Layer 1 "has no query dependency": ctx->q is
// used for deref() and for fresh variables, and only when non-NULL. At
// consult time the clause cells carry no bindings and the parser
// supplies its own variables; that path is dcg_expand_clause(), at the
// foot of this file.
//
// Note that DCG_DECLINE is never returned from here. Layer 1 always
// translates or errors; the decline decision is dcg_is_constr(), which
// '$dcg_body'/4 tests before calling in. The DECLINE branches in both
// bifs are belt and braces.
//
// This reproduces library/dcgs.pl's translation, with ONE deliberate
// divergence: a nonvar non-callable in non-terminal position raises
// type_error(callable, T) here, where the reference silently drops the
// S0/S arguments and lets call/1 report the whole body instead. That is
// issue #1102 (== #832), and it is why '$dcg_rule'/2 is not a drop-in
// oracle match for dcg_rule/2.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "module.h"
#include "prolog.h"
#include "query.h"
#include "heap.h"
#include "parser.h"

// Three outcomes, not two. A caller must be able to tell "this is not a
// construct I handle, fall through to an ordinary non-terminal call"
// from "this is a construct and it is malformed". '$dcg_body'/4 turns
// DECLINE into failure so phrase/3 falls through to call(M:B,S0,S).

typedef enum {
	DCG_OK = 0,		// translated; arena holds the goal
	DCG_DECLINE,		// not a construct; caller falls through
	DCG_ERROR		// error intent recorded in ctx, or OOM
} dcg_rc;

typedef struct {
	cell *buf;
	unsigned len, cap;
} dcg_arena;

typedef struct {
	dcg_arena ar;
	prolog *pl;
	module *m;

	// Exactly one of these is set. The query path derefs, makes fresh
	// variables with create_vars(), and emits variables as REFS carrying
	// a context. The parser path has no bindings to deref, numbers fresh
	// variables straight off p->cl->num_vars, and must emit plain clause
	// variables - a ref in a consulted clause would be nonsense.

	query *q;
	parser *p;
	bool by_ref;		// true on the query path

	// The context fresh variables carry. q->st.cur_ctx on the query
	// path, 0 at consult time. Held here rather than fetched from the
	// query, so nothing below has to know which path it is on.

	pl_ctx v_ctx;
	unsigned nvars;		// consult-time fresh-variable counter
	unsigned depth;
	bool oom;

	// Pending error, raised by the caller in its own idiom. culprit
	// points into the SOURCE term, so it is valid only while that term
	// is alive - every caller raises before returning.

	const char *err_type, *err_expected;
	const cell *culprit;
	pl_ctx culprit_ctx;

	// Set when the arena holds a fully-built ball instead. The reference
	// raises two shapes that throw_error3() cannot compose, because its
	// context is always Name/Arity: [culprit-Term] for \+ and ->, and
	// must_be/2 for a partial terminal list. Matching them keeps the
	// phase 0 differential harness a tight net, so that the divergence
	// list stays what it is meant to be - deliberate semantic choices
	// (issue #1102), not incidental error formatting.

	bool has_ball;
} dcg_ctx;

// File-local interned atoms. Those that already exist as globals
// (g_conjunction_s, g_disjunction_s, g_if_then_s, g_negation_s,
// g_cut_s, g_call_s, g_colon_s, g_unify_s, g_dot_s, g_nil_s,
// g_braces_s, g_neck_s, g_dcg_s) are used directly.

static pl_idx g_bar_s, g_phrase_s, g_string_prefix_s;
static pl_idx g_repr_err_s, g_dcg_body_s, g_culprit_s;
static pl_idx g_inst_err_s, g_must_be_s, g_type_error_s, g_list_s;
// new_atom() is idempotent and takes g_symtab_guard, and the symbol
// table is process-global, so caching the offsets in statics is sound
// across prolog instances. The ordering was not: writing the "done"
// flag last did not stop the compiler or CPU making that store visible
// before the g_* stores, so a second thread could skip initialisation
// and then read an offset that had not been assigned - a zero pl_idx,
// i.e. silently building terms with the wrong functor.
//
// Interning is cheap and idempotent, so the fix is to drop the fast
// path and let every caller run it. bif_tabling.c's tbl_intern_atoms()
// had the same shape and the same change.

static void dcg_init_atoms(prolog *pl)
{
	g_bar_s = new_atom(pl, "|");
	g_phrase_s = new_atom(pl, "phrase");
	g_string_prefix_s = new_atom(pl, "$string_prefix");
	g_repr_err_s = new_atom(pl, "representation_error");
	g_dcg_body_s = new_atom(pl, "dcg_body");
	g_culprit_s = new_atom(pl, "culprit");
	g_inst_err_s = new_atom(pl, "instantiation_error");
	g_must_be_s = new_atom(pl, "must_be");
	g_type_error_s = new_atom(pl, "type_error");
	g_list_s = new_atom(pl, "list");
}

// Bounds the conjunction/alternation spine and the module-qualification
// nest. The terminal-list walk is iterative and not covered by this.

#define MAX_DCG_DEPTH 2000

// Above this many bytes a string terminal is emitted as a
// '$string_prefix'/3 call rather than materialised into the clause as
// two cells per character. Section 6 picked ~64 as the crossover; the
// cost being avoided is clause size, not speed - see the bif's comment.

#define DCG_STRING_INLINE_MAX 64

// --- arena ---
//
// Cells are appended in prefix order and num_cells is patched on the way
// out, the same idiom heap.c uses. Managed cells copied in from the
// source are shared on the way in (dup_cells_by_ref) and released by
// arena_free() if translation is abandoned; on success the buffer's
// references transfer wholesale to the heap copy, so that path frees the
// buffer WITHOUT unsharing.

static bool arena_reserve(dcg_ctx *c, unsigned n)
{
	if (c->oom)
		return false;

	if ((c->ar.len + n) <= c->ar.cap)
		return true;

	unsigned cap = c->ar.cap ? c->ar.cap : 64;

	while (cap < (c->ar.len + n))
		cap *= 2;

	cell *buf = TPL_realloc(c->ar.buf, sizeof(cell) * cap);

	if (!buf) {
		c->oom = true;
		return false;
	}

	c->ar.buf = buf;
	c->ar.cap = cap;
	return true;
}

static cell *arena_alloc(dcg_ctx *c, unsigned n)
{
	if (!arena_reserve(c, n))
		return NULL;

	cell *ptr = c->ar.buf + c->ar.len;
	c->ar.len += n;
	return ptr;
}

static void arena_release(dcg_ctx *c)
{
	if (c->ar.buf) {
		unshare_cells(c->ar.buf, c->ar.len);
		TPL_free(c->ar.buf);
	}

	c->ar.buf = NULL;
	c->ar.len = c->ar.cap = 0;
}

static unsigned emit_open(dcg_ctx *c, pl_idx functor, unsigned arity)
{
	unsigned at = c->ar.len;
	cell *t = arena_alloc(c, 1);

	if (!t)
		return 0;

	make_struct(t, functor, arity, 0);
	return at;
}

// Every open cell's extent runs to whatever the arena holds now, so
// this is correct whenever it is called after the cell's last argument.

static void emit_close(dcg_ctx *c, unsigned at)
{
	if (c->oom)
		return;

	c->ar.buf[at].num_cells = c->ar.len - at;
}

static bool emit_atom(dcg_ctx *c, pl_idx functor)
{
	cell *t = arena_alloc(c, 1);

	if (!t)
		return false;

	make_atom(t, functor);
	return true;
}

// Copy a source subterm in. Variables become refs carrying their own
// context, which is what makes the emitted goal meaningful once it is
// unified into the caller's frame.

static bool emit_term(dcg_ctx *c, const cell *t, pl_ctx t_ctx)
{
	cell *dst = arena_alloc(c, t->num_cells);

	if (!dst)
		return false;

	if (c->by_ref)
		dup_cells_by_ref(dst, t, t_ctx, t->num_cells);
	else
		dup_cells(dst, t, t->num_cells);

	return true;
}

static bool emit_cell(dcg_ctx *c, const cell *t)
{
	cell *dst = arena_alloc(c, 1);

	if (!dst)
		return false;

	*dst = *t;
	share_cell(dst);
	return true;
}

static void set_error(dcg_ctx *c, const char *type, const char *expected, const cell *culprit, pl_ctx culprit_ctx)
{
	if (c->err_type || c->has_ball)
		return;			// first error wins

	c->err_type = type;
	c->err_expected = expected;
	c->culprit = culprit;
	c->culprit_ctx = culprit_ctx;
}

// The partial translation is dead the moment an error is raised, so the
// ball is built in the same arena - reset first, then emit.

static bool start_ball(dcg_ctx *c)
{
	if (c->err_type || c->has_ball)
		return false;

	arena_release(c);
	return !c->oom;
}

// error(representation_error(dcg_body), [culprit-Culprit])

static void set_ball_repr(dcg_ctx *c, const cell *culprit, pl_ctx culprit_ctx)
{
	if (!start_ball(c))
		return;

	unsigned err = emit_open(c, g_error_s, 2);
	unsigned re = emit_open(c, g_repr_err_s, 1);

	if (c->oom || !emit_atom(c, g_dcg_body_s))
		return;

	emit_close(c, re);

	unsigned lst = emit_open(c, g_dot_s, 2);
	unsigned pair = emit_open(c, g_minus_s, 2);

	if (c->oom
		|| !emit_atom(c, g_culprit_s)
		|| !emit_term(c, culprit, culprit_ctx))
		return;

	emit_close(c, pair);

	if (!emit_atom(c, g_nil_s))
		return;

	emit_close(c, lst);
	emit_close(c, err);
	c->has_ball = true;
}

// The reference reaches both of these through must_be(list, [T|Ts]) in
// dcg_cbody/4, so both carry a must_be/2 context:
//
//   partial list  [x|_]  ->  error(instantiation_error, must_be/2)
//   improper list [x|y]  ->  error(type_error(list,[x|y]), must_be/2)

static void emit_must_be_context(dcg_ctx *c)
{
	unsigned sl = emit_open(c, g_slash_s, 2);

	if (c->oom || !emit_atom(c, g_must_be_s))
		return;

	cell n;
	make_int(&n, 2);

	if (!emit_cell(c, &n))
		return;

	emit_close(c, sl);
}

static void set_ball_must_be_inst(dcg_ctx *c)
{
	if (!start_ball(c))
		return;

	unsigned err = emit_open(c, g_error_s, 2);

	if (c->oom || !emit_atom(c, g_inst_err_s))
		return;

	emit_must_be_context(c);

	if (c->oom)
		return;

	emit_close(c, err);
	c->has_ball = true;
}

static void set_ball_must_be_list(dcg_ctx *c, const cell *culprit, pl_ctx culprit_ctx)
{
	if (!start_ball(c))
		return;

	unsigned err = emit_open(c, g_error_s, 2);
	unsigned te = emit_open(c, g_type_error_s, 2);

	if (c->oom
		|| !emit_atom(c, g_list_s)
		|| !emit_term(c, culprit, culprit_ctx))
		return;

	emit_close(c, te);
	emit_must_be_context(c);

	if (c->oom)
		return;

	emit_close(c, err);
	c->has_ball = true;
}

// --- source inspection ---

static cell *dcg_deref(dcg_ctx *c, const cell *t, pl_ctx t_ctx, pl_ctx *out_ctx)
{
	if (!c->q) {
		*out_ctx = t_ctx;
		return (cell*)t;
	}

	cell *r = deref(c->q, (cell*)t, t_ctx);
	*out_ctx = c->q->latest_ctx;
	return r;
}

static bool is_functor(const cell *t, pl_idx functor, unsigned arity)
{
	return is_interned(t) && (t->val_off == functor) && (t->arity == arity);
}

static cell *nth_arg(const cell *t, unsigned n)
{
	cell *a = (cell*)t + 1;

	while (n--)
		a += a->num_cells;

	return a;
}

// 7.14 constructs. Note call/1 and phrase/1..3 ONLY: the reference has
// no clause for call/N with N>1, and for those arities the non-terminal
// path produces the identical goal anyway, so the distinction is not
// observable. Kept faithful rather than tidied.

static bool dcg_is_constr(const cell *t)
{
	if (is_nil(t))
		return true;

	if (is_iso_list(t) || is_string(t))
		return true;

	if (!is_interned(t))
		return false;

	const pl_idx f = t->val_off;
	const unsigned a = t->arity;

	if ((a == 2) && ((f == g_conjunction_s) || (f == g_disjunction_s)
		|| (f == g_bar_s) || (f == g_if_then_s)))
		return true;

	if ((a == 1) && ((f == g_braces_s) || (f == g_call_s) || (f == g_negation_s)))
		return true;

	if ((f == g_phrase_s) && (a >= 1) && (a <= 3))
		return true;

	if ((a == 0) && (f == g_cut_s))
		return true;

	return false;
}

// --- fresh variables ---

static bool new_var(dcg_ctx *c, cell *out)
{
	if (c->q) {
		int n = create_vars(c->q, 1);

		if (n < 0) {
			c->oom = true;
			return false;
		}

		make_ref(out, (unsigned)n, c->v_ctx);
		return true;
	}

	// Consult time. Section 10 option (a): named, and registered in the
	// vartab by the assign_vars() that runs after us - see the note below
	// for why option (b) cannot work. dcg_expand_clause() is called from
	// tokenize() immediately BEFORE assign_vars(), and phase 4 must
	// preserve that order.

	if (c->p->cl->num_vars >= MAX_VARS) {
		set_error(c, "resource_error", "max_vars", NULL, 0);
		return false;
	}

	// Emitted NAMED and unnumbered: assign_vars() runs after this and
	// assigns the slot, registering the name in the vartab as it goes.
	//
	// That registration is the whole point. goal_expansion() prints a
	// goal and re-parses it, reconnecting variables by name through the
	// inherited vartab; a variable with no entry there comes back as a
	// different one, which silently unthreads S0/S. Section 10 option (b)
	// - anonymous, temporary, no vartab entry - cannot work for that
	// reason, and FLAG_VAR_TEMPORARY separately breaks head-argument
	// sharing.
	//
	// The name only has to be unique within this clause. A user variable
	// literally named _S<n> in the same clause would merge with ours;
	// the old round trip had the same exposure with its generated names.

	char name[32];
	snprintf(name, sizeof(name), "_S%u", c->nvars++);
	pl_idx off = new_atom(c->pl, name);

	if (off == ERR_IDX) {
		c->oom = true;
		return false;
	}

	make_var(out, off, 0);
	return true;
}

// --- terminals ---
//
// Emit <Terminals ++ Tail>, which is dcg_terminals/3's append/3 done at
// translate time: a proper list becomes cons cells with Tail as the
// final tail. Iterative, with an explicit index array, because a
// terminal list can be very long (section 9) and this must not sit on
// the C stack.
//
// Strings are materialised, which is what the reference does and what
// section 6 settles on: the CONSUMING direction stays fast regardless,
// because unify.c slices, so this costs cells only when generating.

static dcg_rc emit_terminals(dcg_ctx *c, const cell *l, pl_ctx l_ctx,
	const cell *tail, pl_ctx tail_ctx, const cell *whole, pl_ctx whole_ctx)
{
	unsigned *opens = NULL;
	unsigned n_opens = 0, cap_opens = 0;
	cell *p = (cell*)l;
	pl_ctx p_ctx = l_ctx;
	dcg_rc rc = DCG_OK;
	PROLOG_LIST_HANDLER(p);

	while (is_list(p)) {
		if (n_opens == cap_opens) {
			unsigned newcap = cap_opens ? cap_opens * 2 : 32;
			unsigned *tmp = TPL_realloc(opens, sizeof(unsigned) * newcap);

			if (!tmp) {
				c->oom = true;
				rc = DCG_ERROR;
				goto done;
			}

			opens = tmp;
			cap_opens = newcap;
		}

		cell *h = PROLOG_LIST_HEAD(p);
		pl_ctx h_ctx;
		h = dcg_deref(c, h, p_ctx, &h_ctx);

		opens[n_opens++] = emit_open(c, g_dot_s, 2);

		if (c->oom) {
			rc = DCG_ERROR;
			goto done;
		}

		if (!emit_term(c, h, h_ctx)) {
			rc = DCG_ERROR;
			goto done;
		}

		cell *t = PROLOG_LIST_TAIL(p);
		p = dcg_deref(c, t, p_ctx, &p_ctx);
	}

	if (is_var(p)) {
		// Partial list. The reference's dcg_cbody/4 calls
		// must_be(list, [T|Ts]), which throws instantiation_error.
		// On the term_expansion path that propagates, so '$dcg_rule'/2
		// must raise here too.
		//
		// The goal_expansion path defers instead, where the tail may be
		// bound by runtime (section 5.2). Raising unconditionally here
		// is still right: the deferral is the CALLER's, and lives in
		// library/dcgs.pl's user:goal_expansion, which catches a
		// throwing translation and declines the hook so the ordinary
		// phrase/3 call survives to runtime. That is broader than the
		// reference's error_goal/2, which defers instantiation_error
		// and rethrows the rest.
		set_ball_must_be_inst(c);
		rc = DCG_ERROR;
		goto done;
	}

	if (!is_nil(p)) {
		set_ball_must_be_list(c, whole, whole_ctx);
		rc = DCG_ERROR;
		goto done;
	}

	if (!emit_term(c, tail, tail_ctx)) {
		rc = DCG_ERROR;
		goto done;
	}

	// Each open cons cell extends to the end of what we have emitted,
	// so the patch is index-independent and order-independent.

	for (unsigned i = 0; i < n_opens; i++)
		emit_close(c, opens[i]);

done:
	TPL_free(opens);
	return rc;
}

// --- body translation ---

static dcg_rc xlate_body(dcg_ctx *c, const cell *b, pl_ctx b_ctx,
	const cell *s0, pl_ctx s0_ctx, const cell *s, pl_ctx s_ctx);

// S0 = S, the identity thread.

static dcg_rc emit_unify(dcg_ctx *c, const cell *s0, pl_ctx s0_ctx, const cell *s, pl_ctx s_ctx)
{
	unsigned at = emit_open(c, g_unify_s, 2);

	if (c->oom
		|| !emit_term(c, s0, s0_ctx)
		|| !emit_term(c, s, s_ctx))
		return DCG_ERROR;

	emit_close(c, at);
	return DCG_OK;
}

// NonTerminal with S0,S appended. M:NT composes.

static dcg_rc xlate_nonterminal(dcg_ctx *c, const cell *nt, pl_ctx nt_ctx,
	const cell *s0, pl_ctx s0_ctx, const cell *s, pl_ctx s_ctx)
{
	if (++c->depth > MAX_DCG_DEPTH) {
		set_error(c, "resource_error", "dcg_nesting", nt, nt_ctx);
		c->depth--;
		return DCG_ERROR;
	}

	if (is_functor(nt, g_colon_s, 2)) {
		pl_ctx mod_ctx, inner_ctx;
		cell *mod = dcg_deref(c, nth_arg(nt, 0), nt_ctx, &mod_ctx);
		cell *inner = dcg_deref(c, nth_arg(nt, 1), nt_ctx, &inner_ctx);
		unsigned at = emit_open(c, g_colon_s, 2);

		if (c->oom || !emit_term(c, mod, mod_ctx)) {
			c->depth--;
			return DCG_ERROR;
		}

		dcg_rc rc = xlate_nonterminal(c, inner, inner_ctx, s0, s0_ctx, s, s_ctx);

		if (rc == DCG_OK)
			emit_close(c, at);

		c->depth--;
		return rc;
	}

	c->depth--;

	if (is_var(nt)) {
		set_error(c, "instantiation_error", "callable", nt, nt_ctx);
		return DCG_ERROR;
	}

	// Section 5.3: a nonvar non-callable here is a permanent condition -
	// 1 can never become callable - so it is decidable now and raised
	// now, with the BARE subterm as culprit. This is the deliberate
	// divergence from the reference, which drops S0/S and defers to
	// call/1's whole-body report. Issue #1102.

	if (!is_callable(nt)) {
		set_error(c, "type_error", "callable", nt, nt_ctx);
		return DCG_ERROR;
	}

	unsigned at = emit_open(c, nt->val_off, nt->arity + 2);

	if (c->oom)
		return DCG_ERROR;

	for (unsigned i = 0; i < nt->arity; i++) {
		pl_ctx a_ctx;
		cell *a = dcg_deref(c, nth_arg(nt, i), nt_ctx, &a_ctx);

		if (!emit_term(c, a, a_ctx))
			return DCG_ERROR;
	}

	if (!emit_term(c, s0, s0_ctx) || !emit_term(c, s, s_ctx))
		return DCG_ERROR;

	emit_close(c, at);
	return DCG_OK;
}

// (A, B) and (If -> Then) share a shape: translate left S0->S1, right
// S1->S, under the given functor.

static dcg_rc xlate_pair(dcg_ctx *c, pl_idx functor, const cell *b, pl_ctx b_ctx,
	const cell *s0, pl_ctx s0_ctx, const cell *s, pl_ctx s_ctx)
{
	cell s1;

	if (!new_var(c, &s1))
		return DCG_ERROR;

	pl_ctx l_ctx, r_ctx;
	cell *lhs = dcg_deref(c, nth_arg(b, 0), b_ctx, &l_ctx);
	cell *rhs = dcg_deref(c, nth_arg(b, 1), b_ctx, &r_ctx);
	unsigned at = emit_open(c, functor, 2);

	if (c->oom)
		return DCG_ERROR;

	dcg_rc rc = xlate_body(c, lhs, l_ctx, s0, s0_ctx, &s1, c->v_ctx);

	if (rc != DCG_OK)
		return rc;

	rc = xlate_body(c, rhs, r_ctx, &s1, c->v_ctx, s, s_ctx);

	if (rc != DCG_OK)
		return rc;

	emit_close(c, at);
	return DCG_OK;
}

// (A ; B) and (A | B): both branches thread S0 -> S.

static dcg_rc xlate_alt(dcg_ctx *c, const cell *b, pl_ctx b_ctx,
	const cell *s0, pl_ctx s0_ctx, const cell *s, pl_ctx s_ctx)
{
	pl_ctx l_ctx, r_ctx;
	cell *lhs = dcg_deref(c, nth_arg(b, 0), b_ctx, &l_ctx);
	cell *rhs = dcg_deref(c, nth_arg(b, 1), b_ctx, &r_ctx);
	unsigned at = emit_open(c, g_disjunction_s, 2);

	if (c->oom)
		return DCG_ERROR;

	// Section 5.1, the asymmetry that must be preserved - and it is
	// narrower than "an if-then condition inside an alternation".
	//
	// The reference's ;-with-if-then clause calls dcg_cbody/4 directly
	// on the condition, bypassing dcg_constr/1 and therefore its throw.
	// Its '|' clause does NOT: it calls dcg_body/4 on both branches, and
	// dcg_body/4 goes through dcg_constr/1, which throws. So:
	//
	//     a --> (b -> c ; d)     translates
	//     a --> (b -> c | d)     representation_error(dcg_body)
	//
	// Quad 22 is exactly the '|' form and accepts either answer; quad 23
	// is the ';' form and requires the permissive one. Reproducing the
	// reference means applying the bypass to ';' ONLY.

	dcg_rc rc;

	if ((b->val_off == g_disjunction_s) && is_functor(lhs, g_if_then_s, 2))
		rc = xlate_pair(c, g_if_then_s, lhs, l_ctx, s0, s0_ctx, s, s_ctx);
	else
		rc = xlate_body(c, lhs, l_ctx, s0, s0_ctx, s, s_ctx);

	if (rc != DCG_OK)
		return rc;

	rc = xlate_body(c, rhs, r_ctx, s0, s0_ctx, s, s_ctx);

	if (rc != DCG_OK)
		return rc;

	emit_close(c, at);
	return DCG_OK;
}

static dcg_rc xlate_body(dcg_ctx *c, const cell *b, pl_ctx b_ctx,
	const cell *s0, pl_ctx s0_ctx, const cell *s, pl_ctx s_ctx)
{
	if (c->oom)
		return DCG_ERROR;

	if (++c->depth > MAX_DCG_DEPTH) {
		set_error(c, "resource_error", "dcg_nesting", b, b_ctx);
		c->depth--;
		return DCG_ERROR;
	}

	dcg_rc rc;

	// 1. Var: deferred, never an error at translate time (section 5.2).

	if (is_var(b)) {
		unsigned at = emit_open(c, g_phrase_s, 3);

		if (!c->oom
			&& emit_term(c, b, b_ctx)
			&& emit_term(c, s0, s0_ctx)
			&& emit_term(c, s, s_ctx)) {
			emit_close(c, at);
			rc = DCG_OK;
		} else
			rc = DCG_ERROR;

		c->depth--;
		return rc;
	}

	// 2. [] (7.14.1)

	if (is_nil(b)) {
		rc = emit_unify(c, s0, s0_ctx, s, s_ctx);
		c->depth--;
		return rc;
	}

	// 3. [T|Ts] and string cells (7.14.2): S0 = <Ts ++ S>

	if (is_iso_list(b) || is_string(b)) {
		// A long literal becomes a call rather than thousands of cells.

		if (is_string(b) && (_CSTRING_LEN(b) > DCG_STRING_INLINE_MAX)) {
			unsigned at = emit_open(c, g_string_prefix_s, 3);

			if (c->oom
				|| !emit_term(c, b, b_ctx)
				|| !emit_term(c, s, s_ctx)
				|| !emit_term(c, s0, s0_ctx)) {
				c->depth--;
				return DCG_ERROR;
			}

			emit_close(c, at);
			c->depth--;
			return DCG_OK;
		}

		unsigned at = emit_open(c, g_unify_s, 2);

		if (c->oom || !emit_term(c, s0, s0_ctx)) {
			c->depth--;
			return DCG_ERROR;
		}

		rc = emit_terminals(c, b, b_ctx, s, s_ctx, b, b_ctx);

		if (rc == DCG_OK)
			emit_close(c, at);

		c->depth--;
		return rc;
	}

	if (!is_interned(b)) {
		// 15. nonvar, non-callable: raised here, never emitted bare.
		rc = xlate_nonterminal(c, b, b_ctx, s0, s0_ctx, s, s_ctx);
		c->depth--;
		return rc;
	}

	const pl_idx f = b->val_off;
	const unsigned a = b->arity;

	// 4. (A, B) (7.14.3)

	if ((a == 2) && (f == g_conjunction_s)) {
		rc = xlate_pair(c, g_conjunction_s, b, b_ctx, s0, s0_ctx, s, s_ctx);
		c->depth--;
		return rc;
	}

	// 5/6. (A ; B) (7.14.4) and (A | B) (7.14.6), both emitted as ;

	if ((a == 2) && ((f == g_disjunction_s) || (f == g_bar_s))) {
		rc = xlate_alt(c, b, b_ctx, s0, s0_ctx, s, s_ctx);
		c->depth--;
		return rc;
	}

	// 12. (If -> Then) (7.14.12) at top level: an error, per the
	// reference's dcg_constr/1. Reached inside ; via xlate_alt, which
	// does not come through here. Section 5.1.

	if ((a == 2) && (f == g_if_then_s)) {
		set_ball_repr(c, b, b_ctx);
		c->depth--;
		return DCG_ERROR;
	}

	// 11. \+ G (7.14.11): an error, same source.

	if ((a == 1) && (f == g_negation_s)) {
		set_ball_repr(c, b, b_ctx);
		c->depth--;
		return DCG_ERROR;
	}

	// 7. {G} (7.14.7): contents never inspected, they go to call/1
	// unexpanded - which is why a non-callable inside {} gets the
	// whole-term culprit and is NOT our business (quads 10, 15, 37).

	if ((a == 1) && (f == g_braces_s)) {
		pl_ctx g_ctx;
		cell *g = dcg_deref(c, nth_arg(b, 0), b_ctx, &g_ctx);
		unsigned at = emit_open(c, g_conjunction_s, 2);

		if (c->oom || !emit_term(c, g, g_ctx)) {
			c->depth--;
			return DCG_ERROR;
		}

		rc = emit_unify(c, s0, s0_ctx, s, s_ctx);

		if (rc == DCG_OK)
			emit_close(c, at);

		c->depth--;
		return rc;
	}

	// 10. ! (7.14.10): (!, S0 = S)

	if ((a == 0) && (f == g_cut_s)) {
		unsigned at = emit_open(c, g_conjunction_s, 2);

		if (c->oom || !emit_atom(c, g_cut_s)) {
			c->depth--;
			return DCG_ERROR;
		}

		rc = emit_unify(c, s0, s0_ctx, s, s_ctx);

		if (rc == DCG_OK)
			emit_close(c, at);

		c->depth--;
		return rc;
	}

	// 8/9. call/1 (7.14.8) and phrase/1..3 (7.14.9+): arguments are NOT
	// checked through (quads 32, 45) - just append S0,S.

	if (((a == 1) && (f == g_call_s))
		|| ((f == g_phrase_s) && (a >= 1) && (a <= 3))) {
		unsigned at = emit_open(c, f, a + 2);

		if (c->oom) {
			c->depth--;
			return DCG_ERROR;
		}

		for (unsigned i = 0; i < a; i++) {
			pl_ctx arg_ctx;
			cell *arg = dcg_deref(c, nth_arg(b, i), b_ctx, &arg_ctx);

			if (!emit_term(c, arg, arg_ctx)) {
				c->depth--;
				return DCG_ERROR;
			}
		}

		if (!emit_term(c, s0, s0_ctx) || !emit_term(c, s, s_ctx)) {
			c->depth--;
			return DCG_ERROR;
		}

		emit_close(c, at);
		c->depth--;
		return DCG_OK;
	}

	// 13/14. M:Body and any other callable: an ordinary non-terminal.

	rc = xlate_nonterminal(c, b, b_ctx, s0, s0_ctx, s, s_ctx);
	c->depth--;
	return rc;
}

// --- moving the arena into the query heap ---
//
// dup_cells, not copy_cells, is what the design calls for when the
// source may be released first. Here the arena's references transfer
// wholesale, so a plain copy is right and the arena must NOT be
// unshared afterwards.

// A term synthesized cell-by-cell carries no builtin pointer and no
// operator specifier, so calling it raises existence_error even for
// something as ordinary as =/2. On the consult path process_clause()
// supplies both; on the runtime path nothing does, and the output of
// '$dcg_body'/4 is meant to be CALLED. =../2 does exactly this after
// building its term (bif_predicates.c) - same three steps, same order.
//
// The reference implementation never needed it: its goals were copies of
// cells from its own compiled clauses, which already carried the flags.

static void arena_resolve(dcg_ctx *c, query *q)
{
	for (unsigned i = 0; i < c->ar.len; i++) {
		cell *x = c->ar.buf + i;

		if (!is_interned(x) || !is_callable(x))
			continue;

		bool found = false;
		builtins *ptr = get_builtin_term(q->st.m, x, &found, NULL);

		if (found) {
			x->bif_ptr = ptr;

			if (ptr->evaluable)
				x->flags |= FLAG_INTERNED_EVALUABLE;
			else
				x->flags |= FLAG_INTERNED_BUILTIN;
		}

		unsigned specifier;

		if (!GET_OP(x) && search_op(q->st.m, C_STR(q, x), &specifier, x->arity == 1)) {
			if ((x->arity == 2) && IS_INFIX(specifier))
				SET_OP(x, specifier);
			else if ((x->arity == 1) && IS_POSTFIX(specifier))
				SET_OP(x, specifier);
			else if ((x->arity == 1) && IS_PREFIX(specifier))
				SET_OP(x, specifier);
		}
	}
}

static cell *arena_to_heap(dcg_ctx *c, query *q)
{
	arena_resolve(c, q);

	cell *dst = alloc_heap(q, c->ar.len);

	if (!dst)
		return NULL;

	copy_cells(dst, c->ar.buf, c->ar.len);
	TPL_free(c->ar.buf);
	c->ar.buf = NULL;
	c->ar.len = c->ar.cap = 0;
	return dst;
}

// Takes over the arena in every path: a caller that reaches here must
// NOT have released it, because the ball may be sitting in it.

static bool dcg_raise(query *q, dcg_ctx *c)
{
	if (c->oom) {
		arena_release(c);
		return throw_error(q, q->st.instr, q->st.cur_ctx, "resource_error", "memory");
	}

	if (c->has_ball) {
		cell *ball = arena_to_heap(c, q);

		if (!ball)
			return throw_error(q, q->st.instr, q->st.cur_ctx, "resource_error", "memory");

		// Same idiom as throw/1: the ball travels as printed text.

		q->fullstop = q->nl = false;
		q->parens = q->numbervars = true;
		q->quoted = true;
		char *s = print_term_to_strbuf(q, ball, q->st.cur_ctx, 1);
		clear_write_options(q);

		if (!s)
			return throw_error(q, q->st.instr, q->st.cur_ctx, "resource_error", "memory");

		q->did_throw = true;
		bool ok = find_exception_handler(q, s);
		TPL_free(s);
		return ok;
	}

	arena_release(c);

	if (!c->err_type)
		return throw_error(q, q->st.instr, q->st.cur_ctx, "resource_error", "memory");

	return throw_error(q, (cell*)c->culprit, c->culprit_ctx, c->err_type, c->err_expected);
}

static void dcg_ctx_init(dcg_ctx *c, query *q)
{
	memset(c, 0, sizeof(*c));
	c->pl = q->pl;
	c->m = q->st.m;
	c->q = q;
	c->by_ref = true;
	c->v_ctx = q->st.cur_ctx;
	dcg_init_atoms(q->pl);
}

// '$dcg_body'(+Body, ?S0, ?S, -Goal)
//
// FAILS for a non-construct, so phrase/3 falls through to
// call(M:B,S0,S) and quad 2 keeps working. Throws only where ISO
// requires, including the section 5.3 type_error(callable, T).

static bool bif_dcg_body_4(query *q)
{
	GET_FIRST_ARG(p1,any);
	GET_NEXT_ARG(p2,any);
	GET_NEXT_ARG(p3,any);
	GET_NEXT_ARG(p4,var);

	if (is_var(p1))
		return false;

	if (!dcg_is_constr(p1))
		return false;

	dcg_ctx c;
	dcg_ctx_init(&c, q);
	dcg_rc rc = xlate_body(&c, p1, p1_ctx, p2, p2_ctx, p3, p3_ctx);

	if (rc == DCG_DECLINE) {
		arena_release(&c);
		return false;
	}

	if (rc != DCG_OK)
		return dcg_raise(q, &c);		// owns the arena

	cell *goal = arena_to_heap(&c, q);

	if (!goal) {
		arena_release(&c);
		return throw_error(q, q->st.instr, q->st.cur_ctx, "resource_error", "memory");
	}

	return unify(q, p4, p4_ctx, goal, q->st.cur_ctx);
}

// '$dcg_rule'(+Rule, -Clause)
//
// Head shapes, per the reference's four dcg_rule/2 clauses:
//
//   H --> B            ->  H(S0,S) :- B'(S0,S)
//   H, PB --> B        ->  H(S0,S) :- B'(S0,S1), S = <PB ++ S1>
//   M:H --> B, and M:H, PB --> B
//
// Note the argument order in the pushback case: the reference has
// dcg_terminals(Terminals, S, S1, Goal2), i.e. S = <PB ++ S1>, not the
// other way round.

// Translate (Head --> Body) into (Head' :- Body') in the arena. Shared
// by both front-ends; on anything but DCG_OK the arena still belongs to
// the caller, which must release it or hand it to dcg_raise().
//
// Head shapes, per the reference's four dcg_rule/2 clauses:
//
//   H --> B            ->  H(S0,S) :- B'(S0,S)
//   H, PB --> B        ->  H(S0,S) :- B'(S0,S1), S = <PB ++ S1>
//   M:H --> B, and M:H, PB --> B
//
// Note the argument order in the pushback case: the reference has
// dcg_terminals(Terminals, S, S1, Goal2), i.e. S = <PB ++ S1>, not the
// other way round.

static dcg_rc xlate_rule(dcg_ctx *c, const cell *rule, pl_ctx rule_ctx, pl_ctx v_ctx)
{
	pl_ctx head_ctx, body_ctx;
	cell *head = dcg_deref(c, nth_arg(rule, 0), rule_ctx, &head_ctx);
	cell *body = dcg_deref(c, nth_arg(rule, 1), rule_ctx, &body_ctx);

	// Split an optional pushback list off the head.

	cell *pushback = NULL;
	pl_ctx pushback_ctx = 0;

	if (is_functor(head, g_conjunction_s, 2)) {
		pl_ctx nt_ctx;
		cell *nt = dcg_deref(c, nth_arg(head, 0), head_ctx, &nt_ctx);
		pushback = dcg_deref(c, nth_arg(head, 1), head_ctx, &pushback_ctx);
		head = nt;
		head_ctx = nt_ctx;
	}

	cell s0, s, s1;

	if (!new_var(c, &s0) || !new_var(c, &s))
		return DCG_ERROR;

	bool have_s1 = false;

	if (pushback) {
		if (!new_var(c, &s1))
			return DCG_ERROR;

		have_s1 = true;
	}

	unsigned neck = emit_open(c, g_neck_s, 2);
	dcg_rc rc = c->oom ? DCG_ERROR : xlate_nonterminal(c, head, head_ctx, &s0, v_ctx, &s, v_ctx);

	if (rc != DCG_OK)
		return rc;

	if (!have_s1) {
		rc = xlate_body(c, body, body_ctx, &s0, v_ctx, &s, v_ctx);
	} else {
		unsigned conj = emit_open(c, g_conjunction_s, 2);
		rc = c->oom ? DCG_ERROR : xlate_body(c, body, body_ctx, &s0, v_ctx, &s1, v_ctx);

		if (rc == DCG_OK) {
			unsigned eq = emit_open(c, g_unify_s, 2);

			if (c->oom || !emit_cell(c, &s))
				rc = DCG_ERROR;
			else {
				rc = emit_terminals(c, pushback, pushback_ctx, &s1, v_ctx, pushback, pushback_ctx);

				if (rc == DCG_OK) {
					emit_close(c, eq);
					emit_close(c, conj);
				}
			}
		}
	}

	if (rc != DCG_OK)
		return rc;

	emit_close(c, neck);
	return DCG_OK;
}

static bool bif_dcg_rule_2(query *q)
{
	GET_FIRST_ARG(p1,any);
	GET_NEXT_ARG(p2,var);

	if (is_var(p1))
		return throw_error(q, p1, p1_ctx, "instantiation_error", "dcg_rule");

	if (!is_functor(p1, g_dcg_s, 2))
		return false;

	dcg_ctx c;
	dcg_ctx_init(&c, q);
	dcg_rc rc = xlate_rule(&c, p1, p1_ctx, c.v_ctx);

	if (rc != DCG_OK) {
		if (rc == DCG_DECLINE) {
			arena_release(&c);
			return false;
		}

		return dcg_raise(q, &c);	// owns the arena
	}

	cell *out = arena_to_heap(&c, q);

	if (!out) {
		arena_release(&c);
		return throw_error(q, q->st.instr, q->st.cur_ctx, "resource_error", "memory");
	}

	return unify(q, p2, p2_ctx, out, q->st.cur_ctx);
}

// --- consult-time front end ------------------------------------------
//
// Replaces dcg_expansion(), which created a query, ran dcg_translate/2,
// printed the result canonically, spun up a fresh parser and re-tokenized
// it - per DCG clause. Everything in section 1.1's hazard table came from
// that round trip; none of it survives here.

static clause *arena_to_clause(dcg_ctx *c)
{
	// Headroom, so the first in-place growth downstream
	// (expand_meta_predicate, goal_expansion, insert_call_here) does not
	// immediately realloc. Correctness no longer depends on it - the two
	// stale-pointer bugs in parser.c that an exactly-sized clause used to
	// expose are fixed - but a parsed clause arrives with slack from
	// make_room()'s 3/2 growth and there is no reason to be stingier.

	const unsigned cap = c->ar.len + 64;
	clause *cl = TPL_calloc(1, sizeof(clause) + (sizeof(cell) * cap));

	if (!cl)
		return NULL;

	cl->num_allocated_cells = cap;
	cl->cidx = c->ar.len;

	// assign_vars() runs next and recomputes num_vars from scratch.

	cl->num_vars = 0;

	// A plain copy: the arena's references transfer wholesale, so the
	// arena must NOT be unshared afterwards. clear_clause() will release
	// exactly these cidx cells when the clause dies.

	copy_cells(cl->cells, c->ar.buf, c->ar.len);
	TPL_free(c->ar.buf);
	c->ar.buf = NULL;
	c->ar.len = c->ar.cap = 0;
	return cl;
}

bool dcg_expand_clause(parser *p)
{
	dcg_ctx c;
	memset(&c, 0, sizeof(c));
	c.pl = p->m->pl;
	c.m = p->m;
	c.p = p;
	c.by_ref = false;
	dcg_init_atoms(c.pl);

	dcg_rc rc = xlate_rule(&c, p->cl->cells, 0, c.v_ctx);

	if (rc != DCG_OK) {
		arena_release(&c);

		// The parser has no exception channel - that is section 1.1's
		// last row, and fixing it is not this phase's job. But it can at
		// least say what went wrong, where; dcg_expansion() set the flag
		// and said nothing at all.

		if (c.oom)
			fprintf(stderr, "Error: DCG translation out of memory, %s:%d\n",
				get_loaded(p->m, p->m->filename), p->line_num);
		else
			fprintf(stderr, "Error: %s in DCG rule, %s:%d\n",
				c.has_ball ? "representation_error(dcg_body) or type_error" :
				c.err_type ? c.err_type : "malformed DCG rule",
				get_loaded(p->m, p->m->filename), p->line_num);

		p->error_desc = "dcg_body";
		p->error = true;
		return false;
	}

	// term_to_body() computes cells->num_cells as cidx-1, i.e. it expects
	// a trailing TAG_END the way a tokenized clause has one.

	cell *end = arena_alloc(&c, 1);

	if (!end) {
		arena_release(&c);
		p->error_desc = "memory";
		p->error = true;
		return false;
	}

	make_end(end);

	// At THIS point in tokenize() - before assign_vars() - the root cell's
	// num_cells still counts the trailing TAG_END; the check just above
	// assign_vars rejects a clause where num_cells < cidx, and
	// term_to_body() subtracts the END later ("Drops TAG_END"). Insert
	// after term_to_body instead and the opposite convention applies.

	c.ar.buf[0].num_cells = c.ar.len;
	clause *cl = arena_to_clause(&c);

	if (!cl) {
		arena_release(&c);
		p->error_desc = "memory";
		p->error = true;
		return false;
	}

	clear_clause(p->cl);
	TPL_free(p->cl);
	p->cl = cl;
	return true;
}

// '$string_prefix'(+Str, ?Tail, ?S0)   -- S0 = Str ++ Tail
//
// Emitted instead of materialising a long string terminal into the
// clause. Section 6 chose to materialise, which is correct but stores
// two cells per character IN THE CLAUSE: 200 rules with a 4 KB literal
// cost 100 MB of RSS and 2.4s to consult, against 11.7 MB and 0.06s for
// a one-character literal.
//
// Consuming walks Str and S0 in lockstep. That is O(1) per character
// even for a huge S0, because list_tail() slices a string in place
// rather than copying (the same property section 6 verified for
// unify.c). Only the generating direction materialises, and then on the
// heap per call rather than permanently in the clause.

static bool sp_construct(query *q, cell *l, pl_ctx l_ctx, cell *tail, pl_ctx tail_ctx, cell **out)
{
	unsigned n = 0;

	{
		cell *p = l;
		PROLOG_LIST_HANDLER(p);

		while (is_list(p)) {
			n++;
			p = PROLOG_LIST_TAIL(p);
		}
	}

	cell *dst = alloc_heap(q, (n * 2) + tail->num_cells);

	if (!dst)
		return false;

	cell *w = dst;
	cell *p = l;
	PROLOG_LIST_HANDLER(p);

	while (is_list(p)) {
		cell *h = PROLOG_LIST_HEAD(p);
		make_struct(w, g_dot_s, 2, 0);
		w->num_cells = 1 + 1 + 0;	// patched below
		w++;
		*w = *h;
		share_cell(w);
		w++;
		p = PROLOG_LIST_TAIL(p);
	}

	dup_cells_by_ref(w, tail, tail_ctx, tail->num_cells);

	// Patch each cons cell's extent, innermost last: cell i spans
	// everything from itself to the end.

	cell *end = dst + (n * 2) + tail->num_cells;

	for (unsigned i = 0; i < n; i++) {
		cell *c = dst + (i * 2);
		c->num_cells = (unsigned)(end - c);
	}

	*out = dst;
	return true;
}

static bool bif_dcg_string_prefix_3(query *q)
{
	GET_FIRST_ARG(p1,any);
	GET_NEXT_ARG(p2,any);
	GET_NEXT_ARG(p3,any);

	cell *l = p1;
	pl_ctx l_ctx = p1_ctx;
	cell *s = p3;
	pl_ctx s_ctx = p3_ctx;
	PROLOG_LIST_HANDLER(l);
	PROLOG_LIST_HANDLER(s);

	while (is_list(l)) {
		if (is_var(s)) {
			// Ran out of bound input: build what is left plus the tail
			// and bind it. Covers both an unbound S0 and a partial one.

			cell *tmp = NULL;

			if (!sp_construct(q, l, l_ctx, p2, p2_ctx, &tmp))
				return throw_error(q, q->st.instr, q->st.cur_ctx, "resource_error", "memory");

			return unify(q, s, s_ctx, tmp, q->st.cur_ctx);
		}

		if (!is_list(s))
			return false;

		cell *lh = PROLOG_LIST_HEAD(l);
		cell *sh = PROLOG_LIST_HEAD(s);
		sh = deref(q, sh, s_ctx);
		pl_ctx sh_ctx = q->latest_ctx;

		if (!unify(q, lh, l_ctx, sh, sh_ctx))
			return false;

		l = PROLOG_LIST_TAIL(l);
		s = PROLOG_LIST_TAIL(s);
		s = deref(q, s, s_ctx);
		s_ctx = q->latest_ctx;
	}

	return unify(q, p2, p2_ctx, s, s_ctx);
}

builtins g_dcgs_bifs[] =
{
	{"$dcg_rule", 2, bif_dcg_rule_2, "+term,-term", false, false, BLAH},
	{"$dcg_body", 4, bif_dcg_body_4, "+term,?term,?term,-term", false, false, BLAH},
	{"$string_prefix", 3, bif_dcg_string_prefix_3, "+term,?term,?term", false, false, BLAH},

	{0}
};
