<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def read (path, tabwidth=8, **kwargs):
 """Read a typed tabular text file into a stream of Holders. Arguments: path The path of the file to read. tabwidth=8 The tab width to assume. Please don't monkey with it. mode='rt' The file open mode (passed to io.open()). noexistok=False If True and the file is missing, treat it as empty. ``**kwargs`` Passed to io.open (). Returns a generator for a stream of `pwkit.Holder`s, each of which will contain ints, strings, or some kind of measurement (cf `pwkit.msmt`). """

    datamode = False
    fixedcols = {}

    for text in _trimmedlines (path, **kwargs):
        text = text.expandtabs (tabwidth)

        if datamode:
            # table row
            h = Holder ()
            h.set (**fixedcols)
            for name, cslice, parser in info:
                try:
                    v = parser (text[cslice].strip ())
                except:
                    reraise_context ('while parsing "%s"', text[cslice].strip ())
                h.set_one (name, v)
            yield h
        elif text[0] != '@':
            # fixed column
            padnamekind, padval = text.split ('=', 1)
            name, parser = _getparser (padnamekind.strip ())
            fixedcols[name] = parser (padval.strip ())
        else:
            # column specification
            n = len (text)
            assert n > 1
            start = 0
            info = []

            while start < n:
                end = start + 1
                while end < n and (not text[end].isspace ()):
                    end += 1

                if start == 0:
                    namekind = text[start+1:end] # eat leading @
                else:
                    namekind = text[start:end]

                while end < n and text[end].isspace ():
                    end += 1

                name, parser = _getparser (namekind)
                if parser is None: # allow columns to be ignored
                    skippedlast = True
                else:
                    skippedlast = False
                    info.append ((name, slice (start, end), parser))
                start = end

            datamode = True

            if not skippedlast:
                # make our last column go as long as the line goes
                # (e.g. for "comments" columns)
                # but if the real last column is ":x"-type, then info[-1]
                # doesn't run up to the end of the line, so do nothing in that case.
                lname, lslice, lparser = info[-1]
                info[-1] = lname, slice (lslice.start, None), lparser