from typing import TypedDict # Holds the parsed information from each line. class LineData(TypedDict): # Which character is being used for indentation. Avoids having to specify it on each terrace_parse_line call. indent: str # How many indent characters are present in the current line before the first non-indent character. level: int # The number of characters before the start of the line's "head" section. # (Normally the same as `level`) offsetHead: int # The number of characters before the start of the line's "tail" section. offsetTail: int def createLineData(indent: str = ' ') -> LineData: """ Initialize a LineData instance with default values. Parameters ---------- indent : str The character to use for indenting lines. ONLY ONE CHARACTER IS CURRENTLY PERMITTED. Returns ------- LineData A LineData dict with the specified indent character and all other values initialized to 0. """ return { "indent": indent, "level": 0, "offsetHead": 0, "offsetTail": 0 } def parseLine(line: str, lineData: LineData): """ Core Terrace parser function, sets level, offsetHead, and offsetTail in a LineData object based on the current line. Note that this is a C-style function, lineData is treated as a reference and mutated in-place. Parameters ---------- line : str A string containing a line to parse. Shouldn't end with a newline. lineData: LineData A LineData dict to store information about the current line in. **Mutated in-place!** """ # Blank lines have no characters, the newline should be stripped off. # Special case handling for these allows them to be parsed quickly. if len(line) == 0: # Empty lines are treated as having the same level as the previous line, so lineData.line is not updated. lineData['offsetHead'] = 0 lineData['offsetTail'] = 0 else: # Count the number of indent characters in the current line. level = 0 while level < len(line) and line[level] == lineData['indent']: level += 1 lineData['level'] = level # Set offsetHead and offsetTail to level to start with. # offsetHead should always be equal to level, and offsetTail will always be equal to or greater than level. lineData['offsetHead'] = level lineData['offsetTail'] = level # Increment offsetTail until we encounter a space character (start of tail) or reach EOL (no tail present). while lineData['offsetTail'] < len(line) and line[lineData['offsetTail']] != ' ': lineData['offsetTail'] += 1