The easiest way is probably to define a simple notion of “chunks” that any file can be broken into.
So with Elm code, maybe that means a chunk begins whenever you have a “fresh line” where a newline is directly followed by a letter. That indicates that we are seeing a type, type alias, type annotation, declaration, import, or port. Then you parse the chunks individually.
That approach could get confused by weirdly indented expressions I guess, but it seems like it’s a reasonable start at least! I also wouldn’t be to tied to trying to do it all in a single pass over the file at first. Just start by figuring out if it can get the error messages you’d like to see!