/********************************************************************
* Description: interp_namedparams.cc
*
* collect all code related to named parameter handling
*
* Author: mostly K. Lerman
* rewrite by Michael Haberler to use STL containers
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
*
* Last change: Juli 2011
********************************************************************/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <boost/python.hpp>
namespace bp = boost::python;

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "rtapi_math.h"
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sstream>
#include <map>

#include "rs274ngc.hh"
#include "rs274ngc_return.hh"
#include "interp_internal.hh"
#include "rs274ngc_interp.hh"
#include "inifile.hh"

// for HAL pin variables
#include "hal.h"
#include "hal_priv.h"

enum predefined_named_parameters {
    NP_LINE,
    NP_MOTION_MODE,
    NP_PLANE,
    NP_CCOMP,
    NP_METRIC,
    NP_IMPERIAL,
    NP_ABSOLUTE,
    NP_INCREMENTAL,
    NP_INVERSE_TIME,
    NP_UNITS_PER_MINUTE,
    NP_UNITS_PER_REV,
    NP_COORD_SYSTEM,
    NP_TOOL_OFFSET,
    NP_RETRACT_R_PLANE,
    NP_RETRACT_OLD_Z,
    NP_SPINDLE_RPM_MODE,
    NP_SPINDLE_CSS_MODE,
    NP_IJK_ABSOLUTE_MODE,
    NP_LATHE_DIAMETER_MODE,
    NP_LATHE_RADIUS_MODE,
    NP_SPINDLE_ON,
    NP_SPINDLE_CW,
    NP_MIST,
    NP_FLOOD,
    NP_SPEED_OVERRIDE,
    NP_FEED_OVERRIDE,
    NP_ADAPTIVE_FEED,
    NP_FEED_HOLD,
    NP_FEED,
    NP_RPM,
    NP_CURRENT_TOOL,
    NP_SELECTED_POCKET,
    NP_CURRENT_POCKET,
    NP_X,
    NP_Y,
    NP_Z,
    NP_A,
    NP_B,
    NP_C,
    NP_U,
    NP_V,
    NP_W,
    NP_VALUE,
    NP_CALL_LEVEL,
    NP_REMAP_LEVEL,
    NP_SELECTED_TOOL,
    NP_VALUE_RETURNED,
    NP_TASK,
};

/****************************************************************************/

/*! read_named_parameter

Returned Value: int
   If read_integer_value returns an error code, this returns that code.
   If any of the following errors occur, this returns the error code shown.
   Otherwise, this returns INTERP_OK.
   1. The first character read is not a <:
      NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED
   2. The named parameter string is not terminated by >:
      NCE_NAMED_PARAMETER_NOT_TERSINATED
   3. The named parameter has not been defined before use:
      NCE_NAMED_PARAMETER_NOT_DEFINED

Side effects:
   The value of the given parameter is put into what double_ptr points at.
   The counter is reset to point to the first character after the
   characters which make up the value.

Called by:  read_parameter

This attempts to read the value of a parameter out of the line,
starting at the index given by the counter.

According to the RS274/NGC manual [NCMS, p. 62], the characters following
# may be any "parameter expression". Thus, the following are legal
and mean the same thing (the value of the parameter whose number is
stored in parameter 2):
  ##2
  #[#2]


ADDED by K. Lerman
Named parameters are now supported.
#<_abcd> is a parameter with name "abcd" of global scope
#<abce> is a named parameter of local scope.

*/

