lib/C/iec_std_lib.h
author Edouard Tisserant
Tue, 14 Dec 2021 08:48:05 +0100
changeset 1096 fad6e7a818e2
parent 1070 16dc09ee42d8
child 1104 0bc1f5a4f975
permissions -rwxr-xr-x
Variable forcing now uses limited list and buffer instead of systematical instance tree traversal and in-tree "fvalue" to keep track of forced value for pointed variables (external, located). Pointer swapping is performed when forcing externals and located, with backup being restored when forcing is reset. Match Beremiz commit 93ad018fb602.
/*
 * copyright 2008 Edouard TISSERANT
 * copyright 2011 Mario de Sousa (msousa@fe.up.pt)
 *
 * Offered to the public under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either version 2
 * 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 Lesser
 * General Public License for more details.
 *
 * This code is made available on the understanding that it will not be
 * used in safety-critical situations without a full and competent review.
 */

/****
 * IEC 61131-3 standard function library
 */

/* NOTE: This file is full of (what may seem at first) very strange macros.
 *       If you want to know what all these strange macros are doing,
 *       just parse this file through a C preprocessor (e.g. cpp), 
 *       and analyse the output!
 *       $gcc -E iec_std_lib.h 
 */

#ifndef _IEC_STD_LIB_H
#define _IEC_STD_LIB_H


#include <limits.h>
#include <float.h>
#include <math.h>
#include <stdint.h>
#include <ctype.h>
#include <time.h>

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

#ifdef DEBUG_IEC
#define DBG(...) printf(__VA_ARGS__);
#define DBG_TYPE(TYPENAME, name) __print_##TYPENAME(name);
#else
#define DBG(...)
#define DBG_TYPE(TYPENAME, name)
#endif

/*
 * Include type defs.
 */
#include "iec_types_all.h"

extern TIME __CURRENT_TIME;
extern BOOL __DEBUG;

/* TODO
typedef struct {
    __strlen_t len;
    u_int16_t body[STR_MAX_LEN];
} WSTRING;
*/
/*
# if __WORDSIZE == 64
#define __32b_sufix
#define __64b_sufix L
#else
#define __32b_sufix L
#define __64b_sufix LL
#endif
*/

# if __WORDSIZE == 64
#define __32b_sufix
#define __64b_sufix L
#else
#define __32b_sufix L
/* changed this from LL to L temporarily. It was causing a bug when compiling resulting code with gcc.
 * I have other things to worry about at the moment.. 
 */
#define __64b_sufix L   
#endif


#define __lit(type,value,...) (type)value##__VA_ARGS__
// Keep this macro expention step to let sfx(__VA_ARGS__) change into L or LL
#define __literal(type,value,...) __lit(type,value,__VA_ARGS__)

#define __BOOL_LITERAL(value) __literal(BOOL,value)
#define __SINT_LITERAL(value) __literal(SINT,value)
#define __INT_LITERAL(value) __literal(INT,value)
#define __DINT_LITERAL(value) __literal(DINT,value,__32b_sufix)
#define __LINT_LITERAL(value) __literal(LINT,value,__64b_sufix)
#define __USINT_LITERAL(value) __literal(USINT,value)
#define __UINT_LITERAL(value) __literal(UINT,value)
#define __UDINT_LITERAL(value) __literal(UDINT,value,__32b_sufix)
#define __ULINT_LITERAL(value) __literal(ULINT,value,__64b_sufix)
#define __REAL_LITERAL(value) __literal(REAL,value,__32b_sufix)
#define __LREAL_LITERAL(value) __literal(LREAL,value,__64b_sufix)
#define __TIME_LITERAL(value) __literal(TIME,value)
#define __DATE_LITERAL(value) __literal(DATE,value)
#define __TOD_LITERAL(value) __literal(TOD,value)
#define __DT_LITERAL(value) __literal(DT,value)
#define __STRING_LITERAL(count,value) (STRING){count,value}
#define __BYTE_LITERAL(value) __literal(BYTE,value)
#define __WORD_LITERAL(value) __literal(WORD,value)
#define __DWORD_LITERAL(value) __literal(DWORD,value,__32b_sufix)
#define __LWORD_LITERAL(value) __literal(LWORD,value,__64b_sufix)


