Implement TRANSLATE keyword.

This commit is contained in:
Dianne Skoll
2024-12-09 12:56:40 -05:00
parent cb712ad7e7
commit 973019c4c7
11 changed files with 377 additions and 3 deletions

View File

@@ -29,7 +29,7 @@ MANS= $(srcdir)/../man/rem2ps.1 $(srcdir)/../man/remind.1 \
REMINDSRCS= calendar.c dedupe.c dynbuf.c dorem.c dosubst.c expr.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 trigger.c userfns.c utils.c var.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 hashtab.h \
lang.h md5.h protos.h rem2ps.h types.h version.h

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;

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 */

View File

@@ -185,6 +185,8 @@ void InitRemind(int argc, char const *argv[])
/* 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);

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 {
@@ -609,6 +612,9 @@ int ParseQuotedString(ParsePtr p, DynamicBuffer *dbuf)
DBufFree(dbuf);
c = ParseNonSpaceChar(p, &err, 0);
if (err) return err;
if (!c) {
return E_EOLN;
}
if (c != '"') {
return E_MISS_QUOTE;
}

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,7 @@ int DoDebug (ParsePtr p);
int DoBanner (ParsePtr p);
int DoRun (ParsePtr p);
int DoExpr (ParsePtr p);
int DoTranslate (ParsePtr p);
int DoErrMsg (ParsePtr p);
int ClearGlobalOmits (void);
int DoClear (ParsePtr p);
@@ -253,10 +255,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);

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 },

271
src/trans.c Normal file
View File

@@ -0,0 +1,271 @@
/***************************************************************/
/* */
/* 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)
{
XlateItem *item = NEW(XlateItem);
if (!item) {
return NULL;
}
/* Allocate the string space in ONE go! */
char *s = malloc(strlen(orig) + strlen(translated) + 2);
if (!s) {
free(item);
return NULL;
}
item->orig = s;
item->translated = s + strlen(orig) + 1;
strcpy(item->orig, orig);
strcpy(item->translated, translated);
return item;
}
/***************************************************************/
/* */
/* FreeXlateItem - Free a translation item */
/* */
/***************************************************************/
static void
FreeXlateItem(XlateItem *item)
{
if (!item) return;
if (item->orig) {
free(item->orig);
}
free(item);
}
static void
RemoveTranslation(XlateItem *item)
{
hash_table_delete(&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;
}
}
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(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)
{
hash_table_init(&TranslationTable, offsetof(XlateItem, link),
HashXlateItem, CompareXlateItems);
}
static XlateItem *
FindTranslation(char const *orig)
{
XlateItem *item;
XlateItem candidate;
candidate.orig = (char *) orig;
item = hash_table_find(&TranslationTable, &candidate);
return item;
}
static 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;
}

View File

@@ -218,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 */

View File

@@ -5966,6 +5966,43 @@ Leaving UserFN msgsuffix(5000) => "\b on the same line"
Hello on the same line
# Test TRANSLATE
set a _("Hello")
_("Hello") => "Hello"
TRANSLATE "Hello" "Goodbye"
set a _("Hello")
_("Hello") => "Goodbye"
TRANSLATE "Hello" "Hallo!!!!!"
set a _("Hello")
_("Hello") => "Hallo!!!!!"
TRANSLATE DUMP
"Hello" "Hallo!!!!!"
TRANSLATE CLEAR
TRANSLATE DUMP
set a _("Hello")
_("Hello") => "Hello"
TRANSLATE "Wookie" "Bar"
TRANSLATE "Bar" "Quux"
TRANSLATE "Quux" "OOOGHY"
# Delete Bar translation
TRANSLATE "Bar"
set a _("Wookie")
_("Wookie") => "Bar"
set a _("Bar")
_("Bar") => "Bar"
set a _("Quux")
_("Quux") => "OOOGHY"
TRANSLATE DUMP
"Wookie" "Bar"
"Quux" "OOOGHY"
# Output expression-node stats
DEBUG +s
# Don't want Remind to queue reminders
@@ -5973,6 +6010,7 @@ EXIT
Var hash: total = 141; maxlen = 4; avglen = 1.785
Func hash: total = 17; maxlen = 3; avglen = 1.000
Dedup hash: total = 0; maxlen = 0; avglen = 0.000
Trans hash: total = 2; maxlen = 1; avglen = 0.118
Expression nodes allocated: 128
Expression nodes high-water: 74
Expression nodes leaked: 0
@@ -13427,6 +13465,7 @@ No reminders.
Var hash: total = 1; maxlen = 1; avglen = 0.059
Func hash: total = 0; maxlen = 0; avglen = 0.000
Dedup hash: total = 0; maxlen = 0; avglen = 0.000
Trans hash: total = 0; maxlen = 0; avglen = 0.000
Expression nodes allocated: 512
Expression nodes high-water: 499
Expression nodes leaked: 0
@@ -13666,6 +13705,8 @@ special
tag
third
through
trans
translate
unset
until
warn
@@ -13715,6 +13756,7 @@ wednesday
# Built-in Functions
_
abs
access
adawn

View File

@@ -1184,6 +1184,33 @@ 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")
TRANSLATE DUMP
# Output expression-node stats
DEBUG +s
# Don't want Remind to queue reminders