Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 112 additions & 31 deletions bamboo.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,22 @@ const (
W2uEverywhere = 2
)

const (
BracketTransformFollowFlags = -1
BracketTransformDisabled = 0
BracketTransformNonStart = 1
BracketTransformEverywhere = 2
)

const (
EfreeToneMarking uint = 1 << iota
EstdToneStyle
EautoCorrectEnabled
Ew2uEnabled
EbracketTransformEnabled
EstdFlags = EfreeToneMarking | EstdToneStyle | EautoCorrectEnabled | Ew2uEnabled
)


type Transformation struct {
Rule Rule
Target *Transformation
Expand All @@ -53,6 +60,7 @@ type IEngine interface {
GetInputMethod() InputMethod
ProcessKey(rune, Mode)
SetW2UMode(int)
SetBracketTransformMode(int)
ProcessString(string, Mode)
GetProcessedString(Mode) string
IsValid(bool) bool
Expand All @@ -64,17 +72,19 @@ type IEngine interface {
}

type BambooEngine struct {
composition []*Transformation
inputMethod InputMethod
flags uint
w2uMode int // W2uFollowFlags, W2uDisabled, W2uNonStart, W2uEverywhere
composition []*Transformation
inputMethod InputMethod
flags uint
w2uMode int // W2uFollowFlags, W2uDisabled, W2uNonStart, W2uEverywhere
bracketTransformMode int // BracketTransformFollowFlags, Disabled, NonStart, Everywhere
}

func NewEngine(inputMethod InputMethod, flag uint) IEngine {
engine := BambooEngine{
inputMethod: inputMethod,
flags: flag,
w2uMode: W2uFollowFlags,
inputMethod: inputMethod,
flags: flag,
w2uMode: W2uFollowFlags,
bracketTransformMode: BracketTransformFollowFlags,
}
return &engine
}
Expand All @@ -93,28 +103,52 @@ func (e *BambooEngine) SetW2UMode(mode int) {
}
}

func (e *BambooEngine) SetBracketTransformMode(mode int) {
if mode >= BracketTransformFollowFlags && mode <= BracketTransformEverywhere {
e.bracketTransformMode = mode
}
}

func (e *BambooEngine) GetFlag(flag uint) uint {
return e.flags
}

func (e *BambooEngine) IsValid(inputIsFullComplete bool) bool {
var _, last = extractLastWord(e.composition, e.GetInputMethod().Keys)
var keys []rune
if e.isBracketEnabled() {
keys = append(e.GetInputMethod().Keys, '[', ']', '{', '}')
} else {
keys = e.GetInputMethod().Keys
}
var _, last = extractLastWord(e.composition, keys)
return isValid(last, inputIsFullComplete)
}

func (e *BambooEngine) GetProcessedString(mode Mode) string {
var tmp []*Transformation
var keys []rune
if e.isBracketEnabled() {
keys = append(e.inputMethod.Keys, '[', ']', '{', '}')
} else {
keys = e.inputMethod.Keys
}
if mode&FullText != 0 {
tmp = e.composition
} else if mode&PunctuationMode != 0 {
_, tmp = extractLastWordWithPunctuationMarks(e.composition, e.inputMethod.Keys)
_, tmp = extractLastWordWithPunctuationMarks(e.composition, keys)
return Flatten(tmp, VietnameseMode)
} else {
_, tmp = extractLastWord(e.composition, e.inputMethod.Keys)
_, tmp = extractLastWord(e.composition, keys)
}
return Flatten(tmp, mode)
}

func (e *BambooEngine) isBracketEnabled() bool {
return (e.bracketTransformMode == BracketTransformEverywhere) ||
(e.bracketTransformMode == BracketTransformNonStart && len(e.composition) > 0) ||
(e.bracketTransformMode == BracketTransformFollowFlags && e.flags&EbracketTransformEnabled != 0)
}

func (e *BambooEngine) getApplicableRules(key rune) []Rule {
var applicableRules []Rule
for _, inputRule := range e.inputMethod.Rules {
Expand All @@ -130,7 +164,16 @@ func (e *BambooEngine) findTargetByKey(composition []*Transformation, key rune)
}

func (e *BambooEngine) CanProcessKey(key rune) bool {
return canProcessKey(key, e.inputMethod.Keys)
if canProcessKey(key, e.inputMethod.Keys) {
return true
}
if e.isBracketEnabled() {
lowerKey := unicode.ToLower(key)
if lowerKey == '[' || lowerKey == ']' || lowerKey == '{' || lowerKey == '}' {
return true
}
}
return false
}

func (e *BambooEngine) generateTransformations(composition []*Transformation, lowerKey rune, isUpperCase bool) []*Transformation {
Expand All @@ -139,29 +182,68 @@ func (e *BambooEngine) generateTransformations(composition []*Transformation, lo
// 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)

// Unified Modular W2U Logic
canApplyW2U := (e.w2uMode == W2uEverywhere) ||
(e.w2uMode == W2uNonStart && len(composition) > 0) ||
(e.w2uMode == W2uFollowFlags && e.flags&Ew2uEnabled != 0)

if canApplyW2U && lowerKey == 'w' && len(transformations) > 0 {
if transformations[0].Rule.Result == 'w' {
transformations[0].Rule.Result = 'ư'
transformations[0].Rule.EffectOn = 'ư'
} else if transformations[0].Rule.Result == 'W' {
transformations[0].Rule.Result = 'Ư'
transformations[0].Rule.EffectOn = 'Ư'
}

if e.isBracketEnabled() {
if len(composition) > 0 {
lastTrans := composition[len(composition)-1]
if (lowerKey == '[' || lowerKey == '{') && (lastTrans.Rule.Key == '[' || lastTrans.Rule.Key == '{') && (lastTrans.Rule.Result == 'ơ' || lastTrans.Rule.Result == 'Ơ') {
return []*Transformation{{
Target: lastTrans,
Rule: Rule{
EffectType: MarkTransformation,
Effect: uint8(MarkRaw),
},
}}
}
if (lowerKey == ']' || lowerKey == '}') && (lastTrans.Rule.Key == ']' || lastTrans.Rule.Key == '}') && (lastTrans.Rule.Result == 'ư' || lastTrans.Rule.Result == 'Ư') {
return []*Transformation{{
Target: lastTrans,
Rule: Rule{
EffectType: MarkTransformation,
Effect: uint8(MarkRaw),
},
}}
}
}
if (lowerKey == '[' || lowerKey == '{') && len(transformations) > 0 && (transformations[0].Rule.Result == '[' || transformations[0].Rule.Result == '{') {
transformations[0].Rule.Result = 'ơ'
transformations[0].Rule.EffectOn = 'ơ'
if lowerKey == '{' {
transformations[0].IsUpperCase = true
}
}
var newComposition = append(composition, transformations...)
if (lowerKey == ']' || lowerKey == '}') && len(transformations) > 0 && (transformations[0].Rule.Result == ']' || transformations[0].Rule.Result == '}') {
transformations[0].Rule.Result = 'ư'
transformations[0].Rule.EffectOn = 'ư'
if lowerKey == '}' {
transformations[0].IsUpperCase = true
}
}
}

// 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)
// Keep old W2U logic for backward compatibility
canApplyW2U := (e.w2uMode == W2uEverywhere) ||
(e.w2uMode == W2uNonStart && len(composition) > 0) ||
(e.w2uMode == W2uFollowFlags && e.flags&Ew2uEnabled != 0)

if canApplyW2U && lowerKey == 'w' && len(transformations) > 0 {
switch transformations[0].Rule.Result {
case 'w':
transformations[0].Rule.Result = 'ư'
transformations[0].Rule.EffectOn = 'ư'
case 'W':
transformations[0].Rule.Result = 'Ư'
transformations[0].Rule.EffectOn = 'Ư'
}
}
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
*
Expand All @@ -172,7 +254,6 @@ func (e *BambooEngine) generateTransformations(composition []*Transformation, lo
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)
Expand Down
90 changes: 82 additions & 8 deletions bamboo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import (
)

func newStdEngine() IEngine {
var im = ParseInputMethod(InputMethodDefinitions, "Telex 2")
return NewEngine(im, EstdFlags)
var im = ParseInputMethod(InputMethodDefinitions, "Telex")
var ng = NewEngine(im, EstdFlags)
ng.SetBracketTransformMode(BracketTransformEverywhere)
return ng
}

func TestProcessString(t *testing.T) {
Expand Down Expand Up @@ -171,16 +173,17 @@ func TestTelex23(t *testing.T) {
if ng.GetProcessedString(VietnameseMode) != "]a" {
t.Errorf("Process ]aa, got %s valid=%v expected true", ng.GetProcessedString(VietnameseMode), ng.IsValid(true))
}
var im = ParseInputMethod(InputMethodDefinitions, "Telex 2")
var im = ParseInputMethod(InputMethodDefinitions, "Telex")
var ng = NewEngine(im, EstdFlags)
ng.SetBracketTransformMode(BracketTransformEverywhere)
ng.ProcessString("[", VietnameseMode)
if ng.GetProcessedString(VietnameseMode) != "ơ" {
t.Errorf("Process Telex 2 [[], got [%v] expected [ươ]", ng.GetProcessedString(VietnameseMode))
t.Errorf("Process BracketTransform [[], got [%v] expected [ươ]", ng.GetProcessedString(VietnameseMode))
}
ng.Reset()
ng.ProcessString("{", VietnameseMode)
if ng.GetProcessedString(VietnameseMode) != "Ơ" {
t.Errorf("Process Telex 2 [{], got [%s] expected [Ơ]", ng.GetProcessedString(VietnameseMode))
t.Errorf("Process BracketTransform [{], got [%s] expected [Ơ]", ng.GetProcessedString(VietnameseMode))
}
}

Expand Down Expand Up @@ -228,7 +231,7 @@ func TestProcessAlooString(t *testing.T) {

func TestSpellingCheckForGiư(t *testing.T) {
ng := newStdEngine()
// In Telex 2, ']' is used for 'ư'
// With BracketTransform, ']' is used for 'ư'
ng.ProcessString("gi]", VietnameseMode)
if ng.IsValid(false) == false {
t.Errorf("Process gi], got [%v] expected [%v]", ng.IsValid(false) == false, true)
Expand Down Expand Up @@ -358,8 +361,9 @@ func TestProcessTnoss(t *testing.T) {

//ềng
func TestProcessEenghf(t *testing.T) {
var im = ParseInputMethod(InputMethodDefinitions, "Telex 2")
var im = ParseInputMethod(InputMethodDefinitions, "Telex")
ng := NewEngine(im, EstdFlags)
ng.SetBracketTransformMode(BracketTransformEverywhere)
ng.ProcessString("ddawks", VietnameseMode)
if ng.GetProcessedString(VietnameseMode) != "đắk" {
t.Errorf("Process eenghf, got [%v] expected [đắk]", ng.GetProcessedString(VietnameseMode))
Expand Down Expand Up @@ -414,8 +418,9 @@ func TestProcesshuoswc(t *testing.T) {

//choas, bieecs, uese
func TestProcesschoas(t *testing.T) {
var im = ParseInputMethod(InputMethodDefinitions, "Telex 2")
var im = ParseInputMethod(InputMethodDefinitions, "Telex")
ng := NewEngine(im, EstdFlags&^EstdToneStyle)
ng.SetBracketTransformMode(BracketTransformEverywhere)
ng.ProcessString("choas", VietnameseMode)
if ng.GetProcessedString(VietnameseMode) != "choá" {
t.Errorf("Process [choas], got [%v] expected [choá]", ng.GetProcessedString(VietnameseMode))
Expand Down Expand Up @@ -694,3 +699,72 @@ func BenchmarkRemoveLastChar(b *testing.B) {
}
}
}

func TestBracketTransformGlobal(t *testing.T) {
var im = ParseInputMethod(InputMethodDefinitions, "Telex")
var ng = NewEngine(im, EstdFlags)
ng.SetBracketTransformMode(BracketTransformEverywhere)

ng.ProcessString("[", VietnameseMode)
if ng.GetProcessedString(VietnameseMode | FullText) != "ơ" {
t.Errorf("BracketTransform (Everywhere): [ expected ơ, got %s", ng.GetProcessedString(VietnameseMode | FullText))
}
ng.Reset()
ng.ProcessString("]", VietnameseMode)
if ng.GetProcessedString(VietnameseMode | FullText) != "ư" {
t.Errorf("BracketTransform (Everywhere): ] expected ư, got %s", ng.GetProcessedString(VietnameseMode | FullText))
}

ng.Reset()
ng.SetBracketTransformMode(BracketTransformNonStart)
ng.ProcessString("[", VietnameseMode)
// At start of word, [ should stay [
if ng.GetProcessedString(VietnameseMode | FullText) != "[" {
t.Errorf("BracketTransform (NonStart) at start: [ expected [, got %s", ng.GetProcessedString(VietnameseMode | FullText))
}
ng.Reset()
ng.ProcessString("a[", VietnameseMode)
// After a, [ should become ơ
if ng.GetProcessedString(VietnameseMode | FullText) != "aơ" {
t.Errorf("BracketTransform (NonStart) after a: [ expected aơ, got %s", ng.GetProcessedString(VietnameseMode | FullText))
}

// Test double typing to cancel
ng.Reset()
ng.SetBracketTransformMode(BracketTransformEverywhere)
ng.ProcessString("[[", VietnameseMode)
if ng.GetProcessedString(VietnameseMode | FullText) != "[" {
t.Errorf("BracketTransform (Everywhere) double typing: [[ expected [, got %s", ng.GetProcessedString(VietnameseMode | FullText))
}
}

func TestBracketTransformWithTone(t *testing.T) {
var im = ParseInputMethod(InputMethodDefinitions, "Telex")
var ng = NewEngine(im, EstdFlags)
ng.SetBracketTransformMode(BracketTransformEverywhere)

// Test m[f -> mờ
ng.ProcessString("m[f", VietnameseMode)
if ng.GetProcessedString(VietnameseMode) != "mờ" {
t.Errorf("BracketTransform + Tone: m[f expected mờ, got %s", ng.GetProcessedString(VietnameseMode))
}

// Test d]s -> dứ
ng.Reset()
ng.ProcessString("d]s", VietnameseMode)
if ng.GetProcessedString(VietnameseMode) != "dứ" {
t.Errorf("BracketTransform + Tone: d]s expected dứ, got %s", ng.GetProcessedString(VietnameseMode))
}
}

func TestBracketDisabledWithTone(t *testing.T) {
var im = ParseInputMethod(InputMethodDefinitions, "Telex")
var ng = NewEngine(im, EstdFlags)
ng.SetBracketTransformMode(BracketTransformDisabled)

// Test m[f -> m[f (since [ is literal and not a vowel, f remains f)
ng.ProcessString("m[f", VietnameseMode)
if ng.GetProcessedString(VietnameseMode | FullText) != "m[f" {
t.Errorf("Bracket Disabled + Tone: m[f expected m[f, got %s", ng.GetProcessedString(VietnameseMode | FullText))
}
}
2 changes: 1 addition & 1 deletion bamboo_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ var regUhO = regexp.MustCompile(`(ưo|ươ)`)
**/
func generateTransformations(composition []*Transformation, applicableRules []Rule, flags uint, lowerKey rune, isUpperCase bool) []*Transformation {
var transformations []*Transformation
// Double typing an effect key undoes it and its effects, e.g. w + w -> w (Telex 2)
// Double typing an effect key undoes it and its effects, e.g. w + w -> w
if len(composition) > 0 {
var rule = composition[len(composition)-1].Rule
if rule.EffectType == Appending && rule.Key == lowerKey && rule.Key != rule.Result {
Expand Down
Loading
Loading