/*
 * Copyright (c) 2010, Swedish Institute of Computer Science
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/**
 * \file
 *	Logic engine used for quickly evaluating data constraints in relations.
 * \author
 * 	Nicolas Tsiftes <nvt@sics.se>
 */

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

#include "aql.h"
#include "lvm.h"

#define DEBUG DEBUG_NONE
#include "debug.h"

/*
 * The logic engine determines whether a logical  predicate is true for 
 * each tuple in a relation. It uses a stack-based execution model of
 * operations that are arranged in prefix (Polish) notation.
 */

/* Default option values. */
#ifndef LVM_MAX_NAME_LENGTH
#define LVM_MAX_NAME_LENGTH		16
#endif

#ifndef LVM_MAX_VARIABLE_ID
#define LVM_MAX_VARIABLE_ID		8
#endif

#ifndef LVM_USE_FLOATS
#define LVM_USE_FLOATS			0
#endif

#define IS_CONNECTIVE(op) ((op) & LVM_CONNECTIVE)

struct variable {
  operand_type_t type;
  operand_value_t value;
  char name[LVM_MAX_NAME_LENGTH + 1];
};
typedef struct variable variable_t;

struct derivation {
  operand_value_t max;
  operand_value_t min;
  uint8_t derived;
};
typedef struct derivation derivation_t;

/* Registered variables for a LVM expression. Their values may be 
   changed between executions of the expression. */
static variable_t variables[LVM_MAX_VARIABLE_ID - 1];

/* Range derivations of variables that are used for index searches. */
static derivation_t derivations[LVM_MAX_VARIABLE_ID - 1];

#if DEBUG
static void
print_derivations(derivation_t *d)
{
  int i;

  for(i = 0; i < LVM_MAX_VARIABLE_ID; i++) {
    if(d[i].derived) {
      printf("%s is constrained to (%ld,%ld)\n", variables[i].name, 
	d[i].min.l, d[i].max.l);
    }
  }
}
#endif /* DEBUG */

static variable_id_t
lookup(char *name)
{
  variable_t *var;

  for(var = variables; var <= &variables[LVM_MAX_VARIABLE_ID - 1] && var->name[0] != '\0'; var++) {
    if(strcmp(var->name, name) == 0) {
      break;
    }
  }

  return (variable_id_t)(var - &variables[0]);
}

static operator_t *
get_operator(lvm_instance_t *p)
{
  operator_t *operator;

  operator = (operator_t *)&p->code[p->ip];
  p->ip += sizeof(operator_t);
  return operator;
}

static void
get_operand(lvm_instance_t *p, operand_t *operand)
{
  memcpy(operand, &p->code[p->ip], sizeof(*operand));
  p->ip += sizeof(*operand);
}

static node_type_t
get_type(lvm_instance_t *p)
{
  node_type_t node_type;

  node_type = *(node_type_t *)(p->code + p->ip);
  p->ip += sizeof(node_type);

  return node_type;
}

static long
operand_to_long(operand_t *operand)
{
  switch(operand->type) {
  case LVM_LONG:
    return operand->value.l;
#if LVM_USE_FLOATS
  case LVM_FLOAT:
    return (long)operand->value.f;
    break;
#endif /* LVM_USE_FLOATS */
  case LVM_VARIABLE:
    return variables[operand->value.id].value.l;
  default:
    return 0;
  }
}

static lvm_status_t
eval_expr(lvm_instance_t *p, operator_t op, operand_t *result)
{
  int i;
  node_type_t type;
  operator_t *operator;
  operand_t operand[2];
  long value[2];
  long result_value;
  lvm_status_t r;

  for(i = 0; i < 2; i++) {
    type = get_type(p);
    switch(type) {
    case LVM_ARITH_OP:
      operator = get_operator(p);
      r = eval_expr(p, *operator, &operand[i]);
      if(LVM_ERROR(r)) {
	return r;
      }
      break;
    case LVM_OPERAND:
      get_operand(p, &operand[i]);
      break;
    default:
      return SEMANTIC_ERROR;
    }
    value[i] = operand_to_long(&operand[i]);
  }

  switch(op) {
  case LVM_ADD:
    result_value = value[0] + value[1];
    break;
  case LVM_SUB:
    result_value = value[0] - value[1];
    break;
  case LVM_MUL:
    result_value = value[0] * value[1];
    break;
  case LVM_DIV:
    if(value[1] == 0) {
      return MATH_ERROR;
    }
    result_value = value[0] / value[1];
    break;
  default:
    return EXECUTION_ERROR;
  }

  result->type = LVM_LONG;
  result->value.l = result_value;

  return TRUE;
}

static int
eval_logic(lvm_instance_t *p, operator_t *op)
{
  int i;
  int r;
  operand_t operand;
  long result[2];
  node_type_t type;
  operator_t *operator;
  long l1, l2;
  int logic_result[2];
  unsigned arguments;

  if(IS_CONNECTIVE(*op)) {
    arguments = *op == LVM_NOT ? 1 : 2;
    for(i = 0; i < arguments; i++) {
      type = get_type(p);
      if(type != LVM_CMP_OP) {
	return SEMANTIC_ERROR;
      }
      operator = get_operator(p);
      logic_result[i] = eval_logic(p, operator);
      if(LVM_ERROR(logic_result[i])) {
	return logic_result[i];
      }
    }

    if(*op == LVM_NOT) {
      return !logic_result[0];
    } else if(*op == LVM_AND) {
      return logic_result[0] == TRUE && logic_result[1] == TRUE;
    } else {
      return logic_result[0] == TRUE || logic_result[1] == TRUE;
    }
  }

  for(i = 0; i < 2; i++) {
    type = get_type(p);
    switch(type) {
    case LVM_ARITH_OP:
      operator = get_operator(p);
      r = eval_expr(p, *operator, &operand);
      if(LVM_ERROR(r)) {
	return r;
      }
      break;
    case LVM_OPERAND:
      get_operand(p, &operand);
      break;
    default:
      return SEMANTIC_ERROR;
    }
    result[i] = operand_to_long(&operand);
  }

  l1 = result[0];
  l2 = result[1];
  PRINTF("Result1: %ld\nResult2: %ld\n", l1, l2);

  switch(*op) {
  case LVM_EQ:
    return l1 == l2;
  case LVM_NEQ:
    return l1 != l2;
  case LVM_GE:
    return l1 > l2;
  case LVM_GEQ:
    return l1 >= l2;
  case LVM_LE:
    return l1 < l2;
  case LVM_LEQ:
    return l1 <= l2;
  default:
    break;
  }

  return EXECUTION_ERROR;
}

void
lvm_reset(lvm_instance_t *p, unsigned char *code, lvm_ip_t size)
{
  memset(code, 0, size);
  p->code = code;
  p->size = size;
  p->end = 0;
  p->ip = 0;
  p->error = 0;

  memset(variables, 0, sizeof(variables));
  memset(derivations, 0, sizeof(derivations));
}

lvm_ip_t
lvm_jump_to_operand(lvm_instance_t *p)
{
  lvm_ip_t old_end;

  old_end = p->end;
  p->end += sizeof(operator_t) + sizeof(node_type_t);
  if(p->end >= p->size) {
    p->error = __LINE__;
    p->end = old_end;
  }

  return old_end;
}

lvm_ip_t
lvm_shift_for_operator(lvm_instance_t *p, lvm_ip_t end)
{
  unsigned char *ptr;
  lvm_ip_t old_end;

  old_end = p->end;

  if(p->end + sizeof(operator_t) > p->size || end >= old_end) {
    p->error = __LINE__;
    return 0;
  }

  ptr = p->code + end;

  memmove(ptr + sizeof(operator_t) + sizeof(node_type_t), ptr, old_end - end);
  p->end = end;

  return old_end + sizeof(operator_t) + sizeof(node_type_t);
}

lvm_ip_t
lvm_get_end(lvm_instance_t *p)
{
  return p->end;
}

lvm_ip_t
lvm_set_end(lvm_instance_t *p, lvm_ip_t end)
{
  lvm_ip_t old_end;

  if(end >= p->size) {
    p->error = __LINE__;
    return p->end;
  }

  old_end = p->end;
  p->end = end;

  return old_end;
}

void
lvm_set_type(lvm_instance_t *p, node_type_t type)
{
  *(node_type_t *)(p->code + p->end) = type;
  p->end += sizeof(type);
}