typedef union __IL_DEFVAR_T {
    BOOL    BOOLvar;

    SINT    SINTvar;
    INT     INTvar;
    DINT    DINTvar;
    LINT    LINTvar;

    USINT   USINTvar;
    UINT    UINTvar;
    UDINT   UDINTvar;
    ULINT   ULINTvar;

    BYTE    BYTEvar;
    WORD    WORDvar;
    DWORD   DWORDvar;
    LWORD   LWORDvar;

    REAL    REALvar;
    LREAL   LREALvar;

    TIME    TIMEvar;
    TOD TODvar;
    DT  DTvar;
    DATE    DATEvar;
} __IL_DEFVAR_T;


/**********************************************************************/
/**********************************************************************/
/*****                                                            *****/
/*****      Some helper functions...                              *****/
/*****                     ...used later:                         *****/
/*****    - when declaring the IEC 61131-3 standard functions     *****/
/*****    - in the C source code itself in SFC and ST expressions *****/
/*****                                                            *****/
/**********************************************************************/
/**********************************************************************/


/****************************/
/* Notify IEC runtime error */
/****************************/

/* function that generates an IEC runtime error */
static inline void __iec_error(void) {
  /* TODO... */
  fprintf(stderr, "IEC 61131-3 runtime error.\n");
  /*exit(1);*/
}


/*******************/
/* Math Operations */
/*******************/

static inline double __expt(double in1, double in2) {
  return pow(in1, in2);
}


/*******************************/
/* Time normalization function */
/*******************************/

static inline void __normalize_timespec (IEC_TIMESPEC *ts) {
  if( ts->tv_nsec < -1000000000 || (( ts->tv_sec > 0 ) && ( ts->tv_nsec < 0 ))){
    ts->tv_sec--;
    ts->tv_nsec += 1000000000;
  }
  if( ts->tv_nsec > 1000000000 || (( ts->tv_sec < 0 ) && ( ts->tv_nsec > 0 ))){
    ts->tv_sec++;
    ts->tv_nsec -= 1000000000;
  }
}

/**********************************************/
/* Time conversion to/from timespec functions */
/**********************************************/
/* NOTE: The following function was turned into a macro, so it could be used to initialize the initial value of TIME variables.
 *       Since each macro parameter is evaluated several times, the macro may result in multiple function invocations if an expression
 *       containing a function invocation is passed as a parameter. However, currently matiec only uses this conversion macro with 
 *       constant literals, so it is safe to change it into a macro.
 */
/* NOTE: I (Mario - msousa@fe.up.pt) believe that the following function contains a bug when handling negative times.
 *       The equivalent macro has this bug fixed.
 *       e.g.;
 *          T#3.8s
 *       using the function, will result in a timespec of 3.8s !!!: 
 *          tv_sec  =  4               <-----  1 *  3.8           is rounded up when converting a double to an int!
 *          tv_nsec = -200 000 000     <-----  1 * (3.8 - 4)*1e9
 * 
 *         -T#3.8s
 *       using the function, will result in a timespec of -11.8s !!!: 
 *          tv_sec  = -4                 <-----  -1 *  3.8 is rounded down when converting a double to an int!
 *          tv_nsec = -7 800 000 000     <-----  -1 * (3.8 - -4)*1e9
 */
/* NOTE: Due to the fact that the C compiler may round a tv_sec number away from zero, 
 *       the following macro may result in a timespec that is not normalized, i.e. with a tv_sec > 0, and a tv_nsec < 0 !!!!
 *       This is due to the rounding that C compiler applies when converting a (long double) to a (long int).
 *       To produce normalized timespec's we need to use floor(), but we cannot call any library functions since we want this macro to be 
 *       useable as a variable initializer.
 *       VAR x : TIME = T#3.5h; END_VAR --->  IEC_TIME x = __time_to_timespec(1, 0, 0, 0, 3.5, 0);
 */
/*
static inline IEC_TIMESPEC __time_to_timespec(int sign, double mseconds, double seconds, double minutes, double hours, double days) {
  IEC_TIMESPEC ts;

  // sign is 1 for positive values, -1 for negative time...
  long double total_sec = ((days*24 + hours)*60 + minutes)*60 + seconds + mseconds/1e3;
  if (sign >= 0) sign = 1; else sign = -1;
  ts.tv_sec = sign * (long int)total_sec;
  ts.tv_nsec = sign * (long int)((total_sec - ts.tv_sec)*1e9);

  return ts;
}
*/
/* NOTE: Unfortunately older versions of ANSI C (e.g. C99) do not allow explicit identification of elements in initializers
 *         e.g.  {tv_sec = 1, tv_nsec = 300}
 *       They are therefore commented out. This however means that any change to the definition of IEC_TIMESPEC may require this
 *       macro to be updated too!
 */
