/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  (this file is based on PCB, interactive printed circuit board design)
 *  Copyright (C) 1994,1995,1996, 2003, 2004 Thomas Nau
 *  Copyright (C) 2017,2018,2020 Tibor 'Igor2' Palinkas
 *
 *  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., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/pcb-rnd
 *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 *
 */

#define DRW_MAX_ARG 8

#define DA_FAR_SIDE 0x81
#define DA_THIS_SIDE 0x82

typedef struct {
	/* silk color tune */
	pcb_layergrp_t *backsilk_grp;
	rnd_color_t old_silk_color[PCB_MAX_LAYERGRP];

	pcb_layer_type_t vside, ivside;
	rnd_layergrp_id_t component, solder, side_copper_grp;
	rnd_layergrp_id_t backsilk_gid;


	/* copper ordering and virtual layers */
	legacy_vlayer_t lvly;
	char do_group[PCB_MAX_LAYERGRP]; /* This is the list of layer groups we will draw.  */
	rnd_layergrp_id_t drawn_groups[PCB_MAX_LAYERGRP]; /* This is the reverse of the order in which we draw them.  */
	int ngroups;
	char lvly_inited;
	char do_group_inited;

	/* when scripted: per call args */
	int argc;
	long argv[DRW_MAX_ARG];

	unsigned enable_silk_invis_clr:1;
} draw_everything_t;



typedef struct {
	int (*cmd)(pcb_draw_info_t *info, draw_everything_t *de);
	int argc;
} draw_call_t;

typedef enum {
	DI_IF,
	DI_AND,
	DI_NOT,
	DI_THEN,
	DI_STOP,
	DI_CALL,
	DI_ARG,
	DI_NL,

	/* fake expressions */
	DI_GUI,
	DI_CHECK_PLANES
} draw_inst_t;

typedef struct {
	draw_inst_t inst;
	draw_call_t call;
	long arg;
} draw_stmt_t;



#define GVT(x) vtdrw_ ## x
#define GVT_ELEM_TYPE draw_stmt_t
#define GVT_SIZE_TYPE size_t
#define GVT_DOUBLING_THRS 256
#define GVT_START_SIZE 64
#define GVT_FUNC RND_INLINE
#define GVT_SET_NEW_BYTES_TO 0

#include <genvector/genvector_impl.h>

#define GVT_REALLOC(vect, ptr, size)  realloc(ptr, size)
#define GVT_FREE(vect, ptr)           free(ptr)

#include <genvector/genvector_impl.c>

static vtdrw_t drw_script = {0};

static void draw_compile(const char *src);

static const char *draw_everything_recompile = NULL; /* set to the new script (pointing into a conf val) to get it compiled on next render */
static int draw_everything_error = 0; /* set to 1 on execution error so subsequent renders don't attempt to execute a failing script */


/*** Calls ***/

/* temporarily change the color of the other-side silk */
static int drw_silk_tune_color(pcb_draw_info_t *info, draw_everything_t *de)
{
	if (de->enable_silk_invis_clr && (de->backsilk_grp != NULL)) {
		rnd_cardinal_t n;
		for(n = 0; n < de->backsilk_grp->len; n++) {
			pcb_layer_t *ly = pcb_get_layer(PCB->Data, de->backsilk_grp->lid[n]);
			if (ly != NULL) {
				de->old_silk_color[n] = ly->meta.real.color;
				ly->meta.real.color = conf_core.appearance.color.invisible_objects;
			}
		}
	}
	return 0;
}

/* set back the color of the other-side silk */
static int drw_silk_restore_color(pcb_draw_info_t *info, draw_everything_t *de)
{
	if (de->enable_silk_invis_clr && (de->backsilk_grp != NULL)) {
		rnd_cardinal_t n;
		for(n = 0; n < de->backsilk_grp->len; n++) {
			pcb_layer_t *ly = pcb_get_layer(PCB->Data, de->backsilk_grp->lid[n]);
			if (ly != NULL)
				ly->meta.real.color = de->old_silk_color[n];
		}
	}
	return 0;
}

/* copper groups will be drawn in the order they were last selected
   in the GUI; build an array of their order */
