Skip to content

Commit

Permalink
make parser context mutable
Browse files Browse the repository at this point in the history
  • Loading branch information
slugalisk committed Apr 13, 2020
1 parent 9daa117 commit 58ef669
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 42 deletions.
134 changes: 92 additions & 42 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,73 @@ package parser

import (
"sort"
"sync"
)

type ParserContextValues struct {
Emotes []string
EmoteModifiers []string
Nicks []string
Tags []string
func NewRuneIndex(values [][]rune) *RuneIndex {
sort.Sort(runeSlices(values))
return &RuneIndex{values: values}
}

func toRuneSlices(arr []string) (s [][]rune) {
s = make([][]rune, len(arr))
for i, v := range arr {
s[i] = []rune(v)
}
sort.Sort(runeSlices(s))
return
}

func compareRuneSlices(a, b []rune) int {
if len(a) != len(b) {
return len(a) - len(b)
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return int(a[i] - b[i])
}
}
return 0
type RuneIndex struct {
sync.Mutex
values [][]rune
}

func inRuneSlices(r [][]rune, v []rune) bool {
func (r *RuneIndex) findIndex(v []rune) int {
var min, mid int
max := len(r)
max := len(r.values)

for min != max {
mid = (max + min) >> 1
if compareRuneSlices(r[mid], v) < 0 {
if compareRuneSlices(r.values[mid], v) < 0 {
min = mid + 1
} else {
max = mid
}
}

return min != len(r) && compareRuneSlices(r[min], v) == 0
return min
}

func (r *RuneIndex) Contains(v []rune) bool {
r.Lock()
defer r.Unlock()

i := r.findIndex(v)
return i != len(r.values) && compareRuneSlices(r.values[i], v) == 0
}

func (r *RuneIndex) Insert(v []rune) {
r.Lock()
defer r.Unlock()

i := r.findIndex(v)
r.values = append(r.values, v)
if i != len(r.values)-1 {
copy(r.values[i+1:], r.values[i:])
r.values[i] = v
}
}

func (r *RuneIndex) Remove(v []rune) {
r.Lock()
defer r.Unlock()

i := r.findIndex(v)
if i != len(r.values) {
copy(r.values[i:], r.values[i+1:])
r.values = r.values[:len(r.values)-1]
}
}

func (r *RuneIndex) Replace(values [][]rune) {
sort.Sort(runeSlices(values))

r.Lock()
defer r.Unlock()

r.values = values
}

type runeSlices [][]rune
Expand All @@ -54,24 +77,51 @@ func (a runeSlices) Len() int { return len(a) }
func (a runeSlices) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a runeSlices) Less(i, j int) bool { return compareRuneSlices(a[i], a[j]) < 0 }

var meCmd = []rune("me")
func compareRuneSlices(a, b []rune) int {
if len(a) != len(b) {
return len(a) - len(b)
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return int(a[i] - b[i])
}
}
return 0
}

func RunesFromStrings(s []string) (r [][]rune) {
r = make([][]rune, len(s))
for i, v := range s {
r[i] = []rune(v)
}
return
}

type ParserContextValues struct {
Emotes []string
EmoteModifiers []string
Nicks []string
Tags []string
}

func NewParserContext(opt ParserContextValues) *ParserContext {
return &ParserContext{
emotes: toRuneSlices(opt.Emotes),
emoteModifiers: toRuneSlices(opt.EmoteModifiers),
nicks: toRuneSlices(opt.Nicks),
tags: toRuneSlices(opt.Tags),
Emotes: NewRuneIndex(RunesFromStrings(opt.Emotes)),
EmoteModifiers: NewRuneIndex(RunesFromStrings(opt.EmoteModifiers)),
Nicks: NewRuneIndex(RunesFromStrings(opt.Nicks)),
Tags: NewRuneIndex(RunesFromStrings(opt.Tags)),
}
}

type ParserContext struct {
emotes [][]rune
emoteModifiers [][]rune
nicks [][]rune
tags [][]rune
Emotes *RuneIndex
EmoteModifiers *RuneIndex
Nicks *RuneIndex
Tags *RuneIndex
}

var meCmd = []rune("me")

func NewParser(ctx *ParserContext, l lexer) *Parser {
return &Parser{
ctx: ctx,
Expand Down Expand Up @@ -110,7 +160,7 @@ func (p *Parser) parseEmote() (e *Emote) {
}
p.next()

if !inRuneSlices(p.ctx.emoteModifiers, p.lit) {
if !p.ctx.EmoteModifiers.Contains(p.lit) {
return
}
e.InsertModifier(string(p.lit))
Expand Down Expand Up @@ -146,7 +196,7 @@ func (p *Parser) tryParseAtNick() (n *Nick) {

p.next()

if !inRuneSlices(p.ctx.nicks, p.lit) {
if !p.ctx.Nicks.Contains(p.lit) {
return
}

Expand Down Expand Up @@ -216,11 +266,11 @@ func (p *Parser) parseSpan(t SpanType) (s *Span) {
s.Insert(n)
}
case tokWord:
if inRuneSlices(p.ctx.tags, p.lit) {
if p.ctx.Tags.Contains(p.lit) {
s.Insert(p.parseTag())
} else if inRuneSlices(p.ctx.emotes, p.lit) {
} else if p.ctx.Emotes.Contains(p.lit) {
s.Insert(p.parseEmote())
} else if inRuneSlices(p.ctx.nicks, p.lit) {
} else if p.ctx.Nicks.Contains(p.lit) {
s.Insert(p.parseNick())
} else {
p.next()
Expand Down
28 changes: 28 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,34 @@ func TestParse(t *testing.T) {
}
}

func TestRuneIndex(t *testing.T) {
v := NewRuneIndex(RunesFromStrings([]string{"g", "d", "a", "c", "f"}))

expected := [][]rune{{'a'}, {'c'}, {'d'}, {'f'}, {'g'}}
if !reflect.DeepEqual(expected, v.values) {
t.Error("new rune index should be sorted")
t.FailNow()
}

v.Insert([]rune("b"))
v.Insert([]rune("e"))

expected = [][]rune{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}, {'g'}}
if !reflect.DeepEqual(expected, v.values) {
t.Error("rune index should remain sorted after inserting values")
t.FailNow()
}

v.Remove([]rune("c"))
v.Remove([]rune("f"))

expected = [][]rune{{'a'}, {'b'}, {'d'}, {'e'}, {'g'}}
if !reflect.DeepEqual(expected, v.values) {
t.Error("rune index should remain sorted after inserting values")
t.FailNow()
}
}

func BenchmarkParse(b *testing.B) {
ctx := NewParserContext(ParserContextValues{
Emotes: []string{"PEPE", "CuckCrab"},
Expand Down

0 comments on commit 58ef669

Please sign in to comment.