//! @file a68g-genie.h
//! @author J. Marcel van der Veer

//! @section Copyright
//!
//! This file is part of Algol68G - an Algol 68 compiler-interpreter.
//! Copyright 2001-2025 J. Marcel van der Veer [algol68g@xs4all.nl].

//! @section License
//!
//! 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 3 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, see [http://www.gnu.org/licenses/].

//! @section Synopsis
//!
//! Interpreter related definitions.

#if !defined (__A68G_GENIE_H__)
#define __A68G_GENIE_H__

#include "a68g-frames.h"

//! @brief PROC VOID gc heap

// Prelude errors can also occur in the constant folder

#define CHECK_INT_SHORTEN(p, i)\
  PRELUDE_ERROR (((i) > INT_MAX || (i) < -INT_MAX), p, ERROR_MATH, M_INT)

#define CHECK_INT_ADDITION(p, i, j)\
  PRELUDE_ERROR (\
    ((j) > 0 && (i) > (A68G_MAX_INT - (j))) || ((j) < 0 && (i) < (-A68G_MAX_INT - (j))),\
    p, "M overflow", M_INT)

#define CHECK_INT_MULTIPLICATION(p, i, j)\
  PRELUDE_ERROR (\
    (j) != 0 && ABS (i) > A68G_MAX_INT / ABS (j),\
    p, "M overflow", M_INT)

#define CHECK_BITS_ADDITION(p, i, j)\
  if (!MODULAR_MATH (p)) {\
    PRELUDE_ERROR (((i) > (A68G_MAX_BITS - (j))), p, ERROR_MATH, M_BITS);\
  }

#define CHECK_BITS_SUBTRACTION(p, i, j)\
  if (!MODULAR_MATH (p)) {\
    PRELUDE_ERROR (((j) > (i)), p, ERROR_MATH, M_BITS);\
  }

#define CHECK_BITS_MULTIPLICATION(p, i, j)\
  if (!MODULAR_MATH (p)) {\
    PRELUDE_ERROR ((j) != 0 && (i) > A68G_MAX_BITS / (j), p, ERROR_MATH, M_BITS);\
  }

#define CHECK_INT_DIVISION(p, i, j)\
  PRELUDE_ERROR ((j) == 0, p, ERROR_DIVISION_BY_ZERO, M_INT)

#define PRELUDE_ERROR(cond, p, txt, add)\
  if (cond) {\
    if (A68G (in_execution)) {\
      diagnostic (A68G_RUNTIME_ERROR, p, txt, add);\
      exit_genie (p, A68G_RUNTIME_ERROR);\
    } else {\
      diagnostic (A68G_MATH_ERROR, p, txt, add);\
    }}

// Check on a NIL name

#define CHECK_REF(p, z, m)\
  if (! INITIALISED (&z)) {\
    diagnostic (A68G_RUNTIME_ERROR, (p), ERROR_EMPTY_VALUE_FROM, (m));\
    exit_genie ((p), A68G_RUNTIME_ERROR);\
  } else if (IS_NIL (z)) {\
    diagnostic (A68G_RUNTIME_ERROR, (p), ERROR_ACCESSING_NIL, (m));\
    exit_genie ((p), A68G_RUNTIME_ERROR);\
  }

// Macros for row-handling

// An A68G row is a reference to a descriptor in the heap:
// A68G_REF row -> A68G_ARRAY ----+   ARRAY: Description of row, ref to elements.
//                A68G_TUPLE 1   |   TUPLE: Bounds, one for every dimension.
//                ...           |
//                A68G_TUPLE dim |
//                ...           |
//                ...           |
//                Element 1 <---+   Sequential row elements in the heap.
//                ...
//                Element n

#define DESCRIPTOR_SIZE(n) (SIZE_ALIGNED (A68G_ARRAY) + (n) * SIZE_ALIGNED (A68G_TUPLE))

