Recursive descent parser for FDL.
| 134 | |
| 135 | |
| 136 | class Parser: |
| 137 | """Recursive descent parser for FDL.""" |
| 138 | |
| 139 | def __init__(self, tokens: List[Token], filename: str = "<input>"): |
| 140 | self.tokens = tokens |
| 141 | self.pos = 0 |
| 142 | self.filename = filename |
| 143 | self.source_format = "fdl" |
| 144 | |
| 145 | @classmethod |
| 146 | def from_source(cls, source: str, filename: str = "<input>") -> "Parser": |
| 147 | """Create a parser from source code.""" |
| 148 | lexer = Lexer(source, filename) |
| 149 | tokens = lexer.tokenize() |
| 150 | return cls(tokens, filename) |
| 151 | |
| 152 | def at_end(self) -> bool: |
| 153 | """Check if we've reached the end of tokens.""" |
| 154 | return self.current().type == TokenType.EOF |
| 155 | |
| 156 | def current(self) -> Token: |
| 157 | """Get the current token.""" |
| 158 | if self.pos >= len(self.tokens): |
| 159 | return self.tokens[-1] # Return EOF |
| 160 | return self.tokens[self.pos] |
| 161 | |
| 162 | def previous(self) -> Token: |
| 163 | """Get the previous token.""" |
| 164 | return self.tokens[self.pos - 1] |
| 165 | |
| 166 | def peek(self, offset: int = 0) -> Token: |
| 167 | """Peek at a token without consuming it.""" |
| 168 | pos = self.pos + offset |
| 169 | if pos >= len(self.tokens): |
| 170 | return self.tokens[-1] # Return EOF |
| 171 | return self.tokens[pos] |
| 172 | |
| 173 | def check(self, token_type: TokenType) -> bool: |
| 174 | """Check if the current token has the given type.""" |
| 175 | return self.current().type == token_type |
| 176 | |
| 177 | def match(self, *types: TokenType) -> bool: |
| 178 | """If current token matches any of the types, consume and return True.""" |
| 179 | for token_type in types: |
| 180 | if self.check(token_type): |
| 181 | self.advance() |
| 182 | return True |
| 183 | return False |
| 184 | |
| 185 | def advance(self) -> Token: |
| 186 | """Consume and return the current token.""" |
| 187 | token = self.current() |
| 188 | if not self.at_end(): |
| 189 | self.pos += 1 |
| 190 | return token |
| 191 | |
| 192 | def consume(self, token_type: TokenType, message: str = None) -> Token: |
| 193 | """Consume a token of the expected type, or raise an error.""" |
no outgoing calls