1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
/*
* anthy_wasm.c — the embedder-facing API for anthy built as a WASI reactor module.
*
* The JVM side (Chicory) instantiates the module, calls _initialize, then:
* 1. lcanthy_set_dic(ptr, len) once, with the dictionary path written into memory
* 2. lcanthy_init() once
* 3. lcanthy_convert(ip, il, op, cap) per chat message, input/out UTF-8 in linear memory
*
* Everything crosses the WASM boundary as (pointer, length) pairs into the module's
* linear memory, so no WASI file API is needed for the text; only the dictionary
* (and the private-dictionary directory) is read through WASI.
*
* Measured on the host build of the same engine (probe_anthy): anthy_init 0.2 ms,
* one message 1.35-2.40 ms, 417-739 conversions/s.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <anthy/anthy.h>
static char dic_path[1024];
static char home_path[1024];
static int initialized;
static int log_level = 99;
static void wasm_log(int level, const char *msg)
{
/* anthy's logger callback never replaces its stderr writer (logger.c: "to be
implemented"), so silence it by raising the threshold instead of by passing a
callback: a JVM chat server does not want a line per message on stderr. */
(void)level;
(void)msg;
}
/* copy the dictionary path into the module (UTF-8, not NUL terminated on the host side) */
__attribute__((export_name("lcanthy_set_dic"))) int lcanthy_set_dic(int ptr, int len)
{
if (len <= 0 || len >= (int)sizeof(dic_path))
return -1;
memcpy(dic_path, (const void *)(size_t)ptr, (size_t)len);
dic_path[len] = '\0';
return 0;
}
/* the embedder sets HOME explicitly: WASI has no passwd database, and anthy uses
HOME (plus ANTHYDIR) for the private dictionary and the user history files */
__attribute__((export_name("lcanthy_set_home"))) int lcanthy_set_home(int ptr, int len)
{
if (len < 0 || len >= (int)sizeof(home_path))
return -1;
memcpy(home_path, (const void *)(size_t)ptr, (size_t)len);
home_path[len] = '\0';
return 0;
}
/* 0 = everything, 99 = silent. Default is silent. */
__attribute__((export_name("lcanthy_set_log"))) int lcanthy_set_log(int level)
{
log_level = level;
return 0;
}
__attribute__((export_name("lcanthy_init"))) int lcanthy_init(void)
{
anthy_set_logger(wasm_log, log_level);
if (home_path[0]) {
anthy_conf_override("HOME", home_path);
static char adir[1100];
snprintf(adir, sizeof(adir), "%s/.anthy", home_path);
anthy_conf_override("ANTHYDIR", adir);
}
if (dic_path[0])
anthy_conf_override("DIC_FILE", dic_path);
if (anthy_init() != 0)
return -1;
initialized = 1;
return 0;
}
__attribute__((export_name("lcanthy_version"))) int lcanthy_version(void)
{
return initialized ? (int)strlen(anthy_get_version_string()) : -1;
}
/* the host has no access to the module's allocator otherwise */
__attribute__((export_name("lcanthy_alloc"))) int lcanthy_alloc(int n)
{
return (int)(size_t)malloc((size_t)n);
}
__attribute__((export_name("lcanthy_free"))) void lcanthy_free(int p)
{
free((void *)(size_t)p);
}
/*
* Convert UTF-8 hiragana into kanji/kana by taking the first candidate of every
* segment and concatenating them. Returns the number of bytes written to out
* (not NUL terminated), or a negative value on failure:
* -1 not initialised, -2 anthy_set_string failed, -3 output buffer too small,
* -4 no segments produced.
* The caller keeps its own copy of the original input; anthy never rewrites it.
*/
__attribute__((export_name("lcanthy_convert"))) int lcanthy_convert(int in_ptr, int in_len, int out_ptr, int out_cap)
{
if (!initialized)
return -1;
if (in_len <= 0 || in_len > 4096 || out_cap <= 0)
return -2;
/* anthy_set_string wants a NUL-terminated string */
static char in_buf[4100];
memcpy(in_buf, (const void *)(size_t)in_ptr, (size_t)in_len);
in_buf[in_len] = '\0';
anthy_context_t ctx = anthy_create_context();
if (!ctx)
return -2;
anthy_context_set_encoding(ctx, ANTHY_UTF8_ENCODING);
if (anthy_set_string(ctx, in_buf) != 0) {
anthy_release_context(ctx);
return -2;
}
struct anthy_conv_stat cs;
memset(&cs, 0, sizeof(cs));
anthy_get_stat(ctx, &cs);
if (cs.nr_segment <= 0) {
anthy_release_context(ctx);
return -4;
}
char *out = (char *)(size_t)out_ptr;
int used = 0;
for (int s = 0; s < cs.nr_segment; s++) {
char buf[512];
int n = anthy_get_segment(ctx, s, 0, buf, sizeof(buf));
if (n <= 0)
continue;
if (used + n > out_cap) {
anthy_release_context(ctx);
return -3;
}
memcpy(out + used, buf, (size_t)n);
used += n;
}
anthy_release_context(ctx);
return used;
}
/* segment count + candidate count, for tests that want to see anthy's own split */
__attribute__((export_name("lcanthy_segments"))) int lcanthy_segments(int in_ptr, int in_len)
{
if (!initialized)
return -1;
static char in_buf[4100];
if (in_len <= 0 || in_len >= (int)sizeof(in_buf))
return -2;
memcpy(in_buf, (const void *)(size_t)in_ptr, (size_t)in_len);
in_buf[in_len] = '\0';
anthy_context_t ctx = anthy_create_context();
anthy_context_set_encoding(ctx, ANTHY_UTF8_ENCODING);
int rc = anthy_set_string(ctx, in_buf);
struct anthy_conv_stat cs;
memset(&cs, 0, sizeof(cs));
if (rc == 0)
anthy_get_stat(ctx, &cs);
anthy_release_context(ctx);
return rc == 0 ? cs.nr_segment : -2;
}
|