#define NEW_ROW_1D(des, row, arr, tup, row_m, mod, upb)\
  (des) = heap_generator (p, (row_m), DESCRIPTOR_SIZE (1));\
  (row) = heap_generator (p, (row_m), (upb) * SIZE (mod));\
  DIM (&(arr)) = 1;\
  MOID (&(arr)) = (mod);\
  ELEM_SIZE (&(arr)) = SIZE (mod);\
  SLICE_OFFSET (&(arr)) = 0;\
  FIELD_OFFSET (&(arr)) = 0;\
  ARRAY (&(arr)) = (row);\
  LWB (&(tup)) = 1;\
  UPB (&(tup)) = (upb);\
  SHIFT (&(tup)) = LWB (&(tup));\
  SPAN (&(tup)) = 1;\
  K (&(tup)) = 0;\
  PUT_DESCRIPTOR ((arr), (tup), &(des));

#define GET_DESCRIPTOR(arr, tup, p)\
  arr = (A68G_ARRAY *) ARRAY_ADDRESS (p);\
  tup = (A68G_TUPLE *) & (((BYTE_T *) (arr)) [SIZE_ALIGNED (A68G_ARRAY)]);

#define GET_DESCRIPTOR2(arr, tup1, tup2, p)\
  arr = (A68G_ARRAY *) ARRAY_ADDRESS (p);\
  tup1 = (A68G_TUPLE *) & (((BYTE_T *) (arr)) [SIZE_ALIGNED (A68G_ARRAY)]);\
  tup2 = (A68G_TUPLE *) & (((BYTE_T *) (arr)) [SIZE_ALIGNED (A68G_ARRAY) + sizeof (A68G_TUPLE)]);

#define PUT_DESCRIPTOR(arr, tup, p) {\
  BYTE_T *a_p = ARRAY_ADDRESS (p);\
  *(A68G_ARRAY *) a_p = (arr);\
  *(A68G_TUPLE *) &(((BYTE_T *) (a_p)) [SIZE_ALIGNED (A68G_ARRAY)]) = (tup);\
  }

#define PUT_DESCRIPTOR2(arr, tup1, tup2, p) {\
  BYTE_T *a_p = ARRAY_ADDRESS (p);\
  *(A68G_ARRAY *) a_p = (arr);\
  *(A68G_TUPLE *) &(((BYTE_T *) (a_p)) [SIZE_ALIGNED (A68G_ARRAY)]) = (tup1);\
  *(A68G_TUPLE *) &(((BYTE_T *) (a_p)) [SIZE_ALIGNED (A68G_ARRAY) + sizeof (A68G_TUPLE)]) = (tup2);\
  }

#define ROW_SIZE(tup) ((LWB (tup) <= UPB (tup)) ? (UPB (tup) - LWB (tup) + 1) : 0)
#define ROW_ELEMENT(arr, k) (((ADDR_T) k + SLICE_OFFSET (arr)) * ELEM_SIZE (arr) + FIELD_OFFSET (arr))
#define INDEX_1_DIM(arr, tup, k) ROW_ELEMENT (arr, (SPAN (tup) * (int) (k) - SHIFT (tup)))

#define VECTOR_OFFSET(arr, tup)\
  ((LWB (tup) * SPAN (tup) - SHIFT (tup) + SLICE_OFFSET (arr)) * ELEM_SIZE (arr) + FIELD_OFFSET (arr))

#define MATRIX_OFFSET(arr, tup1, tup2)\
  ((LWB (tup1) * SPAN (tup1) - SHIFT (tup1) + LWB (tup2) * SPAN (tup2) - SHIFT (tup2) + SLICE_OFFSET (arr)) * ELEM_SIZE (arr) + FIELD_OFFSET (arr))

// Execution

#define STORE_GC(p, _sp_) {\
  A68G_REF _loc_;\
  STATUS (&_loc_) = (STATUS_MASK_T) (INIT_MASK | IN_FRAME_MASK);\
  REF_HANDLE (&_loc_) = (A68G_HANDLE *) & nil_handle;\
  OFFSET (&_loc_) = A68G_FP + FRAME_INFO_SIZE + OFFSET (TAX_GC (p));\
  REF_SCOPE (&_loc_) = A68G_FP;\
  COPY_ALIGNED ((BYTE_T *) ADDRESS (&_loc_), STACK_ADDRESS (_sp_), SIZE (MOID (p)));\
}

