Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Populate macro calls #455

Merged
merged 10 commits into from
Oct 6, 2021
12 changes: 12 additions & 0 deletions common/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ type Source interface {
// and second line, or EOF if there is only one line of source.
LineOffsets() []int32

// Macro calls returns the macro calls map containing the original
// expression from a macro replacement, keyed by Id.
MacroCalls() map[int64]*exprpb.Expr

// LocationOffset translates a Location to an offset.
// Given the line and column of the Location returns the
// Location's character offset in the Source, and a bool
Expand All @@ -65,6 +69,7 @@ type sourceImpl struct {
description string
lineOffsets []int32
idOffsets map[int64]int32
macroCalls map[int64]*exprpb.Expr
}

var _ runes.Buffer = &sourceImpl{}
Expand Down Expand Up @@ -93,6 +98,7 @@ func NewStringSource(contents string, description string) Source {
description: description,
lineOffsets: offsets,
idOffsets: map[int64]int32{},
macroCalls: map[int64]*exprpb.Expr{},
}
}

Expand All @@ -103,6 +109,7 @@ func NewInfoSource(info *exprpb.SourceInfo) Source {
description: info.GetLocation(),
lineOffsets: info.GetLineOffsets(),
idOffsets: info.GetPositions(),
macroCalls: info.GetMacroCalls(),
}
}

Expand All @@ -121,6 +128,11 @@ func (s *sourceImpl) LineOffsets() []int32 {
return s.lineOffsets
}

// MacroCalls implements the Source interface method.
func (s *sourceImpl) MacroCalls() map[int64]*exprpb.Expr {
return s.macroCalls
}

