9191from beet import LATEST_MINECRAFT_VERSION
9292from beet .core .utils import VersionNumber , split_version
9393from nbtlib import Byte , Double , Float , Int , Long , OutOfRange , Short , String
94- from tokenstream import InvalidSyntax , SourceLocation , TokenStream , set_location
94+ from tokenstream import (
95+ InvalidSyntax ,
96+ SourceLocation ,
97+ SyntaxRules ,
98+ TokenStream ,
99+ UnexpectedToken ,
100+ set_location ,
101+ Token ,
102+ )
95103
96104from .ast import (
97105 AstAdvancementPredicate ,
108116 AstDustColorTransitionParticleParameters ,
109117 AstDustParticleParameters ,
110118 AstEntityAnchor ,
119+ AstError ,
111120 AstFallingDustParticleParameters ,
112121 AstGamemode ,
113122 AstGreedy ,
@@ -638,6 +647,13 @@ def __init__(self, parser: str):
638647 self .parser = parser
639648
640649
650+ class InvalidSyntaxCollection (MechaError ):
651+ errors : list [InvalidSyntax ]
652+
653+ def __init__ (self , errors : list [InvalidSyntax ]):
654+ self .errors = errors
655+
656+
641657@overload
642658def delegate (parser : str ) -> Parser : ...
643659
@@ -681,6 +697,7 @@ def consume_line_continuation(stream: TokenStream) -> bool:
681697 return False
682698
683699
700+ # TODO: Attempt to move error recovery into AstCommand's parser
684701def parse_root (stream : TokenStream ) -> AstRoot :
685702 """Parse root."""
686703 start = stream .peek ()
@@ -689,20 +706,59 @@ def parse_root(stream: TokenStream) -> AstRoot:
689706 node = AstRoot (commands = AstChildren [AstCommand ]())
690707 return set_location (node , SourceLocation (0 , 1 , 1 ))
691708
692- commands : List [AstCommand ] = []
709+ errors : List [InvalidSyntax ] = []
710+ commands : List [AstCommand | AstError ] = []
693711
694712 with stream .ignore ("comment" ):
695713 for _ in stream .peek_until ():
696714 if stream .get ("newline" ):
697715 continue
698716 if stream .get ("eof" ):
699717 break
700- commands .append (delegate ("root_item" , stream ))
701718
702- node = AstRoot (commands = AstChildren (commands ))
703- return set_location (node , start , stream .current )
719+ result = parse_root_item (stream , errors )
720+ if result is not None :
721+ commands .append (result )
722+
723+ children = AstChildren (commands )
724+
725+ node = AstRoot (commands = children )
726+
727+ if stream .index < 0 :
728+ end_location = SourceLocation (1 , 0 , 0 )
729+ else :
730+ end_location = stream .current .end_location
704731
732+ return set_location (node , start , end_location )
705733
734+ def consume_error (stream : TokenStream , errors : list [InvalidSyntax ]):
735+ next = stream .peek ()
736+ with stream .syntax (unknown = r"." ):
737+ while next := stream .peek ():
738+ stream .expect ()
739+ if next .value == "\n " :
740+ break
741+ node = AstError (errors [- 1 ].location , errors [- 1 ].end_location , errors [- 1 ])
742+ return node
743+
744+
745+
746+ def parse_root_item (stream : TokenStream , errors : list [InvalidSyntax ], colon : bool = False ):
747+
748+ with stream .checkpoint () as commit :
749+ try :
750+ command : AstCommand = delegate ("root_item" , stream )
751+ commit ()
752+ return command
753+ except InvalidSyntax as exc :
754+ errors .append (exc )
755+
756+ if commit .rollback :
757+ if colon :
758+ with stream .syntax (colon = r":" ):
759+ return consume_error (stream , errors )
760+ return consume_error (stream , errors )
761+
706762def parse_command (stream : TokenStream ) -> AstCommand :
707763 """Parse command."""
708764 spec = get_stream_spec (stream )
0 commit comments