code: def interpolate(
      self, s: str, return_missing: bool = False
  ) -> Union[str, Tuple[str, List[str]]]:
      tokens = []
      missing = []
      start = 0
      while start < len(s):
          dollar = s.find("$", start)
          if dollar == -1 or dollar == len(s) - 1:
              tokens.append(s[start:])
              break
          if s[dollar + 1] == "$":  # escaped $$
              tokens.append(s[start : dollar + 1])
              start = dollar + 2
              continue
  
          opening = s.find(Pattern.opening, start)
          if opening == -1:
              tokens.append(s[start:])
              break
          tokens.append(s[start:opening])
          closing = s.find(Pattern.closing, opening)
          if closing == -1:
              raise ValueError(f"No pattern closing: {s[opening:]}")
  
          name = s[opening + len(Pattern.opening) : closing].strip()
          if not self.validate_name(name):
              raise ValueError(f"Illegal reference name: {name}")
          if name.split(".")[0] in self.skip:
              tokens.append(s[opening : closing + len(Pattern.closing)])
          elif name in self.variables:
              tokens.append(self.variables[name])
          else:
              missing.append(name)
          start = closing + len(Pattern.closing)
      s = "".join(tokens)
      return (s, missing) if return_missing else s
  
file_path: dstack/src/dstack/_internal/utils/interpolator.py
imports: import string
  from typing import Dict, Iterable, List, Optional, Tuple, Union
label_desc: Use image node_id 2 for calling the VariablesInterpolator obj's underlying member method code with example usage: obj.interpolate(s, return_missing) and returns: unknown