// LocationOffset implements the Source interface method.
func (s *sourceImpl) LocationOffset(location Location) (int32, bool) {
if lineOffset, found := s.findLineOffset(location.Line()); found {
Expand Down
59 changes: 58 additions & 1 deletion parser/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ func (p *parserHelper) getSourceInfo() *exprpb.SourceInfo {
return &exprpb.SourceInfo{
Location: p.source.Description(),
Positions: p.positions,
LineOffsets: p.source.LineOffsets()}
LineOffsets: p.source.LineOffsets(),
MacroCalls: p.source.MacroCalls()}
}

func (p *parserHelper) newLiteral(ctx interface{}, value *exprpb.Constant) *exprpb.Expr {
Expand Down Expand Up @@ -207,6 +208,62 @@ func (p *parserHelper) getLocation(id int64) common.Location {
return location
}

// buildMacroCallArg iterates the expression and returns a new expression
// where all macros have been replaced by their IDs in MacroCalls
func (p *parserHelper) buildMacroCallArg(expr *exprpb.Expr) *exprpb.Expr {
resultExpr := &exprpb.Expr{Id: expr.GetId()}
if _, found := p.source.MacroCalls()[expr.GetId()]; found {
return resultExpr
}

switch expr.ExprKind.(type) {
case *exprpb.Expr_CallExpr:
resultExpr.ExprKind = &exprpb.Expr_CallExpr{
CallExpr: &exprpb.Expr_Call{
Function: expr.GetCallExpr().GetFunction(),
},
}
resultExpr.GetCallExpr().Args = make([]*exprpb.Expr, len(expr.GetCallExpr().GetArgs()))
// Iterate the AST from `expr` recursively looking for macros. Because we are at most
// starting from the top level macro, this recursion is bounded by the size of the AST. This
// means that the depth check on the AST during parsing will catch recursion overflows
// before we get to here.
for index, arg := range expr.GetCallExpr().GetArgs() {
resultExpr.GetCallExpr().GetArgs()[index] = p.buildMacroCallArg(arg)
}
return resultExpr
}

return expr
}

// addMacroCall adds the macro the the MacroCalls map in source info. If a macro has args/subargs/target
// that are macros, their ID will be stored instead for later self-lookups.
func (p *parserHelper) addMacroCall(exprID int64, function string, target *exprpb.Expr, args ...*exprpb.Expr) {
expr := &exprpb.Expr{
Id: exprID,
ExprKind: &exprpb.Expr_CallExpr{
CallExpr: &exprpb.Expr_Call{
Function: function,
},
},
}

if target != nil {
if _, found := p.source.MacroCalls()[target.GetId()]; found {
expr.GetCallExpr().Target = &exprpb.Expr{Id: target.GetId()}
} else {
expr.GetCallExpr().Target = target
}
}

expr.GetCallExpr().Args = make([]*exprpb.Expr, len(args))
for index, arg := range args {
expr.GetCallExpr().GetArgs()[index] = p.buildMacroCallArg(arg)
}
p.source.MacroCalls()[exprID] = expr
}

// balancer performs tree balancing on operators whose arguments are of equal precedence.
//
// The purpose of the balancer is to ensure a compact serialization format for the logical &&, ||
Expand Down
6 changes: 3 additions & 3 deletions parser/macro.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func makeExistsOne(eh ExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*ex
func makeQuantifier(kind quantifierKind, eh ExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
v, found := extractIdent(args[0])
if !found {
location := eh.OffsetLocation(args[0].Id)
location := eh.OffsetLocation(args[0].GetId())
return nil, &common.Error{
Message: "argument must be a simple name",
Location: location}
Expand Down Expand Up @@ -373,14 +373,14 @@ func makeFilter(eh ExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprp
func extractIdent(e *exprpb.Expr) (string, bool) {
switch e.ExprKind.(type) {
case *exprpb.Expr_IdentExpr:
return e.GetIdentExpr().Name, true
return e.GetIdentExpr().GetName(), true
}
return "", false
}

func makeHas(eh ExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
if s, ok := args[0].ExprKind.(*exprpb.Expr_SelectExpr); ok {
return eh.PresenceTest(s.SelectExpr.Operand, s.SelectExpr.Field), nil
return eh.PresenceTest(s.SelectExpr.GetOperand(), s.SelectExpr.GetField()), nil
}
return nil, &common.Error{Message: "invalid argument to has() macro"}
}
10 changes: 10 additions & 0 deletions parser/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type options struct {
errorRecoveryLimit int
expressionSizeCodePointLimit int
macros map[string]Macro
populateMacroCalls bool
}

// Option configures the behavior of the parser.
Expand Down Expand Up @@ -92,3 +93,12 @@ func Macros(macros ...Macro) Option {
return nil
}
}

// PopulateMacroCalls ensures that the original call signatures replaced by expanded macros
// are preserved in the `SourceInfo` of parse result.
func PopulateMacroCalls(populateMacroCalls bool) Option {
return func(opts *options) error {
opts.populateMacroCalls = populateMacroCalls
return nil
}
}
12 changes: 9 additions & 3 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func NewParser(opts ...Option) (*Parser, error) {
if p.expressionSizeCodePointLimit == -1 {
p.expressionSizeCodePointLimit = int((^uint(0)) >> 1)
}
// Bool is false by default, so populateMacroCalls will be false by default
return p, nil
}

Expand All @@ -90,6 +91,7 @@ func (p *Parser) Parse(source common.Source) (*exprpb.ParsedExpr, *common.Errors
maxRecursionDepth: p.maxRecursionDepth,
errorRecoveryLimit: p.errorRecoveryLimit,
errorRecoveryLookaheadTokenLimit: p.errorRecoveryTokenLookaheadLimit,
populateMacroCalls: p.populateMacroCalls,
}
buf, ok := source.(runes.Buffer)
if !ok {
Expand Down Expand Up @@ -278,6 +280,7 @@ type parser struct {
maxRecursionDepth int
errorRecoveryLimit int
errorRecoveryLookaheadTokenLimit int
populateMacroCalls bool
}

var (
Expand Down Expand Up @@ -804,15 +807,15 @@ func (p *parser) extractQualifiedName(e *exprpb.Expr) (string, bool) {
}
switch e.ExprKind.(type) {
case *exprpb.Expr_IdentExpr:
return e.GetIdentExpr().Name, true
return e.GetIdentExpr().GetName(), true
case *exprpb.Expr_SelectExpr:
s := e.GetSelectExpr()
if prefix, found := p.extractQualifiedName(s.Operand); found {
return prefix + "." + s.Field, true
}
}
// TODO: Add a method to Source to get location from character offset.
location := p.helper.getLocation(e.Id)
location := p.helper.getLocation(e.GetId())
p.reportError(location, "expected a qualified name")
return "", false
}
Expand All @@ -833,7 +836,7 @@ func (p *parser) reportError(ctx interface{}, format string, args ...interface{}
location = ctx.(common.Location)
case antlr.Token, antlr.ParserRuleContext:
err := p.helper.newExpr(ctx)
location = p.helper.getLocation(err.Id)
location = p.helper.getLocation(err.GetId())
}
err := p.helper.newExpr(ctx)
// Provide arguments to the report error.
Expand Down Expand Up @@ -893,5 +896,8 @@ func (p *parser) expandMacro(exprID int64, function string, target *exprpb.Expr,
}
return p.reportError(p.helper.getLocation(exprID), err.Message), true
}
if p.populateMacroCalls {
p.helper.addMacroCall(expr.GetId(), function, target, args...)
}
return expr, true
}
Loading