lvm_status_t
lvm_execute(lvm_instance_t *p)
{
  node_type_t type;
  operator_t *operator;
  lvm_status_t status;

  p->ip = 0;
  status = EXECUTION_ERROR;
  type = get_type(p);
  switch(type) {
  case LVM_CMP_OP:
    operator = get_operator(p);
    status = eval_logic(p, operator);
    if(!LVM_ERROR(status)) {
      PRINTF("The statement is %s\n", status == TRUE ? "true" : "false");
    } else {
      PRINTF("Execution error: %d\n", (int)status);
    }
    break;
  default:
    PRINTF("Error: The code must start with a relational operator\n");
  }

  return status;
}

void
lvm_set_op(lvm_instance_t *p, operator_t op)
{
  lvm_set_type(p, LVM_ARITH_OP);
  memcpy(&p->code[p->end], &op, sizeof(op));
  p->end += sizeof(op);
}

void
lvm_set_relation(lvm_instance_t *p, operator_t op)
{
  lvm_set_type(p, LVM_CMP_OP);
  memcpy(&p->code[p->end], &op, sizeof(op));
  p->end += sizeof(op);
}

void
lvm_set_operand(lvm_instance_t *p, operand_t *op)
{
  lvm_set_type(p, LVM_OPERAND);
  memcpy(&p->code[p->end], op, sizeof(*op));
  p->end += sizeof(*op);
}

void
lvm_set_long(lvm_instance_t *p, long l)
{
  operand_t op;

  op.type = LVM_LONG;
  op.value.l = l;

  lvm_set_operand(p, &op);
}

lvm_status_t
lvm_register_variable(char *name, operand_type_t type)
{
  variable_id_t id;
  variable_t *var;

  id = lookup(name);
  if(id == LVM_MAX_VARIABLE_ID) {
    return VARIABLE_LIMIT_REACHED;
  }

  var = &variables[id];
  if(var->name[0] == '\0') {
    strncpy(var->name, name, sizeof(var->name) - 1);
    var->name[sizeof(var->name) - 1] = '\0';
    var->type = type;
  }

  return TRUE;
}

lvm_status_t
lvm_set_variable_value(char *name, operand_value_t value)
{
  variable_id_t id;

  id = lookup(name);
  if(id == LVM_MAX_VARIABLE_ID) {
    return INVALID_IDENTIFIER;
  }
  variables[id].value = value;
  return TRUE;
}

void
lvm_set_variable(lvm_instance_t *p, char *name)
{
  operand_t op;
  variable_id_t id;

  id = lookup(name);
  if(id < LVM_MAX_VARIABLE_ID) {
    PRINTF("var id = %d\n", id);
    op.type = LVM_VARIABLE;
    op.value.id = id;
    lvm_set_operand(p, &op);
  }
}

void
lvm_clone(lvm_instance_t *dst, lvm_instance_t *src)
{
  memcpy(dst, src, sizeof(*dst));
}

static void
create_intersection(derivation_t *result, derivation_t *d1, derivation_t *d2)
{
  int i;

  for(i = 0; i < LVM_MAX_VARIABLE_ID; i++) {
    if(!d1[i].derived && !d2[i].derived) {
      continue;
    } else if(d1[i].derived && !d2[i].derived) {
      result[i].min.l = d1[i].min.l;
      result[i].max.l = d1[i].max.l;
    } else if(!d1[i].derived && d2[i].derived) {
      result[i].min.l = d2[i].min.l;
      result[i].max.l = d2[i].max.l;
    } else {
      /* Both derivations have been made; create an
         intersection of the ranges. */
      if(d1[i].min.l > d2[i].min.l) {
        result[i].min.l = d1[i].min.l;
      } else {
        result[i].min.l = d2[i].min.l;
      }

      if(d1[i].max.l < d2[i].max.l) {
        result[i].max.l = d1[i].max.l;
      } else {
        result[i].max.l = d2[i].max.l;
      }
    }
    result[i].derived = 1;
  }

#if DEBUG
  PRINTF("Created an intersection of D1 and D2\n");
  PRINTF("D1: \n");
  print_derivations(d1);
  PRINTF("D2: \n");
  print_derivations(d2);
  PRINTF("Result: \n");
  print_derivations(result);
#endif /* DEBUG */
}

