%{
/*
 * lang.y - BOOM grammar
 *
 *  Copyright 2012 by Werner Almesberger
 *
 * 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.
 */


#include <stdlib.h>
#include <assert.h>

#include "util.h"
#include "relop.h"
#include "param.h"
#include "chr.h"
#include "db.h"
#include "subst.h"
#include "lang.h"

#include "y.tab.h"


const char *dollar;

struct action hierarchy;
struct subst *substitutions = NULL;

static struct currency *curr;


static struct field_stack {
	const struct field *field;
	struct field_stack *next;
} *field_stack = NULL;


static void push_field(const struct field *field)
{
	struct field_stack *entry;

	entry = alloc_type(struct field_stack);
	entry->field = field;
	entry->next = field_stack;
	field_stack = entry;
}


static void pop_fields(const struct field *last)
{
	struct field_stack *entry;
	const struct field *popped;

	do {
		assert(field_stack);
		entry = field_stack;
		popped = entry->field;
		field_stack = entry->next;
		free(entry);
	} while (popped != last);
}


static const struct field *top_field(void)
{
	return field_stack ? field_stack->field : NULL;
}


static struct subst *parse_jump(const char *keyword, const char *target)
{
	if (!strcmp(keyword, "break"))
		return subst_break(target);
	if (!strcmp(keyword, "continue"))
		return subst_continue(target);
	if (!strcmp(keyword, "end") || !strcmp(keyword, "ignore"))
		yyerror("unreachable code");
	yyerrorf("unknown keyword \"%s\"", keyword);
}

%}


%union {
	const char *s;
	int num;
	double fnum;
	struct equiv *equiv;
	struct names *names;
	struct format *format;
	enum relop relop;
	struct field *field;
	struct selector *sel;
	struct condition *cond;
	struct action act;
	struct part *part;
	struct param *param;
	struct price *price;
	struct stock *stock;
	struct provider *prov;
	struct subst *subst;
};


%token			START_HIERARCHY START_CHAR START_INVENTORY
%token			START_EXCHANGE START_PROVIDERS START_SUBST START_BOM
%token			START_SYMBOLS
%token			BOM_EESCHEMA
%token			TOK_LE TOK_GE TOK_NL
%token	<s>		WORD PATTERN

%type	<num>		int
%type	<fnum>		float
%type	<equiv>		nameqs
%type	<names>		names nameq
%type	<format>	format
%type	<relop>		relop opt_relop
%type	<field>		/*rules*/ opt_rule rule opt_fields fields field
%type	<sel>		selectors
%type	<cond>		conditions condition
%type	<act>		hierarchy opt_wildcard action
%type	<part>		part inventory_item
%type	<param>		param params
%type	<price>		prices price
%type	<stock>		stock
%type	<prov>		providers provider
%type	<subst>		substitutions block opt_block
%type	<s>		word_or_dollar

%%

all:
	START_HIERARCHY hierarchy
		{
			hierarchy = $2;
		}
	| START_CHAR characteristics
	| START_INVENTORY inventory
	| START_EXCHANGE exchange
	| START_PROVIDERS providers
	| START_SUBST substitutions
		{
			substitutions = $2;
		}
	| START_BOM bom
	| START_SYMBOLS symbols
	;


/* ----- Characteristics hierarchy ----------------------------------------- */


hierarchy:
	nameset hierarchy
		{
			$$ = $2;
		}
	| action
		{
			$$ = $1;
		}
	;

nameset:
	'<' WORD '>' '=' names ';'
		{
			register_nameset($2, $5);
		}
	;

names:
	nameq
		{
			$$ = $1;
		}
	| nameq '<' names
		{
			$$ = $1;
			$$->next = $3;
		}
	;

nameq:
	nameqs
		{
			$$ = alloc_type(struct names);
			$$->equiv = $1;
			$$->next = NULL;
		}
	;

nameqs:
	WORD
		{
			$$ = alloc_type(struct equiv);
			$$->name = $1;
			$$->next = NULL;
		}
	| WORD '=' nameqs
		{
			$$ = alloc_type(struct equiv);
			$$->name = $1;
			$$->next = $3;
		}
	;