#define GENIE_UNIT(p) {\
  PROP_T *_prop_ = &GPROP (p);\
  if (TAX_GC (p) != NO_TAG) {\
    ADDR_T _sp_ = A68G_SP;\
    (void) (*(UNIT (_prop_))) (SOURCE (_prop_));\
    STORE_GC (p, _sp_);\
  } else {\
    (void) (*(UNIT (_prop_))) (SOURCE (_prop_));\
  }}

#define GENIE_UNIT_KEEP(p, dest) {\
  PROP_T *_prop_ = &GPROP (p);\
  if (TAX_GC (p) != NO_TAG) {\
    ADDR_T _sp_ = A68G_SP;\
    dest = (*(UNIT (_prop_))) (SOURCE (_prop_));\
    STORE_GC (p, _sp_);\
  } else {\
    dest = (*(UNIT (_prop_))) (SOURCE (_prop_));\
  }}

#define GENIE_UNIT_NO_GC(p) {\
  A68G_GC (sema) ++;\
  GENIE_UNIT ((p));\
  A68G_GC (sema) --;}

#define GENIE_UNIT_KEEP_NO_GC(p, dest) {\
  A68G_GC (sema) ++;\
  GENIE_UNIT_KEEP ((p), (dest));\
  A68G_GC (sema) --;}

#define GENIE_UNIT_TRACE(p) {\
  if (STATUS_TEST (p, (BREAKPOINT_MASK | BREAKPOINT_TEMPORARY_MASK | \
      BREAKPOINT_INTERRUPT_MASK | BREAKPOINT_WATCH_MASK | BREAKPOINT_TRACE_MASK))) {\
    single_step ((p), STATUS (p));\
  }\
  GENIE_UNIT (p);}

// Stuff for the garbage collector

#define PREEMPTIVE_FRACTION 0.9

// Save a handle from the GC

#define BLOCK_GC_HANDLE(z) {\
  if (IS_IN_HEAP (z)) {\
    STATUS_SET (REF_HANDLE(z), BLOCK_GC_MASK);\
  }}

#define UNBLOCK_GC_HANDLE(z) {\
  if (IS_IN_HEAP (z)) {\
    STATUS_CLEAR (REF_HANDLE (z), BLOCK_GC_MASK);\
  }}

// Tests for objects of mode INT

#define CHECK_INDEX(p, k, t) {\
  if (VALUE (k) < LWB (t) || VALUE (k) > UPB (t)) {\
    diagnostic (A68G_RUNTIME_ERROR, p, ERROR_INDEX_OUT_OF_BOUNDS);\
    exit_genie (p, A68G_RUNTIME_ERROR);\
  }}

// Tests for objects of mode REAL

#if defined (HAVE_IEEE_754)
#define CHECK_REAL(p, u) PRELUDE_ERROR (!a68g_finite_real (u), p, ERROR_INFINITE, M_REAL)
#define CHECK_COMPLEX(p, u, v) PRELUDE_ERROR (!a68g_finite_real (u) || !a68g_finite_real (v), p, ERROR_INFINITE, M_COMPLEX)
#else
#define CHECK_REAL(p, u) {;}
#define CHECK_COMPLEX(p, u, v) {;}
#endif

#define MATH_RTE(p, z, m, t) PRELUDE_ERROR (z, (p), (t == NO_TEXT ? ERROR_MATH : t), (m))

// Macros.

#define C_FUNCTION(p, f)\
  A68G_REAL *x;\
  POP_OPERAND_ADDRESS (p, x, A68G_REAL);\
  errno = 0;\
  VALUE (x) = f (VALUE (x));\
  MATH_RTE (p, errno != 0, M_REAL, NO_TEXT);

#define OWN_FUNCTION(p, f)\
  A68G_REAL *x;\
  POP_OPERAND_ADDRESS (p, x, A68G_REAL);\
  errno = 0;\
  VALUE (x) = f (p, VALUE (x));\
  MATH_RTE (p, errno != 0, M_REAL, NO_TEXT);

// Macros for standard environ

#define A68G_ENV_INT(n, k) void n (NODE_T *p) {PUSH_PRIMAL (p, (k), INT);}
#define A68G_ENV_REAL(n, z) void n (NODE_T *p) {PUSH_PRIMAL (p, (z), REAL);}

// Macros for the evaluation stack

#define INCREMENT_STACK_POINTER(err, i)\
  {A68G_SP += (ADDR_T) A68G_ALIGN (i); (void) (err);}

#define DECREMENT_STACK_POINTER(err, i)\
  {A68G_SP -= A68G_ALIGN (i); (void) (err);}

#define PUSH(p, addr, size) {\
  BYTE_T *_sp_ = STACK_TOP;\
  INCREMENT_STACK_POINTER ((p), (int) (size));\
  COPY (_sp_, (BYTE_T *) (addr), (int) (size));\
  }

#define POP(p, addr, size) {\
  DECREMENT_STACK_POINTER((p), (int) (size));\
  COPY ((BYTE_T *) (addr), STACK_TOP, (int) (size));\
  }

#define POP_ALIGNED(p, addr, size) {\
  DECREMENT_STACK_POINTER((p), (int) (size));\
  COPY_ALIGNED ((BYTE_T *) (addr), STACK_TOP, (int) (size));\
  }

#define POP_ADDRESS(p, addr, type) {\
  DECREMENT_STACK_POINTER((p), (int) SIZE_ALIGNED (type));\
  (addr) = (type *) STACK_TOP;\
  }

#define POP_OPERAND_ADDRESS(p, i, type) {\
  (void) (p);\
  (i) = (type *) (STACK_OFFSET (-SIZE_ALIGNED (type)));\
  }

#define POP_OPERAND_ADDRESSES(p, i, j, type) {\
  DECREMENT_STACK_POINTER ((p), (int) SIZE_ALIGNED (type));\
  (j) = (type *) STACK_TOP;\
  (i) = (type *) (STACK_OFFSET (-SIZE_ALIGNED (type)));\
  }

#define POP_3_OPERAND_ADDRESSES(p, i, j, k, type) {\
  DECREMENT_STACK_POINTER ((p), (int) (2 * SIZE_ALIGNED (type)));\
  (k) = (type *) (STACK_OFFSET (SIZE_ALIGNED (type)));\
  (j) = (type *) STACK_TOP;\
  (i) = (type *) (STACK_OFFSET (-SIZE_ALIGNED (type)));\
  }

#define PUSH_VALUE(p, z, mode) {\
  mode *_x_ = (mode *) STACK_TOP;\
  STATUS (_x_) = INIT_MASK;\
  VALUE (_x_) = (z);\
  INCREMENT_STACK_POINTER ((p), SIZE_ALIGNED (mode));\
  }

#define PUSH_PRIMAL(p, z, m) {\
  A68G_##m *_x_ = (A68G_##m *) STACK_TOP;\
  int _size_ = SIZE_ALIGNED (A68G_##m);\
  STATUS (_x_) = INIT_MASK;\
  VALUE (_x_) = (z);\
  INCREMENT_STACK_POINTER ((p), _size_);\
  }

#define PUSH_OBJECT(p, z, mode) {\
  *(mode *) STACK_TOP = (z);\
  INCREMENT_STACK_POINTER (p, SIZE_ALIGNED (mode));\
  }

#define POP_OBJECT(p, z, mode) {\
  DECREMENT_STACK_POINTER((p), SIZE_ALIGNED (mode));\
  (*(z)) = *((mode *) STACK_TOP);\
  }

#define PUSH_COMPLEX(p, re, im) {\
  PUSH_PRIMAL (p, re, REAL);\
  PUSH_PRIMAL (p, im, REAL);\
  }

#define POP_COMPLEX(p, re, im) {\
  POP_OBJECT (p, im, A68G_REAL);\
  POP_OBJECT (p, re, A68G_REAL);\
  }

#define PUSH_BYTES(p, k) {\
  A68G_BYTES *_z_ = (A68G_BYTES *) STACK_TOP;\
  STATUS (_z_) = INIT_MASK;\
  MOVE (VALUE (_z_), k, A68G_BYTES_WIDTH);\
  INCREMENT_STACK_POINTER((p), SIZE_ALIGNED (A68G_BYTES));\
  }