#define __time_to_timespec(sign,mseconds,seconds,minutes,hours,days) \
          ((IEC_TIMESPEC){\
              /*tv_sec  =*/ ((long int)   (((sign>=0)?1:-1)*((((long double)days*24 + (long double)hours)*60 + (long double)minutes)*60 + (long double)seconds + (long double)mseconds/1e3))), \
              /*tv_nsec =*/ ((long int)(( \
                            ((long double)(((sign>=0)?1:-1)*((((long double)days*24 + (long double)hours)*60 + (long double)minutes)*60 + (long double)seconds + (long double)mseconds/1e3))) - \
                            ((long int)   (((sign>=0)?1:-1)*((((long double)days*24 + (long double)hours)*60 + (long double)minutes)*60 + (long double)seconds + (long double)mseconds/1e3)))   \
                            )*1e9))\
        })




/* NOTE: The following function was turned into a macro, so it could be used to initialize the initial value of TOD (TIME_OF_DAY) variables */
/* NOTE: many (but not all) of the same comments made regarding __time_to_timespec() are also valid here, so go and read those comments too!*/
/*
static inline IEC_TIMESPEC __tod_to_timespec(double seconds, double minutes, double hours) {
  IEC_TIMESPEC ts;

  long double total_sec = (hours*60 + minutes)*60 + seconds;
  ts.tv_sec = (long int)total_sec;
  ts.tv_nsec = (long int)((total_sec - ts.tv_sec)*1e9);

  return ts;
}
*/
#define __tod_to_timespec(seconds,minutes,hours) \
          ((IEC_TIMESPEC){\
              /*tv_sec  =*/ ((long int)   ((((long double)hours)*60 + (long double)minutes)*60 + (long double)seconds)), \
              /*tv_nsec =*/ ((long int)(( \
                            ((long double)((((long double)hours)*60 + (long double)minutes)*60 + (long double)seconds)) - \
                            ((long int)   ((((long double)hours)*60 + (long double)minutes)*60 + (long double)seconds))   \
                            )*1e9))\
        })


#define EPOCH_YEAR 1970
#define SECONDS_PER_MINUTE 60
#define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE)
#define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR)
#define __isleap(year) \
  ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
static const unsigned short int __mon_yday[2][13] =
{
  /* Normal years.  */
  { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
  /* Leap years.  */
  { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
};

typedef struct {
	int tm_sec;			/* Seconds.	[0-60] (1 leap second) */
	int tm_min;			/* Minutes.	[0-59] */
	int tm_hour;			/* Hours.	[0-23] */
	int tm_day;			/* Day.		[1-31] */
	int tm_mon;			/* Month.	[0-11] */
	int tm_year;			/* Year	*/
} tm;

static inline tm convert_seconds_to_date_and_time(long int seconds) {
  tm dt;
  long int days, rem;
  days = seconds / SECONDS_PER_DAY;
  rem = seconds % SECONDS_PER_DAY;
  if (rem < 0) {
	  rem += SECONDS_PER_DAY;
	  days--;
  }

  // time of day
  dt.tm_hour = rem / SECONDS_PER_HOUR;
  rem %= SECONDS_PER_HOUR;
  dt.tm_min = rem / 60;
  dt.tm_sec = rem % 60;

  // date
  dt.tm_year = EPOCH_YEAR;
  while (days >= (rem = __isleap(dt.tm_year) ? 366 : 365)) {
	  dt.tm_year++;
	  days -= rem;
  }
  while (days < 0) {
	  dt.tm_year--;
	  days += __isleap(dt.tm_year) ? 366 : 365;
  }
  dt.tm_mon = 1;
  while (days >= __mon_yday[__isleap(dt.tm_year)][dt.tm_mon]) {
	  dt.tm_mon += 1;
  }
  dt.tm_day = days - __mon_yday[__isleap(dt.tm_year)][dt.tm_mon - 1] + 1;

  return dt;
}

static inline IEC_TIMESPEC __date_to_timespec(int day, int month, int year) {
  IEC_TIMESPEC ts;
  int a4, b4, a100, b100, a400, b400;
  int yday;
  int intervening_leap_days;

  if (month < 1 || month > 12)
	 __iec_error();

  yday = __mon_yday[__isleap(year)][month - 1] + day;

  if (yday > __mon_yday[__isleap(year)][month])
	  __iec_error();

  a4 = (year >> 2) - ! (year & 3);
  b4 = (EPOCH_YEAR >> 2) - ! (EPOCH_YEAR & 3);
  a100 = a4 / 25 - (a4 % 25 < 0);
  b100 = b4 / 25 - (b4 % 25 < 0);
  a400 = a100 >> 2;
  b400 = b100 >> 2;
  intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
  
  ts.tv_sec = ((year - EPOCH_YEAR) * 365 + intervening_leap_days + yday - 1) * 24 * 60 * 60;
  ts.tv_nsec = 0;

  return ts;
}

static inline IEC_TIMESPEC __dt_to_timespec(double seconds, double minutes, double hours, int day, int month, int year) {
  IEC_TIMESPEC ts_date = __date_to_timespec(day, month, year);
  IEC_TIMESPEC ts = __tod_to_timespec(seconds, minutes, hours);

  ts.tv_sec += ts_date.tv_sec;

  return ts;
}

/*******************/
/* Time operations */
/*******************/

#define __time_cmp(t1, t2) (t2.tv_sec == t1.tv_sec ? t1.tv_nsec - t2.tv_nsec : t1.tv_sec - t2.tv_sec)

static inline TIME __time_add(TIME IN1, TIME IN2){
  TIME res ={IN1.tv_sec + IN2.tv_sec,
             IN1.tv_nsec + IN2.tv_nsec };
  __normalize_timespec(&res);
  return res;
}
static inline TIME __time_sub(TIME IN1, TIME IN2){
  TIME res ={IN1.tv_sec - IN2.tv_sec,
             IN1.tv_nsec - IN2.tv_nsec };
  __normalize_timespec(&res);
  return res;
}
static inline TIME __time_mul(TIME IN1, LREAL IN2){
  LREAL s_f = IN1.tv_sec * IN2;
  time_t s = (time_t)s_f;
  div_t ns = div((int)((LREAL)IN1.tv_nsec * IN2), 1000000000);
  TIME res = {(long)s + ns.quot,
		      (long)ns.rem + (s_f - s) * 1000000000 };
  __normalize_timespec(&res);
  return res;
}
static inline TIME __time_div(TIME IN1, LREAL IN2){
  LREAL s_f = IN1.tv_sec / IN2;
  time_t s = (time_t)s_f;
  TIME res = {(long)s,
              (long)(IN1.tv_nsec / IN2 + (s_f - s) * 1000000000) };
  __normalize_timespec(&res);
  return res;
}


/***************/
/* Convertions */
/***************/
    /*****************/
    /*  REAL_TO_INT  */
    /*****************/
static inline LINT __real_round(LREAL IN) {
	return fmod(IN, 1) == 0 ? ((LINT)IN / 2) * 2 : (LINT)IN;
}
static inline LINT __preal_to_sint(LREAL IN) {
   return IN >= 0 ? __real_round(IN + 0.5) : __real_round(IN - 0.5);
}
static inline LINT __preal_to_uint(LREAL IN) {
   return IN >= 0 ? __real_round(IN + 0.5) : 0;
}
static inline LINT __real_to_sint(LREAL IN)  {return (LINT)__preal_to_sint(IN);}
static inline LWORD __real_to_bit(LREAL IN)  {return (LWORD)__preal_to_uint(IN);}
static inline ULINT __real_to_uint(LREAL IN) {return (ULINT)__preal_to_uint(IN);}

    /***************/
    /*  TO_STRING  */
    /***************/
static inline STRING __bool_to_string(BOOL IN) {
    if(IN) return (STRING){4, "TRUE"};
    return (STRING){5,"FALSE"};
}
static inline STRING __bit_to_string(LWORD IN) {
    STRING res;
    res = __INIT_STRING;
    res.len = snprintf((char*)res.body, STR_MAX_LEN, "16#%llx",(long long unsigned int)IN);
    if(res.len > STR_MAX_LEN) res.len = STR_MAX_LEN;
    return res;
}
static inline STRING __real_to_string(LREAL IN) {
    STRING res;
    res = __INIT_STRING;
    res.len = snprintf((char*)res.body, STR_MAX_LEN, "%.10g", IN);
    if(res.len > STR_MAX_LEN) res.len = STR_MAX_LEN;
    return res;
}
static inline STRING __sint_to_string(LINT IN) {
    STRING res;
    res = __INIT_STRING;
    res.len = snprintf((char*)res.body, STR_MAX_LEN, "%lld", (long long int)IN);
    if(res.len > STR_MAX_LEN) res.len = STR_MAX_LEN;
    return res;
}
static inline STRING __uint_to_string(ULINT IN) {
    STRING res;
    res = __INIT_STRING;
    res.len = snprintf((char*)res.body, STR_MAX_LEN, "%llu", (long long unsigned int)IN);
    if(res.len > STR_MAX_LEN) res.len = STR_MAX_LEN;
    return res;
}
    /***************/
    /* FROM_STRING */
    /***************/
static inline BOOL __string_to_bool(STRING IN) {
    int i;
    if (IN.len == 1) return !memcmp(&IN.body,"1", IN.len);
    for (i = 0; i < IN.len; i++) IN.body[i] = toupper(IN.body[i]);
    return IN.len == 4 ? !memcmp(&IN.body,"TRUE", IN.len) : 0;
}

static inline LINT __pstring_to_sint(STRING* IN) {
    LINT res = 0;
    __strlen_t l;
    unsigned int shift = 0;

    if(IN->body[0]=='2' && IN->body[1]=='#'){
        /* 2#0101_1010_1011_1111 */
        for(l = IN->len - 1; l >= 2 && shift < 64; l--)
        {
            char c = IN->body[l];
            if( c >= '0' && c <= '1'){
                res |= ( c - '0') << shift;
                shift += 1;
            }
        }
    }else if(IN->body[0]=='8' && IN->body[1]=='#'){
        /* 8#1234_5665_4321 */
        for(l = IN->len - 1; l >= 2 && shift < 64; l--)
        {
            char c = IN->body[l];
            if( c >= '0' && c <= '7'){
                res |= ( c - '0') << shift;
                shift += 3;
            }
        }
    }else if(IN->body[0]=='1' && IN->body[1]=='6' && IN->body[2]=='#'){
        /* 16#1234_5678_9abc_DEFG */
        for(l = IN->len - 1; l >= 3 && shift < 64; l--)
        {
            char c = IN->body[l];
            if( c >= '0' && c <= '9'){
                res |= (LWORD)( c - '0') << shift;
                shift += 4;
            }else if( c >= 'a' && c <= 'f'){
                res |= (LWORD)( c - 'a' + 10 ) << shift;
                shift += 4;
            }else if( c >= 'A' && c <= 'F'){
                res |= (LWORD)( c - 'A' + 10 ) << shift;
                shift += 4;
            }
        }
    }else{
        /* -123456789 */
        LINT fac = IN->body[0] == '-' ? -1 : 1;
        for(l = IN->len - 1; l >= 0 && shift < 20; l--)
        {
            char c = IN->body[l];
            if( c >= '0' && c <= '9'){
                res += ( c - '0') * fac;
                fac *= 10;
                shift += 1;
            }else if( c >= '.' ){ /* reset value */
                res = 0;
                fac = IN->body[0] == '-' ? -1 : 1;
                shift = 0;
            }
        }
    }
    return res;
}

static inline LINT  __string_to_sint(STRING IN) {return (LINT)__pstring_to_sint(&IN);}
static inline LWORD __string_to_bit (STRING IN) {return (LWORD)__pstring_to_sint(&IN);}
static inline ULINT __string_to_uint(STRING IN) {return (ULINT)__pstring_to_sint(&IN);}
static inline LREAL __string_to_real(STRING IN) {
    __strlen_t l;
    l = IN.len;
    /* search the dot */
    while(--l > 0 && IN.body[l] != '.');
    if(l != 0){
        return atof((const char *)&IN.body);
    }else{
        return (LREAL)__pstring_to_sint(&IN);
    }
}

    /***************/
    /*   TO_TIME   */
    /***************/
static inline TIME    __int_to_time(LINT IN)  {return (TIME){IN, 0};}
static inline TIME   __real_to_time(LREAL IN) {return (TIME){IN, (IN - (LINT)IN) * 1000000000};}
static inline TIME __string_to_time(STRING IN){
    __strlen_t l;
    /* TODO :
     *
     *  Duration literals without underlines: T#14ms    T#-14ms   T#14.7s   T#14.7m
     *                short prefix            T#14.7h    t#14.7d   t#25h15m
     *                                        t#5d14h12m18s3.5ms
     *                long prefix             TIME#14ms    TIME#-14ms   time#14.7s
     *  Duration literals with underlines:
     *                short prefix            t#25h_15m t#5d_14h_12m_18s_3.5ms
     *                long prefix             TIME#25h_15m
     *                                        time#5d_14h_12m_18s_3.5ms
     *
     *  Long prefix notation                 Short prefix notation
     *  DATE#1984-06-25                      D#1984-06-25
     *  date#1984-06-25                      d#1984-06-25
     *  TIME_OF_DAY#15:36:55.36              TOD#15:36:55.36
     *  time_of_day#15:36:55.36              tod#15:36:55.36
     *  DATE_AND_TIME#1984-06-25-15:36:55.36 DT#1984-06-25-15:36:55.36
     *  date_and_time#1984-06-25-15:36:55.36 dt#1984-06-25-15:36:55.36
     *
     */
    /* Quick hack : only transform seconds */
    /* search the dot */
    l = IN.len;
    while(--l > 0 && IN.body[l] != '.');
    if(l != 0){
        LREAL IN_val = atof((const char *)&IN.body);
        return  (TIME){(long)IN_val, (long)(IN_val - (LINT)IN_val)*1000000000};
    }else{
        return  (TIME){(long)__pstring_to_sint(&IN), 0};
    }
}

    /***************/
    /*  FROM_TIME  */
    /***************/
static inline LREAL __time_to_real(TIME IN){
    return (LREAL)IN.tv_sec + ((LREAL)IN.tv_nsec/1000000000);
}
static inline LINT __time_to_int(TIME IN) {return IN.tv_sec;}
static inline STRING __time_to_string(TIME IN){
    STRING res;
    div_t days;
    /*t#5d14h12m18s3.5ms*/
    res = __INIT_STRING;
    days = div(IN.tv_sec, SECONDS_PER_DAY);
    if(!days.rem && IN.tv_nsec == 0){
        res.len = snprintf((char*)&res.body, STR_MAX_LEN, "T#%dd", days.quot);
    }else{
        div_t hours = div(days.rem, SECONDS_PER_HOUR);
        if(!hours.rem && IN.tv_nsec == 0){
            res.len = snprintf((char*)&res.body, STR_MAX_LEN, "T#%dd%dh", days.quot, hours.quot);
        }else{
            div_t minuts = div(hours.rem, SECONDS_PER_MINUTE);
            if(!minuts.rem && IN.tv_nsec == 0){
                res.len = snprintf((char*)&res.body, STR_MAX_LEN, "T#%dd%dh%dm", days.quot, hours.quot, minuts.quot);
            }else{
                if(IN.tv_nsec == 0){
                    res.len = snprintf((char*)&res.body, STR_MAX_LEN, "T#%dd%dh%dm%ds", days.quot, hours.quot, minuts.quot, minuts.rem);
                }else{
                    res.len = snprintf((char*)&res.body, STR_MAX_LEN, "T#%dd%dh%dm%ds%gms", days.quot, hours.quot, minuts.quot, minuts.rem, (LREAL)IN.tv_nsec / 1000000);
                }
            }
        }
    }
    if(res.len > STR_MAX_LEN) res.len = STR_MAX_LEN;
    return res;
}
static inline STRING __date_to_string(DATE IN){
    STRING res;
    tm broken_down_time;
    /* D#1984-06-25 */
    broken_down_time = convert_seconds_to_date_and_time(IN.tv_sec);
    res = __INIT_STRING;
    res.len = snprintf((char*)&res.body, STR_MAX_LEN, "D#%d-%2.2d-%2.2d",
             broken_down_time.tm_year,
             broken_down_time.tm_mon,
             broken_down_time.tm_day);
    if(res.len > STR_MAX_LEN) res.len = STR_MAX_LEN;
    return res;
}
static inline STRING __tod_to_string(TOD IN){
    STRING res;
    tm broken_down_time;
    time_t seconds;
    /* TOD#15:36:55.36 */
    seconds = IN.tv_sec;
    if (seconds >= SECONDS_PER_DAY){
		__iec_error();
		return (STRING){9,"TOD#ERROR"};
	}
    broken_down_time = convert_seconds_to_date_and_time(seconds);
    res = __INIT_STRING;
    if(IN.tv_nsec == 0){
        res.len = snprintf((char*)&res.body, STR_MAX_LEN, "TOD#%2.2d:%2.2d:%2.2d",
                 broken_down_time.tm_hour,
                 broken_down_time.tm_min,
                 broken_down_time.tm_sec);
    }else{
        res.len = snprintf((char*)&res.body, STR_MAX_LEN, "TOD#%2.2d:%2.2d:%09.6f",
                 broken_down_time.tm_hour,
                 broken_down_time.tm_min,
                 (LREAL)broken_down_time.tm_sec + (LREAL)IN.tv_nsec / 1e9);
    }
    if(res.len > STR_MAX_LEN) res.len = STR_MAX_LEN;
    return res;
}
static inline STRING __dt_to_string(DT IN){
    STRING res;
    tm broken_down_time;
    /* DT#1984-06-25-15:36:55.36 */
    broken_down_time = convert_seconds_to_date_and_time(IN.tv_sec);
    if(IN.tv_nsec == 0){
        res.len = snprintf((char*)&res.body, STR_MAX_LEN, "DT#%d-%2.2d-%2.2d-%2.2d:%2.2d:%2.2d",
                 broken_down_time.tm_year,
                 broken_down_time.tm_mon,
                 broken_down_time.tm_day,
                 broken_down_time.tm_hour,
                 broken_down_time.tm_min,
                 broken_down_time.tm_sec);
    }else{
        res.len = snprintf((char*)&res.body, STR_MAX_LEN, "DT#%d-%2.2d-%2.2d-%2.2d:%2.2d:%09.6f",
                 broken_down_time.tm_year,
                 broken_down_time.tm_mon,
                 broken_down_time.tm_day,
                 broken_down_time.tm_hour,
                 broken_down_time.tm_min,
                 (LREAL)broken_down_time.tm_sec + ((LREAL)IN.tv_nsec / 1e9));
    }
    if(res.len > STR_MAX_LEN) res.len = STR_MAX_LEN;
    return res;
}

    /**********************************************/
    /*  [ANY_DATE | TIME] _TO_ [ANY_DATE | TIME]  */
    /**********************************************/

static inline TOD __date_and_time_to_time_of_day(DT IN) {
	return (TOD){
		IN.tv_sec % SECONDS_PER_DAY + (IN.tv_sec < 0 ? SECONDS_PER_DAY : 0),
		IN.tv_nsec};
}
static inline DATE __date_and_time_to_date(DT IN){
	return (DATE){
		IN.tv_sec - IN.tv_sec % SECONDS_PER_DAY - (IN.tv_sec < 0 ? SECONDS_PER_DAY : 0),
		0};
}

    /*****************/
    /*  FROM/TO BCD  */
    /*****************/

static inline BOOL __test_bcd(LWORD IN) {
	while (IN) {
		if ((IN & 0xf) > 9) return 1;
		IN >>= 4;
	}
	return 0;
}

static inline ULINT __bcd_to_uint(LWORD IN){
    ULINT res = IN & 0xf;
    ULINT factor = 10ULL;

    while (IN >>= 4) {
        res += (IN & 0xf) * factor;
        factor *= 10;
    }
    return res;
}

static inline LWORD __uint_to_bcd(ULINT IN){
    LWORD res = IN % 10;
    USINT shift = 4;

    while (IN /= 10) {
        res |= (IN % 10) << shift;
        shift += 4;
    }
    return res;
}


    /************/
    /*  MOVE_*  */
    /************/

/* some helpful __move_[ANY] functions, used in the *_TO_** and MOVE  standard functions */
/* e.g. __move_BOOL, __move_BYTE, __move_REAL, __move_TIME, ... */
#define __move_(TYPENAME)\
static inline TYPENAME __move_##TYPENAME(TYPENAME op1) {return op1;}
__ANY(__move_)







#include "iec_std_functions.h"

#ifdef  DISABLE_EN_ENO_PARAMETERS
  #include "iec_std_FB_no_ENENO.h"
#else
  #include "iec_std_FB.h"
#endif

#endif /* _IEC_STD_LIB_H */