package tokenizer import ( "io" "unicode" "unicode/utf8" ) // DefaultChunkSize default chunk size for reader. const DefaultChunkSize = 4096 // parsing is main parser type parsing struct { t *Tokenizer curr byte pos int line int str []byte err error reader io.Reader token *Token head *Token ptr *Token tail []byte stopKeys []*tokenRef n int // tokens id generator chunkSize int // chunks size for infinite buffer offset int resume bool parsed int } // newParser creates new parser for string func newParser(t *Tokenizer, str []byte) *parsing { tok := t.allocToken() tok.line = 1 return &parsing{ t: t, str: str, line: 1, token: tok, } } func newInfParser(t *Tokenizer, reader io.Reader, bufferSize uint) *parsing { if bufferSize == 0 { bufferSize = DefaultChunkSize } buffer := make([]byte, bufferSize) tok := t.allocToken() tok.line = 1 return &parsing{ t: t, str: buffer, reader: reader, line: 1, chunkSize: int(bufferSize), token: tok, } } func (p *parsing) prev() { if p.pos > 0 { p.pos-- p.curr = p.str[p.pos] } } func (p *parsing) ensureBytes(n int) bool { if p.pos+n >= len(p.str) { if p.reader != nil { p.loadChunk() if p.pos+n < len(p.str) { return true } } return false } return true } func (p *parsing) next() { p.pos++ if p.pos >= len(p.str) { if p.reader == nil || p.loadChunk() == 0 { p.curr = 0 return } } p.curr = p.str[p.pos] } func (p *parsing) nextByte() byte { if p.ensureBytes(1) { return p.str[p.pos+1] } return 0 } func (p *parsing) slice(from, to int) []byte { if to < len(p.str) { return p.str[from:to] } return p.str[from:] } func (p *parsing) preload() { n, err := p.reader.Read(p.str) if n < p.chunkSize { p.str = p.str[:n] p.reader = nil } if err != nil { p.reader = nil if err != io.EOF { p.err = err } } } func (p *parsing) loadChunk() int { // chunk size = new chunk size + size of tail of prev chunk chunk := make([]byte, len(p.str)+p.chunkSize) copy(chunk, p.str) n, err := p.reader.Read(chunk[len(p.str):]) if n < p.chunkSize { p.str = chunk[:len(p.str)+n] p.reader = nil } else { p.str = chunk } if err != nil { p.reader = nil if err != io.EOF { p.err = err } } p.resume = false return n } // checkPoint reset internal values for next chunk of data func (p *parsing) checkPoint() bool { if p.pos > 0 { p.parsed += p.pos p.str = p.str[p.pos:] p.offset += p.pos p.pos = 0 if len(p.str) == 0 { p.curr = 0 } } return p.resume } // parse bytes (p.str) to tokens and append them to the end if stream of tokens. func (p *parsing) parse() { if len(p.str) == 0 { if p.reader == nil || p.loadChunk() == 0 { // if it's not infinite stream or this is the end of stream return } } p.curr = p.str[p.pos] p.resume = true for p.checkPoint() { if p.stopKeys != nil { for _, t := range p.stopKeys { if p.ptr.key == t.Key { return } } } p.parseWhitespace() if p.curr == 0 { break } if p.parseToken() { continue } if p.curr == 0 { break } if p.parseKeyword() { continue } if p.curr == 0 { break } if p.parseNumber() { continue } if p.curr == 0 { break } if p.parseQuote() { continue } if p.curr == 0 { break } if p.t.flags&fStopOnUnknown != 0 { break } p.token.key = TokenUnknown p.token.value = p.str[p.pos : p.pos+1] p.token.offset = p.offset + p.pos p.next() p.emmitToken() if p.curr == 0 { break } } if len(p.token.indent) > 0 { p.tail = p.token.indent } } func (p *parsing) parseWhitespace() bool { var start = -1 for p.curr != 0 { var matched = false for _, ws := range p.t.wSpaces { if p.curr == ws { if start == -1 { start = p.pos } matched = true break } } if !matched { break } if p.curr == newLine { p.line++ } p.next() } if start != -1 { p.token.line = p.line p.token.indent = p.str[start:p.pos] return true } return false } func (p *parsing) parseKeyword() bool { var start = -1 for p.curr != 0 { var r rune var size int p.ensureBytes(4) r, size = utf8.DecodeRune(p.slice(p.pos, p.pos+4)) if unicode.IsLetter(r) || (p.t.flags&fAllowKeywordUnderscore != 0 && p.curr == '_') || (p.t.flags&fAllowNumberInKeyword != 0 && start != -1 && isNumberByte(p.curr)) { if start == -1 { start = p.pos } p.pos += size - 1 // rune may be more than 1 byte } else { break } p.next() } if start != -1 { p.token.key = TokenKeyword p.token.value = p.str[start:p.pos] p.token.offset = p.offset + start p.emmitToken() return true } return false } const ( stageCoefficient = iota + 1 stageMantissa stagePower ) func (p *parsing) parseNumber() bool { var start = -1 var needNumber = true var stage uint8 = 0 for p.curr != 0 { if isNumberByte(p.curr) { needNumber = false if start == -1 { if stage == 0 { stage = stageCoefficient start = p.pos } } } else if p.t.flags&fAllowNumberUnderscore != 0 && p.curr == '_' { if stage != stageCoefficient { break } // todo checks double underscore } else if !needNumber && p.curr == '.' { if stage != stageCoefficient { break } stage = stageMantissa needNumber = true } else if !needNumber && (p.curr == 'e' || p.curr == 'E') { if stage != stageMantissa && stage != stageCoefficient { break } ePowSign := false switch p.nextByte() { case '-', '+': ePowSign = true p.next() } needNumber = true if isNumberByte(p.nextByte()) { stage = stagePower } else { if ePowSign { // rollback sign position p.prev() } break } } else { break } p.next() } if stage == 0 { return false } p.token.value = p.str[start:p.pos] if stage == stageCoefficient { p.token.key = TokenInteger p.token.offset = p.offset + start } else { p.token.key = TokenFloat p.token.offset = p.offset + start } p.emmitToken() return true } // match compare next bytes from data with `r` func (p *parsing) match(r []byte, seek bool) bool { if r[0] == p.curr { if len(r) > 1 { if p.ensureBytes(len(r) - 1) { var i = 1 for ; i < len(r); i++ { if r[i] != p.str[p.pos+i] { return false } } if seek { p.pos += i - 1 p.next() } return true } return false } if seek { p.next() } return true } return false } // parseQuote parses quoted string. func (p *parsing) parseQuote() bool { var quote *StringSettings var start = p.pos for _, q := range p.t.quotes { if p.match(q.StartToken, true) { quote = q break } } if quote == nil { return false } p.token.key = TokenString p.token.offset = p.offset + start p.token.string = quote escapes := false for p.curr != 0 { if escapes { escapes = false } else if p.curr == quote.EscapeSymbol { escapes = true } else if p.match(quote.EndToken, true) { break } else if quote.Injects != nil { loop := true for _, inject := range quote.Injects { for _, token := range p.t.tokens[inject.StartKey] { if p.match(token.Token, true) { p.token.key = TokenStringFragment p.token.value = p.str[start : p.pos-len(token.Token)] p.emmitToken() p.token.key = token.Key p.token.value = token.Token p.token.offset = p.offset + p.pos - len(token.Token) p.emmitToken() stopKeys := p.stopKeys // may be recursive quotes p.stopKeys = p.t.tokens[inject.EndKey] p.parse() p.stopKeys = stopKeys p.token.key = TokenStringFragment p.token.offset = p.offset + p.pos p.token.string = quote start = p.pos loop = false break } } if !loop { break } } } if p.curr == newLine { p.line++ } p.next() } p.token.value = p.str[start:p.pos] p.emmitToken() return true } // parseToken search any rune sequence from tokenItem. func (p *parsing) parseToken() bool { if p.curr != 0 { toks := p.t.index[p.curr] if toks != nil { start := p.pos for _, t := range toks { if p.match(t.Token, true) { p.token.key = t.Key p.token.offset = p.offset + start p.token.value = t.Token p.emmitToken() return true } } } } return false } // emmitToken add new p.token to stream func (p *parsing) emmitToken() { if p.ptr == nil { p.ptr = p.token p.head = p.ptr } else { p.ptr.addNext(p.token) p.ptr = p.token } p.n++ p.token = p.t.allocToken() p.token.id = p.n p.token.line = p.line }