mirror of
https://salsa.debian.org/dskoll/remind.git
synced 2026-04-16 06:18:47 +02:00
5176 lines
150 KiB
C
5176 lines
150 KiB
C
/***************************************************************/
|
|
/* */
|
|
/* 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 FMbpad (func_info *);
|
|
static int FMbstrlen (func_info *);
|
|
static int FMbsubstr (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 },
|
|
{ "mbpad", 3, 4, 1, FMbpad, NULL },
|
|
{ "mbstrlen", 1, 1, 1, FMbstrlen, NULL },
|
|
{ "mbsubstr", 2, 3, 1, FMbsubstr, 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 = DSEToday * MINUTES_PER_DAY + MinutesPastMidnight(0);
|
|
return OK;
|
|
}
|
|
|
|
static int FRealCurrent(func_info *info)
|
|
{
|
|
RetVal.type = DATETIME_TYPE;
|
|
RETVAL = RealToday * MINUTES_PER_DAY + MinutesPastMidnight(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 < > and & */
|
|
/* */
|
|
/****************************************************************/
|
|
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, "<");
|
|
break;
|
|
|
|
case '>':
|
|
r = DBufPuts(&dbuf, ">");
|
|
break;
|
|
|
|
case '&':
|
|
r = DBufPuts(&dbuf, "&");
|
|
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) {
|
|
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) 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);
|
|
}
|
|
}
|