int Interp::read_named_parameter(
				 char *line,   //!< string: line of RS274/NGC code being processed
				 int *counter, //!< pointer to a counter for position on the line
				 double *double_ptr,   //!< pointer to double to be read
				 double *parameters,   //!< array of system parameters
				 bool check_exists)    //!< test for existence, not value
{
    static char name[] = "read_named_parameter";
    char paramNameBuf[LINELEN+1];
    int exists;
    double value;
    parameter_map_iterator pi;

    CHKS((line[*counter] != '<'),
	 NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED);
    CHP(read_name(line, counter, paramNameBuf));

    CHP(find_named_param(paramNameBuf, &exists, &value));
    if (check_exists) {
	*double_ptr = exists ? 1.0 : 0.0;
	return INTERP_OK;
    }
    if (exists) {
	*double_ptr = value;
	return INTERP_OK;
    } else {
        // do not require named parameters to be defined during a 
        // subroutine definition:
        if (_setup.defining_sub)
            return INTERP_OK;

	logNP("%s: referencing undefined named parameter '%s' level=%d",
	      name, paramNameBuf, (paramNameBuf[0] == '_') ? 0 : _setup.call_level);
	ERS(_("Named parameter #<%s> not defined"), paramNameBuf);
    }
    return INTERP_OK;
}

// if the variable is of the form '_ini[section]name', then treat it as
// an inifile  variable. Lookup section/name and cache the value
// as global and read-only.
// the shortest possible ini variable is '_ini[s]n' or 8 chars long .
int Interp::fetch_ini_param( const char *nameBuf, int *status, double *value)
{
    char *s;
    *status = 0;
    int n = strlen(nameBuf);

     if ((n > 7) &&
	((s = (char *) strchr(&nameBuf[6],']')) != NULL)) {

	IniFile inifile;
	const char *iniFileName;
	int retval;
	int closeBracket = s - nameBuf;

	if ((iniFileName = getenv("INI_FILE_NAME")) == NULL) {
	    logNP("warning: referencing ini parameter '%s': no ini file",nameBuf);
	    *status = 0;
	    return INTERP_OK;
	}
	if (!inifile.Open(iniFileName)) {
	    *status = 0;
	    ERS(_("cant open ini file '%s'"), iniFileName);
	}

	char capName[LINELEN];

	strncpy(capName, nameBuf, n);
	capName[n] = '\0';
	for (char *p = capName; *p != 0; p++)
	    *p = toupper(*p);
	capName[closeBracket] = '\0';

	if ((retval = inifile.Find( value, &capName[closeBracket+1], &capName[5])) == 0) {
	    *status = 1;
	    inifile.Close();
	} else {
	    inifile.Close();
	    *status = 0;
	    ERS(_("Named ini parameter #<%s> not found in inifile '%s': error=0x%x"),
		nameBuf, iniFileName, retval);
	}
    }
    return INTERP_OK;
}

