package regexp2import ()// Match is a single regex result match that contains groups and repeated captures//// -Groups// -CapturetypeMatchstruct {Group//embeded group 0 regex *Regexp otherGroups []Group// input to the match textpos int textstart int capcount int sparseCaps map[int]int// output from the match matches [][]int matchcount []int// whether we've done any balancing with this match. If we // have done balancing, we'll need to do extra work in Tidy(). balancing bool}// Group is an explicit or implit (group 0) matched group within the patterntypeGroupstruct {Capture// the last capture of this group is embeded for ease of use Name string// group name Captures []Capture// captures of this group}// Capture is a single capture of text within the larger original stringtypeCapturestruct {// the original string text *matchText// RuneIndex is the position in the underlying rune slice where the first character of // captured substring was found. Even if you pass in a string this will be in Runes. RuneIndex int// RuneLength is the number of runes in the captured substring. RuneLength int}type matchText struct { runes []rune input string hasStringInput bool byteOffsets []int byteOffsetsReady bool}// String returns the captured text as a Stringfunc ( *Capture) () string {returnstring(.text.runes[.RuneIndex : .RuneIndex+.RuneLength])}// Runes returns the captured text as a rune slicefunc ( *Capture) () []rune {return .text.runes[.RuneIndex : .RuneIndex+.RuneLength]}// ByteRange returns the UTF-8 byte index and byte length of the captured// substring. The first call lazily caches byte offsets on shared match text,// so it is not safe to call concurrently with ByteRange on another capture// from the same match until the cache has been initialized.func ( *Capture) () (, int) {if .text == nil {return .RuneIndex, .RuneLength }return .text.byteRange(.RuneIndex, .RuneLength)}func newMatchText( []rune) *matchText {return &matchText{runes: }}func newStringMatchText( string, []rune) *matchText {return &matchText{runes: , input: , hasStringInput: true}}func ( *matchText) (, int) (int, int) {if !.byteOffsetsReady { .byteOffsets = .buildByteOffsets() .byteOffsetsReady = true }if .byteOffsets == nil {return , } := .byteOffsets[]return , .byteOffsets[+] - }func ( *matchText) () []int {if .hasStringInput {returnstringByteOffsets(.input) }returnruneByteOffsets(.runes)}func stringByteOffsets( string) []int {var []int := 0for , := range {if != nil { [] = } := utf8.RuneLen()if == utf8.RuneError { _, = utf8.DecodeRuneInString([:]) }if == nil && ( != || != 1) { = make([]int, len()+1)for := 0; < ; ++ { [] = } [] = } ++ }if != nil { [] = len()return [:+1] }returnnil}func runeByteOffsets( []rune) []int {var []int := 0for , := range {if != nil { [] = } := utf8.RuneLen()if < 0 { = utf8.RuneLen(utf8.RuneError) }if == nil && != 1 { = make([]int, len()+1)for := 0; < ; ++ { [] = } [] = } += }if != nil { [len()] = }return}func newMatch( *Regexp, int, *matchText, int) *Match { := Match{regex: ,matchcount: make([]int, ),matches: make([][]int, ),textstart: ,balancing: false, }if (.options & ECMAScript) == 0 { .Name = "0" } .text = .matches[0] = make([]int, 2)return &}func newMatchSparse( *Regexp, map[int]int, int, *matchText, int) *Match { := newMatch(, , , ) .sparseCaps = return}func ( *Match) ( *matchText, int) { .text = .textstart = for := 0; < len(.matchcount); ++ { .matchcount[] = 0 } .balancing = false}func ( *Match) ( int) { := .matches[0]setCaptureFields(&.Capture, [0], [1]) .textpos = .capcount = .matchcount[0]//copy our root capture to the list .Captures = []Capture{.Capture}if .balancing {// The idea here is that we want to compact all of our unbalanced captures. To do that we // use j basically as a count of how many unbalanced captures we have at any given time // (really j is an index, but j/2 is the count). First we skip past all of the real captures // until we find a balance captures. Then we check each subsequent entry. If it's a balance // capture (it's negative), we decrement j. If it's a real capture, we increment j and copy // it down to the last free position.for := 0; < len(.matchcount); ++ { := .matchcount[] * 2 := .matches[]var , intfor = 0; < ; ++ {if [] < 0 {break } }for = ; < ; ++ {if [] < 0 {// skip negative values -- } else {// but if we find something positive (an actual capture), copy it back to the last // unbalanced position.if != { [] = [] } ++ } } .matchcount[] = / 2 } .balancing = false }}// isMatched tells if a group was matched by capnumfunc ( *Match) ( int) bool {return < len(.matchcount) && .matchcount[] > 0 && .matches[][.matchcount[]*2-1] != (-3+1)}// matchIndex returns the index of the last specified matched group by capnumfunc ( *Match) ( int) int { := .matches[][.matchcount[]*2-2]if >= 0 {return }return .matches[][-3-]}// matchLength returns the length of the last specified matched group by capnumfunc ( *Match) ( int) int { := .matches[][.matchcount[]*2-1]if >= 0 {return }return .matches[][-3-]}// Nonpublic builder: add a capture to the group specified by "c"func ( *Match) (, , int) {if .matches[] == nil { .matches[] = make([]int, 2) } := .matchcount[]if *2+2 > len(.matches[]) { := .matches[] := make([]int, *8)copy(, [:*2]) .matches[] = } .matches[][*2] = .matches[][*2+1] = .matchcount[] = + 1//log.Printf("addMatch: c=%v, i=%v, l=%v ... matches: %v", c, start, l, m.matches)}// Nonpublic builder: Add a capture to balance the specified group. This is used by the//// balanced match construct. (?<foo-foo2>...)//// If there were no such thing as backtracking, this would be as simple as calling RemoveMatch(c).// However, since we have backtracking, we need to keep track of everything.func ( *Match) ( int) { .balancing = true// we'll look at the last capture first := .matchcount[] := *2 - 2// first see if it is negative, and therefore is a reference to the next available // capture group for balancing. If it is, we'll reset target to point to that capture.if .matches[][] < 0 { = -3 - .matches[][] }// move back to the previous capture -= 2// if the previous capture is a reference, just copy that reference to the end. Otherwise, point to it.if >= 0 && .matches[][] < 0 { .addMatch(, .matches[][], .matches[][+1]) } else { .addMatch(, -3-, -4- /* == -3 - (target + 1) */) }}// Nonpublic builder: removes a group match by capnumfunc ( *Match) ( int) { .matchcount[]--}// GroupCount returns the number of groups this match has matchedfunc ( *Match) () int {returnlen(.matchcount)}// GroupByName returns a group based on the name of the group, or nil if the group name does not existfunc ( *Match) ( string) *Group { := .regex.GroupNumberFromName()if < 0 {returnnil }return .GroupByNumber()}// GroupByNumber returns a group based on the number of the group, or nil if the group number does not existfunc ( *Match) ( int) *Group {// check our sparse mapif .sparseCaps != nil {if , := .sparseCaps[]; { = } }if >= len(.matchcount) || < 0 {returnnil }if == 0 {return &.Group } .populateOtherGroups()return &.otherGroups[-1]}// Groups returns all the capture groups, starting with group 0 (the full match)func ( *Match) () []Group { .populateOtherGroups() := make([]Group, len(.otherGroups)+1) [0] = .Groupcopy([1:], .otherGroups)return}func ( *Match) () {// Construct all the Group objects first time calledif .otherGroups == nil { .otherGroups = make([]Group, len(.matchcount)-1)for := 0; < len(.otherGroups); ++ { .otherGroups[] = newGroup(.regex.GroupNameFromNumber(+1), .text, .matches[+1], .matchcount[+1]) } }}func ( *Match) ( int, *bytes.Buffer) { := .matchcount[]if == 0 {return } := .matches[] := [(-1)*2] := + [(*2)-1]for ; < ; ++ { .WriteRune(.text.runes[]) }}func newGroup( string, *matchText, []int, int) Group { := Group{} .text = if > 0 {setCaptureFields(&.Capture, [(-1)*2], [(*2)-1]) } .Name = .Captures = make([]Capture, )for := 0; < ; ++ { .Captures[] = newCapture(, [*2], [*2+1]) }//log.Printf("newGroup! capcount %v, %+v", capcount, g)return}func newCapture( *matchText, , int) Capture { := Capture{text: }setCaptureFields(&, , )return}func setCaptureFields( *Capture, , int) { .RuneIndex = .RuneLength = }func ( *Match) () string { := &bytes.Buffer{} .WriteRune('\n')iflen(.sparseCaps) > 0 {for , := range .sparseCaps {fmt.Fprintf(, "Slot %v -> %v\n", , ) } }for , := range .Groups() {fmt.Fprintf(, "Group %v (%v), %v caps:\n", , .Name, len(.Captures))for , := range .Captures {fmt.Fprintf(, " (%v, %v) %v\n", .RuneIndex, .RuneLength, .String()) } }/* for i := 0; i < len(m.matchcount); i++ { fmt.Fprintf(buf, "\nGroup %v (%v):\n", i, m.regex.GroupNameFromNumber(i)) for j := 0; j < m.matchcount[i]; j++ { text := "" if m.matches[i][j*2] >= 0 { start := m.matches[i][j*2] text = m.text.runes[start : start+m.matches[i][j*2+1]] } fmt.Fprintf(buf, " (%v, %v) %v\n", m.matches[i][j*2], m.matches[i][j*2+1], text) } } */return .String()}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.