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 |
+} |