@@ -927,7 +927,7 @@ class Scanner {
927927 Token _scanTagDirectiveValue (LineScannerState start) {
928928 _skipBlanks ();
929929
930- final handle = _scanTagHandle (directive: true );
930+ final handle = _scanTagHandle (directive: true ).tagHandle ;
931931 if (! _isBlank) {
932932 throw YamlException ('Expected whitespace.' , _scanner.emptySpan);
933933 }
@@ -938,7 +938,7 @@ class Scanner {
938938 ///
939939 /// See: https://yaml.org/spec/1.2.2/#6822-tag-prefixes
940940 var prefix = _scanner.peekChar () == EXCLAMATION
941- ? _scanTagHandle (directive: true , isPrefix : true )
941+ ? _scanTagHandle (directive: true , isGlobalTagPrefix : true ).tagHandle
942942 : '' ;
943943
944944 prefix += _scanTagUri (); // Readability's sake
@@ -994,56 +994,121 @@ class Scanner {
994994
995995 /// Scans a [TokenType.tag] token.
996996 Token _scanTag () {
997- String ? handle;
998- String suffix;
999- var start = _scanner.state;
997+ final start = _scanner.state;
1000998
1001999 // Check if the tag is in the canonical form.
1002- if (_scanner.peekChar (1 ) == LEFT_ANGLE ) {
1003- // Eat '!<'.
1004- _scanner.readChar ();
1005- _scanner.readChar ();
1000+ if (_scanner.peekChar (1 ) == LEFT_ANGLE ) return _scanVerbatimTag (start);
10061001
1007- handle = '' ;
1008- suffix = _scanTagUri ();
1002+ // The tag has either the '!suffix' or the '!handle!suffix' form.
10091003
1010- _scanner.expect ('>' );
1011- } else {
1012- // The tag has either the '!suffix' or the '!handle!suffix' form.
1004+ // First, try to scan a handle.
1005+ final (: tagHandle, : isNamed) = _scanTagHandle ();
10131006
1014- // First, try to scan a handle.
1015- handle = _scanTagHandle () ;
1007+ String ? handle = tagHandle;
1008+ var suffix = '' ;
10161009
1017- if (handle.length > 1 && handle.startsWith ('!' ) && handle.endsWith ('!' )) {
1018- suffix = _scanTagUri (flowSeparators: false );
1019- } else {
1020- suffix = _scanTagUri (head: handle, flowSeparators: false );
1010+ if (isNamed) {
1011+ suffix = _scanTagUri (flowSeparators: false );
10211012
1022- // There was no explicit handle.
1023- if (suffix.isEmpty) {
1024- // This is the special '!' tag.
1025- handle = null ;
1026- suffix = '!' ;
1027- } else {
1028- handle = '!' ;
1029- }
1013+ /// Named tag handles cannot have an empty tag suffix.
1014+ ///
1015+ /// c-ns-shorthand-tag ::=
1016+ /// c-tag-handle
1017+ /// ns-tag-char+
1018+ ///
1019+ /// See: https://yaml.org/spec/1.2.2/#691-node-tags
1020+ if (suffix.isEmpty) {
1021+ throw YamlException (
1022+ 'Expected a non-empty shorthand suffix' ,
1023+ _scanner.spanFrom (start),
1024+ );
1025+ }
1026+ } else {
1027+ suffix = _scanTagUri (head: handle, flowSeparators: false );
1028+
1029+ // There was no explicit handle.
1030+ if (suffix.isEmpty) {
1031+ // This is the special '!' tag.
1032+ handle = null ;
1033+ suffix = '!' ;
1034+ } else {
1035+ handle = '!' ; // Not named.
10301036 }
10311037 }
10321038
10331039 // libyaml insists on whitespace after a tag, but example 7.2 indicates
10341040 // that it's not required: http://yaml.org/spec/1.2/spec.html#id2786720.
1041+ return TagToken (
1042+ _scanner.spanFrom (start),
1043+ handle,
1044+ suffix,
1045+ isVerbatim: false ,
1046+ );
1047+ }
1048+
1049+ /// Scans a canonical [TokenType.tag] token whose [start] position is
1050+ /// provided by [_scanTag] .
1051+ TagToken _scanVerbatimTag (LineScannerState start) {
1052+ // Eat '!<'.
1053+ final buffer = StringBuffer ()
1054+ ..writeCharCode (_scanner.readChar ())
1055+ ..writeCharCode (_scanner.readChar ());
1056+
1057+ var tagUri = '' ;
10351058
1036- return TagToken (_scanner.spanFrom (start), handle, suffix);
1059+ if (_scanner.peekChar () == EXCLAMATION ) {
1060+ tagUri = _scanTagHandle (isVerbatimTag: true ).tagHandle; // !<!foo>
1061+
1062+ if (tagUri == '!' ) {
1063+ throw YamlException (
1064+ 'A non-specific tag cannot be declared as a verbatim tag' ,
1065+ _scanner.spanFrom (start),
1066+ );
1067+ }
1068+ } else {
1069+ tagUri = _scanTagUri (); // !<foo:uri>
1070+
1071+ // Expect !<foo:uri> to be !<tag:yaml.org,2002:*>
1072+ switch (tagUri.replaceFirst ('tag:yaml.org,2002:' , '' )) {
1073+ case 'map' || 'seq' || 'str' || 'null' || 'bool' || 'int' || 'float' :
1074+ break ;
1075+
1076+ default :
1077+ throw YamlException (
1078+ 'Invalid tag uri used as a verbatim tag' ,
1079+ _scanner.spanFrom (start),
1080+ );
1081+ }
1082+ }
1083+
1084+ _scanner.expect ('>' );
1085+ buffer.write ('$tagUri >' );
1086+
1087+ return TagToken (
1088+ _scanner.spanFrom (start),
1089+ '' ,
1090+ buffer.toString (),
1091+ isVerbatim: true ,
1092+ );
10371093 }
10381094
1039- /// Scans a tag handle.
1095+ /// Scans a tag handle and explicitly indicates if the handle was a named
1096+ /// tag handle.
10401097 ///
1041- /// If [isPrefix ] is `true` , the handle can never be a secondary or named
1042- /// tag handle. Such handles cannot be used in a global tag's local tag
1098+ /// If [isGlobalTagPrefix ] is `true` , the handle can never be a secondary or
1099+ /// named tag handle. Such handles cannot be used in a global tag's local tag
10431100 /// prefix.
10441101 ///
1102+ /// If [isVerbatimTag] is `true` , `isNamed` will always be `false` . Verbatim
1103+ /// tags expect the next non-uri char to be `>` .
1104+ ///
10451105 /// See: https://yaml.org/spec/1.2/spec.html#id2783273
1046- String _scanTagHandle ({bool directive = false , bool isPrefix = false }) {
1106+ ({bool isNamed, String tagHandle}) _scanTagHandle ({
1107+ bool directive = false ,
1108+ bool isGlobalTagPrefix = false ,
1109+ bool isVerbatimTag = false ,
1110+ }) {
1111+ var named = false ;
10471112 final start = _scanner.state;
10481113 _scanner.expect ('!' );
10491114
@@ -1053,7 +1118,7 @@ class Scanner {
10531118 // TODO: Do we really need this highlighted?
10541119 final char = _scanner.readCodePoint ();
10551120
1056- if (isPrefix ) {
1121+ if (isGlobalTagPrefix ) {
10571122 throw YamlException (
10581123 'A local tag used as a global tag prefix cannot have a secondary tag'
10591124 ' handle' ,
@@ -1069,14 +1134,16 @@ class Scanner {
10691134 /// For directives, expect the "!" for a named tag. No other handle can
10701135 /// have a tag uri here. For a tag shorthand anywhere else, this needs to
10711136 /// be a separation space (tab included) or line break or nothing.
1072- if (buffer.length > 1 && ! _isBlankOrEnd) {
1137+ ///
1138+ /// Verbatim tags expect '>'.
1139+ if (! isVerbatimTag && buffer.length > 1 && ! _isBlankOrEnd) {
10731140 _scanner.expect ('!' );
10741141
10751142 /// A tag directive doesn't allow a local tag with a named handle as a
10761143 /// local tag prefix.
10771144 ///
10781145 /// See: https://yaml.org/spec/1.2/spec.html#id2783273
1079- if (directive && isPrefix ) {
1146+ if (directive && isGlobalTagPrefix ) {
10801147 throw YamlException (
10811148 'A local tag used as a global tag prefix cannot have a named tag'
10821149 ' handle' ,
@@ -1085,10 +1152,11 @@ class Scanner {
10851152 }
10861153
10871154 buffer.write ('!' );
1155+ named = true ;
10881156 }
10891157 }
10901158
1091- return buffer.toString ();
1159+ return (isNamed : named, tagHandle : buffer.toString () );
10921160 }
10931161
10941162 /// Scans a tag URI.
0 commit comments