| /* |
| Based on the "jsonpath" spec/concept. |
| |
| http://goessner.net/articles/JsonPath/ |
| https://code.google.com/p/json-path/ |
| */ |
| |
| package toml |
| |
| import ( |
| "fmt" |
| ) |
| |
| const maxInt = int(^uint(0) >> 1) |
| |
| type queryParser struct { |
| flow chan token |
| tokensBuffer []token |
| query *Query |
| union []pathFn |
| err error |
| } |
| |
| type queryParserStateFn func() queryParserStateFn |
| |
| // Formats and panics an error message based on a token |
| func (p *queryParser) parseError(tok *token, msg string, args ...interface{}) queryParserStateFn { |
| p.err = fmt.Errorf(tok.Position.String()+": "+msg, args...) |
| return nil // trigger parse to end |
| } |
| |
| func (p *queryParser) run() { |
| for state := p.parseStart; state != nil; { |
| state = state() |
| } |
| } |
| |
| func (p *queryParser) backup(tok *token) { |
| p.tokensBuffer = append(p.tokensBuffer, *tok) |
| } |
| |
| func (p *queryParser) peek() *token { |
| if len(p.tokensBuffer) != 0 { |
| return &(p.tokensBuffer[0]) |
| } |
| |
| tok, ok := <-p.flow |
| if !ok { |
| return nil |
| } |
| p.backup(&tok) |
| return &tok |
| } |
| |
| func (p *queryParser) lookahead(types ...tokenType) bool { |
| result := true |
| buffer := []token{} |
| |
| for _, typ := range types { |
| tok := p.getToken() |
| if tok == nil { |
| result = false |
| break |
| } |
| buffer = append(buffer, *tok) |
| if tok.typ != typ { |
| result = false |
| break |
| } |
| } |
| // add the tokens back to the buffer, and return |
| p.tokensBuffer = append(p.tokensBuffer, buffer...) |
| return result |
| } |
| |
| func (p *queryParser) getToken() *token { |
| if len(p.tokensBuffer) != 0 { |
| tok := p.tokensBuffer[0] |
| p.tokensBuffer = p.tokensBuffer[1:] |
| return &tok |
| } |
| tok, ok := <-p.flow |
| if !ok { |
| return nil |
| } |
| return &tok |
| } |
| |
| func (p *queryParser) parseStart() queryParserStateFn { |
| tok := p.getToken() |
| |
| if tok == nil || tok.typ == tokenEOF { |
| return nil |
| } |
| |
| if tok.typ != tokenDollar { |
| return p.parseError(tok, "Expected '$' at start of expression") |
| } |
| |
| return p.parseMatchExpr |
| } |
| |
| // handle '.' prefix, '[]', and '..' |
| func (p *queryParser) parseMatchExpr() queryParserStateFn { |
| tok := p.getToken() |
| switch tok.typ { |
| case tokenDotDot: |
| p.query.appendPath(&matchRecursiveFn{}) |
| // nested parse for '..' |
| tok := p.getToken() |
| switch tok.typ { |
| case tokenKey: |
| p.query.appendPath(newMatchKeyFn(tok.val)) |
| return p.parseMatchExpr |
| case tokenLeftBracket: |
| return p.parseBracketExpr |
| case tokenStar: |
| // do nothing - the recursive predicate is enough |
| return p.parseMatchExpr |
| } |
| |
| case tokenDot: |
| // nested parse for '.' |
| tok := p.getToken() |
| switch tok.typ { |
| case tokenKey: |
| p.query.appendPath(newMatchKeyFn(tok.val)) |
| return p.parseMatchExpr |
| case tokenStar: |
| p.query.appendPath(&matchAnyFn{}) |
| return p.parseMatchExpr |
| } |
| |
| case tokenLeftBracket: |
| return p.parseBracketExpr |
| |
| case tokenEOF: |
| return nil // allow EOF at this stage |
| } |
| return p.parseError(tok, "expected match expression") |
| } |
| |
| func (p *queryParser) parseBracketExpr() queryParserStateFn { |
| if p.lookahead(tokenInteger, tokenColon) { |
| return p.parseSliceExpr |
| } |
| if p.peek().typ == tokenColon { |
| return p.parseSliceExpr |
| } |
| return p.parseUnionExpr |
| } |
| |
| func (p *queryParser) parseUnionExpr() queryParserStateFn { |
| var tok *token |
| |
| // this state can be traversed after some sub-expressions |
| // so be careful when setting up state in the parser |
| if p.union == nil { |
| p.union = []pathFn{} |
| } |
| |
| loop: // labeled loop for easy breaking |
| for { |
| if len(p.union) > 0 { |
| // parse delimiter or terminator |
| tok = p.getToken() |
| switch tok.typ { |
| case tokenComma: |
| // do nothing |
| case tokenRightBracket: |
| break loop |
| default: |
| return p.parseError(tok, "expected ',' or ']', not '%s'", tok.val) |
| } |
| } |
| |
| // parse sub expression |
| tok = p.getToken() |
| switch tok.typ { |
| case tokenInteger: |
| p.union = append(p.union, newMatchIndexFn(tok.Int())) |
| case tokenKey: |
| p.union = append(p.union, newMatchKeyFn(tok.val)) |
| case tokenString: |
| p.union = append(p.union, newMatchKeyFn(tok.val)) |
| case tokenQuestion: |
| return p.parseFilterExpr |
| default: |
| return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union)) |
| } |
| } |
| |
| // if there is only one sub-expression, use that instead |
| if len(p.union) == 1 { |
| p.query.appendPath(p.union[0]) |
| } else { |
| p.query.appendPath(&matchUnionFn{p.union}) |
| } |
| |
| p.union = nil // clear out state |
| return p.parseMatchExpr |
| } |
| |
| func (p *queryParser) parseSliceExpr() queryParserStateFn { |
| // init slice to grab all elements |
| start, end, step := 0, maxInt, 1 |
| |
| // parse optional start |
| tok := p.getToken() |
| if tok.typ == tokenInteger { |
| start = tok.Int() |
| tok = p.getToken() |
| } |
| if tok.typ != tokenColon { |
| return p.parseError(tok, "expected ':'") |
| } |
| |
| // parse optional end |
| tok = p.getToken() |
| if tok.typ == tokenInteger { |
| end = tok.Int() |
| tok = p.getToken() |
| } |
| if tok.typ == tokenRightBracket { |
| p.query.appendPath(newMatchSliceFn(start, end, step)) |
| return p.parseMatchExpr |
| } |
| if tok.typ != tokenColon { |
| return p.parseError(tok, "expected ']' or ':'") |
| } |
| |
| // parse optional step |
| tok = p.getToken() |
| if tok.typ == tokenInteger { |
| step = tok.Int() |
| if step < 0 { |
| return p.parseError(tok, "step must be a positive value") |
| } |
| tok = p.getToken() |
| } |
| if tok.typ != tokenRightBracket { |
| return p.parseError(tok, "expected ']'") |
| } |
| |
| p.query.appendPath(newMatchSliceFn(start, end, step)) |
| return p.parseMatchExpr |
| } |
| |
| func (p *queryParser) parseFilterExpr() queryParserStateFn { |
| tok := p.getToken() |
| if tok.typ != tokenLeftParen { |
| return p.parseError(tok, "expected left-parenthesis for filter expression") |
| } |
| tok = p.getToken() |
| if tok.typ != tokenKey && tok.typ != tokenString { |
| return p.parseError(tok, "expected key or string for filter funciton name") |
| } |
| name := tok.val |
| tok = p.getToken() |
| if tok.typ != tokenRightParen { |
| return p.parseError(tok, "expected right-parenthesis for filter expression") |
| } |
| p.union = append(p.union, newMatchFilterFn(name, tok.Position)) |
| return p.parseUnionExpr |
| } |
| |
| func parseQuery(flow chan token) (*Query, error) { |
| parser := &queryParser{ |
| flow: flow, |
| tokensBuffer: []token{}, |
| query: newQuery(), |
| } |
| parser.run() |
| return parser.query, parser.err |
| } |