github.com/GuanceCloud/cliutils@v1.1.21/filter/parse.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package filter
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"reflect"
    13  	"regexp"
    14  	"runtime"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  
    19  	"github.com/GuanceCloud/cliutils/logger"
    20  	"github.com/prometheus/prometheus/util/strutil"
    21  )
    22  
    23  var (
    24  	parserPool = sync.Pool{
    25  		New: func() interface{} {
    26  			return &parser{}
    27  		},
    28  	}
    29  
    30  	log = logger.DefaultSLogger("filter-parser")
    31  )
    32  
    33  type parser struct {
    34  	lex      Lexer
    35  	yyParser yyParserImpl
    36  
    37  	parseResult interface{}
    38  	lastClosing Pos
    39  	errs        ParseErrors
    40  	warns       ParseErrors
    41  
    42  	inject    ItemType
    43  	injecting bool
    44  }
    45  
    46  func GetConds(input string) (WhereConditions, error) {
    47  	var err error
    48  	p := newParser(input)
    49  	defer parserPool.Put(p)
    50  	defer p.recover(&err)
    51  
    52  	p.doParse()
    53  
    54  	if len(p.errs) > 0 {
    55  		return nil, &p.errs[0]
    56  	}
    57  
    58  	return p.parseResult.(WhereConditions), nil
    59  }
    60  
    61  func newParser(input string) *parser {
    62  	p, ok := parserPool.Get().(*parser)
    63  	if !ok {
    64  		log.Fatal("parserPool: should not been here")
    65  	}
    66  
    67  	// reset parser fields
    68  	p.injecting = false
    69  	p.errs = nil
    70  	p.warns = nil
    71  	p.parseResult = nil
    72  	p.lex = Lexer{
    73  		input: input,
    74  		state: lexStatements,
    75  	}
    76  
    77  	return p
    78  }
    79  
    80  var errUnexpected = errors.New("unexpected error")
    81  
    82  func (p *parser) unexpected(context string, expected string) {
    83  	var errMsg strings.Builder
    84  
    85  	if p.yyParser.lval.item.Typ == ERROR { // do not report lex error twice
    86  		return
    87  	}
    88  
    89  	errMsg.WriteString("unexpected: ")
    90  	errMsg.WriteString(p.yyParser.lval.item.desc())
    91  
    92  	if context != "" {
    93  		errMsg.WriteString(" in: ")
    94  		errMsg.WriteString(context)
    95  	}
    96  
    97  	if expected != "" {
    98  		errMsg.WriteString(", expected: ")
    99  		errMsg.WriteString(expected)
   100  	}
   101  
   102  	p.addParseErr(p.yyParser.lval.item.PositionRange(), errors.New(errMsg.String()))
   103  }
   104  
   105  func (p *parser) recover(errp *error) {
   106  	e := recover() //nolint:ifshort
   107  	if _, ok := e.(runtime.Error); ok {
   108  		buf := make([]byte, 64<<10) // 64k
   109  		buf = buf[:runtime.Stack(buf, false)]
   110  		fmt.Fprintf(os.Stderr, "parser panic: %v\n%s", e, buf)
   111  		*errp = errUnexpected
   112  	} else if e != nil {
   113  		*errp = e.(error) //nolint:forcetypeassert
   114  	}
   115  }
   116  
   117  func (p *parser) addParseErr(pr *PositionRange, err error) {
   118  	p.errs = append(p.errs, ParseError{
   119  		Pos:   pr,
   120  		Err:   err,
   121  		Query: p.lex.input,
   122  	})
   123  }
   124  
   125  func (p *parser) addParseWarn(pr *PositionRange, err error) {
   126  	p.warns = append(p.warns, ParseError{
   127  		Pos:   pr,
   128  		Err:   err,
   129  		Query: p.lex.input,
   130  	})
   131  }
   132  
   133  func (p *parser) addParseErrf(pr *PositionRange, format string, args ...interface{}) {
   134  	p.addParseErr(pr, fmt.Errorf(format, args...))
   135  }
   136  
   137  func (p *parser) addParseWarnf(pr *PositionRange, format string, args ...interface{}) {
   138  	p.addParseWarn(pr, fmt.Errorf(format, args...))
   139  }
   140  
   141  // impl Lex interface.
   142  func (p *parser) Lex(lval *yySymType) int {
   143  	var typ ItemType
   144  
   145  	if p.injecting {
   146  		p.injecting = false
   147  		return int(p.inject)
   148  	}
   149  
   150  	for { // skip comment
   151  		p.lex.NextItem(&lval.item)
   152  		typ = lval.item.Typ
   153  		if typ != COMMENT {
   154  			break
   155  		}
   156  	}
   157  
   158  	switch typ {
   159  	case ERROR:
   160  		pos := PositionRange{
   161  			Start: p.lex.start,
   162  			End:   Pos(len(p.lex.input)),
   163  		}
   164  
   165  		p.addParseErr(&pos, errors.New(p.yyParser.lval.item.Val))
   166  		return 0 // tell yacc it's the end of input
   167  
   168  	case EOF:
   169  		lval.item.Typ = EOF
   170  		p.InjectItem(0)
   171  	case RIGHT_BRACE, RIGHT_PAREN, RIGHT_BRACKET, DURATION:
   172  		p.lastClosing = lval.item.Pos + Pos(len(lval.item.Val))
   173  	}
   174  	return int(typ)
   175  }
   176  
   177  func (p *parser) Error(e string) {}
   178  
   179  func (p *parser) unquoteString(s string) string {
   180  	unq, err := strutil.Unquote(s)
   181  	if err != nil {
   182  		p.addParseErrf(p.yyParser.lval.item.PositionRange(),
   183  			"error unquoting string %q: %s", s, err)
   184  	}
   185  	return unq
   186  }
   187  
   188  func (p *parser) doParse() {
   189  	p.InjectItem(START_WHERE_CONDITION)
   190  	p.yyParser.Parse(p)
   191  }
   192  
   193  func (p *parser) InjectItem(typ ItemType) {
   194  	if p.injecting {
   195  		log.Warnf("current inject is %v, new inject is %v", p.inject, typ)
   196  		panic("cannot inject multiple Items into the token stream")
   197  	}
   198  
   199  	if typ != 0 && (typ <= startSymbolsStart || typ >= startSymbolsEnd) {
   200  		log.Warnf("current inject is %v", typ)
   201  		panic("cannot inject symbol that isn't start symbol")
   202  	}
   203  	p.inject = typ
   204  	p.injecting = true
   205  }
   206  
   207  func (p *parser) number(v string) *NumberLiteral {
   208  	nl := &NumberLiteral{}
   209  
   210  	n, err := strconv.ParseInt(v, 0, 64)
   211  	if err != nil {
   212  		f, err := strconv.ParseFloat(v, 64)
   213  		if err != nil {
   214  			p.addParseErrf(p.yyParser.lval.item.PositionRange(),
   215  				"error parsing number: %s", err)
   216  		}
   217  		nl.Float = f
   218  	} else {
   219  		nl.IsInt = true
   220  		nl.Int = n
   221  	}
   222  
   223  	return nl
   224  }
   225  
   226  func doNewRegex(s string) (*Regex, error) {
   227  	re, err := regexp.Compile(s)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	return &Regex{
   232  		Regex: s,
   233  		Re:    re,
   234  	}, nil
   235  }
   236  
   237  func (p *parser) newRegex(s string) *Regex {
   238  	if x, err := doNewRegex(s); err != nil {
   239  		p.addParseWarnf(p.yyParser.lval.item.PositionRange(),
   240  			"invalid regex: %s: %s, ignored", err.Error(), s)
   241  		return nil
   242  	} else {
   243  		return x
   244  	}
   245  }
   246  
   247  func (p *parser) newBinExpr(l, r Node, op Item) *BinaryExpr {
   248  	switch op.Typ {
   249  	case DIV, MOD:
   250  		rightNumber, ok := r.(*NumberLiteral)
   251  		if ok {
   252  			if rightNumber.IsInt && rightNumber.Int == 0 ||
   253  				!rightNumber.IsInt && rightNumber.Float == 0 {
   254  				p.addParseErrf(p.yyParser.lval.item.PositionRange(), "division or modulo by zero")
   255  				return nil
   256  			}
   257  		}
   258  
   259  	case MATCH, NOT_MATCH: // convert rhs into regex list
   260  		switch nl := r.(type) {
   261  		case NodeList:
   262  			// convert elems in @n into Regex node, used in CONTAIN/NOTCONTAIN
   263  			var regexArr NodeList
   264  			for _, elem := range nl {
   265  				switch x := elem.(type) {
   266  				case *StringLiteral:
   267  					if re := p.newRegex(x.Val); re != nil {
   268  						regexArr = append(regexArr, re)
   269  					}
   270  
   271  				default:
   272  					p.addParseErrf(p.yyParser.lval.item.PositionRange(),
   273  						"invalid element type in CONTAIN/NOT_CONTAIN list: %s", reflect.TypeOf(elem).String())
   274  				}
   275  			}
   276  			return &BinaryExpr{LHS: l, RHS: regexArr, Op: op.Typ}
   277  
   278  		default:
   279  			p.addParseErrf(p.yyParser.lval.item.PositionRange(),
   280  				"invalid type in CONTAIN/NOT_CONTAIN list: %s(%s)", reflect.TypeOf(l).String(), l.String())
   281  		}
   282  	}
   283  
   284  	return &BinaryExpr{RHS: r, LHS: l, Op: op.Typ}
   285  }
   286  
   287  func (p *parser) newFunc(fname string, args []Node) *FuncExpr {
   288  	agg := &FuncExpr{
   289  		Name:  strings.ToLower(fname),
   290  		Param: args,
   291  	}
   292  	return agg
   293  }
   294  
   295  // end of yylex.(*parser).newXXXX
   296  
   297  type ParseErrors []ParseError
   298  
   299  type ParseError struct {
   300  	Pos        *PositionRange
   301  	Err        error
   302  	Query      string
   303  	LineOffset int
   304  }
   305  
   306  func (e *ParseError) Error() string {
   307  	if e.Pos == nil {
   308  		return fmt.Sprintf("%s", e.Err)
   309  	}
   310  
   311  	pos := int(e.Pos.Start)
   312  	lastLineBrk := -1
   313  	ln := e.LineOffset + 1
   314  	var posStr string
   315  
   316  	if pos < 0 || pos > len(e.Query) {
   317  		posStr = "invalid position:"
   318  	} else {
   319  		for i, c := range e.Query[:pos] {
   320  			if c == '\n' {
   321  				lastLineBrk = i
   322  				ln++
   323  			}
   324  		}
   325  
   326  		col := pos - lastLineBrk
   327  		posStr = fmt.Sprintf("%d:%d", ln, col)
   328  	}
   329  
   330  	return fmt.Sprintf("%s parse error: %s", posStr, e.Err)
   331  }
   332  
   333  // Error impl Error() interface.
   334  func (errs ParseErrors) Error() string {
   335  	var errArray []string
   336  	for _, err := range errs {
   337  		errStr := err.Error()
   338  		if errStr != "" {
   339  			errArray = append(errArray, errStr)
   340  		}
   341  	}
   342  
   343  	return strings.Join(errArray, "\n")
   344  }
   345  
   346  type PositionRange struct {
   347  	Start, End Pos
   348  }
   349  
   350  func (p *parser) newWhereConditions(conditions []Node) *WhereCondition {
   351  	return &WhereCondition{
   352  		conditions: conditions,
   353  	}
   354  }
   355  
   356  func Init() {
   357  	log = logger.SLogger("filter-parser")
   358  }