static void
create_union(derivation_t *result, derivation_t *d1, derivation_t *d2)
{
  int i;

  for(i = 0; i < LVM_MAX_VARIABLE_ID; i++) {
    if(!d1[i].derived && !d2[i].derived) {
      continue;
    } else if(d1[i].derived && !d2[i].derived) {
      result[i].min.l = d1[i].min.l;
      result[i].max.l = d1[i].max.l;
    } else if(!d1[i].derived && d2[i].derived) {
      result[i].min.l = d2[i].min.l;
      result[i].max.l = d2[i].max.l;
    } else {
      /* Both derivations have been made; create a
         union of the ranges. */
      if(d1[i].min.l > d2[i].min.l) {
        result[i].min.l = d2[i].min.l;
      } else {
        result[i].min.l = d1[i].min.l;
      }

      if(d1[i].max.l < d2[i].max.l) {
        result[i].max.l = d2[i].max.l;
      } else {
        result[i].max.l = d1[i].max.l;
      }
    }
    result[i].derived = 1;
  }

#if DEBUG
  PRINTF("Created a union of D1 and D2\n");
  PRINTF("D1: \n");
  print_derivations(d1);
  PRINTF("D2: \n");
  print_derivations(d2);
  PRINTF("Result: \n");
  print_derivations(result);
#endif /* DEBUG */
}

static int
derive_relation(lvm_instance_t *p, derivation_t *local_derivations)
{
  operator_t *operator;
  node_type_t type;
  operand_t operand[2];
  int i;
  int var;
  int variable_id;
  operand_value_t *value;
  derivation_t *derivation;

  type = get_type(p);
  operator = get_operator(p);

  if(IS_CONNECTIVE(*operator)) {
    derivation_t d1[LVM_MAX_VARIABLE_ID];
    derivation_t d2[LVM_MAX_VARIABLE_ID];

    if(*operator != LVM_AND && *operator != LVM_OR) {
      return DERIVATION_ERROR;
    }

    PRINTF("Attempting to infer ranges from a logical connective\n");

    memset(d1, 0, sizeof(d1));
    memset(d2, 0, sizeof(d2));

    if(LVM_ERROR(derive_relation(p, d1)) ||
       LVM_ERROR(derive_relation(p, d2))) {
      return DERIVATION_ERROR;
    }

    if(*operator == LVM_AND) {
      create_intersection(local_derivations, d1, d2);
    } else if(*operator == LVM_OR) {
      create_union(local_derivations, d1, d2);
    }
    return TRUE;
  }

  for(i = 0; i < 2; i++) {
    type = get_type(p);
    switch(type) {
    case LVM_OPERAND:
      get_operand(p, &operand[i]);
      break;
    default:
      return DERIVATION_ERROR;
    }
  }

  if(operand[0].type == LVM_VARIABLE && operand[1].type == LVM_VARIABLE) {
    return DERIVATION_ERROR;
  }

  /* Determine which of the operands that is the variable. */
  if(operand[0].type == LVM_VARIABLE) {
    if(operand[1].type == LVM_VARIABLE) {
      return DERIVATION_ERROR;
    }
    var = 0;
    variable_id = operand[0].value.id;
    value = &operand[1].value;
  } else {
    var = 1;
    variable_id = operand[1].value.id;
    value = &operand[0].value;
  }

  if(variable_id >= LVM_MAX_VARIABLE_ID) {
     return DERIVATION_ERROR;
  }

  PRINTF("variable id %d, value %ld\n", variable_id, *(long *)value);

  derivation = local_derivations + variable_id;
  /* Default values. */
  derivation->max.l = LONG_MAX;
  derivation->min.l = LONG_MIN;

  switch(*operator) {
  case LVM_EQ:
    derivation->max = *value;
    derivation->min = *value;
    break;
  case LVM_GE:
    derivation->min.l = value->l + 1;
    break;
  case LVM_GEQ:
    derivation->min.l = value->l;
    break;
  case LVM_LE:
    derivation->max.l = value->l - 1;
    break;
  case LVM_LEQ:
    derivation->max.l = value->l;
    break;
  default:
    return DERIVATION_ERROR;
  }

  derivation->derived = 1;

  return TRUE;
}

