@@ -44,45 +44,48 @@ private static ProtocolObject parseToken(Tokenizer t) {
4444 if (b == '[' ) return parseSubordinate (t );
4545 if (b == '"' ) return parseQuoted (t );
4646
47- // === Read raw token until whitespace or ( or ) ===
47+ // Read full token until whitespace / ( / )
4848 String raw = t .readAtom ();
49- int bracketIdx = raw .indexOf ('[' );
5049
51- // Case: atom[section] (exactly one '[')
52- if (bracketIdx > 0 && raw .lastIndexOf ('[' ) == bracketIdx ) {
53- String base = raw .substring (0 , bracketIdx );
50+ // === Try to split atom[section]<partial> ===
51+ int partialStart = raw .indexOf ('<' );
52+ String beforePartial = (partialStart >= 0 ) ? raw .substring (0 , partialStart ) : raw ;
53+ String partialStr = (partialStart >= 0 ) ? raw .substring (partialStart ) : null ;
54+
55+ int bracketIdx = beforePartial .indexOf ('[' );
56+
57+ if (bracketIdx > 0 && beforePartial .lastIndexOf ('[' ) == bracketIdx ) {
58+ // Has section
59+ String base = beforePartial .substring (0 , bracketIdx );
5460 if (isValidSectionBase (base )) {
55- int end = raw .lastIndexOf (']' );
56- String sectionContent = (end > bracketIdx )
57- ? raw .substring (bracketIdx + 1 , end )
58- : raw .substring (bracketIdx + 1 );
61+ int close = beforePartial .lastIndexOf (']' );
62+ String sectionContent = (close > bracketIdx )
63+ ? beforePartial .substring (bracketIdx + 1 , close )
64+ : beforePartial .substring (bracketIdx + 1 );
5965
6066 ProtocolAtom atom = new ProtocolAtom (base );
6167 ProtocolSubordinate section = parseSubordinateContent (sectionContent );
6268
63- // Check for <partial> right after the section
64- Integer offset = null ;
65- Integer length = null ;
66- if (t .hasMore () && t .isNextByteImmediate ((byte ) '<' )) {
67- PartialData pd = parsePartialData (t );
68- offset = pd .offset ;
69- length = pd .length ;
69+ PartialData pd = null ;
70+ if (partialStr != null ) {
71+ pd = parsePartialFromString (partialStr );
7072 }
7173
72- return new ProtocolSectionPartial (atom , section , offset , length );
74+ return new ProtocolSectionPartial (atom , section ,
75+ pd != null ? pd .offset : null ,
76+ pd != null ? pd .length : null );
7377 }
7478 }
7579
76- // Normal atom (or atom with only <partial>)
77- ProtocolAtom atom = new ProtocolAtom (raw );
78-
79- // Partial <...> only allowed when base is A-Za-z0-9.
80- if (t .hasMore () && t .isNextByteImmediate ((byte ) '<' ) && isValidSectionBase (raw )) {
81- PartialData pd = parsePartialData (t );
80+ // No section → check for plain atom<partial>
81+ if (partialStr != null && isValidSectionBase (beforePartial )) {
82+ ProtocolAtom atom = new ProtocolAtom (beforePartial );
83+ PartialData pd = parsePartialFromString (partialStr );
8284 return new ProtocolSectionPartial (atom , null , pd .offset , pd .length );
8385 }
8486
85- return atom ;
87+ // Normal atom (including complex ones with [ ] ! # etc.)
88+ return new ProtocolAtom (raw );
8689 }
8790
8891 // ====================== Literal Parsers ======================
@@ -167,27 +170,20 @@ private static ProtocolSubordinate parseSubordinateContent(String content) {
167170 if (content == null || content .isEmpty ()) {
168171 return new ProtocolSubordinate (new ProtocolObject [0 ]);
169172 }
170- // For now treat the inside as a single atom (can be improved later)
171173 return new ProtocolSubordinate (new ProtocolObject []{new ProtocolAtom (content )});
172174 }
173175
174- private static PartialData parsePartialData (Tokenizer t ) {
175- t .consumeNoSkip ((byte ) '<' );
176- StringBuilder sb = new StringBuilder ();
177-
178- while (t .hasMore ()) {
179- byte b = t .nextByteNoSkip ();
180- if (b == '>' ) {
181- t .consumeNoSkip ((byte ) '>' );
182- break ;
183- }
184- sb .append ((char ) b );
176+ private static PartialData parsePartialFromString (String s ) {
177+ if (s == null || !s .startsWith ("<" ) || !s .endsWith (">" )) {
178+ return new PartialData (0 , null );
185179 }
180+ String inner = s .substring (1 , s .length () - 1 ).trim ();
181+ return parsePartialContent (inner );
182+ }
186183
187- String content = sb . toString (). trim ();
184+ private static PartialData parsePartialContent ( String content ) {
188185 Integer offset = null ;
189186 Integer length = null ;
190-
191187 if (!content .isEmpty ()) {
192188 try {
193189 if (content .contains ("." )) {
@@ -201,9 +197,7 @@ private static PartialData parsePartialData(Tokenizer t) {
201197 offset = 0 ;
202198 }
203199 }
204- if (offset == null ) offset = 0 ;
205-
206- return new PartialData (offset , length );
200+ return new PartialData (offset != null ? offset : 0 , length );
207201 }
208202
209203 private static ProtocolQuoted parseQuoted (Tokenizer t ) {
@@ -274,10 +268,7 @@ public boolean isNextByteAfter(byte current, byte expected) {
274268 return pos + 1 < input .length && input [pos + 1 ] == expected ;
275269 }
276270
277- /**
278- * Reads until whitespace, '(' or ')'.
279- * Atoms can NEVER contain ( or ).
280- */
271+ /** Reads full atom until whitespace, ( or ) — allows [ and < inside */
281272 public String readAtom () {
282273 skipWhitespace ();
283274 int start = pos ;
@@ -289,7 +280,6 @@ public String readAtom() {
289280 }
290281 pos ++;
291282 }
292-
293283 return new String (input , start , pos - start , StandardCharsets .US_ASCII );
294284 }
295285
0 commit comments