/*
 * @@@ for now, we can't select on a collective of fields. maybe later, with
 * a syntax change.
 *
rules:
		{
			$$ = NULL;
		}
	| rule rules
		{
			$$ = $1;
			$$->next = $2;
		}
	;
*/

opt_rule:
		{
			$$ = NULL;
		}
	| rule
		{
			$$ = $1;
		}
	;

rule:
	field
		{
			$1->prev = top_field();
			push_field($1);
		}
	    '{' selectors opt_wildcard '}'
		{
			$$ = $1;
			$$->sel = $4;
			$$->any = $5;
			$$->next = NULL;
			field_finalize($$);
			pop_fields($$);
		}
	;

selectors:
		{
			$$ = NULL;
		}
	| conditions ':' action selectors
		{
			$$ = new_selector();
			$$->cond = $1;
			$$->act = $3;
			$$->next = $4;
		}
	;

conditions:
	condition
		{
			$$ = $1;
		}
	| condition '|' conditions
		{
			$$ = $1;
			$$->next = $3;
		}
	;

condition:
	opt_relop WORD
		{
			$$ = new_condition($1, $2);
		}
	;

opt_wildcard:
		{
			$$.fields = NULL;
			$$.rules = NULL;
		}
	| '*' ':' action
		{
			$$ = $3;
		}
	;

action:
		{
			$<field>$ = (struct field *) top_field();
			push_field(top_field());
		}
	opt_fields opt_rule ';'
		{
			$$.fields = $2;
			$$.rules = $3;
			pop_fields($<field>1);
		}
	;

opt_relop:
		{
			$$ = rel_eq;
		}
	| relop
		{
			$$ = $1;
		}
	;

relop:
	'='
		{
			$$ = rel_eq;
		}
	| '<'
		{
			$$ = rel_lt;
		}
	| '>'
		{
			$$ = rel_gt;
		}
	| TOK_LE
		{
			$$ = rel_le;
		}
	| TOK_GE
		{
			$$ = rel_ge;
		}
	;

opt_fields:
		{
			$$ = NULL;
		}
	| '{' fields '}'
		{
			$$ = $2;
		}
	;

fields:
		{
			$$ = NULL;
		}
	| field
		{
			$1->prev = top_field();
			push_field($1);
		}
	    fields
		{
			$$ = $1;
			$$->next = $3;
			if ($3)
				$3->prev = $1;
		}
	;

field:
	WORD '=' format
		{
			$$ = new_field($1, $3, top_field());
		}
	;

format:
	'*'
		{
			$$ = alloc_type(struct format);
			$$->ops = &param_ops_name;
		}
	| '<' WORD '>'
		{
			$$ = alloc_type(struct format);
			$$->ops = &param_ops_set;
			$$->u.set = find_nameset($2);
			if (!$$->u.set)
				yyerrorf("unknown name set \"%s\"", $2);
		}
	| '#' WORD
		{
			$$ = alloc_type(struct format);
			$$->ops = &param_ops_abs;
			$$->u.abs = $2;
		}
	| '#' '#'
		{
			$$ = alloc_type(struct format);
			$$->ops = &param_ops_abs;
			$$->u.abs = NULL;
		}
	| '%' WORD
		{
			$$ = alloc_type(struct format);
			$$->ops = &param_ops_rel;
			$$->u.rel = field_find($2, top_field());
			if (!$$->u.rel)
				yyerrorf("unknown field \"%s\"", $2);
		}
	;


/* ----- Part characteristics --------------------------------------------- */


characteristics:
	| TOK_NL
	| part characteristics
		{
			part_finalize($1, &hierarchy);
		}
	;

part:
	WORD WORD params TOK_NL
		{
			$$ = part_add($1, $2);
			if ($$->param)
				yyerrorf("%s %s parameters already defined",
				    $1, $2);
			$$->param = $3;
		}
	;

params:
		{
			$$ = NULL;
		}
	| param params
		{
			$$ = $1;
			$$->next = $2;
		}
	;

param:
	WORD '=' WORD
		{
			$$ = alloc_type(struct param);
			$$->u.name = $1;
			$$->op = rel_eq;
			$$->value.u.s = $3;
		}
	;


/* ----- Part inventory ---------------------------------------------------- */


inventory:
	| TOK_NL
	| inventory_item inventory
		{
			part_dump(stderr, $1);
		}
	;

