Skip to content

Commit

Permalink
Refactor some handlings and unit tests. (#80)
Browse files Browse the repository at this point in the history
- Also eliminating the use of ExpressibleByStringLiteral, reducing the package compilation time cost from 8.5s to 3.3s (on mac mini 2018 with Core-i7 CPU).
  • Loading branch information
ShikiSuen committed Mar 19, 2024
1 parent 0f04e78 commit f2ff443
Show file tree
Hide file tree
Showing 20 changed files with 7,577 additions and 7,563 deletions.
42 changes: 14 additions & 28 deletions Sources/Tekkon/Tekkon_Phonabets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.

infix operator <~: AssignmentPrecedence

public extension Tekkon {
// MARK: - Dynamic Constants and Basic Enums

/// 定義注音符號的種類
enum PhoneType: Int {
enum PhoneType: Int, Codable, Hashable {
case null = 0 // 假
case consonant = 1 // 聲
case semivowel = 2 // 介
Expand All @@ -19,7 +21,7 @@ public extension Tekkon {
}

/// 定義注音排列的類型
enum MandarinParser: Int {
enum MandarinParser: Int, Codable, Hashable {
case ofDachen = 0
case ofDachen26 = 1
case ofETen = 2
Expand Down Expand Up @@ -85,7 +87,7 @@ public extension Tekkon {
/// 如果遇到被設為多個字符、或者字符不對的情況的話,value 會被清空、PhoneType 會變成 null。
/// 賦值時最好直接重新 init 且一直用 let 來初期化 Phonabet。
/// 其實 value 對外只讀,對內的話另有 valueStorage 代為存儲內容。這樣比較安全一些。
@frozen struct Phonabet: Equatable, Hashable, ExpressibleByStringLiteral {
@frozen struct Phonabet: Equatable, Codable, Hashable {
public var type: PhoneType = .null
private var valueStorage = ""
public var value: String { valueStorage }
Expand Down Expand Up @@ -117,6 +119,15 @@ public extension Tekkon {
ensureType()
}

public mutating func setValue(_ newValue: String) {
valueStorage = newValue
ensureType()
}

public static func <~ (_ lhs: inout Tekkon.Phonabet, _ newValue: String) {
lhs.setValue(newValue)
}

/// 用來自動更新自身的屬性值的函式。
public mutating func ensureType() {
if Tekkon.allowedConsonants.contains(value) {
Expand All @@ -132,30 +143,5 @@ public extension Tekkon {
valueStorage = ""
}
}

// MARK: - Misc Definitions

/// 這些內容用來滿足 "Equatable, Hashable, ExpressibleByStringLiteral" 需求。

public static func == (lhs: Phonabet, rhs: Phonabet) -> Bool {
lhs.value == rhs.value
}

public func hash(into hasher: inout Hasher) {
hasher.combine(value)
hasher.combine(type)
}

public init(stringLiteral value: String) {
self.init(value)
}

public init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}

public init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
}
}
129 changes: 48 additions & 81 deletions Sources/Tekkon/Tekkon_SyllableComposer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ public extension Tekkon {
///
/// 因為是 String Literal,所以初期化時可以藉由 @input 參數指定初期已經傳入的按鍵訊號。
/// 還可以在初期化時藉由 @arrange 參數來指定注音排列(預設為「.ofDachen」大千佈局)。
@frozen struct Composer: Equatable, Hashable, ExpressibleByStringLiteral {
@frozen struct Composer: Equatable, Codable, Hashable, ExpressibleByStringLiteral {
/// 聲母。
public var consonant: Phonabet = ""
public var consonant: Phonabet = .init()

/// 介母。
public var semivowel: Phonabet = ""
public var semivowel: Phonabet = .init()

/// 韻母。
public var vowel: Phonabet = ""
public var vowel: Phonabet = .init()

/// 聲調。
public var intonation: Phonabet = ""
public var intonation: Phonabet = .init()

/// 拼音組音區。
public var romajiBuffer: String = ""
public var romajiBuffer: String = .init()

/// 注音排列種類。預設情況下是大千排列(Windows / macOS 預設注音排列)。
public var parser: MandarinParser = .ofDachen
Expand Down Expand Up @@ -204,10 +204,8 @@ public extension Tekkon {
receiveKey(fromPhonabet: translate(key: input))
return
}
if mapArayuruPinyinIntonation.keys.contains(input) {
if let theTone = mapArayuruPinyinIntonation[input] {
intonation = Phonabet(theTone)
}
if let theTone = mapArayuruPinyinIntonation[input] {
intonation = Phonabet(theTone)
} else {
// 為了防止 romajiBuffer 越敲越長帶來算力負擔,這裡讓它在要溢出時自動丟掉最早輸入的音頭。
let maxCount: Int = (parser == .ofWadeGilesPinyin) ? 7 : 6
Expand Down Expand Up @@ -241,36 +239,32 @@ public extension Tekkon {
if phonabetCombinationCorrectionEnabled {
switch phonabet {
case "", "":
if vowel.value == "" { vowel = "" }
if vowel.value == "" { vowel <~ "" }
case "":
if "".doesHave(semivowel.value) { semivowel = "" }
if "ㄧㄩ".doesHave(semivowel.value) { thePhone = "" }
if "".doesHave(semivowel.value) { semivowel <~ "" }
if "ㄧㄩ".doesHave(semivowel.value) { thePhone <~ "" }
case "":
if "".doesHave(semivowel.value) { semivowel = "" }
if "".doesHave(semivowel.value) { semivowel <~ "" }
case "", "":
if phonabet == "", semivowel.value == "" { semivowel = "" }
if phonabet == "", semivowel.value == "" { semivowel <~ "" }
if "ㄅㄆㄇㄈ".doesHave(consonant.value), semivowel.value == "" { semivowel.clear() }
case "":
if "ㄋㄌ".doesHave(consonant.value), semivowel.value == "" { semivowel.clear() }
case "":
if "ㄅㄆㄇㄈ".doesHave(consonant.value), "ㄛㄥ".doesHave(vowel.value) { vowel.clear() }
if "ㄋㄌ".doesHave(consonant.value), "".doesHave(vowel.value) { vowel.clear() }
if "".doesHave(vowel.value) { vowel = "" }
if "".doesHave(vowel.value) { thePhone = "" }
if "".doesHave(vowel.value) { vowel <~ "" }
if "".doesHave(vowel.value) { thePhone <~ "" }
case "", "", "", "":
if ["ㄨㄛ", "ㄨㄥ"].contains(semivowel.value + vowel.value) { semivowel.clear() }
default: break
}
if [.vowel, .intonation].contains(thePhone.type), "ㄓㄔㄕㄗㄘㄙ".doesHave(consonant.value) {
switch semivowel.value {
case "": semivowel.clear()
case "":
switch consonant {
case _ where "ㄓㄗ".doesHave(consonant.value): consonant = ""
case _ where "ㄔㄘ".doesHave(consonant.value): consonant = ""
case _ where "ㄕㄙ".doesHave(consonant.value): consonant = ""
default: break
}
switch (semivowel.value, consonant.value) {
case ("", _): semivowel.clear()
case ("", ""), ("", ""): consonant <~ ""
case ("", ""), ("", ""): consonant <~ ""
case ("", ""), ("", ""): consonant <~ ""
default: break
}
}
Expand Down Expand Up @@ -430,38 +424,14 @@ public extension Tekkon {
// 處理特殊情形。
switch incomingPhonabet.type {
case .semivowel:
switch consonant {
case "":
switch incomingPhonabet {
// 這裡不處理「ㄍㄧ」到「ㄑㄧ」的轉換,因為只有倚天26需要處理這個。
case "": consonant = "" // ㄍㄨ
case "": consonant = "" // ㄑㄩ
default: break
}
case "":
switch incomingPhonabet {
case "": consonant = "" // ㄐㄧ
case "": consonant = "" // ㄓㄨ
case "": consonant = "" // ㄐㄩ
default: break
}
case "":
switch incomingPhonabet {
case "": consonant = "" // ㄐㄧ
case "": consonant = "" // ㄓㄨ
case "": consonant = "" // ㄐㄩ
default: break
}
case "":
switch incomingPhonabet {
case "": consonant = "" // ㄒㄧ
case "": consonant = "" // ㄕㄨ
case "": consonant = "" // ㄒㄩ
default: break
}
// 這裡不處理「ㄍㄧ」到「ㄑㄧ」的轉換,因為只有倚天26需要處理這個。
switch (consonant.value, incomingPhonabet.value) {
case ("", ""), ("", ""): consonant <~ ""
case ("", ""), ("", ""), ("", ""): consonant <~ ""
case ("", ""), ("", ""): consonant <~ ""
default: break
}
if incomingPhonabet == "" {
if incomingPhonabet.value == "" {
fixValue("", "")
fixValue("", "")
fixValue("", "")
Expand Down Expand Up @@ -491,8 +461,8 @@ public extension Tekkon {
case "f" where isPronounceable: strReturn = "ˊ"
case "j" where isPronounceable: strReturn = "ˇ"
case "k" where isPronounceable: strReturn = "ˋ"
case "e" where consonant == "": consonant = ""
case "p" where !consonant.isEmpty || semivowel == "": strReturn = ""
case "e" where consonant.value == "": consonant <~ ""
case "p" where !consonant.isEmpty || semivowel.value == "": strReturn = ""
case "h" where !consonant.isEmpty || !semivowel.isEmpty: strReturn = ""
case "l" where !consonant.isEmpty || !semivowel.isEmpty: strReturn = ""
case "m" where !consonant.isEmpty || !semivowel.isEmpty: strReturn = ""
Expand Down Expand Up @@ -520,7 +490,7 @@ public extension Tekkon {
}

// 後置修正
if value == "ㄍ˙" { consonant = "" }
if value == "ㄍ˙" { consonant <~ "" }

// 這些按鍵在上文處理過了,就不要再回傳了。
if keysToHandleHere.doesHave(key) { strReturn = "" }
Expand Down Expand Up @@ -585,7 +555,7 @@ public extension Tekkon {
}

// 後置修正
if value == "ㄔ˙" { consonant = "" }
if value == "ㄔ˙" { consonant <~ "" }

// 這些按鍵在上文處理過了,就不要再回傳了。
if keysToHandleHere.doesHave(key) { strReturn = "" }
Expand Down Expand Up @@ -654,41 +624,38 @@ public extension Tekkon {
case "d" where isPronounceable: strReturn = "ˋ"
case "y" where isPronounceable: strReturn = "˙"
case "b" where !consonant.isEmpty || !semivowel.isEmpty: strReturn = ""
case "i" where vowel.isEmpty || vowel == "": strReturn = ""
case "l" where vowel.isEmpty || vowel == "": strReturn = ""
case "i" where vowel.isEmpty || vowel.value == "": strReturn = ""
case "l" where vowel.isEmpty || vowel.value == "": strReturn = ""
case "n" where !consonant.isEmpty || !semivowel.isEmpty:
if value == "" { consonant.clear() }
strReturn = ""
case "o" where vowel.isEmpty || vowel == "": strReturn = ""
case "p" where vowel.isEmpty || vowel == "": strReturn = ""
case "q" where consonant.isEmpty || consonant == "": strReturn = ""
case "t" where consonant.isEmpty || consonant == "": strReturn = ""
case "w" where consonant.isEmpty || consonant == "": strReturn = ""
case "o" where vowel.isEmpty || vowel.value == "": strReturn = ""
case "p" where vowel.isEmpty || vowel.value == "": strReturn = ""
case "q" where consonant.isEmpty || consonant.value == "": strReturn = ""
case "t" where consonant.isEmpty || consonant.value == "": strReturn = ""
case "w" where consonant.isEmpty || consonant.value == "": strReturn = ""
case "m":
if semivowel == "", vowel != "" {
switch (semivowel.value, vowel.value) {
case ("", _):
semivowel.clear()
strReturn = ""
} else if semivowel != "", vowel == "" {
case (_, ""):
vowel.clear()
strReturn = ""
} else if !semivowel.isEmpty {
strReturn = ""
} else {
strReturn = "ㄐㄑㄒ".doesHave(consonant.value) ? "" : ""
case ("", ""), _:
strReturn = (!semivowel.isEmpty || !"ㄐㄑㄒ".doesHave(consonant.value)) ? "" : ""
}
case "u":
if semivowel == "", vowel != "" {
semivowel.clear()
strReturn = ""
} else if semivowel != "", vowel == "" {
strReturn = ""
} else if semivowel == "", vowel == "" {
switch (semivowel.value, vowel.value) {
case ("", ""):
semivowel.clear()
vowel.clear()
} else if !semivowel.isEmpty {
case ("", _):
semivowel.clear()
strReturn = ""
} else {
case (_, ""):
strReturn = ""
case (_, _): strReturn = semivowel.isEmpty ? "" : ""
}
default: break
}
Expand Down
2 changes: 2 additions & 0 deletions Tests/TekkonTests/BasicTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ final class TekkonTestsBasic: XCTestCase {
// Testing tool functions
XCTAssertEqual(Tekkon.restoreToneOneInPhona(target: "ㄉㄧㄠ"), "ㄉㄧㄠ1")
XCTAssertEqual(Tekkon.cnvPhonaToTextbookStyle(target: "ㄓㄜ˙"), "˙ㄓㄜ")
XCTAssertEqual(Tekkon.cnvPhonaToHanyuPinyin(targetJoined: "ㄍㄢˋ"), "gan4")
XCTAssertEqual(Tekkon.cnvHanyuPinyinToTextbookStyle(targetJoined: "起(qi3)居(ju1)"), "起(qǐ)居(jū)")
XCTAssertEqual(Tekkon.cnvHanyuPinyinToPhona(targetJoined: "bian4-le5-tian1"), "ㄅㄧㄢˋ-ㄌㄜ˙-ㄊㄧㄢ")
// 測試這種情形:「如果傳入的字串不包含任何半形英數內容的話,那麼應該直接將傳入的字串原樣返回」。
XCTAssertEqual(Tekkon.cnvHanyuPinyinToPhona(targetJoined: "ㄅㄧㄢˋ-˙ㄌㄜ-ㄊㄧㄢ"), "ㄅㄧㄢˋ-˙ㄌㄜ-ㄊㄧㄢ")
Expand Down
Loading

0 comments on commit f2ff443

Please sign in to comment.