static int drw_copper_order_UI(pcb_draw_info_t *info, draw_everything_t *de)
{
	int i;

	memset(de->do_group, 0, sizeof(de->do_group));

	de->lvly.top_fab = -1;
	de->lvly.top_assy = de->lvly.bot_assy = -1;

	for (de->ngroups = 0, i = 0; i < pcb_max_layer(PCB); i++) {
		pcb_layer_t *l = PCB_STACKLAYER(PCB, i);
		rnd_layergrp_id_t group = pcb_layer_get_group(PCB, pcb_layer_stack[i]);
		pcb_layergrp_t *grp = pcb_get_layergrp(PCB, group);
		unsigned int gflg = 0;

		if (grp != NULL)
			gflg = grp->ltype;

		if ((gflg & PCB_LYT_DOC) && (grp->purpi == F_fab)) {
			de->lvly.top_fab = group;
			de->lvly.top_fab_enable = has_auto(grp);
		}

		if ((gflg & PCB_LYT_DOC) && (grp->purpi == F_assy)) {
			if (gflg & PCB_LYT_TOP) {
				de->lvly.top_assy = group;
				de->lvly.top_assy_enable = has_auto(grp);
			}
			else if (gflg & PCB_LYT_BOTTOM) {
				de->lvly.bot_assy = group;
				de->lvly.bot_assy_enable = has_auto(grp);
			}
		}

		if ((gflg & PCB_LYT_SILK) || (gflg & PCB_LYT_DOC) || (gflg & PCB_LYT_MASK) || (gflg & PCB_LYT_PASTE) || (gflg & PCB_LYT_BOUNDARY) || (gflg & PCB_LYT_MECH)) /* do not draw silk, mask, paste and boundary here, they'll be drawn separately */
			continue;

		if (l->meta.real.vis && !de->do_group[group]) {
			de->do_group[group] = 1;
			de->drawn_groups[de->ngroups++] = group;
		}
	}
	de->lvly_inited = 1;
	de->do_group_inited = 1;
	return 0;
}

/* draw all layers in layerstack order */
static int drw_copper(pcb_draw_info_t *info, draw_everything_t *de)
{
	int i;

	if (!de->do_group_inited) {
		rnd_message(RND_MSG_ERROR, "draw script: drw_copper called without prior copper ordering\nCan not draw copper layers. Fix the script.\n");
		return -1;
	}

	for (i = de->ngroups - 1; i >= 0; i--) {
		rnd_layergrp_id_t group = de->drawn_groups[i];

		if (pcb_layer_gui_set_glayer(PCB, group, 0, &info->xform_exporter)) {
			int is_current = 0;
			rnd_layergrp_id_t cgrp = PCB_CURRLAYER(PCB)->meta.real.grp;

			if ((cgrp == de->solder) || (cgrp == de->component)) {
				/* current group is top or bottom: visibility depends on side we are looking at */
				if (group == de->side_copper_grp)
					is_current = 1;
			}
			else {
				/* internal layer displayed on top: current group is solid, others are "invisible" */
				if (group == cgrp)
					is_current = 1;
			}

			pcb_draw_layer_grp(info, group, is_current);
			rnd_render->end_layer(rnd_render);
		}
	}
	return 0;
}

/* draw all 'invisible' (other-side) layers */
static int drw_invis(pcb_draw_info_t *info, draw_everything_t *de)
{
	if ((info->xform_exporter != NULL) && !info->xform_exporter->check_planes && pcb_layer_gui_set_vlayer(PCB, PCB_VLY_INVISIBLE, 0, &info->xform_exporter)) {
		pcb_draw_silk_doc(info, de->ivside, PCB_LYT_DOC, 0, 1);
		pcb_draw_silk_doc(info, de->ivside, PCB_LYT_SILK, 0, 1);

		TODO("remove this if nobody reports a bug...");
		if (0) {
			rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_RESET, pcb_draw_out.direct, info->drawn_area);
			rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_POSITIVE, pcb_draw_out.direct, info->drawn_area);
			rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_FLUSH, pcb_draw_out.direct, info->drawn_area);
			rnd_render->end_layer(rnd_render);
		}
	}
	return 0;
}

