%prefix forgecond_%

%{

/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - standard cschem attributes
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2023)
 *
 *  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.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

#include <stdio.h>
#include <ctype.h>
#include "cond.h"

#define STR_VAL_NULL -1000
#define STR_VAL_BASE -100

#define INST(a, b, c)   ((((long)a) << 16) | (((long)b) << 8) | ((long)c))

typedef enum {
	FGC_ABORT    = 0,
	FGC_ADD      = INST('A', 'D', 'D'),
	FGC_SUB      = INST('S', 'U', 'B'),
	FGC_MUL      = INST('M', 'U', 'L'),
	FGC_DIV      = INST('D', 'I', 'V'),
	FGC_MOD      = INST('M', 'O', 'D'),
	FGC_STR_EQ   = INST('S', 'E', 'Q'),
	FGC_STR_NEQ  = INST('S', 'N', 'Q'),
	FGC_EQ       = INST('E', 'Q', 0),
	FGC_NEQ      = INST('N', 'E', 'Q'),
	FGC_GT       = INST('G', 'T', 0),
	FGC_LT       = INST('L', 'T', 0),
	FGC_GTEQ     = INST('G', 'T', 'E'),
	FGC_LTEQ     = INST('L', 'T', 'E'),
	FGC_BIN_AND  = INST('&', 0, 0),
	FGC_BIN_OR   = INST('|', 0, 0),
	FGC_LOG_AND  = INST('&', '&', 0),
	FGC_LOG_OR   = INST('|', '|', 0),
	FGC_NEG      = INST('N', 'E', 'G'),
	FGC_NOT      = INST('N', 'O', 'T'),
	FGC_VAR1     = INST('V', 'R', '1'),
	FGC_VAR2     = INST('V', 'R', '2'),
	FGC_NUM      = INST('N', 'U', 'M'),
	FGC_STR      = INST('S', 'T', 'R')   /* string literal */
} forgecond_inst_t;

static void append(forgecond_ctx_t *ctx, long cod) { vtl0_append(&ctx->prg, cod); }

%}

%start cond

%token DIGIT LETTER

%left '|'
%left '&'
%left '+' '-'
%left '*' '/' '%'
%left '=' '!' '<' '>'
%left UMINUS   /* supplies precedence for unary minus */
%left UNOT     /* supplies precedence for unary not */

%union
{
	long l;
}

%type <l> LETTER
%type <l> DIGIT
%type <l> expr
%type <l> number
%type <l> id
%type <l> var
%type <l> str

%% /* beginning of rules section */

cond:
	expr
	;

expr:
	  '(' expr ')'          { /* nothing to generate */ }
	| str '=' '=' str       { append(ctx, FGC_STR_EQ); }
	| str '!' '=' str       { append(ctx, FGC_STR_NEQ); }
	| expr '=' '=' expr     { append(ctx, FGC_EQ); }
	| expr '>' '=' expr     { append(ctx, FGC_GTEQ); }
	| expr '<' '=' expr     { append(ctx, FGC_LTEQ); }
	| expr '>' expr         { append(ctx, FGC_GT); }
	| expr '<' expr         { append(ctx, FGC_LT); }
	| expr '!' '=' expr     { append(ctx, FGC_NEQ); }
	| expr '+' expr         { append(ctx, FGC_ADD); }
	| expr '-' expr         { append(ctx, FGC_SUB); }
	| expr '*' expr         { append(ctx, FGC_MUL); }
	| expr '/' expr         { append(ctx, FGC_DIV); }
	| expr '%' expr         { append(ctx, FGC_MOD); }
	| expr '&' expr         { append(ctx, FGC_BIN_AND); }
	| expr '|' expr         { append(ctx, FGC_BIN_OR); }
	| expr '&' '&' expr     { append(ctx, FGC_LOG_AND); }
	| expr '|' '|' expr     { append(ctx, FGC_LOG_OR); }
	| '-' expr %prec UMINUS { append(ctx, FGC_NEG); }
	| '!' expr %prec UNOT   { append(ctx, FGC_NOT); }
	| number                { append(ctx, FGC_NUM); append(ctx, $1); }
	;

number:
	  DIGIT                 {  $$ = $1; }
	| number DIGIT          {  $$ = 10 * $1 + $2; }
	;

id:
	  LETTER                {  if (ctx->strs.used != 0) gds_append(&ctx->strs, '\0'); $$ = ctx->strs.used; gds_append(&ctx->strs, $1); }
	| id LETTER             {  $$ = $1; gds_append(&ctx->strs, $2); }
	| id DIGIT              {  $$ = $1; gds_append(&ctx->strs, $2 + '0'); }
	;

