LMAssembly // Pack LMUserOverride inside LMInstantiator, etc.

This commit is contained in:
ShikiSuen 2024-02-18 15:51:24 +08:00
parent c5899152e6
commit e44843e603
35 changed files with 302 additions and 315 deletions

View File

@ -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半衰記憶模組。

View File

@ -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()

View File

@ -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 = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"

View File

@ -11,7 +11,7 @@ import Megrez
import Shared import Shared
import SQLite3 import SQLite3
public extension vChewingLM { public extension LMAssembly {
/// LMInstantiatorLMI /// LMInstantiatorLMI
/// 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() {}

View File

@ -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 }
/// ///

View File

@ -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]()

View File

@ -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_"

View File

@ -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)
} }

View File

@ -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)
}
}

View File

@ -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 = ""

View File

@ -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

View File

@ -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>]] = [:]

View File

@ -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)")

View File

@ -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 = ""

View File

@ -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": "˙",
]
}
}

View File

@ -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()

View File

@ -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

View File

@ -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)
} }

View File

@ -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()
} }
} }

View File

@ -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)")

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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
} }

View File

@ -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)

View File

@ -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()
} }
} }

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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: ", "))

View File

@ -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()

View File

@ -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:

View File

@ -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
} }

View File

@ -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)
} }

View File

@ -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: