@@ -44,13 +44,33 @@ private static ProtocolObject parseToken(Tokenizer t) {
4444 if (b == '[' ) return parseSubordinate (t );
4545 if (b == '"' ) return parseQuoted (t );
4646
47- // Atom
48- String atomValue = t .readAtom ();
49- ProtocolAtom atom = new ProtocolAtom (atomValue );
47+ // === Atom (may contain one [section]) ===
48+ String raw = t .readAtom (); // reads until whitespace
49+ int bracketIdx = raw .indexOf ('[' );
50+
51+ // Case 1: Looks like atom[section]
52+ if (bracketIdx > 0 && raw .lastIndexOf ('[' ) == bracketIdx ) { // exactly one [
53+ String base = raw .substring (0 , bracketIdx );
54+ if (isValidSectionBase (base )) {
55+ // Extract content between [ and ]
56+ int end = raw .lastIndexOf (']' );
57+ String sectionContent = (end > bracketIdx )
58+ ? raw .substring (bracketIdx + 1 , end )
59+ : raw .substring (bracketIdx + 1 );
60+
61+ ProtocolAtom atom = new ProtocolAtom (base );
62+ ProtocolSubordinate section = parseSubordinateContent (sectionContent );
63+ return new ProtocolSectionPartial (atom , section , null , null );
64+ }
65+ }
66+
67+ // Normal atom
68+ ProtocolAtom atom = new ProtocolAtom (raw );
5069
51- if (t .hasMore () &&
52- (t .isNextByteImmediate ((byte ) '[' ) || t .isNextByteImmediate ((byte ) '<' ))) {
53- return parseSectionPartial (atom , t );
70+ // Still allow <partial> after a normal atom
71+ if (t .hasMore () && t .isNextByteImmediate ((byte ) '<' )) {
72+ PartialData pd = parsePartialData (t );
73+ return new ProtocolSectionPartial (atom , null , pd .offset , pd .length );
5474 }
5575
5676 return atom ;
@@ -134,6 +154,15 @@ private static ProtocolSubordinate parseSubordinate(Tokenizer t) {
134154 return new ProtocolSubordinate (elements .toArray (new ProtocolObject [0 ]));
135155 }
136156
157+ /** Parse subordinate from a string that was already consumed inside [] */
158+ private static ProtocolSubordinate parseSubordinateContent (String content ) {
159+ if (content == null || content .isEmpty ()) {
160+ return new ProtocolSubordinate (new ProtocolObject [0 ]);
161+ }
162+ // Simple fallback: treat as single atom for now
163+ return new ProtocolSubordinate (new ProtocolObject []{new ProtocolAtom (content )});
164+ }
165+
137166 private static ProtocolSectionPartial parseSectionPartial (ProtocolAtom baseAtom , Tokenizer t ) {
138167 ProtocolSubordinate section = null ;
139168 Integer offset = null ;
@@ -251,23 +280,25 @@ public boolean isNextByteImmediate(byte expected) {
251280 }
252281
253282 public boolean isNextByteAfter (byte current , byte expected ) {
254- if (pos >= input .length || input [pos ] != current ) {
255- return false ;
256- }
283+ if (pos >= input .length || input [pos ] != current ) return false ;
257284 return pos + 1 < input .length && input [pos + 1 ] == expected ;
258285 }
259286
287+ /**
288+ * Reads until whitespace — allows [, ], !, #, $, etc.
289+ */
260290 public String readAtom () {
261291 skipWhitespace ();
262292 int start = pos ;
293+
263294 while (pos < input .length ) {
264295 byte b = input [pos ];
265- if (Character .isWhitespace (b & 0xFF ) || b == '(' || b == ')' || b == '[' || b == ']' ||
266- b == '"' || b == '{' || b == '}' || b == '<' || b == '>' ) {
296+ if (Character .isWhitespace (b & 0xFF )) {
267297 break ;
268298 }
269299 pos ++;
270300 }
301+
271302 return new String (input , start , pos - start , StandardCharsets .US_ASCII );
272303 }
273304
@@ -294,6 +325,19 @@ public byte nextByteNoSkip() {
294325 }
295326 }
296327
328+ // ====================== Helpers ======================
329+
330+ private static boolean isValidSectionBase (String s ) {
331+ if (s == null || s .isEmpty ()) return false ;
332+ for (char c : s .toCharArray ()) {
333+ if (!((c >= 'A' && c <= 'Z' ) || (c >= 'a' && c <= 'z' ) ||
334+ (c >= '0' && c <= '9' ) || c == '.' )) {
335+ return false ;
336+ }
337+ }
338+ return true ;
339+ }
340+
297341 // ====================== Helper Classes ======================
298342
299343 private static class LiteralHeader {
0 commit comments