oxygenlance 1.0.5

A simple crate to run BF Joust matches in Rust.
Documentation
/*
 * gearlance bfjoust interpreter; based on cranklance, itself based on
 * chainlance.
 *
 * Copyright (c) 2011-2013 Heikki Kallasjoki
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

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

#include "gearlance_core.h"

/* #define TRACE 1 */

#ifdef CRANK_IT
#define CRANK(x) x
#define GEARLANCE_CORE_SYMBOL cranklance_core
#else
#define CRANK(x) /* no action */
#define GEARLANCE_CORE_SYMBOL gearlance_core
#endif

/* actual interpretation, impl */

__attribute__((noinline))
union opcode *GEARLANCE_CORE_SYMBOL(
    enum core_action act,
    struct gearlance_result *result,
    struct oplist *ops,
    union opcode *codeA,
    union opcode *codeB,
    union opcode *codeB2
) {
    unsigned char tape[MAXTAPE];

	static void * const xtableA[OP_MAX] = {
		[OP_INC] = &&op_incA, [OP_DEC] = &&op_decA,
		[OP_LEFT] = &&op_leftA, [OP_RIGHT] = &&op_rightA,
		[OP_WAIT] = &&op_waitA,
		[OP_LOOP1] = &&op_loop1A, [OP_LOOP2] = &&op_loop2A,
		[OP_REP1] = &&op_rep1A, [OP_REP2] = &&op_rep2A,
		[OP_IREP1] = &&op_rep1A, [OP_INNER1] = &&op_rep2A,
		[OP_INNER2] = &&op_inner2A, [OP_IREP2] = &&op_irep2A,
		[OP_DONE] = &&op_doneA,
	};
	static void * const xtableB[OP_MAX] = {
		[OP_INC] = &&op_incB, [OP_DEC] = &&op_decB,
		[OP_LEFT] = &&op_leftB, [OP_RIGHT] = &&op_rightB,
		[OP_WAIT] = &&op_waitB,
		[OP_LOOP1] = &&op_loop1B, [OP_LOOP2] = &&op_loop2B,
		[OP_REP1] = &&op_rep1B, [OP_REP2] = &&op_rep2B,
		[OP_IREP1] = &&op_rep1B, [OP_INNER1] = &&op_rep2B,
		[OP_INNER2] = &&op_inner2B, [OP_IREP2] = &&op_irep2B,
		[OP_DONE] = &&nextcycle, /* shortcut */
	};
	static void * const xtableB2[OP_MAX] = {
		[OP_INC] = &&op_decB, [OP_DEC] = &&op_incB,
		[OP_LEFT] = &&op_leftB, [OP_RIGHT] = &&op_rightB,
		[OP_WAIT] = &&op_waitB,
		[OP_LOOP1] = &&op_loop1B, [OP_LOOP2] = &&op_loop2B,
		[OP_REP1] = &&op_rep1B, [OP_REP2] = &&op_rep2B,
		[OP_IREP1] = &&op_rep1B, [OP_INNER1] = &&op_rep2B,
		[OP_INNER2] = &&op_inner2B, [OP_IREP2] = &&op_irep2B,
		[OP_DONE] = &&nextcycle, /* shortcut */
	};

	/* compilation to threaded code */

	if (act == core_compile_a || act == core_compile_b || act == core_compile_b2)
	{
		unsigned *offsets = smalloc(ops->len * sizeof *offsets);
		unsigned len = 0;

		for (unsigned at = 0; at < ops->len; at++)
		{
			offsets[at] = len;

			switch (ops->ops[at].type)
			{
			case OP_IREP2:
				len += 3;
				break;
			case OP_LOOP1: case OP_LOOP2: case OP_REP1: case OP_REP2: case OP_IREP1: case OP_INNER1:
				len += 2;
				break;
			default:
				len += 1;
				break;
			}
		}

		union opcode *code = smalloc(len * sizeof *code);

		void * const *xtable = (act == core_compile_a ? xtableA : act == core_compile_b ? xtableB : xtableB2);
		union opcode *opc = code;

		for (unsigned at = 0; at < ops->len; at++)
		{
			struct op *op = &ops->ops[at];

			opc->xt = xtable[op->type];
			opc++;

			switch (op->type)
			{
			case OP_LOOP1: case OP_LOOP2: case OP_REP2: case OP_INNER1:
				opc->match = &code[offsets[op->match+1]];
				opc++;
				break;
			case OP_REP1: case OP_IREP1:
				opc->count = op->count;
				opc++;
				break;
			case OP_IREP2:
				opc->count = op->count; opc++;
				opc->match = &code[offsets[op->match+1]]; opc++;
				break;
			default: /* no extra operands */
				break;
			}
		}

		sfree(offsets);

		return code;
	}

	/* state-holding variables */

    int repStackA[MAXNEST], repStackB[MAXNEST];

	union opcode *ipA = 0, *ipB = 0;
	unsigned char *ptrA = 0, *ptrB = 0, bcache = 0;
	int repA = 0, repB = 0, *repSA = 0, *repSB = 0;
	int deathsA = 0, deathsB = 0;
	int cycles = 0;
	int score = 0;

	/* execute with all tape lengths and both relative polarities */
	result->cycles = 0;

#define EXECUTE_ALL(sym, pol) \
    curpol = pol; \
	ret = &&sym; \
	for (tapesize = MINTAPE; tapesize <= MAXTAPE; tapesize++) \
	{ \
		result->win_by_time[pol][tapesize] = 0; \
		result->win_by_flag[pol][tapesize] = 0; \
		result->win_by_tape[pol][tapesize] = 0; \
	  \
		ipA = &codeA[0], ipB = &codeB[0]; \
	  \
		memset(tape, 0, tapesize); \
		ptrA = &tape[0], ptrB = &tape[tapesize-1]; \
		*ptrA = 128, *ptrB = 128; bcache = 128; \
	  \
		repSA = repStackA, repSB = repStackB; \
		deathsA = 0, deathsB = 0; \
	  \
		cycles = MAXCYCLES; \
		CRANK(memset(result->xstats.tape_max, 0, sizeof result->xstats.tape_max)); \
		CRANK(memset(result->xstats.heat_position, 0, sizeof result->xstats.heat_position)); \
	  \
		score = 0; \
		goto *ipA->xt; \
	sym: \
	    if (cycles == 0) result->win_by_time[pol][tapesize] = 1; \
		result->scores[pol][tapesize] = score; \
		result->cycles += (MAXCYCLES - cycles); \
	}

	void *ret;
	int tapesize = 0;
	int curpol = 0;

	EXECUTE_ALL(done_normal, 0);

    // switch to the opposite polarity configuration
    codeB = codeB2;

	EXECUTE_ALL(done_flipped, 1);

	return 0;

	/* actual core */

#define NEXTA ipA++; goto *ipB->xt
#define NEXTB ipB++; goto nextcycle

/* #define TRACE 1 */

nextcycle:
	cycles--;

	if (!tape[0]) deathsA++; else if (deathsA == 1) deathsA = 0;
	if (!tape[tapesize-1]) deathsB++; else if (deathsB == 1) deathsB = 0;

#ifdef TRACE
	printf("%6d: ", MAXCYCLES-1-cycles);
	for (int i = 0; i < tapesize; i++)
		printf("%c%02x ",
		       (ptrA - tape) == i
		       ? ((ptrB - tape) == i ? 'X' : 'A')
		       : ((ptrB - tape) == i ? 'B' : ' '),
		       tape[i]);
	printf("  dA %d  dB %d   ipA %td ipB %td   repA %d repB %d\n", deathsA, deathsB, ipA-codeA, ipB-codeB, repA, repB);
#endif

	if (deathsA >= 2 && deathsB >= 2)
	{
		result->win_by_flag[curpol][tapesize] = 1;
		goto *ret;
    }
	else if (deathsA >= 2)
	{
		result->win_by_flag[curpol][tapesize] = 1;
		score--;
		goto *ret;
	}
	else if (deathsB >= 2)
	{
		result->win_by_flag[curpol][tapesize] = 1;
		score++;
		goto *ret;
	}

#ifdef CRANK_IT
	result->xstats.heat_position[0][ptrA-tape]++;
	result->xstats.heat_position[1][ptrB-tape]++;
#endif

	if (!cycles)
		goto *ret;

	bcache = *ptrB;
	goto *ipA->xt;

fallA:
    result->win_by_tape[curpol][tapesize] = 1;
	deathsA = 2;
	goto *ipB->xt;

fallB:
    result->win_by_tape[curpol][tapesize] = 1;
	if (!tape[0]) deathsA++;
	if (deathsA >= 2)
		goto *ret;
	score++;
	goto *ret;

op_incA:
	(*ptrA)++;
	NEXTA;
op_incB:
	(*ptrB)++;
	NEXTB;
op_decA:
	(*ptrA)--;
	NEXTA;
op_decB:
	(*ptrB)--;
	NEXTB;

#ifdef CRANK_IT
#define MAXSTAT(ptr,i) { \
		unsigned char max = *ptr >= 128 ? 256 - *ptr : *ptr; \
		if (max > result->xstats.tape_max[i][ptr-tape]) result->xstats.tape_max[i][ptr-tape] = max; }