lvm_status_t
lvm_derive(lvm_instance_t *p)
{
  return derive_relation(p, derivations);
}

lvm_status_t
lvm_get_derived_range(lvm_instance_t *p, char *name,
                      operand_value_t *min, operand_value_t *max)
{
  int i;

  for(i = 0; i < LVM_MAX_VARIABLE_ID; i++) {
    if(strcmp(name, variables[i].name) == 0) {
      if(derivations[i].derived) {
        *min = derivations[i].min;
        *max = derivations[i].max;
        return TRUE;
      }
      return DERIVATION_ERROR;
    }
  }
  return INVALID_IDENTIFIER;
}

#if DEBUG
static lvm_ip_t
print_operator(lvm_instance_t *p, lvm_ip_t index)
{
  operator_t operator;
  struct operator_map {
    operator_t op;
    char *representation;
  };
  struct operator_map operator_map[] = {
    {LVM_ADD, "+"},
    {LVM_SUB, "-"},
    {LVM_MUL, "*"},
    {LVM_DIV, "/"},
    {LVM_GE, ">"},
    {LVM_GEQ, ">="},
    {LVM_LE, "<"},
    {LVM_LEQ, "<="},
    {LVM_EQ, "="},
    {LVM_NEQ, "<>"},
    {LVM_AND, "/\\"},
    {LVM_OR, "\\/"},
    {LVM_NOT, "!"}
  };
  int i;

  memcpy(&operator, p->code + index, sizeof(operator));

  for(i = 0; i < sizeof(operator_map) / sizeof(operator_map[0]); i++) {
    if(operator_map[i].op == operator) {
      PRINTF("%s ", operator_map[i].representation);
      break;
    }
  }

  return index + sizeof(operator_t);
}

static lvm_ip_t
print_operand(lvm_instance_t *p, lvm_ip_t index)
{
  operand_t operand;

  memcpy(&operand, p->code + index, sizeof(operand));

  switch(operand.type) {
  case LVM_VARIABLE:
  if(operand.value.id >= LVM_MAX_VARIABLE_ID || variables[operand.value.id].name == NULL) {
    PRINTF("var(id:%d):?? ", operand.value.id);
  } else {
    PRINTF("var(%s):%ld ", variables[operand.value.id].name,
	   variables[operand.value.id].value.l);
  }
    break;
  case LVM_LONG:
    PRINTF("long:%ld ", operand.value.l);
    break;
  default:
    PRINTF("?? ");
    break;
  }

  return index + sizeof(operand_t);
}

static lvm_ip_t
print_relation(lvm_instance_t *p, lvm_ip_t index)
{
  /* Relational operators are stored as ordinary operators. */
  return print_operator(p, index);
}
#endif /* DEBUG */

void
lvm_print_code(lvm_instance_t *p)
{
#if DEBUG
  lvm_ip_t ip;

  PRINTF("Code: ");

  for(ip = 0; ip < p->end;) {
    switch(*(node_type_t *)(p->code + ip)) {
    case LVM_CMP_OP:
      ip = print_relation(p, ip + sizeof(node_type_t));
      break;
    case LVM_ARITH_OP:
      ip = print_operator(p, ip + sizeof(node_type_t));
      break;
    case LVM_OPERAND:
      ip = print_operand(p, ip + sizeof(node_type_t));
      break;
    default:
      PRINTF("Invalid opcode: 0x%x ", p->code[ip]);
      ip = p->end;
      break;
    }
  }
  putchar('\n');
#endif
}

void
lvm_print_derivations(lvm_instance_t *p)
{
#if DEBUG
  print_derivations(derivations);
#endif /* DEBUG */
}