var:
	  id                    { append(ctx, FGC_VAR1); append(ctx, $1); }
	| id '.' id             { append(ctx, FGC_VAR2); append(ctx, $1); append(ctx, $3); }
	;

str:
	  var                   { $$ = $1; }
	| '"'                   { ctx->in_quote = 1; }
	      id '"'            { ctx->in_quote = 0; append(ctx, FGC_STR); append(ctx, $3); }
	| '"' '"'               { append(ctx, FGC_STR); append(ctx, ctx->strs.used); gds_append(&ctx->strs, '\0'); }
	;

%% /* start of programs */

/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - standard cschem attributes
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2023)
 *
 *  This program is free software licensed under the GPL, see the top of
 *  the file cond_gram.y.
 */

static int lex(forgecond_ctx_t *ctx, forgecond_STYPE *yylval, const char **inp)
{
	int c;

	if (ctx->in_quote) {
		c = **inp;
		(*inp)++;
		if (c == '\\') {
			c = **inp;
			(*inp)++;
		}
		else if (c == '\"')
			return c;
		yylval->l = c;
		return LETTER;
	}

	do {
		c = **inp;
		(*inp)++;
	} while(isspace(c)); /* skip space */

	if (c == '\0')
		return forgecond_EOF;

	/* c is now nonblank */
	if (islower(c) || (c == '_')) {
		yylval->l = c;
		return LETTER;
	}
	if (isdigit(c)) {
		yylval->l = c - '0';
		return DIGIT;
	}
	return c;
}

void forgecond_dump(FILE *f, forgecond_ctx_t *ctx)
{
	long n, len;

	fprintf(f, "prg:\n");
	for(n = 0; n < ctx->prg.used; n++) {
		char s[4];
		long l, l1, l2;

		switch(ctx->prg.array[n]) {
			case FGC_NUM:  fprintf(f, " NUM: %ld\n", ctx->prg.array[++n]); break;
			case FGC_STR:  fprintf(f, " STR: [%ld]\n", ctx->prg.array[++n]); break;
			case FGC_VAR1: fprintf(f, " VAR1: [%ld]\n", ctx->prg.array[++n]); break;
			case FGC_VAR2:
				l1 = ctx->prg.array[++n];
				l2 = ctx->prg.array[++n];
				fprintf(f, " VAR2: [%ld] [%ld]\n", l1, l2); break;
			default:
				l = ctx->prg.array[n];
				s[0] = (l >> 16) & 0x0ff;
				s[1] = (l >> 8) & 0x0ff;
				s[2] = l & 0x0ff;
				s[3] = '\0';
				fprintf(f, " %s\n", s);
				break;
		}
	}

	fprintf(f, "strings:\n");
	for(n = 0; n < ctx->strs.used; n += len) {
		len = strlen(ctx->strs.array+n)+1;
		fprintf(f, " [%ld] '%s'\n", n, ctx->strs.array+n);
	}
}

static long pop_(forgecond_ctx_t *ctx, vtl0_t *stack)
{
	if (stack->used > 0)
		return (long)stack->array[--stack->used];
	forgecond_error_cb(ctx, "forgecond: stack underflow (internal error)\n");
	return 0;
}

static const char *get_str(forgecond_ctx_t *ctx, long idx, int allow_strval)
{
	if ((allow_strval) && (idx < 0)) {
		if (idx == STR_VAL_NULL)
			return NULL;
		idx = idx - STR_VAL_BASE;
		if ((idx >= 0) && (idx < sizeof(ctx->var_val)/sizeof(ctx->var_val[0]))) {
			const char *res = ctx->var_val[idx];
			ctx->var_val[idx] = NULL;
			return res;
		}
		forgecond_error_cb(ctx, "forgecond: invalid str val address (internal error)\n");
		return NULL;
	}

	if ((idx < 0) || (idx >= ctx->strs.used)) {
		forgecond_error_cb(ctx, "forgecond: invalid string literal address (internal error)\n");
		return NULL;
	}
	return ctx->strs.array + idx;

}

static const char *pop_str_(forgecond_ctx_t *ctx, vtl0_t *stack)
{
	return get_str(ctx, pop_(ctx, stack), 1);
}

#define push(val)  vtl0_append(stack, val)
#define pop()      pop_(ctx, stack)
#define pop_str()  pop_str_(ctx, stack)