static int drw_pstk(pcb_draw_info_t *info, draw_everything_t *de)
{
	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_RESET, pcb_draw_out.direct, info->drawn_area);
	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_POSITIVE, pcb_draw_out.direct, info->drawn_area);
	if (rnd_render->gui) {
		rnd_xform_t tmp;
		xform_setup(info, &tmp, NULL);
		pcb_draw_ppv(info, info->xform->show_solder_side ? de->solder : de->component);
		info->xform = NULL; info->layer = NULL;
	}
	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_FLUSH, pcb_draw_out.direct, info->drawn_area);
	return 0;
}


/* Draw the solder mask if turned on */
static int drw_mask(pcb_draw_info_t *info, draw_everything_t *de)
{
	rnd_layergrp_id_t gid;

	gid = pcb_layergrp_get_top_mask();
	if ((gid >= 0) && (pcb_layer_gui_set_glayer(PCB, gid, 0, &info->xform_exporter))) {
		pcb_draw_mask(info, PCB_COMPONENT_SIDE);
		rnd_render->end_layer(rnd_render);
	}

	gid = pcb_layergrp_get_bottom_mask();
	if ((gid >= 0) && (pcb_layer_gui_set_glayer(PCB, gid, 0, &info->xform_exporter))) {
		pcb_draw_mask(info, PCB_SOLDER_SIDE);
		rnd_render->end_layer(rnd_render);
	}

	return 0;
}

static int drw_hole(pcb_draw_info_t *info, draw_everything_t *de)
{
	int plated, unplated;

	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_RESET, pcb_draw_out.direct, info->drawn_area); 
	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_POSITIVE, pcb_draw_out.direct, info->drawn_area);

	pcb_board_count_holes(PCB, &plated, &unplated, info->drawn_area);

	if (plated && pcb_layer_gui_set_vlayer(PCB, PCB_VLY_PLATED_DRILL, 0, &info->xform_exporter)) {
		pcb_draw_pstk_holes(info, de->side_copper_grp, PCB_PHOLE_PLATED);
		rnd_render->end_layer(rnd_render);
	}

	if (unplated && pcb_layer_gui_set_vlayer(PCB, PCB_VLY_UNPLATED_DRILL, 0, &info->xform_exporter)) {
		pcb_draw_pstk_holes(info, de->side_copper_grp, PCB_PHOLE_UNPLATED);
		rnd_render->end_layer(rnd_render);
	}

	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_FLUSH, pcb_draw_out.direct, info->drawn_area);
	return 0;
}

static int drw_paste(pcb_draw_info_t *info, draw_everything_t *de)
{
	rnd_layergrp_id_t gid;
	rnd_bool paste_empty;

	gid = pcb_layergrp_get_top_paste();
	if (gid >= 0)
		paste_empty = pcb_layergrp_is_empty(PCB, gid);
	if ((gid >= 0) && (pcb_layer_gui_set_glayer(PCB, gid, paste_empty, &info->xform_exporter))) {
		pcb_draw_paste(info, PCB_COMPONENT_SIDE);
		rnd_render->end_layer(rnd_render);
	}

	gid = pcb_layergrp_get_bottom_paste();
	if (gid >= 0)
		paste_empty = pcb_layergrp_is_empty(PCB, gid);
	if ((gid >= 0) && (pcb_layer_gui_set_glayer(PCB, gid, paste_empty, &info->xform_exporter))) {
		pcb_draw_paste(info, PCB_SOLDER_SIDE);
		rnd_render->end_layer(rnd_render);
	}

	return 0;
}

static int drw_boundary_mech(pcb_draw_info_t *info, draw_everything_t *de)
{
	pcb_draw_boundary_mech(info);
	return 0;
}

static int drw_virtual(pcb_draw_info_t *info, draw_everything_t *de)
{
	draw_virtual_layers(info, &de->lvly);
	if ((rnd_render->gui) || (!info->xform_caller->omit_overlay)) {
		rnd_xform_t tmp;
		xform_setup(info, &tmp, NULL);
		draw_rats(info, info->drawn_area);
		draw_pins_and_pads(info, de->component, de->solder);
		info->xform = NULL; info->layer = NULL;
	}

	return 0;
}

static int drw_marks(pcb_draw_info_t *info, draw_everything_t *de)
{
	if (rnd_render->gui) {
		rnd_xform_t tmp;
		xform_setup(info, &tmp, NULL);
		draw_xor_marks(info);
	}
	return 0;
}

