Skip to content

Commit b869bf7

Browse files
authored
Merge pull request #43 from mrchypark/feat/kiwi-v0.23.2-upgrade
feat: Upgrade Kiwi to v0.23.2 with new API bindings
2 parents 18d764b + 6427cec commit b869bf7

4 files changed

Lines changed: 432 additions & 39 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
KIWI_VERSION := v0.23.0
1+
KIWI_VERSION := v0.23.2
22

33
.PHONY: test
44
test: base/default.dict

kiwi.go

Lines changed: 288 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,75 @@ import (
2828
type BuildOption int
2929

3030
const (
31-
KIWI_BUILD_LOAD_DEFAULT_DICT BuildOption = C.KIWI_BUILD_LOAD_DEFAULT_DICT
3231
KIWI_BUILD_INTEGRATE_ALLOMORPH BuildOption = C.KIWI_BUILD_INTEGRATE_ALLOMORPH
32+
KIWI_BUILD_LOAD_DEFAULT_DICT BuildOption = C.KIWI_BUILD_LOAD_DEFAULT_DICT
33+
KIWI_BUILD_LOAD_TYPO_DICT BuildOption = C.KIWI_BUILD_LOAD_TYPO_DICT
34+
KIWI_BUILD_LOAD_MULTI_DICT BuildOption = C.KIWI_BUILD_LOAD_MULTI_DICT
3335
KIWI_BUILD_DEFAULT BuildOption = C.KIWI_BUILD_DEFAULT
36+
37+
// Model type is a single-select field, not a bitmask.
38+
// Select exactly one of the following mutually exclusive model types:
39+
//
40+
// - MODEL_TYPE_DEFAULT: default model (0x0000)
41+
// - MODEL_TYPE_LARGEST: largest available model
42+
// - MODEL_TYPE_KNLM: KNLM model (deprecated)
43+
// - MODEL_TYPE_SBG: SBG model (deprecated)
44+
// - MODEL_TYPE_CONG: CoNg model
45+
// - MODEL_TYPE_CONG_GLOBAL: CoNg global model
46+
//
47+
// Do NOT combine these with bitwise OR.
48+
KIWI_BUILD_MODEL_TYPE_DEFAULT BuildOption = C.KIWI_BUILD_MODEL_TYPE_DEFAULT
49+
KIWI_BUILD_MODEL_TYPE_LARGEST BuildOption = C.KIWI_BUILD_MODEL_TYPE_LARGEST
50+
KIWI_BUILD_MODEL_TYPE_KNLM BuildOption = C.KIWI_BUILD_MODEL_TYPE_KNLM
51+
KIWI_BUILD_MODEL_TYPE_SBG BuildOption = C.KIWI_BUILD_MODEL_TYPE_SBG
52+
KIWI_BUILD_MODEL_TYPE_CONG BuildOption = C.KIWI_BUILD_MODEL_TYPE_CONG
53+
KIWI_BUILD_MODEL_TYPE_CONG_GLOBAL BuildOption = C.KIWI_BUILD_MODEL_TYPE_CONG_GLOBAL
3454
)
3555

3656
// MatchOption is a bitwise OR of the KiwiMatchOption values.
3757
type MatchOption int
3858

3959
const (
40-
KIWI_MATCH_URL MatchOption = C.KIWI_MATCH_URL
41-
KIWI_MATCH_EMAIL MatchOption = C.KIWI_MATCH_EMAIL
42-
KIWI_MATCH_HASHTAG MatchOption = C.KIWI_MATCH_HASHTAG
43-
KIWI_MATCH_MENTION MatchOption = C.KIWI_MATCH_MENTION
60+
KIWI_MATCH_URL MatchOption = C.KIWI_MATCH_URL
61+
KIWI_MATCH_EMAIL MatchOption = C.KIWI_MATCH_EMAIL
62+
KIWI_MATCH_HASHTAG MatchOption = C.KIWI_MATCH_HASHTAG
63+
KIWI_MATCH_MENTION MatchOption = C.KIWI_MATCH_MENTION
64+
KIWI_MATCH_SERIAL MatchOption = C.KIWI_MATCH_SERIAL
65+
KIWI_MATCH_EMOJI MatchOption = C.KIWI_MATCH_EMOJI
66+
67+
// OOV detection mode is a 2-bit field (bits 8-9), not independent flags.
68+
// Select exactly one of the following mutually exclusive modes:
69+
//
70+
// - OOV_RULE_ONLY: rule-based scoring (default, value 0 << 8)
71+
// - OOV_CHR_MODEL: character model-based scoring
72+
// - OOV_CHR_FREQ_MODEL: character + frequency model scoring
73+
// - OOV_CHR_FREQ_BRANCH_MODEL: character + frequency + branch model scoring
74+
//
75+
// Do NOT combine these with bitwise OR. KIWI_MATCH_OOV_MASK is for
76+
// internal use and should not be selected directly.
77+
KIWI_MATCH_OOV_RULE_ONLY MatchOption = C.KIWI_MATCH_OOV_RULE_ONLY
78+
KIWI_MATCH_OOV_CHR_MODEL MatchOption = C.KIWI_MATCH_OOV_CHR_MODEL
79+
KIWI_MATCH_OOV_CHR_FREQ_MODEL MatchOption = C.KIWI_MATCH_OOV_CHR_FREQ_MODEL
80+
KIWI_MATCH_OOV_CHR_FREQ_BRANCH_MODEL MatchOption = C.KIWI_MATCH_OOV_CHR_FREQ_BRANCH_MODEL
81+
KIWI_MATCH_OOV_MASK MatchOption = C.KIWI_MATCH_OOV_MASK
82+
83+
KIWI_MATCH_NORMALIZE_CODA MatchOption = C.KIWI_MATCH_NORMALIZE_CODA
84+
KIWI_MATCH_JOIN_NOUN_PREFIX MatchOption = C.KIWI_MATCH_JOIN_NOUN_PREFIX
85+
KIWI_MATCH_JOIN_NOUN_SUFFIX MatchOption = C.KIWI_MATCH_JOIN_NOUN_SUFFIX
86+
KIWI_MATCH_JOIN_VERB_SUFFIX MatchOption = C.KIWI_MATCH_JOIN_VERB_SUFFIX
87+
KIWI_MATCH_JOIN_ADJ_SUFFIX MatchOption = C.KIWI_MATCH_JOIN_ADJ_SUFFIX
88+
KIWI_MATCH_JOIN_ADV_SUFFIX MatchOption = C.KIWI_MATCH_JOIN_ADV_SUFFIX
89+
KIWI_MATCH_JOIN_V_SUFFIX MatchOption = C.KIWI_MATCH_JOIN_V_SUFFIX
90+
KIWI_MATCH_JOIN_AFFIX MatchOption = C.KIWI_MATCH_JOIN_AFFIX
91+
KIWI_MATCH_SPLIT_COMPLEX MatchOption = C.KIWI_MATCH_SPLIT_COMPLEX
92+
KIWI_MATCH_Z_CODA MatchOption = C.KIWI_MATCH_Z_CODA
93+
KIWI_MATCH_COMPATIBLE_JAMO MatchOption = C.KIWI_MATCH_COMPATIBLE_JAMO
94+
KIWI_MATCH_SPLIT_SAISIOT MatchOption = C.KIWI_MATCH_SPLIT_SAISIOT
95+
KIWI_MATCH_MERGE_SAISIOT MatchOption = C.KIWI_MATCH_MERGE_SAISIOT
96+
KIWI_MATCH_JOIN_PARTICLE_YO MatchOption = C.KIWI_MATCH_JOIN_PARTICLE_YO
97+
KIWI_MATCH_USE_OLD_SPLITTER MatchOption = C.KIWI_MATCH_USE_OLD_SPLITTER
98+
4499
KIWI_MATCH_ALL MatchOption = C.KIWI_MATCH_ALL
45-
KIWI_MATCH_NORMALIZE_CODA MatchOption = C.KIWI_MATCH_NORMALIZE_CODA
46100
KIWI_MATCH_ALL_WITH_NORMALIZING MatchOption = C.KIWI_MATCH_ALL_WITH_NORMALIZING
47101
)
48102

@@ -136,12 +190,80 @@ func WithTopN(n int) AnalyzeOptionFunc {
136190
}
137191
}
138192

