Compare commits

..

20 Commits

Author SHA1 Message Date
Dianne Skoll e0b0d043c6 Add release notes 2024-12-09 19:09:37 -05:00
Dianne Skoll fe4499ab72 Make comparison function also useful for ordering. 2024-12-09 18:39:49 -05:00
Dianne Skoll e50d583659 Use case-sensitive hashing for dedup and translation hash tables. 2024-12-09 18:36:53 -05:00
Dianne Skoll 6b05d772f0 Exit if we run out of memory initializing hash tables. There's no sane way to recover. 2024-12-09 18:10:03 -05:00
Dianne Skoll 84dd73f023 Note connection between translation-related special variables and translation table. 2024-12-09 18:06:21 -05:00
Dianne Skoll 00dca8b70f Bump version to 05.02.00. 2024-12-09 17:59:40 -05:00
Dianne Skoll c4bc145cd9 Document the translation table. 2024-12-09 17:56:52 -05:00
Dianne Skoll bd614c1cde Make only one call to malloc() per XlateItem. 2024-12-09 17:07:58 -05:00
Dianne Skoll 1446ac0552 Make the output of "make test" considerably less verbose.
Remind unit tests / tests (push) Successful in 33s
2024-12-09 14:33:02 -05:00
Dianne Skoll 26ded447ab Fix some translations. 2024-12-09 14:24:14 -05:00
Dianne Skoll a4ccb0738e Make Chinese New Year file translatable. 2024-12-09 14:20:27 -05:00
Dianne Skoll 27a1b449bd Add new keywords and built-in function. 2024-12-09 14:14:29 -05:00
Dianne Skoll 1443282859 Add a whack more torture tests. 2024-12-09 14:09:40 -05:00
Dianne Skoll 4a2d707654 Properly handle deleting everything out of a hash table. 2024-12-09 13:59:41 -05:00
Dianne Skoll fd2a61928c Fix NL translation. 2024-12-09 13:32:21 -05:00
Dianne Skoll a05d9eefc9 Make "SET $foo" also add a translation table entry if $foo is a dynamic translation variable. 2024-12-09 13:29:05 -05:00
Dianne Skoll 6f230e81bd Add translations for moon phases and seasons. 2024-12-09 13:05:34 -05:00
Dianne Skoll 973019c4c7 Implement TRANSLATE keyword. 2024-12-09 12:56:40 -05:00
Dianne Skoll cb712ad7e7 Replace the individual hash table implementations with a unified one. 2024-12-09 11:54:52 -05:00
Dianne Skoll be7c67b6fd Add ParseQuotedString function for eventually implementing TRANSLATE directive. 2024-12-08 11:54:49 -05:00
28 changed files with 321740 additions and 423 deletions
Vendored
+9 -9
View File
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for remind 05.01.01.
# Generated by GNU Autoconf 2.71 for remind 05.02.00.
#
#
# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
@@ -608,8 +608,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='remind'
PACKAGE_TARNAME='remind'
PACKAGE_VERSION='05.01.01'
PACKAGE_STRING='remind 05.01.01'
PACKAGE_VERSION='05.02.00'
PACKAGE_STRING='remind 05.02.00'
PACKAGE_BUGREPORT=''
PACKAGE_URL='https://dianne.skoll.ca/projects/remind/'
@@ -1265,7 +1265,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures remind 05.01.01 to adapt to many kinds of systems.
\`configure' configures remind 05.02.00 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1327,7 +1327,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of remind 05.01.01:";;
short | recursive ) echo "Configuration of remind 05.02.00:";;
esac
cat <<\_ACEOF
@@ -1415,7 +1415,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
remind configure 05.01.01
remind configure 05.02.00
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1865,7 +1865,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by remind $as_me 05.01.01, which was
It was created by remind $as_me 05.02.00, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -4710,7 +4710,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by remind $as_me 05.01.01, which was
This file was extended by remind $as_me 05.02.00, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -4775,7 +4775,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
remind config.status 05.01.01
remind config.status 05.02.00
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"
+1 -1
View File
@@ -1,6 +1,6 @@
dnl Process this file with autoconf to produce a configure script.
AC_INIT(remind, 05.01.01, , , https://dianne.skoll.ca/projects/remind/)
AC_INIT(remind, 05.02.00, , , https://dianne.skoll.ca/projects/remind/)
AC_CONFIG_SRCDIR([src/queue.c])
cat <<'EOF'
+2 -2
View File
@@ -117,7 +117,7 @@
"NOQUEUE" "OMIT" "OMITFUNC" "ONCE" "POP" "POP-OMIT-CONTEXT" "PRESERVE"
"PRIORITY" "PS" "PSFILE" "PUSH" "PUSH-OMIT-CONTEXT" "REM" "RUN"
"SATISFY" "SCAN" "SCANFROM" "SCHED" "SECOND" "SET" "SKIP" "SPECIAL"
"TAG" "THIRD" "THROUGH" "UNSET" "UNTIL" "WARN")
"TAG" "THIRD" "THROUGH" "TRANSLATE" "TRANS" "UNSET" "UNTIL" "WARN")
#'(lambda (a b) (> (length a) (length b)))))
@@ -162,7 +162,7 @@
(defconst remind-builtin-functions
(sort
(list "abs" "access" "adawn" "adusk" "ampm" "ansicolor" "args" "asc"
(list "_" "abs" "access" "adawn" "adusk" "ampm" "ansicolor" "args" "asc"
"baseyr" "char" "choose" "coerce" "columns" "current" "date"
"datepart" "datetime" "dawn" "day" "daysinmon" "defined" "dosubst"
"dusk" "easterdate" "evaltrig" "filedate" "filedatetime" "filedir"
+22
View File
@@ -1,5 +1,27 @@
CHANGES TO REMIND
* VERSION 5.2 Patch 0 - ????-??=??
- MAJOR NEW FEATURE: remind: Add the TRANSLATE command and the _()
built-in function. This allows you to localize your reminder files
more easily.
- UPDATE: Update national holidays following update to upstream Python
library.
- IMPROVEMENT: remind: Refuse to open subdirectories named "*.rem"
under a top-level directory rather than trying and failing with a
confusing error.
- IMPROVEMENT: remind: Remind used to have three completely separate
hash table implementations. Replace them all with one piece of code.
- MINOR FIXES: remind: Fix typos in comments; use memcpy to copy OMIT
contexts internally.
- BUG FIX: Actually allow the documented 9 levels of INCLUDE rather than
8.
* VERSION 5.1 Patch 1 - 2024-11-18
- BUG FIX: Fix a bug in test-rem that could have caused test failures.
+29 -29
View File
@@ -1,29 +1,29 @@
REM 1 Feb 2022 MSG Chinese New Year (Tiger)
REM 22 Jan 2023 MSG Chinese New Year (Rabbit)
REM 10 Feb 2024 MSG Chinese New Year (Dragon)
REM 29 Jan 2025 MSG Chinese New Year (Snake)
REM 17 Feb 2026 MSG Chinese New Year (Horse)
REM 6 Feb 2027 MSG Chinese New Year (Goat)
REM 26 Jan 2028 MSG Chinese New Year (Monkey)
REM 13 Feb 2029 MSG Chinese New Year (Rooster)
REM 3 Feb 2030 MSG Chinese New Year (Dog)
REM 23 Jan 2031 MSG Chinese New Year (Pig)
REM 11 Feb 2032 MSG Chinese New Year (Rat)
REM 31 Jan 2033 MSG Chinese New Year (Ox)
REM 19 Feb 2034 MSG Chinese New Year (Tiger)
REM 8 Feb 2035 MSG Chinese New Year (Rabbit)
REM 28 Jan 2036 MSG Chinese New Year (Dragon)
REM 15 Feb 2037 MSG Chinese New Year (Snake)
REM 4 Feb 2038 MSG Chinese New Year (Horse)
REM 24 Jan 2039 MSG Chinese New Year (Goat)
REM 12 Feb 2040 MSG Chinese New Year (Monkey)
REM 1 Feb 2041 MSG Chinese New Year (Rooster)
REM 22 Jan 2042 MSG Chinese New Year (Dog)
REM 10 Feb 2043 MSG Chinese New Year (Pig)
REM 30 Jan 2044 MSG Chinese New Year (Rat)
REM 17 Feb 2045 MSG Chinese New Year (Ox)
REM 6 Feb 2046 MSG Chinese New Year (Tiger)
REM 26 Jan 2047 MSG Chinese New Year (Rabbit)
REM 14 Feb 2048 MSG Chinese New Year (Dragon)
REM 2 Feb 2049 MSG Chinese New Year (Snake)
REM 23 Jan 2050 MSG Chinese New Year (Horse)
REM 1 Feb 2022 MSG [_("Chinese New Year")] ([_("Tiger")])
REM 22 Jan 2023 MSG [_("Chinese New Year")] ([_("Rabbit")])
REM 10 Feb 2024 MSG [_("Chinese New Year")] ([_("Dragon")])
REM 29 Jan 2025 MSG [_("Chinese New Year")] ([_("Snake")])
REM 17 Feb 2026 MSG [_("Chinese New Year")] ([_("Horse")])
REM 6 Feb 2027 MSG [_("Chinese New Year")] ([_("Goat")])
REM 26 Jan 2028 MSG [_("Chinese New Year")] ([_("Monkey")])
REM 13 Feb 2029 MSG [_("Chinese New Year")] ([_("Rooster")])
REM 3 Feb 2030 MSG [_("Chinese New Year")] ([_("Dog")])
REM 23 Jan 2031 MSG [_("Chinese New Year")] ([_("Pig")])
REM 11 Feb 2032 MSG [_("Chinese New Year")] ([_("Rat")])
REM 31 Jan 2033 MSG [_("Chinese New Year")] ([_("Ox")])
REM 19 Feb 2034 MSG [_("Chinese New Year")] ([_("Tiger")])
REM 8 Feb 2035 MSG [_("Chinese New Year")] ([_("Rabbit")])
REM 28 Jan 2036 MSG [_("Chinese New Year")] ([_("Dragon")])
REM 15 Feb 2037 MSG [_("Chinese New Year")] ([_("Snake")])
REM 4 Feb 2038 MSG [_("Chinese New Year")] ([_("Horse")])
REM 24 Jan 2039 MSG [_("Chinese New Year")] ([_("Goat")])
REM 12 Feb 2040 MSG [_("Chinese New Year")] ([_("Monkey")])
REM 1 Feb 2041 MSG [_("Chinese New Year")] ([_("Rooster")])
REM 22 Jan 2042 MSG [_("Chinese New Year")] ([_("Dog")])
REM 10 Feb 2043 MSG [_("Chinese New Year")] ([_("Pig")])
REM 30 Jan 2044 MSG [_("Chinese New Year")] ([_("Rat")])
REM 17 Feb 2045 MSG [_("Chinese New Year")] ([_("Ox")])
REM 6 Feb 2046 MSG [_("Chinese New Year")] ([_("Tiger")])
REM 26 Jan 2047 MSG [_("Chinese New Year")] ([_("Rabbit")])
REM 14 Feb 2048 MSG [_("Chinese New Year")] ([_("Dragon")])
REM 2 Feb 2049 MSG [_("Chinese New Year")] ([_("Snake")])
REM 23 Jan 2050 MSG [_("Chinese New Year")] ([_("Horse")])
+21
View File
@@ -54,3 +54,24 @@ FSET subst_minutes(m) iif(m==1, "1 minuut", m + " minuten")
FSET subst_hours(h) iif(h==1, "1 uur", h + " uren")
FSET subst_bx(a, d, t) "over " + (d-today()) + " dagen"
TRANSLATE "New Moon" "Nieuwe maan"
TRANSLATE "First Quarter" "Eerste kwartier"
TRANSLATE "Full Moon" "Volle maan"
TRANSLATE "Last Quarter" "Laatste kwartier"
TRANSLATE "Vernal Equiniox" "Lente-equinox"
TRANSLATE "Summer Solstice" "Zomerzonnewend"
TRANSLATE "Autumnal Equinox" "Herfst-equinox"
TRANSLATE "Winter Solstice" "Winterzonnewend"
TRANSLATE "Chinese New Year" "Chinees Nieuwjaar"
TRANSLATE "Snake" "Slang"
TRANSLATE "Horse" "Paard"
TRANSLATE "Goat" "Geit"
TRANSLATE "Monkey" "Aap"
TRANSLATE "Rooster" "Haan"
TRANSLATE "Dog" "Hond"
TRANSLATE "Pig" "Varken"
TRANSLATE "Rat" "Rat"
TRANSLATE "Ox" "Os"
+4 -4
View File
@@ -7,8 +7,8 @@ IF $CalMode || $PsCal
REM [moondate(2)] SPECIAL MOON 2 -1 -1 [moontime(2)]
REM [moondate(3)] SPECIAL MOON 3 -1 -1 [moontime(3)]
ELSE
REM NOQUEUE [moondatetime(0)] MSG New Moon (%2)
REM NOQUEUE [moondatetime(1)] MSG First Quarter (%2)
REM NOQUEUE [moondatetime(2)] MSG Full Moon (%2)
REM NOQUEUE [moondatetime(3)] MSG Last Quarter (%2)
REM NOQUEUE [moondatetime(0)] MSG [_("New Moon")] (%2)
REM NOQUEUE [moondatetime(1)] MSG [_("First Quarter")] (%2)
REM NOQUEUE [moondatetime(2)] MSG [_("Full Moon")] (%2)
REM NOQUEUE [moondatetime(3)] MSG [_("Last Quarter")] (%2)
ENDIF
+8 -8
View File
@@ -3,14 +3,14 @@
IF $LatDeg >= 0
# Northern Hemisphere
REM NOQUEUE [soleq(0)] MSG %"Vernal Equinox%" is %3.
REM NOQUEUE [soleq(1)] MSG %"Summer Solstice%" is %3.
REM NOQUEUE [soleq(2)] MSG %"Autumnal Equinox%" is %3.
REM NOQUEUE [soleq(3)] MSG %"Winter Solstice%" is %3.
REM NOQUEUE [soleq(0)] MSG %"[_("Vernal Equinox")]%" [$Is] %3.
REM NOQUEUE [soleq(1)] MSG %"[_("Summer Solstice")]%" [$Is] %3.
REM NOQUEUE [soleq(2)] MSG %"[_("Autumnal Equinox")]%" [$Is] %3.
REM NOQUEUE [soleq(3)] MSG %"[_("Winter Solstice")]%" [$Is] %3.
ELSE
# Southern Hemisphere
REM NOQUEUE [soleq(0)] MSG %"Autumnal Equinox%" is %3.
REM NOQUEUE [soleq(1)] MSG %"Winter Solstice%" is %3.
REM NOQUEUE [soleq(2)] MSG %"Vernal Equinox%" is %3.
REM NOQUEUE [soleq(3)] MSG %"Summer Solstice%" is %3.
REM NOQUEUE [soleq(0)] MSG %"[_("Autumnal Equinox")]%" [$Is] %3.
REM NOQUEUE [soleq(1)] MSG %"[_("Winter Solstice")]%" [$Is] %3.
REM NOQUEUE [soleq(2)] MSG %"[_("Vernal Equinox")]%" [$Is] %3.
REM NOQUEUE [soleq(3)] MSG %"[_("Summer Solstice")]%" [$Is] %3.
ENDIF
+69 -4
View File
@@ -1196,7 +1196,7 @@ The command:
REM ... whatever ... ADDOMIT MSG Foo
.fi
.PP
is identical in behaviour to the sequence:
is identical in behavior to the sequence:
.PP
.nf
REM ... whatever ... SATISFY 1
@@ -2039,7 +2039,7 @@ Note that if RUN is disabled, then INCLUDECMD will fail with the error
message "RUN disabled"
.PP
INCLUDECMD passes the rest of the line to \fBpopen\fR(3), meaning that
the command is executed by the shell. As such, shell metacharacters
the command is executed by the shell. As such, shell meta-characters
may need escaping or arguments quoting, depending on what you're trying
to do. Remind itself does not perform any modification of the command
line (apart from the normal [expr] expression-pasting mechanism).
@@ -2767,7 +2767,7 @@ rules apply to \fB$Latitude\fR, \fB$LatDeg\fR, \fB$LatMin\fR and \fB$LatSec\fR.
This variable controls how \fBRemind\fR reacts to a computer being suspended
and then woken. Normally, if a timed reminder is queued and then the
computer suspended, and then the computer is woken \fIafter\fR the
timed reminder's trigger time, \fBRemind\fR will triger the timer anyway,
timed reminder's trigger time, \fBRemind\fR will trigger the timer anyway,
despite the fact that the trigger time has already passed.
.RS
.PP
@@ -3018,6 +3018,22 @@ an underscore and an identifier naming the argument.
.PP
The built-in functions are:
.TP
.B _(s_message)
Returns the translation table entry for \fImessage\fR. If there is no
such translation table entry, then returns \fImessage\fR unmodified.
For example, consider this sequence:
.RS
.PP
.nf
TRANSLATE "Goodbye" "Tot ziens"
SET a _("Goodbye")
.fi
.PP
After those two lines have been executed, the variable \fBa\fR will be
set to "Tot ziens". See the section THE TRANSLATION TABLE for more
information.
.RE
.TP
.B abs(i_num)
Returns the absolute value of \fInum\fR.
.TP
@@ -5206,6 +5222,55 @@ You can also define a function on the command line by using:
\fB\-i\fR\fIfunc\fR(\fIargs\fR)=\fIdefinition\fR
.PP
Be sure to protect special characters from shell interpretation.
.SH THE TRANSLATION TABLE
.PP
To assist with localizing reminder files, \fBRemind\fR maintains a
table of translations. This is simple a lookup table that maps one
string (the original string) to a new string (the translated string.)
When \fBRemind\fR starts executing, the translation table is empty.
.PP
To add a message to the translation table, use the \fBTRANSLATE\fR
command (which may be abbreviated to \fBTRANS\fR.) The \fBTRANSLATE\fR
command must be followed by two quoted strings, separated from each
other and from the command by whitespace. For example, a Dutch
language file might contain something like this:
.PP
.nf
TRANSLATE "New Moon" "Nieuwe maan"
TRANSLATE "First Quarter" "Eerste kwartier"
TRANSLATE "Full Moon" "Volle maan"
TRANSLATE "Last Quarter" "Laatste kwartier"
.fi
.PP
To actually use the translation table, make use of the \fB_\fR built-in
function, as follows:
.PP
.nf
REM NOQUEUE [moondatetime(0)] MSG [_("New Moon")] (%2)
REM NOQUEUE [moondatetime(1)] MSG [_("First Quarter")] (%2)
REM NOQUEUE [moondatetime(2)] MSG [_("Full Moon")] (%2)
REM NOQUEUE [moondatetime(3)] MSG [_("Last Quarter")] (%2)
.fi
.PP
By using \fBTRANSLATE\fR and \fB_\fR judiciously, you can make your
reminder files easy to translate.
.PP
\fBTRANSLATE\fR has three additional forms: If it is followed
by \fIone\fR quoted string instead of two, then \fBRemind\fR deletes the
translation table entry for that string. If it is followed by
the keyword \fBDUMP\fR, then \fBRemind\fR dumps all translation table entries
to standard output. And if it is followed by \fBCLEAR\fR, then
\fBRemind\fR deletes all of the translation table entries.
.PP
Note that if you \fBSET\fR various translation-related system
variables such as \fB$Monday\fR, \fB$December\fR, \fB$Ago\fR, etc,
then \fBRemind\fR \fIalso\fR makes a corresponding translation
table entry automatically. This is done for all of the translation-related
system variables \fIexcept for\fR \fB$Hplu\fR and \fB$Mplu\fR.
.PP
The converse does not apply; creating a translation table entry for
"December" does not automatically set \fB$December\fR.
.SH MORE ABOUT POSTSCRIPT
.PP
The \fBPS\fR and \fBPSFILE\fR reminders pass PostScript code directly
@@ -5552,7 +5617,7 @@ day's name in your language. Strings must be valid UTF-8 strings.
Set each of these system variables to a string representing the corresponding
month's name in your language. Strings must be valid UTF-8 strings.
.TP
.B $Ago, $Am, $And, $At, $Hour, $Is, $Minute, $Now, $On, $Pm, $Was
.B $Ago, $Am, $And, $At, $Hour, $Is, $Minute, $Now, $On, $Pm, $Today, $Tomorrow, $Was
Set each of these system variables to the translation of the corresponding
English word into your language. Note that \fB$Am\fR and \fB$Pm\fR should
be the translations of "AM" and "PM" (morning and afternoon time indicators)
+5 -5
View File
@@ -27,12 +27,12 @@ MANS= $(srcdir)/../man/rem2ps.1 $(srcdir)/../man/remind.1 \
.SUFFIXES: .c .o
REMINDSRCS= calendar.c dedupe.c dynbuf.c dorem.c dosubst.c expr.c \
files.c funcs.c globals.c hbcal.c init.c main.c md5.c \
moon.c omit.c queue.c sort.c token.c trigger.c \
userfns.c utils.c var.c
files.c funcs.c globals.c hashtab.c hashtab_stats.c \
hbcal.c init.c main.c md5.c moon.c omit.c queue.c \
sort.c token.c trans.c trigger.c userfns.c utils.c var.c
REMINDHDRS=config.h custom.h dynbuf.h err.h globals.h lang.h \
md5.h protos.h rem2ps.h types.h version.h
REMINDHDRS=config.h custom.h dynbuf.h err.h globals.h hashtab.h \
lang.h md5.h protos.h rem2ps.h types.h version.h
REMINDOBJS= $(REMINDSRCS:.c=.o)
all: remind rem2ps
+1
View File
@@ -1711,6 +1711,7 @@ static void GenerateCalEntries(int col)
case T_Push: r=PushOmitContext(&p); break;
case T_Preserve: r=DoPreserve(&p); break;
case T_Expr: r = DoExpr(&p); break;
case T_Translate: r = DoTranslate(&p); break;
case T_RemType: if (tok.val == RUN_TYPE) {
r=DoRun(&p);
break;
+51 -67
View File
@@ -16,16 +16,37 @@
#include "protos.h"
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#define DEDUPE_HASH_SLOTS 31
typedef struct dedupe_entry {
struct dedupe_entry *next;
struct hash_link link;
int trigger_date;
int trigger_time;
char const *body;
} DedupeEntry;
static DedupeEntry *DedupeTable[DEDUPE_HASH_SLOTS];
static hash_table DedupeTable;
static unsigned int DedupeHashFunc(void *x)
{
DedupeEntry *e = (DedupeEntry *) x;
unsigned int hashval = (unsigned int) e->trigger_date;
if (e->trigger_time != NO_TIME) {
hashval += (unsigned int) e->trigger_time;
}
hashval += HashVal_preservecase(e->body);
return hashval;
}
static int CompareDedupes(void *x, void *y)
{
DedupeEntry *a = (DedupeEntry *) x;
DedupeEntry *b = (DedupeEntry *) y;
if (a->trigger_date != b->trigger_date) return a->trigger_date - b->trigger_date;
if (a->trigger_time != b->trigger_time) return a->trigger_time - b->trigger_time;
return strcmp(a->body, b->body);
}
/***************************************************************/
/* */
@@ -43,24 +64,6 @@ FreeDedupeEntry(DedupeEntry *e)
free(e);
}
/***************************************************************/
/* */
/* GetDedupeBucket */
/* */
/* Get the bucket for a given date and body */
/* */
/***************************************************************/
static unsigned int
GetDedupeBucket(int trigger_date, int trigger_time, char const *body)
{
unsigned int bucket = trigger_date;
if (trigger_time != NO_TIME) {
bucket += trigger_time;
}
bucket += HashVal(body);
return bucket % DEDUPE_HASH_SLOTS;
}
/***************************************************************/
/* */
/* FindDedupeEntry */
@@ -72,19 +75,12 @@ static DedupeEntry *
FindDedupeEntry(int trigger_date, int trigger_time, char const *body)
{
DedupeEntry *e;
unsigned int bucket = GetDedupeBucket(trigger_date, trigger_time, body);
e = DedupeTable[bucket];
while(e) {
if (e->trigger_date == trigger_date &&
e->trigger_time == trigger_time &&
!strcmp(body, e->body)) {
return e;
}
e = e->next;
}
return NULL;
DedupeEntry candidate;
candidate.body = body;
candidate.trigger_date = trigger_date;
candidate.trigger_time = trigger_time;
e = hash_table_find(&DedupeTable, &candidate);
return e;
}
/***************************************************************/
@@ -99,8 +95,6 @@ InsertDedupeEntry(int trigger_date, int trigger_time, char const *body)
{
DedupeEntry *e;
unsigned int bucket = GetDedupeBucket(trigger_date, trigger_time, body);
e = malloc(sizeof(DedupeEntry));
if (!e) {
return; /* No error checking... what can we do? */
@@ -113,8 +107,7 @@ InsertDedupeEntry(int trigger_date, int trigger_time, char const *body)
return;
}
e->next = DedupeTable[bucket];
DedupeTable[bucket] = e;
hash_table_insert(&DedupeTable, e);
}
/***************************************************************/
@@ -149,16 +142,18 @@ void
ClearDedupeTable(void)
{
DedupeEntry *e, *next;
for (int i=0; i<DEDUPE_HASH_SLOTS; i++) {
e = DedupeTable[i];
while (e) {
next = e->next;
FreeDedupeEntry(e);
e = next;
}
DedupeTable[i] = NULL;
e = hash_table_next(&DedupeTable, NULL);
while(e) {
next = hash_table_next(&DedupeTable, e);
hash_table_delete_no_resize(&DedupeTable, e);
FreeDedupeEntry(e);
e = next;
}
hash_table_free(&DedupeTable);
InitDedupeTable();
}
/***************************************************************/
/* */
/* InitDedupeTable */
@@ -169,31 +164,20 @@ ClearDedupeTable(void)
void
InitDedupeTable(void)
{
for (int i=0; i<DEDUPE_HASH_SLOTS; i++) {
DedupeTable[i] = NULL;
if (hash_table_init(&DedupeTable,
offsetof(DedupeEntry, link),
DedupeHashFunc, CompareDedupes) < 0) {
fprintf(stderr, "Unable to initialize function hash table: Out of memory. Exiting.\n");
exit(1);
}
}
void
get_dedupe_hash_stats(int *total, int *maxlen, double *avglen)
{
int len;
int i;
DedupeEntry *e;
*maxlen = 0;
*total = 0;
for (i=0; i<DEDUPE_HASH_SLOTS; i++) {
len = 0;
e = DedupeTable[i];
while (e) {
len++;
(*total)++;
e = e->next;
}
if (len > *maxlen) {
*maxlen = len;
}
}
*avglen = (double) *total / (double) DEDUPE_HASH_SLOTS;
struct hash_table_stats s;
hash_table_get_stats(&DedupeTable, &s);
*total = s.num_entries;
*maxlen = s.max_len;
*avglen = s.avg_len;
}
+1 -1
View File
@@ -2019,7 +2019,7 @@ static int make_atom(expr_node *atom, Var *locals)
atom->u.arg = i;
return OK;
}
v = v->next;
v = v->link.next;
i++;
}
if (strlen(s) < SHORT_NAME_BUF) {
+19 -1
View File
@@ -68,6 +68,7 @@ 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 *);
@@ -226,7 +227,7 @@ static int CacheHebYear, CacheHebMon, CacheHebDay;
/* 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},
@@ -372,6 +373,23 @@ static int RetStrVal(char const *s, func_info *info)
}
/***************************************************************/
/* */
/* F_ - look up a string in the translation table */
/* */
/***************************************************************/
static int F_(func_info *info)
{
char const *translated;
ASSERT_TYPE(0, STR_TYPE);
translated = GetTranslatedString(ARGSTR(0));
if (!translated) {
DCOPYVAL(RetVal, ARG(0));
return OK;
}
return RetStrVal(translated, info);
}
/***************************************************************/
/* */
/* FStrlen - string length */
+450
View File
@@ -0,0 +1,450 @@
/***************************************************************/
/* */
/* HASHTAB_STATS.C */
/* */
/* Implementation of hash table. */
/* */
/* This file is part of REMIND. */
/* Copyright (C) 1992-2024 by Dianne Skoll */
/* SPDX-License-Identifier: GPL-2.0-only */
/* */
/***************************************************************/
/**
* \file hashtab.c
*
* \brief Implementation of hash table
*
* A hash table manages an array of buckets, each of which is the
* head of a singly-linked list. A given hash table can store items
* of a given type. The items in a hash table must be structs, and one
* of their members must be a struct hash_link object. For example,
* a hash table containing integers might have the hash objects
* defined as:
*
* struct int_object {
* int value;
* struct hash_link link;
* };
*
* When you initialize the hash table, you pass in the offset to the hash
* link. For example, to initialize a hash table designed to hold
* int_objects, you'd do something like:
*
* unsigned int hash_int_obj(void *x) {
* return (unsigned int) ((int_object *) x)->value;
* }
* int compare_int_obj(void *a, void *b) {
* return ((int_object *)a)->value - ((int_object *)b)->value;
* }
*
* hash_table tab;
* hash_table_init(&tab, offsetof(struct int_object, link), hash_int_obj, compare_int_obj);
*
* An item can be in multiple hash tables at once; just declare multiple
* hash_link members and pass in the appropriate offset to each hash
* table.
*/
#include "hashtab.h"
#include <stdlib.h>
#include <errno.h>
/*
* The number of buckets should be a prime number.
* Use these numbers of buckets to grow or shrink the hash table.
* Yes, OK, the list below is probably excessive.
*/
/**
* \brief A list of prime numbers from 17 to about 1.4 billion, approximately
* doubling with each successive number.
*
* These are used as choices for the number of hash buckets in the table
*/
static size_t bucket_choices[] = {
17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911, 43853, 87719,
175447, 350899, 701819, 1403641, 2807303, 5614657, 11229331, 22458671,
44917381, 89834777, 179669557, 359339171, 718678369, 1437356741 };
#define NUM_BUCKET_CHOICES (sizeof(bucket_choices) / sizeof(bucket_choices[0]))
#define NUM_BUCKETS(t) (bucket_choices[t->bucket_choice_index])
#define LINK(t, p) ( (struct hash_link *) (( ((char *) p) + t->hash_link_offset)) )
/**
* \brief Initialize a hash table
*
* Initializes a hash table. A given hash table can contain a collection
* of items, all of which must be the same. An item in a hash table is
* a structure and one of the elements in the structure must be a
* struct hash_link object. For example, if you are storing a collection
* of integers in a hash table, your item might look like this:
*
* struct item {
* int value;
* struct hash_link link;
* };
*
* \param t Pointer to a hash_table object
* \param link_offset The offset to the struct hash_link object within the object being put in the hash table. In the example above, it would be
* offsetof(struct item, link)
* \param hashfunc A pointer to a function that computes a hash given a pointer to an object. This function must return an unsigned int.
* \param compare A pointer to a function that compares two objects. It must
* return 0 if they compare equal and non-zero if they do not.
*
* \return 0 on success, -1 on failure (and errno is set appropriately)
*/
int
hash_table_init(hash_table *t,
size_t link_offset,
unsigned int (*hashfunc)(void *x),
int (*compare)(void *a, void *b))
{
t->bucket_choice_index = 0;
t->num_entries = 0;
t->hash_link_offset = link_offset;
t->hashfunc = hashfunc;
t->compare = compare;
t->buckets = malloc(sizeof(void *) * bucket_choices[0]);
if (!t->buckets) {
return -1;
}
for (size_t i=0; i<bucket_choices[0]; i++) {
t->buckets[i] = NULL;
}
return 0;
}
/**
* \brief Free memory used by a hash table
*
* \param t Pointer to a hash_table object
*/
void
hash_table_free(hash_table *t)
{
free(t->buckets);
t->buckets = NULL;
t->bucket_choice_index = -1;
t->num_entries = 0;
}
/**
* \brief Return the number of items in a hash table
*
* \param t Pointer to a hash_table object
*
* \return The number of items in the hash table
*/
size_t
hash_table_num_entries(hash_table *t)
{
return t->num_entries;
}
/**
* \brief Return the number of buckets in a hash table
*
* \param t Pointer to a hash_table object
*
* \return The number of buckets in the hash table
*/
size_t
hash_table_num_buckets(hash_table *t)
{
if (t->bucket_choice_index >= NUM_BUCKET_CHOICES) {
return 0;
}
return NUM_BUCKETS(t);
}
/**
* \brief Return the length of the i'th bucket chain
*
* If i >= num_buckets, returns (size_t) -1
*
* \param t Pointer to a hash_table object
* \param i The bucket whose length we want (0 to num_buckets-1)
* \return The length of the i'th bucket chain
*/
size_t
hash_table_chain_len(hash_table *t, size_t i)
{
if (i >= hash_table_num_buckets(t)) {
return (size_t) -1;
}
size_t len = 0;
void *ptr = t->buckets[i];
while(ptr) {
len++;
ptr = LINK(t, ptr)->next;
}
return len;
}
/**
* \brief Resize a hash table
*
* Resizes (either grows or shrinks) a hash table's bucket array
*
* \param t Pointer to a hash_table object
* \param dir Must be either 1 (to increase the bucket array size) or
* -1 (to decrease it).
* \return 0 on success, non-zero if resizing fails. NOTE: Currently, resizing
* cannot fail; if we fail to allocate memory for the new bucket array,
* we just keep the existing array. This behaviour may change in future.
*/
static int
hash_table_resize(hash_table *t, int dir)
{
if (dir != 1 && dir != -1) {
return 0;
}
if ((dir == -1 && t->bucket_choice_index == 0) ||
(dir == 1 && t->bucket_choice_index == NUM_BUCKET_CHOICES-1)) {
return 0;
}
size_t num_old_buckets = bucket_choices[t->bucket_choice_index];
size_t num_new_buckets = bucket_choices[t->bucket_choice_index + dir];
void **new_buckets = malloc(sizeof(void *) * num_new_buckets);
if (!new_buckets) {
/* Out of memory... just don't resize? */
return 0;
}
for (size_t j=0; j<num_new_buckets; j++) {
new_buckets[j] = NULL;
}
/* Move everything from the old buckets into the new */
for (size_t i=0; i<num_old_buckets; i++) {
if (!t->buckets[i]) {
continue;
}
void *p = t->buckets[i];
while(p) {
struct hash_link *l = LINK(t, p);
void *nxt = l->next;
size_t j = l->hashval % num_new_buckets;
l->next = new_buckets[j];
new_buckets[j] = p;
p = nxt;
}
}
free(t->buckets);
t->buckets = new_buckets;
t->bucket_choice_index += dir;
return 0;
}
/**
* \brief Insert an item into a hash table
*
* Inserts an item into a hash table. The item MUST NOT be freed as
* long as it is in a hash table
*
* \param t Pointer to a hash_table object
* \param item Pointer to the item to insert
*
* \return 0 on success, -1 on failure (and errno is set appropriately)
*/
int
hash_table_insert(hash_table *t, void *item)
{
if (!item) {
errno = EINVAL;
return -1;
}
unsigned int v = t->hashfunc(item);
struct hash_link *l = LINK(t, item);
l->hashval = v;
v = v % NUM_BUCKETS(t);
l->next = t->buckets[v];
t->buckets[v] = item;
t->num_entries++;
/* Grow table for load factor > 2 */
if (t->bucket_choice_index < NUM_BUCKET_CHOICES-1 &&
t->num_entries > 2 * NUM_BUCKETS(t)) {
return hash_table_resize(t, 1);
}
return 0;
}
/**
* \brief Find an item in a hash table
*
* \param t Pointer to a hash_table object
* \param candidate Pointer to an object to be sought in the table
*
* \return A pointer to the object if one that matches candidate is found. NULL if not found
*/
void *
hash_table_find(hash_table *t, void *candidate)
{
if (!candidate) {
return NULL;
}
unsigned int v = t->hashfunc(candidate);
void *ptr = t->buckets[v % NUM_BUCKETS(t)];
while(ptr) {
if (!t->compare(candidate, ptr)) {
return ptr;
}
ptr = LINK(t, ptr)->next;
}
return NULL;
}
/**
* \brief Find the next item in a hash table
*
* \param t Pointer to a hash table object
* \param obj Pointer to an object that was perviously returned by
* hash_table_find() or hash_table_find_next().
*
* \return A pointer to the next object matching obj, or NULL if
* no more exist
*/
void *
hash_table_find_next(hash_table *t, void *obj)
{
if (!obj) {
return NULL;
}
void *ptr = LINK(t, obj)->next;
while(ptr) {
if (!t->compare(obj, ptr)) {
return ptr;
}
ptr = LINK(t, ptr)->next;
}
return NULL;
}
/**
* \brief Delete an item from a hash table
*
* \param t Pointer to a hash_table object
* \param candidate Pointer to an object that is in the table and must be removed from it
* \param resize_ok If non-zero, then it's OK to resize the hash table.
*
* \return 0 on success, -1 on failure
*/
int
hash_table_delete_helper(hash_table *t, void *item, int resize_ok)
{
if (!item) {
errno = EINVAL;
return -1;
}
struct hash_link *l = LINK(t, item);
unsigned int v = l->hashval;
v = v % NUM_BUCKETS(t);
if (t->buckets[v] == item) {
t->buckets[v] = l->next;
t->num_entries--;
if (resize_ok) {
/* Shrink table for load factor < 1 */
if (t->bucket_choice_index > 0 &&
t->num_entries < NUM_BUCKETS(t) / 2) {
return hash_table_resize(t, -1);
}
}
return 0;
}
void *ptr = t->buckets[v];
while(ptr) {
struct hash_link *l2 = LINK(t, ptr);
if (l2->next == item) {
l2->next = l->next;
t->num_entries--;
/* Shrink table for load factor < 1 */
if (resize_ok) {
if (t->bucket_choice_index > 0 &&
t->num_entries < NUM_BUCKETS(t) / 2) {
return hash_table_resize(t, -1);
}
}
return 0;
}
ptr = l2->next;
}
/* Item not found in hash table */
errno = ENOENT;
return -1;
}
int
hash_table_delete(hash_table *t, void *item)
{
return hash_table_delete_helper(t, item, 1);
}
int
hash_table_delete_no_resize(hash_table *t, void *item)
{
return hash_table_delete_helper(t, item, 0);
}
/**
* \brief Iterate to the next item in a hash table
*
* Acts as an iterator. Given a pointer to an item in the hash
* table, returns the next item, or NULL if no more items. If the
* existing-item pointer is supplied as NULL, returns a pointer to the
* first item in the hash table. You can therefore iterate across the
* hash table like this*
*
* void *item = NULL;
* while ( (item = hash_table_next(&table, item) ) != NULL) {
* // Do something with item
* }
*
* NOTE that you MUST NOT modify the hash table while iterating over it.
*
* \param t Pointer to a hash_table object
* \param cur The current item. Supply as NULL to get the first item
*
* \return A pointer to the next item in the hash table, or NULL if there
* are no more items
*/
void *
hash_table_next(hash_table *t, void *cur)
{
size_t n_buckets = NUM_BUCKETS(t);
size_t start_bucket = 0;
if (cur) {
struct hash_link *l = LINK(t, cur);
if (l->next) {
return l->next;
}
/* End of this chain; start searching at the next bucket */
start_bucket = (l->hashval % n_buckets) + 1;
}
for (size_t i=start_bucket; i<n_buckets; i++) {
if (t->buckets[i]) {
return t->buckets[i];
}
}
return NULL;
}
+112
View File
@@ -0,0 +1,112 @@
/***************************************************************/
/* */
/* HASHTAB.H */
/* */
/* Header file for hash-table related functions. */
/* */
/* This file is part of REMIND. */
/* Copyright (C) 1992-2024 by Dianne Skoll */
/* SPDX-License-Identifier: GPL-2.0-only */
/* */
/***************************************************************/
/* For size_t */
#include <stdio.h>
/**
* \brief A structure for holding hash table chain links.
*
* This structure is embedded in a container structure to make up
* a hash table entry
*/
struct hash_link {
void *next; /**< Link to next item in the chain */
unsigned int hashval; /**< Cached hash function value */
};
/**
* \brief A hash table
*/
typedef struct {
unsigned int bucket_choice_index; /**< Index into array of possible bucket counts */
size_t num_entries; /**< Number of entries in the hash table */
size_t hash_link_offset; /**< Offset of the struct hash_link in the container */
void **buckets; /**< Array of buckets */
unsigned int (*hashfunc)(void *x); /**< Pointer to the hashing function */
int (*compare)(void *a, void *b); /**< Pointer to the comparison function */
} hash_table;
/**
* \brief Data type to hold statistics about a hash table
*/
struct hash_table_stats {
size_t num_entries; /**< Number of items in the hash table */
size_t num_buckets; /**< Number of buckets in the hash table */
size_t num_nonempty_buckets; /**< Number of non-emptry buckets */
size_t max_len; /**< Length of longest chain in the hash table */
size_t min_len; /**< Length of the shortest chain in the hash table */
double avg_len; /**< Average chain length */
double avg_nonempty_len; /**< Average chain length of non-empty bucket */
double stddev; /**< Standard deviation of chain lengths */
};
int hash_table_init(hash_table *t,
size_t link_offset,
unsigned int (*hashfunc)(void *x),
int (*compare)(void *a, void *b));
void hash_table_free(hash_table *t);
size_t hash_table_num_entries(hash_table *t);
size_t hash_table_num_buckets(hash_table *t);
size_t hash_table_chain_len(hash_table *t, size_t i);
int hash_table_insert(hash_table *t, void *item);
void *hash_table_find(hash_table *t, void *candidate);
void *hash_table_find_next(hash_table *t, void *obj);
int hash_table_delete(hash_table *t, void *item);
int hash_table_delete_no_resize(hash_table *t, void *item);
void *hash_table_next(hash_table *t, void *cur);
void hash_table_dump_stats(hash_table *t, FILE *fp);
void hash_table_get_stats(hash_table *t, struct hash_table_stats *stat);
/**
* \brief Iterate over all items in a hash table
*
* This macro iterates over all items in a hash table. Here is an
* example of how to use it:
*
* hash_table tab;
* void *item;
* hash_table_for_each(item, &tab) {
* // Do something with item
* }
*/
#define hash_table_for_each(item, t) \
for ((item) = hash_table_next((t), NULL); \
(item); \
(item) = hash_table_next((t), (item)))
/**
* \brief Iterate over all items in a hash table that match a candidate
*
* This macro iterates over all items in a hash table that match a
* candidate object. (In general, a hash table may contain multiple
* objects with the same key.) Here is an example assuming that the hash
* table holds objects of type struct int_object:
*
* struct int_object {
* int value;
* struct hash_link link;
* }
*
* hash_table tab;
* int_object candidate;
*
* candidate.value = 7;
* int_object *item;
* hash_table_for_each_matching(item, &candidate, &tab) {
* // Do something with item, which will match "7"
* }
*/
#define hash_table_for_each_matching(item, candidate, t) \
for ((item) = hash_table_find((t), (candidate)); \
(item); \
(item) = hash_table_find_next((t), (item)))
+96
View File
@@ -0,0 +1,96 @@
/***************************************************************/
/* */
/* HASHTAB_STATS.C */
/* */
/* Utility function to print hash table stats. */
/* */
/* This file is part of REMIND. */
/* Copyright (C) 1992-2024 by Dianne Skoll */
/* SPDX-License-Identifier: GPL-2.0-only */
/* */
/***************************************************************/
/**
* \file hashtab_stats.c
* \brief Obtain or print statistics about a hash table
*
* NOTE: Use of any of the functions in this file will require linking
* with the math library to pull in the sqrt() function.
*/
#include "hashtab.h"
#include <stdio.h>
#include <math.h>
/**
* \brief Dump hash table statistics to a stdio FILE
*
* \param t A pointer to a hash_table object
* \param fp A stdio file pointer that is writable
*/
void
hash_table_dump_stats(hash_table *t, FILE *fp)
{
struct hash_table_stats stat;
hash_table_get_stats(t, &stat);
fprintf(fp, "#Entries: %lu\n#Buckets: %lu\n#Non-empty Buckets: %lu\n",
(unsigned long) stat.num_entries,
(unsigned long) stat.num_buckets,
(unsigned long) stat.num_nonempty_buckets);
fprintf(fp, "Max len: %lu\nMin len: %lu\nAvg len: %.4f\nStd dev: %.4f\nAvg nonempty len: %.4f\n",
(unsigned long) stat.max_len,
(unsigned long) stat.min_len,
stat.avg_len, stat.stddev, stat.avg_nonempty_len);
}
/**
* \brief Obtain hash table statistics
*
* This function fills in the elements of a struct hash_table_stats object
* with hash table statistics.
*
* \param t A pointer to a hash_table object
* \param stat A pointer to a hash_table_stats object that will be filled in
*/
void
hash_table_get_stats(hash_table *t, struct hash_table_stats *stat)
{
size_t n = hash_table_num_buckets(t);
size_t max_len = 0;
size_t min_len = 1000000000;
stat->num_buckets = n;
stat->num_entries = hash_table_num_entries(t);
stat->max_len = 0;
stat->min_len = 0;
stat->avg_len = 0.0;
stat->stddev = 0.0;
stat->num_nonempty_buckets = 0;
stat->avg_nonempty_len = 0.0;
double sum = 0.0;
double sumsq = 0.0;
if (n == 0) {
return;
}
for (size_t i=0; i<n; i++) {
size_t c = hash_table_chain_len(t, i);
if (c != 0) {
stat->num_nonempty_buckets++;
}
sum += (double) c;
sumsq += (double) c * (double) c;
if (c > max_len) max_len = c;
if (c < min_len) min_len = c;
}
double avg_len = sum / (double) n;
double stddev = sqrt( (sumsq / (double) n) - (avg_len * avg_len) );
if (stat->num_nonempty_buckets > 0) {
stat->avg_nonempty_len = sum / (double) stat->num_nonempty_buckets;
}
stat->max_len = max_len;
stat->min_len = min_len;
stat->avg_len = avg_len;
stat->stddev = stddev;
}
+8
View File
@@ -179,6 +179,14 @@ void InitRemind(int argc, char const *argv[])
dse = NO_DATE;
/* Initialize variable hash table */
InitVars();
/* Initialize user-defined functions hash table */
InitUserFunctions();
InitTranslationTable();
/* If stdout is a terminal, initialize $FormWidth to terminal width-8,
but clamp to [20, 500] */
InitCalWidthAndFormWidth(STDOUT_FILENO);
+75
View File
@@ -71,6 +71,8 @@ exitfunc(void)
fprintf(stderr, " Func hash: total = %d; maxlen = %d; avglen = %.3f\n", total, maxlen, avglen);
get_dedupe_hash_stats(&total, &maxlen, &avglen);
fprintf(stderr, "Dedup hash: total = %d; maxlen = %d; avglen = %.3f\n", total, maxlen, avglen);
get_translation_hash_stats(&total, &maxlen, &avglen);
fprintf(stderr, "Trans hash: total = %d; maxlen = %d; avglen = %.3f\n", total, maxlen, avglen);
UnsetAllUserFuncs();
print_expr_nodes_stats();
}
@@ -352,6 +354,7 @@ static void DoReminders(void)
case T_Preserve: r=DoPreserve(&p); break;
case T_Push: r=PushOmitContext(&p); break;
case T_Expr: r = DoExpr(&p); break;
case T_Translate: r = DoTranslate(&p); break;
case T_RemType: if (tok.val == RUN_TYPE) {
r=DoRun(&p);
} else {
@@ -596,6 +599,78 @@ int ParseNonSpaceChar(ParsePtr p, int *err, int peek)
return ch;
}
/***************************************************************/
/* */
/* ParseQuotedString */
/* */
/* Parse a double-quote-delimited string. */
/* */
/***************************************************************/
int ParseQuotedString(ParsePtr p, DynamicBuffer *dbuf)
{
int c, err;
DBufFree(dbuf);
c = ParseNonSpaceChar(p, &err, 0);
if (err) return err;
if (!c) {
return E_EOLN;
}
if (c != '"') {
return E_MISS_QUOTE;
}
c = ParseChar(p, &err, 0);
if (err) {
DBufFree(dbuf);
return err;
}
while (c != '"') {
if (c == '\\') {
c = ParseChar(p, &err, 0);
if (err) {
DBufFree(dbuf);
return err;
}
switch(c) {
case 'a':
err = DBufPutc(dbuf, '\a');
break;
case 'b':
err = DBufPutc(dbuf, '\b');
break;
case 'f':
err = DBufPutc(dbuf, '\f');
break;
case 'n':
err = DBufPutc(dbuf, '\n');
break;
case 'r':
err = DBufPutc(dbuf, '\r');
break;
case 't':
err = DBufPutc(dbuf, '\t');
break;
case 'v':
err = DBufPutc(dbuf, '\v');
break;
default:
err = DBufPutc(dbuf, c);
}
} else {
err = DBufPutc(dbuf, c);
}
if (err) {
DBufFree(dbuf);
return err;
}
c = ParseChar(p, &err, 0);
if (err) {
DBufFree(dbuf);
return err;
}
}
return OK;
}
/***************************************************************/
/* */
/* ParseToken */
+11 -1
View File
@@ -87,6 +87,7 @@ void FromDSE (int dse, int *y, int *m, int *d);
int JulianToGregorianOffset(int y, int m);
int ParseChar (ParsePtr p, int *err, int peek);
int ParseToken (ParsePtr p, DynamicBuffer *dbuf);
int ParseQuotedString (ParsePtr p, DynamicBuffer *dbuf);
int ParseIdentifier (ParsePtr p, DynamicBuffer *dbuf);
expr_node * ParseExpr(ParsePtr p, int *r);
void print_expr_nodes_stats(void);
@@ -111,6 +112,8 @@ int DoDebug (ParsePtr p);
int DoBanner (ParsePtr p);
int DoRun (ParsePtr p);
int DoExpr (ParsePtr p);
int DoTranslate (ParsePtr p);
int InsertTranslation(char const *orig, char const *translated);
int DoErrMsg (ParsePtr p);
int ClearGlobalOmits (void);
int DoClear (ParsePtr p);
@@ -158,7 +161,8 @@ int DoPreserve (Parser *p);
int DoSatRemind (Trigger *trig, TimeTrig *tt, ParsePtr p);
int DoMsgCommand (char const *cmd, char const *msg, int is_queued);
int ParseNonSpaceChar (ParsePtr p, int *err, int peek);
unsigned int HashVal (char const *str);
unsigned int HashVal_ignorecase(char const *str);
unsigned int HashVal_preservecase(char const *str);
int DateOK (int y, int m, int d);
BuiltinFunc *FindBuiltinFunc (char const *name);
int InsertIntoSortBuffer (int dse, int tim, char const *body, int typ, int prio);
@@ -253,8 +257,14 @@ void print_remind_tokens(void);
void get_var_hash_stats(int *total, int *maxlen, double *avglen);
void get_userfunc_hash_stats(int *total, int *maxlen, double *avglen);
void get_dedupe_hash_stats(int *total, int *maxlen, double *avglen);
void get_translation_hash_stats(int *total, int *maxlen, double *avglen);
/* Dedupe code */
int ShouldDedupe(int trigger_date, int trigger_time, char const *body);
void ClearDedupeTable(void);
void InitDedupeTable(void);
void InitVars(void);
void InitUserFunctions(void);
void InitTranslationTable(void);
char const *GetTranslatedString(char const *orig);
+1
View File
@@ -111,6 +111,7 @@ Token TokArray[] = {
{ "third", 5, T_Ordinal, 2 },
{ "through", 7, T_Through, 0 },
{ "thursday", 3, T_WkDay, 3 },
{ "translate", 5, T_Translate, 0 },
{ "tuesday", 3, T_WkDay, 1 },
{ "unset", 5, T_UnSet, 0 },
{ "until", 5, T_Until, 0 },
+274
View File
@@ -0,0 +1,274 @@
/***************************************************************/
/* */
/* TRANS.C */
/* */
/* Functions to manage the translation table. Implements */
/* the TRANSLATE keyword. */
/* */
/* This file is part of REMIND. */
/* Copyright (C) 1992-2024 by Dianne Skoll */
/* SPDX-License-Identifier: GPL-2.0-only */
/* */
/***************************************************************/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include "types.h"
#include "globals.h"
#include "protos.h"
#include "err.h"
/* The structure of a translation item */
typedef struct xlat {
struct hash_link link;
char *orig;
char *translated;
} XlateItem;
hash_table TranslationTable;
/***************************************************************/
/* */
/* AllocateXlateItem - Allocate a new translation item */
/* */
/***************************************************************/
static XlateItem *
AllocateXlateItem(char const *orig, char const *translated)
{
size_t s1 = sizeof(XlateItem);
size_t s2 = strlen(orig)+1;
size_t s3 = strlen(translated)+1;
XlateItem *item;
char *blob = malloc(s1+s2+s3);
if (!blob) {
return NULL;
}
item = (XlateItem *) blob;
/* Allocate the string space in ONE go! */
item->orig = blob + s1;
item->translated = item->orig + s2;
strcpy(item->orig, orig);
strcpy(item->translated, translated);
return item;
}
/***************************************************************/
/* */
/* FreeXlateItem - Free a translation item */
/* */
/***************************************************************/
static void
FreeXlateItem(XlateItem *item)
{
if (!item) return;
free(item);
}
static void
RemoveTranslation(XlateItem *item)
{
hash_table_delete_no_resize(&TranslationTable, item);
FreeXlateItem(item);
}
/***************************************************************/
/* */
/* ClearTranslationTable - free all translation items */
/* */
/***************************************************************/
static void
ClearTranslationTable(void)
{
XlateItem *item;
XlateItem *next;
item = hash_table_next(&TranslationTable, NULL);
while(item) {
next = hash_table_next(&TranslationTable, item);
RemoveTranslation(item);
item = next;
}
hash_table_free(&TranslationTable);
InitTranslationTable();
}
static void
print_escaped_string(FILE *fp, char const *s)
{
putc('"', fp);
while(*s) {
switch(*s) {
case '\a': putc('\\', fp); putc('a', fp); break;
case '\b': putc('\\', fp); putc('b', fp); break;
case '\f': putc('\\', fp); putc('f', fp); break;
case '\n': putc('\\', fp); putc('n', fp); break;
case '\r': putc('\\', fp); putc('r', fp); break;
case '\t': putc('\\', fp); putc('t', fp); break;
case '\v': putc('\\', fp); putc('v', fp); break;
case '"': putc('\\', fp); putc('"', fp); break;
case '\\': putc('\\', fp); putc('\\', fp); break;
default:
if (*s < 32) {
fprintf(fp, "\\x%02x", (unsigned int) *s);
} else {
putc(*s, fp); break;
}
}
s++;
}
putc('"', fp);
}
/***************************************************************/
/* */
/* DumpTranslationTable - Dump the table to a file descriptor */
/* */
/***************************************************************/
static void
DumpTranslationTable(FILE *fp)
{
XlateItem *item;
item = hash_table_next(&TranslationTable, NULL);
while(item) {
print_escaped_string(fp, item->orig);
fprintf(fp, " ");
print_escaped_string(fp, item->translated);
fprintf(fp, "\n");
item = hash_table_next(&TranslationTable, item);
}
}
static unsigned int
HashXlateItem(void *x)
{
XlateItem *item = (XlateItem *) x;
return HashVal_preservecase(item->orig);
}
static int
CompareXlateItems(void *a, void *b)
{
XlateItem *i = (XlateItem *) a;
XlateItem *j = (XlateItem *) b;
return strcmp(i->orig, j->orig);
}
void
InitTranslationTable(void)
{
if (hash_table_init(&TranslationTable, offsetof(XlateItem, link),
HashXlateItem, CompareXlateItems) < 0) {
fprintf(stderr, "Unable to initialize translation hash table: Out of memory. Exiting.\n");
exit(1);
}
}
static XlateItem *
FindTranslation(char const *orig)
{
XlateItem *item;
XlateItem candidate;
candidate.orig = (char *) orig;
item = hash_table_find(&TranslationTable, &candidate);
return item;
}
int
InsertTranslation(char const *orig, char const *translated)
{
XlateItem *item = FindTranslation(orig);
if (item) {
if (!strcmp(item->translated, translated)) {
/* Translation is the same; do nothing */
return OK;
}
RemoveTranslation(item);
}
item = AllocateXlateItem(orig, translated);
if (!item) {
return E_NO_MEM;
}
hash_table_insert(&TranslationTable, item);
return OK;
}
char const *
GetTranslatedString(char const *orig)
{
XlateItem *item = FindTranslation(orig);
if (!item) return NULL;
return item->translated;
}
int
DoTranslate(ParsePtr p)
{
int r;
DynamicBuffer orig, translated;
DBufInit(&orig);
DBufInit(&translated);
int c;
c = ParseNonSpaceChar(p, &r, 1);
if (r) return r;
if (c != '"') {
r = ParseToken(p, &orig);
if (r) return r;
if (!StrCmpi(DBufValue(&orig), "dump")) {
DumpTranslationTable(stdout);
return OK;
}
if (!StrCmpi(DBufValue(&orig), "clear")) {
ClearTranslationTable();
return OK;
}
return E_PARSE_ERR;
}
if ( (r=ParseQuotedString(p, &orig)) ) {
return r;
}
if ( (r=ParseQuotedString(p, &translated)) ) {
if (r == E_EOLN) {
XlateItem *item = FindTranslation(DBufValue(&orig));
if (item) {
RemoveTranslation(item);
}
r = OK;
}
DBufFree(&orig);
return r;
}
if ( (r=VerifyEoln(p)) ) {
DBufFree(&orig);
DBufFree(&translated);
return r;
}
r = InsertTranslation(DBufValue(&orig), DBufValue(&translated));
DBufFree(&orig);
DBufFree(&translated);
return r;
}
void
get_translation_hash_stats(int *total, int *maxlen, double *avglen)
{
struct hash_table_stats s;
hash_table_get_stats(&TranslationTable, &s);
*total = s.num_entries;
*maxlen = s.max_len;
*avglen = s.avg_len;
}
+4 -3
View File
@@ -12,6 +12,7 @@
#include <limits.h>
#include "dynbuf.h"
#include "hashtab.h"
typedef struct udf_struct UserFunc;
@@ -99,7 +100,7 @@ typedef struct expr_node_struct {
/* Define the structure of a variable */
typedef struct var {
struct var *next;
struct hash_link link;
char name[VAR_NAME_LEN+1];
char preserve;
Value v;
@@ -217,7 +218,7 @@ enum TokTypes
T_MaybeUncomputable, T_Month, T_NoQueue, T_Number, T_Omit, T_OmitFunc,
T_Once, T_Ordinal, T_Pop, T_Preserve, T_Priority, T_Push,T_Rem,
T_RemType, T_Rep, T_Scanfrom, T_Sched, T_Set, T_Skip, T_Tag, T_Through,
T_Time, T_UnSet, T_Until, T_Warn, T_WkDay, T_Year
T_Time, T_Translate, T_UnSet, T_Until, T_Warn, T_WkDay, T_Year
};
/* The structure of a token */
@@ -291,7 +292,7 @@ typedef struct {
/* Define the data structure used to hold a user-defined function */
typedef struct udf_struct {
struct udf_struct *next;
struct hash_link link;
char name[VAR_NAME_LEN+1];
expr_node *node;
char **args;
+57 -76
View File
@@ -15,6 +15,7 @@
#include <stdio.h>
#include <ctype.h>
#include <stddef.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
@@ -27,23 +28,46 @@
#include "protos.h"
#include "err.h"
#define FUNC_HASH_SIZE 31 /* Size of User-defined function hash table */
/* The hash table */
static UserFunc *FuncHash[FUNC_HASH_SIZE];
hash_table FuncHash;
static void DestroyUserFunc (UserFunc *f);
static void FUnset (char const *name);
static void FSet (UserFunc *f);
static void RenameUserFunc(char const *oldname, char const *newname);
static unsigned int HashUserFunc(void *x)
{
UserFunc *f = (UserFunc *) x;
return HashVal_preservecase(f->name);
}
static int CompareUserFuncs(void *a, void *b)
{
UserFunc *f = (UserFunc *) a;
UserFunc *g = (UserFunc *) b;
return strcmp(f->name, g->name);
}
void
InitUserFunctions(void)
{
if (hash_table_init(&FuncHash,
offsetof(UserFunc, link),
HashUserFunc,
CompareUserFuncs) < 0) {
fprintf(stderr, "Unable to initialize function hash table: Out of memory. Exiting.\n");
exit(1);
}
}
/***************************************************************/
/* */
/* HashVal */
/* HashVal_preservecase */
/* Given a string, compute the hash value. */
/* */
/***************************************************************/
unsigned int HashVal_nocase(char const *str)
unsigned int HashVal_preservecase(char const *str)
{
unsigned int h = 0, high;
while(*str) {
@@ -257,8 +281,8 @@ int DoFset(ParsePtr p)
}
local_array[i].v.type = ERR_TYPE;
StrnCpy(local_array[i].name, DBufValue(&buf), VAR_NAME_LEN);
local_array[i].next = &(local_array[i+1]);
local_array[i+1].next = NULL;
local_array[i].link.next = &(local_array[i+1]);
local_array[i+1].link.next = NULL;
func->nargs++;
c = ParseNonSpaceChar(p, &r, 0);
if (r) {
@@ -373,21 +397,11 @@ static void DestroyUserFunc(UserFunc *f)
/***************************************************************/
static void FUnset(char const *name)
{
UserFunc *cur, *prev;
int h;
h = HashVal_nocase(name) % FUNC_HASH_SIZE;
cur = FuncHash[h];
prev = NULL;
while(cur) {
if (! strncmp(name, cur->name, VAR_NAME_LEN)) break;
prev = cur;
cur = cur->next;
UserFunc *f = FindUserFunc(name);
if (f) {
hash_table_delete(&FuncHash, f);
DestroyUserFunc(f);
}
if (!cur) return;
if (prev) prev->next = cur->next; else FuncHash[h] = cur->next;
DestroyUserFunc(cur);
}
/***************************************************************/
@@ -399,19 +413,17 @@ static void FUnset(char const *name)
/***************************************************************/
static void FSet(UserFunc *f)
{
int h = HashVal_nocase(f->name) % FUNC_HASH_SIZE;
f->next = FuncHash[h];
FuncHash[h] = f;
hash_table_insert(&FuncHash, f);
}
UserFunc *FindUserFunc(char const *name)
{
UserFunc *f;
int h = HashVal_nocase(name) % FUNC_HASH_SIZE;
UserFunc candidate;
/* Search for the function */
f = FuncHash[h];
while (f && strncmp(name, f->name, VAR_NAME_LEN)) f = f->next;
StrnCpy(candidate.name, name, VAR_NAME_LEN);
f = hash_table_find(&FuncHash, &candidate);
return f;
}
@@ -444,16 +456,16 @@ UnsetAllUserFuncs(void)
{
UserFunc *f;
UserFunc *next;
int i;
for (i=0; i<FUNC_HASH_SIZE; i++) {
f = FuncHash[i];
while(f) {
next = f->next;
DestroyUserFunc(f);
f = next;
}
FuncHash[i] = NULL;
f = hash_table_next(&FuncHash, NULL);
while(f) {
next = hash_table_next(&FuncHash, f);
hash_table_delete_no_resize(&FuncHash, f);
DestroyUserFunc(f);
f = next;
}
hash_table_free(&FuncHash);
InitUserFunctions();
}
/***************************************************************/
@@ -469,7 +481,6 @@ static void
RenameUserFunc(char const *oldname, char const *newname)
{
UserFunc *f = FindUserFunc(oldname);
UserFunc *cur, *prev;
if (!strcmp(oldname, newname)) {
/* Same name; do nothing */
@@ -485,52 +496,22 @@ RenameUserFunc(char const *oldname, char const *newname)
}
/* Remove from hash table */
int h = HashVal_nocase(f->name) % FUNC_HASH_SIZE;
cur = FuncHash[h];
prev = NULL;
while(cur) {
if (cur == f) {
if (prev) {
prev->next = cur->next;
} else {
FuncHash[h] = cur->next;
}
break;
}
prev = cur;
cur = cur->next;
}
hash_table_delete(&FuncHash, f);
/* Rename */
StrnCpy(f->name, newname, VAR_NAME_LEN);
/* Insert into hash table */
h = HashVal_nocase(f->name) % FUNC_HASH_SIZE;
f->next = FuncHash[h];
FuncHash[h] = f;
hash_table_insert(&FuncHash, f);
}
void
get_userfunc_hash_stats(int *total, int *maxlen, double *avglen)
{
int len;
int i;
UserFunc *f;
*maxlen = 0;
*total = 0;
for (i=0; i<FUNC_HASH_SIZE; i++) {
len = 0;
f = FuncHash[i];
while(f) {
len++;
(*total)++;
f = f->next;
}
if (len > *maxlen) {
*maxlen = len;
}
}
*avglen = (double) *total / (double) FUNC_HASH_SIZE;
struct hash_table_stats s;
hash_table_get_stats(&FuncHash, &s);
*total = s.num_entries;
*maxlen = s.max_len;
*avglen = s.avg_len;
}
+101 -85
View File
@@ -17,6 +17,7 @@
#include <string.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
@@ -27,8 +28,6 @@
#include "err.h"
#define UPPER(c) (islower(c) ? toupper(c) : c)
/* The variable hash table */
#define VAR_HASH_SIZE 67
#define VARIABLE ErrMsg[E_VAR]
#define VALUE ErrMsg[E_VAL]
#define UNDEF ErrMsg[E_UNDEF]
@@ -36,7 +35,30 @@
static int IntMin = INT_MIN;
static int IntMax = INT_MAX;
static Var *VHashTbl[VAR_HASH_SIZE];
static hash_table VHashTbl;
static unsigned int VarHashFunc(void *x)
{
Var *v = (Var *) x;
return HashVal_ignorecase(v->name);
}
static int VarCompareFunc(void *a, void *b)
{
Var *x = (Var *) a;
Var *y = (Var *) b;
return StrCmpi(x->name, y->name);
}
void
InitVars(void)
{
if (hash_table_init(&VHashTbl, offsetof(Var, link),
VarHashFunc, VarCompareFunc) < 0) {
fprintf(stderr, "Unable to initialize variable hash table: Out of memory. Exiting.\n");
exit(1);
}
}
static double
strtod_in_c_locale(char const *str, char **endptr)
@@ -455,11 +477,11 @@ static int time_sep_func(int do_set, Value *val)
/***************************************************************/
/* */
/* HashVal */
/* Given a string, compute the hash value. */
/* HashVal_ignorecase */
/* Given a string, compute the hash value case-insensitively */
/* */
/***************************************************************/
unsigned int HashVal(char const *str)
unsigned int HashVal_ignorecase(char const *str)
{
unsigned int h = 0, high;
while(*str) {
@@ -483,31 +505,22 @@ unsigned int HashVal(char const *str)
/***************************************************************/
Var *FindVar(char const *str, int create)
{
register int h;
register Var *v;
register Var *prev;
Var *v;
Var candidate;
StrnCpy(candidate.name, str, VAR_NAME_LEN);
h = HashVal(str) % VAR_HASH_SIZE;
v = VHashTbl[h];
prev = NULL;
v = (Var *) hash_table_find(&VHashTbl, &candidate);
if (v != NULL || !create) return v;
while(v) {
if (! StrinCmp(str, v->name, VAR_NAME_LEN)) return v;
prev = v;
v = v-> next;
}
if (!create) return v;
/* Create the variable */
/* Create the variable */
v = NEW(Var);
if (!v) return v;
v->next = NULL;
v->v.type = INT_TYPE;
v->v.v.val = 0;
v->preserve = 0;
StrnCpy(v->name, str, VAR_NAME_LEN);
if (prev) prev->next = v; else VHashTbl[h] = v;
hash_table_insert(&VHashTbl, v);
return v;
}
@@ -520,23 +533,12 @@ Var *FindVar(char const *str, int create)
/***************************************************************/
int DeleteVar(char const *str)
{
register int h;
register Var *v;
register Var *prev;
Var *v;
h = HashVal(str) % VAR_HASH_SIZE;
v = VHashTbl[h];
prev = NULL;
while(v) {
if (! StrinCmp(str, v->name, VAR_NAME_LEN)) break;
prev = v;
v = v-> next;
}
v = FindVar(str, 0);
if (!v) return E_NOSUCH_VAR;
DestroyValue(v->v);
if (prev) prev->next = v->next; else VHashTbl[h] = v->next;
free(v);
hash_table_delete(&VHashTbl, v);
return OK;
}
@@ -725,19 +727,14 @@ int DoDump(ParsePtr p)
/***************************************************************/
void DumpVarTable(void)
{
register Var *v;
register int i;
Var *v;
fprintf(ErrFp, "%s %s\n\n", VARIABLE, VALUE);
for (i=0; i<VAR_HASH_SIZE; i++) {
v = VHashTbl[i];
while(v) {
fprintf(ErrFp, "%s ", v->name);
PrintValue(&(v->v), ErrFp);
fprintf(ErrFp, "\n");
v = v->next;
}
hash_table_for_each(v, &VHashTbl) {
fprintf(ErrFp, "%s ", v->name);
PrintValue(&(v->v), ErrFp);
fprintf(ErrFp, "\n");
}
}
@@ -751,27 +748,22 @@ void DumpVarTable(void)
/***************************************************************/
void DestroyVars(int all)
{
int i;
Var *v, *next, *prev;
Var *v;
Var *next;
for (i=0; i<VAR_HASH_SIZE; i++) {
v = VHashTbl[i];
VHashTbl[i] = NULL;
prev = NULL;
while(v) {
if (all || !v->preserve) {
DestroyValue(v->v);
next = v->next;
free(v);
} else {
if (prev) prev->next = v;
else VHashTbl[i] = v;
prev = v;
next = v->next;
v->next = NULL;
}
v = next;
v = hash_table_next(&VHashTbl, NULL);
while(v) {
next = hash_table_next(&VHashTbl, v);
if (all || !v->preserve) {
DestroyValue(v->v);
hash_table_delete_no_resize(&VHashTbl, v);
free(v);
}
v = next;
}
if (all) {
hash_table_free(&VHashTbl);
InitVars();
}
}
@@ -967,6 +959,44 @@ static SysVar SysVarArr[] = {
#define NUMSYSVARS ( sizeof(SysVarArr) / sizeof(SysVar) )
static void DumpSysVar (char const *name, const SysVar *v);
static void HandleTranslatableVariable(char **var)
{
if (var == (char **) &DynamicAgo) InsertTranslation("ago", *var);
else if (var == (char **) &DynamicAm) InsertTranslation("am", *var);
else if (var == (char **) &DynamicAnd) InsertTranslation("and", *var);
else if (var == (char **) &DynamicAt) InsertTranslation("at", *var);
else if (var == (char **) &DynamicFromnow) InsertTranslation("from now", *var);
else if (var == (char **) &DynamicHour) InsertTranslation("hour", *var);
else if (var == (char **) &DynamicIs) InsertTranslation("is", *var);
else if (var == (char **) &DynamicMinute) InsertTranslation("minute", *var);
else if (var == (char **) &DynamicNow) InsertTranslation("now", *var);
else if (var == (char **) &DynamicOn) InsertTranslation("on", *var);
else if (var == (char **) &DynamicPm) InsertTranslation("pm", *var);
else if (var == (char **) &DynamicToday) InsertTranslation("today", *var);
else if (var == (char **) &DynamicTomorrow) InsertTranslation("tomorrow", *var);
else if (var == (char **) &DynamicWas) InsertTranslation("was", *var);
else if (var == (char **) &DynamicMonthName[0]) InsertTranslation("January", *var);
else if (var == (char **) &DynamicMonthName[1]) InsertTranslation("February", *var);
else if (var == (char **) &DynamicMonthName[2]) InsertTranslation("March", *var);
else if (var == (char **) &DynamicMonthName[3]) InsertTranslation("April", *var);
else if (var == (char **) &DynamicMonthName[4]) InsertTranslation("May", *var);
else if (var == (char **) &DynamicMonthName[5]) InsertTranslation("June", *var);
else if (var == (char **) &DynamicMonthName[6]) InsertTranslation("July", *var);
else if (var == (char **) &DynamicMonthName[7]) InsertTranslation("August", *var);
else if (var == (char **) &DynamicMonthName[8]) InsertTranslation("September", *var);
else if (var == (char **) &DynamicMonthName[9]) InsertTranslation("October", *var);
else if (var == (char **) &DynamicMonthName[10]) InsertTranslation("November", *var);
else if (var == (char **) &DynamicMonthName[11]) InsertTranslation("December", *var);
else if (var == (char **) &DynamicDayName[0]) InsertTranslation("Monday", *var);
else if (var == (char **) &DynamicDayName[1]) InsertTranslation("Tuesday", *var);
else if (var == (char **) &DynamicDayName[2]) InsertTranslation("Wednesday", *var);
else if (var == (char **) &DynamicDayName[3]) InsertTranslation("Thursday", *var);
else if (var == (char **) &DynamicDayName[4]) InsertTranslation("Friday", *var);
else if (var == (char **) &DynamicDayName[5]) InsertTranslation("Saturday", *var);
else if (var == (char **) &DynamicDayName[6]) InsertTranslation("Sunday", *var);
}
/***************************************************************/
/* */
/* SetSysVar */
@@ -1005,6 +1035,7 @@ int SetSysVar(char const *name, Value *value)
v->been_malloced = 1;
*((char **) v->value) = value->v.str;
value->type = ERR_TYPE; /* So that it's not accidentally freed */
HandleTranslatableVariable((char **) v->value);
} else {
if (v->max != ANY && value->v.val > v->max) return E_2HIGH;
if (v->min != ANY && value->v.val < v->min) return E_2LOW;
@@ -1214,24 +1245,9 @@ print_sysvar_tokens(void)
void
get_var_hash_stats(int *total, int *maxlen, double *avglen)
{
int len;
int i;
Var *v;
*maxlen = 0;
*total = 0;
for (i=0; i<VAR_HASH_SIZE; i++) {
len = 0;
v = VHashTbl[i];
while(v) {
len++;
(*total)++;
v = v->next;
}
if (len > *maxlen) {
*maxlen = len;
}
}
*avglen = (double) *total / (double) VAR_HASH_SIZE;
struct hash_table_stats s;
hash_table_get_stats(&VHashTbl, &s);
*total = s.num_entries;
*maxlen = s.max_len;
*avglen = s.avg_len;
}
+10235 -127
View File
File diff suppressed because it is too large Load Diff
+63
View File
@@ -1184,8 +1184,71 @@ REM MSG Hello
FSET msgsuffix(x) char(8) + " on the same line"
REM MSG Hello
# Test TRANSLATE
set a _("Hello")
TRANSLATE "Hello" "Goodbye"
set a _("Hello")
TRANSLATE "Hello" "Hallo!!!!!"
set a _("Hello")
TRANSLATE DUMP
TRANSLATE CLEAR
TRANSLATE DUMP
set a _("Hello")
TRANSLATE "Wookie" "Bar"
TRANSLATE "Bar" "Quux"
TRANSLATE "Quux" "OOOGHY"
# Delete Bar translation
TRANSLATE "Bar"
set a _("Wookie")
set a _("Bar")
set a _("Quux")
SET $ago "Txago"
SET $am "Txam"
SET $and "Txand"
SET $at "Txat"
SET $fromnow "Txfromnow"
SET $hour "Txhour"
SET $is "Txis"
SET $minute "Txminute"
SET $now "Txnow"
SET $on "Txon"
SET $pm "Txpm"
SET $today "Txtoday"
SET $tomorrow "Txtomorrow"
SET $was "Txwas"
SET $January "TxJanuary"
SET $February "TxFebruary"
SET $March "TxMarch"
SET $April "TxApril"
SET $May "TxMay"
SET $June "TxJune"
SET $July "TxJuly"
SET $August "TxAugust"
SET $September "TxSeptember"
SET $October "TxOctober"
SET $November "TxNovember"
SET $December "TxDecember"
SET $Monday "TxMonday"
SET $Tuesday "TxTuesday"
SET $Wednesday "TxWednesday"
SET $Thursday "TxThursday"
SET $Friday "TxFriday"
SET $Saturday "TxSaturday"
SET $Sunday "TxSunday"
TRANSLATE DUMP
DO torture-test.rem
# Output expression-node stats
DEBUG +s
# Don't want Remind to queue reminders
EXIT
+310011
View File
File diff suppressed because it is too large Load Diff