static int drw_layers(pcb_draw_info_t *info, draw_everything_t *de)
{
	pcb_layer_type_t loc, typ = de->argv[1];
	int setgrp = 1, invis = 0;

	if (de->argc < 2)
		return -1;

	if ((de->argc > 2) && de->argv[2])
		invis = 1;

	if (de->argv[0] == DA_FAR_SIDE) loc = de->ivside;
	else if (de->argv[0] == DA_THIS_SIDE) loc = de->vside;
	else loc = de->argv[0];

	pcb_draw_silk_doc(info, loc, typ, setgrp, invis);
	return 0;
}

static int drw_ui_layers(pcb_draw_info_t *info, draw_everything_t *de)
{
	draw_ui_layers(info);
	return 0;
}

/*** Execute ***/

/* Fallback: the original C implementation, just in case the script fails */
RND_INLINE void draw_everything_hardwired(pcb_draw_info_t *info, draw_everything_t *de)
{
	drw_silk_tune_color(info, de);
	drw_copper_order_UI(info, de);
	drw_invis(info, de);

	/* Draw far side doc and silks */
	pcb_draw_silk_doc(info, de->ivside, PCB_LYT_SILK, 1, 0);
	pcb_draw_silk_doc(info, de->ivside, PCB_LYT_DOC, 1, 0);

	drw_copper(info, de);

	if (conf_core.editor.check_planes && rnd_render->gui)
		return;

	drw_pstk(info, de);
	drw_mask(info, de);

	/* Draw doc and silks */
	pcb_draw_silk_doc(info, PCB_LYT_INTERN, PCB_LYT_SILK, 1, 0);
	pcb_draw_silk_doc(info, PCB_LYT_INTERN, PCB_LYT_DOC, 1, 0);
	pcb_draw_silk_doc(info, 0, PCB_LYT_DOC, 1, 0);
	pcb_draw_silk_doc(info, de->vside, PCB_LYT_SILK, 1, 0);
	pcb_draw_silk_doc(info, de->vside, PCB_LYT_DOC, 1, 0);

	/* holes_after: draw holes after copper, silk and mask, to make sure it punches through everything. */
	drw_hole(info, de);

	drw_paste(info, de);

	drw_boundary_mech(info, de);

	drw_virtual(info, de);
	drw_ui_layers(info, de);

	drw_marks(info, de);
}

#define IF_NOT(cond) (not ? (cond) : !(cond))

RND_INLINE int draw_everything_scripted(pcb_draw_info_t *info, draw_everything_t *de)
{
	draw_stmt_t *s, *end, *call;
	int in_if, false_if = 0, not = 0;

	/* compile on first render */
	if (draw_everything_recompile != NULL) {
		draw_compile(draw_everything_recompile);
		draw_everything_recompile = NULL;
		draw_everything_error = 0;
	}

	if (draw_everything_error || (drw_script.used == 0))
		return -1;

	/* bytecode execution */
	end = drw_script.array + drw_script.used;
	for(s = drw_script.array; s < end; s++) {
		switch(s->inst) {
			case DI_STOP: if (!false_if) return 0; break;

			/* call with arg processing */
			case DI_CALL: call = s; de->argc = 0; break;
			case DI_ARG:  de->argv[de->argc++] = s->arg; break;
			case DI_NL:
				if ((call != NULL) && !false_if) {
					if (call->call.cmd(info, de) != 0)
						return -1;
				}
				call = NULL;
				in_if = 0; /* just in case 'then' is missing */
				false_if = 0;
				break;

			/* if with fake expression processing */
			case DI_IF:           in_if = 1; false_if = 0; break;
			case DI_AND:          if (!in_if) return -1; break;
			case DI_NOT:          if (!in_if) return -1; not = 1; break;
			case DI_THEN:         if (!in_if) return -1; in_if = 0; break;
			case DI_GUI:          if (IF_NOT(rnd_render->gui)) false_if = 1; not = 0; break;
			case DI_CHECK_PLANES: if (IF_NOT((info->xform_exporter != NULL) && (info->xform_exporter->check_planes))) false_if = 1; not = 0; break;
		}
	}

	return 0;
}

static void draw_everything(pcb_draw_info_t *info)
{
	draw_everything_t de;
	rnd_xform_t tmp;

	setup_again:;

	de.backsilk_grp = NULL;
	de.lvly_inited = 0;
	de.do_group_inited = 0;

	xform_setup(info, &tmp, NULL);
	rnd_render->render_burst(rnd_render, RND_HID_BURST_START, info->drawn_area);
	de.solder = de.component = -1;
	pcb_layergrp_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_COPPER, &de.solder, 1);
	pcb_layergrp_list(PCB, PCB_LYT_TOP | PCB_LYT_COPPER, &de.component, 1);
	de.side_copper_grp = info->xform->show_solder_side ? de.solder : de.component;
	de.ivside = PCB_LYT_INVISIBLE_SIDE();
	de.vside = PCB_LYT_VISIBLE_SIDE();
	de.backsilk_gid = ((!info->xform->show_solder_side) ? pcb_layergrp_get_bottom_silk() : pcb_layergrp_get_top_silk());
	de.backsilk_grp = pcb_get_layergrp(PCB, de.backsilk_gid);

	de.enable_silk_invis_clr = info->xform->enable_silk_invis_clr;

	/* ugly special case for compatibility: far-side silk is rendered grey on GUI... */
	if ((rnd_render == rnd_gui) && rnd_render->gui)
		de.enable_silk_invis_clr = 1;

	if (draw_everything_scripted(info, &de) < 0) {
		if (!draw_everything_error) {
			rnd_message(RND_MSG_ERROR, "render_script: runtime error; falling back to hardwired C rendering\n");
			draw_everything_error = 1; /* let the error happen only once per compilation */
			goto setup_again; /* a broken rendering may have left invalid states behind */
		}
		draw_everything_hardwired(info, &de);
	}

	drw_silk_restore_color(info, &de);
}


/*** Compile ***/
typedef struct {
	const char *full;
	int hash;

	draw_inst_t inst;
	int (*cmd)(pcb_draw_info_t *info, draw_everything_t *de);
	long arg;
} drw_kw_t;

#define HASH(s) ((s[0] << 8) | (s[1]))
#define KW_MAXLEN 24

static drw_kw_t drw_kw[] = {
	{"if",              0, DI_IF, NULL, 0},
	{"and",             0, DI_AND, NULL, 0},
	{"not",             0, DI_NOT, NULL, 0},
	{"then",            0, DI_THEN, NULL, 0},
	{"stop",            0, DI_STOP, NULL, 0},

	{"GUI",             0, DI_GUI, NULL, 0},
	{"check_planes",    0, DI_CHECK_PLANES, NULL, 0},

	{"silk_tune_color", 0, DI_CALL, drw_silk_tune_color, 0},
	{"copper_order_UI", 0, DI_CALL, drw_copper_order_UI, 0},
	{"drw_copper",      0, DI_CALL, drw_copper, 0},
	{"drw_invis",       0, DI_CALL, drw_invis, 0},
	{"drw_pstk",        0, DI_CALL, drw_pstk, 0},
	{"drw_mask",        0, DI_CALL, drw_mask, 0},
	{"drw_layers",      0, DI_CALL, drw_layers, 0},
	{"drw_hole",        0, DI_CALL, drw_hole, 0},
	{"drw_paste",       0, DI_CALL, drw_paste, 0},
	{"drw_virtual",     0, DI_CALL, drw_virtual, 0},
	{"drw_ui_layers",   0, DI_CALL, drw_ui_layers, 0},
	{"drw_marks",       0, DI_CALL, drw_marks, 0},
	{"drw_boundary_mech",0,DI_CALL, drw_boundary_mech, 0},


	{"global",          0, DI_ARG,  NULL, 0},
	{"this_side",       0, DI_ARG,  NULL, DA_THIS_SIDE},
	{"far_side",        0, DI_ARG,  NULL, DA_FAR_SIDE},

	{NULL, 0, 0, NULL }
};

