/***************************************************************/
/*                                                             */
/*  FUNCS.C                                                    */
/*                                                             */
/*  This file contains the built-in functions used in          */
/*  expressions.                                               */
/*                                                             */
/*  This file is part of REMIND.                               */
/*  Copyright (C) 1992-2026 by Dianne Skoll                    */
/*  SPDX-License-Identifier: GPL-2.0-only                      */
/*                                                             */
/***************************************************************/
#include "version.h"
#include "config.h"

/* Required on OpenIndiana / Solaris */
#ifdef __sun
#define __EXTENSIONS__
#endif

#define _XOPEN_SOURCE 600
#include <wctype.h>
#include <wchar.h>

#include <stdio.h>

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

#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif

#ifdef HAVE_SYS_TERMIOS_H
#ifdef __sun
#include <sys/termios.h>
#endif
#endif

#include <ctype.h>
#include <math.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#ifdef TM_IN_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif

#ifndef R_OK
#define R_OK 4
#define W_OK 2
#define X_OK 1
#endif

#include "types.h"
#include "globals.h"
#include "protos.h"
#include "err.h"

/* Defines that used to be static variables */
#define Nargs (info->nargs)
#define RetVal (info->retval)

#define DBGX (DebugFlag & DB_PRTEXPR)

#define DBG(x) do { if (DBGX) { x; } } while(0)
/* Debugging helpers for "choose()", "iif(), etc. */
#define PUT(x) DBufPuts(&DebugBuf, x)
#define OUT() do { fprintf(ErrFp, "%s\n", DBufValue(&DebugBuf)); DBufFree(&DebugBuf); } while(0)

/* Last error from catch() */
static int LastCatchError = OK;

static int
solstice_equinox_for_year(int y, int which);

/* Function prototypes */
static int F_              (func_info *);
static int FADawn          (func_info *);
static int FADusk          (func_info *);
static int FAbs            (func_info *);
static int FAccess         (func_info *);
static int FAmpm           (func_info *);
static int FAnsicolor      (func_info *);
static int FArgs           (func_info *);
static int FAsc            (func_info *);
static int FBaseyr         (func_info *);
static int FCatch          (expr_node *, Value *, Value *, int *);
static int FCatchErr       (func_info *);
static int FChar           (func_info *);
static int FChoose         (expr_node *, Value *, Value *, int *);
static int FCodepoint      (func_info *);
static int FCoerce         (func_info *);
static int FColumns        (func_info *);
static int FCurrent        (func_info *);
static int FDate           (func_info *);
static int FDateTime       (func_info *);
static int FDatepart       (func_info *);
static int FDawn           (func_info *);
static int FDay            (func_info *);
static int FDaysinmon      (func_info *);
static int FDefined        (func_info *);
static int FDosubst        (func_info *);
static int FDusk           (func_info *);
static int FEasterdate     (func_info *);
static int FEscape         (func_info *);
static int FEval           (func_info *);
static int FEvalTrig       (func_info *);
static int FFiledate       (func_info *);
static int FFiledatetime   (func_info *);
static int FFiledir        (func_info *);
static int FFilename       (func_info *);
static int FGetenv         (func_info *);
static int FHebdate        (func_info *);
static int FHebday         (func_info *);
static int FHebmon         (func_info *);
static int FHebyear        (func_info *);
static int FHex            (func_info *);
static int FHour           (func_info *);
static int FHtmlEscape     (func_info *);
static int FHtmlStriptags  (func_info *);
static int FIif            (expr_node *, Value *, Value *, int *);
static int FIndex          (func_info *);
static int FIsAny          (expr_node *, Value *, Value *, int *);
static int FIsconst        (expr_node *, Value *, Value *, int *);
static int FIsdst          (func_info *);
static int FIsleap         (func_info *);
static int FIsomitted      (func_info *);
static int FIvritmon       (func_info *);
static int FLanguage       (func_info *);
static int FLocalToUTC     (func_info *);
static int FLower          (func_info *);
static int FMax            (func_info *);
static int FMbchar         (func_info *);
static int FMbindex        (func_info *);
static int FMblower        (func_info *);
static int FMbpad          (func_info *);
static int FMbstrlen       (func_info *);
static int FMbsubstr       (func_info *);
static int FMbupper        (func_info *);
static int FMin            (func_info *);
static int FMinsfromutc    (func_info *);
static int FMinute         (func_info *);
static int FMon            (func_info *);
static int FMonnum         (func_info *);
static int FMoondate       (func_info *);
static int FMoondatetime   (func_info *);
static int FMoonphase      (func_info *);
static int FMoonrise       (func_info *);
static int FMoonrisedir    (func_info *);
static int FMoonset        (func_info *);
static int FMoonsetdir     (func_info *);
static int FMoontime       (func_info *);
static int FMultiTrig      (func_info *);
static int FNDawn          (func_info *);
static int FNDusk          (func_info *);
static int FNonconst       (func_info *);
static int FNonomitted     (func_info *);
static int FNow            (func_info *);
static int FOrd            (func_info *);
static int FOrthodoxeaster (func_info *);
static int FOstype         (func_info *);
static int FPad            (func_info *);
static int FPlural         (func_info *);
static int FPsmoon         (func_info *);
static int FPsshade        (func_info *);
static int FRealCurrent    (func_info *);
static int FRealnow        (func_info *);
static int FRealtoday      (func_info *);
static int FRows           (func_info *);
static int FSgn            (func_info *);
static int FShell          (func_info *);
static int FShellescape    (func_info *);
static int FSlide          (func_info *);
static int FSoleq          (func_info *);
static int FStdout         (func_info *);
static int FStrlen         (func_info *);
static int FSubstr         (func_info *);
static int FSunrise        (func_info *);
static int FSunset         (func_info *);
static int FTime           (func_info *);
static int FTimepart       (func_info *);
static int FTimezone       (func_info *);
static int FToday          (func_info *);
static int FTrig           (func_info *);
static int FTrigback       (func_info *);
static int FTrigbase       (func_info *info);
static int FTrigcompletethrough (func_info *);
static int FTrigdate       (func_info *);
static int FTrigdatetime   (func_info *);
static int FTrigdelta      (func_info *);
static int FTrigduration   (func_info *);
static int FTriginfo       (func_info *);
static int FTrigeventduration(func_info *);
static int FTrigeventstart (func_info *);
static int FTrigeventstarttz (func_info *);
static int FTrigfrom       (func_info *);
static int FTrigger        (func_info *);
static int FTrigistodo     (func_info *);
static int FTrigmaxoverdue (func_info *);
static int FTrigpriority   (func_info *);
static int FTrigrep        (func_info *);
static int FTrigscanfrom   (func_info *);
static int FTrigtags       (func_info *);
static int FTrigtime       (func_info *);
static int FTrigtimetz     (func_info *);
static int FTrigtimedelta  (func_info *);
static int FTrigtimerep    (func_info *);
static int FTrigtz         (func_info *);
static int FTriguntil      (func_info *);
static int FTrigvalid      (func_info *);
static int FTypeof         (func_info *);
static int FTzconvert      (func_info *);
static int FUTCToLocal     (func_info *);
static int FUpper          (func_info *);
static int FValue          (expr_node *, Value *, Value *, int *);
static int FVersion        (func_info *);
static int FWeekno         (func_info *);
static int FWkday          (func_info *);
static int FWkdaynum       (func_info *);
static int FYear           (func_info *);

static int SunStuff        (int rise, double cosz, int dse);

/* Caches for extracting months, days, years from dates - may
   improve performance slightly. */
static int CacheDse = -1;
static int CacheYear, CacheMon, CacheDay;

static int CacheHebDse = -1;
static int CacheHebYear, CacheHebMon, CacheHebDay;

/* Macro for accessing arguments from the value stack - args are numbered
   from 0 to (Nargs - 1) */
#define ARG(x) (info->args[x])

#define ARGV(x) ARG(x).v.val
#define ARGSTR(x) ARG(x).v.str

#define ASSERT_TYPE(x, t) if (ARG(x).type != t) return E_BAD_TYPE

/* Macro for getting date part of a date or datetime value */
#define DATEPART(x) ((x).type == DATE_TYPE ? (x).v.val : ((x).v.val / MINUTES_PER_DAY))

/* Macro for getting time part of a time or datetime value */
#define TIMEPART(x) ((x).type == TIME_TYPE ? (x).v.val : ((x).v.val % MINUTES_PER_DAY))

#define HASDATE(x) ((x).type & DATE_TYPE)
#define HASTIME(x) ((x).type & TIME_TYPE)

#define GETMON(x) get_month(&(ARG(x)))

/* Macro for copying a value while destroying original copy */
#define DCOPYVAL(x, y) ( (x) = (y), (y).type = ERR_TYPE, (y).v.val = 0 )

/* Get at RetVal.v.val easily */
#define RETVAL info->retval.v.val

/* Convenience macros */
#define UPPER(c) (islower(c) ? toupper(c) : c)
#define LOWER(c) (isupper(c) ? tolower(c) : c)

/* The array holding the built-in functions. */
BuiltinFunc Func[] = {
/*      Name            minargs maxargs is_constant func             newfunc*/
    {   "_",            1,      1,      0,          F_, NULL },
    {   "abs",          1,      1,      1,          FAbs, NULL },
    {   "access",       2,      2,      0,          FAccess, NULL },
    {   "adawn",        0,      1,      0,          FADawn, NULL},
    {   "adusk",        0,      1,      0,          FADusk, NULL},
    {   "ampm",         1,      4,      1,          FAmpm, NULL },
    {   "ansicolor",    1,      5,      1,          FAnsicolor, NULL },
    {   "args",         1,      1,      0,          FArgs, NULL },
    {   "asc",          1,      1,      1,          FAsc, NULL },
    {   "baseyr",       0,      0,      1,          FBaseyr, NULL },
    {   "catch",        2,      2,      1,          NULL, FCatch }, /* NEW-STYLE */
    {   "catcherr",     0,      0,      0,          FCatchErr, NULL },
    {   "char",         1,      NO_MAX, 1,          FChar, NULL },
    {   "choose",       2,      NO_MAX, 1,          NULL, FChoose }, /*NEW-STYLE*/
    {   "codepoint",    1,      1,      1,          FCodepoint, NULL },
    {   "coerce",       2,      2,      1,          FCoerce, NULL },
    {   "columns",      0,      1,      0,          FColumns, NULL },
    {   "const",        1,      1,      1,          FNonconst, NULL },
    {   "current",      0,      0,      0,          FCurrent, NULL },
    {   "date",         3,      3,      1,          FDate, NULL },
    {   "datepart",     1,      1,      1,          FDatepart, NULL },
    {   "datetime",     2,      5,      1,          FDateTime, NULL },
    {   "dawn",         0,      1,      0,          FDawn, NULL },
    {   "day",          1,      1,      1,          FDay, NULL },
    {   "daysinmon",    1,      2,      1,          FDaysinmon, NULL },
    {   "defined",      1,      1,      0,          FDefined, NULL },
    {   "dosubst",      1,      3,      0,          FDosubst, NULL },
    {   "dusk",         0,      1,      0,          FDusk, NULL },
    {   "easterdate",   0,      1,      0,          FEasterdate, NULL },
    {   "escape",       1,      2,      1,          FEscape, NULL },
    {   "eval",         1,      1,      1,          FEval, NULL },
    {   "evaltrig",     1,      2,      0,          FEvalTrig, NULL },
    {   "filedate",     1,      1,      0,          FFiledate, NULL },
    {   "filedatetime", 1,      1,      0,          FFiledatetime, NULL },
    {   "filedir",      0,      0,      0,          FFiledir, NULL },
    {   "filename",     0,      0,      0,          FFilename, NULL },
    {   "getenv",       1,      1,      0,          FGetenv, NULL },
    {   "hebdate",      2,      5,      0,          FHebdate, NULL },
    {   "hebday",       1,      1,      0,          FHebday, NULL },
    {   "hebmon",       1,      1,      0,          FHebmon, NULL },
    {   "hebyear",      1,      1,      0,          FHebyear, NULL },
    {   "hex",          1,      1,      1,          FHex, NULL },
    {   "hour",         1,      1,      1,          FHour, NULL },
    {   "htmlescape",   1,      1,      1,          FHtmlEscape, NULL },
    {   "htmlstriptags",1,      1,      1,          FHtmlStriptags, NULL },
    {   "iif",          1,      NO_MAX, 1,          NULL, FIif }, /*NEW-STYLE*/
    {   "index",        2,      3,      1,          FIndex, NULL },
    {   "isany",        1,      NO_MAX, 1,          NULL, FIsAny }, /*NEW-STYLE*/
    {   "isconst",      1,      1,      1,          NULL, FIsconst }, /*NEW-STYLE*/
    {   "isdst",        0,      2,      0,          FIsdst, NULL },
    {   "isleap",       1,      1,      1,          FIsleap, NULL },
    {   "isomitted",    1,      1,      0,          FIsomitted, NULL },
    {   "ivritmon",     1,      1,      0,          FIvritmon, NULL },
    {   "language",     0,      0,      1,          FLanguage, NULL },
    {   "localtoutc",   1,      1,      1,          FLocalToUTC, NULL },
    {   "lower",        1,      1,      1,          FLower, NULL },
    {   "max",          1,      NO_MAX, 1,          FMax, NULL },
    {   "mbchar",       1,      NO_MAX, 1,          FMbchar, NULL },
    {   "mbindex",      2,      3,      1,          FMbindex, NULL },
    {   "mblower",      1,      1,      1,          FMblower, NULL },
    {   "mbpad",        3,      4,      1,          FMbpad, NULL },
    {   "mbstrlen",     1,      1,      1,          FMbstrlen, NULL },
    {   "mbsubstr",     2,      3,      1,          FMbsubstr, NULL },
    {   "mbupper",      1,      1,      1,          FMbupper, NULL },
    {   "min",          1,      NO_MAX, 1,          FMin, NULL },
    {   "minsfromutc",  0,      2,      0,          FMinsfromutc, NULL },
    {   "minute",       1,      1,      1,          FMinute, NULL },
    {   "mon",          1,      1,      1,          FMon, NULL },
    {   "monnum",       1,      1,      1,          FMonnum, NULL },
    {   "moondate",     1,      3,      0,          FMoondate, NULL },
    {   "moondatetime", 1,      3,      0,          FMoondatetime, NULL },
    {   "moonphase",    0,      2,      0,          FMoonphase, NULL },
    {   "moonrise",     0,      1,      0,          FMoonrise, NULL },
    {   "moonrisedir",  0,      1,      0,          FMoonrisedir, NULL },
    {   "moonset",      0,      1,      0,          FMoonset, NULL },
    {   "moonsetdir",   0,      1,      0,          FMoonsetdir, NULL },
    {   "moontime",     1,      3,      0,          FMoontime, NULL },
    {   "multitrig",    1,      NO_MAX, 0,          FMultiTrig, NULL },
    {   "ndawn",        0,      1,      0,          FNDawn, NULL },
    {   "ndusk",        0,      1,      0,          FNDusk, NULL },
    {   "nonconst",     1,      1,      0,          FNonconst, NULL },
    {   "nonomitted",   2,      NO_MAX, 0,          FNonomitted, NULL },
    {   "now",          0,      0,      0,          FNow, NULL },
    {   "ord",          1,      1,      1,          FOrd, NULL },
    {   "orthodoxeaster",0,     1,      0,          FOrthodoxeaster, NULL },
    {   "ostype",       0,      0,      1,          FOstype, NULL },
    {   "pad",          3,      4,      1,          FPad, NULL },
    {   "plural",       1,      3,      1,          FPlural, NULL },
    {   "psmoon",       1,      4,      1,          FPsmoon, NULL },
    {   "psshade",      1,      3,      1,          FPsshade, NULL },
    {   "realcurrent",  0,      0,      0,          FRealCurrent, NULL },
    {   "realnow",      0,      0,      0,          FRealnow, NULL },
    {   "realtoday",    0,      0,      0,          FRealtoday, NULL },
    {   "rows",         0,      0,      0,          FRows, NULL },
    {   "sgn",          1,      1,      1,          FSgn, NULL },
    {   "shell",        1,      2,      0,          FShell, NULL },
    {   "shellescape",  1,      1,      1,          FShellescape, NULL },
    {   "slide",        2,      NO_MAX, 0,          FSlide, NULL },
    {   "soleq",        1,      2,      0,          FSoleq, NULL },
    {   "stdout",       0,      0,      0,          FStdout, NULL },
    {   "strlen",       1,      1,      1,          FStrlen, NULL },
    {   "substr",       2,      3,      1,          FSubstr, NULL },
    {   "sunrise",      0,      1,      0,          FSunrise, NULL },
    {   "sunset",       0,      1,      0,          FSunset, NULL },
    {   "time",         2,      2,      1,          FTime, NULL },
    {   "timepart",     1,      1,      1,          FTimepart, NULL },
    {   "timezone",     0,      1,      0,          FTimezone, NULL },
    {   "today",        0,      0,      0,          FToday, NULL },
    {   "trig",         0,      NO_MAX, 0,          FTrig, NULL },
    {   "trigback",     0,      0,      0,          FTrigback, NULL },
    {   "trigbase",     0,      0,      0,          FTrigbase, NULL },
    {   "trigcompletethrough", 0, 0,    0,          FTrigcompletethrough, NULL },
    {   "trigdate",     0,      0,      0,          FTrigdate, NULL },
    {   "trigdatetime", 0,      0,      0,          FTrigdatetime, NULL },
    {   "trigdelta",    0,      0,      0,          FTrigdelta, NULL },
    {   "trigduration", 0,      0,      0,          FTrigduration, NULL },
    {   "trigeventduration", 0, 0,      0,          FTrigeventduration, NULL },
    {   "trigeventstart", 0,    0,      0,          FTrigeventstart, NULL },
    {   "trigeventstarttz", 0,  0,      0,          FTrigeventstarttz, NULL },
    {   "trigfrom",     0,      0,      0,          FTrigfrom, NULL },
    {   "trigger",      1,      3,      0,          FTrigger, NULL },
    {   "triginfo",     1,      1,      0,          FTriginfo, NULL },
    {   "trigistodo",   0,      0,      0,          FTrigistodo, NULL },
    {   "trigmaxoverdue", 0,    0,      0,          FTrigmaxoverdue, NULL },
    {   "trigpriority", 0,      0,      0,          FTrigpriority, NULL },
    {   "trigrep",      0,      0,      0,          FTrigrep, NULL },
    {   "trigscanfrom", 0,      0,      0,          FTrigscanfrom, NULL },
    {   "trigtags",     0,      0,      0,          FTrigtags, NULL },
    {   "trigtime",     0,      0,      0,          FTrigtime, NULL },
    {   "trigtimedelta",0,      0,      0,          FTrigtimedelta, NULL },
    {   "trigtimerep",  0,      0,      0,          FTrigtimerep, NULL },
    {   "trigtimetz",   0,      0,      0,          FTrigtimetz, NULL },
    {   "trigtz",       0,      0,      0,          FTrigtz, NULL },
    {   "triguntil",    0,      0,      0,          FTriguntil, NULL },
    {   "trigvalid",    0,      0,      0,          FTrigvalid, NULL },
    {   "typeof",       1,      1,      1,          FTypeof, NULL },
    {   "tzconvert",    2,      3,      0,          FTzconvert, NULL },
    {   "upper",        1,      1,      1,          FUpper, NULL },
    {   "utctolocal",   1,      1,      1,          FUTCToLocal, NULL },
    {   "value",        1,      2,      1,          NULL, FValue }, /* NEW-STYLE */
    {   "version",      0,      0,      1,          FVersion, NULL },
    {   "weekno",       0,      3,      0,          FWeekno, NULL },
    {   "wkday",        1,      1,      1,          FWkday, NULL },
    {   "wkdaynum",     1,      1,      1,          FWkdaynum, NULL },
    {   "year",         1,      1,      1,          FYear, NULL }
};

/* Need a variable here - Func[] array not really visible to outside. */
int NumFuncs = sizeof(Func) / sizeof(BuiltinFunc) ;

static int get_month(Value *v)
{
    Token tok;

    if (v->type == INT_TYPE) {
        if (v->v.val < 1) return -E_2LOW;
        if (v->v.val > 12) return -E_2HIGH;
        return v->v.val - 1;
    }
    if (v->type == STR_TYPE) {
        FindToken(v->v.str, &tok);
        if (tok.type != T_Month) {
            return -E_CANT_PARSE_MONTH;
        }
        return tok.val;
    }
    return -E_BAD_TYPE;
}


/***************************************************************/
/*                                                             */
/*  RetStrVal                                                  */
/*                                                             */
/*  Return a string value from a function.                     */
/*                                                             */
/***************************************************************/
static int RetStrVal(char const *s, func_info *info)
{
    RetVal.type = STR_TYPE;
    if (!s) {
        RetVal.v.str = malloc(1);
        if (RetVal.v.str) *RetVal.v.str = 0;
    } else {
        RetVal.v.str = strdup(s);
    }

    if (!RetVal.v.str) {
        RetVal.type = ERR_TYPE;
        return E_NO_MEM;
    }
    return OK;
}


/***************************************************************/
/*                                                             */
/*  F_ - look up a string in the translation table             */
/*                                                             */
/***************************************************************/
static int F_(func_info *info)
{
    DynamicBuffer translated;
    int r;

    DBufInit(&translated);
    ASSERT_TYPE(0, STR_TYPE);
    r = GetTranslatedStringTryingVariants(ARGSTR(0), &translated);
    if (!r) {
        DCOPYVAL(RetVal, ARG(0));
        return OK;
    }
    r = RetStrVal(DBufValue(&translated), info);
    DBufFree(&translated);
    if (DebugFlag & DB_TRANSLATE) {
        TranslationTemplate(ARGSTR(0));
    }
    return r;
}

