The text parser is difficult to follow and does not emit good error messages.
Consider re-implementing as a true lexer/parser pair.
<document> ::= <element>
<element> ::= <dictionary> | <array> | <data> | <extension> | <string>
<dictionary> ::= "{" <dictionary-body> "}"
<dictionary-body> ::= <dictionary-pair> | <dictionary-pair> <dictionary-body>
<dictionary-pair> ::= <string> "=" <element> ";"
<array> ::= "(" <array-body> ")"
<array-body> ::= <element> <opt-comma> | <element> "," <array-body>
<opt-comma> ::= "," | ""
<data> ::= "<" <base64-string> ">"
<base64-character> ::= "A" | "B" | ...
<base64-string> ::= <base64-character> | <base64-character> <base64-string>
<extension> ::= "<" "*" <extension-body> ">"
<extension-body> ::= "I" <extended-integer> | "R" <extended-real> | "B" <extended-bool> | "D" <extended-date>
<digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<integer> ::= <digit> | <digit> <integer>
<extended-integer> ::= <integer> | "-" <integer>
<extended-real> ::= <some floating point rules>
<extended-bool> ::= "Y" | "N"
<extended-date> ::= ...
<string> ::= '"' <quoted-string> '"' | <unquoted-string>
<quoted-character> ::= "A" | "B" | "'"
<quoted-string> ::= <quoted-character> | <quoted-character> <quoted-string>
<unquoted-character> ::= "A" | "B" | ...
<unquoted-string> ::= <unquoted-character> | <unquoted-character> <unquoted-string>