// if the variable is of the form '_hal[hal_name]', then treat it as
// a HAL pin, signal or param. Lookup value, convert to float, and export as global and read-only.
// do not cache.
// the shortest possible ini variable is '_hal[x]' or 7 chars long .
int Interp::fetch_hal_param( const char *nameBuf, int *status, double *value)
{
    static int comp_id;
    int retval;
    int type = 0;
    hal_data_u* ptr;
    char hal_name[LINELEN];

    *status = 0;
    if (!comp_id) {
	char hal_comp[LINELEN];
	sprintf(hal_comp,"interp%d",getpid());
	comp_id = hal_init(hal_comp); // manpage says: NULL ok - which fails miserably
	CHKS(comp_id < 0,_("fetch_hal_param: hal_init(%s): %d"), hal_comp,comp_id);
	CHKS((retval = hal_ready(comp_id)), _("fetch_hal_param: hal_ready(): %d"),retval);
    }
    char *s;
    int n = strlen(nameBuf);
    if ((n > 6) &&
	((s = (char *) strchr(&nameBuf[5],']')) != NULL)) {

	int closeBracket = s - nameBuf;
	hal_pin_t *pin;
	hal_sig_t *sig;
	hal_param_t *param;

	strncpy(hal_name, &nameBuf[5], closeBracket);
	hal_name[closeBracket - 5] = '\0';
	if (nameBuf[closeBracket + 1]) {
	    logOword("%s: trailing garbage after closing bracket", hal_name);
	    *status = 0;
	    ERS("%s: trailing garbage after closing bracket", nameBuf);
	}
	// the result of these lookups could be cached in the parameter struct, but I'm not sure
	// this is a good idea - a removed pin/signal will not be noticed

	// I dont think that's needed - no change in pins/sigs/params
	// rtapi_mutex_get(&(hal_data->mutex)); 
        // rtapi_mutex_give(&(hal_data->mutex));

	if ((pin = halpr_find_pin_by_name(hal_name)) != NULL) {
            if (pin && !pin_is_linked(pin)) {
		logOword("%s: no signal connected", hal_name);
	    } 
	    type = pin_type(pin);
	    sig = signal_of(pin);
	    ptr = pin_value(pin);
	    // if (pin->signal != 0) {
	    // 	sig = (hal_sig_t *) SHMPTR(pin->signal);
	    // 	ptr = (hal_data_u *) SHMPTR(sig->data_ptr);
	    // } else {
	    // 	ptr = (hal_data_u *) &(pin->dummysig);
	    // }
	    goto assign;
	}
	if ((sig = halpr_find_sig_by_name(hal_name)) != NULL) {
	    if (!sig->writers) 
		logOword("%s: signal has no writer", hal_name);
	    type = sig_type(sig);
	    ptr = sig_value(sig);
	    //  ptr = (hal_data_u *) SHMPTR(sig->data_ptr);
	    goto assign;
	}
	if ((param = halpr_find_param_by_name(hal_name)) != NULL) {
	    type = param->type;
	    ptr = (hal_data_u *) SHMPTR(param->data_ptr);
	    goto assign;
	}
	*status = 0;
	ERS("Named hal parameter #<%s> not found", nameBuf);
    }
    return INTERP_OK;

    assign:
    switch (type) {
    case HAL_BIT: *value = (double) get_bit_value(ptr); break;
    case HAL_U32: *value = (double) get_u32_value(ptr); break;
    case HAL_S32: *value = (double) get_s32_value(ptr); break;
    case HAL_FLOAT: *value = (double) get_float_value(ptr); break;
    }
    logOword("%s: value=%f", hal_name, *value);
    *status = 1;
    return INTERP_OK; 
}

int Interp::find_named_param(
    const char *nameBuf, //!< pointer to name to be read
    int *status,    //!< pointer to return status 1 => found
    double *value   //!< pointer to value of found parameter
    )
{
  context_pointer frame;
  parameter_map_iterator pi;
  int level;

  level = (nameBuf[0] == '_') ? 0 : _setup.call_level; // determine scope
  frame = &_setup.sub_context[level];
  *status = 0;

  pi = frame->named_params.find(nameBuf);
  if (pi == frame->named_params.end()) { // not found
      int exists = 0;
      double inivalue;
      if (FEATURE(INI_VARS) && (strncasecmp(nameBuf,"_ini[",5) == 0)) {
	 fetch_ini_param(nameBuf, &exists, &inivalue);
	  if (exists) {
	      logNP("parameter '%s' retrieved from INI: %f",nameBuf,inivalue);
	      *value = inivalue;
	      *status = 1;
	      parameter_value param;  // cache the value
	      param.value = inivalue;
	      param.attr = PA_GLOBAL | PA_READONLY | PA_FROM_INI;
	      _setup.sub_context[0].named_params[strstore(nameBuf)] = param;
	      return INTERP_OK;
	  } 
      }
      if (FEATURE(HAL_PIN_VARS) && (strncasecmp(nameBuf,"_hal[",5) == 0)) {
	  fetch_hal_param(nameBuf, &exists, &inivalue);
	  if (exists) {
	      logNP("parameter '%s' retrieved from HAL: %f",nameBuf,inivalue);
	      *value = inivalue;
	      *status = 1;
	      return INTERP_OK;
	  } 
      }
      *value = 0.0;
      *status = 0;
  } else {
      parameter_pointer pv = &pi->second;
      if (pv->attr & PA_UNSET)
	  logNP("warning: referencing unset variable '%s'",nameBuf);
      if (pv->attr & PA_USE_LOOKUP) {
	  CHP(lookup_named_param(nameBuf, pv->value, value));
	  *status = 1;
      } else if (pv->attr & PA_PYTHON) {
	  bp::object retval, tupleargs, kwargs;
	  bp::list plist;

	  plist.append(_setup.pythis); // self
	  tupleargs = bp::tuple(plist);
	  kwargs = bp::dict();

	  python_plugin->call(NAMEDPARAMS_MODULE, nameBuf, tupleargs, kwargs, retval);
	  CHKS(python_plugin->plugin_status() == PLUGIN_EXCEPTION,
	       "named param - pycall(%s):\n%s", nameBuf,
	       python_plugin->last_exception().c_str());
	  CHKS(retval.ptr() == Py_None, "Python namedparams.%s returns no value", nameBuf);
	  if (PyString_Check(retval.ptr())) {
	      // returning a string sets the interpreter error message and aborts
	      *status = 0;
	      char *msg = bp::extract<char *>(retval);
	      ERS("%s", msg);
	  }
	  if (PyInt_Check(retval.ptr())) { // widen
	      *value = (double) bp::extract<int>(retval);
	      *status = 1;
	      return INTERP_OK;
	  }
	  if (PyFloat_Check(retval.ptr())) {
	      *value =  bp::extract<double>(retval);
	      *status = 1;
	      return INTERP_OK;
	  }
	  // ok, that callable returned something botched.
	  *status = 0;
	  PyObject *res_str = PyObject_Str(retval.ptr());
	  Py_XDECREF(res_str);
	  ERS("Python call %s.%s returned '%s' - expected double, int or string, got %s",
	      NAMEDPARAMS_MODULE, nameBuf,
	      PyString_AsString(res_str),
	      retval.ptr()->ob_type->tp_name);
      } else {
	  *value = pv->value;
	  *status = 1;
      }
  }
  return INTERP_OK;
}


int Interp::store_named_param(setup_pointer settings,
    const char *nameBuf, //!< pointer to name to be written
    double value,   //!< value to be written
    int override_readonly  //!< set to true to init a r/o parameter
    )
{
  context_pointer frame;
  int level;
  parameter_map_iterator pi;

  level = (nameBuf[0] == '_') ? 0 : _setup.call_level; // determine scope
  frame = &settings->sub_context[level];

  pi = frame->named_params.find(nameBuf);
  if (pi == frame->named_params.end()) {
      ERS(_("Internal error: Could not assign #<%s>"), nameBuf);
  } else {
      parameter_pointer pv = &pi->second;

      CHKS(((pv->attr & PA_GLOBAL)  && level),
	   "BUG: variable '%s' marked global, but assigned at level %d", nameBuf, level);

      if ((pv->attr & PA_READONLY) && !override_readonly) {
	  ERS(_("Cannot assign to read-only parameter #<%s>"), nameBuf);
      } else {
	  pv->value = value;
	  pv->attr &= ~PA_UNSET;
	  logNP("store_named_parameter: level[%d] %s value=%lf",
		level, nameBuf, value);
      }
  }
  return INTERP_OK;
}



int Interp::add_named_param(
    const char *nameBuf, //!< pointer to name to be added
    int attr) //!< see PA_* defs in interp_internal.hh
{
  static char name[] = "add_named_param";
  int findStatus;
  double value;
  int level;
  parameter_value param;

  // look it up to see if already exists
  CHP(find_named_param(nameBuf, &findStatus, &value));

  if (findStatus) {
      logNP("%s: parameter:|%s| already exists", name, nameBuf);
      return INTERP_OK;
  }
  attr |= PA_UNSET;

  if (nameBuf[0] != '_') {  // local scope
      level = _setup.call_level;
  } else {
      level = 0;    // call level zero is global scope
      attr |= PA_GLOBAL;
  }
  param.value = 0.0;
  param.attr = attr;
  _setup.sub_context[level].named_params[strstore(nameBuf)] = param;
  return INTERP_OK;
}