static void draw_compile(const char *src)
{
	const char *start, *next;
	int line = 1, nst = 0, cmd_idx = -1;
	char kw0[KW_MAXLEN+2];

	/* calculate hash values for keywords on first execution */
	if (drw_kw[0].hash == 0) {
		drw_kw_t *k;
		for(k = drw_kw; k->full != NULL; k++)
			k->hash = HASH(k->full);
	}

	/* clear the script */
	drw_script.used = 0;

	/* parse word by word */
	for(start = src; start != NULL; start = next) {
		draw_stmt_t *stmt;
		const drw_kw_t *k;
		drw_kw_t karg;
		int h, len, bad;

		/* skip leading whitespace */
		while((*start == ' ') || (*start == '\t')) start++;

		if (*start == '\0')
			break;

		/* ignore comments up to the next newline */
		if (*start == '#')
			for(start++; (*start != '\n') && (*start != '\r') && (*start != '\0'); start++) ;

		/* newline is a special case as it is an instruction */
		if ((*start == '\n') || (*start == '\r')) {
			cmd_idx = -1;
			if ((drw_script.used > 0) && (drw_script.array[drw_script.used - 1].inst != DI_NL)) {
				stmt = vtdrw_alloc_append(&drw_script, 1);
				stmt->inst = DI_NL;
				nst++;
			}
			if (*start == '\n')
				line++;
			next = start + 1;
			continue;
		}

		/* seek end of the current word in 'next' */
		next = start;
		while(!isspace(*next) && (*next != '\0')) next++;

		/* keywords are at least 2 chars long, required for the hash */
		len = next - start;
		if (len < 2) {
			strncpy(kw0, start, len); kw0[len] = '\0';
			rnd_message(RND_MSG_ERROR, "render_script: syntax error in line %d: keyword '%s' too short\n", line, kw0);
			continue;
		}
		if (len > KW_MAXLEN) {
			memcpy(kw0, start, KW_MAXLEN);
			kw0[KW_MAXLEN] = '\0';
			rnd_message(RND_MSG_ERROR, "render_script: syntax error in line %d: keyword %s... too long\n", line, kw0);
			continue;
		}

		/* look up the keyword speeding up the process with a hash comparison that skips the strncmp() most of the time */
		h = HASH(start);
		for(k = drw_kw; k->full != NULL; k++)
			if ((k->hash == h) && (strncmp(k->full, start, len) == 0) && (k->full[len] == '\0'))
				break;

		bad = (k->full == NULL);

		/* not a known keyword, could be a number */
		if (bad) {
			if ((start[0] == '+') || (start[0] == '-') || isdigit(start[0])) {
				char *end;
				long l = strtol(start, &end, 10);
				if (isspace(*end) || (*end == '\0')) {
					k = &karg;
					karg.inst = DI_ARG;
					karg.arg = l;
					bad = 0;
				}
			}
		}

		/* not a known keyword or number, could be a layer type */
		if (bad) {
			pcb_layer_type_t lyt;

			strncpy(kw0, start, len); kw0[len] = '\0';
			lyt = pcb_layer_type_str2bit(kw0);
			if (lyt > 0) {
				k = &karg;
				karg.inst = DI_ARG;
				karg.arg = lyt;
				bad = 0;
			}
		}

		if (bad) {
			strncpy(kw0, start, len); kw0[len] = '\0';
			rnd_message(RND_MSG_ERROR, "render_script: keyword '%s' not found in line %d\n", kw0, line);
			continue;
		}

		if ((cmd_idx >= 0) && (drw_script.array[cmd_idx].call.argc >= DRW_MAX_ARG)) {
			rnd_message(RND_MSG_ERROR, "render_script: too many arguments in line %d; ignored after %d arguments\n", line, DRW_MAX_ARG);
			continue;
		}

		stmt = vtdrw_alloc_append(&drw_script, 1);
		stmt->inst = k->inst;
		stmt->call.cmd = k->cmd;
		stmt->arg = k->arg;

		/* remember last command or update last command's argc */
		if (cmd_idx == -1) {
			cmd_idx = drw_script.used - 1;
			drw_script.array[cmd_idx].call.argc = 0;
		}
		else
			drw_script.array[cmd_idx].call.argc++;
	}

	rnd_message(RND_MSG_DEBUG, "render_script: compiled %ld instructions in %d statements\n", drw_script.used, nst);
}

#undef KW_MAXLEN
#undef HASH
#undef DRW_MAX_ARG
#undef DA_FAR_SIDE
#undef DA_THIS_SIDE
