mirror of
https://github.com/depp/syncfiles.git
synced 2024-05-28 08:41:28 +00:00
Implement extended ASCII converter engine
This adds support for the simplest 8-bit character encodings, which are compatible with ASCII.
This commit is contained in:
parent
612aad382f
commit
c96bb9cd0a
12
.vscode/c_cpp_properties.json
vendored
Normal file
12
.vscode/c_cpp_properties.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": ["${workspaceFolder}"],
|
||||||
|
"cStandard": "c89",
|
||||||
|
"intelliSenseMode": "linux-clang-x64",
|
||||||
|
"compilerPath": "/usr/bin/clang"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
|
@ -4,9 +4,11 @@ go_binary(
|
||||||
name = "macscript",
|
name = "macscript",
|
||||||
srcs = [
|
srcs = [
|
||||||
"data.go",
|
"data.go",
|
||||||
|
"filenames.go",
|
||||||
"main.go",
|
"main.go",
|
||||||
"rez.go",
|
"rez.go",
|
||||||
"scriptmap.go",
|
"scriptmap.go",
|
||||||
|
"source.go",
|
||||||
],
|
],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
|
26
gen/filenames.go
Normal file
26
gen/filenames.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func writeFilenames(charmaps []string, filename string) error {
|
||||||
|
s, err := createCSource(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := s.writer
|
||||||
|
w.WriteString(header)
|
||||||
|
w.WriteString(
|
||||||
|
"#include \"src/test.h\"\n" +
|
||||||
|
"const char *const kCharsetFilename[] = {\n")
|
||||||
|
for _, fn := range charmaps {
|
||||||
|
if fn != "" {
|
||||||
|
w.WriteByte('\t')
|
||||||
|
w.WriteString(strconv.Quote(fn))
|
||||||
|
w.WriteString(",\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteString("\tNULL\n};\n")
|
||||||
|
|
||||||
|
return s.flush()
|
||||||
|
}
|
11
gen/main.go
11
gen/main.go
|
@ -16,9 +16,10 @@ import (
|
||||||
const header = "/* This file is automatically generated. */\n"
|
const header = "/* This file is automatically generated. */\n"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flagDest string
|
flagDest string
|
||||||
flagSrc string
|
flagSrc string
|
||||||
flagQuiet bool
|
flagQuiet bool
|
||||||
|
flagFormat bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func getSrcdir() (string, error) {
|
func getSrcdir() (string, error) {
|
||||||
|
@ -90,6 +91,9 @@ func mainE() error {
|
||||||
if err := writeMap(&d, m, filepath.Join(destdir, "charmap.c")); err != nil {
|
if err := writeMap(&d, m, filepath.Join(destdir, "charmap.c")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := writeFilenames(cms, filepath.Join(destdir, "charmap_name.c")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := writeRez(&d, cms, filepath.Join(destdir, "charmap.r")); err != nil {
|
if err := writeRez(&d, cms, filepath.Join(destdir, "charmap.r")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -100,6 +104,7 @@ func main() {
|
||||||
flag.StringVar(&flagDest, "dest", "", "output directory")
|
flag.StringVar(&flagDest, "dest", "", "output directory")
|
||||||
flag.StringVar(&flagSrc, "src", "", "source directory")
|
flag.StringVar(&flagSrc, "src", "", "source directory")
|
||||||
flag.BoolVar(&flagQuiet, "quiet", false, "only output error messages")
|
flag.BoolVar(&flagQuiet, "quiet", false, "only output error messages")
|
||||||
|
flag.BoolVar(&flagFormat, "format", true, "run clang-format on C output")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if args := flag.Args(); len(args) != 0 {
|
if args := flag.Args(); len(args) != 0 {
|
||||||
fmt.Fprintf(os.Stderr, "Error: unexpected argument: %q\n", args[0])
|
fmt.Fprintf(os.Stderr, "Error: unexpected argument: %q\n", args[0])
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,17 +70,13 @@ func genMap(d *scriptdata) []*scriptmap {
|
||||||
// writeMap writes out a C function that returns the correct character map for a
|
// writeMap writes out a C function that returns the correct character map for a
|
||||||
// given script and region.
|
// given script and region.
|
||||||
func writeMap(d *scriptdata, m []*scriptmap, filename string) error {
|
func writeMap(d *scriptdata, m []*scriptmap, filename string) error {
|
||||||
if !flagQuiet {
|
s, err := createCSource(filename)
|
||||||
fmt.Fprintln(os.Stderr, "Writing:", filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
fp, err := os.Create(filename)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
defer s.close()
|
||||||
w := bufio.NewWriter(fp)
|
|
||||||
|
|
||||||
|
w := s.writer
|
||||||
w.WriteString(header)
|
w.WriteString(header)
|
||||||
w.WriteString(
|
w.WriteString(
|
||||||
"#include \"src/convert.h\"\n" +
|
"#include \"src/convert.h\"\n" +
|
||||||
|
@ -120,17 +113,5 @@ func writeMap(d *scriptdata, m []*scriptmap, filename string) error {
|
||||||
"}\n" +
|
"}\n" +
|
||||||
"}\n")
|
"}\n")
|
||||||
|
|
||||||
if err := w.Flush(); err != nil {
|
return s.flush()
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := fp.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("clang-format", "-i", filename)
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Warning: clang-format failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
64
gen/source.go
Normal file
64
gen/source.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type csource struct {
|
||||||
|
filename string
|
||||||
|
file *os.File
|
||||||
|
writer *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCSource(filename string) (s csource, err error) {
|
||||||
|
if !flagQuiet {
|
||||||
|
fmt.Fprintln(os.Stderr, "Writing:", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return csource{
|
||||||
|
filename: filename,
|
||||||
|
file: fp,
|
||||||
|
writer: bufio.NewWriter(fp),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *csource) close() {
|
||||||
|
if s.file != nil {
|
||||||
|
s.file.Close()
|
||||||
|
s.file = nil
|
||||||
|
}
|
||||||
|
if s.filename != "" {
|
||||||
|
os.Remove(s.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *csource) flush() error {
|
||||||
|
if s.file == nil {
|
||||||
|
panic("already closed")
|
||||||
|
}
|
||||||
|
err := s.writer.Flush()
|
||||||
|
s.writer = nil
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.file.Close()
|
||||||
|
s.file = nil
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flagFormat {
|
||||||
|
cmd := exec.Command("clang-format", "-i", s.filename)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.filename = ""
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,6 +1,20 @@
|
||||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
|
||||||
load("//bazel:copts.bzl", "COPTS")
|
load("//bazel:copts.bzl", "COPTS")
|
||||||
|
|
||||||
|
_data = [
|
||||||
|
"charmap_roman.dat",
|
||||||
|
"charmap_turkish.dat",
|
||||||
|
"charmap_croatian.dat",
|
||||||
|
"charmap_iceland.dat",
|
||||||
|
"charmap_romanian.dat",
|
||||||
|
"charmap_celtic.dat",
|
||||||
|
"charmap_gaelic.dat",
|
||||||
|
"charmap_greek.dat",
|
||||||
|
"charmap_cyrillic.dat",
|
||||||
|
"charmap_inuit.dat",
|
||||||
|
"charmap_centeuro.dat",
|
||||||
|
]
|
||||||
|
|
||||||
genrule(
|
genrule(
|
||||||
name = "data",
|
name = "data",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
@ -10,18 +24,8 @@ genrule(
|
||||||
outs = [
|
outs = [
|
||||||
"charmap.c",
|
"charmap.c",
|
||||||
"charmap.r",
|
"charmap.r",
|
||||||
"charmap_roman.dat",
|
"charmap_name.c",
|
||||||
"charmap_turkish.dat",
|
] + _data,
|
||||||
"charmap_croatian.dat",
|
|
||||||
"charmap_iceland.dat",
|
|
||||||
"charmap_romanian.dat",
|
|
||||||
"charmap_celtic.dat",
|
|
||||||
"charmap_gaelic.dat",
|
|
||||||
"charmap_greek.dat",
|
|
||||||
"charmap_cyrillic.dat",
|
|
||||||
"charmap_inuit.dat",
|
|
||||||
"charmap_centeuro.dat",
|
|
||||||
],
|
|
||||||
cmd = "$(execpath //gen:macscript) -dest=$(RULEDIR) -src=. -quiet",
|
cmd = "$(execpath //gen:macscript) -dest=$(RULEDIR) -src=. -quiet",
|
||||||
tools = [
|
tools = [
|
||||||
"//gen:macscript",
|
"//gen:macscript",
|
||||||
|
@ -32,9 +36,26 @@ cc_library(
|
||||||
name = "convert",
|
name = "convert",
|
||||||
srcs = [
|
srcs = [
|
||||||
"charmap.c",
|
"charmap.c",
|
||||||
|
"convert.c",
|
||||||
"convert.h",
|
"convert.h",
|
||||||
|
"convert_1f.c",
|
||||||
|
"convert_1r.c",
|
||||||
"defs.h",
|
"defs.h",
|
||||||
|
"test.h",
|
||||||
"toolbox.c",
|
"toolbox.c",
|
||||||
],
|
],
|
||||||
copts = COPTS,
|
copts = COPTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "convert_test",
|
||||||
|
srcs = [
|
||||||
|
"charmap_name.c",
|
||||||
|
"convert_test.c",
|
||||||
|
],
|
||||||
|
copts = COPTS,
|
||||||
|
data = _data,
|
||||||
|
deps = [
|
||||||
|
":convert",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
10
src/README.md
Normal file
10
src/README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Converter
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
Tests can be debugged with GDB:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bazel build -c dbg //src:convert_test
|
||||||
|
gdb -ex 'dir .' -ex 'cd bazel-bin' bazel-bin/src/convert_test
|
||||||
|
```
|
38
src/convert.c
Normal file
38
src/convert.c
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#include "src/convert.h"
|
||||||
|
|
||||||
|
struct ConvertEngine {
|
||||||
|
ConvertBuildf build;
|
||||||
|
ConvertRunf run;
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct ConvertEngine kEngines[][2] = {
|
||||||
|
{{Convert1fBuild, Convert1fRun}, {Convert1rBuild, Convert1rRun}}};
|
||||||
|
|
||||||
|
int ConverterBuild(struct Converter *c, Handle data, Size datasz,
|
||||||
|
ConvertDirection direction, OSErr *errp)
|
||||||
|
{
|
||||||
|
int engine, r;
|
||||||
|
const struct ConvertEngine *funcs;
|
||||||
|
Handle out;
|
||||||
|
|
||||||
|
if (datasz == 0) {
|
||||||
|
return kErrorBadData;
|
||||||
|
}
|
||||||
|
engine = (UInt8) * *data - 1;
|
||||||
|
if (engine < 0 || (int)(sizeof(kEngines) / sizeof(*kEngines)) <= engine) {
|
||||||
|
/* Invalid engine. */
|
||||||
|
return kErrorBadData;
|
||||||
|
}
|
||||||
|
funcs = &kEngines[engine][direction];
|
||||||
|
if (funcs->build == NULL || funcs->run == NULL) {
|
||||||
|
/* Invalid engine. */
|
||||||
|
return kErrorBadData;
|
||||||
|
}
|
||||||
|
r = funcs->build(&out, data, datasz, errp);
|
||||||
|
if (r != 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
c->data = out;
|
||||||
|
c->run = funcs->run;
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -2,8 +2,94 @@
|
||||||
#define convert_h
|
#define convert_h
|
||||||
/* convert.h - character set conversion routines. */
|
/* convert.h - character set conversion routines. */
|
||||||
|
|
||||||
|
#include "src/defs.h"
|
||||||
|
|
||||||
|
/* Error codes. */
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/* No error. */
|
||||||
|
kErrorOK,
|
||||||
|
|
||||||
|
/* Memory allocation failed. */
|
||||||
|
kErrorNoMemory,
|
||||||
|
|
||||||
|
/* Invaild table data. */
|
||||||
|
kErrorBadData
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/* Constants for CR and LF. Note that we should not use '\n' or '\r'
|
||||||
|
anywhere, because these character constants may have unexpected values on
|
||||||
|
certain old Mac OS compilers, depending on the compiler settings. In
|
||||||
|
particular, the values of '\n' and '\r' will be swapped. */
|
||||||
|
kCharLF = 10,
|
||||||
|
kCharCR = 13,
|
||||||
|
|
||||||
|
/* Constant for substitution character: '?'. */
|
||||||
|
kCharSubstitute = 63
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/* Don't translite line breaks. */
|
||||||
|
kLineBreakKeep,
|
||||||
|
|
||||||
|
/* Convert line breaks to LF. */
|
||||||
|
kLineBreakLF,
|
||||||
|
|
||||||
|
/* Convert line breaks to CR. */
|
||||||
|
kLineBreakCR,
|
||||||
|
|
||||||
|
/* Convert line breaks to CR LF. */
|
||||||
|
kLineBreakCRLF
|
||||||
|
} LineBreakConversion;
|
||||||
|
|
||||||
|
/* Directions that the converter runs in. */
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
kToUTF8,
|
||||||
|
kFromUTF8
|
||||||
|
} ConvertDirection;
|
||||||
|
|
||||||
/* Get the character map used for the given Mac OS script and region codes.
|
/* Get the character map used for the given Mac OS script and region codes.
|
||||||
Return -1 if no known character map exists. */
|
Return -1 if no known character map exists. */
|
||||||
int GetCharmap(int script, int region);
|
int GetCharmap(int script, int region);
|
||||||
|
|
||||||
|
/* The state of a converter. Must be zeroed prior to first conversion. */
|
||||||
|
struct ConverterState {
|
||||||
|
UInt32 data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Implementation function for building a converter. */
|
||||||
|
typedef int (*ConvertBuildf)(Handle *out, Handle data, Size datasz,
|
||||||
|
OSErr *errp);
|
||||||
|
|
||||||
|
/* Implementation function for running a converter. */
|
||||||
|
typedef void (*ConvertRunf)(const void *cvtptr, LineBreakConversion lc,
|
||||||
|
struct ConverterState *stateptr, UInt8 **optr,
|
||||||
|
UInt8 *oend, const UInt8 **iptr, const UInt8 *iend);
|
||||||
|
|
||||||
|
/* A converter. The converter can be freed by disposing the handle. */
|
||||||
|
struct Converter {
|
||||||
|
Handle data;
|
||||||
|
ConvertRunf run;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Build a converter from the given conversion table data. */
|
||||||
|
int ConverterBuild(struct Converter *c, Handle data, Size datasz,
|
||||||
|
ConvertDirection direction, OSErr *errp);
|
||||||
|
|
||||||
|
/* Engine 1: extended ASCII */
|
||||||
|
|
||||||
|
int Convert1fBuild(Handle *out, Handle data, Size datasz, OSErr *errp);
|
||||||
|
void Convert1fRun(const void *cvtptr, LineBreakConversion lc,
|
||||||
|
struct ConverterState *stateptr, UInt8 **optr, UInt8 *oend,
|
||||||
|
const UInt8 **iptr, const UInt8 *iend);
|
||||||
|
|
||||||
|
int Convert1rBuild(Handle *out, Handle data, Size datasz, OSErr *errp);
|
||||||
|
void Convert1rRun(const void *cvtptr, LineBreakConversion lc,
|
||||||
|
struct ConverterState *stateptr, UInt8 **optr, UInt8 *oend,
|
||||||
|
const UInt8 **iptr, const UInt8 *iend);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
127
src/convert_1f.c
Normal file
127
src/convert_1f.c
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/* convert_1f.c - Forward conversion from extended ASCII to UTF-8. */
|
||||||
|
#include "src/convert.h"
|
||||||
|
#include "src/defs.h"
|
||||||
|
|
||||||
|
struct Convert1fData {
|
||||||
|
/* Unicode characters, encoded in UTF-8, and packed MSB first. Always either
|
||||||
|
2 bytes or 3 bytes. */
|
||||||
|
UInt32 chars[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Convert1fState {
|
||||||
|
UInt8 lastch;
|
||||||
|
};
|
||||||
|
|
||||||
|
int Convert1fBuild(Handle *out, Handle data, Size datasz, OSErr *errp)
|
||||||
|
{
|
||||||
|
Handle h;
|
||||||
|
struct Convert1fData *cvt;
|
||||||
|
int i, n;
|
||||||
|
UInt32 uch;
|
||||||
|
const UInt8 *dptr, *dend;
|
||||||
|
|
||||||
|
h = NewHandle(sizeof(struct Convert1fData));
|
||||||
|
if (h == NULL) {
|
||||||
|
*errp = MemError();
|
||||||
|
return kErrorNoMemory;
|
||||||
|
}
|
||||||
|
cvt = (void *)*h;
|
||||||
|
dptr = (void *)*data;
|
||||||
|
dptr++;
|
||||||
|
dend = dptr + datasz;
|
||||||
|
for (i = 0; i < 128; i++) {
|
||||||
|
if (dptr == dend) {
|
||||||
|
goto bad_table;
|
||||||
|
}
|
||||||
|
n = *dptr++;
|
||||||
|
if (n < 2 || 3 < n) {
|
||||||
|
goto bad_table;
|
||||||
|
}
|
||||||
|
if (dend - dptr < n) {
|
||||||
|
goto bad_table;
|
||||||
|
}
|
||||||
|
uch = 0;
|
||||||
|
while (n-- > 0) {
|
||||||
|
uch = (uch << 8) | *dptr++;
|
||||||
|
}
|
||||||
|
cvt->chars[i] = uch;
|
||||||
|
if (dptr == dend) {
|
||||||
|
goto bad_table;
|
||||||
|
}
|
||||||
|
n = *dptr++;
|
||||||
|
if (dend - dptr < n) {
|
||||||
|
goto bad_table;
|
||||||
|
}
|
||||||
|
dptr += n;
|
||||||
|
}
|
||||||
|
*out = h;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bad_table:
|
||||||
|
DisposeHandle(h);
|
||||||
|
return kErrorBadData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Convert1fRun(const void *cvtptr, LineBreakConversion lc,
|
||||||
|
struct ConverterState *stateptr, UInt8 **optr, UInt8 *oend,
|
||||||
|
const UInt8 **iptr, const UInt8 *iend)
|
||||||
|
{
|
||||||
|
const struct Convert1fData *cvt = cvtptr;
|
||||||
|
struct Convert1fState *state = (struct Convert1fState *)stateptr;
|
||||||
|
UInt8 *opos = *optr;
|
||||||
|
const UInt8 *ipos = *iptr;
|
||||||
|
unsigned ch, lastch;
|
||||||
|
UInt32 uch;
|
||||||
|
|
||||||
|
ch = state->lastch;
|
||||||
|
while (ipos < iend && oend - opos >= 3) {
|
||||||
|
lastch = ch;
|
||||||
|
ch = *ipos++;
|
||||||
|
if (ch < 128) {
|
||||||
|
if (ch == kCharLF || ch == kCharCR) {
|
||||||
|
/* Line breaks. */
|
||||||
|
if (ch == kCharLF && lastch == kCharCR) {
|
||||||
|
if (lc == kLineBreakKeep) {
|
||||||
|
*opos++ = ch;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (lc) {
|
||||||
|
case kLineBreakKeep:
|
||||||
|
*opos++ = ch;
|
||||||
|
break;
|
||||||
|
case kLineBreakLF:
|
||||||
|
*opos++ = kCharLF;
|
||||||
|
break;
|
||||||
|
case kLineBreakCR:
|
||||||
|
*opos++ = kCharCR;
|
||||||
|
break;
|
||||||
|
case kLineBreakCRLF:
|
||||||
|
*opos++ = kCharCR;
|
||||||
|
*opos++ = kCharLF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* ASCII characters. */
|
||||||
|
*opos++ = ch;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Unicode characters. */
|
||||||
|
uch = cvt->chars[ch - 128];
|
||||||
|
if (uch > 0xffff) {
|
||||||
|
opos[0] = uch >> 16;
|
||||||
|
opos[1] = uch >> 8;
|
||||||
|
opos[2] = uch;
|
||||||
|
opos += 3;
|
||||||
|
} else {
|
||||||
|
opos[0] = uch >> 8;
|
||||||
|
opos[1] = uch;
|
||||||
|
opos += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state->lastch = ch;
|
||||||
|
|
||||||
|
*optr = opos;
|
||||||
|
*iptr = ipos;
|
||||||
|
}
|
368
src/convert_1r.c
Normal file
368
src/convert_1r.c
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
/* convert_1r.c - Reverse conversion from UTF-8 to extended ASCII. */
|
||||||
|
#include "src/convert.h"
|
||||||
|
#include "src/defs.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/* Maximum length of encoded character. */
|
||||||
|
kMaxEncodedLength = 8,
|
||||||
|
|
||||||
|
/* Initial number of nodes to allocate when building the tree. */
|
||||||
|
kInitialTableAlloc = 8
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TEntry {
|
||||||
|
/* The output character, or zero if no output. */
|
||||||
|
UInt8 output;
|
||||||
|
/* The next node, or zero if no next node. */
|
||||||
|
UInt8 next;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* A node for building the converter. */
|
||||||
|
struct TNode {
|
||||||
|
struct TEntry entries[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TTree {
|
||||||
|
struct TNode **nodes;
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int CreateTree(struct TTree *tree, Handle data, Size datasz, OSErr *errp)
|
||||||
|
{
|
||||||
|
struct TNode **nodes, *node;
|
||||||
|
int i, j, dpos, enclen, encend, state, cur, nodecount, nodealloc;
|
||||||
|
unsigned ch;
|
||||||
|
OSErr err;
|
||||||
|
|
||||||
|
/* Create a tree with a root node mapping all the ASCII characters except
|
||||||
|
NUL, CR, and LF. NUL won't map because an output of 0 is interpreted as
|
||||||
|
no output. CR and LF are removed so they can be handled specially be the
|
||||||
|
decoder. */
|
||||||
|
nodes =
|
||||||
|
(struct TNode **)NewHandle(kInitialTableAlloc * sizeof(struct TNode));
|
||||||
|
if (nodes == NULL) {
|
||||||
|
err = MemError();
|
||||||
|
goto have_error;
|
||||||
|
}
|
||||||
|
nodecount = 1;
|
||||||
|
nodealloc = kInitialTableAlloc;
|
||||||
|
node = *nodes;
|
||||||
|
MemClear(node, sizeof(struct TNode));
|
||||||
|
for (i = 0; i < 128; i++) {
|
||||||
|
node->entries[i].output = i;
|
||||||
|
}
|
||||||
|
node->entries[kCharLF].output = 0;
|
||||||
|
node->entries[kCharCR].output = 0;
|
||||||
|
|
||||||
|
/* Parse the table data and build up a tree of TNode. */
|
||||||
|
dpos = 1;
|
||||||
|
/* For each high character (128..255). */
|
||||||
|
for (i = 0; i < 128; i++) {
|
||||||
|
/* For each encoding of that character. */
|
||||||
|
for (j = 0; j < 2; j++) {
|
||||||
|
if (dpos >= datasz) {
|
||||||
|
goto bad_table;
|
||||||
|
}
|
||||||
|
enclen = (UInt8)(*data)[dpos++];
|
||||||
|
if (enclen != 0) {
|
||||||
|
if (enclen < 2 || enclen > datasz - dpos ||
|
||||||
|
enclen > kMaxEncodedLength) {
|
||||||
|
goto bad_table;
|
||||||
|
}
|
||||||
|
/* Iterate over all but last byte in encoding, to find the node
|
||||||
|
which will produce the decoded byte as output. */
|
||||||
|
state = 0;
|
||||||
|
node = *nodes;
|
||||||
|
for (encend = dpos + enclen - 1; dpos < encend; dpos++) {
|
||||||
|
ch = (UInt8)(*data)[dpos];
|
||||||
|
cur = state;
|
||||||
|
state = node->entries[ch].next;
|
||||||
|
if (state == 0) {
|
||||||
|
if (nodecount >= nodealloc) {
|
||||||
|
nodealloc *= 2;
|
||||||
|
SetHandleSize((Handle)nodes,
|
||||||
|
nodealloc * sizeof(struct TNode));
|
||||||
|
err = MemError();
|
||||||
|
if (err != 0) {
|
||||||
|
goto have_error;
|
||||||
|
}
|
||||||
|
node = *nodes + cur;
|
||||||
|
}
|
||||||
|
state = nodecount++;
|
||||||
|
node->entries[ch].next = state;
|
||||||
|
node = (*nodes) + state;
|
||||||
|
MemClear(node, sizeof(*node));
|
||||||
|
} else {
|
||||||
|
node = *nodes + state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch = (UInt8)(*data)[dpos++];
|
||||||
|
if (node->entries[ch].output != 0) {
|
||||||
|
goto bad_table;
|
||||||
|
}
|
||||||
|
node->entries[ch].output = i | 0x80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetHandleSize((Handle)nodes, nodecount * sizeof(struct TNode));
|
||||||
|
tree->nodes = nodes;
|
||||||
|
tree->count = nodecount;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bad_table:
|
||||||
|
DisposeHandle((Handle)nodes);
|
||||||
|
return kErrorBadData;
|
||||||
|
|
||||||
|
have_error:
|
||||||
|
DisposeHandle((Handle)nodes);
|
||||||
|
*errp = err;
|
||||||
|
return kErrorNoMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NodeInfo {
|
||||||
|
UInt8 min;
|
||||||
|
UInt8 max;
|
||||||
|
UInt16 offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CEntry {
|
||||||
|
UInt16 output;
|
||||||
|
UInt16 next;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* A compressed table node. Followed by an array of centry. */
|
||||||
|
struct CNode {
|
||||||
|
/* First byte in table. */
|
||||||
|
UInt8 base;
|
||||||
|
/* Number of entries in table, minus one. */
|
||||||
|
UInt8 span;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int CompactTree(Handle *out, struct TNode **nodes, int nodecount,
|
||||||
|
OSErr *errp)
|
||||||
|
{
|
||||||
|
Handle ctree;
|
||||||
|
struct TNode *node;
|
||||||
|
struct NodeInfo **infos, *info;
|
||||||
|
struct CNode *cnode;
|
||||||
|
struct CEntry *centry;
|
||||||
|
int i, j, min, max, count, next;
|
||||||
|
unsigned offset;
|
||||||
|
|
||||||
|
/* Figure out where each compacted node will go. */
|
||||||
|
infos = (struct NodeInfo **)NewHandle(sizeof(struct NodeInfo) * nodecount);
|
||||||
|
if (infos == NULL) {
|
||||||
|
*errp = MemError();
|
||||||
|
return kErrorNoMemory;
|
||||||
|
}
|
||||||
|
offset = 0;
|
||||||
|
for (i = 0; i < nodecount; i++) {
|
||||||
|
node = *nodes + i;
|
||||||
|
min = 0;
|
||||||
|
while (node->entries[min].output == 0 && node->entries[min].next == 0) {
|
||||||
|
min++;
|
||||||
|
}
|
||||||
|
max = 255;
|
||||||
|
while (node->entries[max].output == 0 && node->entries[max].next == 0) {
|
||||||
|
max--;
|
||||||
|
}
|
||||||
|
info = *infos + i;
|
||||||
|
info->min = min;
|
||||||
|
info->max = max;
|
||||||
|
info->offset = offset;
|
||||||
|
count = max - min + 1;
|
||||||
|
offset += sizeof(struct CNode) + count * sizeof(struct CEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the compacted tree. */
|
||||||
|
ctree = NewHandle(offset);
|
||||||
|
if (ctree == NULL) {
|
||||||
|
*errp = MemError();
|
||||||
|
DisposeHandle((Handle)infos);
|
||||||
|
return kErrorNoMemory;
|
||||||
|
}
|
||||||
|
for (i = 0; i < nodecount; i++) {
|
||||||
|
node = *nodes + i;
|
||||||
|
info = *infos + i;
|
||||||
|
min = info->min;
|
||||||
|
max = info->max;
|
||||||
|
offset = info->offset;
|
||||||
|
cnode = (void *)(*ctree + offset);
|
||||||
|
cnode->base = min;
|
||||||
|
cnode->span = max - min;
|
||||||
|
centry = (void *)(*ctree + offset + sizeof(struct CNode));
|
||||||
|
for (j = min; j <= max; j++) {
|
||||||
|
centry->output = node->entries[j].output;
|
||||||
|
next = node->entries[j].next;
|
||||||
|
if (next != 0) {
|
||||||
|
next = (*infos)[next].offset;
|
||||||
|
}
|
||||||
|
centry->next = next;
|
||||||
|
centry++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposeHandle((Handle)infos);
|
||||||
|
*out = ctree;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Convert1rBuild(Handle *out, Handle data, Size datasz, OSErr *errp)
|
||||||
|
{
|
||||||
|
struct TTree table;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = CreateTree(&table, data, datasz, errp);
|
||||||
|
if (r != 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
r = CompactTree(out, table.nodes, table.count, errp);
|
||||||
|
DisposeHandle((Handle)table.nodes);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Convert1rState {
|
||||||
|
UInt8 lastch;
|
||||||
|
UInt8 output;
|
||||||
|
UInt16 tableoffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
void Convert1rRun(const void *cvtptr, LineBreakConversion lc,
|
||||||
|
struct ConverterState *stateptr, UInt8 **optr, UInt8 *oend,
|
||||||
|
const UInt8 **iptr, const UInt8 *iend)
|
||||||
|
{
|
||||||
|
struct Convert1rState *state = (struct Convert1rState *)stateptr;
|
||||||
|
const struct CNode *node;
|
||||||
|
const struct CEntry *entry;
|
||||||
|
UInt8 *opos = *optr;
|
||||||
|
const UInt8 *ipos = *iptr, *savein;
|
||||||
|
unsigned ch, lastch, chlen, output, saveout, toffset, savetoffset;
|
||||||
|
|
||||||
|
ch = state->lastch;
|
||||||
|
savein = ipos;
|
||||||
|
saveout = state->output;
|
||||||
|
toffset = state->tableoffset;
|
||||||
|
savetoffset = toffset;
|
||||||
|
if (oend - opos < 2) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
goto resume;
|
||||||
|
|
||||||
|
next_out:
|
||||||
|
if (oend - opos < 2) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Follow state machine to the end. */
|
||||||
|
savein = ipos;
|
||||||
|
saveout = 0;
|
||||||
|
toffset = 0;
|
||||||
|
savetoffset = 0;
|
||||||
|
resume:
|
||||||
|
for (;;) {
|
||||||
|
if (ipos >= iend) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
lastch = ch;
|
||||||
|
ch = *ipos++;
|
||||||
|
|
||||||
|
node = (const void *)((const UInt8 *)cvtptr + toffset);
|
||||||
|
ch -= node->base;
|
||||||
|
if (ch > node->span) {
|
||||||
|
toffset = 0;
|
||||||
|
goto bad_char;
|
||||||
|
}
|
||||||
|
entry =
|
||||||
|
(const void *)((const UInt8 *)cvtptr + toffset +
|
||||||
|
sizeof(struct CNode) + ch * sizeof(struct CEntry));
|
||||||
|
output = entry->output;
|
||||||
|
toffset = entry->next;
|
||||||
|
if (toffset == 0) {
|
||||||
|
/* Reached end of tree. */
|
||||||
|
if (output == 0) {
|
||||||
|
goto bad_char;
|
||||||
|
}
|
||||||
|
*opos++ = output;
|
||||||
|
goto next_out;
|
||||||
|
}
|
||||||
|
if (output != 0) {
|
||||||
|
/* Can produce output here, or can consume more input. We try
|
||||||
|
consuming more input, but save the state to rewind if that
|
||||||
|
fails. */
|
||||||
|
savein = ipos;
|
||||||
|
saveout = output;
|
||||||
|
savetoffset = toffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bad_char:
|
||||||
|
/* Bad character. Back up and try again. */
|
||||||
|
ipos = savein;
|
||||||
|
if (saveout != 0) {
|
||||||
|
/* Produce saved output. */
|
||||||
|
*opos++ = saveout;
|
||||||
|
ch = 0;
|
||||||
|
} else {
|
||||||
|
/* No saved output, this really is a bad character. Consume one
|
||||||
|
UTF-8 character, emit it as a fallback, and continue. */
|
||||||
|
ch = *ipos++;
|
||||||
|
if ((ch & 0x80) == 0) {
|
||||||
|
/* ASCII character: either NUL, CR, or LF, because only
|
||||||
|
these
|
||||||
|
characters will result in a transition to state 0. */
|
||||||
|
if (ch == 0) {
|
||||||
|
*opos++ = ch;
|
||||||
|
} else if (ch == kCharLF && lastch == kCharCR) {
|
||||||
|
if (lc == kLineBreakKeep) {
|
||||||
|
*opos++ = ch;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (lc) {
|
||||||
|
case kLineBreakKeep:
|
||||||
|
*opos++ = ch;
|
||||||
|
break;
|
||||||
|
case kLineBreakLF:
|
||||||
|
*opos++ = kCharLF;
|
||||||
|
break;
|
||||||
|
case kLineBreakCR:
|
||||||
|
*opos++ = kCharCR;
|
||||||
|
break;
|
||||||
|
case kLineBreakCRLF:
|
||||||
|
*opos++ = kCharCR;
|
||||||
|
*opos++ = kCharLF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((ch & 0xe0) == 0xc0) {
|
||||||
|
chlen = 1;
|
||||||
|
} else if ((ch & 0xf0) == 0xe0) {
|
||||||
|
chlen = 2;
|
||||||
|
} else if ((ch & 0xf8) == 0xf0) {
|
||||||
|
chlen = 3;
|
||||||
|
} else {
|
||||||
|
chlen = 0;
|
||||||
|
}
|
||||||
|
for (; chlen > 0; chlen--) {
|
||||||
|
if (ipos == iend) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
ch = *ipos;
|
||||||
|
if ((ch & 0xc0) != 0x80) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ipos++;
|
||||||
|
}
|
||||||
|
*opos++ = kCharSubstitute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto next_out;
|
||||||
|
|
||||||
|
done:
|
||||||
|
state->lastch = ch;
|
||||||
|
state->output = saveout;
|
||||||
|
state->tableoffset = savetoffset;
|
||||||
|
*optr = opos;
|
||||||
|
*iptr = savein;
|
||||||
|
}
|
321
src/convert_test.c
Normal file
321
src/convert_test.c
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
/* Converter test. */
|
||||||
|
#define _XOPEN_SOURCE 500
|
||||||
|
|
||||||
|
#include "src/convert.h"
|
||||||
|
#include "src/test.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
kInitialBufSize = 4 * 1024,
|
||||||
|
kConvertBufferSize = 1024
|
||||||
|
};
|
||||||
|
|
||||||
|
static int gFailCount;
|
||||||
|
static char gTestName[128];
|
||||||
|
|
||||||
|
static void Failf(const char *msg, ...) __attribute__((format(printf, 1, 2)));
|
||||||
|
|
||||||
|
static void Failf(const char *msg, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
gFailCount++;
|
||||||
|
fputs("Error: ", stderr);
|
||||||
|
fputs(gTestName, stderr);
|
||||||
|
fputs(": ", stderr);
|
||||||
|
va_start(ap, msg);
|
||||||
|
vfprintf(stderr, msg, ap);
|
||||||
|
va_end(ap);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
if (gFailCount >= 10) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *const kErrorNames[] = {"ok", "no memory", "bad data"};
|
||||||
|
|
||||||
|
static const char *ErrorName(int err)
|
||||||
|
{
|
||||||
|
if (err < 0 || (int)(sizeof(kErrorNames) / sizeof(*kErrorNames)) <= err) {
|
||||||
|
Dief("bad error code: %d", err);
|
||||||
|
}
|
||||||
|
return kErrorNames[err];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StringPrintf(char *dest, size_t destsz, const char *fmt, ...)
|
||||||
|
__attribute__((format(printf, 3, 4)));
|
||||||
|
|
||||||
|
static void StringPrintf(char *dest, size_t destsz, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
n = vsnprintf(dest, destsz, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
if (n < 0 || n >= (int)destsz) {
|
||||||
|
Dief("snprintf: overflow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read a file in its entirety. */
|
||||||
|
static void ReadFile(const char *filename, void **datap, size_t *sizep)
|
||||||
|
{
|
||||||
|
char fnbuf[128];
|
||||||
|
FILE *fp = NULL;
|
||||||
|
char *buf = NULL, *newbuf;
|
||||||
|
size_t size, alloc, newalloc, amt;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
StringPrintf(fnbuf, sizeof(fnbuf), "src/%s", filename);
|
||||||
|
|
||||||
|
fp = fopen(fnbuf, "rb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
err = errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
buf = malloc(kInitialBufSize);
|
||||||
|
if (buf == NULL) {
|
||||||
|
err = errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
size = 0;
|
||||||
|
alloc = kInitialBufSize;
|
||||||
|
for (;;) {
|
||||||
|
if (size >= alloc) {
|
||||||
|
newalloc = alloc * 2;
|
||||||
|
newbuf = realloc(buf, newalloc);
|
||||||
|
if (newbuf == NULL) {
|
||||||
|
err = errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
alloc = newalloc;
|
||||||
|
buf = newbuf;
|
||||||
|
}
|
||||||
|
amt = fread(buf + size, 1, alloc - size, fp);
|
||||||
|
if (amt == 0) {
|
||||||
|
if (feof(fp)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
err = errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
size += amt;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
*datap = buf;
|
||||||
|
*sizep = size;
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (fp != NULL) {
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
if (buf != NULL) {
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
DieErrorf(err, "read %s", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static UInt8 *gBuffer[3];
|
||||||
|
|
||||||
|
static void PrintQuotedString(const UInt8 *buf, int len)
|
||||||
|
{
|
||||||
|
int i, c;
|
||||||
|
|
||||||
|
fputc('"', stderr);
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
c = buf[i];
|
||||||
|
if (32 <= c && c <= 126) {
|
||||||
|
if (c == '\\' || c == '"') {
|
||||||
|
fputc('\\', stderr);
|
||||||
|
}
|
||||||
|
fputc(c, stderr);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "\\x%02x", c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fputc('"', stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Check(int len0, int len1, int len2)
|
||||||
|
{
|
||||||
|
int i, n, col, diffcol, c1, c2;
|
||||||
|
|
||||||
|
if (len0 == len2 && memcmp(gBuffer[0], gBuffer[2], len2) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Failf("incorrect output");
|
||||||
|
n = len0;
|
||||||
|
if (n > len2) {
|
||||||
|
n = len2;
|
||||||
|
}
|
||||||
|
diffcol = -1;
|
||||||
|
col = 0;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
c1 = gBuffer[0][i];
|
||||||
|
c2 = gBuffer[2][i];
|
||||||
|
if (c1 != c2) {
|
||||||
|
diffcol = col;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (32 <= c1 && c1 <= 126) {
|
||||||
|
col++;
|
||||||
|
if (c1 == '\\' || c1 == '"') {
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
col += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fputs("Input: ", stderr);
|
||||||
|
PrintQuotedString(gBuffer[1], len1);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
fputs("Expect: ", stderr);
|
||||||
|
PrintQuotedString(gBuffer[0], len0);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
fputs("Output: ", stderr);
|
||||||
|
PrintQuotedString(gBuffer[2], len2);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
if (diffcol >= 0) {
|
||||||
|
for (i = 0; i < diffcol + 9; i++) {
|
||||||
|
fputc(' ', stderr);
|
||||||
|
}
|
||||||
|
fputc('^', stderr);
|
||||||
|
}
|
||||||
|
fputc('\n', stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TestConverter(const char *filename)
|
||||||
|
{
|
||||||
|
void *data;
|
||||||
|
size_t datasz;
|
||||||
|
Ptr datap;
|
||||||
|
Handle datah;
|
||||||
|
struct Converter cf, cr;
|
||||||
|
struct ConverterState st;
|
||||||
|
int r, i, j, jmax, len0, len1, len2;
|
||||||
|
OSErr err;
|
||||||
|
UInt8 *ptr;
|
||||||
|
const UInt8 *iptr, *iend;
|
||||||
|
UInt8 *optr, *oend;
|
||||||
|
|
||||||
|
data = NULL;
|
||||||
|
cf.data = NULL;
|
||||||
|
cr.data = NULL;
|
||||||
|
|
||||||
|
StringPrintf(gTestName, sizeof(gTestName), "%s", filename);
|
||||||
|
|
||||||
|
/* Load the converter into memory and build the conversion table. */
|
||||||
|
ReadFile(filename, &data, &datasz);
|
||||||
|
datap = data;
|
||||||
|
datah = &datap;
|
||||||
|
r = ConverterBuild(&cf, datah, datasz, kToUTF8, &err);
|
||||||
|
if (r != 0) {
|
||||||
|
Failf("ConverterBuild: %s (to UTF-8): %s", filename, ErrorName(r));
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
r = ConverterBuild(&cr, datah, datasz, kFromUTF8, &err);
|
||||||
|
if (r != 0) {
|
||||||
|
Failf("ConverterBuild: %s (from UTF-8): %s", filename, ErrorName(r));
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create sample data to convert: 0-255, followed by 0. */
|
||||||
|
len0 = 257;
|
||||||
|
ptr = gBuffer[0];
|
||||||
|
for (i = 0; i < 256; i++) {
|
||||||
|
ptr[i] = i;
|
||||||
|
}
|
||||||
|
ptr[256] = 0;
|
||||||
|
|
||||||
|
/* Convert sample data. */
|
||||||
|
iptr = gBuffer[0];
|
||||||
|
iend = iptr + 257;
|
||||||
|
optr = gBuffer[1];
|
||||||
|
oend = optr + kConvertBufferSize;
|
||||||
|
st.data = 0;
|
||||||
|
cf.run(*cf.data, kLineBreakKeep, &st, &optr, oend, &iptr, iend);
|
||||||
|
if (iptr != iend) {
|
||||||
|
Failf("some data failed to convert");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
len1 = optr - gBuffer[1];
|
||||||
|
|
||||||
|
/* Convert back, in three calls. The middle call will be to a 1-4 byte slice
|
||||||
|
in the middle. */
|
||||||
|
for (i = 1; i < len1 - 2; i++) {
|
||||||
|
jmax = len1 - i;
|
||||||
|
if (jmax > 4) {
|
||||||
|
jmax = 4;
|
||||||
|
}
|
||||||
|
for (j = 1; j <= jmax; j++) {
|
||||||
|
StringPrintf(gTestName, sizeof(gTestName), "%s reverse i=%d j=%d",
|
||||||
|
filename, i, j);
|
||||||
|
st.data = 0;
|
||||||
|
iptr = gBuffer[1];
|
||||||
|
optr = gBuffer[2];
|
||||||
|
oend = optr + kConvertBufferSize;
|
||||||
|
iend = gBuffer[1] + i;
|
||||||
|
cr.run(*cr.data, kLineBreakKeep, &st, &optr, oend, &iptr, iend);
|
||||||
|
iend = gBuffer[1] + i + j;
|
||||||
|
cr.run(*cr.data, kLineBreakKeep, &st, &optr, oend, &iptr, iend);
|
||||||
|
iend = gBuffer[1] + len1;
|
||||||
|
cr.run(*cr.data, kLineBreakKeep, &st, &optr, oend, &iptr, iend);
|
||||||
|
if (iptr != iend) {
|
||||||
|
Failf("some data failed to convert");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
len2 = optr - gBuffer[2];
|
||||||
|
Check(len0, len1, len2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
free(data);
|
||||||
|
if (cf.data != NULL) {
|
||||||
|
DisposeHandle(cf.data);
|
||||||
|
}
|
||||||
|
if (cr.data != NULL) {
|
||||||
|
DisposeHandle(cr.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
void *buf;
|
||||||
|
const char *filename;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
buf = malloc(kConvertBufferSize);
|
||||||
|
if (buf == NULL) {
|
||||||
|
DieErrorf(errno, "malloc");
|
||||||
|
}
|
||||||
|
gBuffer[i] = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0;; i++) {
|
||||||
|
filename = kCharsetFilename[i];
|
||||||
|
if (filename == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
TestConverter(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
free(gBuffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gFailCount == 0 ? 0 : 1;
|
||||||
|
}
|
17
src/test.h
Normal file
17
src/test.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef test_h
|
||||||
|
#define test_h
|
||||||
|
/* test.h - unit testing definitions. */
|
||||||
|
|
||||||
|
#include "src/defs.h"
|
||||||
|
|
||||||
|
/* List of all data files, terminated by NULL. */
|
||||||
|
extern const char *const kCharsetFilename[];
|
||||||
|
|
||||||
|
/* Print an error message and exit. */
|
||||||
|
void Dief(const char *msg, ...) __attribute__((noreturn, format(printf, 1, 2)));
|
||||||
|
|
||||||
|
/* Print an error message with an error code and exit. */
|
||||||
|
void DieErrorf(int errcode, const char *msg, ...)
|
||||||
|
__attribute__((noreturn, format(printf, 2, 3)));
|
||||||
|
|
||||||
|
#endif
|
|
@ -3,17 +3,16 @@
|
||||||
This is used to run conversion tests on non-Mac OS systems to make
|
This is used to run conversion tests on non-Mac OS systems to make
|
||||||
development easier. These are not intended to make it possible to port the
|
development easier. These are not intended to make it possible to port the
|
||||||
converter to non-Mac OS systems. */
|
converter to non-Mac OS systems. */
|
||||||
#include "defs.h"
|
#include "src/defs.h"
|
||||||
|
#include "src/test.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static void Dief(const char *msg, ...)
|
void Dief(const char *msg, ...)
|
||||||
__attribute__((noreturn, format(printf, 1, 2)));
|
{
|
||||||
|
|
||||||
static void Dief(const char *msg, ...) {
|
|
||||||
va_list ap;
|
va_list ap;
|
||||||
fputs("Error: ", stderr);
|
fputs("Error: ", stderr);
|
||||||
va_start(ap, msg);
|
va_start(ap, msg);
|
||||||
|
@ -23,7 +22,21 @@ static void Dief(const char *msg, ...) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle NewHandle(Size byteCount) {
|
void DieErrorf(int errcode, const char *msg, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
fputs("Error: ", stderr);
|
||||||
|
va_start(ap, msg);
|
||||||
|
vfprintf(stderr, msg, ap);
|
||||||
|
va_end(ap);
|
||||||
|
fputs(": ", stderr);
|
||||||
|
fputs(strerror(errcode), stderr);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle NewHandle(Size byteCount)
|
||||||
|
{
|
||||||
Ptr p;
|
Ptr p;
|
||||||
Handle h;
|
Handle h;
|
||||||
|
|
||||||
|
@ -42,22 +55,26 @@ Handle NewHandle(Size byteCount) {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HLock(Handle h) {
|
void HLock(Handle h)
|
||||||
|
{
|
||||||
(void)h;
|
(void)h;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HUnlock(Handle h) {
|
void HUnlock(Handle h)
|
||||||
|
{
|
||||||
(void)h;
|
(void)h;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisposeHandle(Handle h) {
|
void DisposeHandle(Handle h)
|
||||||
|
{
|
||||||
if (h != NULL) {
|
if (h != NULL) {
|
||||||
free(*h);
|
free(*h);
|
||||||
free(h);
|
free(h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetHandleSize(Handle h, Size newSize) {
|
void SetHandleSize(Handle h, Size newSize)
|
||||||
|
{
|
||||||
Ptr p;
|
Ptr p;
|
||||||
if (h == NULL) {
|
if (h == NULL) {
|
||||||
Dief("SetHandleSize: h = NULL");
|
Dief("SetHandleSize: h = NULL");
|
||||||
|
@ -69,11 +86,13 @@ void SetHandleSize(Handle h, Size newSize) {
|
||||||
*h = p;
|
*h = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
OSErr MemError(void) {
|
OSErr MemError(void)
|
||||||
|
{
|
||||||
/* Memory allocation failures abort the program. */
|
/* Memory allocation failures abort the program. */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemClear(void *ptr, Size size) {
|
void MemClear(void *ptr, Size size)
|
||||||
|
{
|
||||||
memset(ptr, 0, size);
|
memset(ptr, 0, size);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user