#ifdef TEST
int
main(void)
{
  lvm_instance_t p;
  unsigned char code[256];

  lvm_reset(&p, code, sizeof(code));

  lvm_register_variable("z", LVM_LONG);
  lvm_set_variable_value("z", (operand_value_t)15L);

  lvm_register_variable("y", LVM_LONG);
  lvm_set_variable_value("y", (operand_value_t)109L);

  /* Infix: 109 = y /\ 20 > 70 - (6 + z * 3) => 109 = 109 /\ 20 > 19 => true */
  lvm_set_relation(&p, LVM_AND);
  lvm_set_relation(&p, LVM_EQ);
  lvm_set_long(&p, 109);
  lvm_set_variable(&p, "y");
  lvm_set_relation(&p, LVM_GE);
  lvm_set_long(&p, 20);
  lvm_set_op(&p, LVM_SUB);
  lvm_set_long(&p, 70);
  lvm_set_op(&p, LVM_ADD);
  lvm_set_long(&p, 6);
  lvm_set_op(&p, LVM_MUL);
  lvm_set_variable(&p, "z");
  lvm_set_long(&p, 3);

  lvm_print_code(&p);

  lvm_execute(&p);

  /* Infix: !(9999 + 1 < -1 + 10001) => !(10000 < 10000) => true */
  lvm_reset(&p, code, sizeof(code));
  lvm_set_relation(&p, LVM_NOT);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_op(&p, LVM_ADD);
  lvm_set_long(&p, 9999);
  lvm_set_long(&p, 1);
  lvm_set_op(&p, LVM_ADD);
  lvm_set_long(&p, -1);
  lvm_set_long(&p, 10001);

  lvm_print_code(&p);

  lvm_execute(&p);

  /* Derivation tests */

  /* Infix: a = 5 => a:(5,5) */
  lvm_reset(&p, code, sizeof(code));
  lvm_register_variable("a", LVM_LONG);
  lvm_set_relation(&p, LVM_EQ);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 5);

  lvm_derive(&p);
  lvm_print_derivations(&p);

  /* Infix: a < 10 => a:(-oo,9) */
  lvm_reset(&p, code, sizeof(code));
  lvm_register_variable("a", LVM_LONG);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 10);

  lvm_derive(&p);
  lvm_print_derivations(&p);

  /* Infix: a < 100 /\ 10 < a => a:(11,99) */
  lvm_reset(&p, code, sizeof(code));
  lvm_register_variable("a", LVM_LONG);
  lvm_set_relation(&p, LVM_AND);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 100);
  lvm_set_relation(&p, LVM_GE);
  lvm_set_long(&p, 10);
  lvm_set_variable(&p, "a");

  lvm_derive(&p);
  lvm_print_derivations(&p);

  /* Infix: a < 100 /\ b > 100 => a:(-oo,99), b:(101,oo) */
  lvm_reset(&p, code, sizeof(code));
  lvm_register_variable("a", LVM_LONG);
  lvm_register_variable("b", LVM_LONG);
  lvm_set_relation(&p, LVM_AND);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 100);
  lvm_set_relation(&p, LVM_GE);
  lvm_set_variable(&p, "b");
  lvm_set_long(&p, 100);

  lvm_derive(&p);
  lvm_print_derivations(&p);

  /* Infix: a < 100 \/ a < 1000 \/ a < 1902 => a:(-oo,1901) */
  lvm_reset(&p, code, sizeof(code));
  lvm_register_variable("a", LVM_LONG);
  lvm_set_relation(&p, LVM_OR);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 100);
  lvm_set_relation(&p, LVM_OR);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_long(&p, 1000);
  lvm_set_variable(&p, "a");
  lvm_set_relation(&p, LVM_LE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 1902);

  lvm_derive(&p);
  lvm_print_derivations(&p);

  /* Infix: (a < 100 /\ a < 90 /\ a > 80 /\ a < 105) \/ b > 10000 =>
     a:(81,89), b:(10001:oo) */
  lvm_reset(&p, code, sizeof(code));
  lvm_register_variable("a", LVM_LONG);
  lvm_register_variable("b", LVM_LONG);

  lvm_set_relation(&p, LVM_OR);
  lvm_set_relation(&p, LVM_GE);
  lvm_set_variable(&p, "b");
  lvm_set_long(&p, 10000);

  lvm_set_relation(&p, LVM_AND);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 100);
  lvm_set_relation(&p, LVM_AND);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 90);
  lvm_set_relation(&p, LVM_AND);
  lvm_set_relation(&p, LVM_GE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 80);
  lvm_set_relation(&p, LVM_LE);
  lvm_set_variable(&p, "a");
  lvm_set_long(&p, 105);

  lvm_derive(&p);
  lvm_print_derivations(&p);

  printf("Done\n");

  return 0;
}
#endif