int Interp::free_named_parameters(context_pointer frame)
{
    frame->named_params.clear();
    return INTERP_OK;
}


// just a shorthand
int Interp::init_readonly_param(
    const char *nameBuf, //!< pointer to name to be added
    double value,  //!< initial value
    int attr)       //!< see PA_* defs in interp_internal.hh
{
    // static char name[] = "init_readonly_param";
    CHKS( add_named_param((char *) nameBuf, PA_READONLY|attr),
	  "adding r/o '%s'", nameBuf);
    CHKS(store_named_param(&_setup, (char *) nameBuf, value, OVERRIDE_READONLY),
	 "storing r/o '%s' %f", nameBuf, value);
    return INTERP_OK;
}


int Interp::lookup_named_param(const char *nameBuf,
			   double index,
			   double *value)
{
    int cmd = round_to_int(index);

    switch (cmd) {

	// some active_g_codes fields

    case NP_LINE: // _line - sequence number
	*value = _setup.sequence_number;
	break;

    case NP_MOTION_MODE: // _motion_mode
	*value = _setup.motion_mode;
	break;

    case NP_PLANE: // _plane
	switch(_setup.plane) {
	case CANON_PLANE_XY:
	    *value = G_17;
	    break;
	case CANON_PLANE_XZ:
	    *value = G_18;
	    break;
	case CANON_PLANE_YZ:
	    *value = G_19;
	    break;
	case CANON_PLANE_UV:
	    *value = G_17_1;
	    break;
	case CANON_PLANE_UW:
	    *value = G_18_1;
	    break;
	case CANON_PLANE_VW:
	    *value = G_19_1;
	    break;
	}
	break;

    case NP_CCOMP: // _ccomp - cutter compensation
	*value =
	    (_setup.cutter_comp_side == RIGHT) ? G_42 :
	(_setup.cutter_comp_side == LEFT) ? G_41 : G_40;
	break;

    case NP_METRIC: // _metric
	*value = (_setup.length_units == CANON_UNITS_MM);
	break;

    case NP_IMPERIAL: // _imperial
	*value = (_setup.length_units == CANON_UNITS_INCHES);
	break;

    case NP_ABSOLUTE: // _absolute - distance mode
	*value = (_setup.distance_mode == MODE_ABSOLUTE);
	break;

    case NP_INCREMENTAL: // _incremental - distance mode
	*value = (_setup.distance_mode == MODE_INCREMENTAL);
	break;

    case NP_INVERSE_TIME: // _inverse_time - feed mode
	*value = (_setup.feed_mode == INVERSE_TIME);
	break;

    case NP_UNITS_PER_MINUTE: // _units_per_minute - feed mode
	*value = (_setup.feed_mode == UNITS_PER_MINUTE);
	break;

    case NP_UNITS_PER_REV: // _units_per_rev - feed mode
	*value = (_setup.feed_mode == UNITS_PER_REVOLUTION);
	break;

    case NP_COORD_SYSTEM: // _coord_system - 0-9
	*value =
	    (_setup.origin_index < 7) ? (530 + (10 * _setup.origin_index)) :
	    (584 + _setup.origin_index);
	break;

    case NP_TOOL_OFFSET: // _tool_offset
	*value =  (_setup.tool_offset.tran.x || _setup.tool_offset.tran.y || _setup.tool_offset.tran.z ||
		   _setup.tool_offset.a || _setup.tool_offset.b || _setup.tool_offset.c ||
		   _setup.tool_offset.u || _setup.tool_offset.v || _setup.tool_offset.w) ;
	break;

    case NP_RETRACT_R_PLANE: // _retract_r_plane - G98
	*value = (_setup.retract_mode == R_PLANE);
	break;

    case NP_RETRACT_OLD_Z: // _retract_old_z - G99
	*value = (_setup.retract_mode == OLD_Z);
	break;

    case NP_SPINDLE_RPM_MODE: // _spindle_rpm_mode G97
	*value = (_setup.spindle_mode == CONSTANT_RPM);
	break;

    case NP_SPINDLE_CSS_MODE: // _spindle_css_mode G96
	*value = (_setup.spindle_mode == CONSTANT_SURFACE);
	break;

    case NP_IJK_ABSOLUTE_MODE: //_ijk_absolute_mode - G90.1
	*value = (_setup.ijk_distance_mode == MODE_ABSOLUTE);
	break;

    case NP_LATHE_DIAMETER_MODE: // _lathe_diameter_mode - G7
	*value = _setup.lathe_diameter_mode;
	break;

    case NP_LATHE_RADIUS_MODE: // _lathe_radius_mode - G8
	*value = (_setup.lathe_diameter_mode == 0);
	break;

	// some active_m_codes fields

    case NP_SPINDLE_ON: // _spindle_on
	*value = (_setup.spindle_turning != CANON_STOPPED);
	break;

    case NP_SPINDLE_CW: // spindle_cw
	*value = (_setup.spindle_turning == CANON_CLOCKWISE);
	break;

    case NP_MIST: // mist
	*value = _setup.mist;
	break;

    case NP_FLOOD: // flood
	*value = _setup.flood;
	break;

    case NP_SPEED_OVERRIDE: // speed override
	*value = _setup.speed_override;
	break;

    case NP_FEED_OVERRIDE: // feed override
	*value = _setup.feed_override;
	break;

    case NP_ADAPTIVE_FEED: // adaptive feed
	*value = _setup.adaptive_feed;
	break;

    case NP_FEED_HOLD: // feed hold
	*value = _setup.feed_hold;
	break;

	// from active_settings:
    case NP_FEED: // feed
	*value = _setup.feed_rate;
	break;

    case NP_RPM: // speed (rpm)
	*value = abs(_setup.speed);
	break;

    case NP_CURRENT_TOOL:
	*value = _setup.tool_table[_setup.current_pocket].toolno;
	break;

    case NP_SELECTED_POCKET:
	*value = _setup.selected_pocket;
	break;

    case NP_CURRENT_POCKET:
	*value = _setup.current_pocket;
	break;

    case NP_SELECTED_TOOL:
	*value = _setup.selected_tool;
	break;

    case NP_X:  // current position
	*value = _setup.current_x;
	break;

    case NP_Y:  // current position
	*value = _setup.current_y;
	break;

    case NP_Z:  // current position
	*value = _setup.current_z;
	break;

    case NP_A:  // current position
	*value = _setup.AA_current;
	break;

    case NP_B:  // current position
	*value = _setup.BB_current;
	break;

    case NP_C:  // current position
	*value = _setup.CC_current;
	break;

    case NP_U:  // current position
	*value = _setup.u_current;
	break;

    case NP_V:  // current position
	*value = _setup.v_current;
	break;

    case NP_W:  // current position
	*value = _setup.w_current;
	break;

	// o-word subs may optionally have an
	// expression after endsub and return
	// this 'function return value' is accessible as '_value'
    case NP_VALUE:
	*value = _setup.return_value;
	break;

	// predicate: the last NGC procedure did/did not return a value
    case NP_VALUE_RETURNED:
	*value = _setup.value_returned;
	break;

    case NP_CALL_LEVEL:
	*value = _setup.call_level;
	break;

    case NP_REMAP_LEVEL:
	*value = _setup.remap_level;
	break;
	
    case NP_TASK:
	extern int _task;  // zero in gcodemodule, 1 in milltask
	*value = _task;
	break;


    default:
	ERS(_("BUG: lookup_named_param(%s): unhandled index=%fn"),
	      nameBuf,index);
    }
    return INTERP_OK;
}

