docs: def matlabstr2py(string):
      """Return Python object from Matlab string representation.
  
      Return str, bool, int, float, list (Matlab arrays or cells), or
      dict (Matlab structures) types.
  
      Use to access ScanImage metadata.
  
      >>> matlabstr2py('1')
      1
      >>> matlabstr2py("['x y z' true false; 1 2.0 -3e4; NaN Inf @class]")
      [['x y z', True, False], [1, 2.0, -30000.0], [nan, inf, '@class']]
      >>> d = matlabstr2py("SI.hChannels.channelType = {'stripe' 'stripe'}\\n"
      ...                  "SI.hChannels.channelsActive = 2")
      >>> d['SI.hChannels.channelType']
      ['stripe', 'stripe']
  
      """
      # TODO: handle invalid input
      # TODO: review unboxing of multidimensional arrays
  
      def lex(s):
          # return sequence of tokens from matlab string representation
          tokens = ['[']
          while True:
              t, i = next_token(s)
              if t is None:
                  break
              if t == ';':
                  tokens.extend((']', '['))
              elif t == '[':
                  tokens.extend(('[', '['))
              elif t == ']':
                  tokens.extend((']', ']'))
              else:
                  tokens.append(t)
              s = s[i:]
          tokens.append(']')
          return tokens
  
      def next_token(s):
          # return next token in matlab string
          length = len(s)
          if length == 0:
              return None, 0
          i = 0
          while i < length and s[i] == ' ':
              i += 1
          if i == length:
              return None, i
          if s[i] in '{[;]}':
              return s[i], i + 1
          if s[i] == "'":
              j = i + 1
              while j < length and s[j] != "'":
                  j += 1
              return s[i: j+1], j + 1
          if s[i] == '<':
              j = i + 1
              while j < length and s[j] != '>':
                  j += 1
              return s[i: j+1], j + 1
          j = i
          while j < length and not s[j] in ' {[;]}':
              j += 1
          return s[i:j], j
  
      def value(s, fail=False):
          # return Python value of token
          s = s.strip()
          if not s:
              return s
          if len(s) == 1:
              try:
                  return int(s)
              except Exception:
                  if fail:
                      raise ValueError()
                  return s
          if s[0] == "'":
              if fail and s[-1] != "'" or "'" in s[1:-1]:
                  raise ValueError()
              return s[1:-1]
          if s[0] == '<':
              if fail and s[-1] != '>' or '<' in s[1:-1]:
                  raise ValueError()
              return s
          if fail and any(i in s for i in " ';[]{}"):
              raise ValueError()
          if s[0] == '@':
              return s
          if s in ('true', 'True'):
              return True
          if s in ('false', 'False'):
              return False
          if s[:6] == 'zeros(':
              return numpy.zeros([int(i) for i in s[6:-1].split(',')]).tolist()
          if s[:5] == 'ones(':
              return numpy.ones([int(i) for i in s[5:-1].split(',')]).tolist()
          if '.' in s or 'e' in s:
              try:
                  return float(s)
              except Exception:
                  pass
          try:
              return int(s)
          except Exception:
              pass
          try:
              return float(s)  # nan, inf
          except Exception:
              if fail:
                  raise ValueError()
          return s
  
      def parse(s):
          # return Python value from string representation of Matlab value
          s = s.strip()
          try:
              return value(s, fail=True)
          except ValueError:
              pass
          result = add2 = []
          levels = [add2]
          for t in lex(s):
              if t in '[{':
                  add2 = []
                  levels.append(add2)
              elif t in ']}':
                  x = levels.pop()
                  if len(x) == 1 and isinstance(x[0], (list, str)):
                      x = x[0]
                  add2 = levels[-1]
                  add2.append(x)
              else:
                  add2.append(value(t))
          if len(result) == 1 and isinstance(result[0], (list, str)):
              result = result[0]
          return result
  
      if '\r' in string or '\n' in string:
          # structure
          d = {}
          for line in string.splitlines():
              line = line.strip()
              if not line or line[0] == '%':
                  continue
              k, v = line.split('=', 1)
              k = k.strip()
              if any(c in k for c in " ';[]{}<>"):
                  continue
              d[k] = parse(v)
          return d
      return parse(string)