/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Glenn Fowler
 * AT&T Research
 *
 * expression library definitions
 */

#include <ast/ast.h>
#include <inttypes.h>

#include <expr/exparse.h>

#include <assert.h>
#include <cdt.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <util/agxbuf.h>
#include <util/arena.h>

#define EX_VERSION 20000101L

/*
 * flags
 */

#define EX_CHARSTRING (1 << 0) /* '...' same as "..."		*/
#define EX_UNDECLARED (1 << 9) /* allow undeclared identifiers	*/

#define EX_ARRAY (-3)  /* getval() array elt   */
#define EX_CALL (-2)   /* getval() function call elt	*/
#define EX_SCALAR (-1) /* getval() scalar elt		*/

#define EX_NAMELEN 32 /* default Exid_t.name length	*/

#define EX_ID(n, l, i, t) {{0}, (l), (i), (t), 0, 0, 0, n}

#define DELETE_T MINTOKEN /* exexpr() delete `type'	*/

#define INTEGRAL(t) ((t) >= INTEGER && (t) <= CHARACTER)
#define BUILTIN(t) ((t) > MINTOKEN)

/* function type mechanism
 * types are encoded in TBITS
 * Thus, maximum # of parameters, including return type,
 * is sizeof(Exid_t.type)/TBITS. Also see T in exgram.h
 */

/*
 * arg 0 is the return value type
 */

#define F 01 /* FLOATING			*/
#define I 02 /* INTEGER			*/
#define S 03 /* STRING			*/

#define TBITS 4
#define TMASK ((1 << TBITS) - 1)
#define A(n, t) ((t) << ((n) * TBITS)) /* function arg n is type t     */
#define N(t) ((t) >>= TBITS)           /* shift for next arg           */

typedef EX_STYPE Extype_t;

typedef union Exdata_u Exdata_t;
typedef struct Exdisc_s Exdisc_t;
typedef struct Exnode_s Exnode_t;
typedef struct Expr_s Expr_t;
typedef struct Exref_s Exref_t;

typedef void (*Exerror_f)(const char *, void *, int, const char *, ...);
typedef void (*Exexit_f)(void *, int);

typedef struct Exid_s /* id symbol table info		*/
{
  Dtlink_t link;         /* symbol table link		*/
  long lex;              /* lex class			*/
  long index;            /* user defined index		*/
  long type;             /* symbol and arg types		*/
  long index_type;       /* index type for arrays        */
  Exnode_t *value;       /* value			*/
  void *local;           ///< user defined local stuff
  char name[EX_NAMELEN]; /* symbol name			*/
} Exid_t;

struct Exref_s /* . reference list		*/
{
  Exref_t *next;   /* next in list			*/
  Exid_t *symbol;  /* reference id symbol		*/
  Exnode_t *index; /* optional reference index	*/
};

union Exdata_u {

  struct {
    Extype_t value;    /* constant value		*/
    Exid_t *reference; /* conversion reference symbol	*/
  } constant;          /* variable reference		*/

  struct {
    Exnode_t *left;  /* left operand			*/
    Exnode_t *right; /* right operand		*/
  } operand;         /* operands			*/

  struct {
    Exnode_t *statement; /* case label statement(s)	*/
    Exnode_t *next;      /* next case item		*/
    Extype_t **constant; /* case label constant array	*/
  } select;              /* case item			*/

  struct {
    Exid_t *symbol;     /* id symbol table entry	*/
    Exref_t *reference; /* . reference list		*/
    Exnode_t *index;    /* array index expression	*/
    Exnode_t *dyna;     /* dynamic expression		*/
  } variable;           /* variable reference		*/

#ifdef _EX_DATA_PRIVATE_
  _EX_DATA_PRIVATE_
#endif
};

struct Exnode_s /* expression tree node		*/
{
  long type;     ///< value type
  long op;       ///< operator
  bool binary;   ///< data.operand.{left,right} ok
  Exdata_t data; /* node data			*/

#ifdef _EX_NODE_PRIVATE_
  _EX_NODE_PRIVATE_
#endif
};