#define PUSH_LONG_BYTES(p, k) {\
  A68G_LONG_BYTES *_z_ = (A68G_LONG_BYTES *) STACK_TOP;\
  STATUS (_z_) = INIT_MASK;\
  MOVE (VALUE (_z_), k, A68G_LONG_BYTES_WIDTH);\
  INCREMENT_STACK_POINTER((p), SIZE_ALIGNED (A68G_LONG_BYTES));\
  }

#define PUSH_REF(p, z) PUSH_OBJECT (p, z, A68G_REF)
#define PUSH_PROCEDURE(p, z) PUSH_OBJECT (p, z, A68G_PROCEDURE)
#define PUSH_FORMAT(p, z) PUSH_OBJECT (p, z, A68G_FORMAT)

#define PUSH_STRING(p, z) {\
  char *_s_ = (z);\
  PUSH_REF ((p), tmp_to_a68g_string ((p), _s_));\
  }

#define POP_REF(p, z) POP_OBJECT (p, z, A68G_REF)
#define POP_PROCEDURE(p, z) POP_OBJECT (p, z, A68G_PROCEDURE)

#define PUSH_UNION(p, z) {\
  A68G_UNION *_x_ = (A68G_UNION *) STACK_TOP;\
  STATUS (_x_) = INIT_MASK;\
  VALUE (_x_) = (z);\
  INCREMENT_STACK_POINTER ((p), SIZE_ALIGNED (A68G_UNION));\
  }

// Interpreter macros

#define INITIALISED(z) ((BOOL_T) (STATUS (z) & INIT_MASK))
#define MODULAR_MATH(z) ((BOOL_T) (STATUS (z) & MODULAR_MASK))
#define LHS_MODE(p) (MOID (PACK (MOID (p))))
#define RHS_MODE(p) (MOID (NEXT (PACK (MOID (p)))))

// Transput related macros

#define IS_NIL_FORMAT(f) ((BOOL_T) (BODY (f) == NO_NODE && ENVIRON (f) == 0))

// Macros for check on initialisation of values

#define CHECK_INIT(p, c, q)\
  if (!(c)) {\
    diagnostic (A68G_RUNTIME_ERROR, (p), ERROR_EMPTY_VALUE_FROM, (q));\
    exit_genie ((p), A68G_RUNTIME_ERROR);\
  }

#define CHECK_DNS2(p, scope, limit, mode)\
  if (scope > limit) {\
    BUFFER txt;\
    ASSERT (a68g_bufprt (txt, SNPRINTF_SIZE, ERROR_SCOPE_DYNAMIC_1) >= 0);\
    diagnostic (A68G_RUNTIME_ERROR, p, txt, mode);\
    exit_genie (p, A68G_RUNTIME_ERROR);\
  }

#define CHECK_DNS(p, m, w, limit)\
  if (NEED_DNS (GINFO (p))) {\
    ADDR_T _lim = ((limit) < A68G_GLOBALS ? A68G_GLOBALS : (limit));\
    if (IS ((m), REF_SYMBOL)) {\
      CHECK_DNS2 (p, (REF_SCOPE ((A68G_REF *) (w))), _lim, (m));\
    } else if (IS ((m), PROC_SYMBOL)) {\
      CHECK_DNS2 (p, ENVIRON ((A68G_PROCEDURE *) (w)), _lim, (m));\
    } else if (IS ((m), FORMAT_SYMBOL)) {\
      CHECK_DNS2 (p, ENVIRON ((A68G_FORMAT *) w), _lim, (m));\
  }}

// 
// The void * cast in next macro is to stop warnings about dropping a volatile
// qualifier to a pointer. This is safe here.

#define STACK_DNS(p, m, limit)\
  if (p != NO_NODE && GINFO (p) != NO_GINFO) {\
    CHECK_DNS ((NODE_T *)(void *)(p), (m),\
               (STACK_OFFSET (-SIZE (m))), (limit));\
  }

// Genie routines.

