balanced string, frontier pattern, gsub_with and their tests added

This commit is contained in:
skytrias
2022-12-18 23:11:23 +01:00
parent bd3596f012
commit 70bd220f34
2 changed files with 140 additions and 77 deletions
+92 -64
View File
@@ -19,6 +19,7 @@ Error :: enum {
Invalid_Capture_Index, Invalid_Capture_Index,
Invalid_Pattern_Capture, Invalid_Pattern_Capture,
Unfinished_Capture, Unfinished_Capture,
Malformed_Pattern,
} }
L_ESC :: '%' L_ESC :: '%'
@@ -143,20 +144,22 @@ classend :: proc(ms: ^MatchState, p: int) -> (int, Error) {
p += 1 p += 1
} }
// TODO double check for ms.pattern[p] != ']' {
for { // if p == len(ms.pattern) {
ch := ms.pattern[p] // return 0, .Malformed_Pattern
// }
if ch == L_ESC && p <= len(ms.pattern) { ch := ms.pattern[p]
p += 1
if p < len(ms.pattern) && ch == L_ESC {
// skip escapes like '%' // skip escapes like '%'
p += 1 p += 1
} }
if ms.pattern[p] == ']' { // if ms.pattern[p] == ']' {
break // break
} // }
p += 1
} }
return p + 1, .OK return p + 1, .OK
@@ -183,13 +186,14 @@ matchbracketclass :: proc(ms: ^MatchState, c: u8, p, ec: int) -> bool {
for p < ec { for p < ec {
ch := ms.pattern[p] ch := ms.pattern[p]
// e.g. %a
if ms.pattern[p] == L_ESC { if ms.pattern[p] == L_ESC {
p += 1 p += 1
if match_class(c, ms.pattern[p]) { if match_class(c, ms.pattern[p]) {
return sig return sig
} }
} else if ms.pattern[p + 1] == '-' && p + 2 < len(ms.pattern) { } else if p + 2 < len(ms.pattern) && ms.pattern[p + 1] == '-' {
// e.g. [a-z] check // e.g. [a-z] check
if ms.pattern[p] <= c && c <= ms.pattern[p + 2] { if ms.pattern[p] <= c && c <= ms.pattern[p + 2] {
return sig return sig
@@ -219,39 +223,40 @@ singlematch :: proc(ms: ^MatchState, s, p, ep: int) -> bool {
} }
} }
// matchbalance :: proc(ms: ^MatchState, s, p: int) -> (int, Error) { matchbalance :: proc(ms: ^MatchState, s, p: int) -> (int, Error) {
// s_begin := s if p >= len(ms.pattern) - 1 {
// s := s + 1 return INVALID, .Invalid_Pattern_Capture
// cont := 0 }
// begin := ms.pattern[p] // skip until the src and pattern match
// end := ms.pattern[p + 1] if ms.src[s] != ms.pattern[p] {
// print("BALANCED between", rune(begin), "AND", rune(end)) return INVALID, .OK
}
// for s < len(ms.src) { s_begin := s
// ch := ms.src[s] cont := 1
// print("\t", rune(ch)) s := s + 1
begin := ms.pattern[p]
end := ms.pattern[p + 1]
// if ch == end { for s < len(ms.src) {
// cont -= 1 ch := ms.src[s]
// print("END", cont)
// if cont == 0 { if ch == end {
// print("BALANCED RET", s + 1, len(ms.src), ms.src[s_begin:s + 1]) cont -= 1
// return s + 1
// }
// } else if ch == begin {
// cont += 1
// print("BEGIN", cont)
// }
// s += 1 if cont == 0 {
// } return s + 1, .OK
}
} else if ch == begin {
cont += 1
}
// print("OUT OF BALANCE", cont) s += 1
// // out of balance }
// return 0, .
// } return INVALID, .OK
}
max_expand :: proc(ms: ^MatchState, s, p, ep: int) -> (res: int, err: Error) { max_expand :: proc(ms: ^MatchState, s, p, ep: int) -> (res: int, err: Error) {
i := 0 i := 0
@@ -263,7 +268,6 @@ max_expand :: proc(ms: ^MatchState, s, p, ep: int) -> (res: int, err: Error) {
result := match(ms, s + i, ep + 1) or_return result := match(ms, s + i, ep + 1) or_return
if result != INVALID { if result != INVALID {
// print("SET", result)
return result, .OK return result, .OK
} }
@@ -368,35 +372,34 @@ match :: proc(ms: ^MatchState, s, p: int) -> (unused: int, err: Error) {
switch ms.pattern[p + 1] { switch ms.pattern[p + 1] {
// balanced string // balanced string
case 'b': { case 'b': {
// res := matchbalance(ms, s, p + 2) s = matchbalance(ms, s, p + 2) or_return
// if data, ok := res.?; ok {
// // s = data
// // eg after %b()
// // print("SUCCESS")
// return patt_match(ms, s, p + 4)
// }
if s != INVALID {
// eg after %b()
return match(ms, s, p + 4)
}
} }
// frontier // frontier
case 'f': { case 'f': {
// p += 2 p += 2
// if ms.pattern[p] != '[' { if ms.pattern[p] != '[' {
// print("missing '[' after %f in pattern") return INVALID, .Invalid_Pattern_Capture
// return nil }
// }
// ep := classend(ms, p).? ep := classend(ms, p) or_return
// previous := 0 if s == 0 else s - 1 previous := s == 0 ? '\x00' : ms.src[s - 1]
// allow last character to count too
current := s >= len(ms.src) ? '\x00' : ms.src[s]
// if !matchbracketclass(ms, ms.src[previous], p, ep - 1) && // fmt.eprintln("TRY", rune(ms.src[s]), ep)
// matchbracketclass(ms, ms.src[s], p, ep) { if !matchbracketclass(ms, previous, p, ep - 1) &&
// return patt_match(ms, s, ep) matchbracketclass(ms, current, p, ep - 1) {
// } return match(ms, s, ep)
}
// return nil s = INVALID
} }
// capture group // capture group
@@ -416,7 +419,6 @@ match :: proc(ms: ^MatchState, s, p: int) -> (unused: int, err: Error) {
case: { case: {
return match_default(ms, s, p) return match_default(ms, s, p)
// print("PATT DEF", rune(ms.src[s]), rune(ms.pattern[p]))
} }
} }
@@ -426,11 +428,9 @@ match :: proc(ms: ^MatchState, s, p: int) -> (unused: int, err: Error) {
match_default :: proc(ms: ^MatchState, s, p: int) -> (unused: int, err: Error) { match_default :: proc(ms: ^MatchState, s, p: int) -> (unused: int, err: Error) {
s := s s := s
ep := classend(ms, p) or_return ep := classend(ms, p) or_return
// ch := s < len(ms.src) ? rune(ms.src[s]) : 0
if !singlematch(ms, s, p, ep) { if !singlematch(ms, s, p, ep) {
epc := ep < len(ms.pattern) ? ms.pattern[ep] : 0 epc := ep < len(ms.pattern) ? ms.pattern[ep] : 0
// print("+++", rune(epc))
if epc == '*' || epc == '?' || epc == '-' { if epc == '*' || epc == '?' || epc == '-' {
return match(ms, s, ep + 1) return match(ms, s, ep + 1)
@@ -439,7 +439,6 @@ match_default :: proc(ms: ^MatchState, s, p: int) -> (unused: int, err: Error) {
} }
} else { } else {
epc := ep < len(ms.pattern) ? ms.pattern[ep] : 0 epc := ep < len(ms.pattern) ? ms.pattern[ep] : 0
// print("~~~", ch, rune(epc))
switch epc { switch epc {
case '?': { case '?': {
@@ -652,7 +651,7 @@ gmatch :: proc(
return return
} }
// gsub with builder // gsub with builder, replace patterns found with the replace content
gsub_builder :: proc( gsub_builder :: proc(
builder: ^strings.Builder, builder: ^strings.Builder,
haystack: string, haystack: string,
@@ -702,9 +701,38 @@ gsub_allocator :: proc(
return gsub_builder(&builder, haystack, pattern, replace) return gsub_builder(&builder, haystack, pattern, replace)
} }
// call a procedure on every match in the haystack
gsub_with :: proc(
haystack: string,
pattern: string,
data: rawptr,
call: proc(data: rawptr, word: string),
) {
// find matches
captures: [MAXCAPTURES]Match
haystack := haystack
for {
length, err := find_aux(haystack, pattern, 0, false, &captures)
// done
if length == 0 || err != .OK {
break
}
cap := captures[0]
word := haystack[cap.start:cap.end]
call(data, word)
// advance string till end
haystack = haystack[cap.end:]
}
}
gsub :: proc { gsub_builder, gsub_allocator } gsub :: proc { gsub_builder, gsub_allocator }
// iterative find with first capture only // iterative find with zeroth capture only
gfind :: proc( gfind :: proc(
haystack: ^string, haystack: ^string,
pattern: string, pattern: string,
+47 -12
View File
@@ -15,7 +15,7 @@ when ODIN_TEST {
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.printf("[%v] %v\n", loc, message) fmt.printf("%v %v\n", loc, message)
return return
} }
} }
@@ -166,6 +166,12 @@ test_match :: proc(t: ^testing.T) {
{ " testing this", "^testing", "", false }, { " testing this", "^testing", "", false },
{ "testing this", "^%w+", "testing", true }, { "testing this", "^%w+", "testing", true },
{ " testing this", "^%w+", "", false }, { " testing this", "^%w+", "", false },
// balanced string %b
{ "testing (this) out", "%b()", "(this)", true },
{ "testing athisz out", "%baz", "athisz", true },
{ "testing _this_ out", "%b__", "_this_", true },
{ "testing _this_ out", "%b_", "", false },
} }
captures: [lua.MAXCAPTURES]lua.Match captures: [lua.MAXCAPTURES]lua.Match
@@ -294,21 +300,49 @@ test_gsub :: proc(t: ^testing.T) {
@test @test
test_gfind :: proc(t: ^testing.T) { test_gfind :: proc(t: ^testing.T) {
{ haystack := "test1 123 test2 123 test3"
haystack := "test1 123 test2 123 test3" pattern := "%w+"
pattern := "%w+" captures: [lua.MAXCAPTURES]lua.Match
captures: [lua.MAXCAPTURES]lua.Match s := &haystack
s := &haystack output := [?]string { "test1", "123", "test2", "123", "test3" }
output := [?]string { "test1", "123", "test2", "123", "test3" } index: int
index: int
for word in lua.gfind(s, pattern, &captures) { for word in lua.gfind(s, pattern, &captures) {
expect(t, output[index] == word, fmt.tprintf("GFIND %d failed: %s != %s\n", index, output[index], word)) expect(t, output[index] == word, fmt.tprintf("GFIND %d failed: %s != %s\n", index, output[index], word))
index += 1 index += 1
}
} }
} }
test_frontier :: proc(t: ^testing.T) {
Temp :: struct {
t: ^testing.T,
index: int,
output: [3]string,
}
call :: proc(data: rawptr, word: string) {
temp := cast(^Temp) data
expect(
temp.t,
word == temp.output[temp.index],
fmt.tprintf("frontier temp didnt match: %s != %s\n", word, temp.output[temp.index]),
)
temp.index += 1
}
temp := Temp {
t = t,
output = {
"THE",
"QUICK",
"JUMPS",
},
}
// https://lua-users.org/wiki/FrontierPattern example taken from here
lua.gsub_with("THE (QUICK) brOWN FOx JUMPS", "%f[%a]%u+%f[%A]", &temp, call)
}
main :: proc() { main :: proc() {
t: testing.T t: testing.T
test_find(&t) test_find(&t)
@@ -317,6 +351,7 @@ main :: proc() {
test_gmatch(&t) test_gmatch(&t)
test_gsub(&t) test_gsub(&t)
test_gfind(&t) test_gfind(&t)
test_frontier(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 { if TEST_fail > 0 {