struct Exdisc_s /* discipline			*/
{
  uint64_t version; /* EX_VERSION			*/
  uint64_t flags;   /* EX_* flags			*/
  Exid_t *symbols;  /* static symbols		*/
  char **data;      /* compiled function arg data	*/
  int (*castf)(Expr_t *, Exnode_t *, const char *, int, Exid_t *, int,
               Exdisc_t *);
  /* unknown cast function	*/
  int (*convertf)(Exnode_t *, long, int);
  /* type conversion function	*/
  int (*binaryf)(Exnode_t *, Exnode_t *, Exnode_t *, int);
  /* binary operator function     */
  char *(*typename)(long);
  /* application type names       */
  int (*stringof)(Expr_t *, Exnode_t *, int);
  /* value to string conversion   */
  Extype_t (*keyf)(Extype_t, long);
  /* dictionary key for external type objects     */
  Exerror_f errorf; /* error function		*/
  Extype_t (*getf)(Expr_t *, Exnode_t *, Exid_t *, Exref_t *, void *, int,
                   Exdisc_t *);
  /* get value function		*/
  Extype_t (*reff)(Expr_t *, Exnode_t *, Exid_t *, Exref_t *);
  /* reference value function	*/
  int (*setf)(Expr_t *, Exnode_t *, Exid_t *, Exref_t *, void *, Extype_t);
  /* set value function		*/
  /// length function
  ///
  /// This callback allows a user-defined '#' handler. That is, extending the
  /// array length operator’s function.
  ///
  /// @param rhs The right-hand side operand. That is 'foo' in the expression
  ///   '# foo'.
  /// @param disc Pointer to the containing discipline.
  /// @return The “length”, according to the user’s interpretation.
  Extype_t (*lengthf)(Exid_t *rhs, Exdisc_t *disc);
  /// array membership function
  ///
  /// This callback allows a user-defined `in` handler. That is, extending the
  /// array membership test’s function.
  ///
  /// @param lhs The left-hand side operand. That is 'foo' in the expression
  ///   'foo in bar'.
  /// @param rhs The right-hand side operand. That is 'bar' in the expression
  ///   'foo in bar'.
  /// @param disc Pointer to the containing discipline.
  /// @return 0 if `lhs` is not in the array `rhs`, non-zero otherwise
  int (*inf)(Extype_t lhs, Exid_t *rhs, Exdisc_t *disc);
  /* exit function           */
  Exexit_f exitf;
  int *types;
  void *user;
};

struct Expr_s /* ex program state		*/
{
  const char *id; /* library id			*/
  Dt_t *symbols;  /* symbol table			*/
  FILE *file[10]; /* io streams			*/
  arena_t vm;     /* program store		*/

#ifdef _EX_PROG_PRIVATE_
  _EX_PROG_PRIVATE_
#endif
};

Exnode_t *excast(Expr_t *, Exnode_t *, long, Exnode_t *, int);
Exnode_t *exnoncast(Exnode_t *);
void exclose(Expr_t *);

/** Compile an expression
 *
 * The callee takes ownership of the pointer \p prefix and will free it during
 * \p expop or \p exclose.
 *
 * \param p Program structure to store result in
 * \param name Filename of originating source file
 * \param line Line number of originating source file
 * \param fp Handle to source file
 * \param prefix Optional program text to include ahead of the file content
 * \return 0 on success
 */
int excomp(Expr_t *p, const char *name, int line, FILE *fp, char *prefix);

char *excontext(Expr_t *, char *, int);
void exdump(Expr_t *, Exnode_t *, agxbuf *);
#ifdef __GNUC__
__attribute__((format(printf, 1, 2)))
#endif
void exerror(const char *, ...);
#ifdef __GNUC__
__attribute__((format(printf, 1, 2)))
#endif
void exwarn(const char *, ...);
Extype_t exeval(Expr_t *, Exnode_t *, void *);
Exnode_t *exexpr(Expr_t *, const char *, Exid_t *, int);
void exfreenode(Expr_t *, Exnode_t *);
Exnode_t *exnewnode(Expr_t *, long, bool, long, Exnode_t *, Exnode_t *);
char *exnospace(void);
Expr_t *exopen(Exdisc_t *);
int expop(Expr_t *);
int expush(Expr_t *, const char *, int, FILE *);
int extoken_fn(Expr_t *);
char *exstring(Expr_t *, char *);
void *exstralloc(Expr_t *, size_t);
char *extype(long int);
Extype_t exzero(long int);
char *exopname(long);
void exinit(void);
char *extypename(Expr_t *p, long);
int exisAssign(Exnode_t *);

/** Construct an arena-backed string.
 *
 * \param vm Allocator to use.
 * \param fmt printf-style format sting.
 * \param ... printf-style varargs.
 * \return Dynamically allocated string.
 */
#ifdef __GNUC__
__attribute__((format(printf, 2, 3)))
#endif
static inline char *
exprintf(arena_t *vm, const char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);

  // how many bytes do we need for this string?
  va_list ap2;
  va_copy(ap2, ap);
  int len = vsnprintf(NULL, 0, fmt, ap2);
  assert(len >= 0 && "invalid vsnprintf call");
  ++len; // for NUL terminator
  va_end(ap2);

  // ask arena for enough space for this
  char *const s = gv_arena_alloc(vm, 1, (size_t)len);

  // construct the output string
  (void)vsnprintf(s, (size_t)len, fmt, ap);
  va_end(ap);

  return s;
}

#ifdef __cplusplus
}
#endif
