package parser type BlockType string const ( BlockData BlockType = "data" // content between lines "{" and "}" BlockMatching BlockType = "matching" // everything outside data blocks ) type Block struct { Type BlockType Start, End int // byte offsets [Start, End) src *string } func (b Block) GetContent() string { if b.src == nil || b.Start < 0 || b.End > len(*b.src) || b.Start > b.End { return "" } return (*b.src)[b.Start:b.End] } // ExtractBlocks scans once, emitting: // - data blocks: inner content between a line that's exactly "{" and a line that's exactly "}" // - matching blocks: gaps between data blocks (excluding the brace lines themselves) func ExtractBlocks(src string) []Block { var out []Block var curlyIndex int const CLOSING = '}' const OPENING = '{' if len(src) > 0 && src[0] == OPENING { curlyIndex = 1 out = append(out, Block{ Start: 0, Type: BlockData, src: &src, }) } else { out = append(out, Block{ Start: 0, Type: BlockMatching, src: &src, }) } for i, r := range src { var nextCurlyIndex = curlyIndex switch r { case OPENING: nextCurlyIndex++ case CLOSING: nextCurlyIndex-- } var nextChar rune = ' ' if i+1 < len(src) { nextChar = rune(src[i+1]) } if curlyIndex == 0 && nextCurlyIndex == 1 { out[len(out)-1].End = i out = append(out, Block{ Start: i, Type: BlockData, src: &src, }) } else if curlyIndex == 1 && nextCurlyIndex == 0 { out[len(out)-1].End = i + 1 if nextChar == OPENING { out = append(out, Block{ Start: i + 1, Type: BlockData, src: &src, }) } else { out = append(out, Block{ Start: i + 1, Type: BlockMatching, src: &src, }) } } curlyIndex = nextCurlyIndex } var lastBlock = out[len(out)-1] if lastBlock.End == 0 { out = out[:len(out)-1] } return out }