#endif

op_leftA:
	CRANK(MAXSTAT(ptrA, 0));
	ptrA--;
	if (ptrA < tape) goto fallA;
	NEXTA;
op_leftB:
	CRANK(MAXSTAT(ptrB, 1));
	ptrB++;
	if (ptrB >= &tape[tapesize]) goto fallB;
	NEXTB;
op_rightA:
	CRANK(MAXSTAT(ptrA, 0));
	ptrA++;
	if (ptrA >= &tape[tapesize]) goto fallA;
	NEXTA;
op_rightB:
	CRANK(MAXSTAT(ptrB, 1));
	ptrB--;
	if (ptrB < tape) goto fallB;
	NEXTB;

op_waitA:
	NEXTA;
op_waitB:
	NEXTB;

op_loop1A:
	ipA++;
	if (!*ptrA) ipA = ipA->match - 1;
	NEXTA;
op_loop1B:
	ipB++;
	if (!bcache) ipB = ipB->match - 1;
	NEXTB;
op_loop2A:
	ipA++;
	if (*ptrA) ipA = ipA->match - 1;
	NEXTA;
op_loop2B:
	ipB++;
	if (bcache) ipB = ipB->match - 1;
	NEXTB;

	/* simple (..) repeats with no corresponding {..} block */

op_rep1A:
	ipA++;
	*repSA++ = repA; repA = ipA->count;
	goto *(++ipA)->xt;
op_rep1B:
	ipB++;
	*repSB++ = repB; repB = ipB->count;
	goto *(++ipB)->xt;

op_rep2A:
	ipA++;
	if (--repA) ipA = ipA->match - 1;
	else repA = *--repSA;
	goto *(++ipA)->xt;
op_rep2B:
	ipB++;
	if (--repB) ipB = ipB->match - 1;
	else repB = *--repSB;
	goto *(++ipB)->xt;

	/* complex (..{ and }..) repeats; count in different dirs in }..) */

op_inner2A:
	*repSA++ = repA; repA = 1;
	goto *(++ipA)->xt;
op_inner2B:
	*repSB++ = repB; repB = 1;
	goto *(++ipB)->xt;

op_irep2A:
	ipA += 3;
	if (++repA <= ipA[-2].count) ipA = ipA[-1].match;
	else repA = *--repSA;
	goto *ipA->xt;
op_irep2B:
	ipB += 3;
	if (++repB <= ipB[-2].count) ipB = ipB[-1].match;
	else repB = *--repSB;
	goto *ipB->xt;

op_doneA:
	goto *ipB->xt;
}

/* core interface */