lib/C/iec_std_lib.h
author Edouard Tisserant <edouard.tisserant@gmail.com>
Fri, 05 Apr 2024 13:23:53 +0200
changeset 1103 f3c6c938d5ff
parent 1070 16dc09ee42d8
child 1104 0bc1f5a4f975
permissions -rwxr-xr-x
merge
/*
 * 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 */