mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-05 15:18:49 +00:00
balanced string, frontier pattern, gsub_with and their tests added
This commit is contained in:
+93
-65
@@ -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]
|
||||||
|
|
||||||
if ms.pattern[p] == L_ESC {
|
// e.g. %a
|
||||||
|
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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user