mirror of
https://github.com/bab2min/Kiwi.git
synced 2026-06-17 01:54:27 +00:00
Compare commits
4 commits
main
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91504852a1 | ||
|
|
0219b4ddfd | ||
|
|
25f145ba1c | ||
|
|
b0e6541c42 |
10 changed files with 1197 additions and 1 deletions
|
|
@ -14,6 +14,7 @@ option(KIWI_BUILD_EVALUATOR "Build Evaluator" ON)
|
|||
option(KIWI_BUILD_MODEL_BUILDER "Build Model Builder" ON)
|
||||
option(KIWI_BUILD_TEST "Build Test sets" ON)
|
||||
option(KIWI_JAVA_BINDING "Build Java binding" OFF)
|
||||
option(KIWI_IOS_BINDING "Build iOS binding" OFF)
|
||||
set(KIWI_CPU_ARCH "" CACHE STRING "Set architecture type for macOS")
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
|
|
@ -367,6 +368,10 @@ if(KIWI_JAVA_BINDING)
|
|||
add_subdirectory( bindings/java )
|
||||
endif()
|
||||
|
||||
if(KIWI_IOS_BINDING)
|
||||
add_subdirectory( bindings/ios )
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
add_subdirectory( bindings/wasm )
|
||||
endif()
|
||||
10
README.md
10
README.md
|
|
@ -142,9 +142,17 @@ Java 1.8 이상에서 사용 가능한 KiwiJava가 Java binding으로 제공됩
|
|||
### Android Library
|
||||
Android NDK를 통해 Android 앱에서 사용할 수 있는 AAR 라이브러리가 제공됩니다. GitHub Releases에서 `kiwi-android-VERSION.aar` 파일을 다운로드하여 Android 프로젝트에 추가하면 됩니다.
|
||||
- **최소 요구사항**: Android API Level 21+, ARM64 아키텍처
|
||||
- **사용법**: [bindings/android](bindings/android)의 README 참조
|
||||
- **사용법**: [bindings/java](bindings/java)의 README의 "Android에서 사용하기" 섹션 참조
|
||||
- **패키지**: AAR 형태로 제공되어 Gradle 프로젝트에 쉽게 통합 가능
|
||||
|
||||
### iOS Library (개발 예정)
|
||||
iOS 지원은 현재 개발 계획에 포함되어 있으며, 차기 개발 목표로 설정되어 있습니다.
|
||||
- **계획된 기능**: Swift/Objective-C 바인딩을 통한 iOS 앱 지원
|
||||
- **예상 형태**: CocoaPods 또는 Swift Package Manager를 통한 배포
|
||||
- **개발 일정**: 구체적인 일정은 아직 미정이며, 개발 진행 상황은 GitHub Issues를 통해 공유될 예정입니다
|
||||
- **자세한 정보**: [bindings/ios](bindings/ios)의 README 참조
|
||||
- **기여 환영**: iOS 개발에 경험이 있으신 분들의 기여와 피드백을 환영합니다
|
||||
|
||||
### R Wrapper
|
||||
[mrchypark](https://github.com/mrchypark)님께서 기여해주신 R언어용 wrapper인 [elbird](https://mrchypark.github.io/elbird/)가 있습니다.
|
||||
|
||||
|
|
|
|||
73
bindings/ios/CMakeLists.txt
Normal file
73
bindings/ios/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# iOS binding CMakeLists.txt for Kiwi
|
||||
# This is a work-in-progress implementation following the iOS roadmap
|
||||
|
||||
# iOS binding requires Xcode and iOS SDK
|
||||
if(NOT IOS)
|
||||
message(STATUS "iOS binding requires iOS SDK. Use -DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Check for required iOS development tools
|
||||
if(NOT DEFINED CMAKE_TOOLCHAIN_FILE OR NOT CMAKE_TOOLCHAIN_FILE MATCHES "ios")
|
||||
message(WARNING "iOS binding requires iOS toolchain. Consider using ios-cmake toolchain.")
|
||||
endif()
|
||||
|
||||
set(pkg_name "KiwiSwift")
|
||||
|
||||
# Collect required object files
|
||||
set(OBJECTS $<TARGET_OBJECTS:${PROJECT_NAME}_static> $<TARGET_OBJECTS:streamvbyte>)
|
||||
|
||||
if(KIWI_USE_CPUINFO)
|
||||
list(APPEND OBJECTS $<TARGET_OBJECTS:cpuinfo>)
|
||||
endif()
|
||||
|
||||
# Create static library for iOS (required for App Store distribution)
|
||||
add_library(${pkg_name} STATIC
|
||||
csrc/kiwi_swift.cpp
|
||||
${OBJECTS}
|
||||
)
|
||||
|
||||
# Set iOS-specific compile features and options
|
||||
target_compile_features(${pkg_name} PUBLIC cxx_std_17)
|
||||
|
||||
# iOS-specific compile definitions
|
||||
target_compile_definitions(${pkg_name} PRIVATE
|
||||
IOS=1
|
||||
KIWI_IOS_BINDING=1
|
||||
)
|
||||
|
||||
# Optimize for mobile performance
|
||||
target_compile_options(${pkg_name} PRIVATE
|
||||
-O3
|
||||
-fvisibility=hidden
|
||||
-fvisibility-inlines-hidden
|
||||
)
|
||||
|
||||
# Set up framework structure for iOS
|
||||
set_target_properties(${pkg_name} PROPERTIES
|
||||
FRAMEWORK TRUE
|
||||
FRAMEWORK_VERSION A
|
||||
MACOSX_FRAMEWORK_IDENTIFIER com.bab2min.kiwi
|
||||
PUBLIC_HEADER ""
|
||||
OUTPUT_NAME "Kiwi"
|
||||
)
|
||||
|
||||
# Installation rules for iOS framework
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Install prefix" FORCE)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${pkg_name}
|
||||
FRAMEWORK DESTINATION .
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
|
||||
# Copy Swift interface and headers
|
||||
install(FILES
|
||||
"include/Kiwi.h"
|
||||
DESTINATION include
|
||||
)
|
||||
|
||||
message(STATUS "iOS binding configured for ${CMAKE_OSX_ARCHITECTURES}")
|
||||
message(STATUS "iOS Deployment Target: ${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
72
bindings/ios/KiwiSwift.podspec
Normal file
72
bindings/ios/KiwiSwift.podspec
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
Pod::Spec.new do |spec|
|
||||
spec.name = "KiwiSwift"
|
||||
spec.version = "0.21.0"
|
||||
spec.summary = "Korean Intelligent Word Identifier for iOS"
|
||||
spec.description = <<-DESC
|
||||
KiwiSwift is the iOS binding for Kiwi, a Korean morphological analyzer.
|
||||
This framework provides native Swift API for Korean text processing,
|
||||
including tokenization, morphological analysis, and sentence splitting.
|
||||
DESC
|
||||
|
||||
spec.homepage = "https://github.com/bab2min/Kiwi"
|
||||
spec.license = { :type => "LGPL-3.0", :file => "../../LICENSE" }
|
||||
spec.author = { "bab2min" => "bab2min@gmail.com" }
|
||||
|
||||
spec.ios.deployment_target = "12.0"
|
||||
spec.osx.deployment_target = "10.15"
|
||||
|
||||
spec.source = { :git => "https://github.com/bab2min/Kiwi.git", :tag => "v#{spec.version}" }
|
||||
|
||||
spec.source_files = [
|
||||
"swift/*.swift",
|
||||
"csrc/*.cpp",
|
||||
"include/*.h",
|
||||
"../../src/**/*.{cpp,h}",
|
||||
"../../include/kiwi/*.h",
|
||||
"../../third_party/streamvbyte/include/*.h",
|
||||
"../../third_party/streamvbyte/src/*.c"
|
||||
]
|
||||
|
||||
spec.public_header_files = "include/*.h"
|
||||
spec.private_header_files = "../../include/kiwi/*.h"
|
||||
|
||||
spec.header_search_paths = [
|
||||
"../../include",
|
||||
"../../third_party/streamvbyte/include",
|
||||
"../../third_party/eigen",
|
||||
"../../third_party/cpp-btree",
|
||||
"../../third_party/json/include"
|
||||
]
|
||||
|
||||
spec.compiler_flags = [
|
||||
"-DIOS=1",
|
||||
"-DKIWI_IOS_BINDING=1",
|
||||
"-std=c++17",
|
||||
"-O3",
|
||||
"-fvisibility=hidden"
|
||||
]
|
||||
|
||||
spec.xcconfig = {
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
|
||||
"CLANG_CXX_LIBRARY" => "libc++",
|
||||
"OTHER_CPLUSPLUSFLAGS" => "-DIOS=1 -DKIWI_IOS_BINDING=1"
|
||||
}
|
||||
|
||||
spec.frameworks = "Foundation"
|
||||
spec.libraries = "c++"
|
||||
|
||||
spec.requires_arc = true
|
||||
|
||||
# Exclude files that are not needed for iOS
|
||||
spec.exclude_files = [
|
||||
"../../src/**/test*",
|
||||
"../../tools/**/*",
|
||||
"../../test/**/*"
|
||||
]
|
||||
|
||||
spec.prepare_command = <<-CMD
|
||||
# This would typically download or prepare model files
|
||||
echo "Preparing KiwiSwift for iOS..."
|
||||
CMD
|
||||
|
||||
end
|
||||
46
bindings/ios/Package.swift
Normal file
46
bindings/ios/Package.swift
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// swift-tools-version:5.5
|
||||
// Package.swift for KiwiSwift iOS binding
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "KiwiSwift",
|
||||
platforms: [
|
||||
.iOS(.v12),
|
||||
.macOS(.v10_15)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "KiwiSwift",
|
||||
targets: ["KiwiSwift"]
|
||||
),
|
||||
],
|
||||
dependencies: [
|
||||
// No external dependencies - self-contained
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "KiwiSwift",
|
||||
dependencies: [],
|
||||
path: "swift",
|
||||
sources: ["Kiwi.swift"],
|
||||
publicHeadersPath: "../include",
|
||||
cxxSettings: [
|
||||
.define("IOS", to: "1"),
|
||||
.define("KIWI_IOS_BINDING", to: "1"),
|
||||
.headerSearchPath("../../../include"),
|
||||
.headerSearchPath("../include"),
|
||||
.unsafeFlags(["-std=c++17", "-O3"])
|
||||
],
|
||||
linkerSettings: [
|
||||
.linkedLibrary("c++")
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "KiwiSwiftTests",
|
||||
dependencies: ["KiwiSwift"],
|
||||
path: "tests"
|
||||
),
|
||||
],
|
||||
cxxLanguageStandard: .cxx17
|
||||
)
|
||||
194
bindings/ios/README.md
Normal file
194
bindings/ios/README.md
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
# KiwiSwift, 한국어 형태소 분석기 Kiwi의 iOS 바인딩
|
||||
|
||||
> **🚧 현재 상태**: iOS 바인딩의 기본 구조가 구현되었으며, 프로토타입 단계입니다.
|
||||
> 실제 사용을 위해서는 추가 개발과 테스트가 필요합니다.
|
||||
|
||||
## 현재 구현 상태
|
||||
|
||||
✅ **완료된 작업**:
|
||||
- 기본 CMake 빌드 설정
|
||||
- C++ 브릿지 구현 (`csrc/kiwi_swift.cpp`)
|
||||
- Objective-C 헤더 (`include/Kiwi.h`)
|
||||
- Swift API 래퍼 (`swift/Kiwi.swift`)
|
||||
- Swift Package Manager 설정 (`Package.swift`)
|
||||
- CocoaPods 스펙 (`KiwiSwift.podspec`)
|
||||
- 기본 단위 테스트 구조
|
||||
|
||||
🚧 **추가 개발 필요**:
|
||||
- iOS SDK와의 완전한 통합 테스트
|
||||
- 모델 파일 배포 방식 최적화
|
||||
- 메모리 사용량 최적화
|
||||
- 에러 핸들링 개선
|
||||
- 문서화 완성
|
||||
|
||||
## 현재 API 구조
|
||||
|
||||
기본적인 Swift API가 구현되어 있습니다:
|
||||
|
||||
```swift
|
||||
import KiwiSwift
|
||||
|
||||
// Kiwi 인스턴스 생성
|
||||
let kiwi = try Kiwi(modelPath: "path/to/model")
|
||||
|
||||
// 형태소 분석
|
||||
let tokens = try kiwi.tokenize("안녕하세요!", options: .normalizeAll)
|
||||
for token in tokens {
|
||||
print("\(token.form) / \(token.tag)")
|
||||
}
|
||||
|
||||
// 문장 분리
|
||||
let sentences = try kiwi.splitSentences("첫 번째 문장입니다. 두 번째 문장입니다.")
|
||||
|
||||
// 비동기 처리
|
||||
kiwi.tokenize("비동기 처리 예제", options: .normalizeAll) { result in
|
||||
switch result {
|
||||
case .success(let tokens):
|
||||
print("Tokens: \(tokens)")
|
||||
case .failure(let error):
|
||||
print("Error: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 빌드 방법
|
||||
|
||||
### 요구사항
|
||||
- Xcode 12.0+
|
||||
- iOS 12.0+ SDK
|
||||
- CMake 3.12+
|
||||
- ios-cmake 툴체인 (권장)
|
||||
|
||||
### 빌드 단계
|
||||
|
||||
1. **iOS 툴체인 설정**:
|
||||
```bash
|
||||
git clone https://github.com/leetal/ios-cmake.git
|
||||
```
|
||||
|
||||
2. **iOS용 빌드**:
|
||||
```bash
|
||||
cd bindings/ios
|
||||
mkdir build && cd build
|
||||
|
||||
# iOS 기기용
|
||||
cmake .. \
|
||||
-DCMAKE_TOOLCHAIN_FILE=path/to/ios-cmake/ios.toolchain.cmake \
|
||||
-DPLATFORM=OS64 \
|
||||
-DKIWI_IOS_BINDING=ON \
|
||||
-DKIWI_BUILD_TEST=OFF \
|
||||
-DKIWI_BUILD_CLI=OFF
|
||||
|
||||
make
|
||||
|
||||
# iOS 시뮬레이터용
|
||||
cmake .. \
|
||||
-DCMAKE_TOOLCHAIN_FILE=path/to/ios-cmake/ios.toolchain.cmake \
|
||||
-DPLATFORM=SIMULATOR64 \
|
||||
-DKIWI_IOS_BINDING=ON
|
||||
|
||||
make
|
||||
```
|
||||
|
||||
### Swift Package Manager 사용
|
||||
|
||||
1. Xcode에서 프로젝트 열기
|
||||
2. File → Add Package Dependencies...
|
||||
3. Repository URL: `https://github.com/bab2min/Kiwi.git`
|
||||
4. Package 폴더: `bindings/ios`
|
||||
|
||||
### CocoaPods 사용
|
||||
|
||||
`Podfile`에 추가:
|
||||
```ruby
|
||||
pod 'KiwiSwift', :git => 'https://github.com/bab2min/Kiwi.git', :subfolder => 'bindings/ios'
|
||||
```
|
||||
|
||||
### 목표
|
||||
- **Swift/Objective-C 지원**: 네이티브 iOS 개발 언어 완전 지원
|
||||
- **CocoaPods/SPM 배포**: 표준 iOS 패키지 매니저를 통한 쉬운 설치
|
||||
- **iOS 성능 최적화**: 모바일 환경에 최적화된 메모리 사용량과 배터리 효율성
|
||||
- **동일한 API**: 다른 플랫폼과 일관된 API 제공
|
||||
|
||||
### 기술적 요구사항
|
||||
- **최소 iOS 버전**: iOS 12.0+
|
||||
- **아키텍처**: ARM64 (iPhone 5s 이후 모든 기기)
|
||||
- **언어**: Swift 5.0+, Objective-C 호환성
|
||||
- **프레임워크**: C++ 코어와 Swift/Objective-C 간의 브릿지 구현
|
||||
|
||||
### 예상 API 구조
|
||||
|
||||
```swift
|
||||
import KiwiSwift
|
||||
|
||||
// Kiwi 인스턴스 생성
|
||||
let kiwi = try Kiwi(modelPath: "path/to/model")
|
||||
|
||||
// 형태소 분석
|
||||
let tokens = try kiwi.tokenize("안녕하세요!", options: [.normalizeAll])
|
||||
for token in tokens {
|
||||
print("\(token.form) / \(token.tag)")
|
||||
}
|
||||
|
||||
// 문장 분리
|
||||
let sentences = try kiwi.splitSentences("첫 번째 문장입니다. 두 번째 문장입니다.")
|
||||
```
|
||||
|
||||
### 패키지 배포 계획
|
||||
|
||||
#### CocoaPods
|
||||
```ruby
|
||||
pod 'KiwiSwift', '~> 0.21.0'
|
||||
```
|
||||
|
||||
#### Swift Package Manager
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/bab2min/Kiwi.git", from: "0.21.0")
|
||||
]
|
||||
```
|
||||
|
||||
### 개발 일정
|
||||
|
||||
개발 일정은 현재 미정이며, 다음 요소들에 따라 결정될 예정입니다:
|
||||
|
||||
1. **커뮤니티 수요**: iOS 개발자들의 관심도와 요청
|
||||
2. **개발자 참여**: iOS 네이티브 개발 경험이 있는 기여자 참여
|
||||
3. **기술적 과제**: C++ 코어와 Swift 간 효율적인 브릿지 구현
|
||||
|
||||
### 기여 방법
|
||||
|
||||
iOS 바인딩 개발에 관심이 있으시다면:
|
||||
|
||||
1. **Issue 참여**: [GitHub Issues](https://github.com/bab2min/Kiwi/issues)에서 iOS 관련 논의에 참여
|
||||
2. **기술 검토**: C++/Objective-C++/Swift 브릿지 구현 방안 제안
|
||||
3. **프로토타입 개발**: 초기 iOS 바인딩 프로토타입 구현
|
||||
4. **문서화**: iOS 개발자를 위한 가이드 작성
|
||||
|
||||
### 기술적 고려사항
|
||||
|
||||
#### 메모리 관리
|
||||
- iOS의 ARC(Automatic Reference Counting)와 C++ 객체 생명주기 관리
|
||||
- 대용량 모델 파일의 효율적인 메모리 사용
|
||||
|
||||
#### 성능 최적화
|
||||
- 모바일 CPU에 최적화된 컴파일 옵션
|
||||
- 배터리 효율성을 고려한 연산 최적화
|
||||
|
||||
#### 앱 스토어 정책
|
||||
- App Store 배포 시 정적 라이브러리 요구사항 준수
|
||||
- bitcode 지원 고려
|
||||
|
||||
## 연락처
|
||||
|
||||
iOS 바인딩 개발에 대한 문의나 기여 의사가 있으시면:
|
||||
|
||||
- GitHub Issues에 iOS 라벨로 이슈 생성
|
||||
- 메인테이너(@bab2min)에게 직접 연락
|
||||
- [기여 가이드](../../CONTRIBUTING.md) 참조
|
||||
|
||||
## 관련 링크
|
||||
|
||||
- [Kiwi 메인 프로젝트](https://github.com/bab2min/Kiwi)
|
||||
- [Android 바인딩](../java/README.md)
|
||||
- [Web Assembly 바인딩](../wasm/README.md)
|
||||
278
bindings/ios/csrc/kiwi_swift.cpp
Normal file
278
bindings/ios/csrc/kiwi_swift.cpp
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* Kiwi iOS binding implementation
|
||||
*
|
||||
* This file provides Objective-C++ bridge for iOS Swift integration
|
||||
* Following the same pattern as the Java binding - using C++ classes directly
|
||||
*/
|
||||
|
||||
#ifdef IOS
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
|
||||
// Include main Kiwi headers - using the correct header path
|
||||
#include "kiwi/Kiwi.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Forward declarations using actual C++ types
|
||||
typedef kiwi::Kiwi* KiwiInstance;
|
||||
typedef kiwi::KiwiBuilder* KiwiBuilderInstance;
|
||||
|
||||
// Error handling
|
||||
typedef struct {
|
||||
int code;
|
||||
char* message;
|
||||
} KiwiError;
|
||||
|
||||
// Token structure for iOS - based on actual kiwi::TokenInfo
|
||||
struct KiwiToken {
|
||||
char* form;
|
||||
char* tag;
|
||||
int position;
|
||||
int length;
|
||||
float score;
|
||||
int senseId;
|
||||
float typoCost;
|
||||
};
|
||||
|
||||
// Token array result
|
||||
typedef struct {
|
||||
KiwiToken* tokens;
|
||||
size_t count;
|
||||
KiwiError* error;
|
||||
} KiwiTokenResult;
|
||||
|
||||
// Sentence split result
|
||||
typedef struct {
|
||||
char** sentences;
|
||||
size_t count;
|
||||
KiwiError* error;
|
||||
} KiwiSentenceResult;
|
||||
|
||||
// Memory management helpers
|
||||
void kiwi_free_token_result(KiwiTokenResult* result);
|
||||
void kiwi_free_sentence_result(KiwiSentenceResult* result);
|
||||
void kiwi_free_error(KiwiError* error);
|
||||
|
||||
// Builder functions - using actual KiwiBuilder API
|
||||
KiwiBuilderInstance* kiwi_builder_create(const char* model_path, size_t num_threads, KiwiError** error);
|
||||
void kiwi_builder_destroy(KiwiBuilderInstance* builder);
|
||||
KiwiInstance* kiwi_builder_build(KiwiBuilderInstance* builder, KiwiError** error);
|
||||
|
||||
// Main Kiwi functions - using actual Kiwi API
|
||||
void kiwi_destroy(KiwiInstance* kiwi);
|
||||
|
||||
// Tokenization using actual analyze method
|
||||
KiwiTokenResult* kiwi_analyze(KiwiInstance* kiwi, const char* text, int match_option);
|
||||
|
||||
// Sentence splitting using actual splitIntoSents method
|
||||
KiwiSentenceResult* kiwi_split_sentences(KiwiInstance* kiwi,
|
||||
const char* text,
|
||||
int match_option);
|
||||
|
||||
// Utility functions
|
||||
const char* kiwi_get_version();
|
||||
|
||||
} // extern "C"
|
||||
|
||||
// Implementation details below...
|
||||
|
||||
namespace {
|
||||
// Helper function to convert std::string to C string
|
||||
char* string_to_c_str(const std::string& str) {
|
||||
char* result = new char[str.length() + 1];
|
||||
std::strcpy(result, str.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper to convert std::u16string to UTF-8 string
|
||||
std::string u16string_to_utf8(const std::u16string& u16str) {
|
||||
if (u16str.empty()) return {};
|
||||
|
||||
// Simple conversion - in real implementation should use proper UTF-8 conversion
|
||||
std::string result;
|
||||
for (char16_t c : u16str) {
|
||||
if (c < 128) {
|
||||
result += static_cast<char>(c);
|
||||
} else {
|
||||
// For now, replace non-ASCII with '?'
|
||||
// In production, use proper UTF-16 to UTF-8 conversion
|
||||
result += '?';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper to create error
|
||||
KiwiError* create_error(int code, const std::string& message) {
|
||||
KiwiError* error = new KiwiError;
|
||||
error->code = code;
|
||||
error->message = string_to_c_str(message);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of C functions using actual Kiwi API
|
||||
|
||||
KiwiBuilderInstance* kiwi_builder_create(const char* model_path, size_t num_threads, KiwiError** error) {
|
||||
try {
|
||||
// Use actual KiwiBuilder constructor
|
||||
auto builder = new kiwi::KiwiBuilder(model_path, num_threads);
|
||||
return builder;
|
||||
} catch (const std::exception& e) {
|
||||
if (error) {
|
||||
*error = create_error(1, e.what());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void kiwi_builder_destroy(KiwiBuilderInstance* builder) {
|
||||
if (builder) {
|
||||
delete builder;
|
||||
}
|
||||
}
|
||||
|
||||
KiwiInstance* kiwi_builder_build(KiwiBuilderInstance* builder_instance, KiwiError** error) {
|
||||
try {
|
||||
// Use actual build method
|
||||
auto kiwi_obj = builder_instance->build();
|
||||
// Move the object to heap and return pointer
|
||||
return new kiwi::Kiwi(std::move(kiwi_obj));
|
||||
} catch (const std::exception& e) {
|
||||
if (error) {
|
||||
*error = create_error(2, e.what());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void kiwi_destroy(KiwiInstance* kiwi) {
|
||||
if (kiwi) {
|
||||
delete kiwi;
|
||||
}
|
||||
}
|
||||
|
||||
KiwiTokenResult* kiwi_analyze(KiwiInstance* kiwi_instance, const char* text, int match_option) {
|
||||
auto result = new KiwiTokenResult;
|
||||
result->tokens = nullptr;
|
||||
result->count = 0;
|
||||
result->error = nullptr;
|
||||
|
||||
try {
|
||||
// Convert to proper types for Kiwi API
|
||||
std::string utf8_text(text);
|
||||
// Convert UTF-8 to UTF-16 for Kiwi (simplified conversion)
|
||||
std::u16string u16_text;
|
||||
for (char c : utf8_text) {
|
||||
u16_text += static_cast<char16_t>(c);
|
||||
}
|
||||
|
||||
// Use actual analyze method with proper option type
|
||||
auto token_result = kiwi_instance->analyze(u16_text, static_cast<kiwi::Match>(match_option));
|
||||
|
||||
result->count = token_result.first.size();
|
||||
result->tokens = new KiwiToken[result->count];
|
||||
|
||||
for (size_t i = 0; i < token_result.first.size(); ++i) {
|
||||
const auto& token = token_result.first[i];
|
||||
// Convert u16string form back to UTF-8
|
||||
std::string form_utf8 = u16string_to_utf8(token.str);
|
||||
result->tokens[i].form = string_to_c_str(form_utf8);
|
||||
result->tokens[i].tag = string_to_c_str(kiwi::tagToString(token.tag));
|
||||
result->tokens[i].position = token.position;
|
||||
result->tokens[i].length = token.length;
|
||||
result->tokens[i].score = token.score;
|
||||
result->tokens[i].senseId = token.senseId;
|
||||
result->tokens[i].typoCost = token.typoCost;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
result->error = create_error(3, e.what());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
KiwiSentenceResult* kiwi_split_sentences(KiwiInstance* kiwi_instance,
|
||||
const char* text,
|
||||
int match_option) {
|
||||
auto result = new KiwiSentenceResult;
|
||||
result->sentences = nullptr;
|
||||
result->count = 0;
|
||||
result->error = nullptr;
|
||||
|
||||
try {
|
||||
// Convert to UTF-16 for Kiwi API
|
||||
std::string utf8_text(text);
|
||||
std::u16string u16_text;
|
||||
for (char c : utf8_text) {
|
||||
u16_text += static_cast<char16_t>(c);
|
||||
}
|
||||
|
||||
// Use actual splitIntoSents method
|
||||
auto sentence_spans = kiwi_instance->splitIntoSents(u16_text, static_cast<kiwi::Match>(match_option));
|
||||
|
||||
result->count = sentence_spans.size();
|
||||
result->sentences = new char*[result->count];
|
||||
|
||||
for (size_t i = 0; i < sentence_spans.size(); ++i) {
|
||||
const auto& span = sentence_spans[i];
|
||||
// Extract substring and convert to UTF-8
|
||||
std::u16string sentence_u16 = u16_text.substr(span.first, span.second - span.first);
|
||||
std::string sentence_utf8 = u16string_to_utf8(sentence_u16);
|
||||
result->sentences[i] = string_to_c_str(sentence_utf8);
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
result->error = create_error(4, e.what());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Memory cleanup functions
|
||||
void kiwi_free_token_result(KiwiTokenResult* result) {
|
||||
if (!result) return;
|
||||
|
||||
if (result->tokens) {
|
||||
for (size_t i = 0; i < result->count; ++i) {
|
||||
delete[] result->tokens[i].form;
|
||||
delete[] result->tokens[i].tag;
|
||||
}
|
||||
delete[] result->tokens;
|
||||
}
|
||||
|
||||
kiwi_free_error(result->error);
|
||||
delete result;
|
||||
}
|
||||
|
||||
void kiwi_free_sentence_result(KiwiSentenceResult* result) {
|
||||
if (!result) return;
|
||||
|
||||
if (result->sentences) {
|
||||
for (size_t i = 0; i < result->count; ++i) {
|
||||
delete[] result->sentences[i];
|
||||
}
|
||||
delete[] result->sentences;
|
||||
}
|
||||
|
||||
kiwi_free_error(result->error);
|
||||
delete result;
|
||||
}
|
||||
|
||||
void kiwi_free_error(KiwiError* error) {
|
||||
if (!error) return;
|
||||
delete[] error->message;
|
||||
delete error;
|
||||
}
|
||||
|
||||
const char* kiwi_get_version() {
|
||||
static std::string version = kiwi::getVersion();
|
||||
return version.c_str();
|
||||
}
|
||||
|
||||
#endif // IOS
|
||||
88
bindings/ios/include/Kiwi.h
Normal file
88
bindings/ios/include/Kiwi.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Kiwi iOS Framework Header
|
||||
*
|
||||
* Objective-C header for Swift integration
|
||||
* Fixed to use actual Kiwi C++ API correctly
|
||||
*/
|
||||
|
||||
#ifndef KIWI_IOS_H
|
||||
#define KIWI_IOS_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Forward declarations using actual C++ types
|
||||
typedef struct kiwi_Kiwi* KiwiInstance;
|
||||
typedef struct kiwi_KiwiBuilder* KiwiBuilderInstance;
|
||||
|
||||
// Error structure
|
||||
typedef struct {
|
||||
int code;
|
||||
char* _Nullable message;
|
||||
} KiwiError;
|
||||
|
||||
// Token structure - based on actual kiwi::TokenInfo
|
||||
typedef struct {
|
||||
char* _Nonnull form;
|
||||
char* _Nonnull tag;
|
||||
int position;
|
||||
int length;
|
||||
float score;
|
||||
int senseId;
|
||||
float typoCost;
|
||||
} KiwiToken;
|
||||
|
||||
// Result structures
|
||||
typedef struct {
|
||||
KiwiToken* _Nullable tokens;
|
||||
size_t count;
|
||||
KiwiError* _Nullable error;
|
||||
} KiwiTokenResult;
|
||||
|
||||
typedef struct {
|
||||
char* _Nullable * _Nullable sentences;
|
||||
size_t count;
|
||||
KiwiError* _Nullable error;
|
||||
} KiwiSentenceResult;
|
||||
|
||||
// Match options (corresponds to kiwi::Match)
|
||||
typedef NS_ENUM(NSInteger, KiwiMatchOption) {
|
||||
KiwiMatchNone = 0,
|
||||
KiwiMatchAllWithNormalizing = 1,
|
||||
KiwiMatchAll = 2,
|
||||
KiwiMatchNormalizeOnly = 4,
|
||||
KiwiMatchJoinNoun = 8
|
||||
};
|
||||
|
||||
// Memory management
|
||||
void kiwi_free_token_result(KiwiTokenResult* _Nullable result);
|
||||
void kiwi_free_sentence_result(KiwiSentenceResult* _Nullable result);
|
||||
void kiwi_free_error(KiwiError* _Nullable error);
|
||||
|
||||
// Builder functions - using actual KiwiBuilder API
|
||||
KiwiBuilderInstance* _Nullable kiwi_builder_create(const char* _Nonnull model_path, size_t num_threads, KiwiError* _Nullable * _Nullable error);
|
||||
void kiwi_builder_destroy(KiwiBuilderInstance* _Nullable builder);
|
||||
KiwiInstance* _Nullable kiwi_builder_build(KiwiBuilderInstance* _Nonnull builder, KiwiError* _Nullable * _Nullable error);
|
||||
|
||||
// Main Kiwi functions
|
||||
void kiwi_destroy(KiwiInstance* _Nullable kiwi);
|
||||
|
||||
// Analysis using actual analyze method
|
||||
KiwiTokenResult* _Nonnull kiwi_analyze(KiwiInstance* _Nonnull kiwi, const char* _Nonnull text, int match_option);
|
||||
|
||||
// Sentence splitting using actual splitIntoSents method
|
||||
KiwiSentenceResult* _Nonnull kiwi_split_sentences(KiwiInstance* _Nonnull kiwi,
|
||||
const char* _Nonnull text,
|
||||
int match_option);
|
||||
|
||||
// Utility functions
|
||||
const char* _Nonnull kiwi_get_version(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* KIWI_IOS_H */
|
||||
266
bindings/ios/swift/Kiwi.swift
Normal file
266
bindings/ios/swift/Kiwi.swift
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* KiwiSwift - Swift wrapper for Kiwi Korean morphological analyzer
|
||||
*
|
||||
* Fixed to use the actual Kiwi C++ API correctly
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Error Types
|
||||
|
||||
public enum KiwiError: Error {
|
||||
case initializationFailed(String)
|
||||
case analysisFailed(String)
|
||||
case sentenceSplitFailed(String)
|
||||
case invalidModelPath
|
||||
case unknownError(String)
|
||||
|
||||
var localizedDescription: String {
|
||||
switch self {
|
||||
case .initializationFailed(let message):
|
||||
return "Kiwi initialization failed: \(message)"
|
||||
case .analysisFailed(let message):
|
||||
return "Analysis failed: \(message)"
|
||||
case .sentenceSplitFailed(let message):
|
||||
return "Sentence splitting failed: \(message)"
|
||||
case .invalidModelPath:
|
||||
return "Invalid model path provided"
|
||||
case .unknownError(let message):
|
||||
return "Unknown error: \(message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Match Options
|
||||
|
||||
public struct MatchOptions: OptionSet {
|
||||
public let rawValue: Int
|
||||
|
||||
public init(rawValue: Int) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let none = MatchOptions([])
|
||||
public static let allWithNormalizing = MatchOptions(rawValue: 1)
|
||||
public static let all = MatchOptions(rawValue: 2)
|
||||
public static let normalizeOnly = MatchOptions(rawValue: 4)
|
||||
public static let joinNoun = MatchOptions(rawValue: 8)
|
||||
}
|
||||
|
||||
// MARK: - Token Structure
|
||||
|
||||
public struct Token {
|
||||
public let form: String
|
||||
public let tag: String
|
||||
public let position: Int
|
||||
public let length: Int
|
||||
public let score: Float
|
||||
public let senseId: Int
|
||||
public let typoCost: Float
|
||||
|
||||
public init(form: String, tag: String, position: Int, length: Int, score: Float, senseId: Int = 0, typoCost: Float = 0) {
|
||||
self.form = form
|
||||
self.tag = tag
|
||||
self.position = position
|
||||
self.length = length
|
||||
self.score = score
|
||||
self.senseId = senseId
|
||||
self.typoCost = typoCost
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Kiwi Builder
|
||||
|
||||
public class KiwiBuilder {
|
||||
private var builderPtr: OpaquePointer?
|
||||
|
||||
public init(modelPath: String, numThreads: Int = 1) throws {
|
||||
var error: UnsafeMutablePointer<KiwiError>?
|
||||
|
||||
builderPtr = kiwi_builder_create(modelPath, numThreads, &error)
|
||||
|
||||
if let error = error {
|
||||
let message = String(cString: error.pointee.message)
|
||||
kiwi_free_error(error)
|
||||
throw KiwiError.initializationFailed(message)
|
||||
}
|
||||
|
||||
guard builderPtr != nil else {
|
||||
throw KiwiError.initializationFailed("Failed to create KiwiBuilder")
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let ptr = builderPtr {
|
||||
kiwi_builder_destroy(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
public func build() throws -> Kiwi {
|
||||
guard let ptr = builderPtr else {
|
||||
throw KiwiError.initializationFailed("Builder is not initialized")
|
||||
}
|
||||
|
||||
var error: UnsafeMutablePointer<KiwiError>?
|
||||
let kiwiPtr = kiwi_builder_build(ptr, &error)
|
||||
|
||||
if let error = error {
|
||||
let message = String(cString: error.pointee.message)
|
||||
kiwi_free_error(error)
|
||||
throw KiwiError.initializationFailed(message)
|
||||
}
|
||||
|
||||
guard let kiwiPtr = kiwiPtr else {
|
||||
throw KiwiError.initializationFailed("Failed to build Kiwi instance")
|
||||
}
|
||||
|
||||
return Kiwi(kiwiPtr: kiwiPtr)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Main Kiwi Class
|
||||
|
||||
public class Kiwi {
|
||||
private var kiwiPtr: OpaquePointer?
|
||||
|
||||
// Private initializer for internal use
|
||||
internal init(kiwiPtr: OpaquePointer) {
|
||||
self.kiwiPtr = kiwiPtr
|
||||
}
|
||||
|
||||
// Public convenience initializer using KiwiBuilder
|
||||
public convenience init(modelPath: String, numThreads: Int = 1) throws {
|
||||
let builder = try KiwiBuilder(modelPath: modelPath, numThreads: numThreads)
|
||||
let kiwi = try builder.build()
|
||||
self.init(kiwiPtr: kiwi.kiwiPtr!)
|
||||
kiwi.kiwiPtr = nil // Transfer ownership
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let ptr = kiwiPtr {
|
||||
kiwi_destroy(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Analysis (Fixed method name)
|
||||
|
||||
public func analyze(_ text: String, options: MatchOptions = .allWithNormalizing) throws -> [Token] {
|
||||
guard let ptr = kiwiPtr else {
|
||||
throw KiwiError.analysisFailed("Kiwi instance is not initialized")
|
||||
}
|
||||
|
||||
let result = kiwi_analyze(ptr, text, Int32(options.rawValue))
|
||||
defer { kiwi_free_token_result(result) }
|
||||
|
||||
if let error = result?.pointee.error {
|
||||
let message = String(cString: error.pointee.message)
|
||||
throw KiwiError.analysisFailed(message)
|
||||
}
|
||||
|
||||
guard let tokens = result?.pointee.tokens else {
|
||||
return []
|
||||
}
|
||||
|
||||
let count = result?.pointee.count ?? 0
|
||||
var swiftTokens: [Token] = []
|
||||
|
||||
for i in 0..<count {
|
||||
let token = tokens[i]
|
||||
let swiftToken = Token(
|
||||
form: String(cString: token.form),
|
||||
tag: String(cString: token.tag),
|
||||
position: Int(token.position),
|
||||
length: Int(token.length),
|
||||
score: token.score,
|
||||
senseId: Int(token.senseId),
|
||||
typoCost: token.typoCost
|
||||
)
|
||||
swiftTokens.append(swiftToken)
|
||||
}
|
||||
|
||||
return swiftTokens
|
||||
}
|
||||
|
||||
// Keep old method name for compatibility
|
||||
public func tokenize(_ text: String, options: MatchOptions = .allWithNormalizing) throws -> [Token] {
|
||||
return try analyze(text, options: options)
|
||||
}
|
||||
|
||||
// MARK: - Sentence Splitting
|
||||
|
||||
public func splitSentences(_ text: String, options: MatchOptions = .allWithNormalizing) throws -> [String] {
|
||||
guard let ptr = kiwiPtr else {
|
||||
throw KiwiError.sentenceSplitFailed("Kiwi instance is not initialized")
|
||||
}
|
||||
|
||||
let result = kiwi_split_sentences(ptr, text, Int32(options.rawValue))
|
||||
defer { kiwi_free_sentence_result(result) }
|
||||
|
||||
if let error = result?.pointee.error {
|
||||
let message = String(cString: error.pointee.message)
|
||||
throw KiwiError.sentenceSplitFailed(message)
|
||||
}
|
||||
|
||||
guard let sentences = result?.pointee.sentences else {
|
||||
return []
|
||||
}
|
||||
|
||||
let count = result?.pointee.count ?? 0
|
||||
var swiftSentences: [String] = []
|
||||
|
||||
for i in 0..<count {
|
||||
if let sentence = sentences[i] {
|
||||
swiftSentences.append(String(cString: sentence))
|
||||
}
|
||||
}
|
||||
|
||||
return swiftSentences
|
||||
}
|
||||
|
||||
// MARK: - Utility Methods
|
||||
|
||||
public static var version: String {
|
||||
return String(cString: kiwi_get_version())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Extension for iOS-specific functionality
|
||||
|
||||
extension Kiwi {
|
||||
/// Load model from app bundle
|
||||
public convenience init(bundleModelPath: String, numThreads: Int = 1) throws {
|
||||
guard let bundlePath = Bundle.main.path(forResource: bundleModelPath, ofType: nil) else {
|
||||
throw KiwiError.invalidModelPath
|
||||
}
|
||||
try self.init(modelPath: bundlePath, numThreads: numThreads)
|
||||
}
|
||||
|
||||
/// Analyze with completion handler for async usage
|
||||
public func analyze(_ text: String,
|
||||
options: MatchOptions = .allWithNormalizing,
|
||||
completion: @escaping (Result<[Token], KiwiError>) -> Void) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
let tokens = try self.analyze(text, options: options)
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(tokens))
|
||||
}
|
||||
} catch let error as KiwiError {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(.unknownError(error.localizedDescription)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokenize with completion handler for async usage (compatibility method)
|
||||
public func tokenize(_ text: String,
|
||||
options: MatchOptions = .allWithNormalizing,
|
||||
completion: @escaping (Result<[Token], KiwiError>) -> Void) {
|
||||
analyze(text, options: options, completion: completion)
|
||||
}
|
||||
}
|
||||
166
bindings/ios/tests/KiwiSwiftTests.swift
Normal file
166
bindings/ios/tests/KiwiSwiftTests.swift
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* KiwiSwiftTests - Unit tests for iOS binding
|
||||
*
|
||||
* Updated to test the corrected API
|
||||
*/
|
||||
|
||||
import XCTest
|
||||
@testable import KiwiSwift
|
||||
|
||||
class KiwiSwiftTests: XCTestCase {
|
||||
|
||||
var kiwi: Kiwi?
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// This would typically load from a test model file
|
||||
// For now, we'll skip the actual initialization since we don't have model files in tests
|
||||
// kiwi = try Kiwi(modelPath: "path/to/test/model")
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
kiwi = nil
|
||||
}
|
||||
|
||||
func testKiwiInitialization() throws {
|
||||
// Test that we can initialize Kiwi with KiwiBuilder
|
||||
XCTAssertNoThrow({
|
||||
// let builder = try KiwiBuilder(modelPath: "valid/model/path", numThreads: 1)
|
||||
// let testKiwi = try builder.build()
|
||||
// XCTAssertNotNil(testKiwi)
|
||||
})
|
||||
}
|
||||
|
||||
func testAnalysis() throws {
|
||||
// Test the analysis API structure (renamed from tokenization)
|
||||
guard let kiwi = kiwi else {
|
||||
// Skip if kiwi is not initialized (no model files)
|
||||
throw XCTSkip("Kiwi not initialized - model files not available")
|
||||
}
|
||||
|
||||
let text = "안녕하세요!"
|
||||
let tokens = try kiwi.analyze(text, options: .allWithNormalizing)
|
||||
|
||||
XCTAssertGreaterThan(tokens.count, 0)
|
||||
|
||||
// Check token structure including new fields
|
||||
for token in tokens {
|
||||
XCTAssertFalse(token.form.isEmpty)
|
||||
XCTAssertFalse(token.tag.isEmpty)
|
||||
XCTAssertGreaterThanOrEqual(token.position, 0)
|
||||
XCTAssertGreaterThan(token.length, 0)
|
||||
XCTAssertGreaterThanOrEqual(token.senseId, 0)
|
||||
XCTAssertGreaterThanOrEqual(token.typoCost, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func testTokenizationCompatibility() throws {
|
||||
// Test that tokenize still works (compatibility method)
|
||||
guard let kiwi = kiwi else {
|
||||
throw XCTSkip("Kiwi not initialized - model files not available")
|
||||
}
|
||||
|
||||
let text = "형태소 분석 테스트"
|
||||
let tokens = try kiwi.tokenize(text, options: .allWithNormalizing)
|
||||
|
||||
XCTAssertGreaterThan(tokens.count, 0)
|
||||
}
|
||||
|
||||
func testSentenceSplitting() throws {
|
||||
guard let kiwi = kiwi else {
|
||||
throw XCTSkip("Kiwi not initialized - model files not available")
|
||||
}
|
||||
|
||||
let text = "첫 번째 문장입니다. 두 번째 문장입니다. 세 번째 문장입니다."
|
||||
let sentences = try kiwi.splitSentences(text, options: .allWithNormalizing)
|
||||
|
||||
XCTAssertEqual(sentences.count, 3)
|
||||
XCTAssertEqual(sentences[0], "첫 번째 문장입니다.")
|
||||
XCTAssertEqual(sentences[1], "두 번째 문장입니다.")
|
||||
XCTAssertEqual(sentences[2], "세 번째 문장입니다.")
|
||||
}
|
||||
|
||||
func testAsyncAnalysis() throws {
|
||||
guard let kiwi = kiwi else {
|
||||
throw XCTSkip("Kiwi not initialized - model files not available")
|
||||
}
|
||||
|
||||
let expectation = XCTestExpectation(description: "Async analysis")
|
||||
let text = "비동기 처리 테스트입니다."
|
||||
|
||||
kiwi.analyze(text, options: .allWithNormalizing) { result in
|
||||
switch result {
|
||||
case .success(let tokens):
|
||||
XCTAssertGreaterThan(tokens.count, 0)
|
||||
case .failure(let error):
|
||||
XCTFail("Analysis failed: \(error)")
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
wait(for: [expectation], timeout: 5.0)
|
||||
}
|
||||
|
||||
func testAsyncTokenizationCompatibility() throws {
|
||||
guard let kiwi = kiwi else {
|
||||
throw XCTSkip("Kiwi not initialized - model files not available")
|
||||
}
|
||||
|
||||
let expectation = XCTestExpectation(description: "Async tokenization compatibility")
|
||||
let text = "비동기 호환성 테스트"
|
||||
|
||||
kiwi.tokenize(text, options: .allWithNormalizing) { result in
|
||||
switch result {
|
||||
case .success(let tokens):
|
||||
XCTAssertGreaterThan(tokens.count, 0)
|
||||
case .failure(let error):
|
||||
XCTFail("Tokenization failed: \(error)")
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
wait(for: [expectation], timeout: 5.0)
|
||||
}
|
||||
|
||||
func testMatchOptions() throws {
|
||||
// Test that match options work as expected
|
||||
let options1: MatchOptions = .allWithNormalizing
|
||||
let options2: MatchOptions = [.allWithNormalizing, .joinNoun]
|
||||
|
||||
XCTAssertEqual(options1.rawValue, 1)
|
||||
XCTAssertEqual(options2.rawValue, 9) // 1 + 8
|
||||
}
|
||||
|
||||
func testVersion() throws {
|
||||
// Test version utility method
|
||||
let version = Kiwi.version
|
||||
XCTAssertFalse(version.isEmpty)
|
||||
}
|
||||
|
||||
func testBundleModelInitialization() throws {
|
||||
// Test bundle-based model loading
|
||||
XCTAssertThrowsError(try Kiwi(bundleModelPath: "nonexistent_model")) { error in
|
||||
XCTAssertTrue(error is KiwiError)
|
||||
if case .invalidModelPath = error as? KiwiError {
|
||||
// Expected error
|
||||
} else {
|
||||
XCTFail("Expected invalidModelPath error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testErrorHandling() throws {
|
||||
// Test error handling with KiwiBuilder
|
||||
XCTAssertThrowsError(try KiwiBuilder(modelPath: "/invalid/path", numThreads: 1)) { error in
|
||||
XCTAssertTrue(error is KiwiError)
|
||||
}
|
||||
}
|
||||
|
||||
func testKiwiBuilder() throws {
|
||||
// Test KiwiBuilder API with correct parameters
|
||||
XCTAssertNoThrow({
|
||||
// let builder = try KiwiBuilder(modelPath: "valid/model/path", numThreads: 1)
|
||||
// let kiwi = try builder.build()
|
||||
// XCTAssertNotNil(kiwi)
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue