diff --git a/App/tools/driver.makefile b/App/tools/driver.makefile
index e29ae1606f1b411a5d352f46516d399b39ef0987..39f8da7abef372af4d916c8f7eb24fe49da16757 100644
--- a/App/tools/driver.makefile
+++ b/App/tools/driver.makefile
@@ -115,6 +115,8 @@ TEMPLATES=
 SOURCES=
 DBDS=
 HEADERS=
+BASH_ENV=
+ENV=
 
 # Default target is "build" for all versions.
 # Don't install anything (different from default EPICS make rules).
@@ -331,9 +333,13 @@ ${CONFIG}/CONFIG:
 EB=${EPICS_BASE}
 TOP:=${EPICS_BASE}
 -include ${CONFIG}/CONFIG
+BASE_CPPFLAGS=
 EPICS_BASE:=${EB}
-SHRLIB_VERSION=
 COMMON_DIR = O.${EPICSVERSION}_Common
+ifndef LEGACY_RSET
+USR_CPPFLAGS+=-DUSE_TYPED_RSET
+endif
+SHRLIB_VERSION=
 # do not link *everything* with readline (and curses)
 COMMANDLINE_LIBRARY =
 # Relax (3.13) cross compilers (default is STRICT) to allow sloppier syntax.
diff --git a/GNUmakefile b/GNUmakefile
index d2388a219e0d0973c47d69ad64fe4465af84c8e9..d56841bd83d13cb70f425480c5009718dfc2ca12 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -10,6 +10,8 @@ SOURCES += require.c
 DBDS    += require.dbd
 SOURCES += runScript.c
 DBDS    += runScript.dbd
+SOURCES += expr.c
+#DBDS    += expr.dbd
 
 SOURCES += dbLoadTemplate.y
 DBDS    += dbLoadTemplate.dbd
@@ -23,7 +25,7 @@ HEADERS += require.h
 USR_INCLUDES_Linux=-idirafter ${EPICS_BASE}/include 
 
 # Pass T_A to the code
-USR_CFLAGS += -DT_A=${T_A}
+USR_CFLAGS += -DT_A='"${T_A}"'
 
 # This should really go into some global WIN32 config file
 USR_CFLAGS_WIN32 += /D_WIN32_WINNT=0x501
diff --git a/Makefile b/Makefile
index 4d5a232ede3dbdc83dbd2e899009197aacd2e07f..2f85c3b20e84a1b3d5a5a9b6968c2885eae64037 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,9 @@ requireSup_DBD += require.dbd
 LIB_SRCS += runScript.c
 requireSup_DBD += runScript.dbd
 
+LIB_SRCS += expr.c
+#requireSup_DBD += expr.dbd
+
 LIB_SRCS += dbLoadTemplate.y
 requireSup_DBD += dbLoadTemplate.dbd
 
@@ -25,7 +28,7 @@ LIB_LIBS += $(EPICS_BASE_IOC_LIBS)
 USR_INCLUDES_Linux=-idirafter ${EPICS_BASE}/include 
 
 # Pass T_A to the code
-USR_CFLAGS += -DT_A=${T_A}
+USR_CFLAGS += -DT_A='"${T_A}"'
 
 # This should really go into some global WIN32 config file
 USR_CFLAGS_WIN32 += /D_WIN32_WINNT=0x501
diff --git a/dbLoadTemplate.y b/dbLoadTemplate.y
index 4b216bc35cde0cab3ca7fc477a7b52a603e3d9c3..648a01096ced1b156c4ab122b9cd2313af1dc391 100644
--- a/dbLoadTemplate.y
+++ b/dbLoadTemplate.y
@@ -17,7 +17,10 @@
 #include <stddef.h>
 #include <string.h>
 #include <errno.h>
+
+#if !defined (_WIN32)
 #include <unistd.h>
+#endif
 
 #include "macLib.h"
 #include "dbmf.h"
