-
Notifications
You must be signed in to change notification settings - Fork 75
/
bamboo.go
254 lines (220 loc) · 7.23 KB
/
bamboo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
* Bamboo - A Vietnamese Input method editor
* Copyright (C) Luong Thanh Lam <ltlam93@gmail.com>
*
* This software is licensed under the MIT license. For more information,
* see <https://github.com/BambooEngine/bamboo-core/blob/master/LICENSE>.
*/
// Package bamboo implements text processing for Vietnamese
package bamboo
import (
"unicode"
)
type Mode uint
const (
VietnameseMode Mode = 1 << iota
EnglishMode
ToneLess
MarkLess
LowerCase
FullText
PunctuationMode
InReverseOrder
)
const (
EfreeToneMarking uint = 1 << iota
EstdToneStyle
EautoCorrectEnabled
EstdFlags = EfreeToneMarking | EstdToneStyle | EautoCorrectEnabled
)
type Transformation struct {
Rule Rule
Target *Transformation
IsUpperCase bool
}
type IEngine interface {
SetFlag(uint)
GetInputMethod() InputMethod
ProcessKey(rune, Mode)
ProcessString(string, Mode)
GetProcessedString(Mode) string
IsValid(bool) bool
CanProcessKey(rune) bool
RemoveLastChar(bool)
RestoreLastWord(bool)
Reset()
}
type BambooEngine struct {
composition []*Transformation
inputMethod InputMethod
flags uint
}
func NewEngine(inputMethod InputMethod, flag uint) IEngine {
engine := BambooEngine{
inputMethod: inputMethod,
flags: flag,
}
return &engine
}
func (e *BambooEngine) GetInputMethod() InputMethod {
return e.inputMethod
}
func (e *BambooEngine) SetFlag(flag uint) {
e.flags = flag
}
func (e *BambooEngine) GetFlag(flag uint) uint {
return e.flags
}
func (e *BambooEngine) isSuperKey(lowerKey rune) bool {
return inKeyList(e.GetInputMethod().SuperKeys, lowerKey)
}
func (e *BambooEngine) isToneKey(key rune) bool {
return inKeyList(e.GetInputMethod().ToneKeys, key)
}
func (e *BambooEngine) isEffectiveKey(key rune) bool {
return inKeyList(e.GetInputMethod().Keys, key)
}
func (e *BambooEngine) IsValid(inputIsFullComplete bool) bool {
var _, last = extractLastWord(e.composition, e.GetInputMethod().Keys)
return isValid(last, inputIsFullComplete)
}
func (e *BambooEngine) GetProcessedString(mode Mode) string {
var tmp []*Transformation
if mode&FullText != 0 {
tmp = e.composition
} else if mode&PunctuationMode != 0 {
_, tmp = extractLastWordWithPunctuationMarks(e.composition, e.inputMethod.Keys)
return Flatten(tmp, VietnameseMode)
} else {
_, tmp = extractLastWord(e.composition, e.inputMethod.Keys)
}
return Flatten(tmp, mode)
}
func (e *BambooEngine) getApplicableRules(key rune) []Rule {
var applicableRules []Rule
for _, inputRule := range e.inputMethod.Rules {
if inputRule.Key == unicode.ToLower(key) {
applicableRules = append(applicableRules, inputRule)
}
}
return applicableRules
}
func (e *BambooEngine) findTargetByKey(composition []*Transformation, key rune) (*Transformation, Rule) {
return findTarget(composition, e.getApplicableRules(key), e.flags)
}
func (e *BambooEngine) CanProcessKey(key rune) bool {
return canProcessKey(key, e.inputMethod.Keys)
}
func (e *BambooEngine) generateTransformations(composition []*Transformation, lowerKey rune, isUpperCase bool) []*Transformation {
var transformations = generateTransformations(composition, e.getApplicableRules(lowerKey), e.flags, lowerKey, isUpperCase)
if transformations == nil {
// If none of the applicable_rules can actually be applied then this new
// transformation fall-backs to an APPENDING one.
transformations = generateFallbackTransformations(composition, e.getApplicableRules(lowerKey), lowerKey, isUpperCase)
var newComposition = append(composition, transformations...)
// Implement the uwo+ typing shortcut by creating a virtual
// Mark.HORN rule that targets 'u' or 'o'.
if virtualTrans := e.applyUowShortcut(newComposition); virtualTrans != nil {
transformations = append(transformations, virtualTrans)
}
}
/**
* Sometimes, a tone's position in a previous state must be changed to fit the new state
*
* e.g.
* prev state: chuyr -> chuỷ
* this state: chuyrene -> chuyển
**/
transformations = append(transformations, e.refreshLastToneTarget(append(composition, transformations...))...)
return transformations
}
func (e *BambooEngine) newComposition(composition []*Transformation, key rune, isUpperCase bool) ([]*Transformation) {
// Just process the key stroke on the last syllable
var previousTransformations, lastSyllable = extractLastSyllable(composition)
// Find all possible transformations this keypress can generate
lastSyllable = append(lastSyllable, e.generateTransformations(lastSyllable, key, isUpperCase)...)
// Put these transformations back to the composition
return append(previousTransformations, lastSyllable...)
}
func (e *BambooEngine) applyUowShortcut(syllable []*Transformation) *Transformation {
str := Flatten(syllable, ToneLess|LowerCase)
if len(e.inputMethod.SuperKeys) > 0 && regUOhTail.MatchString(str) {
if target, missingRule := e.findTargetByKey(syllable, e.inputMethod.SuperKeys[0]); target != nil {
missingRule.Key = rune(0) // virtual rule should not appear in the raw string
virtualTrans := &Transformation{
Rule: missingRule,
Target: target,
}
return virtualTrans
}
}
return nil
}
func (e *BambooEngine) refreshLastToneTarget(syllable []*Transformation) []*Transformation {
if e.flags&EfreeToneMarking != 0 && isValid(syllable, false) {
return refreshLastToneTarget(syllable, e.flags&EstdToneStyle != 0)
}
return nil
}
/***** BEGIN SIDE-EFFECT METHODS ******/
func (e *BambooEngine) ProcessString(str string, mode Mode) {
for _, key := range str {
e.ProcessKey(key, mode)
}
}
func (e *BambooEngine) ProcessKey(key rune, mode Mode) {
var lowerKey = unicode.ToLower(key)
var isUpperCase = unicode.IsUpper(key)
if mode&EnglishMode != 0 || !e.CanProcessKey(lowerKey) {
if mode&InReverseOrder != 0 {
e.composition = append([]*Transformation{newAppendingTrans(lowerKey, isUpperCase)}, e.composition...)
return
}
e.composition = append(e.composition, newAppendingTrans(lowerKey, isUpperCase))
return
}
e.composition = e.newComposition(e.composition, lowerKey, isUpperCase)
}
func (e *BambooEngine) RestoreLastWord(toVietnamese bool) {
var previous, lastComb = extractLastWord(e.composition, e.GetInputMethod().Keys)
if len(lastComb) == 0 {
return
}
if !toVietnamese {
e.composition = append(previous, breakComposition(lastComb)...)
} else {
var newComp []*Transformation
for _, tnx := range lastComb {
newComp = e.newComposition(newComp, tnx.Rule.Key, tnx.IsUpperCase)
}
e.composition = append(previous, newComp...)
}
}
func (e *BambooEngine) Reset() {
e.composition = nil
}
// Find the last APPENDING transformation and all
// the transformations that add effects to it.
func (e *BambooEngine) RemoveLastChar(refreshLastToneTarget bool) {
var lastAppending = findLastAppendingTrans(e.composition)
if lastAppending == nil {
return
}
if !e.CanProcessKey(lastAppending.Rule.Key) {
e.composition = e.composition[:len(e.composition)-1]
return
}
var previous, lastComb = extractLastWord(e.composition, e.GetInputMethod().Keys)
var newComb []*Transformation
for _, t := range lastComb {
if t.Target == lastAppending || t == lastAppending {
continue
}
newComb = append(newComb, t)
}
if refreshLastToneTarget {
newComb = append(newComb, e.refreshLastToneTarget(newComb)...)
}
e.composition = append(previous, newComb...)
}
/***** END SIDE-EFFECT METHODS ******/