trangetest: switch to C implementation - numtools - perform numerical operations on vectors and matrices in unix pipes
 (HTM) git clone git://src.adamsgaard.dk/numtools
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 7a3d0ac06b0b914fbd04bd19e76fc682fcb38a20
 (DIR) parent 1fb885b94e493d9d36da5ddf5d2cd8fb8a99510f
 (HTM) Author: Anders Damsgaard <anders@adamsgaard.dk>
       Date:   Tue, 31 Aug 2021 10:41:51 +0200
       
       rangetest: switch to C implementation
       
       Diffstat:
         M Makefile                            |      51 +++++++++++++++++++++++++------
         D rangetest                           |      75 -------------------------------
         A rangetest.c                         |     137 +++++++++++++++++++++++++++++++
       
       3 files changed, 178 insertions(+), 85 deletions(-)
       ---
 (DIR) diff --git a/Makefile b/Makefile
       t@@ -8,25 +8,53 @@ PREFIX = /usr/local
        MANPREFIX = ${PREFIX}/man
        DOCPREFIX = ${PREFIX}/share/doc/${NAME}
        
       -SCRIPTS = \
       +SCRIPTS =\
                histpdf\
                max\
                mean\
                min\
       -        rangetest\
                sum\
       -        transpose
       +        transpose\
       +
       +BIN =\
       +        rangetest\
       +
       +SRC = ${BIN:=.c}
        
       -MAN1 = ${SCRIPTS:=.1}
       -DOC = \
       +HDR =\
       +        arg.h\
       +
       +LIBS = -lm
       +
       +_CFLAGS = ${CFLAGS} ${INCS} -DVERSION=\"${VERSION}\"
       +_LDFLAGS = ${LDFLAGS} ${LIBS}
       +_CPPFLAGS = ${CPPFLAGS}
       +
       +MAN1 = ${BIN:=.1} ${SCRIPTS:=.1}
       +DOC =\
                LICENSE\
       -        README
       +        README\
       +
       +all: ${BIN}
       +
       +${BIN}: ${@:=.o}
       +
       +OBJ = ${SRC:.c=.o}
       +
       +${OBJ}: ${HDR}
       +
       +.o:
       +        ${CC} -o $@ $< ${_LDFLAGS}
       +
       +.c.o:
       +        ${CC} ${_CFLAGS} ${_CPPFLAGS} -o $@ -c $<
       +
        
        install: 
                # installing executable files and scripts.
                mkdir -p "${DESTDIR}${PREFIX}/bin"
                cp -f ${SCRIPTS} "${DESTDIR}${PREFIX}/bin"
       -        for f in ${SCRIPTS}; do chmod 755 "${DESTDIR}${PREFIX}/bin/$$f"; done
       +        for f in ${BIN} ${SCRIPTS}; do chmod 755 "${DESTDIR}${PREFIX}/bin/$$f"; done
                # installing documentation files.
                mkdir -p "${DESTDIR}${DOCPREFIX}"
                cp -f ${DOC} "${DESTDIR}${DOCPREFIX}"
       t@@ -38,7 +66,7 @@ install:
        
        uninstall:
                # removing executable files and scripts.
       -        for f in ${SCRIPTS}; do rm -f "${DESTDIR}${PREFIX}/bin/$$f"; done
       +        for f in ${BIN} ${SCRIPTS}; do rm -f "${DESTDIR}${PREFIX}/bin/$$f"; done
                # removing example files.
                for d in ${DOC}; do rm -f "${DESTDIR}${DOCPREFIX}/$$d"; done
                -rmdir "${DESTDIR}${DOCPREFIX}"
       t@@ -48,10 +76,13 @@ uninstall:
        dist:
                rm -rf "${NAME}-${VERSION}"
                mkdir -p "${NAME}-${VERSION}"
       -        cp -rf ${MAN1} ${DOC} ${SCRIPTS} "${NAME}-${VERSION}"
       +        cp -rf ${MAN1} ${DOC} ${BIN} ${SCRIPTS} "${NAME}-${VERSION}"
                # make tarball
                tar cf - "${NAME}-${VERSION}" | \
                        gzip -c > "${NAME}-${VERSION}.tar.gz"
                rm -rf "${NAME}-${VERSION}"
        
       -.PHONY: install uninstall dist
       +clean:
       +        rm -f ${BIN} ${OBJ}
       +
       +.PHONY: install uninstall dist clean
 (DIR) diff --git a/rangetest b/rangetest
       t@@ -1,75 +0,0 @@
       -#!/usr/bin/awk -f
       -# uses a binary search to run a "cmd" where the first occurence of
       -# string @VAL@ is substituted for a value between "min_val" and
       -# "max_val".  Successful runs are reported on stdout, failed runs
       -# on stderr.  The "cmd" command must successfully run with either
       -# "min_val" or "max_val".
       -
       -function die(s) {
       -        printf "error: %s\n", s > "/dev/stderr"
       -        exit 1
       -}
       -
       -function launch(cmd, val) {
       -        sub(/@VAL@/, val, cmd)
       -        if (system(cmd)) {
       -                printf "%g\n", val > "/dev/stderr"
       -                return 1
       -        } else {
       -                printf "%g\n", val > "/dev/stdout"
       -                return 0
       -        }
       -}
       -
       -function binary_search(cmd, min, max, maxiter) {
       -
       -        minfail = launch(cmd, min)
       -        maxfail = launch(cmd, max)
       -
       -        if (minfail && maxfail)
       -                die("both min_val and max_val runs errored")
       -
       -        if (!minfail && !maxfail)
       -                die("both min_val and max_val ran successfully")
       -
       -        while (min <= max && iter < maxiter) {
       -                val = min + 0.5 * (max - min)
       -
       -                if (launch(cmd, val)) {   # the cmd fails
       -                        if (maxfail) {
       -                                max = val
       -                                maxfail = 1
       -                        } else {
       -                                min = val
       -                                minfail = 1
       -                        }
       -                } else {                  # the cmd is ok
       -                        if (maxfail) {
       -                                min = val
       -                                minfail = 0
       -                        } else {
       -                                max = val
       -                                maxfail = 0
       -                        }
       -                }
       -                iter++
       -        }
       -}
       -
       -BEGIN {
       -
       -        if (ARGC != 4)
       -                die("usage: rangetest cmd min_val max_val")
       -
       -        cmd = ARGV[1]
       -        min = ARGV[2]
       -        max = ARGV[3]
       -
       -        if (!match(cmd, /@VAL@/))
       -                die("@VAL@ not found in cmd")
       -
       -        if (min >= max)
       -                die("min_val must be smaller than max_val")
       -
       -        binary_search(cmd, min, max, 10)
       -}
 (DIR) diff --git a/rangetest.c b/rangetest.c
       t@@ -0,0 +1,137 @@
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <err.h>
       +#include <limits.h>
       +#include <string.h>
       +
       +#include "arg.h"
       +
       +#define VALUESTR "@VAL@"
       +
       +char *argv0;
       +
       +#ifdef NEED_STRLCPY /* OpenBSD implementation */
       +size_t
       +strlcpy(char *dst, const char *src, size_t dsize) {
       +        const char *osrc = src;
       +        size_t nleft = dsize;
       +
       +        if (nleft != 0) {
       +                while (--nleft != 0) {
       +                        if ((*dst++= *src++) == '\0')
       +                                break;
       +                }
       +        }
       +
       +        if (nleft == 0) {
       +                if (dsize != 0)
       +                        *dst = '\0';
       +                while (*src++)
       +                        ;
       +        }
       +
       +        return(src - osrc - 1);
       +}
       +#endif /* NEED_STRLCPY */
       +
       +static void
       +usage(void)
       +{
       +        errx(1, "usage: %s [-h] [-n maxiter] cmd min_val max_val\n"
       +                "where cmd must contain the string '" VALUESTR "'", argv0);
       +}
       +
       +static int
       +launch(char *cmd, char *cmd0, double val)
       +{
       +        char *c;
       +
       +        if ((c = strstr(cmd0, VALUESTR)) == NULL)
       +                errx(1, VALUESTR " not found in cmd");
       +
       +        if (strlcpy(cmd, cmd0, PATH_MAX) >= PATH_MAX)
       +                err(1, "cmd too long");
       +
       +        sprintf(cmd + (c - cmd0), "%g%s >/dev/null", val, c + strnlen(VALUESTR, sizeof(cmd)));
       +        if (system(cmd)) {
       +                fprintf(stderr, "%g\n", val);
       +                return 1;
       +        } else {
       +                printf("%g\n", val);
       +                return 0;
       +        }
       +}
       +
       +static void
       +binary_search(char *cmd, char *cmd0, double minv, double maxv, int maxiter)
       +{
       +        int minfail, maxfail;
       +        int iter;
       +        double val;
       +
       +        minfail = launch(cmd, cmd0, minv);
       +        maxfail = launch(cmd, cmd0, maxv);
       +
       +        if (minfail && maxfail)
       +                errx(2, "both min_val and max_val runs errored");
       +
       +        else if (!minfail && !maxfail)
       +                errx(3, "both min_val and max_val ran successfully");
       +
       +        while (minv <= maxv && iter < maxiter) {
       +                val = minv + 0.5 * (maxv - minv);
       +
       +                if (launch(cmd, cmd0, val)) {
       +                        if (maxfail) {
       +                                maxv = val;
       +                                maxfail = 1;
       +                        } else {
       +                                minv = val;
       +                                minfail = 1;
       +                        }
       +                } else {
       +                        if (maxfail) {
       +                                minv = val;
       +                                minfail = 0;
       +                        } else {
       +                                maxv = val;
       +                                maxfail = 0;
       +                        }
       +                }
       +                iter++;
       +        }
       +}
       +
       +int
       +main(int argc, char *argv[])
       +{
       +        int maxiter = 10;
       +        double minv, maxv;
       +        char cmd0[PATH_MAX] = "", cmd[PATH_MAX] = "";
       +
       +        ARGBEGIN {
       +        case 'h':
       +                usage();
       +                break;
       +        case 'n':
       +                maxiter = atoi(EARGF(usage()));
       +                if (maxiter < 1)
       +                        errx(1, "maxiter (-n) must be positive");
       +                break;
       +        default:
       +                usage();
       +        } ARGEND;
       +
       +        if (argc == 3) {
       +                if (strlcpy(cmd0, argv[0], sizeof(cmd0)) >= sizeof(cmd0))
       +                        err(1, "cmd too long");
       +                minv = atof(argv[1]);
       +                maxv = atof(argv[2]);
       +                if (minv >= maxv)
       +                        errx(1, "min_val must be smaller than max_val");
       +                binary_search(cmd, cmd0, minv, maxv, maxiter);
       +        } else
       +                usage();
       +
       +        return 0;
       +}