static void push_var_val(forgecond_ctx_t *ctx, vtl0_t *stack, long name_idx2, long name_idx1)
{
	int n;
	const char *name1 = (name_idx1 >= 0) ? get_str(ctx, name_idx1, 0) : NULL;
	const char *name2 = (name_idx2 >= 0) ? get_str(ctx, name_idx2, 0) : NULL;
	const char *res = forgecond_var_resolve_cb(ctx, name2, name1);

	if (res == NULL) {
		push(STR_VAL_NULL);
		return;
	}

	for(n = 0; n < sizeof(ctx->var_val)/sizeof(ctx->var_val[0]); n++) {
		if (ctx->var_val[n] == NULL) {
			ctx->var_val[n] = res;
			push(STR_VAL_BASE+n);
			return;
		}
	}

	forgecond_error_cb(ctx, "forgecond: no slot for str val (internal error)\n");
	push(STR_VAL_NULL);
	return;
}



static int safe_strcmp(const char *a, const char *b)
{
	if ((a == NULL) || (b == NULL)) return -1; /* an invalid string never equals to anyting */
	return strcmp(a, b);
}

long forgecond_exec(forgecond_ctx_t *ctx, vtl0_t *stack)
{
	long n, a, b;
	const char *sa, *sb;

	stack->used = 0;
	for(n = 0; n < ctx->prg.used; n++) {
		switch(ctx->prg.array[n]) {

			/* push immediate */
			case FGC_VAR2:
				a = ctx->prg.array[++n];
				b = ctx->prg.array[++n];
				push_var_val(ctx, stack, a, b);
				break;
				/* fall-thru to push two string indices */
			case FGC_VAR1:
				push_var_val(ctx, stack, ctx->prg.array[++n], -1);
				break;

			case FGC_NUM:
			case FGC_STR:
				push(ctx->prg.array[++n]);
				break;

			case FGC_ADD:      push(pop() + pop()); break;
			case FGC_SUB:      a = pop(); b = pop(); push(b - a); break;
			case FGC_MUL:      push(pop() * pop()); break;
			case FGC_DIV:      a = pop(); b = pop(); push(b / a); break;
			case FGC_MOD:      a = pop(); b = pop(); push(b % a); break;
			case FGC_EQ:       push(pop() == pop()); break;
			case FGC_NEQ:      push(pop() != pop()); break;
			case FGC_GT:       a = pop(); b = pop(); push(b > a); break;
			case FGC_LT:       a = pop(); b = pop(); push(b < a); break;
			case FGC_GTEQ:     a = pop(); b = pop(); push(b >= a); break;
			case FGC_LTEQ:     a = pop(); b = pop(); push(b <= a); break;
			case FGC_BIN_AND:  push(pop() & pop()); break;
			case FGC_BIN_OR:   push(pop() | pop()); break;
			case FGC_LOG_AND:  push(pop() && pop()); break;
			case FGC_LOG_OR:   push(pop() || pop()); break;
			case FGC_NEG:      push(-pop()); break;
			case FGC_NOT:      push(!pop()); break;

			case FGC_STR_EQ:   sa = pop_str(); sb = pop_str(); push(safe_strcmp(sa, sb) == 0); break;
			case FGC_STR_NEQ:  sa = pop_str(); sb = pop_str(); push(safe_strcmp(sa, sb) != 0); break;
		}
	}

	return pop();
}

int forgecond_parse_str(forgecond_ctx_t *ctx, const char *str)
{
	forgecond_res_t res;
	forgecond_yyctx_t yyctx;

	ctx->strs.used = 0;
	ctx->prg.used = 0;
	ctx->in_quote = 0;
	memset(&ctx->var_val, 0, sizeof(ctx->var_val));

	if (str == NULL)
		return -1;

	if (forgecond_parse_init(&yyctx) != 0)
		return -1;

	do {
		forgecond_STYPE lval;
		int tok = lex(ctx, &lval, &str);
		res = forgecond_parse(&yyctx, ctx, tok, &lval);
	} while(res == forgecond_RES_NEXT);

	return (res == forgecond_RES_DONE) ? 0 : -1;
}

void forgecond_error(forgecond_ctx_t *ctx, forgecond_STYPE tok, const char *s)
{
	forgecond_error_cb(ctx, s);
}

void forgecond_uninit(forgecond_ctx_t *ctx)
{
	gds_uninit(&ctx->strs);
	vtl0_uninit(&ctx->prg);
}