/***************************************************************/
/*                                                             */
/*  FStrlen - string length                                    */
/*                                                             */
/***************************************************************/
static int FStrlen(func_info *info)
{
    ASSERT_TYPE(0, STR_TYPE);
    RetVal.type = INT_TYPE;
    size_t l = strlen(ARGSTR(0));
    if (l > INT_MAX) return E_2HIGH;
    RETVAL = (int) l;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FMBstrlen - string length in wide characters               */
/*                                                             */
/***************************************************************/
static int FMbstrlen(func_info *info)
{
    ASSERT_TYPE(0, STR_TYPE);
    size_t l = mbstowcs(NULL, ARGSTR(0), 0);
    if (l == (size_t) -1) {
        return E_BAD_MB_SEQ;
    }
    if (l > INT_MAX) return E_2HIGH;
    RetVal.type = INT_TYPE;
    RETVAL = (int) l;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FBaseyr - system base year                                 */
/*                                                             */
/***************************************************************/
static int FBaseyr(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = BASE;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FDate - make a date from year, month, day.                 */
/*                                                             */
/***************************************************************/
static int FDate(func_info *info)
{
    int y, m, d;

    /* Any arg can be a date (in which case we use the corresponding
       component) or an integer */
    if (HASDATE(ARG(0))) {
        FromDSE(DATEPART(ARG(0)), &y, NULL, NULL);
    } else {
        ASSERT_TYPE(0, INT_TYPE);
        y = ARGV(0);
    }

    if (HASDATE(ARG(1))) {
        FromDSE(DATEPART(ARG(1)), NULL, &m, NULL);
    } else {
        m = GETMON(1);
        if (m < 0) {
            return -m;
        }
    }

    if (HASDATE(ARG(2))) {
        FromDSE(DATEPART(ARG(2)), NULL, NULL, &d);
    } else {
        ASSERT_TYPE(2, INT_TYPE);
        d = ARGV(2);
    }

    if (!DateOK(y, m, d)) {
        return E_BAD_DATE;
    }
    RetVal.type = DATE_TYPE;
    RETVAL = DSE(y, m, d);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FDateTime - make a datetime from one of these combos:      */
/*  DATE, TIME                                                 */
/*  DATE, HOUR, MINUTE                                         */
/*  YEAR, MONTH, DAY, TIME                                     */
/*  YEAR, MONTH, DAY, HOUR, MINUTE                             */
/*                                                             */
/***************************************************************/
static int FDateTime(func_info *info)
{
    int y, m, d;

    RetVal.type = DATETIME_TYPE;

    switch(Nargs) {
    case 2:
        if (ARG(0).type != DATE_TYPE ||
            ARG(1).type != TIME_TYPE) return E_BAD_TYPE;
        RETVAL = (MINUTES_PER_DAY * ARGV(0)) + ARGV(1);
        return OK;
    case 3:
        if (ARG(0).type != DATE_TYPE ||
            ARG(1).type != INT_TYPE ||
            ARG(2).type != INT_TYPE) return E_BAD_TYPE;
        if (ARGV(1) < 0 || ARGV(2) < 0) return E_2LOW;
        if (ARGV(1) > 23 || ARGV(2) > 59) return E_2HIGH;
        RETVAL = (MINUTES_PER_DAY * ARGV(0)) + 60 * ARGV(1) + ARGV(2);
        return OK;
    case 4:
        if (ARG(0).type != INT_TYPE ||
            ARG(2).type != INT_TYPE ||
            ARG(3).type != TIME_TYPE) return E_BAD_TYPE;
        y = ARGV(0);
        m = GETMON(1);
        if (m < 0) return -m;
        d = ARGV(2);

        if (!DateOK(y, m, d)) return E_BAD_DATE;
        RETVAL = DSE(y, m, d) * MINUTES_PER_DAY + ARGV(3);
        return OK;
    case 5:
        if (ARG(0).type != INT_TYPE ||
            ARG(2).type != INT_TYPE ||
            ARG(3).type != INT_TYPE ||
            ARG(4).type != INT_TYPE) return E_BAD_TYPE;

        y = ARGV(0);
        m = GETMON(1);
        if (m < 0) return -m;
        d = ARGV(2);
        if (!DateOK(y, m, d)) return E_BAD_DATE;

        if (ARGV(3) < 0 || ARGV(4) < 0) return E_2LOW;
        if (ARGV(3) > 23 || ARGV(4) > 59) return E_2HIGH;
        RETVAL = DSE(y, m, d) * MINUTES_PER_DAY + ARGV(3) * 60 + ARGV(4);
        return OK;

    default:
        return E_2MANY_ARGS;
    }
}

/***************************************************************/
/*                                                             */
/*  FCoerce - type coercion function.                          */
/*                                                             */
/***************************************************************/
static int FCoerce(func_info *info)
{
    char const *s;
    char const *v = PrintValue(&(ARG(1)), NULL);
    ASSERT_TYPE(0, STR_TYPE);
    s = ARGSTR(0);

    int r = OK;

    /* Copy the value of ARG(1) into RetVal, and make ARG(1) invalid so
       it won't be destroyed */
    DCOPYVAL(RetVal, ARG(1));

    if (! strcasecmp(s, "int")) r =  DoCoerce(INT_TYPE, &RetVal);
    else if (! strcasecmp(s, "date")) r =  DoCoerce(DATE_TYPE, &RetVal);
    else if (! strcasecmp(s, "time")) r =  DoCoerce(TIME_TYPE, &RetVal);
    else if (! strcasecmp(s, "string")) r =  DoCoerce(STR_TYPE, &RetVal);
    else if (! strcasecmp(s, "datetime")) r =  DoCoerce(DATETIME_TYPE, &RetVal);
    else {
        Eprint("coerce(): Invalid type `%s'", s);
        return E_CANT_COERCE;
    }
    if (r) {
        Eprint("coerce(): Cannot convert %s to %s", v, s);
    }
    return r;
}

/***************************************************************/
/*                                                             */
/*  FNonconst - return arg, but with nonconst_expr flag set    */
/*                                                             */
/***************************************************************/
static int FNonconst(func_info *info)
{
    DCOPYVAL(RetVal, ARG(0));
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FMax - select maximum from a list of args.                 */
/*                                                             */
/***************************************************************/
static int FMax(func_info *info)
{
    Value *maxptr;
    int i;
    char type;

    maxptr = &ARG(0);
    type = maxptr->type;

    for (i=1; i<Nargs; i++) {
        if (ARG(i).type != type) return E_BAD_TYPE;
        if (type != STR_TYPE) {
            if (ARG(i).v.val > maxptr->v.val) maxptr = &ARG(i);
        } else {
            if (strcmp(ARG(i).v.str, maxptr->v.str) > 0) maxptr = &ARG(i);
        }
    }
    DCOPYVAL(RetVal, *maxptr);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FMin - select minimum from a list of args.                 */
/*                                                             */
/***************************************************************/
static int FMin(func_info *info)
{
    Value *minptr;
    int i;
    char type;

    minptr = &ARG(0);
    type = minptr->type;

    for (i=1; i<Nargs; i++) {
        if (ARG(i).type != type) return E_BAD_TYPE;
        if (type != STR_TYPE) {
            if (ARG(i).v.val < minptr->v.val) minptr = &ARG(i);
        } else {
            if (strcmp(ARG(i).v.str, minptr->v.str) < 0) minptr = &ARG(i);
        }
    }
    DCOPYVAL(RetVal, *minptr);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FAsc - ASCII value of first char of string                 */
/*                                                             */
/***************************************************************/
static int FAsc(func_info *info)
{
    ASSERT_TYPE(0, STR_TYPE);
    RetVal.type = INT_TYPE;
    RETVAL = (int) *( (unsigned char const *) ARGSTR(0));
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FHex - return hexadecimal representation of an integer     */
/*                                                             */
/***************************************************************/
static int FHex(func_info *info)
{
    char buf[64];

    ASSERT_TYPE(0, INT_TYPE);
    snprintf(buf, sizeof(buf), "%X", (unsigned int) ARGV(0));
    return RetStrVal(buf, info);
}

/***************************************************************/
/*                                                             */
/*  FCodepoint - wide-character codepoint of start of str      */
/*                                                             */
/***************************************************************/
static int FCodepoint(func_info *info)
{
    wchar_t arr[2];
    size_t len;

    ASSERT_TYPE(0, STR_TYPE);

    len = mbstowcs(arr, ARGSTR(0), sizeof(arr) / sizeof(arr[0]));
    if (len == (size_t) -1) {
        return E_BAD_MB_SEQ;
    }

    RetVal.type = INT_TYPE;
    RETVAL = (int) arr[0];
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FChar - build a string from ASCII values                   */
/*                                                             */
/***************************************************************/
static int FChar(func_info *info)
{

    int i, len;

/* Special case of one arg - if given ascii value 0, create empty string */
    if (Nargs == 1) {
        ASSERT_TYPE(0, INT_TYPE);
        if (ARGV(0) < -128) return E_2LOW;
        if (ARGV(0) > 255) return E_2HIGH;
        len = ARGV(0) ? 2 : 1;
        RetVal.v.str = malloc(len);
        if (!RetVal.v.str) return E_NO_MEM;
        RetVal.type = STR_TYPE;
        *(RetVal.v.str) = ARGV(0);
        if (len>1) *(RetVal.v.str + 1) = 0;
        return OK;
    }

    RetVal.v.str = malloc(Nargs + 1);
    if (!RetVal.v.str) return E_NO_MEM;
    RetVal.type = STR_TYPE;
    for (i=0; i<Nargs; i++) {
        if (ARG(i).type != INT_TYPE) {
            free(RetVal.v.str);
            RetVal.type = ERR_TYPE;
            return E_BAD_TYPE;
        }
        if (ARG(i).v.val < -128 || ARG(i).v.val == 0) {
            free(RetVal.v.str);
            RetVal.type = ERR_TYPE;
            return E_2LOW;
        }
        if (ARG(i).v.val > 255) {
            free(RetVal.v.str);
            RetVal.type = ERR_TYPE;
            return E_2HIGH;
        }
        *(RetVal.v.str + i) = ARG(i).v.val;
    }
    *(RetVal.v.str + Nargs) = 0;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FMbchar - build a string from wide character code points   */
/*                                                             */
/***************************************************************/
static int FMbchar(func_info *info)
{
    int i;
    size_t len;
    wchar_t *arr;
    char *s;

    for (i=0; i<Nargs; i++) {
        ASSERT_TYPE(i, INT_TYPE);
    }

    /* Special case of one arg - if given value 0, create empty string */
    if (Nargs == 1) {
        if (ARGV(0) == 0) {
            return RetStrVal("", info);
        }
    }

    arr = calloc(Nargs+1, sizeof(wchar_t));
    if (!arr) {
        return E_NO_MEM;
    }

    for (i=0; i<Nargs; i++) {
        if (ARGV(i) <= 0) {
            return E_2LOW;
        }
        arr[i] = (wchar_t) ARGV(i);
    }
    arr[Nargs] = (wchar_t) 0;

    len = wcstombs(NULL, arr, 0);
    if (len == (size_t) -1) {
        free( (void *) arr);
        return E_BAD_MB_SEQ;
    }

    s = malloc(len+1);
    if (!s) {
        free( (void *) arr);
        return E_NO_MEM;
    }
    (void) wcstombs(s, arr, len+1);
    free( (void *) arr);
    RetVal.type = STR_TYPE;
    RetVal.v.str = s;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  Functions for extracting the components of a date.         */
/*                                                             */
/*  FDay - get day of month                                    */
/*  FMonnum - get month (1-12)                                 */
/*  FYear - get year                                           */
/*  FWkdaynum - get weekday num (0 = Sun)                      */
/*  FWkday - get weekday (string)                              */
/*  FMon - get month (string)                                  */
/*                                                             */
/***************************************************************/
static int FDay(func_info *info)
{
    int y, m, d, v;
    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    v = DATEPART(ARG(0));

    if (v == CacheDse)
        d = CacheDay;
    else {
        FromDSE(v, &y, &m, &d);
        CacheDse = v;
        CacheYear = y;
        CacheMon = m;
        CacheDay = d;
    }
    RetVal.type = INT_TYPE;
    RETVAL = d;
    return OK;
}

static int FMonnum(func_info *info)
{
    int y, m, d, v;
    Token tok;
    if (ARG(0).type == STR_TYPE) {
        /* Convert a month name to a month number */
        FindToken(ARG(0).v.str, &tok);
        if (tok.type != T_Month) {
            return E_CANT_PARSE_MONTH;
        }
        RetVal.type = INT_TYPE;
        RETVAL = tok.val + 1;
        return OK;
    }

    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    v = DATEPART(ARG(0));

    if (v == CacheDse)
        m = CacheMon;
    else {
        FromDSE(v, &y, &m, &d);
        CacheDse = v;
        CacheYear = y;
        CacheMon = m;
        CacheDay = d;
    }
    RetVal.type = INT_TYPE;
    RETVAL = m+1;
    return OK;
}

static int FYear(func_info *info)
{
    int y, m, d, v;
    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    v = DATEPART(ARG(0));

    if (v == CacheDse)
        y = CacheYear;
    else {
        FromDSE(v, &y, &m, &d);
        CacheDse = v;
        CacheYear = y;
        CacheMon = m;
        CacheDay = d;
    }
    RetVal.type = INT_TYPE;
    RETVAL = y;
    return OK;
}

static int FWkdaynum(func_info *info)
{
    int v;
    Token tok;
    if (ARG(0).type == STR_TYPE) {
        /* Convert a day name to a day number */
        FindToken(ARG(0).v.str, &tok);
        if (tok.type != T_WkDay) {
            return E_CANT_PARSE_WKDAY;
        }
        RetVal.type = INT_TYPE;
        RETVAL = (tok.val + 1) % 7;
        return OK;
    }
    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    v = DATEPART(ARG(0));

    RetVal.type = INT_TYPE;

    /* Correct so that 0 = Sunday */
    RETVAL = (v+1) % 7;
    return OK;
}

static int FWkday(func_info *info)
{
    char const *s;

    if (!HASDATE(ARG(0)) && ARG(0).type != INT_TYPE) return E_BAD_TYPE;
    if (ARG(0).type == INT_TYPE) {
        if (ARGV(0) < 0) return E_2LOW;
        if (ARGV(0) > 6) return E_2HIGH;
        /* Convert 0=Sun to 0=Mon */
        ARGV(0)--;
        if (ARGV(0) < 0) ARGV(0) = 6;
        s = get_day_name(ARGV(0));
    } else s = get_day_name(DATEPART(ARG(0)) % 7);
    return RetStrVal(s, info);
}

static int FMon(func_info *info)
{
    char const *s;
    int y, m, d, v;

    if (!HASDATE(ARG(0)) && ARG(0).type != INT_TYPE && ARG(0).type != STR_TYPE) return E_BAD_TYPE;

    if (ARG(0).type == INT_TYPE || ARG(0).type == STR_TYPE) {
        m = GETMON(0);
        if (m < 0) return -m;
    } else {
        v = DATEPART(ARG(0));
        if (v == CacheDse)
            m = CacheMon;
        else {
            FromDSE(v, &y, &m, &d);
            CacheDse = v;
            CacheYear = y;
            CacheMon = m;
            CacheDay = d;
        }
    }
    s = get_month_name(m);
    return RetStrVal(s, info);
}

/***************************************************************/
/*                                                             */
/*  FHour - extract hour from a time                           */
/*  FMinute - extract minute from a time                       */
/*  FTime - create a time from hour and minute                 */
/*                                                             */
/***************************************************************/
static int FHour(func_info *info)
{
    int v;
    if (!HASTIME(ARG(0))) return E_BAD_TYPE;
    v = TIMEPART(ARG(0));
    RetVal.type = INT_TYPE;
    RETVAL = v / 60;
    return OK;
}

static int FMinute(func_info *info)
{
    int v;
    if (!HASTIME(ARG(0))) return E_BAD_TYPE;
    v = TIMEPART(ARG(0));
    RetVal.type = INT_TYPE;
    RETVAL = v % 60;
    return OK;
}

static int FTime(func_info *info)
{
    int h, m;

    if (ARG(0).type != INT_TYPE || ARG(1).type != INT_TYPE) return E_BAD_TYPE;

    h = ARGV(0);
    m = ARGV(1);
    if (h<0 || m<0) return E_2LOW;
    if (h>23 || m>59) return E_2HIGH;
    RetVal.type = TIME_TYPE;
    RETVAL = h*60 + m;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FAbs - absolute value                                      */
/*  FSgn - signum function                                     */
/*                                                             */
/***************************************************************/
static int FAbs(func_info *info)
{
    volatile int v;

    ASSERT_TYPE(0, INT_TYPE);
    v = ARGV(0);
    if (v == INT_MIN) return E_2HIGH;

    RetVal.type = INT_TYPE;
    RETVAL = (v < 0) ? (-v) : v;
    v = RETVAL;

    /* The following test is probably redundant given the test
       for v == INT_MIN above, but I'll leave it in just in case. */
    if (v < 0) return E_2HIGH;
    return OK;
}

static int FSgn(func_info *info)
{
    int v;

    ASSERT_TYPE(0, INT_TYPE);
    v = ARGV(0);
    RetVal.type = INT_TYPE;
    if (v>0) RETVAL = 1;
    else if (v<0) RETVAL = -1;
    else RETVAL = 0;
    return OK;
}

static int parse_color_helper(char const *str, int *r, int *g, int *b)
{
    if (!*str) {
        /* Empty string means "reset to normal" */
        *r = -1;
        *g = -1;
        *b = -1;
        return OK;
    }
    if (sscanf(str, "%d %d %d", r, g, b) != 3) {
        return E_BAD_TYPE;
    }
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FAnsicolor - return an ANSI terminal color sequence        */
/*                                                             */
/***************************************************************/
static int FAnsicolor(func_info *info)
{
    int r=0, g=0, b=0, bg=0, clamp=1;
    int status;
    int index = 0;
    bg = 0;
    clamp = 1;

    /* If first arg is a string: Parse out the colors */
    if (ARG(0).type == STR_TYPE) {
        /* If first arg is a string: Parse out the colors */
        status = parse_color_helper(ARGSTR(0), &r, &g, &b);
        if (status != 0) {
            return status;
        }
        index = 1;
    } else if (ARG(0).type == INT_TYPE) {
        /* Must be at least three arguments */
        if (Nargs < 3) return E_2FEW_ARGS;
        ASSERT_TYPE(1, INT_TYPE);
        ASSERT_TYPE(2, INT_TYPE);
        r = ARGV(0);
        g = ARGV(1);
        b = ARGV(2);
        index = 3;
    }
    if (r < -1 || g < -1 || b < -1) return E_2LOW;
    if (r > 255 || g > 255 || b > 255) return E_2HIGH;
    /* If any is -1, then all must be -1 */
    if (r == -1 || g == -1 || b == -1) {
        if (r != -1 || g != -1 || b != -1) {
            return E_2LOW;
        }
    }
    if (Nargs > index) {
        ASSERT_TYPE(index, INT_TYPE);
        if (ARGV(index) < 0) return E_2LOW;
        if (ARGV(index) > 1) return E_2HIGH;
        bg = ARGV(index);
        index++;
        if (Nargs > index) {
            ASSERT_TYPE(index, INT_TYPE);
            if (ARGV(index) < 0) return E_2LOW;
            if (ARGV(index) > 1) return E_2HIGH;
            clamp = ARGV(index);
        }
    }

    /* All righ!  We have our parameters; now return the string */
    if (!UseVTColors) {
        /* Not using any colors: Empty strin */
        return RetStrVal("", info);
    }

    if (r < 0) {
        /* Return ANSI "reset to normal" string */
        return RetStrVal(Decolorize(), info);
    }
    return RetStrVal(Colorize(r, g, b, bg, clamp), info);
}

/***************************************************************/
/*                                                             */
/*  FAmpm - return a time as a string with "AM" or "PM" suffix */
/*                                                             */
/***************************************************************/
static int FAmpm(func_info *info)
{
    int h, m;
    int yr=0, mo=0, da=0;

    char const *am = "AM";
    char const *pm = "PM";
    char const *ampm = NULL;

    int include_leading_zero = 0;

    char outbuf[128];

    if (ARG(0).type != DATETIME_TYPE && ARG(0).type != TIME_TYPE) {
        return E_BAD_TYPE;
    }
    if (HASDATE(ARG(0))) {
        FromDSE(DATEPART(ARG(0)), &yr, &mo, &da);
    }
    if (Nargs >= 2) {
        ASSERT_TYPE(1, STR_TYPE);
        am = ARGSTR(1);
        if (Nargs >= 3) {
            ASSERT_TYPE(2, STR_TYPE);
            pm = ARGSTR(2);
            if (Nargs >= 4) {
                ASSERT_TYPE(3, INT_TYPE);
                include_leading_zero = ARGV(3);
            }
        }
    }
    h = TIMEPART(ARG(0)) / 60;
    m = TIMEPART(ARG(0)) % 60;
    if (h <= 11) {
        /* AM */
        if (h == 0) {
            if (ARG(0).type == DATETIME_TYPE) {
                snprintf(outbuf, sizeof(outbuf), "%04d%c%02d%c%02d%c12%c%02d", yr, DateSep, mo+1, DateSep, da, DateTimeSep, TimeSep, m);
            } else {
                snprintf(outbuf, sizeof(outbuf), "12%c%02d", TimeSep, m);
            }
        } else {
            if (ARG(0).type == DATETIME_TYPE) {
                if (include_leading_zero) {
                    snprintf(outbuf, sizeof(outbuf), "%04d%c%02d%c%02d%c%02d%c%02d", yr, DateSep, mo+1, DateSep, da, DateTimeSep, h, TimeSep, m);
                } else {
                    snprintf(outbuf, sizeof(outbuf), "%04d%c%02d%c%02d%c%d%c%02d", yr, DateSep, mo+1, DateSep, da, DateTimeSep, h, TimeSep, m);
                }
            } else {
                if (include_leading_zero) {
                    snprintf(outbuf, sizeof(outbuf), "%02d%c%02d", h, TimeSep, m);
                } else {
                    snprintf(outbuf, sizeof(outbuf), "%d%c%02d", h, TimeSep, m);
                }
            }
        }
        ampm = am;
    } else {
        if (h > 12) {
            h -= 12;
        }
        if (ARG(0).type == DATETIME_TYPE) {
            if (include_leading_zero) {
                snprintf(outbuf, sizeof(outbuf), "%04d%c%02d%c%02d%c%02d%c%02d", yr, DateSep, mo+1, DateSep, da, DateTimeSep, h, TimeSep, m);
            } else {
                snprintf(outbuf, sizeof(outbuf), "%04d%c%02d%c%02d%c%d%c%02d", yr, DateSep, mo+1, DateSep, da, DateTimeSep, h, TimeSep, m);
            }
        } else {
            if (include_leading_zero) {
                snprintf(outbuf, sizeof(outbuf), "%02d%c%02d", h, TimeSep, m);
            } else {
                snprintf(outbuf, sizeof(outbuf), "%d%c%02d", h, TimeSep, m);
            }
        }
        ampm = pm;
    }
    RetVal.type = STR_TYPE;
    RetVal.v.str = malloc(strlen(outbuf) + strlen(ampm) + 1);
    if (!RetVal.v.str) {
        RetVal.type = ERR_TYPE;
        return E_NO_MEM;
    }
    strcpy(RetVal.v.str, outbuf);
    strcat(RetVal.v.str, ampm);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FOrd - returns a string containing ordinal number.         */
/*                                                             */
/*  EG - ord(2) == "2nd", etc.                                 */
/*                                                             */
/***************************************************************/
static int FOrd(func_info *info)
{
    static int in_ford = 0;
    int t, u, v;
    char const *s;

    char buf[32];

    ASSERT_TYPE(0, INT_TYPE);

    if (!in_ford && UserFuncExists("ordx") == 1) {
        /* Can't use EvalExprRunDisabled here because we need
           the nonconst result */
        expr_node *n;
        int r;
        char const *e = buf;
        Value val;
        int nonconst;
        int old_rundisabled;

        val.type = ERR_TYPE;
        snprintf(buf, sizeof(buf), "ordx(%d)", ARGV(0));
        n = parse_expression(&e, &r, NULL);
        if (r != OK) {
            return r;
        }
        in_ford = 1;
        old_rundisabled = RunDisabled;
        RunDisabled |= RUN_CB;
        r = evaluate_expr_node(n, NULL, &val, &nonconst);
        RunDisabled = old_rundisabled;
        in_ford = 0;
        free_expr_tree(n);
        if (r != OK) {
            return r;
        }
        if (nonconst) info->nonconst = nonconst;
        r = DoCoerce(STR_TYPE, &val);
        if (r != OK) {
            DestroyValue(val);
            return r;
        }
        DCOPYVAL(RetVal, val);
        return OK;
    }

    v = ARGV(0);
    if (v < 0) {
        t = (-v) % 100;
    } else {
        t = v % 100;
    }
    u = t % 10;
    s = "th";
    if (u == 1 && t != 11) s = "st";
    if (u == 2 && t != 12) s = "nd";
    if (u == 3 && t != 13) s = "rd";
    snprintf(buf, sizeof(buf), "%d%s", v, s);
    return RetStrVal(buf, info);
}

/***************************************************************/
/*                                                             */
/*  FPad - Pad a string to min length                          */
/*                                                             */
/*  pad("1", "0", 4) --> "0001"                                */
/*  pad("1", "0", 4, 1) --> "1000"                             */
/*  pad("foo", "bar", 7) -> "barbfoo"                          */
/*                                                             */
/***************************************************************/
static int FPad(func_info *info)
{
    int r;
    char const *s;
    DynamicBuffer dbuf;
    size_t len;
    size_t wantlen;
    size_t i;

    ASSERT_TYPE(1, STR_TYPE);
    ASSERT_TYPE(2, INT_TYPE);
    if (Nargs == 4) {
        ASSERT_TYPE(3, INT_TYPE);
    }

    if (ARG(0).type != STR_TYPE) {
        r = DoCoerce(STR_TYPE, &ARG(0));
        if (r != OK) return r;
    }

    wantlen = ARGV(2);
    len = strlen(ARGSTR(0));
    if (len >= wantlen) {
        DCOPYVAL(RetVal, ARG(0));
        return OK;
    }

    if (strlen(ARGSTR(1)) == 0) {
        return E_BAD_TYPE;
    }

    if (MaxStringLen > 0 && wantlen > (size_t) MaxStringLen) {
        return E_STRING_TOO_LONG;
    }

    DBufInit(&dbuf);
    s = ARGSTR(1);
    if (Nargs < 4 || !ARGV(3)) {
        /* Pad on the LEFT */
        for (i=0; i<wantlen-len; i++) {
            if (DBufPutc(&dbuf, *s++) != OK) {
                DBufFree(&dbuf);
                return E_NO_MEM;
            }
            if (!*s) s = ARGSTR(1);
        }
        if (DBufPuts(&dbuf, ARGSTR(0)) != OK) {
                DBufFree(&dbuf);
                return E_NO_MEM;
        }
    } else {
        /* Pad on the RIGHT */
        if (DBufPuts(&dbuf, ARGSTR(0)) != OK) {
            DBufFree(&dbuf);
            return E_NO_MEM;
        }
        for (i=0; i<wantlen-len; i++) {
            if (DBufPutc(&dbuf, *s++) != OK) {
                DBufFree(&dbuf);
                return E_NO_MEM;
            }
            if (!*s) s = ARGSTR(1);
        }
    }
    r = RetStrVal(DBufValue(&dbuf), info);
    DBufFree(&dbuf);
    return r;
}

/***************************************************************/
/*                                                             */
/*  FMbpad - multibyte version of Fpad                         */
/*                                                             */
/***************************************************************/
static int FMbpad(func_info *info)
{
    int r;
    wchar_t *s;
    wchar_t *d;

    size_t len;
    size_t len2;
    size_t wantlen;
    size_t i;

    wchar_t *src;
    wchar_t *pad;
    wchar_t *dest;

    char *result;
    ASSERT_TYPE(1, STR_TYPE);
    ASSERT_TYPE(2, INT_TYPE);
    if (Nargs == 4) {
        ASSERT_TYPE(3, INT_TYPE);
    }

    if (ARG(0).type != STR_TYPE) {
        r = DoCoerce(STR_TYPE, &ARG(0));
        if (r != OK) return r;
    }

    wantlen = ARGV(2);

    /* Convert ARGV(0) and ARGV(1) to wide-char strings */
    len = mbstowcs(NULL, ARGSTR(0), 0);
    if (len == (size_t) -1) {
        return E_BAD_MB_SEQ;
    }

    if (len >= wantlen) {
        DCOPYVAL(RetVal, ARG(0));
        return OK;
    }

    if (strlen(ARGSTR(1)) == 0) {
        return E_BAD_TYPE;
    }

    if (MaxStringLen > 0 && wantlen > (size_t) MaxStringLen) {
        return E_STRING_TOO_LONG;
    }

    src = calloc(len+1, sizeof(wchar_t));
    if (!src) {
        return E_NO_MEM;
    }
    (void) mbstowcs(src, ARGSTR(0), len+1);
    len2 = mbstowcs(NULL, ARGSTR(1), 0);
    if (len2 == (size_t) -1) {
        free(src);
        return E_BAD_MB_SEQ;
    }
    pad = calloc(len2+1, sizeof(wchar_t));
    if (!pad) {
        free(src);
        return E_NO_MEM;
    }
    (void) mbstowcs(pad, ARGSTR(1), len2+1);

    dest = calloc(wantlen+1, sizeof(wchar_t));
    if (!dest) {
        free(src);
        free(pad);
        return E_NO_MEM;
    }

    d = dest;
    if (Nargs < 4 || !ARGV(3)) {
        /* Pad on the LEFT */
        s = pad;
        for (i=0; i<wantlen-len; i++) {
            *d++ = *s++;
            if (!*s) s = pad;
        }
        s = src;
        while (*s) {
            *d++ = *s++;
        }
    } else {
        s = src;
        while (*s) {
            *d++ = *s++;
        }
        s = pad;
        for (i=0; i<wantlen-len; i++) {
            *d++ = *s++;
            if (!*s) s = pad;
        }
    }

    len = wcstombs(NULL, dest, 0);
    result = calloc(len+1, 1);
    if (!result) {
        free(src);
        free(pad);
        free(dest);
        return E_NO_MEM;
    }
    (void) wcstombs(result, dest, len+1);

    r = RetStrVal(result, info);
    free(result);
    free(src);
    free(pad);
    free(dest);
    return r;
}


/***************************************************************/
/*                                                             */
/*  FPlural - pluralization function                           */
/*                                                             */
/*  plural(n) -->  "" or "s"                                   */
/*  plural(n, str) --> "str" or "strs"                         */
/*  plural(n, str1, str2) --> "str1" or "str2"                 */
/*                                                             */
/***************************************************************/
static int FPlural(func_info *info)
{
    ASSERT_TYPE(0, INT_TYPE);

    switch(Nargs) {
    case 1:
        if (ARGV(0) == 1) return RetStrVal("", info);
        else return RetStrVal("s", info);

    case 2:
        ASSERT_TYPE(1, STR_TYPE);
        if (ARGV(0) == 1) {
            DCOPYVAL(RetVal, ARG(1));
            return OK;
        }
        RetVal.type = STR_TYPE;
        RetVal.v.str = malloc(strlen(ARGSTR(1))+2);
        if (!RetVal.v.str) {
            RetVal.type = ERR_TYPE;
            return E_NO_MEM;
        }
        strcpy(RetVal.v.str, ARGSTR(1));
        strcat(RetVal.v.str, "s");
        return OK;

    default:
        if (ARG(1).type != STR_TYPE || ARG(2).type != STR_TYPE)
            return E_BAD_TYPE;
        if (ARGV(0) == 1) DCOPYVAL(RetVal, ARG(1));
        else DCOPYVAL(RetVal, ARG(2));
        return OK;
    }
}

/***************************************************************/
/*                                                             */
/*  FIsconst                                                   */
/*  Return 1 if the first arg is constant; 0 otherwise         */
/*                                                             */
/***************************************************************/
static int FIsconst(expr_node *node, Value *locals, Value *ans, int *nonconst)
{
    Value junk;
    int my_nonconst;
    DynamicBuffer DebugBuf;
    int r;

    UNUSED(nonconst);
    DBG(DBufInit(&DebugBuf));
    DBG(PUT("isconst("));

    my_nonconst = 0;
    r = evaluate_expr_node(node->child, locals, &junk, &my_nonconst);
    if (r != OK) {
        DBG(DBufFree(&DebugBuf));
        return r;
    }
    ans->type = INT_TYPE;
    ans->v.val = (my_nonconst ? 0 : 1);
    DBG(PUT(PrintValue(&junk, NULL)));
    if (DBGX) {
        PUT(") => ");
        PUT(PrintValue(ans, NULL));
        OUT();
    }
    DestroyValue(junk);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FIsAny                                                     */
/*  Return 1 if the first arg equals any subsequent arg, 0     */
/*  otherwise.                                                 */
/*                                                             */
/***************************************************************/
static int FIsAny(expr_node *node, Value *locals, Value *ans, int *nonconst)
{
    DynamicBuffer DebugBuf;
    expr_node *cur;
    int r;

    Value v;
    Value candidate;

    ans->type = INT_TYPE;
    ans->v.val = 0;

    DBG(DBufInit(&DebugBuf));
    DBG(PUT("isany("));

    cur = node->child;
    r = evaluate_expr_node(cur, locals, &v, nonconst);
    if (r != OK) {
        DBG(DBufFree(&DebugBuf));
        return r;
    }
    DBG(PUT(PrintValue(&v, NULL)));
    while(cur->sibling) {
        cur = cur->sibling;
        r = evaluate_expr_node(cur, locals, &candidate, nonconst);
        if (r != OK) {
            DestroyValue(v);
            DBG(DBufFree(&DebugBuf));
            return r;
        }
        DBG(PUT(", "));
        DBG(PUT(PrintValue(&candidate, NULL)));
        if (candidate.type != v.type) {
            DestroyValue(candidate);
            continue;
        }
        if (v.type == STR_TYPE) {
            if (strcmp(v.v.str, candidate.v.str)) {
                DestroyValue(candidate);
                continue;
            }
        } else {
            if (v.v.val != candidate.v.val) {
                DestroyValue(candidate);
                continue;
            }
        }
        DestroyValue(candidate);
        ans->v.val = 1;
        break;
    }
    DestroyValue(v);
    if (DBGX) {
        while(cur->sibling) {
            cur = cur->sibling;
            PUT(", ?");
        }
        PUT(") => ");
        PUT(PrintValue(ans, NULL));
        OUT();
    }
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FCatch                                                     */
/*  Evaluate the first argument.  If no error occurs, return   */
/*  the result.  If an error occurs, return the valie of the   */
/*  second argument.                                           */
/*                                                             */
/***************************************************************/
static int FCatch(expr_node *node, Value *locals, Value *ans, int *nonconst)
{
    DynamicBuffer DebugBuf;
    expr_node *cur;
    int r;
    int old_suppress;

    DBG(DBufInit(&DebugBuf));
    DBG(PUT("catch("));
    cur = node->child;

    old_suppress = SuppressErrorOutputInCatch;
    SuppressErrorOutputInCatch = 1;
    r = evaluate_expr_node(cur, locals, ans, nonconst);
    SuppressErrorOutputInCatch = old_suppress;

    if (r == OK) {
        if (DBGX) {
            PUT(PrintValue(ans, NULL));
            PUT(", ?) => ");
            PUT(PrintValue(ans, NULL));
            OUT();
        }
        return r;
    }

    /* Save the catch error */
    LastCatchError = r;
    if (DBGX) {
        PUT("*");
        PUT(GetErr(r));
        PUT("*, ");
    }
    r = evaluate_expr_node(cur->sibling, locals, ans, nonconst);
    if (r == OK) {
        if (DBGX) {
            PUT(PrintValue(ans, NULL));
            PUT(") => ");
            PUT(PrintValue(ans, NULL));
            OUT();
        }
        return r;
    }
    if (DBGX) {
        PUT("*");
        PUT(GetErr(r));
        PUT("*) => ");
        PUT(GetErr(r));
        OUT();
    }
    return r;
}

/***************************************************************/
/*                                                             */
/*  FCatchErr                                                  */
/*  Return (as a string) the English error thrown by the last  */
/*  catch() expression that errored out.                       */
/*                                                             */
/***************************************************************/
static int FCatchErr(func_info *info)
{
    return RetStrVal(GetEnglishErr(LastCatchError), info);
}

/***************************************************************/
/*                                                             */
/*  FChoose                                                    */
/*  Choose the nth value from a list of value.  If n<1, choose */
/*  first.  If n>N, choose Nth value.  Indexes always start    */
/*  from 1.                                                    */
/*                                                             */
/***************************************************************/
static int FChoose(expr_node *node, Value *locals, Value *ans, int *nonconst)
{
    DynamicBuffer DebugBuf;
    expr_node *cur;
    int r;
    int n;
    int nargs = node->num_kids;
    Value v;
    DBG(DBufInit(&DebugBuf));
    DBG(PUT("choose("));

    cur = node->child;
    r = evaluate_expr_node(cur, locals, &v, nonconst);
    if (r != OK) {
        DBG(DBufFree(&DebugBuf));
        return r;
    }
    DBG(PUT(PrintValue(&v, NULL)));
    if (v.type != INT_TYPE) {
        if (DBGX) {
            cur = cur->sibling;
            while(cur) {
                PUT(", ?");
                cur = cur->sibling;
            }
            PUT(") => ");
            PUT(GetErr(E_BAD_TYPE));
            OUT();
        }
        DestroyValue(v);
        Eprint("choose(): %s", GetErr(E_BAD_TYPE));
        return E_BAD_TYPE;
    }
    n = v.v.val;
    if (n < 1) n = 1;
    if (n > nargs-1) n = nargs-1;

    while(n--) {
        cur = cur->sibling;
        DBG(if (n) { PUT(", ?"); });
        if (!cur) return E_SWERR; /* Should not happen! */
    }
    r = evaluate_expr_node(cur, locals, ans, nonconst);
    if (r != OK) {
        DBG(DBufFree(&DebugBuf));
        return r;
    }
    if (DBGX) {
        PUT(", ");
        PUT(PrintValue(ans, NULL));
        cur = cur->sibling;
        while(cur) {
            PUT(", ?");
            cur = cur->sibling;
        }
        PUT(") => ");
        PUT(PrintValue(ans, NULL));
        OUT();
    }
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FVersion - version of Remind                               */
/*                                                             */
/***************************************************************/
static int FVersion(func_info *info)
{
    return RetStrVal(VERSION, info);
}

/***************************************************************/
/*                                                             */
/*  FOstype - the type of operating system                     */
/*  (UNIX, OS/2, or MSDOS)                                     */
/*                                                             */
/***************************************************************/
static int FOstype(func_info *info)
{
    return RetStrVal("UNIX", info);
}

/***************************************************************/
/*                                                             */
/*  FShellescape - escape shell meta-characters                */
/*                                                             */
/***************************************************************/
static int FShellescape(func_info *info)
{
    DynamicBuffer buf;
    int r;

    ASSERT_TYPE(0, STR_TYPE);
    DBufInit (&buf);
    if (ShellEscape(ARG(0).v.str, &buf) != OK) {
        DBufFree(&buf);
        return E_NO_MEM;
    }

    r = RetStrVal(DBufValue(&buf), info);
    DBufFree(&buf);
    return r;
}

/***************************************************************/
/*                                                             */
/*  FUpper - convert string to upper-case                      */
/*  FLower - convert string to lower-case                      */
/*                                                             */
/***************************************************************/
static int FUpper(func_info *info)
{
    char *s;

    ASSERT_TYPE(0, STR_TYPE);
    DCOPYVAL(RetVal, ARG(0));
    s = RetVal.v.str;
    while (*s) {
        *s = UPPER(*s);
        s++;
    }
    return OK;
}

static int FLower(func_info *info)
{
    char *s;

    ASSERT_TYPE(0, STR_TYPE);
    DCOPYVAL(RetVal, ARG(0));
    s = RetVal.v.str;
    while (*s) {
        *s = LOWER(*s);
        s++;
    }
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FStdout - return the type of file descriptor for stdout    */
/*                                                             */
/***************************************************************/
static int FStdout(func_info *info)
{
    struct stat statbuf;
    int r;

    if (isatty(STDOUT_FILENO)) {
        return RetStrVal("TTY", info);
    }
    if (fstat(STDOUT_FILENO, &statbuf) < 0) {
        return RetStrVal("UNKNOWN", info);
    }
    switch(statbuf.st_mode & S_IFMT) {
    case S_IFBLK:  r = RetStrVal("BLOCKDEV", info); break;
    case S_IFCHR:  r = RetStrVal("CHARDEV", info);  break;
    case S_IFDIR:  r = RetStrVal("DIR",info);       break;
    case S_IFIFO:  r = RetStrVal("PIPE",info);      break;
    case S_IFLNK:  r = RetStrVal("SYMLINK", info);  break;
    case S_IFREG:  r = RetStrVal("FILE",info);      break;
    case S_IFSOCK: r = RetStrVal("SOCKET", info);   break;
    default:       r = RetStrVal("UNKNOWN", info);  break;
    }
    return r;
}

/***************************************************************/
/*                                                             */
/*  FToday - return the system's notion of "today"             */
/*  Frealtoday - return today's date as read from OS.          */
/*  FNow - return the system time (or time on cmd line.)       */
/*  FRealnow - return the true system time                     */
/*                                                             */
/***************************************************************/
static int FToday(func_info *info)
{
    RetVal.type = DATE_TYPE;
    RETVAL = DSEToday;
    return OK;
}

static int FRealtoday(func_info *info)
{
    RetVal.type = DATE_TYPE;
    RETVAL = RealToday;
    return OK;
}

static int FNow(func_info *info)
{
    RetVal.type = TIME_TYPE;
    RETVAL = MinutesPastMidnight(0);
    return OK;
}

static int FRealnow(func_info *info)
{
    RetVal.type = TIME_TYPE;
    RETVAL = MinutesPastMidnight(1);
    return OK;
}

static int FCurrent(func_info *info)
{
    RetVal.type = DATETIME_TYPE;
    RETVAL = SystemDateTime(0);
    return OK;
}

static int FRealCurrent(func_info *info)
{
    RetVal.type = DATETIME_TYPE;
    RETVAL = SystemDateTime(1);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FGetenv - get the value of an environment variable.        */
/*                                                             */
/***************************************************************/
static int FGetenv(func_info *info)
{
    ASSERT_TYPE(0, STR_TYPE);
    return RetStrVal(getenv(ARGSTR(0)), info);
}

/***************************************************************/
/*                                                             */
/*  FValue                                                     */
/*                                                             */
/*  Get the value of a variable.  If a second arg is supplied, */
/*  it is returned if variable is undefined.                   */
/*                                                             */
/***************************************************************/
static int FValue(expr_node *node, Value *locals, Value *ans, int *nonconst)
{
    DynamicBuffer DebugBuf;
    expr_node *cur;
    int r;
    Value varname;
    Var *v;

    DBG(DBufInit(&DebugBuf));

    cur = node->child;
    r = evaluate_expr_node(cur, locals, &varname, nonconst);
    if (r != OK) {
        DBG(DBufFree(&DebugBuf));
        return r;
    }

    DBG(PUT("value("));
    DBG(PUT(PrintValue(&varname, NULL)));
    if (node->num_kids == 1) {
        DBG(PUT(") => "));
    } else {
        DBG(PUT(", "));
    }
    if (varname.type != STR_TYPE) {
        if (DBGX) {
            if (node->num_kids == 2) {
                PUT("?) => ");
            }
            PUT(GetErr(E_BAD_TYPE));
            OUT();
        }
        DestroyValue(varname);
        return E_BAD_TYPE;
    }

    v = FindVar(varname.v.str, 0);
    if (!v) {
        r = E_NOSUCH_VAR;
    } else {
        r = OK;
        v->used_since_set = 1;
        CopyValue(ans, &v->v);
        if (!v->is_constant) {
            nonconst_debug(*nonconst, tr("Global variable `%s' makes expression non-constant"), varname.v.str);
            *nonconst = 1;
        }
    }
    if (r == OK || node->num_kids == 1) {
        DestroyValue(varname);
        if (DBGX) {
            if (node->num_kids == 2) {
                PUT("?) => ");
            }
            if (r != OK) {
                PUT(GetErr(r));
            } else {
                PUT(PrintValue(ans, NULL));
            }
            OUT();
        }
        return r;
    }

    /* Variable does not exist.  We have to mark the result of value()
       as non-constant because the nonexistence of a variable may depend
       on today's date (for example) */
    nonconst_debug(*nonconst, tr("Nonexistence of global variable `%s' makes value() non-constant"), varname.v.str);
    DestroyValue(varname);
    *nonconst = 1;
    r = evaluate_expr_node(cur->sibling, locals, ans, nonconst);
    if (DBGX) {
        if (node->num_kids == 2) {
            if (r != OK) {
                PUT(GetErr(r));
                PUT(") => ");
                PUT(GetErr(r));
            } else {
                PUT(PrintValue(ans, NULL));
                PUT(") => ");
                PUT(PrintValue(ans, NULL));
            }
            OUT();
        }
    }
    return r;
}

/***************************************************************/
/*                                                             */
/*  FDefined                                                   */
/*                                                             */
/*  Return 1 if a variable is defined, 0 if it is not.         */
/*                                                             */
/***************************************************************/
static int FDefined(func_info *info)
{
    ASSERT_TYPE(0, STR_TYPE);

    RetVal.type = INT_TYPE;

    if (FindVar(ARGSTR(0), 0))
        RETVAL = 1;
    else
        RETVAL = 0;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FTrigdate and FTrigtime                                    */
/*                                                             */
/*  Date and time of last trigger.  These are stored in global */
/*  vars.                                                      */
/*                                                             */
/***************************************************************/
static int FTrigdate(func_info *info)
{
    if (LastTrigValid) {
        RetVal.type = DATE_TYPE;
        RETVAL = LastTriggerDate;
    } else {
        RetVal.type = INT_TYPE;
        RETVAL = 0;
    }
    return OK;
}

static int FTrigbase(func_info *info)
{
    if (LastTrigger.d != NO_DAY &&
        LastTrigger.m != NO_MON &&
        LastTrigger.y != NO_YR) {
        RetVal.type = DATE_TYPE;
        RETVAL = DSE(LastTrigger.y, LastTrigger.m, LastTrigger.d);
    } else {
        RetVal.type = INT_TYPE;
        RETVAL = 0;
    }
    return OK;
}

static int FTrigback(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTrigger.back;
    return OK;
}

static int FTrigcompletethrough(func_info *info)
{
    if (!LastTrigger.is_todo || LastTrigger.complete_through == NO_DATE) {
        RetVal.type = INT_TYPE;
        RETVAL = 0;
        return OK;
    }
    RetVal.type = DATE_TYPE;
    RETVAL = LastTrigger.complete_through;
    return OK;
}

static int FTrigistodo(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTrigger.is_todo;
    return OK;
}

static int FTrigmaxoverdue(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTrigger.max_overdue;
    return OK;
}

static int FTrigdelta(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTrigger.delta;
    return OK;
}

static int FTrigtimedelta(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTimeTrig.delta;
    return OK;
}

static int FTrigtimerep(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTimeTrig.rep;
    return OK;
}

static int FTrigtz(func_info *info)
{
    if (!LastTrigger.tz) {
        return RetStrVal("", info);
    }
    return RetStrVal(LastTrigger.tz, info);
}

static int FTrigeventduration(func_info *info)
{
    if (LastTrigger.eventduration == NO_TIME) {
        RetVal.type = INT_TYPE;
        RETVAL = -1;
    } else {
        RetVal.type = TIME_TYPE;
        RETVAL = LastTrigger.eventduration;
    }
    return OK;
}

static int FTriginfo(func_info *info)
{
    char const *s;
    ASSERT_TYPE(0, STR_TYPE);
    s = FindTrigInfo(&LastTrigger, ARGSTR(0));
    if (!s) {
        return RetStrVal("", info);
    }
    return RetStrVal(s, info);
}

static int FTrigeventstart(func_info *info)
{
    if (LastTrigger.eventstart == NO_TIME) {
        RetVal.type = INT_TYPE;
        RETVAL = -1;
    } else {
        RetVal.type = DATETIME_TYPE;
        RETVAL = LastTrigger.eventstart;
    }
    return OK;
}

static int FTrigeventstarttz(func_info *info)
{
    if (LastTrigger.eventstart == NO_TIME) {
        RetVal.type = INT_TYPE;
        RETVAL = -1;
    } else {
        RetVal.type = DATETIME_TYPE;
        if (LastTrigger.eventstart_orig != NO_TIME) {
            RETVAL = LastTrigger.eventstart_orig;
        } else {
            RETVAL = LastTrigger.eventstart;
        }
    }
    return OK;
}

static int FTrigduration(func_info *info)
{
    if (LastTimeTrig.duration == NO_TIME) {
        RetVal.type = INT_TYPE;
        RETVAL = -1;
    } else {
        RetVal.type = TIME_TYPE;
        RETVAL = LastTimeTrig.duration;
    }
    return OK;
}

static int FTrigrep(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTrigger.rep;
    return OK;
}

static int FTrigtags(func_info *info)
{
    return RetStrVal(DBufValue(&(LastTrigger.tags)), info);
}

static int FTrigpriority(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTrigger.priority;
    return OK;
}

static int FTriguntil(func_info *info)
{
    if (LastTrigger.until == NO_UNTIL) {
        RetVal.type = INT_TYPE;
        RETVAL = -1;
    } else {
        RetVal.type = DATE_TYPE;
        RETVAL = LastTrigger.until;
    }
    return OK;
}

static int FTrigscanfrom(func_info *info)
{
    if (get_scanfrom(&LastTrigger) == NO_DATE) {
        RetVal.type = INT_TYPE;
        RETVAL = -1;
    } else {
        RetVal.type = DATE_TYPE;
        RETVAL = get_scanfrom(&LastTrigger);
    }
    return OK;
}

static int FTrigfrom(func_info *info)
{
    if (LastTrigger.from == NO_DATE) {
        RetVal.type = INT_TYPE;
        RETVAL = -1;
    } else {
        RetVal.type = DATE_TYPE;
        RETVAL = LastTrigger.from;
    }
    return OK;
}

static int FTrigvalid(func_info *info)
{
    RetVal.type = INT_TYPE;
    RETVAL = LastTrigValid;
    return OK;
}

static int FTrigtime(func_info *info)
{
    if (LastTriggerTime != NO_TIME) {
        RetVal.type = TIME_TYPE;
        RETVAL = LastTriggerTime;
    } else {
        RetVal.type = INT_TYPE;
        RETVAL = 0;
    }
    return OK;
}

static int FTrigtimetz(func_info *info)
{
    if (LastTriggerTime != NO_TIME) {
        RetVal.type = TIME_TYPE;
        if (LastTimeTrig.ttime_orig != NO_TIME) {
            RETVAL = LastTimeTrig.ttime_orig;
        } else {
            RETVAL = LastTriggerTime;
        }
    } else {
        RetVal.type = INT_TYPE;
        RETVAL = 0;
    }
    return OK;
}

static int FTrigdatetime(func_info *info)
{
    if (!LastTrigValid) {
        RetVal.type = INT_TYPE;
        RETVAL = 0;
    } else if (LastTriggerTime != NO_TIME) {
        RetVal.type = DATETIME_TYPE;
        RETVAL = LastTriggerDate * MINUTES_PER_DAY + LastTriggerTime;
    } else {
        RetVal.type = DATE_TYPE;
        RETVAL = LastTriggerDate;
    }
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FDaysinmon                                                 */
/*                                                             */
/*  Returns the number of days in mon,yr OR month containing   */
/*  given date.                                                */
/*                                                             */
/***************************************************************/
static int FDaysinmon(func_info *info)
{
    int y, m;

    if (Nargs == 1) {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        FromDSE(DATEPART(ARG(0)), &y, &m, NULL);
    } else {
        if (ARG(1).type != INT_TYPE) return E_BAD_TYPE;

        m = GETMON(0);
        if (m < 0) return -m;
        y = ARGV(1);
        if (y < BASE) return E_2LOW;
        if (y > BASE + YR_RANGE) return E_2HIGH;
    }

    RetVal.type = INT_TYPE;
    RETVAL = DaysInMonth(m, y);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FIsleap                                                    */
/*                                                             */
/*  Return 1 if year is a leap year, zero otherwise.           */
/*                                                             */
/***************************************************************/
static int FIsleap(func_info *info)
{
    int y;

    if (ARG(0).type != INT_TYPE && !HASDATE(ARG(0))) return E_BAD_TYPE;

    /* If it's a date, extract the year */
    if (HASDATE(ARG(0)))
        FromDSE(DATEPART(ARG(0)), &y, NULL, NULL);
    else
        y = ARGV(0);

    RetVal.type = INT_TYPE;
    RETVAL = IsLeapYear(y);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FTrigger                                                   */
/*                                                             */
/*  Put out a date in a format suitable for triggering.        */
/*                                                             */
/***************************************************************/
static int FTrigger(func_info *info)
{
    int y, m, d;
    int date, tim;
    char buf[128];

    tim = NO_TIME;
    if (ARG(0).type != DATE_TYPE &&
        ARG(0).type != DATETIME_TYPE) return E_BAD_TYPE;

    if (ARG(0).type == DATE_TYPE) {
        date = ARGV(0);
    } else {
        date = ARGV(0) / MINUTES_PER_DAY;
        tim = ARGV(0) % MINUTES_PER_DAY;
    }

    if (ARG(0).type == DATE_TYPE) {
        if (Nargs > 2) {
            /* Date Time UTCFlag */
            if (ARG(0).type == DATETIME_TYPE) return E_BAD_TYPE;
            ASSERT_TYPE(2, INT_TYPE);
            ASSERT_TYPE(1, TIME_TYPE);
            tim = ARGV(1);
            if (ARGV(2)) {
                UTCToLocal(date, tim, &date, &tim);
		if (date < 0) {
		  date = 0;
		  tim = 0;
		}
            }
        } else if (Nargs > 1) {
            /* Date Time */
            ASSERT_TYPE(1, TIME_TYPE);
            tim = ARGV(1);
        }
    } else {
        if (Nargs > 2) {
            return E_2MANY_ARGS;
        } else if (Nargs > 1) {
            /* DateTime UTCFlag */
            ASSERT_TYPE(1, INT_TYPE);
            if (ARGV(1)) {
                UTCToLocal(date, tim, &date, &tim);
		if (date < 0) {
		  date = 0;
		  tim = 0;
		}
            }
        }
    }

    FromDSE(date, &y, &m, &d);
    if (tim != NO_TIME) {
        snprintf(buf, sizeof(buf), "%d %s %d AT %02d:%02d", d, MonthName[m], y,
                tim/60, tim%60);
    } else {
        snprintf(buf, sizeof(buf), "%d %s %d", d, MonthName[m], y);
    }
    return RetStrVal(buf, info);
}

/***************************************************************/
/*                                                             */
/*  FShell                                                     */
/*                                                             */
/*  The shell function.                                        */
/*                                                             */
/*  If run is disabled, will not be executed.                  */
/*                                                             */
/***************************************************************/
static int FShell(func_info *info)
{
    DynamicBuffer buf;
    int ch, r;
    FILE *fp;

    /* For compatibility with previous versions of Remind, which
       used a static buffer for reading results from shell() command */
    int maxlen = 511;

    /* Redirect stdin to /dev/null */
    int stdin_dup;
    int devnull;

    DBufInit(&buf);
    if (RunDisabled) return E_RUN_DISABLED;
    ASSERT_TYPE(0, STR_TYPE);
    if (Nargs >= 2) {
        ASSERT_TYPE(1, INT_TYPE);
        maxlen = ARGV(1);
    }

    /* Don't allow maxlen to exceed the maximum length of
       a string variable */
    if (MaxStringLen > 0) {
        if (maxlen <= 0 || maxlen > MaxStringLen) {
            maxlen = MaxStringLen;
        }
    }

    stdin_dup = dup(STDIN_FILENO);
    if (stdin_dup >= 0) {
        devnull = open("/dev/null", O_RDONLY);
        if (devnull >= 0) {
            if (dup2(devnull, STDIN_FILENO) >= 0) {
                set_cloexec(stdin_dup);
            } else {
                (void) close(stdin_dup);
                stdin_dup = -1;
            }
            (void) close(devnull);
        }
    }

    fp = popen(ARGSTR(0), "r");
    if (!fp) {
        if (stdin_dup >= 0) {
            (void) dup2(stdin_dup, STDIN_FILENO);
            (void) close(stdin_dup);
        }
        return E_IO_ERR;
    }
    while (1) {
        ch = getc(fp);
        if (ch == EOF) {
            break;
        }
        if (isspace(ch)) ch = ' ';
        if (DBufPutc(&buf, (char) ch) != OK) {
            pclose(fp);
            if (stdin_dup >= 0) {
                (void) dup2(stdin_dup, STDIN_FILENO);
                (void) close(stdin_dup);
            }
            DBufFree(&buf);
            return E_NO_MEM;
        }
        if (maxlen > 0 && DBufLen(&buf) >= (size_t) maxlen) {
            break;
        }
    }

    /* Delete trailing newline (converted to space) */
    if (DBufLen(&buf) && DBufValue(&buf)[DBufLen(&buf)-1] == ' ') {
        DBufValue(&buf)[DBufLen(&buf)-1] = 0;
    }

    /* XXX Should we consume remaining output from cmd? */

    pclose(fp);
    if (stdin_dup >= 0) {
        (void) dup2(stdin_dup, STDIN_FILENO);
        (void) close(stdin_dup);
    }
    r = RetStrVal(DBufValue(&buf), info);
    DBufFree(&buf);
    return r;
}

/***************************************************************/
/*                                                             */
/*  FIsomitted                                                 */
/*                                                             */
/*  Is a date omitted?                                         */
/*                                                             */
/***************************************************************/
static int FIsomitted(func_info *info)
{
    int r;
    if (!HASDATE(ARG(0))) return E_BAD_TYPE;

    RetVal.type = INT_TYPE;
    r = IsOmitted(DATEPART(ARG(0)), 0, NULL, &RETVAL);
    return r;
}

/***************************************************************/
/*                                                             */
/*  FSubstr                                                    */
/*                                                             */
/*  The substr function.  We destroy the value on the stack.   */
/*                                                             */
/***************************************************************/
static int FSubstr(func_info *info)
{
    char *s;
    char const *t;
    int start, end;

    if (ARG(0).type != STR_TYPE || ARG(1).type != INT_TYPE) return E_BAD_TYPE;
    if (Nargs == 3 && ARG(2).type != INT_TYPE) return E_BAD_TYPE;

    s = ARGSTR(0);
    start = 1;
    while (start < ARGV(1)) {
        if (!*s) break;
        s++;
        start++;
    }
    if (Nargs == 2 || !*s) return RetStrVal(s, info);
    end = start;
    t = s;
    while (end <= ARGV(2)) {
        if (!*s) break;
        s++;
        end++;
    }
    *s = 0;
    return RetStrVal(t, info);
}

/***************************************************************/
/*                                                             */
/*  FMbubstr                                                   */
/*                                                             */
/*  The mbsubstr function.                                     */
/*                                                             */
/***************************************************************/
static int FMbsubstr(func_info *info)
{
    wchar_t *str;
    wchar_t *s;
    wchar_t const *t;
    size_t mblen;
    char *converted;
    size_t len;
    int start;
    int end;

    if (ARG(0).type != STR_TYPE || ARG(1).type != INT_TYPE) return E_BAD_TYPE;
    if (Nargs == 3 && ARG(2).type != INT_TYPE) return E_BAD_TYPE;

    mblen = mbstowcs(NULL, ARGSTR(0), 0);
    if (mblen == (size_t) -1) {
        return E_BAD_MB_SEQ;
    }
    str = calloc(mblen+1, sizeof(wchar_t));
    if (!str) {
        return E_NO_MEM;
    }
    (void) mbstowcs(str, ARGSTR(0), mblen+1);
    s = str;
    start = 1;
    while (start < ARGV(1)) {
        if (!*s) break;
        s++;
        start++;
    }
    t = s;
    if (Nargs >= 3) {
        end = start;
        while (end <= ARGV(2)) {
            if (!*s) break;
            s++;
            end++;
        }
        *s = (wchar_t) 0;
    }

    len = wcstombs(NULL, t, 0);
    if (len == (size_t) -1) {
        free( (void *) str);
        return E_BAD_MB_SEQ;
    }
    converted = malloc(len+1);
    if (!converted) {
        free( (void *) str);
        return E_NO_MEM;
    }
    (void) wcstombs(converted, t, len+1);
    RetVal.type = STR_TYPE;
    RetVal.v.str = converted;
    free( (void *) str);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FIndex                                                     */
/*                                                             */
/*  The index of one string embedded in another.               */
/*                                                             */
/***************************************************************/
static int FIndex(func_info *info)
{
    char const *s;
    int start;

    if (ARG(0).type != STR_TYPE || ARG(1).type != STR_TYPE ||
        (Nargs == 3 && ARG(2).type != INT_TYPE)) return E_BAD_TYPE;

    s = ARGSTR(0);

/* If 3 args, bump up the start */
    if (Nargs == 3) {
        start = 1;
        while (start < ARGV(2)) {
            if (!*s) break;
            s++;
            start++;
        }
    }

/* Find the string */
    s = strstr(s, ARGSTR(1));
    RetVal.type = INT_TYPE;
    if (!s) {
        RETVAL = 0;
        return OK;
    }
    RETVAL = (s - ARGSTR(0)) + 1;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FMbindex                                                   */
/*                                                             */
/*  The wide-char of one string embedded in another.           */
/*                                                             */
/***************************************************************/
static int FMbindex(func_info *info)
{
    wchar_t *haystack;
    wchar_t *needle;
    wchar_t const *s;
    size_t haylen, needlelen;

    if (ARG(0).type != STR_TYPE || ARG(1).type != STR_TYPE ||
        (Nargs == 3 && ARG(2).type != INT_TYPE)) return E_BAD_TYPE;

    haylen = mbstowcs(NULL, ARGSTR(0), INT_MAX);
    if (haylen == (size_t) -1) {
        return E_BAD_MB_SEQ;
    }
    haystack = calloc(haylen+1, sizeof(wchar_t));
    if (!haystack) {
        return E_NO_MEM;
    }
    (void) mbstowcs(haystack, ARGSTR(0), haylen+1);
    needlelen = mbstowcs(NULL, ARGSTR(1), INT_MAX);
    if (needlelen == (size_t) -1) {
        free( (void *) haystack);
        return E_BAD_MB_SEQ;
    }
    needle = calloc(needlelen+1, sizeof(wchar_t));
    if (!needle) {
        free( (void *) haystack);
        return E_NO_MEM;
    }
    (void) mbstowcs(needle, ARGSTR(1), needlelen+1);
    s = haystack;

/* If 3 args, bump up the start */
    if (Nargs == 3) {
        if (ARGV(2) > (int) haylen) {
            s += haylen;
        } else {
            s += ARGV(2) - 1;
        }
    }

/* Find the string */
    RetVal.type = INT_TYPE;
    s = wcsstr(s, needle);
    if (!s) {
        free( (void *) haystack);
        free( (void *) needle);
        RETVAL = 0;
        return OK;
    }
    RETVAL = s - haystack + 1;
    free( (void *) haystack);
    free( (void *) needle);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FIif                                                       */
/*                                                             */
/*  The IIF function.  Uses new-style evaluation               */
/*                                                             */
/***************************************************************/
static int FIif(expr_node *node, Value *locals, Value *ans, int *nonconst)
{
    int r;
    int done;
    Value v;
    expr_node *cur;
    DynamicBuffer DebugBuf;

    DBG(DBufInit(&DebugBuf));
    DBG(PUT("iif("));
    cur = node->child;

    if (!(node->num_kids % 2)) {
        if (DBGX) {
            r = 0;
            while(cur) {
                if (r) PUT(", ");
                r=1;
                PUT("?");
                cur = cur->sibling;
            }
            PUT(") => ");
            PUT(GetErr(E_IIF_ODD));
            OUT();
        }
        return E_IIF_ODD;
    }


    done = 0;
    while(cur->sibling) {
        r = evaluate_expr_node(cur, locals, &v, nonconst);
        if (r != OK) {
            DBG(DBufFree(&DebugBuf));
            return r;
        }
        if (DBGX) {
            if (done) PUT(", ");
            done = 1;
            PUT(PrintValue(&v, NULL));
        }

        if (truthy(&v)) {
            DestroyValue(v);
            r = evaluate_expr_node(cur->sibling, locals, ans, nonconst);
            if (r == OK && DBGX) {
                PUT(", ");
                PUT(PrintValue(ans, NULL));
                cur = cur->sibling->sibling;
                while(cur) {
                    PUT(", ?");
                    cur = cur->sibling;
                }
                PUT(") => ");
                PUT(PrintValue(ans, NULL));
                OUT();
            }
            DBG(DBufFree(&DebugBuf));
            return r;
        } else {
            DestroyValue(v);
        }
        DBG(PUT(", ?"));
        cur = cur->sibling->sibling;
    }

    /* Return the last arg */
    r = evaluate_expr_node(cur, locals, ans, nonconst);
    if (DBGX) {
        if (done) PUT(", ");
        PUT(PrintValue(ans, NULL));
        PUT(") => ");
        PUT(PrintValue(ans, NULL));
        OUT();
    }
    return r;
}

/***************************************************************/
/*                                                             */
/*  FFilename                                                  */
/*                                                             */
/*  Return name of current file                                */
/*                                                             */
/***************************************************************/
static int FFilename(func_info *info)
{
    return RetStrVal(GetCurrentFilename(), info);
}

/***************************************************************/
/*                                                             */
/*  FFiledir                                                   */
/*                                                             */
/*  Return directory of current file                           */
/*                                                             */
/***************************************************************/
static int FFiledir(func_info *info)
{
    char *s;
    DynamicBuffer buf;
    int r;

    DBufInit(&buf);

    if (DBufPuts(&buf, GetCurrentFilename()) != OK) return E_NO_MEM;
    if (DBufLen(&buf) == 0) {
        DBufFree(&buf);
        return RetStrVal(".", info);
    }

    s = DBufValue(&buf) + DBufLen(&buf) - 1;
    while (s > DBufValue(&buf) && *s != '/') s--;
    if (*s == '/') {
        *s = 0;
        r = RetStrVal(DBufValue(&buf), info);
    } else r = RetStrVal(".", info);
    DBufFree(&buf);
    return r;
}
/***************************************************************/
/*                                                             */
/*  FAccess                                                    */
/*                                                             */
/*  The UNIX access() system call.                             */
/*                                                             */
/***************************************************************/
static int FAccess(func_info *info)
{
    int amode;
    char const *s;

    if (ARG(0).type != STR_TYPE ||
        (ARG(1).type != INT_TYPE && ARG(1).type != STR_TYPE)) return E_BAD_TYPE;

    if (ARG(1).type == INT_TYPE) amode = ARGV(1);
    else {
        amode = 0;
        s = ARGSTR(1);
        while (*s) {
            switch(*s++) {
            case 'r':
            case 'R': amode |= R_OK; break;
            case 'w':
            case 'W': amode |= W_OK; break;
            case 'x':
            case 'X': amode |= X_OK; break;
            }
        }
    }
    RetVal.type = INT_TYPE;
    RETVAL = access(ARGSTR(0), amode);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FTypeof                                                    */
/*                                                             */
/*  Implement the typeof() function.                           */
/*                                                             */
/***************************************************************/
static int FTypeof(func_info *info)
{
    switch(ARG(0).type) {
    case INT_TYPE:      return RetStrVal("INT", info);
    case DATE_TYPE:     return RetStrVal("DATE", info);
    case TIME_TYPE:     return RetStrVal("TIME", info);
    case STR_TYPE:      return RetStrVal("STRING", info);
    case DATETIME_TYPE: return RetStrVal("DATETIME", info);
    default:            return RetStrVal("ERR", info);
    }
}

/***************************************************************/
/*                                                             */
/*  FLanguage                                                  */
/*                                                             */
/*  Implement the language() function.                         */
/*                                                             */
/***************************************************************/
static int FLanguage(func_info *info)
{
    return RetStrVal("English", info);
}

/***************************************************************/
/*                                                             */
/*  FArgs                                                      */
/*                                                             */
/*  Implement the args() function.                             */
/*                                                             */
/***************************************************************/
static int FArgs(func_info *info)
{
    ASSERT_TYPE(0, STR_TYPE);
    RetVal.type = INT_TYPE;
    strtolower(ARGSTR(0));
    RETVAL = UserFuncExists(ARGSTR(0));
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FDosubst                                                   */
/*                                                             */
/*  Implement the dosubst() function.                          */
/*                                                             */
/***************************************************************/
static int FDosubst(func_info *info)
{
    int dse, tim, r;
    DynamicBuffer buf;

    DBufInit(&buf);

    dse = NO_DATE;
    tim = NO_TIME;
    ASSERT_TYPE(0, STR_TYPE);
    if (Nargs >= 2) {
        if (ARG(1).type == DATETIME_TYPE) {
            dse = DATEPART(ARG(1));
            tim = TIMEPART(ARG(1));
        } else {
            ASSERT_TYPE(1, DATE_TYPE);
            dse = ARGV(1);
        }
        if (Nargs >= 3) {
            if (ARG(1).type == DATETIME_TYPE) {
                return E_2MANY_ARGS;
            }
            ASSERT_TYPE(2, TIME_TYPE);
            tim = ARGV(2);
        }
    }

    if ((r=DoSubstFromString(ARGSTR(0), &buf, dse, tim))) return r;
    r = RetStrVal(DBufValue(&buf), info);
    DBufFree(&buf);
    return r;
}

/***************************************************************/
/*                                                             */
/*  FHebdate                                                   */
/*  FHebday                                                    */
/*  FHebmon                                                    */
/*  FHebyear                                                   */
/*                                                             */
/*  Hebrew calendar support functions                          */
/*                                                             */
/***************************************************************/
static int FHebdate(func_info *info)
{
    int year, day, mon, jahr;
    int mout, dout;
    int ans, r;
    int adarbehave;

    if (ARG(0).type != INT_TYPE || ARG(1).type != STR_TYPE) return E_BAD_TYPE;
    day = ARGV(0);
    mon = HebNameToNum(ARGSTR(1));
    if (mon < 0) return E_BAD_HEBDATE;
    if (Nargs == 2) {
        r = GetNextHebrewDate(DSEToday, mon, day, 0, 0, &ans);
        if (r) return r;
        RetVal.type = DATE_TYPE;
        RETVAL = ans;
        return OK;
    }
    if (Nargs == 5) {
        ASSERT_TYPE(4, INT_TYPE);
        adarbehave = ARGV(4);
        if (adarbehave < 0) return E_2LOW;
        if (adarbehave > 2) return E_2HIGH;
    } else adarbehave = 0;

    if (Nargs >= 4) {
        ASSERT_TYPE(3, INT_TYPE);
        jahr = ARGV(3);
        if (jahr < 0) return E_2LOW;
        if (jahr > 2) {
            r = ComputeJahr(jahr, mon, day, &jahr);
            if (r) return r;
        }
    } else jahr = 0;


    if (ARG(2).type == INT_TYPE) {
        year = ARGV(2);
        r = GetValidHebDate(year, mon, day, 0, &mout, &dout, jahr);
        if (r) return r;
        r = HebToDSE(year, mout, dout);
        if (r<0) return E_DATE_OVER;
        RETVAL = r;
        RetVal.type = DATE_TYPE;
        return OK;
    } else if (HASDATE(ARG(2))) {
        r = GetNextHebrewDate(DATEPART(ARG(2)), mon, day, jahr, adarbehave, &ans);
        if (r) return r;
        RETVAL = ans;
        RetVal.type = DATE_TYPE;
        return OK;
    } else return E_BAD_TYPE;
}

static int FHebday(func_info *info)
{
    int y, m, d, v;

    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    v = DATEPART(ARG(0));
    if (v == CacheHebDse)
        d = CacheHebDay;
    else {
        DSEToHeb(v, &y, &m, &d);
        CacheHebDse = v;
        CacheHebYear = y;
        CacheHebMon = m;
        CacheHebDay = d;
    }
    RetVal.type = INT_TYPE;
    RETVAL = d;
    return OK;
}

static int FHebmon(func_info *info)
{
    int y, m, d, v;

    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    v = DATEPART(ARG(0));

    if (v == CacheHebDse) {
        m = CacheHebMon;
        y = CacheHebYear;
    } else {
        DSEToHeb(v, &y, &m, &d);
        CacheHebDse = v;
        CacheHebYear = y;
        CacheHebMon = m;
        CacheHebDay = d;
    }
    return RetStrVal(HebMonthName(m, y), info);
}

static int FIvritmon(func_info *info)
{
    int y, m, d, v;

    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    v = DATEPART(ARG(0));

    if (v == CacheHebDse) {
        m = CacheHebMon;
        y = CacheHebYear;
    } else {
        DSEToHeb(v, &y, &m, &d);
        CacheHebDse = v;
        CacheHebYear = y;
        CacheHebMon = m;
        CacheHebDay = d;
    }
    return RetStrVal(IvritMonthName(m, y), info);
}

static int FHebyear(func_info *info)
{
    int y, m, d, v;

    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    v = DATEPART(ARG(0));

    if (v == CacheHebDse)
        y = CacheHebYear;
    else {
        DSEToHeb(v, &y, &m, &d);
        CacheHebDse = v;
        CacheHebYear = y;
        CacheHebMon = m;
        CacheHebDay = d;
    }
    RetVal.type = INT_TYPE;
    RETVAL = y;
    return OK;
}

/****************************************************************/
/*                                                              */
/* escape - escape special characters with "\xx" sequences      */
/*                                                              */
/****************************************************************/
static int FEscape(func_info *info)
{
    DynamicBuffer dbuf;
    char const *s;
    char hexbuf[16];
    int r;
    int include_quotes = 0;
    ASSERT_TYPE(0, STR_TYPE);
    if (Nargs >= 2) {
        ASSERT_TYPE(1, INT_TYPE);
        include_quotes = ARGV(1);
    }

    DBufInit(&dbuf);
    if (include_quotes) {
        r = DBufPutc(&dbuf, '"');
        if (r != OK) {
            DBufFree(&dbuf);
            return r;
        }
    }
    s = ARGSTR(0);
    while(*s) {
        switch(*s) {
        case '\a':
            r = DBufPuts(&dbuf, "\\a");
            break;
        case '\b':
            r = DBufPuts(&dbuf, "\\b");
            break;
        case '\f':
            r = DBufPuts(&dbuf, "\\f");
            break;
        case '\n':
            r = DBufPuts(&dbuf, "\\n");
            break;
        case '\r':
            r = DBufPuts(&dbuf, "\\r");
            break;
        case '\t':
            r = DBufPuts(&dbuf, "\\t");
            break;
        case '\v':
            r = DBufPuts(&dbuf, "\\v");
            break;
        case '\\':
            r = DBufPuts(&dbuf, "\\\\");
            break;
        case '"':
            r = DBufPuts(&dbuf, "\\\"");
            break;
        default:
            if ((*s > 0 && *s < ' ') || *s == 0x7f) {
                snprintf(hexbuf, sizeof(hexbuf), "\\x%02x", (unsigned int) *s);
                r = DBufPuts(&dbuf, hexbuf);
            } else {
                r = DBufPutc(&dbuf, *s);
            }
            break;
        }
        if (r != OK) {
            DBufFree(&dbuf);
            return r;
        }
        s++;
    }
    if (include_quotes) {
        r = DBufPutc(&dbuf, '"');
        if (r != OK) {
            DBufFree(&dbuf);
            return r;
        }
    }
    r = RetStrVal(DBufValue(&dbuf), info);
    DBufFree(&dbuf);
    return r;
}

/****************************************************************/
/*                                                              */
/* htmlescape - replace <. > and & by &lt; &gt; and &amp;       */
/*                                                              */
/****************************************************************/
static int FHtmlEscape(func_info *info)
{
    DynamicBuffer dbuf;
    char const *s;
    int r;

    ASSERT_TYPE(0, STR_TYPE);

    DBufInit(&dbuf);

    s = ARGSTR(0);
    while(*s) {
        switch(*s) {
        case '<':
            r = DBufPuts(&dbuf, "&lt;");
            break;

        case '>':
            r = DBufPuts(&dbuf, "&gt;");
            break;

        case '&':
            r = DBufPuts(&dbuf, "&amp;");
            break;

        default:
            r = DBufPutc(&dbuf, *s);
            break;
        }
        if (r != OK) {
            DBufFree(&dbuf);
            return r;
        }
        s++;
    }
    r = RetStrVal(DBufValue(&dbuf), info);
    DBufFree(&dbuf);
    return r;
}

/****************************************************************/
/*                                                              */
/* htmlstriptags - strip out HTML tags from a string            */
/*                                                              */
/****************************************************************/
static int FHtmlStriptags(func_info *info)
{
    DynamicBuffer dbuf;
    char const *s;
    int r = OK;

    int in_tag = 0;
    ASSERT_TYPE(0, STR_TYPE);

    DBufInit(&dbuf);

    s = ARGSTR(0);
    while(*s) {
        if (!in_tag) {
            if (*s == '<') {
                in_tag = 1;
            } else {
                r = DBufPutc(&dbuf, *s);
            }
        } else {
            if (*s == '>') {
                in_tag = 0;
            }
        }
        if (r != OK) {
            DBufFree(&dbuf);
            return r;
        }
        s++;
    }
    r = RetStrVal(DBufValue(&dbuf), info);
    DBufFree(&dbuf);
    return r;
}

/****************************************************************/
/*                                                              */
/*  FEasterdate - calc. easter Sunday from a year.              */
/*                                                              */
/*    from The Art of Computer Programming Vol 1.               */
/*            Fundamental Algorithms                            */
/*    by Donald Knuth.                                          */
/*                                                              */
/* Donated by Michael Salmon - thanks!                          */
/*                                                              */
/* I haven't examined this in detail, but I *think* int         */
/* arithmetic is fine, even on 16-bit machines.                 */
/*                                                              */
/****************************************************************/
static int FEasterdate(func_info *info)
{
    int y, m, d;
    int g, c, x, z, e, n;
    int base;
    if (Nargs == 0) {
        base = DSEToday;
        FromDSE(DSEToday, &y, NULL, NULL);
    } else {
        if (ARG(0).type == INT_TYPE) {
            base = -1;
            y = ARGV(0);
            if (y < BASE) return E_2LOW;
            else if (y > BASE+YR_RANGE) return E_2HIGH;
        } else if (HASDATE(ARG(0))) {
            base = DATEPART(ARG(0));
            FromDSE(DATEPART(ARG(0)), &y, NULL, NULL);  /* We just want the year */
        } else return E_BAD_TYPE;
    }

    do {
        g = (y % 19) + 1;  /* golden number */
        c = (y / 100) + 1; /* century */
        x = (3 * c)/4 - 12;        /* correction for non-leap year centuries */
        z = (8 * c + 5)/25 - 5;    /* special constant for moon sync */
        d = (5 * y)/4 - x - 10;    /* find sunday */
        e = (11 * g + 20 + z - x) % 30;    /* calc epact */
        if ( e < 0 ) e += 30;
        if ( e == 24 || (e == 25 && g > 11)) e++;
        n = 44 - e;                        /* find full moon */
        if ( n < 21 ) n += 30;     /* after 21st */
        d = n + 7 - (d + n)%7;     /* calc sunday after */
        if (d <= 31) m = 2;
        else
        {
            d = d - 31;
            m = 3;
        }

        RetVal.type = DATE_TYPE;
        RETVAL = DSE(y, m, d);
        y++; } while (base > -1 && RETVAL < base);

    return OK;
}

/****************************************************************/
/*                                                              */
/*  FOrthodoxeaster - calc. Orthodox easter Sunday              */
/*                                                              */
/*  From Meeus, Astronomical Algorithms                         */
/*                                                              */
/****************************************************************/
static int FOrthodoxeaster(func_info *info)
{
    int y, m, d;
    int a, b, c, dd, e, f, dse;
    int base = -1;
    if (Nargs == 0) {
        base = DSEToday;
        FromDSE(DSEToday, &y, NULL, NULL);
    } else {
        if (ARG(0).type == INT_TYPE) {
            y = ARGV(0);
            if (y < BASE) return E_2LOW;
            else if (y > BASE+YR_RANGE) return E_2HIGH;
        } else if (HASDATE(ARG(0))) {
            base = DATEPART(ARG(0));
            FromDSE(DATEPART(ARG(0)), &y, NULL, NULL);  /* We just want the year */
        } else return E_BAD_TYPE;
    }

    do {
        a = y % 4;
        b = y % 7;
        c = y % 19;
        dd = (19 * c + 15) % 30;
        e = (2*a + 4*b - dd + 34) % 7;
        f = dd + e + 114;
        m = (f / 31) - 1;
        d = (f % 31) + 1;

        dse = DSE(y, m, d);
        dse += JulianToGregorianOffset(y, m);
        RetVal.type = DATE_TYPE;
        RETVAL = dse;
        y++;
    } while (base > -1 && RETVAL < base);

    return OK;
}
/***************************************************************/
/*                                                             */
/*  FIsdst and FMinsfromutc                                    */
/*                                                             */
/*  Check whether daylight saving time is in effect, and      */
/*  get minutes from UTC.                                      */
/*                                                             */
/***************************************************************/
static int FTimeStuff (int wantmins, func_info *info);
static int FIsdst(func_info *info)
{
    return FTimeStuff(0, info);
}

static int FMinsfromutc(func_info *info)
{
    return FTimeStuff(1, info);
}

static int FTimeStuff(int wantmins, func_info *info)
{
    int dse, tim;
    int mins, dst;

    dse = DSEToday;
    tim = 0;

    if (Nargs >= 1) {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        dse = DATEPART(ARG(0));
        if (HASTIME(ARG(0))) {
            tim = TIMEPART(ARG(0));
        }
        if (Nargs >= 2) {
            if (HASTIME(ARG(0))) return E_2MANY_ARGS;
            ASSERT_TYPE(1, TIME_TYPE);
            tim = ARGV(1);
        }
    }

    if (CalcMinsFromUTC(dse, tim, &mins, &dst)) {
        return E_MKTIME_PROBLEM;
    }
    RetVal.type = INT_TYPE;
    if (wantmins) RETVAL = mins; else RETVAL = dst;

    return OK;
}

static int FTimezone(func_info *info)
{
    int yr, mon, day, hr, min, dse, now;
    struct tm local;
    struct tm const * withzone;
    time_t t;
    char buf[64];

    if (Nargs == 0) {
        dse = DSEToday;
        now = MinutesPastMidnight(0);
    } else {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        dse = DATEPART(ARG(0));
        if (HASTIME(ARG(0))) {
            now = TIMEPART(ARG(0));
        } else {
            now = 0;
        }
    }
    FromDSE(dse, &yr, &mon, &day);
    hr = now / 60;
    min = now % 60;

    memset(&local, 0, sizeof(local));
    local.tm_sec = 0;
    local.tm_min = min;
    local.tm_hour = hr;
    local.tm_mday = day;
    local.tm_mon = mon;
    local.tm_year = yr-1900;
    local.tm_isdst = -1;

    t = mktime(&local);
    withzone = localtime(&t);
    buf[0] = 0;
    strftime(buf, sizeof(buf), "%Z", withzone);
    return RetStrVal(buf, info);
}

static int FLocalToUTC(func_info *info)
{
    int yr, mon, day, hr, min, dse;
    time_t loc_t;
    struct tm local, *utc;

    int fold_year = -1;
    int wkday, isleap;

    ASSERT_TYPE(0, DATETIME_TYPE);

    FromDSE(DATEPART(ARG(0)), &yr, &mon, &day);
    hr = TIMEPART(ARG(0))/60;
    min = TIMEPART(ARG(0))%60;

    memset(&local, 0, sizeof(local));
    local.tm_sec = 0;
    local.tm_min = min;
    local.tm_hour = hr;
    local.tm_mday = day;
    local.tm_mon = mon;
    local.tm_year = yr-1900;
    local.tm_isdst = -1;
    loc_t = mktime(&local);
    if (loc_t == -1) {
        /* Try folding the year */
        wkday = DSE(yr, 0, 1) % 7;
        isleap = IsLeapYear(yr);
        fold_year = FoldArray[isleap][wkday];
        memset(&local, 0, sizeof(local));
        local.tm_sec = 0;
        local.tm_min = min;
        local.tm_hour = hr;
        local.tm_mday = day;
        local.tm_mon = mon;
        local.tm_year = fold_year-1900;
        local.tm_isdst = -1;
        loc_t = mktime(&local);
        if (loc_t == -1) {
            /* Still no joy */
            return E_MKTIME_PROBLEM;
        }
    }

    utc = gmtime(&loc_t);

    /* Unfold the year, if necessary */
    if (fold_year > 0) {
        utc->tm_year = yr + utc->tm_year - fold_year; /* The two 1900s cancel */
    }

    dse = DSE(utc->tm_year+1900, utc->tm_mon, utc->tm_mday);
    RetVal.type = DATETIME_TYPE;
    RETVAL = MINUTES_PER_DAY * dse + utc->tm_hour*60 + utc->tm_min;
    return OK;
}

static int UTCToLocalHelper(int datetime, int *ret)
{
    int yr, mon, day, hr, min, dse;
    time_t utc_t;
    struct tm *local, utc;
    char const *old_tz;
    int fold_year = -1;
    int isleap, wkday;

    FromDSE(datetime / MINUTES_PER_DAY, &yr, &mon, &day);
    hr =  (datetime % MINUTES_PER_DAY) / 60;
    min = (datetime % MINUTES_PER_DAY) % 60;

    old_tz = getenv("TZ");
    if (old_tz) {
        old_tz = strdup(old_tz);
        if (!old_tz) return E_NO_MEM;
    }

    tz_set_tz("UTC");

    memset(&utc, 0, sizeof(utc));
    utc.tm_sec = 0;
    utc.tm_min = min;
    utc.tm_hour = hr;
    utc.tm_mday = day;
    utc.tm_mon = mon;
    utc.tm_year = yr-1900;
    utc.tm_isdst = 0;
    utc_t = mktime(&utc);

    if (utc_t == -1) {
        /* Try folding the year */
        wkday = DSE(yr, 0, 1) % 7;
        isleap = IsLeapYear(yr);
        fold_year = FoldArray[isleap][wkday];
        memset(&utc, 0, sizeof(utc));
        utc.tm_sec = 0;
        utc.tm_min = min;
        utc.tm_hour = hr;
        utc.tm_mday = day;
        utc.tm_mon = mon;
        utc.tm_year = fold_year-1900;
        utc.tm_isdst = 0;
        utc_t = mktime(&utc);
    }
    tz_set_tz(old_tz);
    if (old_tz) {
        free( (void *) old_tz);
    }
    if (utc_t == -1) {
        return E_MKTIME_PROBLEM;
    }

    local = localtime(&utc_t);
    if (fold_year > 0) {
        local->tm_year = yr + local->tm_year - fold_year; /* The two 1900s cancel */
    }
    dse = DSE(local->tm_year+1900, local->tm_mon, local->tm_mday);
    *ret = MINUTES_PER_DAY * dse + local->tm_hour*60 + local->tm_min;
    return OK;
}

static int FUTCToLocal(func_info *info)
{

    int ret;
    int r;

    ASSERT_TYPE(0, DATETIME_TYPE);

    r = UTCToLocalHelper(ARGV(0), &ret);
    if (r != 0) {
        return r;
    }
    RetVal.type = DATETIME_TYPE;
    RETVAL = ret;
    return OK;
}

/***************************************************************/
/*                                                             */
/*  Sunrise and sunset functions.                              */
/*                                                             */
/*  Algorithm from "Almanac for computers for the year 1978"   */
/*  by L. E. Doggett, Nautical Almanac Office, USNO.           */
/*                                                             */
/*  This code also uses some ideas found in programs written   */
/*  by Michael Schwartz and Marc T. Kaufman.                   */
/*                                                             */
/***************************************************************/
#ifdef PI
#undef PI
#endif
#define PI 3.14159265358979323846
#define DEGRAD (PI/180.0)
#define RADDEG (180.0/PI)

static int SunStuff(int rise, double cosz, int dse)
{
    int mins, hours;
    int year, mon, day;

    double M, L, sinDelta, cosDelta, a, a_hr, cosH, t, H, T;
    double latitude, longdeg, UT, local;

/* Get offset from UTC */
    if (CalculateUTC) {
        if (CalcMinsFromUTC(dse, 12*60, &mins, NULL)) {
            Eprint(GetErr(E_MKTIME_PROBLEM));
            return NO_TIME;
        }
    } else mins = MinsFromUTC;

/* Get latitude and longitude */
    longdeg = -Longitude;
    latitude = DEGRAD * Latitude;

    FromDSE(dse, &year, &mon, &day);

/* Following formula on page B6 exactly... */
    t = (double) dse;
    if (rise) {
        t += (6.0 + longdeg/15.0) / 24.0;
    } else {
        t += (18.0 + longdeg/15.0) / 24.0;
    }

/* Mean anomaly of sun starting from 1 Jan 1990 */
/* NOTE: This assumes that BASE = 1990!!! */
#if BASE != 1990
#warning Sun calculations assume a BASE of 1990!
#endif
    t = 0.9856002585 * t;
    M = t + 357.828757; /* In degrees */

    /* Make sure M is in the range [0, 360) */
    M -= (floor(M/360.0) * 360.0);

/* Sun's true longitude */
    L = M + 1.916*sin(DEGRAD*M) + 0.02*sin(2*DEGRAD*M) + 283.07080214;
    if (L > 360.0) L -= 360.0;

/* Tan of sun's right ascension */
    a = RADDEG * atan2(0.91746*sin(DEGRAD*L), cos(DEGRAD*L));
    if (a<0) {
        a += 360.0;
    }

    a_hr = a / 15.0;

/* Sine of sun's declination */
    sinDelta = 0.39782 * sin(DEGRAD*L);
    cosDelta = sqrt(1 - sinDelta*sinDelta);

/* Cosine of sun's local hour angle */
    cosH = (cosz - sinDelta * sin(latitude)) / (cosDelta * cos(latitude));

    if (cosH < -1.0) { /* Summer -- permanent daylight */
        if (rise) return NO_TIME;
        else      return -NO_TIME;
    }
    if (cosH > 1.0) { /* Winter -- permanent darkness */
        if (rise) return -NO_TIME;
        else      return NO_TIME;
    }

    H = RADDEG * acos(cosH);
    if (rise) H = 360.0 - H;

    t -= 360.0*floor(t/360.0);
    T = (H-t) / 15.0 + a_hr - 6.726637276;

    if (T >= 24.0) T -= 24.0;
    else if (T < 0.0) T+= 24.0;

    UT = T + longdeg / 15.0;


    local = UT + (double) mins / 60.0;
    if (local < 0.0) local += 24.0;
    else if (local >= 24.0) local -= 24.0;

    /* Round off local time to nearest minute */
    local = floor(local * 60.0 + 0.5) / 60.0;

    hours = (int) local;
    mins = (int) ((local - hours) * 60.0);

    /* Sometimes, we get roundoff error.  Check for "reasonableness" of
       answer. */
    if (rise) {
        /* Sunrise so close to midnight it wrapped around -- permanent light */
        if (hours >= 23) return NO_TIME;
    } else {
        /* Sunset so close to midnight it wrapped around -- permanent light */
        if (hours <= 1) return -NO_TIME;
    }
    return hours*60 + mins;
}

/***************************************************************/
/*                                                             */
/*  Sunrise and Sunset functions.                              */
/*                                                             */
/***************************************************************/
static int FSun(int rise, func_info *info)
{
    int dse = DSEToday;
    /* Assignment below is not necessary, but it silences
       a GCC warning about a possibly-uninitialized variable */
    double cosz = 0.0;
    int r;

    /* Sun calculations assume BASE is 1990 */
    if (BASE != 1990) {
        return E_SWERR;
    }
    if (rise == 0 || rise == 1) {
    /* Sunrise and sunset : cos(90 degrees + 50 arcminutes) */
    cosz = -0.01454389765158243;
    } else if (rise == 2 || rise == 3) {
    /* Civil twilight: cos(96 degrees) */
        cosz = -0.10452846326765333;
    } else if (rise == 4 || rise == 5) {
    /* Nautical twilight: cos(102 degrees) */
        cosz = -0.20791169081775912;
    } else if (rise == 6 || rise == 7) {
    /* Astronomical twilight: cos(108 degrees) */
        cosz = -0.30901699437494734;
    }
    if (Nargs >= 1) {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        dse = DATEPART(ARG(0));
    }

    r = SunStuff(rise % 2, cosz, dse);
    if (r == NO_TIME) {
        RETVAL = 0;
        RetVal.type = INT_TYPE;
    } else if (r == -NO_TIME) {
        RETVAL = MINUTES_PER_DAY;
        RetVal.type = INT_TYPE;
    } else {
        RETVAL = r;
        RetVal.type = TIME_TYPE;
    }
    return OK;
}

static int FSunrise(func_info *info)
{
    return FSun(1, info);
}
static int FSunset(func_info *info)
{
    return FSun(0, info);
}

static int FDawn(func_info *info)
{
    return FSun(3, info);
}
static int FDusk(func_info *info)
{
    return FSun(2, info);
}

static int FNDawn(func_info *info)
{
    return FSun(5, info);
}
static int FNDusk(func_info *info)
{
    return FSun(4, info);
}

static int FADawn(func_info *info)
{
    return FSun(7, info);
}
static int FADusk(func_info *info)
{
    return FSun(6, info);
}

/***************************************************************/
/*                                                             */
/*  FFiledate                                                  */
/*                                                             */
/*  Return modification date of a file                         */
/*                                                             */
/***************************************************************/
static int FFiledate(func_info *info)
{
    struct stat statbuf;
    struct tm const *t1;

    RetVal.type = DATE_TYPE;

    ASSERT_TYPE(0, STR_TYPE);

    if (stat(ARGSTR(0), &statbuf)) {
        RETVAL = 0;
        return OK;
    }

    t1 = localtime(&(statbuf.st_mtime));

    if (t1->tm_year + 1900 < BASE)
        RETVAL=0;
    else
        RETVAL=DSE(t1->tm_year+1900, t1->tm_mon, t1->tm_mday);

    return OK;
}

/***************************************************************/
/*                                                             */
/*  FFiledatetime                                              */
/*                                                             */
/*  Return modification datetime of a file                     */
/*                                                             */
/***************************************************************/
static int FFiledatetime(func_info *info)
{
    struct stat statbuf;
    struct tm const *t1;

    RetVal.type = DATETIME_TYPE;

    ASSERT_TYPE(0, STR_TYPE);

    if (stat(ARGSTR(0), &statbuf)) {
        RETVAL = 0;
        return OK;
    }

    t1 = localtime(&(statbuf.st_mtime));

    if (t1->tm_year + 1900 < BASE)
        RETVAL=0;
    else
        RETVAL = MINUTES_PER_DAY * DSE(t1->tm_year+1900, t1->tm_mon, t1->tm_mday) + t1->tm_hour * 60 + t1->tm_min;

    return OK;
}

/***************************************************************/
/*                                                             */
/*  FPsshade                                                   */
/*                                                             */
/*  Canned PostScript code for shading a calendar square       */
/*                                                             */
/***************************************************************/
static int psshade_warned = 0;
static int FPsshade(func_info *info)
{
    char psbuff[256];
    char *s = psbuff;
    int i;
    size_t len = sizeof(psbuff);

    /* 1 or 3 args */
    if (Nargs != 1 && Nargs != 3) return E_2MANY_ARGS;

    for (i=0; i<Nargs; i++) {
        if (ARG(i).type != INT_TYPE) return E_BAD_TYPE;
        if (ARG(i).v.val < 0) return E_2LOW;
        if (ARG(i).v.val > 100) return E_2HIGH;
    }

    if (warning_level("03.01.02")) {
        if (!psshade_warned) {
            psshade_warned = 1;
            Wprint(tr("psshade() is deprecated; use SPECIAL SHADE instead."));
        }
    }

    snprintf(s, len, "/_A LineWidth 2 div def ");
    len -= strlen(s);
    s += strlen(s);
    snprintf(s, len, "_A _A moveto ");
    len -= strlen(s);
    s += strlen(s);
    snprintf(s, len, "BoxWidth _A sub _A lineto BoxWidth _A sub BoxHeight _A sub lineto ");
    len -= strlen(s);
    s += strlen(s);
    if (Nargs == 1) {
        snprintf(s, len, "_A BoxHeight _A sub lineto closepath %d 100 div setgray fill 0.0 setgray", ARGV(0));
    } else {
        snprintf(s, len, "_A BoxHeight _A sub lineto closepath %d 100 div %d 100 div %d 100 div setrgbcolor fill 0.0 setgray", ARGV(0), ARGV(1), ARGV(2));
    }
    return RetStrVal(psbuff, info);
}

/***************************************************************/
/*                                                             */
/*  FPsmoon                                                    */
/*                                                             */
/*  Canned PostScript code for generating moon phases          */
/*                                                             */
/***************************************************************/
static int psmoon_warned = 0;

static int FPsmoon(func_info *info)
{
    char psbuff[512];
    char sizebuf[30];
    char fontsizebuf[30];
    char *s = psbuff;
    char const *extra = NULL;
    int size = -1;
    int fontsize = -1;
    size_t len = sizeof(psbuff);

    ASSERT_TYPE(0, INT_TYPE);
    if (ARGV(0) < 0) return E_2LOW;
    if (ARGV(0) > 3) return E_2HIGH;
    if (Nargs > 1) {
        ASSERT_TYPE(1, INT_TYPE);
        if (ARGV(1) < -1) return E_2LOW;
        size = ARGV(1);
        if (Nargs > 2) {
            ASSERT_TYPE(2, STR_TYPE);
            extra = ARGSTR(2);
            if (Nargs > 3) {
                ASSERT_TYPE(3, INT_TYPE);
                if (ARGV(3) <= 0) return E_2LOW;
                fontsize = ARGV(3);
            }
        }
    }
    if (warning_level("03.01.02")) {
        if (!psmoon_warned) {
            psmoon_warned = 1;
            Wprint(tr("psmoon() is deprecated; use SPECIAL MOON instead."));
        }
    }
    if (size > 0) {
        snprintf(sizebuf, sizeof(sizebuf), "%d", size);
    } else {
        strcpy(sizebuf, "DaySize 2 div");
    }

    if (fontsize > 0) {
        snprintf(fontsizebuf, sizeof(fontsizebuf), "%d", fontsize);
    } else {
        strcpy(fontsizebuf, "EntrySize");
    }

    snprintf(s, len, "gsave 0 setgray newpath Border %s add BoxHeight Border sub %s sub",
            sizebuf, sizebuf);
    len -= strlen(s);
    s += strlen(s);
    snprintf(s, len, " %s 0 360 arc closepath", sizebuf);
    len -= strlen(s);
    s += strlen(s);
    switch(ARGV(0)) {
    case 0:
        snprintf(s, len, " fill");
        len -= strlen(s);
        s += strlen(s);
        break;

    case 2:
        snprintf(s, len, " stroke");
        len -= strlen(s);
        s += strlen(s);
        break;

    case 1:
        snprintf(s, len, " stroke");
        len -= strlen(s);
        s += strlen(s);
        snprintf(s, len, " newpath Border %s add BoxHeight Border sub %s sub",
                sizebuf, sizebuf);
        len -= strlen(s);
        s += strlen(s);
        snprintf(s, len, " %s 90 270 arc closepath fill", sizebuf);
        len -= strlen(s);
        s += strlen(s);
        break;

    default:
        snprintf(s, len, " stroke");
        len -= strlen(s);
        s += strlen(s);
        snprintf(s, len, " newpath Border %s add BoxHeight Border sub %s sub",
                sizebuf, sizebuf);
        len -= strlen(s);
        s += strlen(s);
        snprintf(s, len, " %s 270 90 arc closepath fill", sizebuf);
        len -= strlen(s);
        s += strlen(s);
        break;
    }
    if (extra) {
        snprintf(s, len, " Border %s add %s add Border add BoxHeight border sub %s sub %s sub moveto /EntryFont findfont %s scalefont setfont (%s) show",
                sizebuf, sizebuf, sizebuf, sizebuf, fontsizebuf, extra);
        len -= strlen(s);
        s += strlen(s);
    }

    snprintf(s, len, " grestore");
    return RetStrVal(psbuff, info);
}

/***************************************************************/
/*                                                             */
/*  FMoonphase                                                 */
/*                                                             */
/*  Phase of moon for specified date/time.                     */
/*                                                             */
/***************************************************************/
static int FMoonphase(func_info *info)
{
    int date, time;

    switch(Nargs) {
    case 0:
        date = DSEToday;
        time = 0;
        break;
    case 1:
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        date = DATEPART(ARG(0));
        if (HASTIME(ARG(0))) {
            time = TIMEPART(ARG(0));
        } else {
            time = 0;
        }
        break;
    case 2:
        if (ARG(0).type == DATETIME_TYPE) return E_2MANY_ARGS;
        if (ARG(0).type != DATE_TYPE && ARG(1).type != TIME_TYPE) return E_BAD_TYPE;
        date = ARGV(0);
        time = ARGV(1);
        break;

    default: return E_SWERR;
    }

    RetVal.type = INT_TYPE;
    RETVAL = MoonPhase(date, time);
    return OK;
}

/***************************************************************/
/*                                                             */
/*  FMoondate                                                  */
/*                                                             */
/*  Hunt for next occurrence of specified moon phase           */
/*                                                             */
/***************************************************************/
static int MoonStuff (int want_time, func_info *info);
static int FMoondate(func_info *info)
{
    return MoonStuff(DATE_TYPE, info);
}

static int FMoontime(func_info *info)
{
    return MoonStuff(TIME_TYPE, info);
}

static int FMoondatetime(func_info *info)
{
    return MoonStuff(DATETIME_TYPE, info);
}

static int FMoonrise(func_info *info)
{
    int start = DSEToday;
    if (Nargs >= 1) {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        start = DATEPART(ARG(0));
    }
    RetVal.type = DATETIME_TYPE;
    RETVAL = GetMoonrise(start);
    return OK;
}

static int FMoonset(func_info *info)
{
    int start = DSEToday;
    if (Nargs >= 1) {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        start = DATEPART(ARG(0));
    }
    RetVal.type = DATETIME_TYPE;
    RETVAL = GetMoonset(start);
    return OK;
}

static int FMoonrisedir(func_info *info)
{
    int start = DSEToday;
    if (Nargs >= 1) {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        start = DATEPART(ARG(0));
    }
    RetVal.type = INT_TYPE;
    RETVAL = GetMoonrise_angle(start);
    return OK;
}

static int FMoonsetdir(func_info *info)
{
    int start = DSEToday;
    if (Nargs >= 1) {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        start = DATEPART(ARG(0));
    }
    RetVal.type = INT_TYPE;
    RETVAL = GetMoonset_angle(start);
    return OK;
}

static int MoonStuff(int type_wanted, func_info *info)
{
    int startdate, starttim;
    int d, t;

    startdate = DSEToday;
    starttim = 0;

    ASSERT_TYPE(0, INT_TYPE);
    if (ARGV(0) < 0) return E_2LOW;
    if (ARGV(0) > 3) return E_2HIGH;
    if (Nargs >= 2) {
        if (!HASDATE(ARG(1))) return E_BAD_TYPE;
        startdate = DATEPART(ARG(1));
        if (HASTIME(ARG(1))) {
                starttim = TIMEPART(ARG(1));
        }

        if (Nargs >= 3) {
            if (HASTIME(ARG(1))) return E_2MANY_ARGS;
            ASSERT_TYPE(2, TIME_TYPE);
            starttim = ARGV(2);
        }
    }

    HuntPhase(startdate, starttim, ARGV(0), &d, &t);
    RetVal.type = type_wanted;
    switch(type_wanted) {
    case TIME_TYPE:
        RETVAL = t;
        break;
    case DATE_TYPE:
        RETVAL = d;
        break;
    case DATETIME_TYPE:
        RETVAL = d * MINUTES_PER_DAY + t;
        break;
    default:
        return E_BAD_TYPE;
    }
    return OK;
}

static int FTimepart(func_info *info)
{
    if (!HASTIME(ARG(0))) return E_BAD_TYPE;
    RetVal.type = TIME_TYPE;
    RETVAL = TIMEPART(ARG(0));
    return OK;
}

static int FDatepart(func_info *info)
{
    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    RetVal.type = DATE_TYPE;
    RETVAL = DATEPART(ARG(0));
    return OK;
}

#ifndef HAVE_SETENV
/* This is NOT a general-purpose replacement for setenv.  It's only
 * used for the timezone stuff! */
static int setenv(char const *varname, char const *val, int overwrite)
{
    static char tzbuf[128];
    if (strcmp(varname, "TZ")) {
        fprintf(ErrFp, "built-in setenv can only be used with TZ\n");
        abort();
    }
    if (!overwrite) {
        fprintf(ErrFp, "built-in setenv must have overwrite=1\n");
        abort();
    }

    if (strlen(val) > 250) {
        return -1;
    }
    snprintf(tzbuf, sizeof(tzbuf), "%s=%s", varname, val);
    return(putenv(tzbuf));
}
#endif
#ifndef HAVE_UNSETENV
/* This is NOT a general-purpose replacement for unsetenv.  It's only
 * used for the timezone stuff! */
static void unsetenv(char const *varname)
{
    static char tzbuf[128];
    if (strcmp(varname, "TZ")) {
        fprintf(ErrFp, "built-in unsetenv can only be used with TZ\n");
        abort();
    }
    snprintf(tzbuf, sizeof(tzbuf), "%s", varname);
    putenv(tzbuf);
}
#endif

/***************************************************************/
/*                                                             */
/*  FTz                                                        */
/*                                                             */
/*  Conversion between different timezones.                    */
/*                                                             */
/***************************************************************/
int tz_set_tz(char const *tz)
{
    int r;
    if (tz == NULL) {
       unsetenv("TZ");
       r = 0;
    } else {
        r = setenv("TZ", tz, 1);
    }
    tzset();
    return r;
}

int tz_convert(int year, int month, int day,
                      int hour, int minute,
                      char const *src_tz, char const *tgt_tz,
                      struct tm *tm)
{
    int r;
    time_t t;
    struct tm const *res;
    char const *old_tz;

    /* init tm struct */
    tm->tm_sec = 0;
    tm->tm_min = minute;
    tm->tm_hour = hour;
    tm->tm_mday = day;
    tm->tm_mon = month;
    tm->tm_year = year - 1900;
    tm->tm_wday = 0; /* ignored by mktime */
    tm->tm_yday = 0; /* ignored by mktime */
    tm->tm_isdst = -1;  /* information not available */

    /* backup old TZ env var */
    old_tz = getenv("TZ");
    if (old_tz) {
        old_tz = strdup(old_tz);
        if (!old_tz) return E_NO_MEM;
    }
    if (tgt_tz == NULL || !*tgt_tz) {
        tgt_tz = old_tz;
    }
    if (src_tz == NULL || !*src_tz) {
        src_tz = old_tz;
    }

    /* set source TZ */
    r = tz_set_tz(src_tz);
    if (r == -1) {
        tz_set_tz(old_tz);
        if (old_tz) free((void *) old_tz);
        return -1;
    }

    /* create timestamp in UTC */
    t = mktime(tm);

    if (t == (time_t) -1) {
        tz_set_tz(old_tz);
        if (old_tz) free((void *) old_tz);
        return -1;
    }

    /* set target TZ */
    r = tz_set_tz(tgt_tz);
    if (r == -1) {
        tz_set_tz(old_tz);
        if (old_tz) free((void *) old_tz);
        return -1;
    }

    /* convert to target TZ */
    res = localtime_r(&t, tm);

    /* restore old TZ */
    tz_set_tz(old_tz);
    if (old_tz) free((void *) old_tz);

    /* return result */
    if (res == NULL) {
        return -1;
    } else {
        return 1;
    }
}

static int FTzconvert(func_info *info)
{
    int year, month, day, hour, minute, r;
    int dse, tim;
    char const *src_tz, *tgt_tz;
    struct tm tm;

    if (ARG(0).type != DATETIME_TYPE ||
        ARG(1).type != STR_TYPE) return E_BAD_TYPE;
    if (Nargs == 3 && ARG(2).type != STR_TYPE) return E_BAD_TYPE;

    FromDSE(DATEPART(ARG(0)), &year, &month, &day);

    r = TIMEPART(ARG(0));
    hour = r / 60;
    minute = r % 60;

    if (Nargs == 2) {
        src_tz = ARGSTR(1);
        warn_if_timezone_bad(src_tz);
        if (*src_tz == '!') {
            src_tz++;
        }
        r = tz_convert(year, month, day, hour, minute,
                       src_tz, NULL, &tm);
    } else {
        src_tz = ARGSTR(1);
        warn_if_timezone_bad(src_tz);
        if (*src_tz == '!') {
            src_tz++;
        }
        tgt_tz = ARGSTR(2);
        warn_if_timezone_bad(tgt_tz);
        if (*tgt_tz == '!') {
            tgt_tz++;
        }
        r = tz_convert(year, month, day, hour, minute,
                       src_tz, tgt_tz, &tm);
    }

    if (r == -1) return E_CANT_CONVERT_TZ;

    dse = DSE(tm.tm_year + 1900, tm.tm_mon, tm.tm_mday);
    tim = tm.tm_hour * 60 + tm.tm_min;
    RetVal.type = DATETIME_TYPE;
    RETVAL = dse * MINUTES_PER_DAY + tim;
    return OK;
}

static int
FSlide(func_info *info)
{
    int r, omit, d, i, localomit, amt;
    Token tok;
    int step = 1;
    int localargs = 2;

    if (!HASDATE(ARG(0))) return E_BAD_TYPE;
    ASSERT_TYPE(1, INT_TYPE);

    d = DATEPART(ARG(0));
    amt = ARGV(1);
    if (amt > 1000000) return E_2HIGH;
    if (amt < -1000000) return E_2LOW;

    if (Nargs > 2 && ARG(2).type == INT_TYPE) {
        step = ARGV(2);
        if (step < 1) return E_2LOW;
        localargs++;
    }
    localomit = 0;
    for (i=localargs; i<Nargs; i++) {
        if (ARG(i).type != STR_TYPE) return E_BAD_TYPE;
        FindToken(ARG(i).v.str, &tok);
        if (tok.type != T_WkDay) return E_CANT_PARSE_WKDAY;
        localomit |= (1 << tok.val);
    }

    /* If ALL weekdays are omitted... barf! */
    if ((WeekdayOmits | localomit) == 0x7F && amt != 0) return E_2MANY_LOCALOMIT;
    if (amt > 0) {
        while(amt) {
            d += step;
            r = IsOmitted(d, localomit, NULL, &omit);
            if (r) return r;
            if (!omit) amt--;
        }
    } else {
        while(amt) {
            d -= step;
            if (d < 0) return E_DATE_OVER;
            r = IsOmitted(d, localomit, NULL, &omit);
            if (r) return r;
            if (!omit) amt++;
        }
    }
    RetVal.type = DATE_TYPE;
    RETVAL = d;
    return OK;
}

static int
FNonomitted(func_info *info)
{
    int d1, d2, ans, localomit, i;
    int omit, r;
    int step = 1;
    int localargs = 2;
    Token tok;

    if (!HASDATE(ARG(0)) ||
        !HASDATE(ARG(1))) {
        return E_BAD_TYPE;
    }
    d1 = DATEPART(ARG(0));
    d2 = DATEPART(ARG(1));
    if (d2 < d1) {
        i = d1;
        d1 = d2;
        d2 = i;
    }

    /* Check for a "step" argument - it's an INT */
    if (Nargs > 2 && ARG(2).type == INT_TYPE) {
        step = ARGV(2);
        if (step < 1) return E_2LOW;
        localargs++;
    }
    localomit = 0;
    for (i=localargs; i<Nargs; i++) {
        if (ARG(i).type != STR_TYPE) return E_BAD_TYPE;
        FindToken(ARG(i).v.str, &tok);
        if (tok.type != T_WkDay) return E_CANT_PARSE_WKDAY;
        localomit |= (1 << tok.val);
    }

    ans = 0;
    while (d1 < d2) {
        r = IsOmitted(d1, localomit, NULL, &omit);
        if (r) return r;
        if (!omit) {
            ans++;
        }
        d1 += step;
    }
    RetVal.type = INT_TYPE;
    RETVAL = ans;
    return OK;
}

static int
FWeekno(func_info *info)
{
    int dse = DSEToday;
    int wkstart = 0; /* Week start on Monday */
    int daystart = 29; /* First week starts on wkstart on or after Dec. 29 */
    int monstart;
    int candidate;

    int y, m, d;

    if (Nargs >= 1) {
        if (!HASDATE(ARG(0))) return E_BAD_TYPE;
        dse = DATEPART(ARG(0));
    }
    if (Nargs >= 2) {
        ASSERT_TYPE(1, INT_TYPE);
        if (ARGV(1) < 0) return E_2LOW;
        if (ARGV(1) > 6) return E_2HIGH;
        wkstart = ARGV(1);
        /* Convert 0=Sun to 0=Mon */
        wkstart--;
        if (wkstart < 0) wkstart = 6;
        if (Nargs >= 3) {
            ASSERT_TYPE(2, INT_TYPE);
            if (ARGV(2) < 1) return E_2LOW;
            if (ARGV(2) > 31) return E_2HIGH;
            daystart = ARGV(2);
        }
    }

    RetVal.type = INT_TYPE;
    /* If start day is 7, first week starts after Jan,
       otherwise after Dec. */
    if (daystart <= 7) {
        monstart = 0;
    } else {
        monstart = 11;
    }

    FromDSE(dse, &y, &m, &d);

    /* Try this year */
    candidate = DSE(y, monstart, daystart);
    while((candidate % 7) != wkstart) candidate++;

    if (candidate <= dse) {
        RETVAL = ((dse - candidate) / 7) + 1;
        return OK;
    }

    if (y-1 < BASE) return E_DATE_OVER;
    /* Must be last year */
    candidate = DSE(y-1, monstart, daystart);
    while((candidate % 7) != wkstart) candidate++;
    if (candidate <= dse) {
        RETVAL = ((dse - candidate) / 7) + 1;
        return OK;
    }

    if (y-2 < BASE) return E_DATE_OVER;
    /* Holy cow! */
    candidate = DSE(y-2, monstart, daystart);
    while((candidate % 7) != wkstart) candidate++;
    RETVAL = ((dse - candidate) / 7) + 1;
    return OK;
}

static int
FEval(func_info *info)
{
    expr_node *n;
    int r;
    int old_run_disabled;

    ASSERT_TYPE(0, STR_TYPE);
    char const *e = ARGSTR(0);

    n = parse_expression(&e, &r, NULL);
    if (r != OK) {
        info->nonconst = 1;
        return r;
    }

    /* Disable shell() command in eval */
    old_run_disabled = RunDisabled;
    RunDisabled |= RUN_IN_EVAL;

    r = evaluate_expr_node(n, NULL, &(info->retval), &(info->nonconst));

    RunDisabled = old_run_disabled;

    free_expr_tree(n);
    return r;
}

static int
FEvalTrig(func_info *info)
{
    Parser p;
    Trigger trig;
    TimeTrig tim;
    int dse, scanfrom;
    int r;

    ASSERT_TYPE(0, STR_TYPE);
    if (Nargs >= 2) {
        if (!HASDATE(ARG(1))) return E_BAD_TYPE;
        scanfrom = DATEPART(ARG(1));
    } else {
        scanfrom = NO_DATE;
    }

    CreateParser(ARGSTR(0), &p);
    p.allownested = 0;
    r = ParseRem(&p, &trig, &tim);
    if (r) {
        DestroyParser(&p);
        return r;
    }
    if (trig.tz != NULL && tim.ttime == NO_TIME) {
        DestroyParser(&p);
        FreeTrig(&trig);
        return E_TZ_NO_AT;
    }
    if (trig.typ != NO_TYPE) {
        DestroyParser(&p);
        FreeTrig(&trig);
        return E_PARSE_ERR;
    }
    if (scanfrom == NO_DATE) {
        EnterTimezone(trig.tz);
        dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 0);
        ExitTimezone(trig.tz);
    } else {
        /* Hokey... */
        if (get_scanfrom(&trig) != DSEToday) {
            Wprint(tr("Warning: SCANFROM is ignored in two-argument form of evaltrig()"));
        }
        EnterTimezone(trig.tz);
        dse = ComputeTrigger(scanfrom, &trig, &tim, &r, 0);
        ExitTimezone(trig.tz);
    }
    if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
        r = 0;
        dse = -1;
    }
    DestroyParser(&p);
    if (r) {
        FreeTrig(&trig);
        return r;
    }
    if (dse < 0) {
        RetVal.type = INT_TYPE;
        RETVAL = dse;
    } else if (tim.ttime == NO_TIME) {
        RetVal.type = DATE_TYPE;
        RETVAL = dse;
    } else {
        dse = AdjustTriggerForTimeZone(&trig, dse, &tim, 1);
        RetVal.type = DATETIME_TYPE;
        RETVAL = (MINUTES_PER_DAY * dse) + tim.ttime;
    }
    FreeTrig(&trig);
    return OK;
}

static int
FMultiTrig(func_info *info)
{
    Parser p;
    Trigger trig;
    TimeTrig tim;
    int dse;
    int r;
    int i;
    int earliest = -1;

    RetVal.type = DATE_TYPE;
    RETVAL = 0;

    for (i=0; i<Nargs; i++) {
        ASSERT_TYPE(i, STR_TYPE);
    }
    for (i=0; i<Nargs; i++) {
        CreateParser(ARGSTR(i), &p);
        p.allownested = 0;
        r = ParseRem(&p, &trig, &tim);
        if (r) {
            DestroyParser(&p);
            return r;
        }
        if (trig.tz != NULL && tim.ttime == NO_TIME) {
            FreeTrig(&trig);
            return E_TZ_NO_AT;
        }
        if (trig.typ != NO_TYPE) {
            DestroyParser(&p);
            FreeTrig(&trig);
            return E_PARSE_ERR;
        }
        if (tim.ttime != NO_TIME) {
            Eprint(tr("Cannot use AT clause in multitrig() function"));
            return E_PARSE_ERR;
        }
        dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 0);

        /* multitrig only does untimed reminders, so no need to worry
           about time zones */
        DestroyParser(&p);

        if (r != E_CANT_TRIG) {
            if (dse < earliest || earliest < 0) {
                earliest = dse;
            }
        }
        FreeTrig(&trig);
    }
    if (earliest >= 0) {
        RETVAL = earliest;
    }

    return OK;
}

static int LastTrig = 0;
static int
FTrig(func_info *info)
{
    Parser p;
    Trigger trig;
    TimeTrig tim;
    int dse;
    int r;
    int i;

    RetVal.type = DATE_TYPE;
    if (Nargs == 0) {
        RETVAL = LastTrig;
        return OK;
    }

    for (i=0; i<Nargs; i++) {
        ASSERT_TYPE(i, STR_TYPE);
    }

    RETVAL = 0;

    for (i=0; i<Nargs; i++) {
        CreateParser(ARGSTR(i), &p);
        p.allownested = 0;
        r = ParseRem(&p, &trig, &tim);
        if (r) {
            DestroyParser(&p);
            return r;
        }
        if (trig.tz != NULL && tim.ttime == NO_TIME) {
            FreeTrig(&trig);
            return E_TZ_NO_AT;
        }
        if (trig.typ != NO_TYPE) {
            DestroyParser(&p);
            FreeTrig(&trig);
            return E_PARSE_ERR;
        }
        EnterTimezone(trig.tz);
        dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 0);
        ExitTimezone(trig.tz);
        DestroyParser(&p);

        if (r == E_CANT_TRIG) {
            FreeTrig(&trig);
            continue;
        }
        dse = AdjustTriggerForTimeZone(&trig, dse, &tim, 1);
        if (ShouldTriggerReminder(&trig, &tim, dse, &r)) {
            LastTrig = dse;
            RETVAL = dse;
            FreeTrig(&trig);
            return OK;
        }
        FreeTrig(&trig);
    }
    return OK;
}

static int
rows_or_cols(func_info *info, int want_rows)
{
    struct winsize w;
    int fd = STDOUT_FILENO;
    int opened = 0;

    RetVal.type = INT_TYPE;
    if (!isatty(fd)) {
        /* Try STDERR fd if STDOUT fd is not a tty */
        fd = STDERR_FILENO;
    }
    if (!isatty(fd)) {
        fd = open("/dev/tty", O_RDONLY);
        if (fd < 0) {
            RETVAL = -1;
            return OK;
        }
        opened = 1;
    }
    if (ioctl(fd, TIOCGWINSZ, &w) == 0) {
        if (want_rows) RETVAL = w.ws_row;
        else           RETVAL = w.ws_col;
    } else {
        RETVAL = -1;
    }
    if (opened) {
        close(fd);
    }
    return OK;
}

static int FRows(func_info *info)
{
    return rows_or_cols(info, 1);
}
static int FColumns(func_info *info)
{
    size_t len;
    wchar_t *buf, *s;
    int width;

    if (Nargs == 0) {
        return rows_or_cols(info, 0);
    }
    ASSERT_TYPE(0, STR_TYPE);
    len = mbstowcs(NULL, ARGSTR(0), 0);
    if (len == (size_t) -1) return E_NO_MEM;
    buf = calloc(len+1, sizeof(wchar_t));
    if (!buf) return E_NO_MEM;
    (void) mbstowcs(buf, ARGSTR(0), len+1);

    s = buf;
    width = 0;
    while (*s) {
        if (*s == 0x1B && *(s+1) == '[') {
            /* Skip escape sequences */
            s += 2;
            while (*s && (*s < 0x40 || *s > 0x7E)) {
                s++;
            }
            if (*s) {
                s++;
            }
            continue;
        }
        width += wcwidth(*s);
        s++;
    }
    free(buf);
    RetVal.type = INT_TYPE;
    RETVAL = width;
    return OK;
}

/* The following sets of functions are for computing solstices and equinoxes.
   They are based on the algorithms described in "Astronomical Algorithms",
   second edition, by Jean Meeus.  ISBN 0-943396-61-1 */

/* The following are taken from Astronomical Algorithms, 2nd ed., page 178 */
static double
mean_march_equinox(double y)
{
    return 2451623.80984 + 365242.37404*y + 0.05169*y*y - 0.00411*y*y*y - 0.00057*y*y*y*y;
}

static double
mean_june_solstice(double y)
{
    return 2451716.56767 + 365241.62603*y + 0.00325*y*y + 0.00888*y*y*y - 0.00030*y*y*y*y;
}

static double
mean_september_equinox(double y)
{
    return 2451810.21715 + 365242.01767*y - 0.11575*y*y + 0.00337*y*y*y + 0.00078*y*y*y*y;
}

static double
mean_december_solstice(double y)
{
    return 2451900.05952 + 365242.74049*y - 0.06223*y*y - 0.00823*y*y*y + 0.00032*y*y*y*y;
}

/* Cosine of an angle specified in degrees */
#define PI_BY_180 0.01745329251994329576923690768
#define cosd(theta) cos( (theta) * PI_BY_180)

/* Astronomical Algorithms by Meeus, p. 179
   These weird periodic components refine the mean solstice/equinox dates
   calculated with the simpler degree-4 polynomials above */
static double
meeus_periodic_components(double t)
{
    return
        485 * cosd(324.96 +   1934.136 * t) +
        203 * cosd(337.23 +  32964.467 * t) +
        199 * cosd(342.08 +     20.186 * t) +
        182 * cosd( 27.85 + 445267.112 * t) +
        156 * cosd( 73.14 +  45036.886 * t) +
        136 * cosd(171.52 +  22518.443 * t) +
         77 * cosd(222.54 +  65928.934 * t) +
         74 * cosd(296.72 +   3034.906 * t) +
         70 * cosd(243.58 +   9037.513 * t) +
         58 * cosd(119.81 +  33718.147 * t) +
         52 * cosd(297.17 +    150.678 * t) +
         50 * cosd( 21.02 +   2281.226 * t) +
         45 * cosd(247.54 +  29929.562 * t) +
         44 * cosd(325.15 +  31555.956 * t) +
         29 * cosd( 60.93 +   4443.417 * t) +
         18 * cosd(155.12 +  67555.328 * t) +
         17 * cosd(288.79 +   4562.452 * t) +
         16 * cosd(198.04 +  62894.029 * t) +
         14 * cosd(199.76 +  31436.921 * t) +
         12 * cosd( 95.39 +  14577.848 * t) +
         12 * cosd(287.11 +  31931.756 * t) +
         12 * cosd(320.81 +  34777.259 * t) +
          9 * cosd(227.73 +   1222.114 * t) +
          8 * cosd( 15.45 +  16859.074 * t);
}

static double
julian_solstice_equinox(int y, int which)
{
    double jde0;
    double dy;
    double t, w, dlambda, s;

    dy = ((double) y - 2000.0) / 1000.0;
    switch(which) {
    case 0:
        jde0 = mean_march_equinox(dy);
        break;
    case 1:
        jde0 = mean_june_solstice(dy);
        break;
    case 2:
        jde0 = mean_september_equinox(dy);
        break;
    case 3:
        jde0 = mean_december_solstice(dy);
        break;
    default:
        return -1.0;
    }

    t = (jde0 - 2451545.0) / 36525.0;
    w = 35999.373 * t - 2.47;
    dlambda = 1 + 0.0334 * cosd(w) + 0.0007 * cosd(2*w);
    s = meeus_periodic_components(t);

    return jde0 + (0.00001 * s) / dlambda;
}

/* Returns a value suitable for a datetime object.  Assumes that BASE = 1990*/
static int
solstice_equinox_for_year(int y, int which)
{
    double j = julian_solstice_equinox(y, which);

    if (j < 0) {
        return -1;
    }

    j -= 2447892.50000;  /* This is the Julian date of midnight, 1 Jan 1990 UTC */
    int dse = (int) j;

    int min = (int) floor((j - (double) dse) * MINUTES_PER_DAY);
    int ret;

    /* Convert from UTC to local time */
    if (UTCToLocalHelper(dse * MINUTES_PER_DAY + min, &ret) != OK) {
        return -1;
    }
    return ret;
}

/* Solstice / equinox function */
static int
FSoleq(func_info *info)
{
    int y, dse, which, ret;

    RetVal.type = ERR_TYPE;

    dse = NO_DATE;
    ASSERT_TYPE(0, INT_TYPE);
    which = ARGV(0);
    if (which < 0) {
        return E_2LOW;
    } else if (which > 3) {
        return E_2HIGH;
    }

    if (Nargs > 1) {
        if (ARG(1).type == INT_TYPE) {
            y = ARGV(1);
            if (y < BASE) {
                return E_2LOW;
            } else if (y > BASE+YR_RANGE) {
                return E_2HIGH;
            }
        } else if (HASDATE(ARG(1))) {
            dse = DATEPART(ARG(1));
            FromDSE(dse, &y, NULL, NULL);  /* We just want the year */
        } else {
            return E_BAD_TYPE;
        }
    } else {
        /* If no second argument, default to today */
        dse = DSEToday;
        FromDSE(dse, &y, NULL, NULL);  /* We just want the year */
    }

    ret = solstice_equinox_for_year(y, which);
    if (ret < 0) return E_MKTIME_PROBLEM;
    if (dse != NO_DATE && (ret / MINUTES_PER_DAY) < dse) {
        ret = solstice_equinox_for_year(y+1, which);
        if (ret < 0) return E_MKTIME_PROBLEM;
    }
    RetVal.type = DATETIME_TYPE;
    RETVAL = ret;
    return OK;
}

/* Compare two strings case-insensitively, where we KNOW
   that the second string is definitely lower-case */
static int strcmp_lcfirst(char const *s1, char const *s2)
{
    int r;
    while (*s1 && *s2) {
        r = tolower(*s1) - *s2;
        if (r) return r;
        s1++;
        s2++;
    }
    return tolower(*s1) - *s2;
}

/***************************************************************/
/*                                                             */
/*  FindBuiltinFunc                                            */
/*                                                             */
/*  Find a built-in function.                                  */
/*                                                             */
/***************************************************************/
BuiltinFunc *FindBuiltinFunc(char const *name)
{
    int top=NumFuncs-1, bot=0;
    int mid, r;
    while (top >= bot) {
        mid = (top + bot) / 2;
        r = strcmp_lcfirst(name, Func[mid].name);
        if (!r) return &Func[mid];
        else if (r > 0) bot = mid+1;
        else top = mid-1;
    }
    return NULL;
}

void
print_builtinfunc_tokens(void)
{
    int i;
    printf("\n# Built-in Functions\n\n");
    for (i=0; i<NumFuncs; i++) {
        printf("%s\n", Func[i].name);
    }
}

static int mbupper_lower(func_info *info, int upper)
{
    wchar_t *ws;
    char *s;
    size_t i, len;
    int r;
    ASSERT_TYPE(0, STR_TYPE);
    len = mbstowcs(NULL, ARGSTR(0), 0);
    if (len == (size_t) -1) {
        return E_BAD_MB_SEQ;
    }
    ws = calloc(len+1, sizeof(wchar_t));
    if (!ws) {
        return E_NO_MEM;
    }
    (void) mbstowcs(ws, ARGSTR(0), len+1);
    for (i=0; i<len; i++) {
        if (upper) {
            ws[i] = towupper(ws[i]);
        } else {
            ws[i] = towlower(ws[i]);
        }
    }
    len = wcstombs(NULL, ws, 0);
    s = calloc(len+1, 1);
    if (!s) {
        free(ws);
        return E_NO_MEM;
    }
    (void) wcstombs(s, ws, len+1);
    r = RetStrVal(s, info);
    free(s);
    free(ws);
    return r;
}

static int FMblower(func_info *info)
{
    return mbupper_lower(info, 0);
}

static int FMbupper(func_info *info)
{
    return mbupper_lower(info, 1);
}

