|
- /*
- Copyright 2015 Google LLC
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
-
- package main
-
- import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "strconv"
- "strings"
- "unicode"
-
- "cloud.google.com/go/bigtable"
- )
-
- // Parse a GC policy. Valid policies include
- // never
- // maxage = 5d
- // maxversions = 3
- // maxage = 5d || maxversions = 3
- // maxage=30d || (maxage=3d && maxversions=100)
- func parseGCPolicy(s string) (bigtable.GCPolicy, error) {
- if strings.TrimSpace(s) == "never" {
- return bigtable.NoGcPolicy(), nil
- }
- r := strings.NewReader(s)
- p, err := parsePolicyExpr(r)
- if err != nil {
- return nil, fmt.Errorf("invalid GC policy: %v", err)
- }
- tok, err := getToken(r)
- if err != nil {
- return nil, err
- }
- if tok != "" {
- return nil, fmt.Errorf("invalid GC policy: want end of input, got %q", tok)
- }
- return p, nil
- }
-
- // expr ::= term (op term)*
- // op ::= "and" | "or" | "&&" | "||"
- func parsePolicyExpr(r io.RuneScanner) (bigtable.GCPolicy, error) {
- policy, err := parsePolicyTerm(r)
- if err != nil {
- return nil, err
- }
- for {
- tok, err := getToken(r)
- if err != nil {
- return nil, err
- }
- var f func(...bigtable.GCPolicy) bigtable.GCPolicy
- switch tok {
- case "and", "&&":
- f = bigtable.IntersectionPolicy
- case "or", "||":
- f = bigtable.UnionPolicy
- default:
- ungetToken(tok)
- return policy, nil
- }
- p2, err := parsePolicyTerm(r)
- if err != nil {
- return nil, err
- }
- policy = f(policy, p2)
- }
- }
-
- // term ::= "maxage" "=" duration | "maxversions" "=" int | "(" policy ")"
- func parsePolicyTerm(r io.RuneScanner) (bigtable.GCPolicy, error) {
- tok, err := getToken(r)
- if err != nil {
- return nil, err
- }
- switch tok {
- case "":
- return nil, errors.New("empty GC policy term")
-
- case "maxage", "maxversions":
- if err := expectToken(r, "="); err != nil {
- return nil, err
- }
- tok2, err := getToken(r)
- if err != nil {
- return nil, err
- }
- if tok2 == "" {
- return nil, errors.New("expected a token after '='")
- }
- if tok == "maxage" {
- dur, err := parseDuration(tok2)
- if err != nil {
- return nil, err
- }
- return bigtable.MaxAgePolicy(dur), nil
- }
- n, err := strconv.ParseUint(tok2, 10, 16)
- if err != nil {
- return nil, err
- }
- return bigtable.MaxVersionsPolicy(int(n)), nil
-
- case "(":
- p, err := parsePolicyExpr(r)
- if err != nil {
- return nil, err
- }
- if err := expectToken(r, ")"); err != nil {
- return nil, err
- }
- return p, nil
-
- default:
- return nil, fmt.Errorf("unexpected token: %q", tok)
- }
-
- }
-
- func expectToken(r io.RuneScanner, want string) error {
- got, err := getToken(r)
- if err != nil {
- return err
- }
- if got != want {
- return fmt.Errorf("expected %q, saw %q", want, got)
- }
- return nil
- }
-
- const noToken = "_" // empty token is valid, so use "_" instead
-
- // If not noToken, getToken will return this instead of reading a new token
- // from the input.
- var ungotToken = noToken
-
- // getToken extracts the first token from the input. Valid tokens include
- // any sequence of letters and digits, and these symbols: &&, ||, =, ( and ).
- // getToken returns ("", nil) at end of input.
- func getToken(r io.RuneScanner) (string, error) {
- if ungotToken != noToken {
- t := ungotToken
- ungotToken = noToken
- return t, nil
- }
- var err error
- // Skip leading whitespace.
- c := ' '
- for unicode.IsSpace(c) {
- c, _, err = r.ReadRune()
- if err == io.EOF {
- return "", nil
- }
- if err != nil {
- return "", err
- }
- }
- switch {
- case c == '=' || c == '(' || c == ')':
- return string(c), nil
-
- case c == '&' || c == '|':
- c2, _, err := r.ReadRune()
- if err != nil && err != io.EOF {
- return "", err
- }
- if c != c2 {
- return "", fmt.Errorf("expected %c%c", c, c)
- }
- return string([]rune{c, c}), nil
-
- case unicode.IsLetter(c) || unicode.IsDigit(c):
- // Collect an alphanumeric token.
- var b bytes.Buffer
- for unicode.IsLetter(c) || unicode.IsDigit(c) {
- b.WriteRune(c)
- c, _, err = r.ReadRune()
- if err == io.EOF {
- break
- }
- if err != nil {
- return "", err
- }
- }
- r.UnreadRune()
- return b.String(), nil
-
- default:
- return "", fmt.Errorf("bad rune %q", c)
- }
- }
-
- // "unget" a token so the next call to getToken will return it.
- func ungetToken(tok string) {
- if ungotToken != noToken {
- panic("ungetToken called twice")
- }
- ungotToken = tok
- }
|