mirror of
https://github.com/depp/syncfiles.git
synced 2024-11-23 10:31:02 +00:00
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:
parent
a4ce2924db
commit
def1c2c253
16
macos/BUILD.bazel
Normal file
16
macos/BUILD.bazel
Normal 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
72
macos/pstrbuilder.c
Normal 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
40
macos/pstrbuilder.h
Normal 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
|
||||
it’s 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
163
macos/pstrbuilder_test.c
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user