diff --git a/expr.c b/expr.c
new file mode 100644
index 0000000000000000000000000000000000000000..15738d60e1af98cb521979074d559c618db9aaac
--- /dev/null
+++ b/expr.c
@@ -0,0 +1,280 @@
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "expr.h"
+
+int exprDebug;
+
+static int parseSubExpr(const char** pp, long* v, int pr, int op);
+#define parseExpr(pp,v) parseSubExpr(pp, v, 0, 0)
+
+static int parseValue(const char** pp, long* v)
+{
+    long val;
+    const char *p = *pp;
+    char o;
+
+    /* A value is optionally prefixed with an unary operator + - ! ~.
+     * It is either a number (decimal, octal or hex)
+     * or an expression in ().
+     * Allowed chars after a number: operators, closing parenthesis, whitespace, quotes, end of string
+     */
+
+    /* first look for value */
+    while (isspace((unsigned char)*p)) p++;
+    o = *p;
+    if (memchr("+-~!", o, 4))
+    {
+        /* handle unary operators */
+        p++;
+        if (!parseValue(&p, &val)) return 0; /* no valid value */
+        if (o == '-') val=-val;
+        else if (o == '~') val=~val;
+        else if (o == '!') val=!val;
+    }
+    else if (o == '(')
+    {
+        /*  handle sub-expression */
+        p++;
+        if (parseExpr(&p, &val) < 0) return 0; /* no valid expression */
+        while (isspace((unsigned char)*p)) p++;
+        if (*p++ != ')') return 0; /* missing ) */
+    }
+    else
+    {
+        /* get number */
+        char* e;
+        val = strtol(p, &e, 0);
+        if (e == p) return 0; /* no number */
+        
+        if (isalpha((unsigned char)*p))
+        {
+            /* followed by rubbish */
+            return 0; 
+        }
+        p = e;
+    }
+    *pp = p;
+    *v = val;
+    return 1;
+}
+
+static long ipow(long base, long exp)
+{
+    long v;
+    if (exp < 0) return 0;
+    if (exp == 0) return 1;
+    v = base;
+    while (--exp) v *= base; /* optimize this! */
+    return v;
+}
+
+struct {char str[4]; int pr;} ops[] = {
+    {"",0},
+    {"**",11},
+    {"*", 10},{"/",10},{"%",10},
+    {"+",9},{"-",9},
+    {"<<",8},{">>>",8},{">>",8},
+    {"<=>",7},{"<=",7},{">=",7},{"<",7},{">",7},
+    {"==",6},{"!=",6},
+    {"&&",5},{"||",5},
+    {"&",4},{"^",3},{"|",2},
+    {"?:",1},{"?",1},{":",0}
+};
+
+static int startsWith(const char* p, const char* s)
+{
+    int i = 0;
+    while (*s) { i++; if (*p++ != *s++) return 0; }
+    return i;
+}
+
+static int parseOp(const char** pp)
+{
+    const char *p = *pp;
+    int o, l;
+
+    while (isspace((unsigned char)*p)) p++;
+    if (ispunct((unsigned char)*p))
+    {
+        for (o = 1; o < (int)(sizeof(ops)/sizeof(ops[0])); o++)
+        {
+            if ((l = startsWith(p, ops[o].str)))
+            {
+                /* operator found */
+                *pp = p+l;
+                return o;
+            }
+        }
+    }
+    return 0;
+}
+
+static int parseSubExpr(const char** pp, long* v, int pr, int o)
+{
+    const char *p = *pp;
+    long val = o ? *v : 0;
+    long val2;
+    int o2 = o;
+
+    if (exprDebug) printf("parseExpr(%d): start %ld %s %s\n", pr, val, ops[o].str, p);
+    do {
+        if (!parseValue(&p, &val2))
+        {
+            if (exprDebug) printf("parseExpr(%d): no value after %ld %s\n", pr, val, ops[o].str);
+            return -1;
+        }
+        if ((o2 = parseOp(&p)))
+        {
+            if (exprDebug) printf("parseExpr(%d): %ld %s %ld %s %s\n", pr, val, ops[o].str, val2, ops[o2].str, p);
+            if (o && ops[o2].pr > ops[o].pr)
+            {
+                if ((o2 = parseSubExpr(&p, &val2, ops[o].pr, o2)) < 0)
+                {
+                    if (exprDebug) printf("parseExpr(%d): parse failed after %ld %s %ld\n", pr, val, ops[o].str, val2);
+                    return -1;
+                }
+            }
+        }
+        if (exprDebug) printf("parseExpr(%d): %ld %s %ld", pr, val, ops[o].str, val2);
+        switch (o)
+        {
+            case  0: val = val2; break;
+            case  1: val = ipow(val, val2); break;
+            case  2: val *= val2; break;
+            case  3: val /= val2; break;
+            case  4: val %= val2; break;
+            case  5: val += val2; break;
+            case  6: val -= val2; break;
+            case  7: val <<= val2; break;
+            case  8: val = (unsigned long)val >> val2; break;
+            case  9: val >>= val2; break;
+            case 10: val = val < val2 ? -1 : val == val2 ? 0 : 1; break;
+            case 11: val = val <= val2; break;
+            case 12: val = val >= val2; break;
+            case 13: val = val < val2; break;
+            case 14: val = val > val2; break;
+            case 15: val = val == val2; break;
+            case 16: val = val != val2; break;
+            case 17: val = val && val2; break;
+            case 18: val = val || val2; break;
+            case 19: val &= val2; break;
+            case 20: val ^= val2; break;
+            case 21: val |= val2; break;
+            case 22: if (!val) val = val2; break;
+        }
+        if (exprDebug) printf(" = %ld\n", val);
+        if (o2 == 23) /* ? ... : ... */
+        {
+            long val3 = 0;
+            val2 = 1;
+            if (exprDebug) printf("parseExpr(%d) if %ld ...\n", pr, val);
+            if ((o2 = parseSubExpr(&p, &val2, 1, 0)) == 24)
+                o2 = parseSubExpr(&p, &val3, 1, 0);
+            else o2 = 0;
+            if (exprDebug) printf("parseExpr(%d) if %ld then %ld else %ld\n", pr, val, val2, val3);
+            val = val ? val2 : val3;
+        }
+        o = o2;
+    } while (ops[o].pr && pr <= ops[o].pr);
+    if (exprDebug) printf("parseExpr(%d): value = %ld return %d %s\n", pr, val, o, ops[o].str);
+    *pp = p;
+    *v = val;
+    return o;
+}
+
+const char* getFormat(const char** pp)
+{
+    static char format [20];
+    const char* p = *pp;
+    unsigned int i = 1;
+    if (exprDebug) printf("getFormat %s\n", p);
+    if ((format[0] = *p++) == '%')
+    {
+        while (i < sizeof(format) && memchr(" #-+0", *p, 5))
+            format[i++] = *p++;
+        while (i < sizeof(format) && *p >= '0' && *p <= '9')
+            format[i++] = *p++;
+        if (i < sizeof(format))
+            format[i++] = 'l';
+        if (i < sizeof(format) && memchr("diouxXc", *p, 7))
+        {
+            format[i++] = *p++;
+            format[i] = 0;
+            *pp = p;
+            if (exprDebug) printf("format = '%s'\n", format);
+            return format;
+        }
+    }
+    if (exprDebug) printf("no format\n");
+    return NULL;
+}
+
+size_t replaceExpressions(const char* r, char* buffer, size_t buffersize)
+{
+    long val;
+    char* w = buffer;
+    char* s;
+
+    while (*r)
+    {
+        s = w;
+        if (*r == '"' || *r == '\'')
+        {
+            /* quoted strings */
+            char c = *w++ = *r++;
+            while (*r && *r != c) {
+                if (*r == '\\' && !(*w++ = *r++)) break;
+                *w++ = *r++;
+            }
+            if (*r) *w++ = *r++;
+            *w = 0;
+            if (exprDebug) printf("quoted string %s\n", s);
+        }
+        else if (*r == '%')
+        {
+            /* formatted expression */
+            const char* r2 = r;
+            const char* f;
+            if (exprDebug) printf("formatted expression after '%s'\n", s);
+            if ((f = getFormat(&r2)) && parseExpr(&r2, &val) == 0)
+            {
+                r = r2;
+                if (w > buffer && w[-1] == '(' && *r2++ == ')')
+                {
+                    w--;
+                    r = r2;
+                }
+                w += sprintf(w, f , val);
+                if (exprDebug) printf("formatted expression %s\n", s);
+            }
+        }
+        else if (parseExpr(&r, &val) == 0)
+        {
+            /* unformatted expression */
+            w += sprintf(w, "%ld", val);
+            *w = 0;
+            if (exprDebug) printf("simple expression %s\n", s);
+        }
+        else if (*r == ',')
+        {
+            /* single comma */
+            *w++ = *r++;
+        }
+        else {
+            /* unquoted string (i.e plain word) */
+            do {
+                *w++ = *r++;
+            } while (*r && !strchr("%(\"', \t\n", *r));
+            *w = 0;
+            if (exprDebug) printf("plain word '%s'\n", s);
+        }
+        /* copy space */
+        while (isspace((unsigned char)*r)) *w++ = *r++;
+        /* terminate */
+        *w = 0;
+    }
+    return w - buffer;
+}
diff --git a/expr.h b/expr.h
new file mode 100644
index 0000000000000000000000000000000000000000..d3ee4edaf5126b50accd9ed5a144625506589191
--- /dev/null
+++ b/expr.h
@@ -0,0 +1,21 @@
+#ifndef expr_h
+#define expr_h
+
+#ifdef __cplusplus
+extern {
+#endif
+
+extern int exprDebug;
+
+size_t replaceExpressions(const char* source, char* buffer, size_t buffersize);
+/* Resolve integer expressions that are either free standing
+ * or in parentheses () embedded in an unquoted word.
+ * Do not resolve expressions in single or double quoted strings.
+ * An expression optionally starts with a integer format such as %x.
+ * It consists of integer numbers, operators and parentheses ().
+ */
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/iocsh b/iocsh
index bde7ec7809031e41fcfd5abba27100e1d4d64792..0e7f982f0a3c53dd6db0a845ffe70389ec4a0a64 100755
--- a/iocsh
+++ b/iocsh
@@ -2,30 +2,39 @@
 
 help () {
     {
-    echo "usage: iocsh [options] [files]"
+    echo "Usage: iocsh [options] [files] [macro=value] ..."
     echo "Start an EPICS iocsh and load files"
-    echo "Recognized filetypes: *.db *.dbt *.template *.subs *.subst *.dbd *.so"
     echo
-    echo "Possible options:"
-    echo " -3.xx.yy: Set EPICS base version."
-    echo " -32: Force 32 bit version (on 64 bit systems)."
-    echo " -? or -h or --help: Show this page and exit."
-    echo " -v or --version: Show version and exit."
-    echo " -c: The next string is executed as a command by the EPICS shell."
-    echo " -s: The next string is a sequencer program (and arguments), run with 'seq'."
-    echo "     This forces an 'iocInit' before running the program."
-    echo " -r: The next string is a module (and version), loaded via 'require'."
-    echo " -n: The next string is the IOC name (used for prompt)."
-    echo "     Default: dirname if parent dir is \"ioc\" otherwise hostname."
+    echo "Options:"
+    echo " -?, -h, --help  Show this page and exit."
+    echo " -v, --version   Show version and exit."
+    echo " -32             Force 32 bit version (on 64 bit systems)."
+    echo " -x[.z[.y]]      Select EPICS base version x.z.y (e.g. 3.14.8, 3.15, 7)."
+    echo " -d, --debug     Run IOC with gdb."
+    echo " -dv             Run IOC with valgrind."
+    echo " -dp             Run IOC with perf record."
+    echo " -c 'cmd args'   Ioc shell command."
+    echo " -s 'prog m=v'   Sequencer program (and arguments), run with 'seq'."
+    echo "                 This forces an 'iocInit' before running the program."
+    echo " -r module[,ver] Modue (optionally with version) loaded via 'require'."
+    echo " -n name         Name of the IOC, used for prompt and \${IOC} variable."
+    echo "                 Default: dirname if parent dir is \"ioc\" otherwise hostname."
+    echo " @file           More arguments are read from file."
     echo
     echo "Supported filetypes:"
-    echo "*.db, *.dbt and *.template are loaded via 'dbLoadRecords'."
-    echo "  After the filename, you can specify substitutions like MACRO=value."
-    echo "*.subs and *.subst are loaded via 'dbLoadTemplate'."
-    echo "*.dbd is loaded via 'dbLoadDatabase'."
-    echo "*.so is loaded via 'ld' or 'dlload' (3.14.12 or higer)."
-    echo "If an argument is @file, more arguments are read from that file."
+    echo " *.db, *.dbt, *.template  loaded via 'dbLoadRecords'"
+    echo " *.subs, *.subst          loaded via 'dbLoadTemplate'"
+    echo " *.dbd                    loaded via 'dbLoadDatabase'"
+    echo " *.so                     loaded via 'dlload' (or 'ld' before 3.14.12)"
     echo "All other files are executed as startup scripts by the EPICS shell."
+    echo "After a file you can specify substitutions like m1=v1 m2=v1 for that file."
+    echo
+    echo "Examples:"
+    echo "  iocsh st.cmd"
+    echo "  iocsh my_database.template P=XY M=3"
+    echo "  iocsh -r my_module,version -c 'initModule()'"
+    echo "  iocsh -3.15.4 -dp st.cmd"
+    echo "  iocsh -c 'var requireDebug 1' st.cmd"
     } >&2
     exit
 }
@@ -37,20 +46,6 @@ version () {
     exit
 }
 
-case $1 in
-    ( -h | "-?" | -help | --help )
-        help
-        ;;
-    ( -v | -ver | --ver | -version | --version )
-        version
-        ;;
-    ( -3.* )
-        unset EPICS_BASE
-        BASE=${1#-}
-        shift
-    ;;
-esac
-
 # realpath and readlink are not available on all systems, let's try what works...
 rp() {
   ( realpath $1 || readlink -f $1 || readlink $1 || (cd -P $1 && echo $PWD) || (x=$(\ls -ld $1) && echo ${x##* }) || echo $1 ) 2>/dev/null
@@ -59,23 +54,31 @@ rp() {
 # if EPICS_HOST_ARCH is not set guess it
 if [ -z "$EPICS_HOST_ARCH" ]
 then
-    echo "EPICS_HOST_ARCH is not set"
     EPICS_HOST_ARCH=$(basename $(dirname $(rp $(which caRepeater))))
     if [ -n "$EPICS_HOST_ARCH" ]
     then
-        echo "Guessing EPICS_HOST_ARCH=$EPICS_HOST_ARCH"
+        echo "Guessing EPICS_HOST_ARCH=$EPICS_HOST_ARCH" >&2
     else
+        echo "EPICS_HOST_ARCH is not set" >&2
         exit 1
     fi
 fi
 
-# check if user requested 32 bit version on a 64 bit system
-case $1 in
-    ( -32 )
-        EPICS_HOST_ARCH=${EPICS_HOST_ARCH%_64}
-        shift
-    ;;
-esac
+while true
+do
+    case $1 in
+        ( -32 )
+            EPICS_HOST_ARCH=${EPICS_HOST_ARCH%_64}
+        ;;
+        ( -[1-9]* )
+            unset EPICS_BASE
+            BASE=${1#-}
+        ;;
+        ( * ) break
+        ;;
+    esac
+    shift
+done
 
 # Either EPICS or EPICS_BASE should be set to the install directory
 if [ -z "$EPICS_BASE" ]
@@ -87,25 +90,44 @@ then
         do
             if [ -d $EPICS ]
             then
-                break 2
+                break
             fi
-            echo "Cannot find any EPICS installation directory." >&2
-            echo "Try setting EPICS_BASE environment variable to full path" >&2
-            exit 1
         done
+        if [ ! -d "$EPICS" ]
+        then
+            EPICS=$(dirname $(dirname $(dirname $(dirname $(ldd $(which caRepeater) | awk '/libca/ {print $3}')))))
+            echo "Guessing EPICS=$EPICS"
+        fi
+        if [ ! -d "$EPICS" ]
+        then
+            echo "Cannot find EPICS installation directory." >&2
+            echo "Try setting EPICS environment variable." >&2
+            exit 1
+        fi
+    fi
+    if [ -z "$BASE" ]
+    then
+        EPICS_BASE=$(\ls -1vrd $EPICS/base/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null | head -n1)
+    else
+        # find highest (requested) EPICS version that supports our architecture (or its 32 bit version)
+        EPICS_BASE=$(\ls -1vrd $EPICS/base-$BASE*/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null | head -n1)
     fi
-    # find highest (requested) EPICS version that supports our architecture (or its 32 bit version)
-    EPICS_BASE=$(\ls -1vrd $EPICS/base*$BASE*/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null | head -n1)
     if [ -z $EPICS_BASE ]
     then
-        echo Cannot find any $BASE EPICS version for $EPICS_HOST_ARCH. >&2
+        if [ -z "$(\ls -1vrd $EPICS/base-$BASE*/ 2>/dev/null)" ]
+        then
+            echo "No EPICS $BASE installed." >&2
+            exit 1
+        fi
+        echo EPICS $BASE not available for EPICS_HOST_ARCH=$EPICS_HOST_ARCH. >&2
         exit 1
     fi
     # maybe we need to change from 64 bit to 32 bit
     if [ $EPICS_HOST_ARCH != ${EPICS_BASE#*/bin/} ]
     then
         EPICS_HOST_ARCH=${EPICS_BASE#*/bin/}
-        echo "No 64 bit version in ${EPICS_BASE%bin*}. Switch to 32 bit version $EPICS_HOST_ARCH"
+        echo "No 64 bit version in ${EPICS_BASE%bin*}." >&2
+        echo "Switching to 32 bit version $EPICS_HOST_ARCH." >&2
     fi
     EPICS_BASE=$(rp ${EPICS_BASE%bin*})
 fi
@@ -116,28 +138,13 @@ then
     exit 1
 fi
 
-# Check revision
-if [ -r $EPICS_BASE/configure/CONFIG_BASE_VERSION ]
-then
-BASE=$(awk -F '[ \t]*=[ \t]*' '
+# Get actual EPICS revision
+eval $(awk -F '[ \t]*=[ \t]*' '
     /^[ \t]*EPICS_VERSION[ \t]*=/ {v=$2}
     /^[ \t]*EPICS_REVISION[ \t]*=/ {r=$2}
-    /^[ \t]*EPICS_MODIFICATION[ \t]*=/ {m=$2}
-    END {print v"."r"."m}' < $EPICS_BASE/configure/CONFIG_BASE_VERSION)
-else
-BASE=$(basename $(rp $EPICS_BASE))
-BASE=${BASE#*base-}
-fi
-if [ "${BASE#3.}" = "$BASE" ]
-then
-    echo "Cannot find any EPICS base version" >&2
-    echo "Try setting EPICS_BASE environment variable to full path" >&2
-    exit 1
-fi
-export BASE
-BASEMINOR=${BASE#3.}
-BASEPATCH=${BASEMINOR#*.}
-BASEMINOR=${BASEMINOR%.*}
+    /^[ \t]*EPICS_MODIFICATION[ \t]*=/ {m=$2+0}
+    END {print "BASE="v"."r"."m";BASECODE="v*10000+r*100+m}
+' < $EPICS_BASE/configure/CONFIG_BASE_VERSION)
 
 # IOC name derives from hostname
 # (trailing possible '\r' under cygwin)
@@ -154,7 +161,7 @@ export IOC
 # Check for 64 bit versions, default to 32 bit
 if [ ! -d $EPICS_BASE/lib/${EPICS_HOST_ARCH} -a -d $EPICS_BASE/lib/${EPICS_HOST_ARCH%_64} ]
 then
-    echo "No 64 bit EPICS installation found. Defaulting to 32 bit"
+    echo "No 64 bit EPICS installation found. Defaulting to 32 bit" >&2
     EPICS_HOST_ARCH=${EPICS_HOST_ARCH%_64}
 fi
 export EPICS_HOST_ARCH
@@ -186,27 +193,19 @@ then
 fi
 export EPICS_DRIVER_PATH
 
-subst () {
-    subst=""
-    while [ "$#" -gt 1 ]
-    do
-        case $2 in 
-            ( *=* )
-                subst="$subst,$2"; shift
-                ;;
-            ( * )
-                break
-                ;;
-        esac
-    done
-    echo ${subst#,}
-}
-
 loadFiles () {
 while [ "$#" -gt 0 ]
 do
   file=$1
   case $file in
+    ( -32 )
+        echo "-32 option must be set earlier" >&2
+        exit 1
+        ;;
+    ( -[1-9]* )
+        echo "EPICS version $file option must be set earlier" >&2
+        exit 1
+        ;;
     ( -h | "-?" | -help | --help )
         help
         ;;
@@ -216,35 +215,14 @@ do
     ( @* )              
         loadFiles $(cat ${file#@})
         ;;
-    ( *.db | *.template)
-        subst=""
-        while [ "$#" -gt 1 ]
-        do
-            case $2 in 
-                ( *=* )
-                    subst="$subst,$2"; shift
-                    ;;
-                ( * )
-                    break
-                    ;;
-            esac
-        done
-        echo "dbLoadRecords \"$file\",\"${subst#,}\""
-        ;;
-    ( *.subs | *.subst )
-        echo "dbLoadTemplate \"$file\""
+    ( -d | -dg | --debug )
+        LOADER="gdb --eval-command run --args $LOADER"
         ;;
-    ( *.dbd )
-        # some dbd files must be loaded before main to take effect
-        echo "dbLoadDatabase \"$file\",\"$DBD\""
+    ( -dv )
+        LOADER="valgrind --leak-check=full $LOADER"
         ;;
-    ( *.so )             
-        if [ $BASEMINOR -ge 15 -o $BASEPATCH -ge 12 ]
-        then
-            echo "dlload \"$file\""
-        else
-            echo "ld \"$file\""
-        fi
+    ( -dp )
+        LOADER="perf record $LOADER"
         ;;
     ( -c )               
         shift
@@ -279,24 +257,58 @@ do
         shift
         IOC="$1"
         ;;
-    ( -3.* )
-        echo "Version $file must be first argument" >&2
-        exit 1
-        ;;
-    ( -32 )
-        echo "-32 option must come before all others (except -3.xx.yy)" >&2
-        exit 1
-        ;;
     ( -* )
-        {
-        echo "unknown option $1"
-        echo "try: $(basename $0) --help"
-        } >&2
+        echo "Unknown option $1" >&2
+        echo "Try: $(basename $0) --help" >&2
         exit 1
        ;;
-    ( * )                
-        echo "< \"$file\""
-        if grep -q iocInit $file; then init=NO; fi
+    ( *.so )
+        if [ $BASECODE -ge 31412 ]
+        then
+            echo "dlload \"$file\""
+        else
+            echo "ld \"$file\""
+        fi
+        ;;
+    ( *=* )
+        echo -n $file | awk -F '=' '{printf "epicsEnvSet %s '\''%s'\''\n", $1, $2}'
+        ;;
+    ( * )
+        subst=""
+        while [ "$#" -gt 1 ]
+        do
+            case $2 in
+                ( *=* )
+                    subst="$subst,$2"; shift
+                    ;;
+                ( * )
+                    break
+                    ;;
+            esac
+        done
+        subst=${subst#,}
+        case $file in
+            ( *.db | *.template)
+                echo "dbLoadRecords '$file','$subst'"
+                ;;
+            ( *.subs | *.subst )
+                echo "dbLoadTemplate '$file','$subst'"
+                ;;
+            ( *.dbd )
+                # some dbd files must be loaded before main to take effect
+                echo "dbLoadDatabase '$file','$DBD','$subst'"
+                ;;
+            ( * )
+                if [ $BASECODE -ge 31500 ]
+                then
+                    echo "iocshLoad '$file','$subst'"
+                else
+                    echo -n $subst | awk -F '=' -v 'RS=,' '{printf "epicsEnvSet %s '\''%s'\''\n", $1, $2}'
+                    echo "< '$file'"
+                fi
+                if grep -q iocInit $file; then init=NO; fi
+                ;;
+        esac
         ;;
   esac
   shift
@@ -306,12 +318,12 @@ done
 startup=/tmp/iocsh.startup.$$
 
 # clean up and kill the softIoc when killed by any signal
-trap "kill -s SIGTERM 0; stty sane; echo; rm -f $startup; " EXIT
+trap "kill -s SIGTERM 0; (stty sane && echo) 2>/dev/null; rm -f $startup; " EXIT
 
 {
 echo "# date=\"$(date)\""
 echo "# user=\"${USER:-$(whoami)}\""
-for var in PWD BASE EPICS_HOST_ARCH SHELLBOX EPICS_CA_ADDR_LIST EPICS_DRIVER_PATH
+for var in IOC PWD BASE EPICS_HOST_ARCH SHELLBOX EPICS_CA_ADDR_LIST EPICS_DRIVER_PATH
 do
     echo "# $var=\"${!var}\""
 done
@@ -329,14 +341,14 @@ else # old driver pool model
     REQUIRE_DBD=$INSTBASE/iocBoot/R$BASE/dbd/$REQUIRE.dbd
 fi
 
-if [ $BASEMINOR -ge 15 -o $BASEPATCH -ge 12 ]
+if [ $BASECODE -ge 31412 ]
 then
     EXE=$EPICS_BASE/bin/$EPICS_HOST_ARCH/softIoc
     ARGS="-D $EPICS_BASE/dbd/softIoc.dbd"
     LDCMD="dlload"
 else
     # get rid of the compiled-in rpath because at PSI that is a link pointing to current EPICS version.
-    LOADER=/lib/ld-linux.so.2
+    LOADER="$LOADER /lib/ld-linux.so.2"
     LOADERARGS="--library-path $EPICS_BASE/lib/$EPICS_HOST_ARCH --inhibit-rpath ''"
     APP=ioc
     EXE=$EPICS_EXTENSIONS/bin/$EPICS_HOST_ARCH/$APP
@@ -346,6 +358,12 @@ else
     echo "${APP}_registerRecordDeviceDriver(pdbbase)"
 fi
 
+if [ ! -x $EXE ]
+then
+    echo "$EXE not found or not executable." >&2
+    exit 1
+fi
+
 if [ ! -f "$REQUIRE_LIB" ]
 then
     echo "Library $REQUIRE_LIB not found." >&2
@@ -388,5 +406,6 @@ then
 fi
 
 echo $EXE $ARGS $startup
+#enable core dumps
 ulimit -c unlimited
 eval "$LOADER $LOADERARGS $EXE" $ARGS "$startup" 2>&1
diff --git a/require.c b/require.c
index 840c5e6ac930d0d928d51e0801204b0bb9680728..b19f91be4c1269c474bb042475419041b684808c 100644
--- a/require.c
+++ b/require.c
@@ -70,6 +70,7 @@ int requireDebug;
     #include <loadLib.h>
     #include <shellLib.h>
     #include <ioLib.h>
+    #include <fioLib.h>
     #include <envLib.h>
     #include <epicsAssert.h>
     #include "strdup.h"
@@ -83,19 +84,54 @@ int requireDebug;
     #define getAddress(module, name) __extension__ \
         ({SYM_TYPE t; char* a = NULL; symFindByName(sysSymTbl, (name), &a, &t); a;})
 
-    /* vxWorks has no snprintf() */
-    #define snprintf(s, maxchars, f, args...) __extension__ \
-        ({int printed=sprintf(s, f, ## args); assert(printed < maxchars); printed;})
+#ifndef _WRS_VXWORKS_MAJOR
+    /* vxWorks 5 has no snprintf() */
+    struct outStr_s {
+        char *str;
+        int free;
+    };
+
+    static STATUS outRoutine(char *buffer, int nchars, int outarg) {
+        struct outStr_s *poutStr = (struct outStr_s *) outarg;
+        int free = poutStr->free;
+
+        if (free < 1) { /*let fioFormatV continue to count length*/
+            return OK;
+        } else if (free > 1) {
+            if (nchars >= free) nchars = free-1;
+            strncpy(poutStr->str, buffer, nchars);
+            poutStr->str += nchars;
+            poutStr->free -= nchars;
+        }
+        /*make sure final string is null terminated*/
+        *poutStr->str = 0;
+        return OK;
+    }
+
+    static int snprintf(char *str, size_t size, const char *format, ...)
+    {
+        struct outStr_s outStr;
+        int nchars;
+        va_list ap;
+
+        outStr.str = str;
+        outStr.free = size;
+        va_start(ap, format);
+        nchars = fioFormatV(format, ap, outRoutine, (int)&outStr);
+        va_end(ap);
+        return nchars;
+    }
+#endif
 
     /* vxWorks has no realpath() -- at least make directory absolute */
     static char* realpath(const char* path, char* buf)
     {
         size_t len = 0;
-        if (!buf) buf = malloc(MAX_FILENAME_LENGTH);
+        if (!buf) buf = malloc(PATH_MAX+1);
         if (!buf) return NULL;
         if (path[0] != OSI_PATH_SEPARATOR[0])
         {
-            getcwd(buf, MAX_FILENAME_LENGTH);
+            getcwd(buf, PATH_MAX);
             len = strlen(buf);
             if (len && buf[len-1] != OSI_PATH_SEPARATOR[0])
                 buf[len++] = OSI_PATH_SEPARATOR[0];
@@ -164,7 +200,7 @@ int requireDebug;
     #include "asprintf.h"
     #define snprintf _snprintf
     #define setenv(name,value,overwrite) _putenv_s(name,value)
-    #define NAME_MAX MAX_PATH
+    #define PATH_MAX MAX_PATH
 
     #define PREFIX
     #define INFIX
@@ -227,7 +263,17 @@ int requireDebug;
 #define LIBDIR "lib" OSI_PATH_SEPARATOR
 #define TEMPLATEDIR "db"
 
+      /* 
+#define TOSTR(s) TOSTR2(s)
+#define TOSTR2(s) #s
+const char epicsRelease[] = TOSTR(EPICS_VERSION)"."TOSTR(EPICS_REVISION)"."TOSTR(EPICS_MODIFICATION);
+const char epicsBasetype[] = TOSTR(EPICS_VERSION)"."TOSTR(EPICS_REVISION);
 
+#ifndef T_A
+#error T_A not defined: Compile with USR_CFLAGS += -DT_A='"${T_A}"'
+#endif
+const char targetArch[] = T_A;
+      */
 
 #ifndef OS_CLASS
 #error OS_CLASS not defined: Try to compile with USR_CFLAGS += -DOS_CLASS='"${OS_CLASS}"'
@@ -696,7 +742,7 @@ static int findLibRelease (
     char* p;
     char* version;
     char* symname;
-    char name[NAME_MAX + 11];                               /* get space for library path + "LibRelease" */
+    char name[PATH_MAX + 11];                               /* get space for library path + "LibRelease" */
     
     (void)data;                                             /* unused */
     if (size < sizeof(struct dl_phdr_info)) return 0;       /* wrong version of struct dl_phdr_info */
@@ -771,7 +817,6 @@ static void registerExternalModules()
     }
 }
 
-
 #else
 static void registerExternalModules()
 {
@@ -1240,23 +1285,21 @@ require_priv(const char* module,
 	     const char* versionstr  /* "-<version>" or "" (for old style only */
 	     )
 {
-  
   int status;
-  int ifexists = 0;
-  int releasediroffs;
-  int libdiroffs;
-  int extoffs;
-  
-  HMODULE libhandle;
-
   const char* loaded = NULL;
   const char* found = NULL;
+  HMODULE libhandle;
+  int ifexists = 0;
   const char* driverpath;
   const char* dirname;
   const char *end;
+  
+  int releasediroffs;
+  int libdiroffs;
+  int extoffs;
   char* founddir = NULL;
   char* symbolname;
-  char filename[NAME_MAX];
+  char filename[PATH_MAX];
     
   int someVersionFound = 0;
   int someArchFound = 0;
diff --git a/runScript.c b/runScript.c
index 6017663981bb990095568d9456bfdc890a5836f4..daf79fa343ed257ac9be183d4447db286bf374a7 100644
--- a/runScript.c
+++ b/runScript.c
@@ -47,110 +47,11 @@ epicsShareFunc int epicsShareAPI iocshCmd(const char *cmd);
 
 #define IS_ABS_PATH(filename) (filename[0] == OSI_PATH_SEPARATOR[0])  /* may be different for other OS ? */
 
+#include "expr.h"
 #include "require.h"
 
 int runScriptDebug=0;
 
-static int parseExpr(const char** pp, int* v);
-
-static int parseValue(const char** pp, int* v)
-{
-    int val;
-    const char *p = *pp;
-    int neg = 0;
-
-    while (isspace((unsigned char)*p)) p++;
-    if (*p == '+' || *p == '-') neg = *p++ == '-';
-    while (isspace((unsigned char)*p)) p++;
-    if (*p == '(')
-    {
-        p++;
-        if (!parseExpr(&p, &val)) return 0;
-        if (*p++ != ')') return 0;
-    }
-    else
-    {
-        char* e;
-        val = strtol(p, &e, 0);
-        if (e == p) return 0;
-        p = e;
-    }
-    if (neg) val = -val;
-    if (*p == '?')
-    {
-        p++;
-        val = (val != 0);
-    }
-    *pp = p;
-    *v = val;
-    if (runScriptDebug > 1) printf("parseValue: %d rest=\"%s\"\n", *v, p);
-    return 1;
-}
-
-static int parseExpr(const char** pp, int* v)
-{
-    const char *p = *pp;
-    const char *q;
-    int o;
-    int val;
-    int val2;
-    int status = 0;
-
-    *v = 0;
-    do {
-        if (!parseValue(&p, &val)) return status;
-        if (runScriptDebug > 1) printf("parseExp val=%d rest=%s\n", val, p);
-        q = p;
-        while (isspace((unsigned char)*q)) q++;
-        o = *q;
-        while (o == '*' || o == '/' || o == '%')
-        {
-            q++;
-            if (!parseValue(&q, &val2)) break;
-            if (o == '*') val *= val2;
-            else if (val2 == 0) val = 0;
-            else if (o == '/') val /= val2;
-            else val %= val2;
-            p = q;
-            while (isspace((unsigned char)*p)) p++;
-            o = *p;
-        }
-        status = 1;
-        *v += val;
-        *pp = p;
-        if (runScriptDebug > 1) printf("parseExpr: sum %d rest=\"%s\"\n", *v, p);
-    } while (o == '+' || o == '-');
-    return 1;
-}
-
-const char* getFormat(const char** pp)
-{
-    static char format [20];
-    const char* p = *pp;
-    unsigned int i = 1;
-    if (runScriptDebug > 1) printf ("getFormat %s\n", p);
-    if ((format[0] = *p++) == '%')
-    {
-        if (runScriptDebug > 1) printf ("getFormat0 %s\n", p);
-        while (i < sizeof(format) && strchr(" #-+0", *p))
-            format[i++] = *p++;
-        if (runScriptDebug > 1) printf ("getFormat1 %s\n", p);
-        while (i < sizeof(format) && strchr("0123456789", *p))
-            format[i++] = *p++;
-        if (runScriptDebug > 1) printf ("getFormat2 %s\n", p);
-        if (i < sizeof(format) && strchr("diouxXc", *p))
-        {
-            format[i++] = *p++;
-            format[i] = 0;
-            *pp = p;
-            if (runScriptDebug > 1) printf ("format=%s\n", format);
-            return format;
-        }
-    }
-    if (runScriptDebug > 1) printf ("no format\n");
-    return NULL;
-}
-
 int runScript(const char* filename, const char* args)
 {
     MAC_HANDLE *mac = NULL;
@@ -175,7 +76,7 @@ int runScript(const char* filename, const char* args)
 #endif
         char*[]){ "", "environ", NULL, NULL }) != 0) goto error;
     macSuppressWarning(mac, 1);
-#if (EPICSVER<31400)
+#if (EPICSVER<31403)
     /* Have no environment macro substitution, thus load envionment explicitly */
     /* Actually, environmant macro substitution was introduced in 3.14.3 */
     for (pairs = ppGlobalEnviron; *pairs; pairs++)
@@ -299,70 +200,10 @@ int runScript(const char* filename, const char* args)
         if (*p == 0 || *p == '#') continue;
         
         /* find local variable assignments */
-        if ((x = strpbrk(p, "=(, \t\n")) != NULL && *x=='=')
+        if ((x = strpbrk(p, "=(, \t\n\r")) != NULL && *x=='=')
         {
-            const char* r;
-            char* w;
-            int val;
-
             *x++ = 0;
-            r = x;
-            w = line_raw;
-            while (*r)
-            {
-                if (runScriptDebug > 1) printf ("expr %s\n", r);
-                if (*r == '%')
-                {
-                    const char* r2 = r;
-                    const char* f;
-                    if ((f = getFormat(&r2)) && parseExpr(&r2, &val))
-                    {
-                        w += sprintf(w, f , val);
-                        r = r2;
-                    }
-                    else
-                    {
-                        if (runScriptDebug > 1) printf ("skip %c\n", *r);
-                        *w++ = *r++;
-                    }
-                    continue;
-                }
-                if (parseExpr(&r, &val))
-                {
-                    if (runScriptDebug > 1) printf ("val=%d, rest=%s\n", val, r);
-                    w += sprintf(w, "%d", val);
-                    if (runScriptDebug > 1) printf ("rest=%s\n", r);
-                }
-                else if (*r == '(' || *r == '+')
-                {
-                    if (runScriptDebug > 1) printf ("skip %c\n", *r);
-                    *w++ = *r++;
-                    continue;
-                }
-                while (1)
-                {
-                    if ((*r >= '0' && *r <= '9') || *r == '(' || *r == '%') break;
-                    if (*r == '"' || *r == '\'')
-                    {
-                        char c = *r++;
-                        if (runScriptDebug > 1) printf ("string %c\n", c);
-                        while (*r && *r != c) {
-                            *w++ = *r++;
-                        }
-                        *w = 0;
-                        if (*r) r++;
-                        if (*r == '+')
-                        {
-                            if (runScriptDebug > 1) printf ("skip %c\n", *r);
-                            *w++ = *r++;
-                        }
-                        break;
-                    }
-                    if (runScriptDebug > 1) printf ("copy %c\n", *r);
-                    if (!(*w++ = *r)) break;
-                    r++;
-                };
-            }
+            replaceExpressions(x, line_raw, line_raw_size);
             if (runScriptDebug)
                 printf("runScript: assign %s=%s\n", p, line_raw);
             macPutValue(mac, p, line_raw);
@@ -503,6 +344,7 @@ int afterInit(char* cmd, char* a1, char* a2, char* a3, char* a4, char* a5, char*
 }
 
 epicsExportAddress(int, runScriptDebug);
+epicsExportAddress(int, exprDebug);
 
 static const iocshFuncDef runScriptDef = {
     "runScript", 2, (const iocshArg *[]) {
diff --git a/runScript.dbd b/runScript.dbd
index a16c6c234ee2e199481112243cedeb2bfa66b388..ab7b21dc3715dc551931416a2a39d9e7719e02a3 100644
--- a/runScript.dbd
+++ b/runScript.dbd
@@ -1,2 +1,3 @@
 registrar(runScriptRegister)
 variable(runScriptDebug,int)
+variable(exprDebug,int)
diff --git a/testscript b/testscript
new file mode 100644
index 0000000000000000000000000000000000000000..168ee74c5e372b172527853358060dbe8b2b9c2d
--- /dev/null
+++ b/testscript
@@ -0,0 +1,82 @@
+#var exprDebug 1
+
+x=3, !3, -3, ~3, 3?
+# $(x) should be: 3, 0, -3, -4, 1
+
+x=10+3, 10-3, 10*3, 10/3, 10%3, 10**3
+# $(x) should be: 13, 7, 30, 3, 1, 1000
+
+x=%x -10<<2, %x -10>>2, %x -10>>>2 
+# $(x) should be: ffffffffffffffd8, fffffffffffffffd, 3ffffffffffffffd
+
+x=%x 0xaa & 0x0f, %x 0xaa | 0x0f, %x 0xaa ^ 0x0f
+# $(x) should be: a, af, a5
+
+x=10<20 10<=20 10==20 10>=20 10>20 10<=>20
+# $(x) should be: 1 1 0 0 0 -1
+
+x=20<20 20<=20 20==20 20>=20 20>20 20<=>20
+# $(x) should be: 0 1 1 1 0 0
+
+x=20<10 20<=10 20==10 20>=10 20>10 20<=>10
+# $(x) should be: 0 0 0 1 1 1
+
+x= (0|0)(0|1)(1|0)(1|1) (0&0)(0&1)(1&0)(1&1)
+# $(x) should be: 0111 0001
+
+x=1*2*3+4*5*6-7-8
+# $(x) should be: 111
+
+x=1*2*3+4*5*6-7*8
+# $(x) should be: 70
+
+x=-3**2+1, -(3**2)+1, (-3)**2+1, -3**-2+1, (1+2), (1+2, 1+2)
+# $(x) should be: 10, -8, 10, 1, 3, (1+2, 3)
+
+x=%#X 0x5555&0xfff0|0xaf<<8^0xa00a0>>4
+# $(x) should be: 0X5F5A
+
+x=7+4*2|32
+# $(x) should be: 47
+
+x=3<5 ? 7+4*2|32 : 9 & 10
+# $(x) should be: 47
+
+x=3>5 ? 7+4*2|32 : 9 & 10
+# $(x) should be: 8
+
+x=7 ?: -1
+# $(x) should be: 7
+
+x=0 ?: -1
+# $(x) should be: -1
+
+x=7 ? -1
+# $(x) should be: -1
+
+x=0 ? -1
+# $(x) should be: 0
+
+x=1+0?0?1:2:3?4:5
+# $(x) should be: 2
+
+x=1+0?7?1:2:3?4:5
+# $(x) should be: 1
+
+x=1+1?0?1:2:3-3?4:5
+# $(x) should be: 2
+
+x=1-1?0?1:2:3?4:5
+# $(x) should be: 4
+
+x=1-1?0?1:2:3-3?4:5
+# $(x) should be: 5
+
+x=0?; 1?; 2?; -4?
+# $(x) should be: 0; 1; 1; 1
+
+x=a030b a(030)b a( 030 )b "a"030"b" a(%x 030)b
+# $(x) should be: a030b a24b a24b a24b a18b
+
+x=-2**2 0-2**2 0+-2**2 0--2**2
+# $(x) should be: 4 -4 4 -4