Add Pascal string builder module

This will be used for formatting messages for the user. The idea is to
have something more usable than the Mac OS toolbox ParamText function.
This commit is contained in:
Dietrich Epp 2023-04-19 22:14:34 -04:00
parent a4ce2924db
commit def1c2c253
4 changed files with 291 additions and 0 deletions

16
macos/BUILD.bazel Normal file
View File

@ -0,0 +1,16 @@
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load("//bazel:copts.bzl", "COPTS")
cc_test(
name = "pstrbuilder_test",
size = "small",
srcs = [
"pstrbuilder.c",
"pstrbuilder.h",
"pstrbuilder_test.c",
],
copts = COPTS,
deps = [
"//lib",
],
)

72
macos/pstrbuilder.c Normal file
View File

@ -0,0 +1,72 @@
// Copyright 2022-2023 Dietrich Epp.
// This file is part of SyncFiles. SyncFiles is licensed under the terms of the
// Mozilla Public License, version 2.0. See LICENSE.txt for details.
#include "macos/pstrbuilder.h"
#include <string.h>
static void PStrAppendMem(struct PStrBuilder *buf, const unsigned char *src,
int slen)
{
int dlen;
if (slen == 0 || buf->truncated) {
return;
}
dlen = buf->data[0];
if (slen + dlen > 255) {
// Too long, truncate.
// FIXME: Handle other scripts:
// - Truncation should respect multibyte character boundaries.
// - Ellipsis should be taken from internationalization table.
slen = 255 - dlen;
memcpy(buf->data + 1 + dlen, src, slen);
dlen += slen;
if (dlen >= 255) {
dlen = 254;
}
buf->data[1 + dlen] = 0xc9; // 0xc9 is ellipsis.
dlen++;
buf->data[0] = dlen;
buf->truncated = true;
return;
}
memcpy(buf->data + 1 + dlen, src, slen);
buf->data[0] = dlen + slen;
}
void PStrAppend(struct PStrBuilder *buf, const unsigned char *src)
{
PStrAppendMem(buf, src + 1, src[0]);
}
void PStrAppendSubstitute(struct PStrBuilder *buf, const unsigned char *src,
int paramcount, const unsigned char *const *params)
{
int srclen, srcpos, start, ch;
const unsigned char *param;
srclen = src[0];
srcpos = 1;
start = 1;
while (srcpos <= srclen) {
if (src[srcpos] == '^' && srcpos + 1 <= srclen) {
ch = src[srcpos + 1];
if ('1' <= ch && ch <= '9') {
ch -= '1';
if (ch < paramcount && (param = params[ch]) != NULL) {
PStrAppendMem(buf, src + start, srcpos - start);
param = params[ch];
PStrAppendMem(buf, param + 1, param[0]);
srcpos += 2;
start = srcpos;
} else {
srcpos += 2;
}
continue;
}
}
srcpos++;
}
PStrAppendMem(buf, src + start, srcpos - start);
}

40
macos/pstrbuilder.h Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2022-2023 Dietrich Epp.
// This file is part of SyncFiles. SyncFiles is licensed under the terms of the
// Mozilla Public License, version 2.0. See LICENSE.txt for details.
#ifndef MACOS_PSTRBUILDER_H
#define MACOS_PSTRBUILDER_H
#include "lib/defs.h"
/*
Pascal String Builder
=====================
These functions operate on the Pascal-style Str255 string type, which consists
of a length byte, followed by 0-255 characters of text. Pascal string literals
can be written by putting \p at the beginning of the literal (this is a C
extension), and have an unsigned char type. C-style strings (null-terminated,
char type) are not used.
The functions here construct strings for human consumption only, not
machine-readable strings. Strings which overflow the destination buffer are
silently truncated, with an ellipsis added to the end of the buffer to show that
its been truncated.
*/
// A PStrBuilder is a buffer for writing a string. This should be initialized by
// setting data[0] to 0 and truncated to false.
struct PStrBuilder {
unsigned char data[256];
Boolean truncated;
};
// PStrAppend appends a string to the buffer.
void PStrAppend(struct PStrBuilder *buf, const unsigned char *src);
// PStrAppendSubstitute appends a Pascal string to another string, expanding any
// ^N substitutions, where N is a positive digit, 1-9.
void PStrAppendSubstitute(struct PStrBuilder *buf, const unsigned char *src,
int paramcount, const unsigned char *const *params);
#endif

163
macos/pstrbuilder_test.c Normal file
View File

@ -0,0 +1,163 @@
// Copyright 2023 Dietrich Epp.
// This file is part of SyncFiles. SyncFiles is licensed under the terms of the
// Mozilla Public License, version 2.0. See LICENSE.txt for details.
#include "macos/pstrbuilder.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// =============================================================================
// Temporary buffer for Pascal strings
static size_t PStrBufPos;
static unsigned char PStrBuf[1024];
static const unsigned char *P2CStr(const char *str)
{
size_t len;
unsigned char *ptr;
len = strlen(str);
if (len > 255) {
fputs("Error: PStr too long\n", stderr);
exit(1);
}
if (len + 1 > sizeof(PStrBuf) - PStrBufPos) {
fputs("Error: PStr overflow\n", stderr);
exit(1);
}
ptr = PStrBuf + PStrBufPos;
*ptr = len;
memcpy(ptr + 1, str, len);
PStrBufPos += len + 1;
return ptr;
}
static void PStrReset(void)
{
PStrBufPos = 0;
}
#define PSTR(x) P2CStr(x)
#define PSTR_RESET() PStrReset()
// =============================================================================
static Boolean TestFailed;
static void PrintQuoted(const unsigned char *str)
{
const unsigned char *ptr = str + 1;
const unsigned char *end = ptr + str[0];
int ch;
for (; ptr < end; ptr++) {
ch = *ptr;
if (32 <= ch && ch <= 126) {
if (ch == '\\' || ch == '"') {
fputc('\\', stderr);
}
fputc(ch, stderr);
} else {
fprintf(stderr, "\\x%02x", ch);
}
}
}
static void TestString(int lineno, const struct PStrBuilder *buf,
const unsigned char *expect, Boolean expect_truncated)
{
if (memcmp(buf->data, expect, expect[0] + 1) != 0 ||
buf->truncated != expect_truncated) {
fprintf(stderr, "%s:%d: Incorrect result\n", __FILE__, lineno);
fputs(" expect: \"", stderr);
PrintQuoted(expect);
fprintf(stderr, "\" (truncated=%d)\n", expect_truncated);
fputs(" got: \"", stderr);
PrintQuoted(buf->data);
fprintf(stderr, "\" (truncated=%d)\n", buf->truncated);
TestFailed = true;
}
}
#define TEST_STRING(buf, expect, expect_truncated) \
TestString(__LINE__, buf, expect, expect_truncated)
#define STR50 "0123456789abcdef0123456789abcdef0123456789abcdef0."
#define STR250 STR50 STR50 STR50 STR50 STR50
static void Clear(struct PStrBuilder *buf, const unsigned char **params)
{
int i;
buf->data[0] = 0;
buf->truncated = false;
for (i = 0; i < 9; i++) {
params[i] = NULL;
}
}
int main(int argc, char **argv)
{
struct PStrBuilder buf;
const unsigned char *params[9];
(void)argc;
(void)argv;
Clear(&buf, params);
TEST_STRING(&buf, PSTR(""), false);
PSTR_RESET();
// Append string to empty buffer.
PStrAppend(&buf, PSTR("String1"));
TEST_STRING(&buf, PSTR("String1"), false);
PSTR_RESET();
// Append string to non-empty buffer.
PStrAppend(&buf, PSTR("String2"));
TEST_STRING(&buf, PSTR("String1String2"), false);
PSTR_RESET();
// Append string with parameters.
Clear(&buf, params);
PStrAppend(&buf, PSTR("initial: "));
params[0] = PSTR("(param1)");
params[1] = PSTR("(param2)");
params[8] = PSTR("(param9)");
PStrAppendSubstitute(&buf, PSTR("^1 ^9 -> ^2"), 9, params);
TEST_STRING(&buf, PSTR("initial: (param1) (param9) -> (param2)"), false);
PSTR_RESET();
// Missing parameters are left alone, including NULL parameters.
Clear(&buf, params);
params[0] = PSTR("param1");
PStrAppendSubstitute(&buf, PSTR("^0 ^1 ^2 ^3 ^9"), 2, params);
TEST_STRING(&buf, PSTR("^0 param1 ^2 ^3 ^9"), false);
// String length 255 works.
Clear(&buf, params);
PStrAppend(&buf, PSTR(STR250));
PStrAppend(&buf, PSTR("VWXYZ"));
TEST_STRING(&buf, PSTR(STR250 "VWXYZ"), false);
PSTR_RESET();
// String length 256 is truncated.
PStrAppend(&buf, PSTR("."));
TEST_STRING(&buf, PSTR(STR250 "VWXY\xc9"), true);
PSTR_RESET();
// Truncate with parameters.
Clear(&buf, params);
params[0] = PSTR(STR50);
PStrAppendSubstitute(&buf, PSTR("^1^1^1^1^1^1"), 1, params);
TEST_STRING(&buf, PSTR(STR250 "0123\xc9"), true);
PSTR_RESET();
fputs("OK?\n", stderr);
if (TestFailed) {
fputs("Test failed\n", stderr);
exit(1);
}
return 0;
}