LMAssembly // Pack LMUserOverride inside LMInstantiator, etc.
This commit is contained in:
parent
c5899152e6
commit
e44843e603
|
@ -1,17 +1,17 @@
|
||||||
# LangModelAssembly
|
# LangModelAssembly
|
||||||
|
|
||||||
威注音輸入法的語言模組總成套裝。
|
威注音輸入法的語言模組總成套裝,以 LMAssembly 命名空間承載下述唯二對外物件:
|
||||||
|
|
||||||
- vChewingLM:總命名空間,也承載一些在套裝內共用的工具函式。
|
|
||||||
- LMConsolidator:自動格式整理模組。
|
- LMConsolidator:自動格式整理模組。
|
||||||
- LMInstantiator:語言模組副本化模組。另有其日期時間擴充模組可用(對 CIN 磁帶模式無效)。
|
- LMInstantiator:語言模組副本化模組,亦集成一些自身功能擴展。
|
||||||
|
|
||||||
|
LMAssembly 總命名空間也承載一些在套裝內共用的工具函式。
|
||||||
|
|
||||||
以下是子模組:
|
以下是子模組:
|
||||||
|
|
||||||
- lmCassette:專門用來處理 CIN 磁帶檔案的模組,命名為「遠野」引擎。
|
|
||||||
- LMAssociates:關聯詞語模組。
|
- LMAssociates:關聯詞語模組。
|
||||||
|
- lmCassette:專門用來處理 CIN 磁帶檔案的模組,命名為「遠野」引擎。
|
||||||
- LMCoreEX:可以直接讀取 TXT 格式的帶有權重資料的語彙檔案的模組。
|
- LMCoreEX:可以直接讀取 TXT 格式的帶有權重資料的語彙檔案的模組。
|
||||||
- LMCoreJSON:專門用來讀取原廠 JSON 檔案的模組。
|
|
||||||
- lmPlainBopomofo:專門用來讀取使用者自訂ㄅ半候選字順序覆蓋定義檔案(plist)的模組。
|
- lmPlainBopomofo:專門用來讀取使用者自訂ㄅ半候選字順序覆蓋定義檔案(plist)的模組。
|
||||||
- lmReplacements:專門用來讀取使用者語彙置換模式的辭典資料的模組。
|
- lmReplacements:專門用來讀取使用者語彙置換模式的辭典資料的模組。
|
||||||
- lmUserOverride:半衰記憶模組。
|
- lmUserOverride:半衰記憶模組。
|
||||||
|
|
|
@ -11,7 +11,8 @@ import Foundation
|
||||||
/// 工作原理:先用 InputToken.parse 分析原始字串,給出準確的 Token。
|
/// 工作原理:先用 InputToken.parse 分析原始字串,給出準確的 Token。
|
||||||
/// 然後再讓這個 Token 用 .translated() 自我表述出轉換結果。
|
/// 然後再讓這個 Token 用 .translated() 自我表述出轉換結果。
|
||||||
|
|
||||||
public enum InputToken {
|
extension LMAssembly {
|
||||||
|
enum InputToken {
|
||||||
case timeZone(shortened: Bool)
|
case timeZone(shortened: Bool)
|
||||||
case timeNow(shortened: Bool)
|
case timeNow(shortened: Bool)
|
||||||
case date(dayDelta: Int = 0, yearDelta: Int = 0, shortened: Bool = true, luna: Bool = false)
|
case date(dayDelta: Int = 0, yearDelta: Int = 0, shortened: Bool = true, luna: Bool = false)
|
||||||
|
@ -20,20 +21,21 @@ public enum InputToken {
|
||||||
case yearGanzhi(yearDelta: Int = 0)
|
case yearGanzhi(yearDelta: Int = 0)
|
||||||
case yearZodiac(yearDelta: Int = 0)
|
case yearZodiac(yearDelta: Int = 0)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - 正式對外投入使用的 API。
|
// MARK: - 正式對外投入使用的 API。
|
||||||
|
|
||||||
public extension String {
|
public extension String {
|
||||||
func parseAsInputToken(isCHS: Bool) -> [String] {
|
func parseAsInputToken(isCHS: Bool) -> [String] {
|
||||||
InputToken.parse(from: self).map { $0.translated(isCHS: isCHS) }.flatMap { $0 }.deduplicated
|
LMAssembly.InputToken.parse(from: self).map { $0.translated(isCHS: isCHS) }.flatMap { $0 }.deduplicated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Parser parsing raw token value to construct token.
|
// MARK: - Parser parsing raw token value to construct token.
|
||||||
|
|
||||||
public extension InputToken {
|
extension LMAssembly.InputToken {
|
||||||
static func parse(from rawToken: String) -> [InputToken] {
|
static func parse(from rawToken: String) -> [LMAssembly.InputToken] {
|
||||||
var result: [InputToken] = []
|
var result: [LMAssembly.InputToken] = []
|
||||||
guard rawToken.prefix(6) == "MACRO@" else { return result }
|
guard rawToken.prefix(6) == "MACRO@" else { return result }
|
||||||
var mapParams: [String: Int] = [:]
|
var mapParams: [String: Int] = [:]
|
||||||
let tokenComponents = rawToken.dropFirst(6).split(separator: "_").map { param in
|
let tokenComponents = rawToken.dropFirst(6).split(separator: "_").map { param in
|
||||||
|
@ -69,7 +71,7 @@ public extension InputToken {
|
||||||
|
|
||||||
// MARK: - Parser parsing token itself.
|
// MARK: - Parser parsing token itself.
|
||||||
|
|
||||||
public extension InputToken {
|
extension LMAssembly.InputToken {
|
||||||
func translated(isCHS: Bool) -> [String] {
|
func translated(isCHS: Bool) -> [String] {
|
||||||
let locale = Locale(identifier: isCHS ? "zh-Hans" : "zh-Hant-TW")
|
let locale = Locale(identifier: isCHS ? "zh-Hans" : "zh-Hant-TW")
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
import LineReader
|
import LineReader
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
public extension vChewingLM {
|
public extension LMAssembly {
|
||||||
enum LMConsolidator {
|
enum LMConsolidator {
|
||||||
public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"
|
public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
import SQLite3
|
import SQLite3
|
||||||
|
|
||||||
public extension vChewingLM {
|
public extension LMAssembly {
|
||||||
/// 語言模組副本化模組(LMInstantiator,下稱「LMI」)自身為符合天權星組字引擎內
|
/// 語言模組副本化模組(LMInstantiator,下稱「LMI」)自身為符合天權星組字引擎內
|
||||||
/// 的 LangModelProtocol 協定的模組、統籌且整理來自其它子模組的資料(包括使
|
/// 的 LangModelProtocol 協定的模組、統籌且整理來自其它子模組的資料(包括使
|
||||||
/// 用者語彙、繪文字模組、語彙濾除表、原廠語言模組等)。
|
/// 用者語彙、繪文字模組、語彙濾除表、原廠語言模組等)。
|
||||||
|
@ -56,8 +56,12 @@ public extension vChewingLM {
|
||||||
public var config = Config()
|
public var config = Config()
|
||||||
|
|
||||||
// 這句需要留著,不然無法被 package 外界存取。
|
// 這句需要留著,不然無法被 package 外界存取。
|
||||||
public init(isCHS: Bool = false) {
|
public init(
|
||||||
|
isCHS: Bool = false,
|
||||||
|
uomDataURL: URL? = nil
|
||||||
|
) {
|
||||||
self.isCHS = isCHS
|
self.isCHS = isCHS
|
||||||
|
lmUserOverride = .init(dataURL: uomDataURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setOptions(handler: (inout Config) -> Void) {
|
public func setOptions(handler: (inout Config) -> Void) {
|
||||||
|
@ -109,6 +113,9 @@ public extension vChewingLM {
|
||||||
var lmAssociates = LMAssociates()
|
var lmAssociates = LMAssociates()
|
||||||
var lmPlainBopomofo = LMPlainBopomofo()
|
var lmPlainBopomofo = LMPlainBopomofo()
|
||||||
|
|
||||||
|
// 半衰记忆模组
|
||||||
|
var lmUserOverride: LMUserOverride
|
||||||
|
|
||||||
// MARK: - 工具函式
|
// MARK: - 工具函式
|
||||||
|
|
||||||
public func resetFactoryJSONModels() {}
|
public func resetFactoryJSONModels() {}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
import Megrez
|
import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
public extension vChewingLM.LMInstantiator {
|
public extension LMAssembly.LMInstantiator {
|
||||||
/// 磁帶模式專用:當前磁帶所規定的花牌鍵。
|
/// 磁帶模式專用:當前磁帶所規定的花牌鍵。
|
||||||
var cassetteWildcardKey: String { Self.lmCassette.wildcardKey }
|
var cassetteWildcardKey: String { Self.lmCassette.wildcardKey }
|
||||||
/// 磁帶模式專用:當前磁帶規定的最大碼長。
|
/// 磁帶模式專用:當前磁帶規定的最大碼長。
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Megrez
|
||||||
|
|
||||||
// MARK: - 日期時間便捷輸入功能
|
// MARK: - 日期時間便捷輸入功能
|
||||||
|
|
||||||
extension vChewingLM.LMInstantiator {
|
extension LMAssembly.LMInstantiator {
|
||||||
func queryDateTimeUnigrams(with key: String = "") -> [Megrez.Unigram] {
|
func queryDateTimeUnigrams(with key: String = "") -> [Megrez.Unigram] {
|
||||||
guard let tokenTrigger = TokenTrigger(rawValue: key) else { return [] }
|
guard let tokenTrigger = TokenTrigger(rawValue: key) else { return [] }
|
||||||
var results = [Megrez.Unigram]()
|
var results = [Megrez.Unigram]()
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Megrez
|
import Megrez
|
||||||
|
|
||||||
public extension vChewingLM.LMInstantiator {
|
public extension LMAssembly.LMInstantiator {
|
||||||
func supplyNumPadUnigrams(key: String) -> [Megrez.Unigram] {
|
func supplyNumPadUnigrams(key: String) -> [Megrez.Unigram] {
|
||||||
guard let status = config.numPadFWHWStatus else { return [] }
|
guard let status = config.numPadFWHWStatus else { return [] }
|
||||||
let initials = "_NumPad_"
|
let initials = "_NumPad_"
|
||||||
|
|
|
@ -31,6 +31,7 @@ import SQLite3
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
extension LMAssembly.LMInstantiator {
|
||||||
enum CoreColumn: Int32 {
|
enum CoreColumn: Int32 {
|
||||||
case theDataCHS = 1 // 簡體中文
|
case theDataCHS = 1 // 簡體中文
|
||||||
case theDataCHT = 2 // 繁體中文
|
case theDataCHT = 2 // 繁體中文
|
||||||
|
@ -53,8 +54,9 @@ enum CoreColumn: Int32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension vChewingLM.LMInstantiator {
|
extension LMAssembly.LMInstantiator {
|
||||||
fileprivate static func querySQL(strStmt sqlQuery: String, coreColumn column: CoreColumn, handler: (String) -> Void) {
|
fileprivate static func querySQL(strStmt sqlQuery: String, coreColumn column: CoreColumn, handler: (String) -> Void) {
|
||||||
guard Self.ptrSQL != nil else { return }
|
guard Self.ptrSQL != nil else { return }
|
||||||
performStatementSansResult { ptrStatement in
|
performStatementSansResult { ptrStatement in
|
||||||
|
@ -134,7 +136,9 @@ extension vChewingLM.LMInstantiator {
|
||||||
/// - parameters:
|
/// - parameters:
|
||||||
/// - key: 讀音索引鍵。
|
/// - key: 讀音索引鍵。
|
||||||
/// - column: 資料欄位。
|
/// - column: 資料欄位。
|
||||||
func factoryUnigramsFor(key: String, column: CoreColumn) -> [Megrez.Unigram] {
|
func factoryUnigramsFor(
|
||||||
|
key: String, column: LMAssembly.LMInstantiator.CoreColumn
|
||||||
|
) -> [Megrez.Unigram] {
|
||||||
if key == "_punctuation_list" { return [] }
|
if key == "_punctuation_list" { return [] }
|
||||||
var grams: [Megrez.Unigram] = []
|
var grams: [Megrez.Unigram] = []
|
||||||
var gramsHW: [Megrez.Unigram] = []
|
var gramsHW: [Megrez.Unigram] = []
|
||||||
|
@ -210,7 +214,7 @@ extension vChewingLM.LMInstantiator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension vChewingLM.LMInstantiator {
|
private extension LMAssembly.LMInstantiator {
|
||||||
/// 內部函式,用以將注音讀音索引鍵進行加密。
|
/// 內部函式,用以將注音讀音索引鍵進行加密。
|
||||||
///
|
///
|
||||||
/// 使用這種加密字串作為索引鍵,可以增加對 json 資料庫的存取速度。
|
/// 使用這種加密字串作為索引鍵,可以增加對 json 資料庫的存取速度。
|
||||||
|
@ -258,7 +262,7 @@ private extension vChewingLM.LMInstantiator {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension vChewingLM.LMInstantiator {
|
public extension LMAssembly.LMInstantiator {
|
||||||
@discardableResult static func connectToTestSQLDB() -> Bool {
|
@discardableResult static func connectToTestSQLDB() -> Bool {
|
||||||
Self.connectSQLDB(dbPath: #":memory:"#) && sqlTestCoreLMData.runAsSQLExec(dbPointer: &ptrSQL)
|
Self.connectSQLDB(dbPath: #":memory:"#) && sqlTestCoreLMData.runAsSQLExec(dbPointer: &ptrSQL)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// No trademark license is granted to use the trade names, trademarks, service
|
||||||
|
// marks, or product names of Contributor, except as required to fulfill notice
|
||||||
|
// requirements defined in MIT License.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Megrez
|
||||||
|
|
||||||
|
public extension LMAssembly.LMInstantiator {
|
||||||
|
func performUOMObservation(
|
||||||
|
walkedBefore: [Megrez.Node],
|
||||||
|
walkedAfter: [Megrez.Node],
|
||||||
|
cursor: Int,
|
||||||
|
timestamp: Double,
|
||||||
|
saveCallback: (() -> Void)? = nil
|
||||||
|
) {
|
||||||
|
lmUserOverride.performObservation(
|
||||||
|
walkedBefore: walkedBefore,
|
||||||
|
walkedAfter: walkedAfter,
|
||||||
|
cursor: cursor,
|
||||||
|
timestamp: timestamp,
|
||||||
|
saveCallback: saveCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchUOMSuggestion(
|
||||||
|
currentWalk: [Megrez.Node],
|
||||||
|
cursor: Int,
|
||||||
|
timestamp: Double
|
||||||
|
) -> LMAssembly.OverrideSuggestion {
|
||||||
|
lmUserOverride.fetchSuggestion(
|
||||||
|
currentWalk: currentWalk,
|
||||||
|
cursor: cursor,
|
||||||
|
timestamp: timestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadUOMData(fromURL fileURL: URL? = nil) {
|
||||||
|
lmUserOverride.loadData(fromURL: fileURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveUOMData(toURL fileURL: URL? = nil) {
|
||||||
|
lmUserOverride.saveData(toURL: fileURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearUOMData(withURL fileURL: URL? = nil) {
|
||||||
|
lmUserOverride.clearData(withURL: fileURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bleachSpecifiedUOMSuggestions(targets: [String], saveCallback: (() -> Void)? = nil) {
|
||||||
|
lmUserOverride.bleachSpecifiedSuggestions(targets: targets, saveCallback: saveCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bleachUOMUnigrams(saveCallback: (() -> Void)? = nil) {
|
||||||
|
lmUserOverride.bleachUnigrams(saveCallback: saveCallback)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,8 +10,8 @@ import Megrez
|
||||||
import PinyinPhonaConverter
|
import PinyinPhonaConverter
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
public extension vChewingLM {
|
extension LMAssembly {
|
||||||
@frozen struct LMAssociates {
|
struct LMAssociates {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:]
|
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:]
|
||||||
var strData: String = ""
|
var strData: String = ""
|
||||||
|
|
|
@ -12,9 +12,9 @@ import LineReader
|
||||||
import Megrez
|
import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
public extension vChewingLM {
|
extension LMAssembly {
|
||||||
/// 磁帶模組,用來方便使用者自行擴充字根輸入法。
|
/// 磁帶模組,用來方便使用者自行擴充字根輸入法。
|
||||||
@frozen struct LMCassette {
|
struct LMCassette {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
public private(set) var nameShort: String = ""
|
public private(set) var nameShort: String = ""
|
||||||
public private(set) var nameENG: String = ""
|
public private(set) var nameENG: String = ""
|
||||||
|
@ -45,7 +45,7 @@ public extension vChewingLM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension vChewingLM.LMCassette {
|
extension LMAssembly.LMCassette {
|
||||||
/// 計算頻率時要用到的東西 - fscale
|
/// 計算頻率時要用到的東西 - fscale
|
||||||
private static let fscale = 2.7
|
private static let fscale = 2.7
|
||||||
/// 萬用花牌字符,哪怕花牌鍵仍不可用。
|
/// 萬用花牌字符,哪怕花牌鍵仍不可用。
|
||||||
|
@ -86,7 +86,7 @@ public extension vChewingLM.LMCassette {
|
||||||
if FileManager.default.fileExists(atPath: path) {
|
if FileManager.default.fileExists(atPath: path) {
|
||||||
do {
|
do {
|
||||||
guard let fileHandle = FileHandle(forReadingAtPath: path) else {
|
guard let fileHandle = FileHandle(forReadingAtPath: path) else {
|
||||||
throw vChewingLM.FileErrors.fileHandleError("")
|
throw LMAssembly.FileErrors.fileHandleError("")
|
||||||
}
|
}
|
||||||
let lineReader = try LineReader(file: fileHandle)
|
let lineReader = try LineReader(file: fileHandle)
|
||||||
var theMaxKeyLength = 1
|
var theMaxKeyLength = 1
|
||||||
|
|
|
@ -10,12 +10,12 @@ import Megrez
|
||||||
import PinyinPhonaConverter
|
import PinyinPhonaConverter
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
public extension vChewingLM {
|
extension LMAssembly {
|
||||||
/// 與之前的 LMCore 不同,LMCoreEX 不在辭典內記錄實體,而是記錄 range 範圍。
|
/// 與之前的 LMCore 不同,LMCoreEX 不在辭典內記錄實體,而是記錄 range 範圍。
|
||||||
/// 需要資料的時候,直接拿 range 去 strData 取資料。
|
/// 需要資料的時候,直接拿 range 去 strData 取資料。
|
||||||
/// 資料記錄原理與上游 C++ 的 ParselessLM 差不多,但用的是 Swift 原生手段。
|
/// 資料記錄原理與上游 C++ 的 ParselessLM 差不多,但用的是 Swift 原生手段。
|
||||||
/// 主要時間消耗仍在 For 迴圈,但這個算法可以顯著減少記憶體佔用。
|
/// 主要時間消耗仍在 For 迴圈,但這個算法可以顯著減少記憶體佔用。
|
||||||
@frozen struct LMCoreEX {
|
struct LMCoreEX {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
/// 資料庫辭典。索引內容為注音字串,資料內容則為字串首尾範圍、方便自 strData 取資料。
|
/// 資料庫辭典。索引內容為注音字串,資料內容則為字串首尾範圍、方便自 strData 取資料。
|
||||||
var rangeMap: [String: [Range<String.Index>]] = [:]
|
var rangeMap: [String: [Range<String.Index>]] = [:]
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
public extension vChewingLM {
|
public extension LMAssembly {
|
||||||
@frozen struct LMPlainBopomofo {
|
@frozen struct LMPlainBopomofo {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
var dataMap: [String: String] = [:]
|
var dataMap: [String: String] = [:]
|
||||||
|
@ -29,13 +29,8 @@ public extension vChewingLM {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
|
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
|
||||||
if let rawJSON = try? JSONSerialization.jsonObject(with: rawData) as? [String: String] {
|
let rawJSON = try JSONDecoder().decode([String: String].self, from: rawData)
|
||||||
dataMap = rawJSON
|
dataMap = rawJSON
|
||||||
} else {
|
|
||||||
filePath = oldPath
|
|
||||||
vCLog("↑ Exception happened when reading JSON file at: \(path).")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
filePath = oldPath
|
filePath = oldPath
|
||||||
vCLog("\(error)")
|
vCLog("\(error)")
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
|
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
public extension vChewingLM {
|
extension LMAssembly {
|
||||||
@frozen struct LMReplacements {
|
struct LMReplacements {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
var rangeMap: [String: Range<String.Index>] = [:]
|
var rangeMap: [String: Range<String.Index>] = [:]
|
||||||
var strData: String = ""
|
var strData: String = ""
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
|
||||||
// ====================
|
|
||||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
|
||||||
// ... with NTL restriction stating that:
|
|
||||||
// No trademark license is granted to use the trade names, trademarks, service
|
|
||||||
// marks, or product names of Contributor, except as required to fulfill notice
|
|
||||||
// requirements defined in MIT License.
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Shared
|
|
||||||
|
|
||||||
public extension vChewingLM {
|
|
||||||
@frozen struct LMRevLookup {
|
|
||||||
public private(set) var dataMap: [String: [String]] = [:]
|
|
||||||
public private(set) var filePath: String = ""
|
|
||||||
|
|
||||||
public init(data dictData: (dict: [String: [String]]?, path: String)) {
|
|
||||||
guard let theDict = dictData.dict else {
|
|
||||||
vCLog("↑ Exception happened when reading JSON file at: \(dictData.path).")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
filePath = dictData.path
|
|
||||||
dataMap = theDict
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(path: String) {
|
|
||||||
if path.isEmpty { return }
|
|
||||||
do {
|
|
||||||
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
|
|
||||||
if let rawJSON = try? JSONSerialization.jsonObject(with: rawData) as? [String: [String]] {
|
|
||||||
dataMap = rawJSON
|
|
||||||
} else {
|
|
||||||
vCLog("↑ Exception happened when reading JSON file at: \(path).")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
vCLog("↑ Exception happened when reading JSON file at: \(path).")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
filePath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
public func query(with kanji: String) -> [String]? {
|
|
||||||
guard let resultData = dataMap[kanji] else { return nil }
|
|
||||||
let resultArray = resultData.compactMap {
|
|
||||||
let result = restorePhonabetFromASCII($0)
|
|
||||||
return result.isEmpty ? nil : result
|
|
||||||
}
|
|
||||||
return resultArray.isEmpty ? nil : resultArray
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 內部函式,用以將被加密的注音讀音索引鍵進行解密。
|
|
||||||
///
|
|
||||||
/// 如果傳入的字串當中包含 ASCII 下畫線符號的話,則表明該字串並非注音讀音字串,會被忽略處理。
|
|
||||||
/// - parameters:
|
|
||||||
/// - incoming: 傳入的已加密注音讀音字串。
|
|
||||||
func restorePhonabetFromASCII(_ incoming: String) -> String {
|
|
||||||
var strOutput = incoming
|
|
||||||
if !strOutput.contains("_") {
|
|
||||||
for entry in Self.dicPhonabet4ASCII {
|
|
||||||
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Constants
|
|
||||||
|
|
||||||
static let dicPhonabet4ASCII: [String: String] = [
|
|
||||||
"b": "ㄅ", "p": "ㄆ", "m": "ㄇ", "f": "ㄈ", "d": "ㄉ", "t": "ㄊ", "n": "ㄋ", "l": "ㄌ", "g": "ㄍ", "k": "ㄎ", "h": "ㄏ",
|
|
||||||
"j": "ㄐ", "q": "ㄑ", "x": "ㄒ", "Z": "ㄓ", "C": "ㄔ", "S": "ㄕ", "r": "ㄖ", "z": "ㄗ", "c": "ㄘ", "s": "ㄙ", "i": "ㄧ",
|
|
||||||
"u": "ㄨ", "v": "ㄩ", "a": "ㄚ", "o": "ㄛ", "e": "ㄜ", "E": "ㄝ", "B": "ㄞ", "P": "ㄟ", "M": "ㄠ", "F": "ㄡ", "D": "ㄢ",
|
|
||||||
"T": "ㄣ", "N": "ㄤ", "L": "ㄥ", "R": "ㄦ", "2": "ˊ", "3": "ˇ", "4": "ˋ", "5": "˙",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,72 +11,40 @@ import Foundation
|
||||||
import Megrez
|
import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
public extension vChewingLM {
|
// MARK: - Public Types.
|
||||||
class LMUserOverride {
|
|
||||||
// MARK: - Main
|
|
||||||
|
|
||||||
|
public extension LMAssembly {
|
||||||
|
struct OverrideSuggestion {
|
||||||
|
public var candidates = [(String, Megrez.Unigram)]()
|
||||||
|
public var forceHighScoreOverride = false
|
||||||
|
public var isEmpty: Bool { candidates.isEmpty }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - LMUserOverride Class Definition.
|
||||||
|
|
||||||
|
extension LMAssembly {
|
||||||
|
class LMUserOverride {
|
||||||
var mutCapacity: Int
|
var mutCapacity: Int
|
||||||
var mutDecayExponent: Double
|
var mutDecayExponent: Double
|
||||||
var mutLRUList: [KeyObservationPair] = []
|
var mutLRUList: [KeyObservationPair] = []
|
||||||
var mutLRUMap: [String: KeyObservationPair] = [:]
|
var mutLRUMap: [String: KeyObservationPair] = [:]
|
||||||
let kDecayThreshold: Double = 1.0 / 1_048_576.0 // 衰減二十次之後差不多就失效了。
|
let kDecayThreshold: Double = 1.0 / 1_048_576.0 // 衰減二十次之後差不多就失效了。
|
||||||
var fileSaveLocationURL: URL
|
var fileSaveLocationURL: URL?
|
||||||
|
|
||||||
public static let kObservedOverrideHalfLife: Double = 3600.0 * 6 // 6 小時半衰一次,能持續不到六天的記憶。
|
public static let kObservedOverrideHalfLife: Double = 3600.0 * 6 // 6 小時半衰一次,能持續不到六天的記憶。
|
||||||
|
|
||||||
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL) {
|
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL? = nil) {
|
||||||
mutCapacity = max(capacity, 1) // Ensures that this integer value is always > 0.
|
mutCapacity = max(capacity, 1) // Ensures that this integer value is always > 0.
|
||||||
mutDecayExponent = log(0.5) / decayConstant
|
mutDecayExponent = log(0.5) / decayConstant
|
||||||
fileSaveLocationURL = dataURL
|
fileSaveLocationURL = dataURL
|
||||||
}
|
}
|
||||||
|
|
||||||
public func performObservation(
|
|
||||||
walkedBefore: [Megrez.Node], walkedAfter: [Megrez.Node],
|
|
||||||
cursor: Int, timestamp: Double, saveCallback: @escaping () -> Void
|
|
||||||
) {
|
|
||||||
// 參數合規性檢查。
|
|
||||||
guard !walkedAfter.isEmpty, !walkedBefore.isEmpty else { return }
|
|
||||||
guard walkedBefore.totalKeyCount == walkedAfter.totalKeyCount else { return }
|
|
||||||
// 先判斷用哪種覆寫方法。
|
|
||||||
var actualCursor = 0
|
|
||||||
guard let currentNode = walkedAfter.findNode(at: cursor, target: &actualCursor) else { return }
|
|
||||||
// 當前節點超過三個字的話,就不記憶了。在這種情形下,使用者可以考慮新增自訂語彙。
|
|
||||||
guard currentNode.spanLength <= 3 else { return }
|
|
||||||
// 前一個節點得從前一次爬軌結果當中來找。
|
|
||||||
guard actualCursor > 0 else { return } // 該情況應該不會出現。
|
|
||||||
let currentNodeIndex = actualCursor
|
|
||||||
actualCursor -= 1
|
|
||||||
var prevNodeIndex = 0
|
|
||||||
guard let prevNode = walkedBefore.findNode(at: actualCursor, target: &prevNodeIndex) else { return }
|
|
||||||
|
|
||||||
let forceHighScoreOverride: Bool = currentNode.spanLength > prevNode.spanLength
|
|
||||||
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
|
|
||||||
|
|
||||||
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
|
|
||||||
let key: String = vChewingLM.LMUserOverride.formObservationKey(
|
|
||||||
walkedNodes: walkedAfter, headIndex: targetNodeIndex
|
|
||||||
)
|
|
||||||
guard !key.isEmpty else { return }
|
|
||||||
doObservation(
|
|
||||||
key: key, candidate: currentNode.currentUnigram.value, timestamp: timestamp,
|
|
||||||
forceHighScoreOverride: forceHighScoreOverride, saveCallback: { saveCallback() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func fetchSuggestion(
|
|
||||||
currentWalk: [Megrez.Node], cursor: Int, timestamp: Double
|
|
||||||
) -> Suggestion {
|
|
||||||
var headIndex = 0
|
|
||||||
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
|
|
||||||
let key = vChewingLM.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
|
|
||||||
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.joinedKey())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Structures
|
// MARK: - Private Structures
|
||||||
|
|
||||||
extension vChewingLM.LMUserOverride {
|
extension LMAssembly.LMUserOverride {
|
||||||
enum OverrideUnit: CodingKey { case count, timestamp, forceHighScoreOverride }
|
enum OverrideUnit: CodingKey { case count, timestamp, forceHighScoreOverride }
|
||||||
enum ObservationUnit: CodingKey { case count, overrides }
|
enum ObservationUnit: CodingKey { case count, overrides }
|
||||||
enum KeyObservationPairUnit: CodingKey { case key, observation }
|
enum KeyObservationPairUnit: CodingKey { case key, observation }
|
||||||
|
@ -153,10 +121,52 @@ extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Hash and Dehash the entire UOM data, etc.
|
// MARK: - Internal Methods in LMAssembly.
|
||||||
|
|
||||||
public extension vChewingLM.LMUserOverride {
|
extension LMAssembly.LMUserOverride {
|
||||||
func bleachSpecifiedSuggestions(targets: [String], saveCallback: @escaping () -> Void) {
|
func performObservation(
|
||||||
|
walkedBefore: [Megrez.Node], walkedAfter: [Megrez.Node],
|
||||||
|
cursor: Int, timestamp: Double, saveCallback: (() -> Void)? = nil
|
||||||
|
) {
|
||||||
|
// 參數合規性檢查。
|
||||||
|
guard !walkedAfter.isEmpty, !walkedBefore.isEmpty else { return }
|
||||||
|
guard walkedBefore.totalKeyCount == walkedAfter.totalKeyCount else { return }
|
||||||
|
// 先判斷用哪種覆寫方法。
|
||||||
|
var actualCursor = 0
|
||||||
|
guard let currentNode = walkedAfter.findNode(at: cursor, target: &actualCursor) else { return }
|
||||||
|
// 當前節點超過三個字的話,就不記憶了。在這種情形下,使用者可以考慮新增自訂語彙。
|
||||||
|
guard currentNode.spanLength <= 3 else { return }
|
||||||
|
// 前一個節點得從前一次爬軌結果當中來找。
|
||||||
|
guard actualCursor > 0 else { return } // 該情況應該不會出現。
|
||||||
|
let currentNodeIndex = actualCursor
|
||||||
|
actualCursor -= 1
|
||||||
|
var prevNodeIndex = 0
|
||||||
|
guard let prevNode = walkedBefore.findNode(at: actualCursor, target: &prevNodeIndex) else { return }
|
||||||
|
|
||||||
|
let forceHighScoreOverride: Bool = currentNode.spanLength > prevNode.spanLength
|
||||||
|
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
|
||||||
|
|
||||||
|
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
|
||||||
|
let key: String = LMAssembly.LMUserOverride.formObservationKey(
|
||||||
|
walkedNodes: walkedAfter, headIndex: targetNodeIndex
|
||||||
|
)
|
||||||
|
guard !key.isEmpty else { return }
|
||||||
|
doObservation(
|
||||||
|
key: key, candidate: currentNode.currentUnigram.value, timestamp: timestamp,
|
||||||
|
forceHighScoreOverride: forceHighScoreOverride, saveCallback: saveCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchSuggestion(
|
||||||
|
currentWalk: [Megrez.Node], cursor: Int, timestamp: Double
|
||||||
|
) -> LMAssembly.OverrideSuggestion {
|
||||||
|
var headIndex = 0
|
||||||
|
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
|
||||||
|
let key = LMAssembly.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
|
||||||
|
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.joinedKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
func bleachSpecifiedSuggestions(targets: [String], saveCallback: (() -> Void)? = nil) {
|
||||||
if targets.isEmpty { return }
|
if targets.isEmpty { return }
|
||||||
for neta in mutLRUMap {
|
for neta in mutLRUMap {
|
||||||
for target in targets {
|
for target in targets {
|
||||||
|
@ -166,44 +176,50 @@ public extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetMRUList()
|
resetMRUList()
|
||||||
saveCallback()
|
saveCallback?() ?? saveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 自 LRU 辭典內移除所有的單元圖。
|
/// 自 LRU 辭典內移除所有的單元圖。
|
||||||
func bleachUnigrams(saveCallback: @escaping () -> Void) {
|
func bleachUnigrams(saveCallback: (() -> Void)? = nil) {
|
||||||
for key in mutLRUMap.keys {
|
for key in mutLRUMap.keys {
|
||||||
if !key.contains("(),()") { continue }
|
if !key.contains("(),()") { continue }
|
||||||
mutLRUMap.removeValue(forKey: key)
|
mutLRUMap.removeValue(forKey: key)
|
||||||
}
|
}
|
||||||
resetMRUList()
|
resetMRUList()
|
||||||
saveCallback()
|
saveCallback?() ?? saveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func resetMRUList() {
|
func resetMRUList() {
|
||||||
mutLRUList.removeAll()
|
mutLRUList.removeAll()
|
||||||
for neta in mutLRUMap.reversed() {
|
for neta in mutLRUMap.reversed() {
|
||||||
mutLRUList.append(neta.value)
|
mutLRUList.append(neta.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearData(withURL fileURL: URL) {
|
func clearData(withURL fileURL: URL? = nil) {
|
||||||
mutLRUMap = .init()
|
mutLRUMap = .init()
|
||||||
mutLRUList = .init()
|
mutLRUList = .init()
|
||||||
do {
|
do {
|
||||||
let nullData = "{}"
|
let nullData = "{}"
|
||||||
|
guard let fileURL = fileURL ?? fileSaveLocationURL else {
|
||||||
|
throw "given fileURL is invalid or nil."
|
||||||
|
}
|
||||||
try nullData.write(to: fileURL, atomically: false, encoding: .utf8)
|
try nullData.write(to: fileURL, atomically: false, encoding: .utf8)
|
||||||
} catch {
|
} catch {
|
||||||
vCLog("UOM Error: Unable to clear data. Details: \(error)")
|
vCLog("UOM Error: Unable to clear the data in the UOM file. Details: \(error)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveData(toURL fileURL: URL? = nil) {
|
func saveData(toURL fileURL: URL? = nil) {
|
||||||
|
guard let fileURL: URL = fileURL ?? fileSaveLocationURL else {
|
||||||
|
vCLog("UOM saveData() failed. At least the file Save URL is not set for the current UOM.")
|
||||||
|
return
|
||||||
|
}
|
||||||
// 此處不要使用 JSONSerialization,不然執行緒會炸掉。
|
// 此處不要使用 JSONSerialization,不然執行緒會炸掉。
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
do {
|
do {
|
||||||
guard let jsonData = try? encoder.encode(mutLRUMap) else { return }
|
guard let jsonData = try? encoder.encode(mutLRUMap) else { return }
|
||||||
let fileURL: URL = fileURL ?? fileSaveLocationURL
|
|
||||||
try jsonData.write(to: fileURL, options: .atomic)
|
try jsonData.write(to: fileURL, options: .atomic)
|
||||||
} catch {
|
} catch {
|
||||||
vCLog("UOM Error: Unable to save data, abort saving. Details: \(error)")
|
vCLog("UOM Error: Unable to save data, abort saving. Details: \(error)")
|
||||||
|
@ -211,7 +227,11 @@ public extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadData(fromURL fileURL: URL) {
|
func loadData(fromURL fileURL: URL? = nil) {
|
||||||
|
guard let fileURL: URL = fileURL ?? fileSaveLocationURL else {
|
||||||
|
vCLog("UOM loadData() failed. At least the file Load URL is not set for the current UOM.")
|
||||||
|
return
|
||||||
|
}
|
||||||
// 此處不要使用 JSONSerialization,不然執行緒會炸掉。
|
// 此處不要使用 JSONSerialization,不然執行緒會炸掉。
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
do {
|
do {
|
||||||
|
@ -228,20 +248,14 @@ public extension vChewingLM.LMUserOverride {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Suggestion {
|
|
||||||
public var candidates = [(String, Megrez.Unigram)]()
|
|
||||||
public var forceHighScoreOverride = false
|
|
||||||
public var isEmpty: Bool { candidates.isEmpty }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Methods
|
// MARK: - Other Non-Public Internal Methods
|
||||||
|
|
||||||
extension vChewingLM.LMUserOverride {
|
extension LMAssembly.LMUserOverride {
|
||||||
func doObservation(
|
func doObservation(
|
||||||
key: String, candidate: String, timestamp: Double, forceHighScoreOverride: Bool,
|
key: String, candidate: String, timestamp: Double, forceHighScoreOverride: Bool,
|
||||||
saveCallback: @escaping () -> Void
|
saveCallback: (() -> Void)?
|
||||||
) {
|
) {
|
||||||
guard mutLRUMap[key] != nil else {
|
guard mutLRUMap[key] != nil else {
|
||||||
var observation: Observation = .init()
|
var observation: Observation = .init()
|
||||||
|
@ -258,7 +272,7 @@ extension vChewingLM.LMUserOverride {
|
||||||
mutLRUList.removeLast()
|
mutLRUList.removeLast()
|
||||||
}
|
}
|
||||||
vCLog("UOM: Observation finished with new observation: \(key)")
|
vCLog("UOM: Observation finished with new observation: \(key)")
|
||||||
saveCallback()
|
saveCallback?() ?? saveData()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 這裡還是不要做 decayCallback 判定「是否不急著更新觀察」了,不然會在嘗試覆寫掉錯誤的記憶時失敗。
|
// 這裡還是不要做 decayCallback 判定「是否不急著更新觀察」了,不然會在嘗試覆寫掉錯誤的記憶時失敗。
|
||||||
|
@ -269,11 +283,11 @@ extension vChewingLM.LMUserOverride {
|
||||||
mutLRUList.insert(theNeta, at: 0)
|
mutLRUList.insert(theNeta, at: 0)
|
||||||
mutLRUMap[key] = theNeta
|
mutLRUMap[key] = theNeta
|
||||||
vCLog("UOM: Observation finished with existing observation: \(key)")
|
vCLog("UOM: Observation finished with existing observation: \(key)")
|
||||||
saveCallback()
|
saveCallback?() ?? saveData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSuggestion(key: String, timestamp: Double, headReading: String) -> Suggestion {
|
func getSuggestion(key: String, timestamp: Double, headReading: String) -> LMAssembly.OverrideSuggestion {
|
||||||
guard !key.isEmpty, let kvPair = mutLRUMap[key] else { return .init() }
|
guard !key.isEmpty, let kvPair = mutLRUMap[key] else { return .init() }
|
||||||
let observation: Observation = kvPair.observation
|
let observation: Observation = kvPair.observation
|
||||||
var candidates: [(String, Megrez.Unigram)] = .init()
|
var candidates: [(String, Megrez.Unigram)] = .init()
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
//
|
// 下述詞頻資料取自 libTaBE 資料庫 (http://sourceforge.net/projects/libtabe/)
|
||||||
// File.swift
|
// (2002 最終版). 該專案於 1999 年由 Pai-Hsiang Hsiao 發起、以 BSD 授權發行。
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by ShikiSuen on 2023/11/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
import Shared
|
import Shared
|
||||||
import SQLite3
|
import SQLite3
|
||||||
|
|
||||||
public enum vChewingLM {
|
public enum LMAssembly {
|
||||||
enum FileErrors: Error {
|
enum FileErrors: Error {
|
||||||
case fileHandleError(String)
|
case fileHandleError(String)
|
||||||
}
|
}
|
|
@ -57,8 +57,8 @@ final class InputTokenTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGeneratedResultsFromLMInstantiator() throws {
|
func testGeneratedResultsFromLMInstantiator() throws {
|
||||||
let instance = vChewingLM.LMInstantiator(isCHS: true)
|
let instance = LMAssembly.LMInstantiator(isCHS: true)
|
||||||
XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB())
|
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
|
||||||
instance.setOptions { config in
|
instance.setOptions { config in
|
||||||
config.isCNSEnabled = false
|
config.isCNSEnabled = false
|
||||||
config.isSymbolEnabled = false
|
config.isSymbolEnabled = false
|
||||||
|
@ -70,6 +70,6 @@ final class InputTokenTests: XCTestCase {
|
||||||
)
|
)
|
||||||
let x = instance.unigramsFor(keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"]).description
|
let x = instance.unigramsFor(keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"]).description
|
||||||
print(x)
|
print(x)
|
||||||
vChewingLM.LMInstantiator.disconnectSQLDB()
|
LMAssembly.LMInstantiator.disconnectSQLDB()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ private let testDataPath: String = packageRootPath + "/Tests/TestCINData/"
|
||||||
final class LMCassetteTests: XCTestCase {
|
final class LMCassetteTests: XCTestCase {
|
||||||
func testCassetteLoadWubi86() throws {
|
func testCassetteLoadWubi86() throws {
|
||||||
let pathCINFile = testDataPath + "wubi.cin"
|
let pathCINFile = testDataPath + "wubi.cin"
|
||||||
var lmCassette = vChewingLM.LMCassette()
|
var lmCassette = LMAssembly.LMCassette()
|
||||||
NSLog("LMCassette: Start loading CIN.")
|
NSLog("LMCassette: Start loading CIN.")
|
||||||
lmCassette.open(pathCINFile)
|
lmCassette.open(pathCINFile)
|
||||||
NSLog("LMCassette: Finished loading CIN. Entries: \(lmCassette.count)")
|
NSLog("LMCassette: Finished loading CIN. Entries: \(lmCassette.count)")
|
||||||
|
@ -41,7 +41,7 @@ final class LMCassetteTests: XCTestCase {
|
||||||
|
|
||||||
func testCassetteLoadArray30() throws {
|
func testCassetteLoadArray30() throws {
|
||||||
let pathCINFile = testDataPath + "array30.cin2"
|
let pathCINFile = testDataPath + "array30.cin2"
|
||||||
var lmCassette = vChewingLM.LMCassette()
|
var lmCassette = LMAssembly.LMCassette()
|
||||||
NSLog("LMCassette: Start loading CIN.")
|
NSLog("LMCassette: Start loading CIN.")
|
||||||
lmCassette.open(pathCINFile)
|
lmCassette.open(pathCINFile)
|
||||||
NSLog("LMCassette: Finished loading CIN. Entries: \(lmCassette.count)")
|
NSLog("LMCassette: Finished loading CIN. Entries: \(lmCassette.count)")
|
||||||
|
|
|
@ -38,7 +38,7 @@ private let sampleData: String = #"""
|
||||||
|
|
||||||
final class LMCoreEXTests: XCTestCase {
|
final class LMCoreEXTests: XCTestCase {
|
||||||
func testLMCoreEXAsFactoryCoreDict() throws {
|
func testLMCoreEXAsFactoryCoreDict() throws {
|
||||||
var lmTest = vChewingLM.LMCoreEX(
|
var lmTest = LMAssembly.LMCoreEX(
|
||||||
reverse: false, consolidate: false, defaultScore: 0, forceDefaultScore: false
|
reverse: false, consolidate: false, defaultScore: 0, forceDefaultScore: false
|
||||||
)
|
)
|
||||||
lmTest.replaceData(textData: sampleData)
|
lmTest.replaceData(textData: sampleData)
|
||||||
|
|
|
@ -22,8 +22,8 @@ private let expectedReverseLookupResults: [String] = [
|
||||||
|
|
||||||
final class LMInstantiatorSQLTests: XCTestCase {
|
final class LMInstantiatorSQLTests: XCTestCase {
|
||||||
func testSQL() throws {
|
func testSQL() throws {
|
||||||
let instance = vChewingLM.LMInstantiator(isCHS: true)
|
let instance = LMAssembly.LMInstantiator(isCHS: true)
|
||||||
XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB())
|
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
|
||||||
instance.setOptions { config in
|
instance.setOptions { config in
|
||||||
config.isCNSEnabled = false
|
config.isCNSEnabled = false
|
||||||
config.isSymbolEnabled = false
|
config.isSymbolEnabled = false
|
||||||
|
@ -41,13 +41,13 @@ final class LMInstantiatorSQLTests: XCTestCase {
|
||||||
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).count, 10)
|
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).count, 10)
|
||||||
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).last?.description, "(☉☉,-13.0)")
|
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).last?.description, "(☉☉,-13.0)")
|
||||||
// 再測試反查。
|
// 再測試反查。
|
||||||
XCTAssertEqual(vChewingLM.LMInstantiator.getFactoryReverseLookupData(with: "和"), expectedReverseLookupResults)
|
XCTAssertEqual(LMAssembly.LMInstantiator.getFactoryReverseLookupData(with: "和"), expectedReverseLookupResults)
|
||||||
vChewingLM.LMInstantiator.disconnectSQLDB()
|
LMAssembly.LMInstantiator.disconnectSQLDB()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCNSMask() throws {
|
func testCNSMask() throws {
|
||||||
let instance = vChewingLM.LMInstantiator(isCHS: false)
|
let instance = LMAssembly.LMInstantiator(isCHS: false)
|
||||||
XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB())
|
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
|
||||||
instance.setOptions { config in
|
instance.setOptions { config in
|
||||||
config.isCNSEnabled = false
|
config.isCNSEnabled = false
|
||||||
config.isSymbolEnabled = false
|
config.isSymbolEnabled = false
|
||||||
|
|
|
@ -17,12 +17,12 @@ private let halfLife: Double = 5400
|
||||||
private let nullURL = URL(fileURLWithPath: "/dev/null")
|
private let nullURL = URL(fileURLWithPath: "/dev/null")
|
||||||
|
|
||||||
final class LMUserOverrideTests: XCTestCase {
|
final class LMUserOverrideTests: XCTestCase {
|
||||||
private func observe(who uom: vChewingLM.LMUserOverride, key: String, candidate: String, timestamp stamp: Double) {
|
private func observe(who uom: LMAssembly.LMUserOverride, key: String, candidate: String, timestamp stamp: Double) {
|
||||||
uom.doObservation(key: key, candidate: candidate, timestamp: stamp, forceHighScoreOverride: false, saveCallback: {})
|
uom.doObservation(key: key, candidate: candidate, timestamp: stamp, forceHighScoreOverride: false, saveCallback: {})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUOM_1_BasicOps() throws {
|
func testUOM_1_BasicOps() throws {
|
||||||
let uom = vChewingLM.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
|
let uom = LMAssembly.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
|
||||||
let key = "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)"
|
let key = "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)"
|
||||||
let headReading = "ㄍㄡˇ"
|
let headReading = "ㄍㄡˇ"
|
||||||
let expectedSuggestion = "狗"
|
let expectedSuggestion = "狗"
|
||||||
|
@ -45,7 +45,7 @@ final class LMUserOverrideTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUOM_2_NewestAgainstRepeatedlyUsed() throws {
|
func testUOM_2_NewestAgainstRepeatedlyUsed() throws {
|
||||||
let uom = vChewingLM.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
|
let uom = LMAssembly.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
|
||||||
let key = "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)"
|
let key = "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)"
|
||||||
let headReading = "ㄍㄡˇ"
|
let headReading = "ㄍㄡˇ"
|
||||||
let valRepeatedlyUsed = "狗" // 更常用
|
let valRepeatedlyUsed = "狗" // 更常用
|
||||||
|
@ -74,7 +74,7 @@ final class LMUserOverrideTests: XCTestCase {
|
||||||
let b = (key: "((ㄆㄞˋ-ㄇㄥˊ,派蒙),(ㄉㄜ˙,的),ㄐㄧㄤˇ-ㄐㄧㄣ)", value: "伙食費", head: "ㄏㄨㄛˇ-ㄕˊ-ㄈㄟˋ")
|
let b = (key: "((ㄆㄞˋ-ㄇㄥˊ,派蒙),(ㄉㄜ˙,的),ㄐㄧㄤˇ-ㄐㄧㄣ)", value: "伙食費", head: "ㄏㄨㄛˇ-ㄕˊ-ㄈㄟˋ")
|
||||||
let c = (key: "((ㄍㄨㄛˊ-ㄅㄥ,國崩),(ㄉㄜ˙,的),ㄇㄠˋ-ㄗ˙)", value: "帽子", head: "ㄇㄠˋ-ㄗ˙")
|
let c = (key: "((ㄍㄨㄛˊ-ㄅㄥ,國崩),(ㄉㄜ˙,的),ㄇㄠˋ-ㄗ˙)", value: "帽子", head: "ㄇㄠˋ-ㄗ˙")
|
||||||
let d = (key: "((ㄌㄟˊ-ㄉㄧㄢˋ-ㄐㄧㄤ-ㄐㄩㄣ,雷電將軍),(ㄉㄜ˙,的),ㄐㄧㄠˇ-ㄔㄡˋ)", value: "腳臭", head: "ㄐㄧㄠˇ-ㄔㄡˋ")
|
let d = (key: "((ㄌㄟˊ-ㄉㄧㄢˋ-ㄐㄧㄤ-ㄐㄩㄣ,雷電將軍),(ㄉㄜ˙,的),ㄐㄧㄠˇ-ㄔㄡˋ)", value: "腳臭", head: "ㄐㄧㄠˇ-ㄔㄡˋ")
|
||||||
let uom = vChewingLM.LMUserOverride(capacity: 2, decayConstant: Double(halfLife), dataURL: nullURL)
|
let uom = LMAssembly.LMUserOverride(capacity: 2, decayConstant: Double(halfLife), dataURL: nullURL)
|
||||||
observe(who: uom, key: a.key, candidate: a.value, timestamp: nowTimeStamp)
|
observe(who: uom, key: a.key, candidate: a.value, timestamp: nowTimeStamp)
|
||||||
observe(who: uom, key: b.key, candidate: b.value, timestamp: nowTimeStamp + halfLife * 1)
|
observe(who: uom, key: b.key, candidate: b.value, timestamp: nowTimeStamp + halfLife * 1)
|
||||||
observe(who: uom, key: c.key, candidate: c.value, timestamp: nowTimeStamp + halfLife * 2)
|
observe(who: uom, key: c.key, candidate: c.value, timestamp: nowTimeStamp + halfLife * 2)
|
||||||
|
|
|
@ -13,7 +13,7 @@ import XCTest
|
||||||
|
|
||||||
final class LMInstantiatorNumericPadTests: XCTestCase {
|
final class LMInstantiatorNumericPadTests: XCTestCase {
|
||||||
func testSQL() throws {
|
func testSQL() throws {
|
||||||
let instance = vChewingLM.LMInstantiator(isCHS: true)
|
let instance = LMAssembly.LMInstantiator(isCHS: true)
|
||||||
instance.setOptions { config in
|
instance.setOptions { config in
|
||||||
config.numPadFWHWStatus = nil
|
config.numPadFWHWStatus = nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,7 @@ import Tekkon
|
||||||
// MARK: - InputHandler 自身協定 (Protocol).
|
// MARK: - InputHandler 自身協定 (Protocol).
|
||||||
|
|
||||||
public protocol InputHandlerProtocol {
|
public protocol InputHandlerProtocol {
|
||||||
var currentLM: vChewingLM.LMInstantiator { get set }
|
var currentLM: LMAssembly.LMInstantiator { get set }
|
||||||
var currentUOM: vChewingLM.LMUserOverride { get set }
|
|
||||||
var delegate: InputHandlerDelegate? { get set }
|
var delegate: InputHandlerDelegate? { get set }
|
||||||
var keySeparator: String { get }
|
var keySeparator: String { get }
|
||||||
static var keySeparator: String { get }
|
static var keySeparator: String { get }
|
||||||
|
@ -99,8 +98,7 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
var composer: Tekkon.Composer = .init() // 注拼槽
|
var composer: Tekkon.Composer = .init() // 注拼槽
|
||||||
var compositor: Megrez.Compositor // 組字器
|
var compositor: Megrez.Compositor // 組字器
|
||||||
|
|
||||||
public var currentUOM: vChewingLM.LMUserOverride
|
public var currentLM: LMAssembly.LMInstantiator {
|
||||||
public var currentLM: vChewingLM.LMInstantiator {
|
|
||||||
didSet {
|
didSet {
|
||||||
compositor.langModel = .init(withLM: currentLM)
|
compositor.langModel = .init(withLM: currentLM)
|
||||||
clear()
|
clear()
|
||||||
|
@ -108,10 +106,9 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 初期化。
|
/// 初期化。
|
||||||
public init(lm: vChewingLM.LMInstantiator, uom: vChewingLM.LMUserOverride, pref: PrefMgrProtocol) {
|
public init(lm: LMAssembly.LMInstantiator, pref: PrefMgrProtocol) {
|
||||||
prefs = pref
|
prefs = pref
|
||||||
currentLM = lm
|
currentLM = lm
|
||||||
currentUOM = uom
|
|
||||||
/// 同步組字器單個詞的幅位長度上限。
|
/// 同步組字器單個詞的幅位長度上限。
|
||||||
Megrez.Compositor.maxSpanLength = prefs.maxCandidateLength
|
Megrez.Compositor.maxSpanLength = prefs.maxCandidateLength
|
||||||
/// 組字器初期化。因為是首次初期化變數,所以這裡不能用 ensureCompositor() 代勞。
|
/// 組字器初期化。因為是首次初期化變數,所以這裡不能用 ensureCompositor() 代勞。
|
||||||
|
@ -369,8 +366,8 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
let currentNode = currentWalk.findNode(at: actualNodeCursorPosition, target: &accumulatedCursor)
|
let currentNode = currentWalk.findNode(at: actualNodeCursorPosition, target: &accumulatedCursor)
|
||||||
guard let currentNode = currentNode else { return }
|
guard let currentNode = currentNode else { return }
|
||||||
|
|
||||||
uom: if currentNode.currentUnigram.score > -12, prefs.fetchSuggestionsFromUserOverrideModel {
|
uomProcessing: if currentNode.currentUnigram.score > -12, prefs.fetchSuggestionsFromUserOverrideModel {
|
||||||
if skipObservation { break uom }
|
if skipObservation { break uomProcessing }
|
||||||
vCLog("UOM: Start Observation.")
|
vCLog("UOM: Start Observation.")
|
||||||
// 這個過程可能會因為使用者半衰記憶模組內部資料錯亂、而導致輸入法在選字時崩潰。
|
// 這個過程可能會因為使用者半衰記憶模組內部資料錯亂、而導致輸入法在選字時崩潰。
|
||||||
// 於是在這裡引入災後狀況察覺專用變數,且先開啟該開關。順利執行完觀察後會關閉。
|
// 於是在這裡引入災後狀況察覺專用變數,且先開啟該開關。順利執行完觀察後會關閉。
|
||||||
|
@ -378,9 +375,9 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
prefs.failureFlagForUOMObservation = true
|
prefs.failureFlagForUOMObservation = true
|
||||||
// 令半衰記憶模組觀測給定的三元圖。
|
// 令半衰記憶模組觀測給定的三元圖。
|
||||||
// 這個過程會讓半衰引擎根據當前上下文生成三元圖索引鍵。
|
// 這個過程會讓半衰引擎根據當前上下文生成三元圖索引鍵。
|
||||||
currentUOM.performObservation(
|
currentLM.performUOMObservation(
|
||||||
walkedBefore: previousWalk, walkedAfter: currentWalk, cursor: actualNodeCursorPosition,
|
walkedBefore: previousWalk, walkedAfter: currentWalk, cursor: actualNodeCursorPosition,
|
||||||
timestamp: Date().timeIntervalSince1970, saveCallback: { self.currentUOM.saveData() }
|
timestamp: Date().timeIntervalSince1970
|
||||||
)
|
)
|
||||||
// 如果沒有出現崩框的話,那就將這個開關復位。
|
// 如果沒有出現崩框的話,那就將這個開關復位。
|
||||||
prefs.failureFlagForUOMObservation = false
|
prefs.failureFlagForUOMObservation = false
|
||||||
|
@ -432,7 +429,7 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
/// 如果這個開關沒打開的話,直接放棄執行這個函式。
|
/// 如果這個開關沒打開的話,直接放棄執行這個函式。
|
||||||
if !prefs.fetchSuggestionsFromUserOverrideModel { return arrResult }
|
if !prefs.fetchSuggestionsFromUserOverrideModel { return arrResult }
|
||||||
/// 獲取來自半衰記憶模組的建議結果
|
/// 獲取來自半衰記憶模組的建議結果
|
||||||
let suggestion = currentUOM.fetchSuggestion(
|
let suggestion = currentLM.fetchUOMSuggestion(
|
||||||
currentWalk: compositor.walkedNodes, cursor: actualNodeCursorPosition, timestamp: Date().timeIntervalSince1970
|
currentWalk: compositor.walkedNodes, cursor: actualNodeCursorPosition, timestamp: Date().timeIntervalSince1970
|
||||||
)
|
)
|
||||||
arrResult.append(contentsOf: suggestion.candidates)
|
arrResult.append(contentsOf: suggestion.candidates)
|
||||||
|
|
|
@ -15,26 +15,20 @@ import SwiftExtension
|
||||||
// MARK: - Input Mode Extension for Language Models
|
// MARK: - Input Mode Extension for Language Models
|
||||||
|
|
||||||
public extension Shared.InputMode {
|
public extension Shared.InputMode {
|
||||||
private static let lmCHS = vChewingLM.LMInstantiator(isCHS: true)
|
private static let lmCHS = LMAssembly.LMInstantiator(
|
||||||
private static let lmCHT = vChewingLM.LMInstantiator(isCHS: false)
|
isCHS: true, uomDataURL: LMMgr.userOverrideModelDataURL(.imeModeCHS)
|
||||||
private static let uomCHS = vChewingLM.LMUserOverride(dataURL: LMMgr.userOverrideModelDataURL(.imeModeCHS))
|
)
|
||||||
private static let uomCHT = vChewingLM.LMUserOverride(dataURL: LMMgr.userOverrideModelDataURL(.imeModeCHT))
|
private static let lmCHT = LMAssembly.LMInstantiator(
|
||||||
|
isCHS: false, uomDataURL: LMMgr.userOverrideModelDataURL(.imeModeCHT)
|
||||||
|
)
|
||||||
|
|
||||||
var langModel: vChewingLM.LMInstantiator {
|
var langModel: LMAssembly.LMInstantiator {
|
||||||
switch self {
|
switch self {
|
||||||
case .imeModeCHS: return Self.lmCHS
|
case .imeModeCHS: return Self.lmCHS
|
||||||
case .imeModeCHT: return Self.lmCHT
|
case .imeModeCHT: return Self.lmCHT
|
||||||
case .imeModeNULL: return .init()
|
case .imeModeNULL: return .init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var uom: vChewingLM.LMUserOverride {
|
|
||||||
switch self {
|
|
||||||
case .imeModeCHS: return Self.uomCHS
|
|
||||||
case .imeModeCHT: return Self.uomCHT
|
|
||||||
case .imeModeNULL: return .init(dataURL: LMMgr.userOverrideModelDataURL(IMEApp.currentInputMode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Language Model Manager.
|
// MARK: - Language Model Manager.
|
||||||
|
@ -54,14 +48,14 @@ public class LMMgr {
|
||||||
Self.loadUserPhrasesData()
|
Self.loadUserPhrasesData()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var isCoreDBConnected: Bool { vChewingLM.LMInstantiator.isSQLDBConnected }
|
public static var isCoreDBConnected: Bool { LMAssembly.LMInstantiator.isSQLDBConnected }
|
||||||
|
|
||||||
public static func connectCoreDB(dbPath: String? = nil) {
|
public static func connectCoreDB(dbPath: String? = nil) {
|
||||||
guard let path: String = dbPath ?? Self.getCoreDictionaryDBPath() else {
|
guard let path: String = dbPath ?? Self.getCoreDictionaryDBPath() else {
|
||||||
assertionFailure("vChewing factory SQLite data not found.")
|
assertionFailure("vChewing factory SQLite data not found.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let result = vChewingLM.LMInstantiator.connectSQLDB(dbPath: path)
|
let result = LMAssembly.LMInstantiator.connectSQLDB(dbPath: path)
|
||||||
assert(result, "vChewing factory SQLite connection failed.")
|
assert(result, "vChewing factory SQLite connection failed.")
|
||||||
Notifier.notify(
|
Notifier.notify(
|
||||||
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
||||||
|
@ -71,10 +65,10 @@ public class LMMgr {
|
||||||
/// 載入磁帶資料。
|
/// 載入磁帶資料。
|
||||||
/// - Remark: cassettePath() 會在輸入法停用磁帶時直接返回
|
/// - Remark: cassettePath() 會在輸入法停用磁帶時直接返回
|
||||||
public static func loadCassetteData() {
|
public static func loadCassetteData() {
|
||||||
vChewingLM.LMInstantiator.loadCassetteData(path: cassettePath())
|
LMAssembly.LMInstantiator.loadCassetteData(path: cassettePath())
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func loadUserPhrasesData(type: vChewingLM.ReplacableUserDataType? = nil) {
|
public static func loadUserPhrasesData(type: LMAssembly.ReplacableUserDataType? = nil) {
|
||||||
guard let type = type else {
|
guard let type = type else {
|
||||||
Shared.InputMode.validCases.forEach { mode in
|
Shared.InputMode.validCases.forEach { mode in
|
||||||
mode.langModel.loadUserPhrasesData(
|
mode.langModel.loadUserPhrasesData(
|
||||||
|
@ -82,7 +76,7 @@ public class LMMgr {
|
||||||
filterPath: userDictDataURL(mode: mode, type: .theFilter).path
|
filterPath: userDictDataURL(mode: mode, type: .theFilter).path
|
||||||
)
|
)
|
||||||
mode.langModel.loadUserSymbolData(path: userDictDataURL(mode: mode, type: .theSymbols).path)
|
mode.langModel.loadUserSymbolData(path: userDictDataURL(mode: mode, type: .theSymbols).path)
|
||||||
mode.uom.loadData(fromURL: userOverrideModelDataURL(mode))
|
mode.langModel.loadUOMData()
|
||||||
}
|
}
|
||||||
|
|
||||||
if PrefMgr.shared.associatedPhrasesEnabled { Self.loadUserAssociatesData() }
|
if PrefMgr.shared.associatedPhrasesEnabled { Self.loadUserAssociatesData() }
|
||||||
|
@ -187,12 +181,12 @@ public class LMMgr {
|
||||||
// MARK: UOM
|
// MARK: UOM
|
||||||
|
|
||||||
public static func saveUserOverrideModelData() {
|
public static func saveUserOverrideModelData() {
|
||||||
let globalQueue = DispatchQueue(label: "vChewingLM_UOM", qos: .unspecified, attributes: .concurrent)
|
let globalQueue = DispatchQueue(label: "LMAssembly_UOM", qos: .unspecified, attributes: .concurrent)
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
Shared.InputMode.validCases.forEach { mode in
|
Shared.InputMode.validCases.forEach { mode in
|
||||||
group.enter()
|
group.enter()
|
||||||
globalQueue.async {
|
globalQueue.async {
|
||||||
mode.uom.saveData(toURL: userOverrideModelDataURL(mode))
|
mode.langModel.saveUOMData()
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,11 +195,11 @@ public class LMMgr {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func bleachSpecifiedSuggestions(targets: [String], mode: Shared.InputMode) {
|
public static func bleachSpecifiedSuggestions(targets: [String], mode: Shared.InputMode) {
|
||||||
mode.uom.bleachSpecifiedSuggestions(targets: targets, saveCallback: { mode.uom.saveData() })
|
mode.langModel.bleachSpecifiedUOMSuggestions(targets: targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func removeUnigramsFromUserOverrideModel(_ mode: Shared.InputMode) {
|
public static func removeUnigramsFromUserOverrideModel(_ mode: Shared.InputMode) {
|
||||||
mode.uom.bleachUnigrams(saveCallback: { mode.uom.saveData() })
|
mode.langModel.bleachUOMUnigrams()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func relocateWreckedUOMData() {
|
public static func relocateWreckedUOMData() {
|
||||||
|
@ -227,6 +221,6 @@ public class LMMgr {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func clearUserOverrideModelData(_ mode: Shared.InputMode = .imeModeNULL) {
|
public static func clearUserOverrideModelData(_ mode: Shared.InputMode = .imeModeNULL) {
|
||||||
mode.uom.clearData(withURL: userOverrideModelDataURL(mode))
|
mode.langModel.clearUOMData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,23 +17,23 @@ import Shared
|
||||||
extension LMMgr: PhraseEditorDelegate {
|
extension LMMgr: PhraseEditorDelegate {
|
||||||
public var currentInputMode: Shared.InputMode { IMEApp.currentInputMode }
|
public var currentInputMode: Shared.InputMode { IMEApp.currentInputMode }
|
||||||
|
|
||||||
public func openPhraseFile(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType, using app: FileOpenMethod) {
|
public func openPhraseFile(mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType, using app: FileOpenMethod) {
|
||||||
Self.openPhraseFile(fromURL: Self.userDictDataURL(mode: mode, type: type), using: app)
|
Self.openPhraseFile(fromURL: Self.userDictDataURL(mode: mode, type: type), using: app)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func consolidate(text strProcessed: inout String, pragma shouldCheckPragma: Bool) {
|
public func consolidate(text strProcessed: inout String, pragma shouldCheckPragma: Bool) {
|
||||||
vChewingLM.LMConsolidator.consolidate(text: &strProcessed, pragma: shouldCheckPragma)
|
LMAssembly.LMConsolidator.consolidate(text: &strProcessed, pragma: shouldCheckPragma)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func checkIfPhrasePairExists(userPhrase: String, mode: Shared.InputMode, key unigramKey: String) -> Bool {
|
public func checkIfPhrasePairExists(userPhrase: String, mode: Shared.InputMode, key unigramKey: String) -> Bool {
|
||||||
Self.checkIfPhrasePairExists(userPhrase: userPhrase, mode: mode, keyArray: [unigramKey])
|
Self.checkIfPhrasePairExists(userPhrase: userPhrase, mode: mode, keyArray: [unigramKey])
|
||||||
}
|
}
|
||||||
|
|
||||||
public func retrieveData(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType) -> String {
|
public func retrieveData(mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType) -> String {
|
||||||
Self.retrieveData(mode: mode, type: type)
|
Self.retrieveData(mode: mode, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func retrieveData(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType) -> String {
|
public static func retrieveData(mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType) -> String {
|
||||||
vCLog("Retrieving data. Mode: \(mode.localizedDescription), type: \(type.localizedDescription)")
|
vCLog("Retrieving data. Mode: \(mode.localizedDescription), type: \(type.localizedDescription)")
|
||||||
let theURL = Self.userDictDataURL(mode: mode, type: type)
|
let theURL = Self.userDictDataURL(mode: mode, type: type)
|
||||||
do {
|
do {
|
||||||
|
@ -44,12 +44,12 @@ extension LMMgr: PhraseEditorDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func saveData(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType, data: String) -> String {
|
public func saveData(mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType, data: String) -> String {
|
||||||
Self.saveData(mode: mode, type: type, data: data)
|
Self.saveData(mode: mode, type: type, data: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult public static func saveData(
|
@discardableResult public static func saveData(
|
||||||
mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType, data: String
|
mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType, data: String
|
||||||
) -> String {
|
) -> String {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let theURL = Self.userDictDataURL(mode: mode, type: type)
|
let theURL = Self.userDictDataURL(mode: mode, type: type)
|
||||||
|
|
|
@ -88,7 +88,7 @@ public extension LMMgr {
|
||||||
/// 有些使用者的語彙檔案已經過於龐大了(超過一千行),
|
/// 有些使用者的語彙檔案已經過於龐大了(超過一千行),
|
||||||
/// 每次寫入時都全文整理格式的話,會引發嚴重的效能問題。
|
/// 每次寫入時都全文整理格式的話,會引發嚴重的效能問題。
|
||||||
/// 所以這裡不再強制要求整理格式。
|
/// 所以這裡不再強制要求整理格式。
|
||||||
let theType: vChewingLM.ReplacableUserDataType = toFilter ? .theFilter : .thePhrases
|
let theType: LMAssembly.ReplacableUserDataType = toFilter ? .theFilter : .thePhrases
|
||||||
let theURL = LMMgr.userDictDataURL(mode: inputMode, type: theType)
|
let theURL = LMMgr.userDictDataURL(mode: inputMode, type: theType)
|
||||||
var fileSize: UInt64?
|
var fileSize: UInt64?
|
||||||
do {
|
do {
|
||||||
|
@ -143,7 +143,7 @@ public extension LMMgr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let theURL = LMMgr.userDictDataURL(mode: inputMode, type: .theFilter)
|
let theURL = LMMgr.userDictDataURL(mode: inputMode, type: .theFilter)
|
||||||
if forceConsolidate, !vChewingLM.LMConsolidator.consolidate(path: theURL.path, pragma: false) { return false }
|
if forceConsolidate, !LMAssembly.LMConsolidator.consolidate(path: theURL.path, pragma: false) { return false }
|
||||||
// Get FileSize.
|
// Get FileSize.
|
||||||
var fileSize: UInt64?
|
var fileSize: UInt64?
|
||||||
do {
|
do {
|
||||||
|
|
|
@ -61,7 +61,7 @@ public extension LMMgr {
|
||||||
/// - mode: 繁簡模式。
|
/// - mode: 繁簡模式。
|
||||||
/// - type: 辭典資料類型
|
/// - type: 辭典資料類型
|
||||||
/// - Returns: 資料路徑(URL)。
|
/// - Returns: 資料路徑(URL)。
|
||||||
static func userDictDataURL(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType) -> URL {
|
static func userDictDataURL(mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType) -> URL {
|
||||||
var fileName: String = {
|
var fileName: String = {
|
||||||
switch type {
|
switch type {
|
||||||
case .thePhrases: return "userdata"
|
case .thePhrases: return "userdata"
|
||||||
|
@ -271,7 +271,7 @@ public extension LMMgr {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
static func openUserDictFile(type: vChewingLM.ReplacableUserDataType, dual: Bool = false, alt: Bool) {
|
static func openUserDictFile(type: LMAssembly.ReplacableUserDataType, dual: Bool = false, alt: Bool) {
|
||||||
let app: FileOpenMethod = alt ? .textEdit : .finder
|
let app: FileOpenMethod = alt ? .textEdit : .finder
|
||||||
openPhraseFile(fromURL: userDictDataURL(mode: IMEApp.currentInputMode, type: type), using: app)
|
openPhraseFile(fromURL: userDictDataURL(mode: IMEApp.currentInputMode, type: type), using: app)
|
||||||
guard dual else { return }
|
guard dual else { return }
|
||||||
|
@ -324,7 +324,7 @@ public extension LMMgr {
|
||||||
/// 前者的話,需要該檔案存在的人自己會建立。
|
/// 前者的話,需要該檔案存在的人自己會建立。
|
||||||
/// 後者的話,你在敲字時自己就會建立。
|
/// 後者的話,你在敲字時自己就會建立。
|
||||||
var failed = false
|
var failed = false
|
||||||
caseCheck: for type in vChewingLM.ReplacableUserDataType.allCases {
|
caseCheck: for type in LMAssembly.ReplacableUserDataType.allCases {
|
||||||
let templateName = Self.templateName(for: type, mode: mode)
|
let templateName = Self.templateName(for: type, mode: mode)
|
||||||
if !ensureFileExists(userDictDataURL(mode: mode, type: type), deployTemplate: templateName) {
|
if !ensureFileExists(userDictDataURL(mode: mode, type: type), deployTemplate: templateName) {
|
||||||
failed = true
|
failed = true
|
||||||
|
@ -334,7 +334,7 @@ public extension LMMgr {
|
||||||
return !failed
|
return !failed
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static func templateName(for type: vChewingLM.ReplacableUserDataType, mode: Shared.InputMode) -> String {
|
internal static func templateName(for type: LMAssembly.ReplacableUserDataType, mode: Shared.InputMode) -> String {
|
||||||
switch type {
|
switch type {
|
||||||
case .thePhrases: return kTemplateNameUserPhrases
|
case .thePhrases: return kTemplateNameUserPhrases
|
||||||
case .theFilter: return kTemplateNameUserFilterList
|
case .theFilter: return kTemplateNameUserFilterList
|
||||||
|
|
|
@ -150,7 +150,7 @@ class FrmRevLookupWindow: NSWindow {
|
||||||
strBuilder.append("Maximum 15 results returnable.".localized + "\n")
|
strBuilder.append("Maximum 15 results returnable.".localized + "\n")
|
||||||
break theLoop
|
break theLoop
|
||||||
}
|
}
|
||||||
let arrResult = vChewingLM.LMInstantiator.getFactoryReverseLookupData(with: char)?.deduplicated ?? []
|
let arrResult = LMAssembly.LMInstantiator.getFactoryReverseLookupData(with: char)?.deduplicated ?? []
|
||||||
if !arrResult.isEmpty {
|
if !arrResult.isEmpty {
|
||||||
strBuilder.append(char + "\t")
|
strBuilder.append(char + "\t")
|
||||||
strBuilder.append(arrResult.joined(separator: ", "))
|
strBuilder.append(arrResult.joined(separator: ", "))
|
||||||
|
|
|
@ -178,7 +178,6 @@ public class SessionCtl: IMKInputController {
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
/// 重設所有語言模組。這裡不需要做按需重設,因為對運算量沒有影響。
|
/// 重設所有語言模組。這裡不需要做按需重設,因為對運算量沒有影響。
|
||||||
inputHandler?.currentLM = inputMode.langModel // 會自動更新組字引擎內的模組。
|
inputHandler?.currentLM = inputMode.langModel // 會自動更新組字引擎內的模組。
|
||||||
inputHandler?.currentUOM = inputMode.uom
|
|
||||||
/// 清空注拼槽+同步最新的注拼槽排列設定。
|
/// 清空注拼槽+同步最新的注拼槽排列設定。
|
||||||
inputHandler?.ensureKeyboardParser()
|
inputHandler?.ensureKeyboardParser()
|
||||||
/// 將輸入法偏好設定同步至語言模組內。
|
/// 將輸入法偏好設定同步至語言模組內。
|
||||||
|
@ -214,9 +213,7 @@ public class SessionCtl: IMKInputController {
|
||||||
// 關掉所有之前的副本的視窗。
|
// 關掉所有之前的副本的視窗。
|
||||||
Self.current?.hidePalettes()
|
Self.current?.hidePalettes()
|
||||||
Self.current = self
|
Self.current = self
|
||||||
self.inputHandler = InputHandler(
|
self.inputHandler = InputHandler(lm: self.inputMode.langModel, pref: PrefMgr.shared)
|
||||||
lm: self.inputMode.langModel, uom: self.inputMode.uom, pref: PrefMgr.shared
|
|
||||||
)
|
|
||||||
self.inputHandler?.delegate = self
|
self.inputHandler?.delegate = self
|
||||||
self.syncBaseLMPrefs()
|
self.syncBaseLMPrefs()
|
||||||
// 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。
|
// 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。
|
||||||
|
@ -313,9 +310,7 @@ public extension SessionCtl {
|
||||||
if self.isActivated { return }
|
if self.isActivated { return }
|
||||||
|
|
||||||
// 這裡不需要 setValue(),因為 IMK 會在自動呼叫 activateServer() 之後自動執行 setValue()。
|
// 這裡不需要 setValue(),因為 IMK 會在自動呼叫 activateServer() 之後自動執行 setValue()。
|
||||||
self.inputHandler = InputHandler(
|
self.inputHandler = InputHandler(lm: self.inputMode.langModel, pref: PrefMgr.shared)
|
||||||
lm: self.inputMode.langModel, uom: self.inputMode.uom, pref: PrefMgr.shared
|
|
||||||
)
|
|
||||||
self.inputHandler?.delegate = self
|
self.inputHandler?.delegate = self
|
||||||
self.syncBaseLMPrefs()
|
self.syncBaseLMPrefs()
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,7 @@ extension SettingsPanesCocoa.Phrases: NSTextViewDelegate, NSTextFieldDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var selUserDataType: vChewingLM.ReplacableUserDataType {
|
var selUserDataType: LMAssembly.ReplacableUserDataType {
|
||||||
switch cmbPEDataTypeMenu.selectedTag() {
|
switch cmbPEDataTypeMenu.selectedTag() {
|
||||||
case 0: return .thePhrases
|
case 0: return .thePhrases
|
||||||
case 1: return .theFilter
|
case 1: return .theFilter
|
||||||
|
@ -238,7 +238,7 @@ extension SettingsPanesCocoa.Phrases: NSTextViewDelegate, NSTextFieldDelegate {
|
||||||
// 嚴重警告:NSMenu.items 在 macOS 10.13 為止的系統下是唯讀的!!
|
// 嚴重警告:NSMenu.items 在 macOS 10.13 為止的系統下是唯讀的!!
|
||||||
// 往這個 property 裡面直接寫東西會導致整個視窗叫不出來!!!
|
// 往這個 property 裡面直接寫東西會導致整個視窗叫不出來!!!
|
||||||
cmbPEDataTypeMenu.menu?.appendItems {
|
cmbPEDataTypeMenu.menu?.appendItems {
|
||||||
for (tag, neta) in vChewingLM.ReplacableUserDataType.allCases.enumerated() {
|
for (tag, neta) in LMAssembly.ReplacableUserDataType.allCases.enumerated() {
|
||||||
NSMenu.Item(verbatim: neta.localizedDescription)?.tag(tag)
|
NSMenu.Item(verbatim: neta.localizedDescription)?.tag(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +332,7 @@ extension SettingsPanesCocoa.Phrases: NSTextViewDelegate, NSTextFieldDelegate {
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
vChewingLM.LMConsolidator.consolidate(text: &self.tfdPETextEditor.string, pragma: false)
|
LMAssembly.LMConsolidator.consolidate(text: &self.tfdPETextEditor.string, pragma: false)
|
||||||
if self.selUserDataType == .thePhrases {
|
if self.selUserDataType == .thePhrases {
|
||||||
LMMgr.shared.tagOverrides(in: &self.tfdPETextEditor.string, mode: self.selInputMode)
|
LMMgr.shared.tagOverrides(in: &self.tfdPETextEditor.string, mode: self.selInputMode)
|
||||||
}
|
}
|
||||||
|
@ -416,7 +416,7 @@ private enum PETerminology {
|
||||||
case weightInputBox =
|
case weightInputBox =
|
||||||
"If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result."
|
"If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result."
|
||||||
|
|
||||||
public static func sampleDictionaryContent(for type: vChewingLM.ReplacableUserDataType) -> String {
|
public static func sampleDictionaryContent(for type: LMAssembly.ReplacableUserDataType) -> String {
|
||||||
var result = ""
|
var result = ""
|
||||||
switch type {
|
switch type {
|
||||||
case .thePhrases:
|
case .thePhrases:
|
||||||
|
|
|
@ -34,16 +34,15 @@ func vCTestLog(_ str: String) {
|
||||||
/// 該單元測試使用獨立的語彙資料,因此會在選字時的候選字
|
/// 該單元測試使用獨立的語彙資料,因此會在選字時的候選字
|
||||||
/// 順序等方面與威注音輸入法實際使用時的體驗有差異。
|
/// 順序等方面與威注音輸入法實際使用時的體驗有差異。
|
||||||
class MainAssemblyTests: XCTestCase {
|
class MainAssemblyTests: XCTestCase {
|
||||||
let testUOM = LangModelAssembly.vChewingLM.LMUserOverride(dataURL: .init(fileURLWithPath: "/dev/null"))
|
var testLM = LMAssembly.LMInstantiator.construct { _ in
|
||||||
var testLM = LangModelAssembly.vChewingLM.LMInstantiator.construct { _ in
|
LMAssembly.LMInstantiator.connectToTestSQLDB()
|
||||||
vChewingLM.LMInstantiator.connectToTestSQLDB()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static let testServer = IMKServer(name: "org.atelierInmu.vChewing.MainAssembly.UnitTests_Connection", bundleIdentifier: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
|
static let testServer = IMKServer(name: "org.atelierInmu.vChewing.MainAssembly.UnitTests_Connection", bundleIdentifier: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
|
||||||
|
|
||||||
static var _testHandler: InputHandler?
|
static var _testHandler: InputHandler?
|
||||||
var testHandler: InputHandler {
|
var testHandler: InputHandler {
|
||||||
let result = Self._testHandler ?? InputHandler(lm: testLM, uom: testUOM, pref: PrefMgr.shared)
|
let result = Self._testHandler ?? InputHandler(lm: testLM, pref: PrefMgr.shared)
|
||||||
if Self._testHandler == nil { Self._testHandler = result }
|
if Self._testHandler == nil { Self._testHandler = result }
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -65,7 +64,7 @@ class MainAssemblyTests: XCTestCase {
|
||||||
let dataTab = NSEvent.KeyEventData(chars: NSEvent.SpecialKey.tab.unicodeScalar.description, keyCode: KeyCode.kTab.rawValue)
|
let dataTab = NSEvent.KeyEventData(chars: NSEvent.SpecialKey.tab.unicodeScalar.description, keyCode: KeyCode.kTab.rawValue)
|
||||||
|
|
||||||
func clearTestUOM() {
|
func clearTestUOM() {
|
||||||
testUOM.clearData(withURL: URL(fileURLWithPath: "/dev/null"))
|
testLM.clearUOMData()
|
||||||
}
|
}
|
||||||
|
|
||||||
func typeSentenceOrCandidates(_ sequence: String) {
|
func typeSentenceOrCandidates(_ sequence: String) {
|
||||||
|
@ -106,11 +105,11 @@ class MainAssemblyTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension vChewingLM.LMInstantiator {
|
extension LMAssembly.LMInstantiator {
|
||||||
static func construct(
|
static func construct(
|
||||||
isCHS: Bool = false, completionHandler: @escaping (_ this: vChewingLM.LMInstantiator) -> Void
|
isCHS: Bool = false, completionHandler: @escaping (_ this: LMAssembly.LMInstantiator) -> Void
|
||||||
) -> vChewingLM.LMInstantiator {
|
) -> LMAssembly.LMInstantiator {
|
||||||
let this = vChewingLM.LMInstantiator(isCHS: isCHS)
|
let this = LMAssembly.LMInstantiator(isCHS: isCHS)
|
||||||
completionHandler(this)
|
completionHandler(this)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,11 @@ import Shared
|
||||||
|
|
||||||
public protocol PhraseEditorDelegate {
|
public protocol PhraseEditorDelegate {
|
||||||
var currentInputMode: Shared.InputMode { get }
|
var currentInputMode: Shared.InputMode { get }
|
||||||
func retrieveData(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType) -> String
|
func retrieveData(mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType) -> String
|
||||||
@discardableResult func saveData(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType, data: String)
|
@discardableResult func saveData(mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType, data: String)
|
||||||
-> String
|
-> String
|
||||||
func checkIfPhrasePairExists(userPhrase: String, mode: Shared.InputMode, key unigramKey: String) -> Bool
|
func checkIfPhrasePairExists(userPhrase: String, mode: Shared.InputMode, key unigramKey: String) -> Bool
|
||||||
func consolidate(text strProcessed: inout String, pragma shouldCheckPragma: Bool)
|
func consolidate(text strProcessed: inout String, pragma shouldCheckPragma: Bool)
|
||||||
func openPhraseFile(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType, using: FileOpenMethod)
|
func openPhraseFile(mode: Shared.InputMode, type: LMAssembly.ReplacableUserDataType, using: FileOpenMethod)
|
||||||
func tagOverrides(in strProcessed: inout String, mode: Shared.InputMode)
|
func tagOverrides(in strProcessed: inout String, mode: Shared.InputMode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ public struct VwrPhraseEditorUI: View {
|
||||||
@State var txtAddPhraseField3 = ""
|
@State var txtAddPhraseField3 = ""
|
||||||
@State var txtAddPhraseField4 = ""
|
@State var txtAddPhraseField4 = ""
|
||||||
@State public var selInputMode: Shared.InputMode = .imeModeNULL
|
@State public var selInputMode: Shared.InputMode = .imeModeNULL
|
||||||
@State public var selUserDataType: vChewingLM.ReplacableUserDataType = .thePhrases
|
@State public var selUserDataType: LMAssembly.ReplacableUserDataType = .thePhrases
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var textEditorTooltip = PETerms.TooltipTexts.sampleDictionaryContent(for: .thePhrases)
|
@State private var textEditorTooltip = PETerms.TooltipTexts.sampleDictionaryContent(for: .thePhrases)
|
||||||
public weak var window: NSWindow?
|
public weak var window: NSWindow?
|
||||||
|
@ -212,16 +212,16 @@ public struct VwrPhraseEditorUI: View {
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
Picker("", selection: $selUserDataType.didChange { dropDownMenuDidChange() }) {
|
Picker("", selection: $selUserDataType.didChange { dropDownMenuDidChange() }) {
|
||||||
Text(vChewingLM.ReplacableUserDataType.thePhrases.localizedDescription).tag(
|
Text(LMAssembly.ReplacableUserDataType.thePhrases.localizedDescription).tag(
|
||||||
vChewingLM.ReplacableUserDataType.thePhrases)
|
LMAssembly.ReplacableUserDataType.thePhrases)
|
||||||
Text(vChewingLM.ReplacableUserDataType.theFilter.localizedDescription).tag(
|
Text(LMAssembly.ReplacableUserDataType.theFilter.localizedDescription).tag(
|
||||||
vChewingLM.ReplacableUserDataType.theFilter)
|
LMAssembly.ReplacableUserDataType.theFilter)
|
||||||
Text(vChewingLM.ReplacableUserDataType.theReplacements.localizedDescription).tag(
|
Text(LMAssembly.ReplacableUserDataType.theReplacements.localizedDescription).tag(
|
||||||
vChewingLM.ReplacableUserDataType.theReplacements)
|
LMAssembly.ReplacableUserDataType.theReplacements)
|
||||||
Text(vChewingLM.ReplacableUserDataType.theAssociates.localizedDescription).tag(
|
Text(LMAssembly.ReplacableUserDataType.theAssociates.localizedDescription).tag(
|
||||||
vChewingLM.ReplacableUserDataType.theAssociates)
|
LMAssembly.ReplacableUserDataType.theAssociates)
|
||||||
Text(vChewingLM.ReplacableUserDataType.theSymbols.localizedDescription).tag(
|
Text(LMAssembly.ReplacableUserDataType.theSymbols.localizedDescription).tag(
|
||||||
vChewingLM.ReplacableUserDataType.theSymbols)
|
LMAssembly.ReplacableUserDataType.theSymbols)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
Button("Reload") {
|
Button("Reload") {
|
||||||
|
@ -340,7 +340,7 @@ public enum PETerms {
|
||||||
case weightInputBox =
|
case weightInputBox =
|
||||||
"If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result."
|
"If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result."
|
||||||
|
|
||||||
public static func sampleDictionaryContent(for type: vChewingLM.ReplacableUserDataType) -> String {
|
public static func sampleDictionaryContent(for type: LMAssembly.ReplacableUserDataType) -> String {
|
||||||
var result = ""
|
var result = ""
|
||||||
switch type {
|
switch type {
|
||||||
case .thePhrases:
|
case .thePhrases:
|
||||||
|
|
Loading…
Reference in New Issue