inventory_item:
	WORD WORD stock TOK_NL
		{
			$$ = part_add($1, $2);
			$3->provider = provider_add($1);
			part_add_stock($$, $3);
		}
	;

stock:
	WORD int int WORD float prices
		{
			$$ = alloc_type(struct stock);
			$$->provider = NULL;
			$$->cat = $1;
			$$->avail = $2;
			$$->package = $3;
			$$->curr = currency_lookup($4);
			if (!$$->curr)
				yyerrorf("unknown currency %s", $4);
			$$->add = $5;
			$$->price = $6;
		}
	;

prices:
	price
		{
			$$ = $1;
		}
	| price prices
		{
			$$ = $1;
			$$->next = $2;
		}
	;

price:
	int float
		{
			$$ = alloc_type(struct price);
			$$->qty = $1;
			$$->value = $2;
			$$->next = NULL;
		}
	;

int:
	WORD
		{
			char *end;

			$$ = strtol($1, &end, 10);
			if (*end)
				yyerrorf("\"%s\" is not an integer", $1);
		}
	;

float:
	WORD
		{
			char *end;

			$$ = strtod($1, &end);
			if (*end)
				yyerrorf("\"%s\" is not a number", $1);
		}
	;


/* ----- Currency exchange ------------------------------------------------- */


exchange:
	| TOK_NL
	| currency exchange
	;

currency:
	WORD
		{
			curr = currency_add($1);
		}
	    rates TOK_NL
	;

rates:
	| rate rates
	;

rate:
	WORD float
		{
			const struct currency *to;

			/* add and not lookup, since we just introduce the
			   currency here */
			to = currency_add($1);
			currency_exchange(curr, to, $2);
		}
	;


/* ----- Providers --------------------------------------------------------- */


providers:
		{
			$$ = NULL;
		}
	| TOK_NL
		{
			$$ = NULL;
		}
	| provider providers
		{
			$$ = $1;
			$$->next = $2;
		}
	;

provider:
	WORD WORD float float TOK_NL
		{
			$$ = provider_add($1);
			if ($$->curr)
				yyerrorf("provider %s is already defined", $1);
			$$->curr = currency_add($2);
			$$->shipping = $3;
			$$->minimum = $4;
		}
	;


/* ----- Substitutions ----------------------------------------------------- */


substitutions:
	block
		{
			$$ = $1;
			subst_finalize($$);
		}
	;

block:
		{
			$$ = NULL;
		}
	| word_or_dollar relop PATTERN opt_block block
		{
			if ($4) {
				if ($2 != rel_eq)
					yyerror("only = allow for matching");
				$$ = subst_match($1, $3, NULL);
				$$->u.match.block = $4;
			} else {
				$$ = subst_assign($1, $2, $3);
			}
			free((void *) $3);
			$$->next = $5;
		}
	| WORD WORD block
		{
			if (!strcmp($1, "print")) {
				$$ = subst_print($2);
				$$->next = $3;
			} else {
				$$ = parse_jump($1, $2);
				if ($3)
					yyerror("unreachable code");
			}
		}
	| WORD '$'
		{
			$$ = parse_jump($1, NULL);
		}
	| WORD
		{
			if (!strcmp($1, "end"))
				$$ = subst_end();
			else if (!strcmp($1, "ignore"))
				$$ = subst_ignore();
			else
				$$ = parse_jump($1, NULL);
		}
	| WORD PATTERN
		{
		}
	;

word_or_dollar:
	'$'
		{
			$$ = dollar;
		}
	| WORD
		{
			$$ = $1;
		}
	;

opt_block:
		{
			$$ = NULL;
		}
	| '{' block '}'
		{
			$$ = $2;
		}
	;


/* ----- KiCad BOM (from eeschema) ----------------------------------------- */


bom:
	BOM_EESCHEMA
		/*
		 * The KiCad BOM syntax is quite alien, so we just check the
		 * overall structure here, leave extracting the lines with
		 * actual content to the lexer, and do all the detail work in
		 * bom.c
		 */
	;


/* ----- Symbols (BOM supplement) ------------------------------------------ */


symbols:
	| TOK_NL
	| symbol symbols
	;

symbol:
	WORD WORD TOK_NL
		{
			bom_set_sym($1, $2);
		}
	;