PROP_T genie_column_function (NODE_T *);
PROP_T genie_diagonal_function (NODE_T *);
PROP_T genie_row_function (NODE_T *);
PROP_T genie_transpose_function (NODE_T *);
PROP_T genie_and_function (NODE_T *);
PROP_T genie_assertion (NODE_T *);
PROP_T genie_assignation_constant (NODE_T *);
PROP_T genie_assignation (NODE_T *);
PROP_T genie_assignation_quick (NODE_T * p);
PROP_T genie_call (NODE_T *);
PROP_T genie_cast (NODE_T *);
PROP_T genie_closed (volatile NODE_T *);
PROP_T genie_coercion (NODE_T *);
PROP_T genie_collateral (NODE_T *);
PROP_T genie_conditional (volatile NODE_T *);
PROP_T genie_constant (NODE_T *);
PROP_T genie_denotation (NODE_T *);
PROP_T genie_deproceduring (NODE_T *);
PROP_T genie_dereference_frame_identifier (NODE_T *);
PROP_T genie_dereference_generic_identifier (NODE_T *);
PROP_T genie_dereference_selection_name_quick (NODE_T *);
PROP_T genie_dereference_slice_name_quick (NODE_T *);
PROP_T genie_dereferencing (NODE_T *);
PROP_T genie_dereferencing_quick (NODE_T *);
PROP_T genie_dyadic (NODE_T *);
PROP_T genie_dyadic_quick (NODE_T *);
PROP_T genie_enclosed (volatile NODE_T *);
PROP_T genie_field_selection (NODE_T *);
PROP_T genie_format_text (NODE_T *);
PROP_T genie_formula (NODE_T *);
PROP_T genie_frame_identifier (NODE_T *);
PROP_T genie_identifier (NODE_T *);
PROP_T genie_identifier_standenv (NODE_T *);
PROP_T genie_identifier_standenv_proc (NODE_T *);
PROP_T genie_identity_relation (NODE_T *);
PROP_T genie_int_case (volatile NODE_T *);
PROP_T genie_loop (volatile NODE_T *);
PROP_T genie_loop (volatile NODE_T *);
PROP_T genie_monadic (NODE_T *);
PROP_T genie_nihil (NODE_T *);
PROP_T genie_or_function (NODE_T *);
PROP_T genie_routine_text (NODE_T *);
PROP_T genie_rowing (NODE_T *);
PROP_T genie_rowing_ref_row_of_row (NODE_T *);
PROP_T genie_rowing_ref_row_row (NODE_T *);
PROP_T genie_rowing_row_of_row (NODE_T *);
PROP_T genie_rowing_row_row (NODE_T *);
PROP_T genie_selection_name_quick (NODE_T *);
PROP_T genie_selection (NODE_T *);
PROP_T genie_selection_value_quick (NODE_T *);
PROP_T genie_skip (NODE_T *);
PROP_T genie_slice_name_quick (NODE_T *);
PROP_T genie_slice (NODE_T *);
PROP_T genie_united_case (volatile NODE_T *);
PROP_T genie_uniting (NODE_T *);
PROP_T genie_unit (NODE_T *);
PROP_T genie_voiding_assignation_constant (NODE_T *);
PROP_T genie_voiding_assignation (NODE_T *);
PROP_T genie_voiding (NODE_T *);
PROP_T genie_widen_int_to_real (NODE_T *);
PROP_T genie_widen (NODE_T *);

A68G_REF genie_clone (NODE_T *, MOID_T *, A68G_REF *, A68G_REF *);
A68G_REF genie_make_ref_row_of_row (NODE_T *, MOID_T *, MOID_T *, ADDR_T);
A68G_REF genie_make_ref_row_row (NODE_T *, MOID_T *, MOID_T *, ADDR_T);
A68G_REF genie_make_rowrow (NODE_T *, MOID_T *, int, ADDR_T);

void genie (void *);
void genie_clone_stack (NODE_T *, MOID_T *, A68G_REF *, A68G_REF *);
void genie_serial_units_no_label (NODE_T *, ADDR_T, NODE_T **);
void genie_jump (NODE_T *);

#endif
