| Index: appengine/cmd/dm/distributor/impl/jobsim/parser/grammar.peg
|
| diff --git a/appengine/cmd/dm/distributor/impl/jobsim/parser/grammar.peg b/appengine/cmd/dm/distributor/impl/jobsim/parser/grammar.peg
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0ca703206936fe23b358ab0f8db38488eaf1bc14
|
| --- /dev/null
|
| +++ b/appengine/cmd/dm/distributor/impl/jobsim/parser/grammar.peg
|
| @@ -0,0 +1,197 @@
|
| +// vim: syntax=go
|
| +{
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package parser
|
| +
|
| +type retriesHolder uint64
|
| +type identityHolder uint64
|
| +
|
| +var whitespaceRE = regexp.MustCompile("#[^\n]*\n|\\s+")
|
| +
|
| +// CleanPhrase removes all whitespace and comments from a phrase in preparation
|
| +// for parsing it. The parsers defined in this package only operate on cleaned
|
| +// phrases, and will fail to parse if you provide a phrase with whitespace.
|
| +func CleanPhrase(p string) string {
|
| + return whitespaceRE.ReplaceAllLiteralString(p, "")
|
| +}
|
| +
|
| +// ParsePhrase is a convenience function for the parser functions in this
|
| +// package to return a typed Phrase object as the result of a string.
|
| +func ParsePhrase(p string) (Phrase, error) {
|
| + ret, err := Parse("<string>", []byte(p))
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| + return ret.(Phrase), nil
|
| +}
|
| +}
|
| +
|
| +Overall = p:Phrase !. {
|
| + return p, nil
|
| +} / p:Phrase ext:Extra {
|
| + return nil, fmt.Errorf("found extra: %q", ext.(string))
|
| +} / !. {
|
| + return nil, fmt.Errorf("empty phrase")
|
| +} / ext:Extra {
|
| + return nil, fmt.Errorf("expected phrase: %q", ext.(string))
|
| +}
|
| +
|
| +Extra = .* {
|
| + return string(c.text), nil
|
| +}
|
| +
|
| +Phrase = ret:ReturnStage {
|
| + return Phrase{ret.(Stage)}, nil
|
| +} / first:Stage middleI:(',' Stage)* retI:(',' ReturnStage)? {
|
| + middle, _ := middleI.([]interface{})
|
| + retStage, _ := retI.([]interface{})
|
| +
|
| + amt := 1
|
| + if middle != nil {
|
| + amt += len(middle)
|
| + }
|
| + if retStage != nil {
|
| + amt++
|
| + }
|
| +
|
| + ret := make(Phrase, 0, amt)
|
| + ret = append(ret, first.(Stage))
|
| + for _, itm := range middle {
|
| + ret = append(ret, itm.([]interface{})[1].(Stage))
|
| + }
|
| + if retStage != nil {
|
| + ret = append(ret, retStage[1].(Stage))
|
| + }
|
| +
|
| + return ret, nil
|
| +} / ',' .* {
|
| + return nil, fmt.Errorf("expected stage in %q", string(c.text))
|
| +}
|
| +
|
| +Stage = s:(FailureStage / StallStage / DepsStage) {
|
| + return s, nil
|
| +}
|
| +
|
| +FailureStage = '%' num:Num {
|
| + return FailureStage(num.(uint64)), nil
|
| +}
|
| +
|
| +StallStage = '@' amt:Duration {
|
| + return StallStage(amt.(time.Duration)), nil
|
| +}
|
| +
|
| +ReturnStage = '=' val:Num exp:('<' Duration)? {
|
| + ret := &ReturnStage{Value: val.(uint64)}
|
| + if exp != nil {
|
| + ret.Expiration = exp.([]interface{})[1].(time.Duration)
|
| + }
|
| + return ret, nil
|
| +}
|
| +
|
| +DepsStage = first:Dependency restI:('&' Dependency)* {
|
| + rest, _ := restI.([]interface{})
|
| +
|
| + if rest == nil {
|
| + return DepsStage{first.(*Dependency)}, nil
|
| + }
|
| + ret := make(DepsStage, 0, 1+len(rest))
|
| + ret = append(ret, first.(*Dependency))
|
| + for _, itm := range rest{
|
| + ret = append(ret, itm.([]interface{})[1].(*Dependency))
|
| + }
|
| + return ret, nil
|
| +} / '&' .* {
|
| + return nil, fmt.Errorf("expected dependency in %q", string(c.text))
|
| +}
|
| +
|
| +Dependency = shards:Shards? attempts:Attempts? id:ID options:Option? subI:( '(' Phrase ')' )? {
|
| + ret := &Dependency{Name: id.(string)}
|
| +
|
| + if shards != nil {
|
| + ret.ShardCount = shards.(uint64)
|
| + }
|
| +
|
| + if attempts != nil {
|
| + ret.AttemptNums = attempts.(RangeSlice)
|
| + }
|
| +
|
| + if options != nil {
|
| + switch x := options.(type) {
|
| + case retriesHolder:
|
| + if attempts != nil {
|
| + return nil, fmt.Errorf(
|
| + "in %q: retries are incompatible with specified Attempts",
|
| + string(c.text))
|
| + }
|
| + ret.Retries = uint64(x)
|
| +
|
| + case identityHolder:
|
| + ret.Uniq = uint64(x)
|
| + }
|
| + }
|
| +
|
| + sub, _ := subI.([]interface{})
|
| + if sub != nil {
|
| + ret.Substages = sub[1].(Phrase)
|
| + }
|
| +
|
| + return ret, nil
|
| +}
|
| +
|
| +Option = '+' num:Num {
|
| + return retriesHolder(num.(uint64)), nil
|
| +} / '^' num:Num {
|
| + return identityHolder(num.(uint64)), nil
|
| +}
|
| +
|
| +Shards = '{' num:Num '}' {
|
| + return num, nil
|
| +}
|
| +
|
| +Attempts = '[' rs:RangeSlice ']' {
|
| + return rs, nil
|
| +}
|
| +
|
| +RangeSlice = firstI:Range restI:(',' Range)* {
|
| + rest, _ := restI.([]interface{})
|
| +
|
| + ret := make(RangeSlice, 0, 1+len(rest))
|
| + ret = append(ret, firstI.(Range))
|
| + for _, itm := range rest {
|
| + ret = append(ret, itm.([]interface{})[1].(Range))
|
| + }
|
| +
|
| + return ret, nil
|
| +}
|
| +
|
| +Range = loI:Num '-' hiI:Num {
|
| + lo := loI.(uint64)
|
| + hi := hiI.(uint64)
|
| +
|
| + if lo >= hi {
|
| + return nil, fmt.Errorf("invalid range %q: lo >= hi", string(c.text))
|
| + }
|
| +
|
| + return Range{lo, hi}, nil
|
| +} / num:Num {
|
| + return Range{num.(uint64), 0}, nil
|
| +}
|
| +
|
| +Duration = num:Num {
|
| + return time.Second * time.Duration(num.(uint64)), nil
|
| +}
|
| +
|
| +Num = '0' {
|
| + return nil, fmt.Errorf("zero value not acceptable")
|
| +} / [1-9][0-9]* {
|
| + return strconv.ParseUint(string(c.text), 10, 64)
|
| +} / .* {
|
| + return nil, fmt.Errorf("expected number, got %q", string(c.text))
|
| +}
|
| +
|
| +ID = [a-zA-Z'_]+ {
|
| + return string(c.text), nil
|
| +}
|
|
|