go.uber.org/yarpc@v1.72.1/internal/interpolate/parse.rl (about)

     1  package interpolate
     2  
     3  import "fmt"
     4  
     5  %%{
     6      machine interpolate;
     7      write data;
     8  }%%
     9  
    10  // Parse parses a string for interpolation.
    11  //
    12  // Variables may be specified anywhere in the string in the format ${foo} or
    13  // ${foo:default} where 'default' will be used if the variable foo was unset.
    14  func Parse(data string) (out String, _ error) {
    15      var (
    16          // Variables used by Ragel
    17          cs  = 0         // current state
    18          p   = 0         // current position in data
    19          pe  = len(data)
    20          eof = pe        // eof == pe if this is the last data block
    21  
    22          // We use the following variables to actually build the String.
    23  
    24          // Index in data where the currently captured string started.
    25          idx int
    26  
    27          v variable  // variable being read, if any
    28          l literal   // literal being read, if any
    29  
    30          // Current term. This is either the variable that we just read or the
    31          // literal. We will append it to `out` and move on.
    32          t term
    33      )
    34  
    35      %%{
    36          # Record the current position as the start of a string. This is
    37          # usually used with the entry transition (>) to start capturing the
    38          # string when a state machine is entered.
    39          #
    40          # fpc is the current position in the string (basically the same as the
    41          # variable `p` but a special Ragel keyword) so after executing
    42          # `start`, data[idx:fpc+1] is the string from when start was called to
    43          # the current position (inclusive).
    44          action start { idx = fpc }
    45  
    46          # A variable always starts with an alphabet or an underscore and
    47          # contains alphanumeric characters, underscores, and non-consecutive
    48          # dots or dashes.
    49          var_name
    50              = ( [a-zA-Z_] ([a-zA-Z0-9_]
    51                | ('.' | '-') [a-zA-Z0-9_])*
    52                )
    53              >start
    54              @{ v.Name = data[idx:fpc+1] }
    55              ;
    56  
    57          var_default
    58              = (any - '}')* >start @{ v.Default = data[idx:fpc+1] };
    59  
    60          # var is a reference to a variable and optionally has a default value
    61          # for that variable.
    62          var = '${' var_name (':' @{ v.HasDefault = true } var_default)?  '}'
    63              ;
    64  
    65          # Anything followed by a '\' is used as-is.
    66          escaped_lit = '\\' any @{ l = literal(data[fpc:fpc+1]) };
    67  
    68          # Anything followed by a '$' that is not a '{' is used as-is with the
    69          # dollar.
    70          dollar_lit = '$' (any - '{') @{ l = literal(data[fpc-1:fpc+1]) };
    71  
    72          # Literal strings that don't contain '$' or '\'.
    73          simple_lit
    74              = (any - '$' - '\\')+
    75              >start
    76              @{ l = literal(data[idx:fpc + 1]) }
    77              ;
    78  
    79          lit = escaped_lit | dollar_lit | simple_lit;
    80  
    81          # Terms are the two possible components in a string. Either a literal
    82          # or a variable reference.
    83          term = (var @{ t = v }) | (lit @{ t = l });
    84  
    85          main := (term %{ out = append(out, t) })**;
    86  
    87          write init;
    88          write exec;
    89      }%%
    90  
    91      if cs < %%{ write first_final; }%% {
    92          return out, fmt.Errorf("cannot parse string %q", data)
    93      }
    94  
    95      return out, nil
    96  }