Skip to content

Commit

Permalink
Reduce gc pressure (#89)
Browse files Browse the repository at this point in the history
Reduce memory allocation behaviour of stack tracing.
  • Loading branch information
marcelocantos authored Sep 5, 2020
1 parent 33af489 commit faecaef
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 67 deletions.
80 changes: 41 additions & 39 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,13 @@ func (g Grammar) Compile(node interface{}) Parsers {
}

//-----------------------------------------------------------------------------
func parseEscape(p Parser, scope Scope, input *Scanner, output *TreeElement) (bool, error) {
func parseEscape(p Parser, scope Scope, ident string, t Term, input *Scanner, output *TreeElement) (bool, error) {
if esc := scope.getParserEscape(); esc != nil {
var match Scanner
if _, ok := input.EatRegexp(esc.openDelim, &match, nil); ok {
if ident != "" {
scope = scope.With(ident, t)
}
te, err := esc.external(scope.With("(term)", p.AsTerm()), input)
if err != nil {
unconsumed, ok := err.(UnconsumedInputError)
Expand Down Expand Up @@ -166,7 +169,7 @@ type ruleParser struct {
t Rule
}

func (p ruleParser) Parse(scope Scope, input *Scanner, output *TreeElement) error {
func (p ruleParser) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) error {
panic(errors.Inconceivable)
}
func (p ruleParser) AsTerm() Term { return p.t }
Expand Down Expand Up @@ -227,15 +230,15 @@ type sParser struct {
re *regexp.Regexp
}

func (p *sParser) Parse(scope Scope, input *Scanner, output *TreeElement) error { //nolint:dupl
if escaped, err := parseEscape(p, scope.PushCall(string(p.rule), p.t), input, output); escaped || err != nil {
func (p *sParser) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) error { //nolint:dupl
if escaped, err := parseEscape(p, scope, string(p.rule), p.t, input, output); escaped || err != nil {
return err
}
if ok := eatRegexp(input, p.re, output); !ok {
return newParseError(p.rule, "")(scope.GetCutPoint(),
func() error { return fmt.Errorf("expect: %s", NewScanner(p.t.String()).Context(DefaultLimit)) },
func() error { return fmt.Errorf("actual: %s", getErrorStrings(input)) },
func() error { return scope.GetCallStack() },
func() error { return stk },
)
}
return nil
Expand All @@ -257,15 +260,15 @@ type reParser struct {
re *regexp.Regexp
}

func (p *reParser) Parse(scope Scope, input *Scanner, output *TreeElement) error { //nolint:dupl
if escaped, err := parseEscape(p, scope.PushCall(string(p.rule), p.t), input, output); escaped || err != nil {
func (p *reParser) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) error { //nolint:dupl
if escaped, err := parseEscape(p, scope, string(p.rule), p.t, input, output); escaped || err != nil {
return err
}
if ok := eatRegexp(input, p.re, output); !ok {
return newParseError(p.rule, "")(scope.GetCutPoint(),
func() error { return fmt.Errorf("expect: %s", NewScanner(p.re.String()).Context(DefaultLimit)) },
func() error { return fmt.Errorf("actual: %s", getErrorStrings(input)) },
func() error { return scope.GetCallStack() },
func() error { return stk },
)
}
return nil
Expand Down Expand Up @@ -330,9 +333,9 @@ func nodesEqual(a, b interface{}) bool {
return false
}

func (p *seqParser) Parse(scope Scope, input *Scanner, output *TreeElement) (out error) {
func (p *seqParser) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) (out error) {
// defer enterf("%s: %T %[2]v", p.rule, p.t).exitf("%v %v", &out, output)
if escaped, err := parseEscape(p, scope, input, output); escaped || err != nil {
if escaped, err := parseEscape(p, scope, "", nil, input, output); escaped || err != nil {
return err
}
result := make([]TreeElement, 0, len(p.parsers))
Expand All @@ -341,14 +344,14 @@ func (p *seqParser) Parse(scope Scope, input *Scanner, output *TreeElement) (out
for i, item := range p.parsers {
var v TreeElement
ident := identFromTerm(p.t[i])
if err := item.Parse(scope.PushCall(ident, item.AsTerm()), input, &v); err != nil {
if err := item.Parse(scope, input, &v, stk.push(ident, item.AsTerm())); err != nil {
if isFatal(err) {
return err
}
*input = furthest
return newParseError(p.rule, "could not complete sequence")(scope.GetCutPoint(),
func() error { return err },
func() error { return scope.GetCallStack() },
func() error { return stk },
)
}
if _, ok := item.(*cutPointParser); ok {
Expand Down Expand Up @@ -384,16 +387,16 @@ type Empty struct{}

func (Empty) IsTreeElement() {}

func (p *delimParser) Parse(scope Scope, input *Scanner, output *TreeElement) (out error) {
func (p *delimParser) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) (out error) {
// defer enterf("%s: %T %[2]v", p.rule, p.t).exitf("%v %v", &out, output)
if escaped, err := parseEscape(p, scope, input, output); escaped || err != nil {
if escaped, err := parseEscape(p, scope, "", nil, input, output); escaped || err != nil {
return err
}
result := []TreeElement{}

scope = scope.PushCall(string(p.rule), p.AsTerm())
stk = stk.push(string(p.rule), p.AsTerm())

if out := p.child.Parse(scope, input, output); out != nil {
if out := p.child.Parse(scope, input, output, stk); out != nil {
return out
}

Expand Down Expand Up @@ -514,20 +517,20 @@ type quantParser struct {
put putter
}

func (p *quantParser) Parse(scope Scope, input *Scanner, output *TreeElement) (out error) {
func (p *quantParser) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) (out error) {
// defer enterf("%s: %T %[2]v", p.rule, p.t).exitf("%v %v", &out, output)
if escaped, err := parseEscape(p, scope, input, output); escaped || err != nil {
if escaped, err := parseEscape(p, scope, "", nil, input, output); escaped || err != nil {
return err
}
result := make([]TreeElement, 0, p.t.Min)
var v TreeElement
start := *input

scope = scope.PushCall(string(p.rule), p.AsTerm())
stk = stk.push(string(p.rule), p.AsTerm())

scope, prevcp, mycp := scope.ReplaceCutPoint(false)
for p.t.Max == 0 || len(result) < p.t.Max {
if out = p.term.Parse(scope, &start, &v); out != nil {
if out = p.term.Parse(scope, &start, &v, stk); out != nil {
if isNotMyFatalError(out, mycp) {
return out
}
Expand All @@ -544,7 +547,7 @@ func (p *quantParser) Parse(scope Scope, input *Scanner, output *TreeElement) (o
return newParseError(p.rule,
"quant failed, expected: (%d, %d), have %d value(s)",
p.t.Min, p.t.Max, len(result),
)(prevcp, func() error { return out }, func() error { return scope.GetCallStack() })
)(prevcp, func() error { return out }, func() error { return stk })
}
func (p *quantParser) AsTerm() Term { return p.t }

Expand All @@ -568,20 +571,20 @@ type oneofParser struct {
put putter
}

func (p *oneofParser) Parse(scope Scope, input *Scanner, output *TreeElement) (out error) {
func (p *oneofParser) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) (out error) {
// defer enterf("%s: %T %[2]v", p.rule, p.t).exitf("%v %v", &out, output)
if escaped, err := parseEscape(p, scope, input, output); escaped || err != nil {
if escaped, err := parseEscape(p, scope, "", nil, input, output); escaped || err != nil {
return err
}
furthest := *input

scope = scope.PushCall(string(p.rule), p.AsTerm())
stk = stk.push(string(p.rule), p.AsTerm())
scope, prevcp, mycp := scope.ReplaceCutPoint(false)
var errors []func() error
for i, par := range p.parsers {
var v TreeElement
start := *input
if err := par.Parse(scope, &start, &v); err != nil {
if err := par.Parse(scope, &start, &v, stk); err != nil {
if isNotMyFatalError(err, mycp) {
return err
}
Expand All @@ -595,7 +598,7 @@ func (p *oneofParser) Parse(scope Scope, input *Scanner, output *TreeElement) (o
return p.put(output, Choice(i), v)
}
}
errors = append(errors, func() error { return scope.GetCallStack() })
errors = append(errors, func() error { return stk })
*input = furthest
return newParseError(p.rule, "None of the available options could be satisfied")(prevcp, errors...)
}
Expand Down Expand Up @@ -641,32 +644,32 @@ func termFromRefVal(from TreeElement) Term {
return term
}

func (t *REF) Parse(scope Scope, input *Scanner, output *TreeElement) (out error) {
scope = scope.PushCall(t.Ident, t.AsTerm())
if escaped, err := parseEscape(t, scope, input, output); escaped || err != nil {
func (t *REF) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) (out error) {
if escaped, err := parseEscape(t, scope, "", nil, input, output); escaped || err != nil {
return err
}
stk = stk.push(t.Ident, t.AsTerm())
var v TreeElement
if _, expected, ok := scope.GetVal(t.Ident); ok {
term := termFromRefVal(expected)
parser := term.Parser(Rule(t.Ident), cache{})
if err := parser.Parse(scope, input, &v); err != nil {
if err := parser.Parse(scope, input, &v, stk); err != nil {
return err
}
if !nodesEqual(v, expected) {
return newParseError(Rule(t.Ident), "Backref not matched")(invalidCutpoint,
func() error { return fmt.Errorf("expected: %s", expected) },
func() error { return fmt.Errorf("actual: %s", v) },
func() error { return scope.GetCallStack() },
func() error { return stk },
)
}
} else if t.Default != nil {
if err := t.Default.Parser(Rule(t.Ident), cache{}).Parse(scope, input, &v); err != nil {
if err := t.Default.Parser(Rule(t.Ident), cache{}).Parse(scope, input, &v, stk); err != nil {
return err
}
} else {
return newParseError(Rule(t.Ident), "Backref not found")(invalidCutpoint,
func() error { return scope.GetCallStack() },
func() error { return stk },
)
}
*output = v
Expand All @@ -678,15 +681,14 @@ func (t REF) Parser(rule Rule, c cache) Parser {
}
func (t REF) AsTerm() Term { return t }

func (t ExtRef) Parse(scope Scope, input *Scanner, output *TreeElement) (out error) {
scope = scope.PushCall(string(t), t.AsTerm())
if escaped, err := parseEscape(t, scope, input, output); escaped || err != nil {
func (t ExtRef) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) (out error) {
if escaped, err := parseEscape(t, scope, "", nil, input, output); escaped || err != nil {
return err
}
fn := scope.GetExternal(string(t))
if fn == nil {
return newParseError(Rule(string(t)), "External handler not found")(Cutpointdata(1),
func() error { return scope.GetCallStack() },
func() error { return stk.push(string(t), t.AsTerm()) },
)
}
*output, out = fn(scope, input)
Expand Down Expand Up @@ -758,8 +760,8 @@ type cutPointParser struct {
t CutPoint
}

func (t *cutPointParser) Parse(scope Scope, input *Scanner, output *TreeElement) (out error) {
return t.p.Parse(scope, input, output)
func (t *cutPointParser) Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) (out error) {
return t.p.Parse(scope, input, output, stk)
}
func (t *cutPointParser) AsTerm() Term { return t.t }
func (t CutPoint) Parser(rule Rule, c cache) Parser {
Expand Down
2 changes: 1 addition & 1 deletion parser/parsernodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,6 @@ func (n Node) Format(state fmt.State, c rune) {
}

type Parser interface {
Parse(scope Scope, input *Scanner, output *TreeElement) error
Parse(scope Scope, input *Scanner, output *TreeElement, stk *call) error
AsTerm() Term
}
54 changes: 29 additions & 25 deletions parser/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type escape struct {
closeDelim *regexp.Regexp
external ExternalRef
}

type Scope struct {
m frozen.Map
}
Expand All @@ -27,14 +28,29 @@ func (s Scope) Keys() frozen.Set {
}

func (s Scope) With(ident string, v interface{}) Scope {
s.m = s.m.With(ident, v)
return s
return Scope{m: s.m.With(ident, v)}
}

func (s Scope) Has(ident string) bool {
return s.m.Has(ident)
}

func (s Scope) Merge(t Scope) Scope {
return Scope{m: s.m.Update(t.m)}
}

type scopeBuilder struct {
mb frozen.MapBuilder
}

func (b *scopeBuilder) Put(ident string, v interface{}) {
b.mb.Put(ident, v)
}

func (b *scopeBuilder) Finish() Scope {
return Scope{m: b.mb.Finish()}
}

type scopeVal struct {
p Parser
val TreeElement
Expand Down Expand Up @@ -83,6 +99,7 @@ const parseEscapeKey = ".ParseEscape-key."

func (s Scope) WithExternals(extRefs ExternalRefs) Scope {
var e *escape
var sb scopeBuilder
for name, external := range extRefs {
if strings.HasPrefix(name, "*") {
if e != nil {
Expand All @@ -94,10 +111,11 @@ func (s Scope) WithExternals(extRefs ExternalRefs) Scope {
closeDelim: regexp.MustCompile(`(?m)\A` + openClose[1]),
external: external,
}
s = s.With(parseEscapeKey, e)
sb.Put(parseEscapeKey, e)
}
}
return s.With(externalsKey, extRefs)
sb.Put(externalsKey, extRefs)
return s.Merge(sb.Finish())
}

func (s Scope) GetExternal(ident string) ExternalRef {
Expand All @@ -117,31 +135,17 @@ func (s Scope) getParserEscape() *escape {
type call struct {
ident string
term Term
scope Scope
}
type CallStack struct {
stack []call
next *call
}

func (c CallStack) Error() string {
parts := make([]string, 0, len(c.stack))
for _, call := range c.stack {
parts = append(parts, fmt.Sprintf("%+v", call))
func (c *call) Error() string {
var parts []string
for ; c != nil; c = c.next {
parts = append(parts, fmt.Sprintf("%+v", *c))
}
return strings.Join(parts, "\n")
}

const callStackKey = ".CallStack-key."

func (s Scope) PushCall(ident string, t Term) Scope {
cs := s.GetCallStack()
cs.stack = append(cs.stack, call{ident, t, s})
return s.With(callStackKey, cs)
}

func (s Scope) GetCallStack() CallStack {
if e, has := s.m.Get(callStackKey); has {
return e.(CallStack)
}
return CallStack{}
func (c *call) push(ident string, t Term) *call {
return &call{ident, t, c}
}
4 changes: 2 additions & 2 deletions parser/terms.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ func (p Parsers) Unparse(e TreeElement, w io.Writer) (n int, err error) {

// Parse parses some source per a given rule.
func (p Parsers) ParseWithExternals(rule Rule, input *Scanner, exts ExternalRefs) (TreeElement, error) {
scope := Scope{}.WithExternals(exts).PushCall(string(rule), rule)
scope := Scope{}.WithExternals(exts)
var e TreeElement
if err := p.parsers[rule].Parse(scope, input, &e); err != nil {
if err := p.parsers[rule].Parse(scope, input, &e, nil); err != nil {
return nil, err
}

Expand Down

0 comments on commit faecaef

Please sign in to comment.