int Interp::init_python_predef_parameter(const char *name)
{
    int exists = 0;
    double value;
    parameter_value param;

    if (name[0] == '_') { // globals only
	find_named_param(name, &exists, &value);
	if (exists) {
	    fprintf(stderr, "warning: redefining named parameter %s\n",name);
	    _setup.sub_context[0].named_params.erase(name);
	}
	param.value = 0.0;
	param.attr = PA_READONLY|PA_PYTHON|PA_GLOBAL;
	_setup.sub_context[0].named_params[strstore(name)] = param;
    }
    return INTERP_OK;
}

int Interp::init_named_parameters()
{

// version       major   minor      Note
// ------------ -------- ---------- -------------------------------------
// M.N.m         M.N     0.m        normal format
// M.N.m~xxx     M.N     0.m        pre-release format
  const char *pkgversion = PACKAGE_VERSION;  //examples: 2.4.6, 2.5.0~pre
  const char *version_major = "_vmajor";// named_parameter name (use lower case)
  const char *version_minor = "_vminor";// named_parameter name (use lower case)
  double vmajor=0.0, vminor=0.0;
  sscanf(pkgversion, "%lf%lf", &vmajor, &vminor);

  init_readonly_param(version_major,vmajor,0);
  init_readonly_param(version_minor,vminor,0);

  // params tagged with PA_USE_LOOKUP will call the lookup_named_param()
  // method. The value is used as a index for the switch() statement.

  // the active_g_codes fields

  // I guess this is the line number
  init_readonly_param("_line", NP_LINE, PA_USE_LOOKUP);

  // any of G1 G2 G3 G5.2 G73 G80 G82 G83 G86 G87 G88 G89
  // value is number after 'G' mutiplied by 10 (10,20,30,52..)

  init_readonly_param("_motion_mode", NP_MOTION_MODE, PA_USE_LOOKUP);

  // G17/18/19/17.1/18.1/19.1 -> return 170/180/190/171/181/191
  init_readonly_param("_plane", NP_PLANE, PA_USE_LOOKUP);

  // return 400,410,420 depending if (G40,G41,G42) is on
  init_readonly_param("_ccomp", NP_CCOMP, PA_USE_LOOKUP);

  // 1.0 if G21 is on
  init_readonly_param("_metric", NP_METRIC, PA_USE_LOOKUP);

  // 1.0 if G20 is on
  init_readonly_param("_imperial", NP_IMPERIAL, PA_USE_LOOKUP);

  //1.0 if G90 is on
  init_readonly_param("_absolute", NP_ABSOLUTE, PA_USE_LOOKUP);

  //1.0 if G91 is on
  init_readonly_param("_incremental", NP_INCREMENTAL, PA_USE_LOOKUP);

  // 1.0 if G93 is on
  init_readonly_param("_inverse_time", NP_INVERSE_TIME, PA_USE_LOOKUP);

  // 1.0 if G94 is on
  init_readonly_param("_units_per_minute", NP_UNITS_PER_MINUTE, PA_USE_LOOKUP);

  // 1.0 if G95 is on
  init_readonly_param("_units_per_rev", NP_UNITS_PER_REV, PA_USE_LOOKUP);

  // 0..9 for G54..G59.3
  init_readonly_param("_coord_system", NP_COORD_SYSTEM, PA_USE_LOOKUP);

  // 1.0 if G43 is on
  init_readonly_param("_tool_offset", NP_TOOL_OFFSET, PA_USE_LOOKUP);

  // 1 if G98 set
  init_readonly_param("_retract_r_plane", NP_RETRACT_R_PLANE, PA_USE_LOOKUP);

  // 1 if G99 set
  init_readonly_param("_retract_old_z", NP_RETRACT_OLD_Z, PA_USE_LOOKUP);

  // really esoteric
  // init_readonly_param("_control_mode", 110, PA_USE_LOOKUP);


  // 1 if G97 is on
  init_readonly_param("_spindle_rpm_mode", NP_SPINDLE_RPM_MODE, PA_USE_LOOKUP);

  init_readonly_param("_spindle_css_mode", NP_SPINDLE_CSS_MODE, PA_USE_LOOKUP);

  // 1 if G90.1 is on
  init_readonly_param("_ijk_absolute_mode", NP_IJK_ABSOLUTE_MODE, PA_USE_LOOKUP);

  // 1 if G7 is on
  init_readonly_param("_lathe_diameter_mode", NP_LATHE_DIAMETER_MODE, PA_USE_LOOKUP);

  // 1 if G8 is on
  init_readonly_param("_lathe_radius_mode", NP_LATHE_RADIUS_MODE, PA_USE_LOOKUP);


  // the active_m_codes fields
  init_readonly_param("_spindle_on", NP_SPINDLE_ON, PA_USE_LOOKUP);
  init_readonly_param("_spindle_cw", NP_SPINDLE_CW, PA_USE_LOOKUP);

  init_readonly_param("_mist", NP_MIST, PA_USE_LOOKUP);
  init_readonly_param("_flood", NP_FLOOD, PA_USE_LOOKUP);
  init_readonly_param("_speed_override", NP_SPEED_OVERRIDE, PA_USE_LOOKUP);
  init_readonly_param("_feed_override", NP_FEED_OVERRIDE, PA_USE_LOOKUP);
  init_readonly_param("_adaptive_feed", NP_ADAPTIVE_FEED, PA_USE_LOOKUP);
  init_readonly_param("_feed_hold", NP_FEED_HOLD, PA_USE_LOOKUP);

  // active_settings
  init_readonly_param("_feed", NP_FEED, PA_USE_LOOKUP);
  init_readonly_param("_rpm", NP_RPM, PA_USE_LOOKUP);


  // tool related
  init_readonly_param("_current_tool", NP_CURRENT_TOOL, PA_USE_LOOKUP);
  init_readonly_param("_current_pocket", NP_CURRENT_POCKET, PA_USE_LOOKUP);
  init_readonly_param("_selected_pocket", NP_SELECTED_POCKET, PA_USE_LOOKUP);
  init_readonly_param("_selected_tool", NP_SELECTED_TOOL, PA_USE_LOOKUP);

  // current position - alias to #5420-#5429
  init_readonly_param("_x", NP_X, PA_USE_LOOKUP);
  init_readonly_param("_y", NP_Y, PA_USE_LOOKUP);
  init_readonly_param("_z", NP_Z, PA_USE_LOOKUP);
  init_readonly_param("_a", NP_A, PA_USE_LOOKUP);
  init_readonly_param("_b", NP_B, PA_USE_LOOKUP);
  init_readonly_param("_c", NP_C, PA_USE_LOOKUP);
  init_readonly_param("_u", NP_U, PA_USE_LOOKUP);
  init_readonly_param("_v", NP_V, PA_USE_LOOKUP);
  init_readonly_param("_w", NP_W, PA_USE_LOOKUP);

  // last (optional) endsub/return value
  init_readonly_param("_value", NP_VALUE, PA_USE_LOOKUP);

  // predicate: last NGC procedure did return a value on endsub/return
  init_readonly_param("_value_returned", NP_VALUE_RETURNED, PA_USE_LOOKUP);

  // predicate: 1 in milltask instance, 0 in UI - control preview behaviour
  init_readonly_param("_task", NP_TASK, PA_USE_LOOKUP);

  // debugging aids
  init_readonly_param("_call_level", NP_CALL_LEVEL, PA_USE_LOOKUP);
  init_readonly_param("_remap_level", NP_REMAP_LEVEL, PA_USE_LOOKUP);

  return INTERP_OK;
}
