forked from flashcat/categraf
116 lines
3.5 KiB
Go
116 lines
3.5 KiB
Go
// Copyright 2016 Google Inc. All Rights Reserved.
|
|
// This file is available under the Apache license.
|
|
|
|
// Build the parser:
|
|
//go:generate goyacc -v y.output -o parser.go -p mtail parser.y
|
|
|
|
// Package parser implements the parse phase of the mtail program compilation.
|
|
// The parser itself is defined in parser.y, and goyacc generates the program
|
|
// code and token definitions. The parser fetches tokens from the lexer, which
|
|
// scans the input converting the program source into a token stream. The
|
|
// driver code wraps the generated parser and marshals the ast and errors back
|
|
// to the caller.
|
|
//
|
|
// Two pretty-printers are used for debugging: the unparser, which converts an
|
|
// ast back into program text, and an approximation of an s-expression printer,
|
|
// which tries to model in indented text the structure of the ast.
|
|
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"flashcat.cloud/categraf/inputs/mtail/internal/runtime/compiler/ast"
|
|
"flashcat.cloud/categraf/inputs/mtail/internal/runtime/compiler/errors"
|
|
"flashcat.cloud/categraf/inputs/mtail/internal/runtime/compiler/position"
|
|
)
|
|
|
|
// Parse reads the program named name from the input, and if successful returns
|
|
// an ast.Node for the root of the AST, otherwise parser errors.
|
|
func Parse(name string, input io.Reader) (ast.Node, error) {
|
|
p := newParser(name, input)
|
|
r := mtailParse(p)
|
|
if r != 0 || p.errors != nil {
|
|
return nil, p.errors
|
|
}
|
|
return p.root, nil
|
|
}
|
|
|
|
// EOF is a marker for end of file. It has the same value as the goyacc internal Kind `$end`.
|
|
const EOF = 0
|
|
|
|
// parser defines the data structure for parsing an mtail program.
|
|
type parser struct {
|
|
name string
|
|
root ast.Node
|
|
errors errors.ErrorList
|
|
l *Lexer
|
|
t Token // Most recently lexed token.
|
|
pos position.Position // Optionally contains the position of the start of a production
|
|
}
|
|
|
|
func newParser(name string, input io.Reader) *parser {
|
|
return &parser{name: name, l: NewLexer(name, input)}
|
|
}
|
|
|
|
func (p *parser) ErrorP(s string, pos *position.Position) {
|
|
p.errors.Add(pos, s)
|
|
}
|
|
|
|
func (p *parser) Error(s string) {
|
|
p.errors.Add(&p.t.Pos, s)
|
|
}
|
|
|
|
// Lex reads the next token from the Lexer, turning it into a form useful for the goyacc generated parser.
|
|
// The variable lval is modified to carry token information, and the token type is returned.
|
|
func (p *parser) Lex(lval *mtailSymType) int {
|
|
p.t = p.l.NextToken()
|
|
switch p.t.Kind {
|
|
case INVALID:
|
|
p.Error(p.t.Spelling)
|
|
lval.text = p.t.Spelling
|
|
return INVALID
|
|
case INTLITERAL:
|
|
var err error
|
|
lval.intVal, err = strconv.ParseInt(p.t.Spelling, 10, 64)
|
|
if err != nil {
|
|
p.Error(fmt.Sprintf("bad number '%s': %s", p.t.Spelling, err))
|
|
return INVALID
|
|
}
|
|
case FLOATLITERAL:
|
|
var err error
|
|
lval.floatVal, err = strconv.ParseFloat(p.t.Spelling, 64)
|
|
if err != nil {
|
|
p.Error(fmt.Sprintf("bad number '%s': %s", p.t.Spelling, err))
|
|
return INVALID
|
|
}
|
|
case DURATIONLITERAL:
|
|
var err error
|
|
lval.duration, err = time.ParseDuration(p.t.Spelling)
|
|
if err != nil {
|
|
p.Error(fmt.Sprintf("%s", err))
|
|
return INVALID
|
|
}
|
|
case LT, GT, LE, GE, NE, EQ, SHL, SHR, BITAND, BITOR, AND, OR, XOR, NOT, INC, DEC, DIV, MUL, MINUS, PLUS, ASSIGN, ADD_ASSIGN, POW, MOD, MATCH, NOT_MATCH:
|
|
lval.op = int(p.t.Kind)
|
|
default:
|
|
lval.text = p.t.Spelling
|
|
}
|
|
return int(p.t.Kind)
|
|
}
|
|
|
|
func (p *parser) inRegex() {
|
|
// glog.V(2).Info("Entering regex")
|
|
p.l.InRegex = true
|
|
}
|
|
|
|
func init() {
|
|
// Initialise globals defined in generated parser.go, defaults to 0 and false
|
|
debug := os.Getenv("MTAIL_DEBUG")
|
|
mtailDebug, _ = strconv.Atoi(debug)
|
|
mtailErrorVerbose = true
|
|
}
|