/* * db.c - Parts database * * 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 #include #include #include "util.h" #include "lang.h" #include "chr.h" #include "db.h" static GTree *tree = NULL; static gint comp(gconstpointer a, gconstpointer b) { const struct part *pa = a; const struct part *pb = b; /* * Just subtracting the pointers may produce values outside the * range of gint values. */ return pa->domain == pb->domain ? pa->name < pb->name ? -1 : pa->name > pb->name ? 1 : 0 : pa->domain < pb->domain ? -1 : 1; } struct part *part_lookup(const char *domain, const char *name) { struct part part = { .domain = domain, .name = name, }; if (!tree) return NULL; return g_tree_lookup(tree, &part); } struct part *part_add(const char *domain, const char *name) { struct part part = { .domain = domain, .name = name, .param = NULL, .stock = NULL, }; struct part *p; if (!tree) tree = g_tree_new(comp); p = g_tree_lookup(tree, &part); if (!p) { p = alloc_type(struct part); *p = part; p->next = p->prev = p; g_tree_insert(tree, p, p); } return p; } void part_alias(struct part *a, struct part *b) { struct part *tmp, *tmp2; tmp = a->next; a->next = b; b->prev->next = tmp; tmp2 = b->prev; b->prev = a; tmp->prev = tmp2; } static void convert_params(struct param **params, const struct field *f, struct param ***res) { struct param **p, *prm; const struct selector *sel; const struct condition *cond; while (f) { for (p = params; *p; p = &(*p)->next) if ((*p)->u.name == f->name) break; if (!*p) { f = f->next; continue; } /* remove from list */ prm = *p; *p = prm->next; /* convert parameter */ prm->u.field = f; if (!evaluate(f->fmt, prm->value.u.s, &prm->value)) yyerrorf("invalid value for %s", f->name); /* add to result list */ **res = prm; *res = &prm->next; prm->next = NULL; /* process selections */ for (sel = f->sel; sel; sel = sel->next) { for (cond = sel->cond; cond; cond = cond->next) if (compare(f->fmt, &prm->value, cond->relop, &cond->value)) break; if (cond) { convert_params(params, sel->act.fields, res); convert_params(params, sel->act.rules, res); break; } } if (!sel) { convert_params(params, f->any.fields, res); convert_params(params, f->any.rules, res); } f = f->next; } } void part_finalize(struct part *part, const struct action *act) { struct param *param = part->param; struct param **res = &part->param; struct param *next; part->param = NULL; convert_params(¶m, act->fields, &res); convert_params(¶m, act->rules, &res); while (param) { yywarnf("extra parameter: %s", param->u.name); next = param->next; free(param); param = next; } } void part_add_stock(struct part *part, struct stock *s) { if (!part->stock) { part->stock = s; return; } yyerrorf("part %s %s already has stock", part->domain, part->name); } /* ----- Dumping ----------------------------------------------------------- */ static void dump_stock(FILE *file, const struct stock *s) { const struct price *p; fprintf(file, " %s %s %d %d %s %g", s->provider->name, s->cat, s->avail, s->package, s->curr->name, s->add); for (p = s->price; p; p = p->next) fprintf(file, " %d %g", p->qty, p->value); fprintf(file, "\n"); } void part_dump(FILE *file, const struct part *part) { const struct param *p; fprintf(file, "%s %s\n", part->domain, part->name); if (part->param) { fprintf(file, " "); for (p = part->param; p; p = p->next) { fprintf(file, " %s=", p->u.field->name); dump_param(file, p->u.field->fmt, &p->value); } fprintf(file, "\n"); } if (part->stock) dump_stock(file, part->stock); } static gboolean dump_prm_traverse(gpointer key, gpointer value, gpointer data) { struct part *p = key; FILE *file = data; (void) value; part_dump(file, p); return FALSE; } void parts_dump(FILE *file) { g_tree_foreach(tree, dump_prm_traverse, (void *) file); } /* ----- Currencies -------------------------------------------------------- */ static struct currency *currencies = NULL; const struct currency *currency_lookup(const char *name) { const struct currency *c; for (c = currencies; c; c = c->next) if (c->name == name) break; return c; } double currency_convert(const struct currency *from, const struct currency *to, double value) { const struct exchange *ex; for (ex = from->exchange; ex; ex = ex->next) if (ex->dst == to) return ex->rate*value; fprintf(stderr, "cannot convert %s to %s\n", from->name, to->name); exit(1); } struct currency *currency_add(const char *name) { struct currency **c; for (c = ¤cies; *c; c = &(*c)->next) if ((*c)->name == name) return *c; *c = alloc_type(struct currency); (*c)->name = name; (*c)->exchange = NULL; (*c)->next = NULL; return *c; } void currency_exchange(struct currency *from, const struct currency *to, double rate) { struct exchange **ex; for (ex = &from->exchange; *ex; ex = &(*ex)->next) if ((*ex)->dst == to) yyerrorf("exchange %s -> %s is already defined", from->name, to->name); *ex = alloc_type(struct exchange); (*ex)->dst = to; (*ex)->rate = rate; (*ex)->next = NULL; } /* ----- Provider database ------------------------------------------------- */ static struct provider *providers = NULL; struct provider *provider_lookup(const char *name) { struct provider *p; for (p = providers; p; p = p->next) if (p->name == name) break; return p; } struct provider *provider_add(const char *name) { struct provider *p; p = provider_lookup(name); if (p) return p; p = alloc_type(struct provider); p->name = name; p->curr = NULL; p->shipping = 0; p->minimum = 0; p->next = providers; providers = p; return p; } /* ----- Part search (by characteristics) ---------------------------------- */ struct sel_prm_data { struct param *param; const struct part **res; int n_res; }; static struct param *convert_vars(struct param *vars, const struct action *act) { struct param *res = NULL, **last = &res; convert_params(&vars, act->fields, &last); convert_params(&vars, act->rules, &last); free_vars(vars); return res; } /* * @@@ By matching fields (and not field names), we avoid having to check for * field compatibility. However, this also means that only fields that appear * in the same place in the hierarchy can be matched. * * For example, a tolerance TOL in T=R could thus never match the tolerance TOL * in T=C. This seems reasonable, but may need some more pondering. */ /* * @@@ We require all fields specified in the query to exist (and to match). * Could there be cases where this doesn't make sense ? */ static int params_match(const struct param *query, const struct param *part) { const struct param *q, *p; for (q = query; q; q = q->next) { for (p = part; p; p = p->next) { if (q->u.field != p->u.field) continue; if (compare(q->u.field->fmt, &p->value, q->op, &q->value)) goto next; return 0; } return 0; next: ; } return 1; } static gboolean sel_prm_traverse(gpointer key, gpointer value, gpointer data) { struct part *p = key; struct sel_prm_data *d = data; (void) value; if (!params_match(d->param, p->param)) return FALSE; d->res = realloc(d->res, sizeof(const struct part *)*(d->n_res+1)); if (!d->res) abort(); d->res[d->n_res++] = key; return FALSE; } const struct part **select_parametric(struct param *vars, const struct action *act) { struct sel_prm_data data = { .param = convert_vars(vars, act), .res = NULL, .n_res = 0, }; g_tree_foreach(tree, sel_prm_traverse, (void *) &data); if (data.n_res) data.res[data.n_res] = NULL; return data.res; }