@@ -274,6 +274,38 @@ def _skip_whitespace(self) -> None:
274274 if match := _RE_WHITESPACE .match (self .data , self ._pos ):
275275 self ._take (match .end () - self ._pos )
276276
277+ def _skip_comment (self ) -> None :
278+ """Skip a comment starting at the current position, if present."""
279+ if self ._current () == "/" :
280+ peek = self ._peek ()
281+
282+ if peek == "*" :
283+ self ._take (2 ) # Consume /*
284+ end = self .data .find ("*/" , self ._pos )
285+ if end != - 1 :
286+ self ._take (end - self ._pos + 2 )
287+ else :
288+ self ._take (len (self .data ) - self ._pos )
289+
290+ elif peek == "/" :
291+ self ._take (2 ) # Consume //
292+ end = self .data .find ("\n " , self ._pos )
293+ if end != - 1 :
294+ self ._take (end - self ._pos )
295+ else :
296+ self ._take (len (self .data ) - self ._pos )
297+
298+ def _skip_whitespace_and_comments (self ) -> None :
299+ """Skip whitespace and comments."""
300+ while True :
301+ start_pos = self ._pos
302+ self ._skip_whitespace ()
303+ if self .eof :
304+ break
305+ self ._skip_comment ()
306+ if self .eof or self ._pos == start_pos :
307+ break
308+
277309 def _read_identifier (self ) -> str :
278310 """Read an identifier starting with a letter or underscore, followed by letters, digits, or underscores."""
279311 if match := _RE_IDENTIFIER .match (self .data , self ._pos ):
@@ -348,13 +380,6 @@ def _read_string(self) -> str:
348380
349381 return result
350382
351- def _read_angle_string (self ) -> str :
352- """Read an angle-bracket string for ``#include <...>``."""
353- self ._expect ("<" ) # Consume `<`
354- value = self ._read_until (">" , or_eof = False )
355- self ._expect (">" ) # Consume closing `>`
356- return f"<{ value } >"
357-
358383 def _read_preprocessor (self ) -> None :
359384 """Read a preprocessor directive starting with ``#``."""
360385 line = self ._line
@@ -373,23 +398,41 @@ def _read_preprocessor(self) -> None:
373398 return
374399
375400 # Read the keyword after #
376- self ._skip_whitespace ()
401+ self ._skip_whitespace_and_comments ()
377402 keyword = self ._read_identifier ()
378403
379404 if (token_type := _PP_KEYWORDS .get (keyword )) is None :
380405 raise self ._error (f"unknown preprocessor directive '#{ keyword } '" , line = line )
381406
382407 self ._emit (token_type , keyword , line , col )
383408
384- if token_type == TokenType .PP_INCLUDE :
409+ if token_type == TokenType .PP_DEFINE :
410+ self ._skip_whitespace_and_comments ()
411+
412+ if not (name := self ._read_identifier ()):
413+ raise self ._error ("expected identifier after '#define'" , line = line )
414+ self ._emit (TokenType .IDENTIFIER , name , line )
415+
416+ self ._skip_whitespace_and_comments ()
417+ if self .eof or self ._line != line :
418+ # No value, just a simple macro definition
419+ return
420+
421+ if (value := self ._read_until ("\n " )).strip ():
422+ self ._emit (TokenType .STRING , value , line )
423+
424+ elif token_type == TokenType .PP_INCLUDE :
385425 # Read include path — either "..." or <...>
386- self ._skip_whitespace ()
426+ self ._skip_whitespace_and_comments ()
387427
388428 ch = self ._current ()
389429 if ch == '"' or ch == "'" :
390430 value = self ._read_string ()
391431 elif ch == "<" :
392- value = self ._read_angle_string ()
432+ self ._expect ("<" ) # Consume `<`
433+ value = self ._read_until (">" , or_eof = False )
434+ self ._expect (">" ) # Consume closing `>`
435+ value = f"<{ value } >"
393436 else :
394437 raise self ._error ("expected include path after '#include'" , line = line )
395438
@@ -398,34 +441,11 @@ def _read_preprocessor(self) -> None:
398441 def tokenize (self ) -> list [Token ]:
399442 """Tokenize the input data and return a list of tokens."""
400443 while not self .eof :
401- self ._skip_whitespace ()
444+ self ._skip_whitespace_and_comments ()
402445 if self .eof :
403446 break
404447
405448 ch = self ._current ()
406-
407- # Skip comments
408- if ch == "/" :
409- peek = self ._peek ()
410-
411- if peek == "*" :
412- self ._take (2 ) # Consume /*
413- end = self .data .find ("*/" , self ._pos )
414- if end != - 1 :
415- self ._take (end - self ._pos + 2 )
416- else :
417- self ._take (len (self .data ) - self ._pos )
418- continue
419-
420- if peek == "/" :
421- self ._take (2 ) # Consume //
422- end = self .data .find ("\n " , self ._pos )
423- if end != - 1 :
424- self ._take (end - self ._pos )
425- else :
426- self ._take (len (self .data ) - self ._pos )
427- continue
428-
429449 line = self ._line
430450 col = self ._column
431451
@@ -439,7 +459,7 @@ def tokenize(self) -> list[Token]:
439459 elif ch in ("b" , "B" ) and self ._peek () in ("'" , '"' ):
440460 # Binary string literal like `b"..."` or `b'...'`
441461 self ._take () # Consume `b`
442- self ._emit (TokenType .BYTES , f"b' { self ._read_string ()} ' " , line , col )
462+ self ._emit (TokenType .BYTES , f"b{ self ._read_string ()!r } " , line , col )
443463
444464 elif ch .isdigit ():
445465 self ._emit (TokenType .NUMBER , self ._read_number (), line , col )
0 commit comments