193+
// WithBlocklist sets the blocklist for Analyze.
194+
func WithBlocklist(blocklist *Morphset) AnalyzeOptionFunc {
195+
return func(opts *AnalyzeOptions) {
196+
opts.Blocklist = blocklist
197+
}
198+
}
199+
200+
// WithOpenEnding sets whether to keep the sentence open after the last morpheme.
201+
func WithOpenEnding(openEnding bool) AnalyzeOptionFunc {
202+
return func(opts *AnalyzeOptions) {
203+
opts.OpenEnding = openEnding
204+
}
205+
}
206+
207+
// WithAllowedDialects sets the allowed dialects for Analyze.
208+
func WithAllowedDialects(dialects Dialect) AnalyzeOptionFunc {
209+
return func(opts *AnalyzeOptions) {
210+
opts.AllowedDialects = dialects
211+
}
212+
}
213+
139214
// AnalyzeOptions provides configuration for the Analyze function.
140215
type AnalyzeOptions struct {
141-
MatchOptions MatchOption
142-
DialectCost float32
143-
TypoThreshold float32
144-
TopN int
216+
MatchOptions MatchOption
217+
Blocklist *Morphset
218+
OpenEnding bool
219+
AllowedDialects Dialect
220+
DialectCost float32
221+
TypoThreshold float32
222+
TopN int
223+
}
224+
225+
// Morphset represents a set of morphemes that can be used as a blocklist.
226+
type Morphset struct {
227+
handler C.kiwi_morphset_h
228+
}
229+
230+
// NewMorphset creates a new morpheme set.
231+
// The Morphset must be closed after use, before the parent Kiwi instance is closed.
232+
func (k *Kiwi) NewMorphset() (*Morphset, error) {
233+
h := C.kiwi_new_morphset(k.handler)
234+
if h == nil {
235+
return nil, fmt.Errorf("failed to create morphset: %s", KiwiError())
236+
}
237+
return &Morphset{handler: h}, nil
238+
}
239+
240+
// Add adds a morpheme to the set.
241+
// tag is a POS tag such as "NNG". If tag is empty, all morphemes matching form are added.
242+
// Returns the number of morphemes added, or an error.
243+
func (ms *Morphset) Add(form string, tag string) (int, error) {
244+
cForm := C.CString(form)
245+
defer C.free(unsafe.Pointer(cForm))
246+
247+
var cTag *C.char
248+
if tag != "" {
249+
cTag = C.CString(tag)
250+
defer C.free(unsafe.Pointer(cTag))
251+
}
252+
253+
result := int(C.kiwi_morphset_add(ms.handler, cForm, cTag))
254+
if result < 0 {
255+
return 0, fmt.Errorf("failed to add morpheme: %s", KiwiError())
256+
}
257+
return result, nil
258+
}
259+
260+
// Close frees the resources allocated for the Morphset.
261+
// Must be called before the parent Kiwi instance is closed.
262+
func (ms *Morphset) Close() {
263+
if ms.handler != nil {
264+
C.kiwi_morphset_close(ms.handler)
265+
ms.handler = nil
266+
}
145267
}
146268

