/* * 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 #include #include #include 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; }