147269
// DefaultAnalyzeOptions returns the default AnalyzeOptions recommended by Kiwi.
@@ -170,13 +292,18 @@ func KiwiClearError() {
170292
}
171293

172294
// Kiwi is a wrapper for the kiwi C library.
295+
//
296+
// Thread Safety: Concurrent calls to Analyze are safe.
297+
// However, SetGlobalConfig must not be called concurrently with Analyze
298+
// or other methods that read the configuration.
173299
type Kiwi struct {
174-
handler C.kiwi_h
300+
handler C.kiwi_h
301+
dialects Dialect
175302
}
176303

177304
// New returns a new Kiwi instance.
178305
// Don't forget to call Close after this.
179-
func New(modelPath string, opts ...Option) *Kiwi {
306+
func New(modelPath string, opts ...Option) (*Kiwi, error) {
180307
options := kiwiOptions{
181308
buildOptions: KIWI_BUILD_DEFAULT,
182309
dialects: DialectStandard,
@@ -186,9 +313,18 @@ func New(modelPath string, opts ...Option) *Kiwi {
186313
opt(&options)
187314
}
188315

189-
return &Kiwi{
190-
handler: C.kiwi_init(C.CString(modelPath), C.int(options.numThread), C.int(options.buildOptions), C.int(options.dialects)),
316+
cModelPath := C.CString(modelPath)
317+
defer C.free(unsafe.Pointer(cModelPath))
318+
319+
h := C.kiwi_init(cModelPath, C.int(options.numThread), C.int(options.buildOptions), C.int(options.dialects))
320+
if h == nil {
321+
return nil, fmt.Errorf("kiwi_init failed: %s", KiwiError())
191322
}
323+
324+
return &Kiwi{
325+
handler: h,
326+
dialects: options.dialects,
327+
}, nil
192328
}
193329

194330
// TokenInfo returns the token info for the given token(Str).
@@ -223,10 +359,28 @@ func (k *Kiwi) Analyze(text string, opts ...AnalyzeOptionFunc) ([]TokenResult, e
223359

224360
defer C.free(unsafe.Pointer(cText))
225361

362+
allowedDialects := options.AllowedDialects
363+
if allowedDialects == 0 {
364+
allowedDialects = k.dialects
365+
}
366+
367+
var blocklistHandler C.kiwi_morphset_h
368+
if options.Blocklist != nil {
369+
blocklistHandler = options.Blocklist.handler
370+
}
371+
372+
openEnding := 0
373+
if options.OpenEnding {
374+
openEnding = 1
375+
}
376+
226377
cOptions := C.kiwi_analyze_option_t{
227-
match_options: C.int(options.MatchOptions),
228-
dialect_cost: C.float(options.DialectCost),
229-
typo_threshold: C.float(options.TypoThreshold),
378+
match_options: C.int(options.MatchOptions),
379+
blocklist: blocklistHandler,
380+
open_ending: C.int(openEnding),
381+
allowed_dialects: C.int(allowedDialects),
382+
dialect_cost: C.float(options.DialectCost),
383+
typo_threshold: C.float(options.TypoThreshold),
230384
}
231385

232386
kiwiResH := C.kiwi_analyze(k.handler, cText, C.int(options.TopN), cOptions, pretokenized)
@@ -334,7 +488,7 @@ type KiwiBuilder struct {
334488

335489
// NewBuilder returns a new KiwiBuilder instance.
336490
// Don't forget to call Close after this.
337-
func NewBuilder(modelPath string, opts ...Option) *KiwiBuilder {
491+
func NewBuilder(modelPath string, opts ...Option) (*KiwiBuilder, error) {
338492
options := kiwiOptions{
339493
buildOptions: KIWI_BUILD_DEFAULT,
340494
dialects: DialectStandard,
@@ -344,19 +498,36 @@ func NewBuilder(modelPath string, opts ...Option) *KiwiBuilder {
344498
opt(&options)
345499
}
346500

347-
return &KiwiBuilder{
348-
handler: C.kiwi_builder_init(C.CString(modelPath), C.int(options.numThread), C.int(options.buildOptions), C.int(options.dialects)),
501+
cModelPath := C.CString(modelPath)
502+
defer C.free(unsafe.Pointer(cModelPath))
503+
504+
h := C.kiwi_builder_init(cModelPath, C.int(options.numThread), C.int(options.buildOptions), C.int(options.dialects))
505+
if h == nil {
506+
return nil, fmt.Errorf("kiwi_builder_init failed: %s", KiwiError())
349507
}
508+
509+
return &KiwiBuilder{
510+
handler: h,
511+
}, nil
350512
}
351513

352514
// AddWord set custom word with word, pos, score.
353515
func (kb *KiwiBuilder) AddWord(word string, pos POSType, score float32) int {
354-
return int(C.kiwi_builder_add_word(kb.handler, C.CString(word), C.CString(string(pos)), C.float(score)))
516+
cWord := C.CString(word)
517+
defer C.free(unsafe.Pointer(cWord))
518+
519+
cPos := C.CString(string(pos))
520+
defer C.free(unsafe.Pointer(cPos))
521+
522+
return int(C.kiwi_builder_add_word(kb.handler, cWord, cPos, C.float(score)))
355523
}
356524

357525
// LoadDict loads user dict with dict file path.
358526
func (kb *KiwiBuilder) LoadDict(dictPath string) int {
359-
return int(C.kiwi_builder_load_dict(kb.handler, C.CString(dictPath)))
527+
cDictPath := C.CString(dictPath)
528+
defer C.free(unsafe.Pointer(cDictPath))
529+
530+
return int(C.kiwi_builder_load_dict(kb.handler, cDictPath))
360531
}
361532

362533
// Build creates kiwi instance with user word etc.
@@ -451,3 +622,98 @@ func (kb *KiwiBuilder) ExtractWords(readSeeker io.ReadSeeker, minCnt int, maxWor
451622

452623
return res, nil
453624
}
625+
626+
// Config represents the configuration for Kiwi analysis.
627+
type Config struct {
628+
IntegrateAllomorph bool
629+
CutOffThreshold float32
630+
OovRuleScale float32
631+
OovRuleBias float32
632+
OovChrBias float32
633+
OovGlobalWeight float32
634+
OovLocalWeight float32
635+
OovGlobalMinFreq float32
636+
SpacePenalty float32
637+
TypoCostWeight float32
638+
MaxUnkFormSize uint32
639+
MaxUnkFormSizeFollowedByJClass uint32
640+
SpaceTolerance uint32
641+
}
642+
643+
// GetGlobalConfig returns the global configuration of the Kiwi instance.
644+
func (k *Kiwi) GetGlobalConfig() Config {
645+
cConfig := C.kiwi_get_global_config(k.handler)
646+
return Config{
647+
IntegrateAllomorph: cConfig.integrate_allomorph != 0,
648+
CutOffThreshold: float32(cConfig.cut_off_threshold),
649+
OovRuleScale: float32(cConfig.oov_rule_scale),
650+
OovRuleBias: float32(cConfig.oov_rule_bias),
651+
OovChrBias: float32(cConfig.oov_chr_bias),
652+
OovGlobalWeight: float32(cConfig.oov_global_weight),
653+
OovLocalWeight: float32(cConfig.oov_local_weight),
654+
OovGlobalMinFreq: float32(cConfig.oov_global_min_freq),
655+
SpacePenalty: float32(cConfig.space_penalty),
656+
TypoCostWeight: float32(cConfig.typo_cost_weight),
657+
MaxUnkFormSize: uint32(cConfig.max_unk_form_size),
658+
MaxUnkFormSizeFollowedByJClass: uint32(cConfig.max_unk_form_size_followed_by_j_class),
659+
SpaceTolerance: uint32(cConfig.space_tolerance),
660+
}
661+
}
662+
663+
// SetGlobalConfig sets the global configuration of the Kiwi instance.
664+
func (k *Kiwi) SetGlobalConfig(config Config) {
665+
cConfig := C.kiwi_config_t{
666+
integrate_allomorph: boolToCUint8(config.IntegrateAllomorph),
667+
cut_off_threshold: C.float(config.CutOffThreshold),
668+
oov_rule_scale: C.float(config.OovRuleScale),
669+
oov_rule_bias: C.float(config.OovRuleBias),
670+
oov_chr_bias: C.float(config.OovChrBias),
671+
oov_global_weight: C.float(config.OovGlobalWeight),
672+
oov_local_weight: C.float(config.OovLocalWeight),
673+
oov_global_min_freq: C.float(config.OovGlobalMinFreq),
674+
space_penalty: C.float(config.SpacePenalty),
675+
typo_cost_weight: C.float(config.TypoCostWeight),
676+
max_unk_form_size: C.uint(config.MaxUnkFormSize),
677+
max_unk_form_size_followed_by_j_class: C.uint(config.MaxUnkFormSizeFollowedByJClass),
678+
space_tolerance: C.uint(config.SpaceTolerance),
679+
}
680+
C.kiwi_set_global_config(k.handler, cConfig)
681+
}
682+
683+
func boolToCUint8(b bool) C.uint8_t {
684+
if b {
685+
return 1
686+
}
687+
return 0
688+
}
689+
690+
// OptionType represents the type of option for kiwi_set_option/kiwi_get_option.
691+
type OptionType int
692+
693+
const (
694+
KIWI_NUM_THREADS OptionType = C.KIWI_NUM_THREADS
695+
)
696+
697+
// GetOptionF returns the float value of the specified option.
698+
// Note: As of Kiwi v0.23.2, there are no float options available.
699+
// This function is provided for future compatibility.
700+
func (k *Kiwi) GetOptionF(option OptionType) float32 {
701+
return float32(C.kiwi_get_option_f(k.handler, C.int(option)))
702+
}
703+
704+
// SetOptionF sets the float value of the specified option.
705+
// Note: As of Kiwi v0.23.2, there are no float options available.
706+
// This function is provided for future compatibility.
707+
func (k *Kiwi) SetOptionF(option OptionType, value float32) {
708+
C.kiwi_set_option_f(k.handler, C.int(option), C.float(value))
709+
}
710+
711+
// GetOption returns the int value of the specified option.
712+
func (k *Kiwi) GetOption(option OptionType) int {
713+
return int(C.kiwi_get_option(k.handler, C.int(option)))
714+
}
715+
716+
// SetOption sets the int value of the specified option.
717+
func (k *Kiwi) SetOption(option OptionType, value int) {
718+
C.kiwi_set_option(k.handler, C.int(option), C.int(value))
719+
}

0 commit comments

Comments
 (0)