%- % Copyright (c) 2012-2014 Yakaz % Copyright (c) 2016 Jean-Sébastien Pédron % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions % are met: % 1. Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % 2. Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND % ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE % FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL % DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS % OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) % HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT % LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY % OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF % SUCH DAMAGE. -module(yamerl_parser). -include("yamerl_errors.hrl"). -include("yamerl_tokens.hrl"). -include("internal/yamerl_parser.hrl"). %% Public API. -export([ new/1, new/2, string/1, string/2, file/1, file/2, next_chunk/2, next_chunk/3, last_chunk/2, get_token_fun/1, set_token_fun/2, option_names/0 ]). %% ------------------------------------------------------------------- %% Exported types. %% ------------------------------------------------------------------- %% FIXME: %% This type should be "-opaque". However, up-to Erlang R15B03, an issue %% with either this code or Dialyzer prevents us from declaring it %% properly: Dialyzer reports warning regarding the stream_state_fun() %% type and several guard expression which will never match. -type yamerl_parser() :: #yamerl_parser{}. -export_type([ yamerl_parser/0, yamerl_parser_option/0, yamerl_parser_token_fun/0 ]). %% ------------------------------------------------------------------- %% Secondary records to store the scanner state. %% ------------------------------------------------------------------- -record(directive_ctx, { line :: position(), col :: position(), name = "" :: string() }). -record(yaml_directive_ctx, { line :: position(), col :: position(), major :: non_neg_integer(), minor :: non_neg_integer() }). -record(tag_directive_ctx, { line :: position(), col :: position(), handle :: tag_handle() | [] | undefined, prefix :: tag_prefix() | [] | undefined }). -record(reserved_directive_ctx, { line :: position(), col :: position(), name :: string(), current :: string(), args = [] :: [string()], args_count = 0 :: non_neg_integer() }). -type whitespace() :: [9 | 10 | 32]. -record(block_scalar_hd_ctx, { style = literal :: literal | folded, line = 1 :: position(), %% Line where the token starts. col = 1 :: position(), %% Column where the token starts. chomp :: strip | keep | undefined, %% Chomping indicator. indent :: pos_integer() %% Indentation indicator. | {tmp, pos_integer()} | undefined, in_comment = false :: boolean() %% Trailing comment. }). -record(block_scalar_ctx, { style = literal :: literal | folded, line = 1 :: position(), %% Line where the token starts. col = 1 :: position(), %% Column where the token starts. endline = 1 :: position(), %% Line where the token ends. endcol = 1 :: position(), %% Column where the token ends. chomp = clip :: strip | keep | clip, %% Chomping method. indent :: pos_integer() | undefined, %% Block indent. longest_empty = 0 :: non_neg_integer(), %% Longest leading empty line. newline = false :: boolean(), %% Met a newline character. spaces = "" :: whitespace(), %% Last white spaces seen. more_indent = false :: boolean(), %% Last line is more indented. output = "" :: string() %% Already parsed characters. }). -record(flow_scalar_ctx, { style = plain :: double_quoted | single_quoted | plain, line = 1 :: position(), %% Line where the token starts. col = 1 :: position(), %% Column where the token starts. endline = 1 :: position(), %% Line where the token ends. endcol = 1 :: position(), %% Column where the token ends. surrogate :: 16#d800..16#dbff | undefined, %% High surrogate. newline = false :: boolean(), %% Met a newline character. spaces = "" :: whitespace(), %% Last white spaces seen. output = "" :: string() %% Already parsed characters. }). -record(anchor_ctx, { type :: anchor | alias, line :: position(), col :: position(), output = "" :: string() }). -record(tag_ctx, { line :: position(), col :: position(), prefix :: string() | undefined, suffix :: string() | tag_uri() }). -define(IO_BLOCKSIZE, 4096). %% Common filesystem blocksize. -define(FAKE_IMPL_KEY, #impl_key{}). -define(IN_BLOCK_CTX(P), (is_record(P#yamerl_parser.cur_coll, bcoll))). -define(IN_FLOW_CTX(P), (is_record(P#yamerl_parser.cur_coll, fcoll))). -define(IS_SPACE(C), (C == $\s orelse C == $\t)). -define(IS_NEWLINE(C), (C == $\n orelse C == $\r)). -define(IS_NEWLINE_11(C), (C == 16#85 orelse C == 16#2028 orelse C == 16#2029)). -define(IS_FLOW_INDICATOR(C), ( C == $[ orelse C == $] orelse C == ${ orelse C == $} orelse C == $,)). -define(IS_HEXADECIMAL(C), ( (O1 >= $0 andalso O1 =< $9) orelse (O1 >= $a andalso O1 =< $f) orelse (O1 >= $A andalso O1 =< $F) )). -define(IS_URI_CHAR(C), ( (C >= $a andalso C =< $z) orelse (C >= $A andalso C =< $Z) orelse (C >= $0 andalso C =< $9) orelse C == $- orelse C == $% orelse C == $# orelse C == $; orelse C == $/ orelse C == $? orelse C == $: orelse C == $@ orelse C == $& orelse C == $= orelse C == $+ orelse C == $$ orelse C == $, orelse C == $_ orelse C == $. orelse C == $! orelse C == $~ orelse C == $* orelse C == $' orelse C == $( orelse C == $) orelse C == $[ orelse C == $] )). -define(IS_BOM(C), (C == 16#feff)). -define(IS_HIGH_SURROGATE(C), (C >= 16#d800 andalso C =< 16#dbff)). -define(IS_LOW_SURROGATE(C), (C >= 16#dc00 andalso C =< 16#dfff)). -define(MISSING_ENTRY(S), ( S#yamerl_parser.pending_entry andalso S#yamerl_parser.last_tag == undefined )). -define(MISSING_KVPAIR(S), ( S#yamerl_parser.waiting_for_kvpair andalso not element(#impl_key.possible, hd(S#yamerl_parser.ik_stack)) )). -define(IS_JSON_LIKE(T), ( (is_record(T, yamerl_scalar) andalso (T#yamerl_scalar.substyle == single_quoted orelse T#yamerl_scalar.substyle == double_quoted)) orelse (is_record(T, yamerl_collection_end) andalso T#yamerl_collection_end.style == flow) )). -define(DEFAULT_TAG(U, L, C), #yamerl_tag{ uri = U, line = L, column = C }). -define(NEXT_COL(Co, De, Count), {Co + Count, De + Count}). -define(NEXT_LINE(Ch, Li, De, P), case Ch of [$\r, $\n | R] -> {R, Li + 1, 1, De + 2}; [_ | R] -> {R, Li + 1, 1, De + 1} end). %% %% We use macros instead of functions for a few #yamerl_parser updates %% to take advantage of the optimization described in §3.5 in the %% Efficiency Guide. %% -define(PUSH_FAKE_IMPL_KEY(P), P#yamerl_parser{ik_stack = [?FAKE_IMPL_KEY | P#yamerl_parser.ik_stack]}). -define(POP_IMPL_KEY(P), P#yamerl_parser{ik_stack = tl(P#yamerl_parser.ik_stack)}). -define(ALLOW_IMPL_KEY(P, F), P#yamerl_parser{ik_allowed = F}). -define(FLUSH_TO_PARSER(Ch, Li, Co, De, P), P#yamerl_parser{ chars = Ch, chars_len = P#yamerl_parser.chars_len - De, chars_idx = P#yamerl_parser.chars_idx + De, line = Li, col = Co }). -define(WARN_IF_NON_ASCII_LINE_BREAK(Ch, Li, Co, P), case Ch of [NL | _] when ?IS_NEWLINE_11(NL) -> %% Non-ASCII line break in a YAML 1.2 document. Err = #yamerl_parsing_error{ type = warning, name = non_ascii_line_break, line = Li, column = Co }, add_error(P, Err, "Use of non-ASCII line break is not supported anymore starting " "with YAML 1.2; treated as non-break character", []); _ -> P end). -define(BLOCK_SCALAR_DEFAULT_TAG(L, C), ?DEFAULT_TAG({non_specific, "!"}, L, C)). -define(PLAIN_SCALAR_DEFAULT_TAG(L, C), ?DEFAULT_TAG({non_specific, "?"}, L, C)). -define(FLOW_SCALAR_DEFAULT_TAG(L, C), ?DEFAULT_TAG({non_specific, "!"}, L, C)). -define(COLL_SCALAR_DEFAULT_TAG(L, C), ?DEFAULT_TAG({non_specific, "?"}, L, C)). %% ------------------------------------------------------------------- %% Public API: chunked stream scanning. %% ------------------------------------------------------------------- -spec new(Source) -> Parser | no_return() when Source :: term(), Parser :: yamerl_parser(). new(Source) -> new(Source, []). -spec new(Source, Options) -> Parser | no_return() when Source :: term(), Options :: [yamerl_parser_option()], Parser :: yamerl_parser(). new(Source, Options) -> Options0 = proplists:unfold(Options), check_options(Options0), #yamerl_parser{ source = Source, options = Options0, stream_state = fun start_stream/5, token_fun = proplists:get_value(token_fun, Options0, acc) }. -spec next_chunk(Parser, Chunk) -> Ret | no_return() when Parser :: yamerl_parser(), Chunk :: unicode_binary(), Ret :: {continue, New_Parser}, New_Parser :: yamerl_parser(). next_chunk(Parser, Chunk) -> next_chunk(Parser, Chunk, false). -spec next_chunk(Parser, Chunk, Last_Chunk) -> Ret | no_return() when Parser :: yamerl_parser(), Chunk :: unicode_binary(), Last_Chunk :: boolean(), Ret :: {continue, New_Parser} | New_Parser, New_Parser :: yamerl_parser(). next_chunk(Parser, <<>>, false) -> %% No need to proceed further without any data. do_return(Parser); next_chunk(#yamerl_parser{raw_data = Data} = Parser, Chunk, EOS) -> %% Append new data to the remaining data. Those data must then be %% decoded to Unicode characters. New_Data = list_to_binary([Data, Chunk]), Parser1 = Parser#yamerl_parser{ raw_data = New_Data, raw_eos = EOS }, decode_unicode(Parser1). -spec last_chunk(Parser, Chunk) -> Ret | no_return() when Parser :: yamerl_parser(), Chunk :: unicode_binary(), Ret :: {continue, New_Parser} | New_Parser, New_Parser :: yamerl_parser(). last_chunk(Parser, Chunk) -> next_chunk(Parser, Chunk, true). %% ------------------------------------------------------------------- %% Public API: common stream sources. %% ------------------------------------------------------------------- -spec string(String) -> Parser | no_return() when String :: unicode_data(), Parser :: yamerl_parser(). string(String) -> string(String, []). -spec string(String, Options) -> Parser | no_return() when String :: unicode_data(), Options :: [yamerl_parser_option()], Parser :: yamerl_parser(). string(String, Options) when is_binary(String) -> Parser = new(string, Options), next_chunk(Parser, String, true); string(String, Options) when is_list(String) -> string(unicode:characters_to_binary(String), Options). -spec file(Filename) -> Parser | no_return() when Filename :: string(), Parser :: yamerl_parser(). file(Filename) -> file(Filename, []). -spec file(Filename, Options) -> Parser | no_return() when Filename :: string(), Options :: [yamerl_parser_option()], Parser :: yamerl_parser(). file(Filename, Options) -> Parser = new({file, Filename}, Options), Blocksize = proplists:get_value(io_blocksize, Options, ?IO_BLOCKSIZE), case file:open(Filename, [read, binary]) of {ok, FD} -> %% The file is read in binary mode. The scanner is %% responsible for determining the encoding and converting %% the stream accordingly. file2(Parser, FD, Blocksize); {error, Reason} -> Error2 = #yamerl_parsing_error{ name = file_open_failure, extra = [{error, Reason}] }, Parser2 = add_error(Parser, Error2, "Failed to open file \"~s\": ~s", [Filename, file:format_error(Reason)]), do_return(Parser2) end. file2(#yamerl_parser{source = {file, Filename}} = Parser, FD, Blocksize) -> case file:read(FD, Blocksize) of {ok, Data} -> %% If the chunk is smaller than the requested size, we %% reached EOS. EOS = byte_size(Data) < Blocksize, if EOS -> file:close(FD); true -> ok end, try case next_chunk(Parser, Data, EOS) of {continue, Parser1} -> file2(Parser1, FD, Blocksize); Parser1 -> Parser1 end catch throw:{yamerl_parser, _} = Exception -> %% Close the file and throw the exception again. file:close(FD), throw(Exception) end; eof -> file:close(FD), next_chunk(Parser, <<>>, true); {error, Reason} -> Error = #yamerl_parsing_error{ name = file_read_failure, extra = [{error, Reason}] }, Parser1 = add_error(Parser, Error, "Failed to read file \"~s\": ~s", [Filename, file:format_error(Reason)]), do_return(Parser1) end. %% ------------------------------------------------------------------- %% Public API: get/set the token function. %% ------------------------------------------------------------------- get_token_fun(#yamerl_parser{token_fun = Fun}) -> Fun. set_token_fun(Parser, Fun) when is_function(Fun, 1) -> Parser#yamerl_parser{token_fun = Fun}. %% ------------------------------------------------------------------- %% Determine encoding and decode Unicode. %% ------------------------------------------------------------------- decode_unicode(#yamerl_parser{stream_state = State, encoding = Encoding, raw_data = Data, raw_idx = Raw_Index, chars = Chars, chars_len = Chars_Count} = Parser) when Encoding /= undefined -> %% We have previously determined the encoding of the stream. We can %% decode the Unicode characters from the raw data. Ret = unicode:characters_to_list(Data, Encoding), {Parser2, Chars2} = case Ret of {Reason, New_Chars, Remaining_Data} -> %% Ok, we have more characters to scan! Raw_Index1 = Raw_Index + (byte_size(Data) - byte_size(Remaining_Data)), Parser1 = Parser#yamerl_parser{ raw_data = Remaining_Data, raw_idx = Raw_Index1, chars_len = Chars_Count + length(New_Chars) }, Chars1 = Chars ++ New_Chars, case Reason of incomplete -> {Parser1, Chars1}; error -> Error = #yamerl_parsing_error{ name = invalid_unicode, extra = [{byte, Raw_Index1 + 1}] }, { add_error(Parser1, Error, "Invalid Unicode character at byte #~b", [Raw_Index1 + 1]), Chars1 } end; New_Chars -> %% Ok, we have more characters to scan! Raw_Index1 = Raw_Index + byte_size(Data), Parser1 = Parser#yamerl_parser{ raw_data = <<>>, raw_idx = Raw_Index1, chars_len = Chars_Count + length(New_Chars) }, Chars1 = Chars ++ New_Chars, {Parser1, Chars1} end, State(Chars2, Parser2#yamerl_parser.line, Parser2#yamerl_parser.col, 0, Parser2); decode_unicode(#yamerl_parser{raw_data = Data, raw_eos = EOS} = Parser) when ((EOS == false andalso byte_size(Data) >= 4) orelse EOS == true) -> %% We have enough (maybe even all) data to determine the encoding. %% Let's check if the stream starts with a BOM. {Encoding, Length} = get_encoding(Data), %% The stream may start with a BOM: remove it. <<_:Length/binary, New_Data/binary>> = Data, Parser1 = Parser#yamerl_parser{ encoding = Encoding, raw_data = New_Data, raw_idx = Length, chars_idx = 1 }, decode_unicode(Parser1); decode_unicode(Parser) -> %% We don't have enough data to determine the encoding. We ask for %% more data. do_return(Parser). get_encoding(<<16#00, 16#00, 16#fe, 16#ff, _/binary>>) -> {{utf32, big}, 4}; get_encoding(<<16#00, 16#00, 16#00, _, _/binary>>) -> {{utf32, big}, 0}; get_encoding(<<16#ff, 16#fe, 16#00, 16#00, _/binary>>) -> {{utf32, little}, 4}; get_encoding(<<_, 16#00, 16#00, 16#00, _/binary>>) -> {{utf32, little}, 0}; get_encoding(<<16#fe, 16#ff, _, _, _/binary>>) -> {{utf16, big}, 2}; get_encoding(<<16#00, _, _, _, _/binary>>) -> {{utf16, big}, 0}; get_encoding(<<16#ff, 16#fe, _, _, _/binary>>) -> {{utf16, little}, 2}; get_encoding(<<_, 16#00, _, _, _/binary>>) -> {{utf16, little}, 0}; get_encoding(<<16#ef, 16#bb, 16#bf, _, _/binary>>) -> {utf8, 3}; get_encoding(_) -> {utf8, 0}. %% ------------------------------------------------------------------- %% Scan characters and emit tokens. %% ------------------------------------------------------------------- %% %% Stream start/end. %% start_stream(Chars, Line, Col, Delta, #yamerl_parser{encoding = Encoding} = Parser) -> %% The very first token to emit is the stream start. The stream %% encoding is provided as an attribute. The encoding may appear at %% the start of each document but can't be changed: all documents %% must have the same encoding! Parser1 = ?PUSH_FAKE_IMPL_KEY(Parser), Parser2 = ?ALLOW_IMPL_KEY(Parser1, true), Parser3 = setup_default_tags(Parser2), Token = #yamerl_stream_start{ encoding = Encoding, line = Line, column = Col }, Parser4 = queue_token(Parser3, Token), find_next_token(Chars, Line, Col, Delta, Parser4). end_stream(Chars, Line, Col, Delta, #yamerl_parser{last_token_endline = Last_Line, last_token_endcol = Last_Col} = Parser) -> %% Reset cursor on column 0 to close all opened block collections. Parser1 = check_for_closed_block_collections(Chars, Line, Col, Delta, Parser, 0), Parser2 = remove_impl_key_pos(Parser1), Parser3 = ?ALLOW_IMPL_KEY(Parser2, false), %% Set the line and column number to the last token endline/endcol %% number. This is useful when parsing a file: the last line is %% often terminated by a newline character. Thanks to this, the %% stream_end token will be on the last token line. Token = #yamerl_stream_end{ line = Last_Line, column = Last_Col }, Parser4 = queue_token(Parser3, Token), return(Chars, Line, Col, Delta, Parser4). %% %% Next token. %% find_next_token(Chars, Line, Col, Delta, #yamerl_parser{endpos_set_by_token = true} = Parser) -> %% The line and column numbers where the last token ends was already %% set during token parsing. Parser1 = Parser#yamerl_parser{ endpos_set_by_token = false }, do_find_next_token(Chars, Line, Col, Delta, Parser1); find_next_token(Chars, Line, Col, Delta, Parser) -> %% Record the line and columns numbers where the last token ends. %% It's used to determine if an implicit key would span several %% lines and therefore would be unacceptable. Parser1 = Parser#yamerl_parser{ endpos_set_by_token = false, last_token_endline = Line, last_token_endcol = Col }, do_find_next_token(Chars, Line, Col, Delta, Parser1). %% Skip spaces. do_find_next_token([$\s | Rest], Line, Col, Delta, Parser) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), do_find_next_token(Rest, Line, Col1, Delta1, Parser); %% Skip tabs only when they're separation spaces, not indentation. do_find_next_token([$\t | Rest], Line, Col, Delta, #yamerl_parser{ik_allowed = IK_Allowed} = Parser) when ?IN_FLOW_CTX(Parser) orelse not IK_Allowed -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), do_find_next_token(Rest, Line, Col1, Delta1, Parser); %% Skip comments. do_find_next_token([$# | _] = Chars, Line, Col, Delta, Parser) -> parse_comment(Chars, Line, Col, Delta, Parser); %% Continue with next line. do_find_next_token(Chars, Line, Col, Delta, #yamerl_parser{missed_nl = true} = Parser) -> Parser1 = Parser#yamerl_parser{ missed_nl = false }, Parser2 = if ?IN_BLOCK_CTX(Parser1) -> ?ALLOW_IMPL_KEY(Parser1, true); true -> Parser1 end, do_find_next_token(Chars, Line, Col, Delta, Parser2); do_find_next_token([$\r] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser) -> %% Can't be sure it's a newline. It may be followed by a LF. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_find_next_token/5); do_find_next_token([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Chars1, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Parser1 = if ?IN_BLOCK_CTX(Parser) -> ?ALLOW_IMPL_KEY(Parser, true); true -> Parser end, do_find_next_token(Chars1, Line1, Col1, Delta1, Parser1); %% End-of-stream reached. do_find_next_token([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser) -> end_stream(Chars, Line, Col, Delta, Parser); %% Wait for more data. do_find_next_token([] = Chars, Line, Col, Delta, Parser) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_find_next_token/5); %% Next token found! do_find_next_token(Chars, Line, Col, Delta, Parser) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), Parser2 = check_for_closed_block_collections(Chars, Line, Col, Delta, Parser1, Col), determine_token_type(Chars, Line, Col, Delta, Parser2). %% %% Token type. %% %% Not enough data to determine the token type. determine_token_type(Chars, Line, Col, Delta, #yamerl_parser{chars_len = Len, raw_eos = false} = Parser) when (Len - Delta) < 4 -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun determine_token_type/5); %% BOM, before a document only! determine_token_type([C | Rest], Line, Col, Delta, #yamerl_parser{doc_started = false} = Parser) when ?IS_BOM(C) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser); determine_token_type([C | Rest], Line, Col, Delta, #yamerl_parser{doc_started = true} = Parser) when ?IS_BOM(C) -> %% A BOM is forbidden after the document start. Because it's not %% fatal during parsing, we only add a warning. Note that the YAML %% specification considers this to be an error. Error = #yamerl_parsing_error{ type = warning, name = bom_after_doc_start, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "A BOM must not appear inside a document", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser1); %% Directives end indicator. determine_token_type([$-, $-, $-, C | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{doc_version = Version} = Parser) when ?IS_NEWLINE(C) orelse ?IS_SPACE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> parse_document_sep(Chars, Line, Col, Delta, Parser, directives_end); %% Document end indicator. determine_token_type([$., $., $., C | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{doc_version = Version} = Parser) when ?IS_NEWLINE(C) orelse ?IS_SPACE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> parse_document_sep(Chars, Line, Col, Delta, Parser, document_end); %% Directive indicator. determine_token_type([$% | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{doc_started = false} = Parser) -> parse_directive(Chars, Line, Col, Delta, Parser); %% Flow sequence indicators. determine_token_type([$[ | _] = Chars, Line, Col, Delta, Parser) -> parse_flow_collection_start(Chars, Line, Col, Delta, Parser, sequence); determine_token_type([$] | _] = Chars, Line, Col, Delta, Parser) -> parse_flow_collection_end(Chars, Line, Col, Delta, Parser, sequence); %% Flow mapping indicators. determine_token_type([${ | _] = Chars, Line, Col, Delta, Parser) -> parse_flow_collection_start(Chars, Line, Col, Delta, Parser, mapping); determine_token_type([$} | _] = Chars, Line, Col, Delta, Parser) -> parse_flow_collection_end(Chars, Line, Col, Delta, Parser, mapping); %% Flow collection entry indicator. determine_token_type([$, | _] = Chars, Line, Col, Delta, Parser) -> parse_flow_entry(Chars, Line, Col, Delta, Parser); %% Block collection entry indicator. determine_token_type([$-, C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser) when ?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> parse_block_entry(Chars, Line, Col, Delta, Parser); %% Mapping key indicator. determine_token_type([$?, C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser) when ?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> parse_mapping_key(Chars, Line, Col, Delta, Parser); %% Mapping value indicator. determine_token_type([$:, C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser) when ?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse ?IS_FLOW_INDICATOR(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> parse_mapping_value(Chars, Line, Col, Delta, Parser); determine_token_type([$: | _] = Chars, Line, Col, Delta, #yamerl_parser{last_is_json_like = true} = Parser) when ?IN_FLOW_CTX(Parser) -> %% This is a key: value pair indicator only when the last token is %% JSON-like and we're in flow context. parse_mapping_value(Chars, Line, Col, Delta, Parser); %% Anchor and alias indicator. determine_token_type([$& | _] = Chars, Line, Col, Delta, Parser) -> parse_anchor_or_alias(Chars, Line, Col, Delta, Parser, anchor); determine_token_type([$* | _] = Chars, Line, Col, Delta, Parser) -> parse_anchor_or_alias(Chars, Line, Col, Delta, Parser, alias); %% Tag indicator. determine_token_type([$! | _] = Chars, Line, Col, Delta, Parser) -> parse_tag(Chars, Line, Col, Delta, Parser); %% Block scalar. determine_token_type([$| | _] = Chars, Line, Col, Delta, Parser) -> parse_block_scalar(Chars, Line, Col, Delta, Parser, literal); determine_token_type([$> | _] = Chars, Line, Col, Delta, Parser) -> parse_block_scalar(Chars, Line, Col, Delta, Parser, folded); %% Single-quoted flow scalar. determine_token_type([$' | _] = Chars, Line, Col, Delta, Parser) -> parse_flow_scalar(Chars, Line, Col, Delta, Parser, single_quoted); %% Double-quoted flow scalar. determine_token_type([$" | _] = Chars, Line, Col, Delta, Parser) -> parse_flow_scalar(Chars, Line, Col, Delta, Parser, double_quoted); %% Reserved indicators. %% We add a warning and parse it as a plain scalar. determine_token_type([C | _] = Chars, Line, Col, Delta, Parser) when C == $@ orelse C == $` -> Error = #yamerl_parsing_error{ name = reserved_indicator, type = warning, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "The reserved indicator \"~c\" is not allowed at the " "beginning of a plain scalar", [C]), parse_flow_scalar(Chars, Line, Col, Delta, Parser1, plain); %% Plain flow scalar. determine_token_type(Chars, Line, Col, Delta, Parser) -> parse_flow_scalar(Chars, Line, Col, Delta, Parser, plain). %% ------------------------------------------------------------------- %% Directives and document ends. %% ------------------------------------------------------------------- parse_document_sep([_, _, _ | Rest] = Chars, Line, Col, Delta, Parser, Type) -> %% Reset cursor on column 0 to close all opened block collections. Parser1 = check_for_closed_block_collections(Chars, Line, Col, Delta, Parser, 0), Parser2 = remove_impl_key_pos(Parser1), Parser3 = ?ALLOW_IMPL_KEY(Parser2, false), Parser4 = case Type of directives_end -> start_doc(Parser3, Line, Col, tail); document_end -> end_doc(Parser3, Line, Col, tail) end, {Col1, Delta1} = ?NEXT_COL(Col, Delta, 3), find_next_token(Rest, Line, Col1, Delta1, Parser4). start_doc(#yamerl_parser{doc_started = true} = Parser, Line, Col, Insert_At) -> %% A document is already opened: we must close it before starting a %% new one. Parser1 = end_doc(Parser, Line, Col, Insert_At), start_doc(Parser1, Line, Col, next_insert_at(Insert_At, 1)); start_doc( #yamerl_parser{options = Options, doc_version = Version, tags = Tags} = Parser, Line, Col, Insert_At) -> %% When a document starts, we set the version to %% ?IMPLICIT_DOC_VERSION if no YAML directive were specified. Forced = proplists:get_value(doc_version, Options), Version1 = case Version of _ when Forced /= undefined -> Forced; undefined -> ?IMPLICIT_YAML_VERSION; _ -> Version end, Token = #yamerl_doc_start{ version = Version1, tags = Tags, line = Line, column = Col }, Parser1 = case Version1 of {Major, Minor} when Major < ?MIN_YAML_MAJOR_VERSION_SUPPORTED orelse (Major == ?MIN_YAML_MAJOR_VERSION_SUPPORTED andalso Minor < ?MIN_YAML_MINOR_VERSION_SUPPORTED) -> %% The document's version is not supported at all (below %% minimum supported version). Error = #yamerl_parsing_error{ name = version_not_supported, token = Token, line = Line, column = Col }, Parser0 = add_error(Parser, Error, "Version ~b.~b not supported (minimum version ~b.~b)", [ Major, Minor, ?MIN_YAML_MAJOR_VERSION_SUPPORTED, ?MIN_YAML_MINOR_VERSION_SUPPORTED ]), %% Caution: Chars/Line/Col/Delta aren't flushed to Parser. do_return(Parser0); {Major, Minor} when Major < ?MAX_YAML_MAJOR_VERSION_SUPPORTED orelse (Major == ?MAX_YAML_MAJOR_VERSION_SUPPORTED andalso Minor =< ?MAX_YAML_MINOR_VERSION_SUPPORTED) -> %% Version supported. Parser; {Major, Minor} when Major > ?MAX_YAML_MAJOR_VERSION_SUPPORTED -> %% The document's version is not supported at all (major %% above maximum supported major). Error = #yamerl_parsing_error{ name = version_not_supported, token = Token, line = Line, column = Col }, Parser0 = add_error(Parser, Error, "Version ~b.~b not supported (maximum version ~b.~b)", [ Major, Minor, ?MAX_YAML_MAJOR_VERSION_SUPPORTED, ?MAX_YAML_MINOR_VERSION_SUPPORTED ]), %% Caution: Chars/Line/Col/Delta aren't flushed to Parser. do_return(Parser0); {Major, Minor} when Minor > ?MAX_YAML_MINOR_VERSION_SUPPORTED -> %% The document's minor version is greater than the %% supported version. Add a warning and continue anyway. Error = #yamerl_parsing_error{ name = version_not_supported, type = warning, token = Token, line = Line, column = Col }, Parser0 = add_error(Parser, Error, "Version ~b.~b not supported (maximum version ~b.~b); " "parsing may fail", [ Major, Minor, ?MAX_YAML_MAJOR_VERSION_SUPPORTED, ?MAX_YAML_MINOR_VERSION_SUPPORTED ]), Parser0 end, Parser2 = Parser1#yamerl_parser{ doc_started = true, doc_version = Version1 }, %% Emit a token with the determined version and the tags table. queue_token(Parser2, Token, Insert_At). end_doc(#yamerl_parser{doc_started = false} = Parser, _, _, _) -> %% No document to end. Parser; end_doc(Parser, Line, Col, Insert_At) -> %% At the end of the document, we reset the version and the tags %% table. Parser1 = Parser#yamerl_parser{ doc_started = false, doc_version = undefined }, Parser2 = setup_default_tags(Parser1), Token = #yamerl_doc_end{ line = Line, column = Col }, queue_token(Parser2, Token, Insert_At). %% ------------------------------------------------------------------- %% Directives. %% ------------------------------------------------------------------- parse_directive([_ | Rest] = Chars, Line, Col, Delta, Parser) -> Ctx = #directive_ctx{ line = Line, col = Col }, %% Reset cursor on column 0 to close all opened block collections. Parser1 = check_for_closed_block_collections(Chars, Line, Col, Delta, Parser, 0), Parser2 = remove_impl_key_pos(Parser1), Parser3 = ?ALLOW_IMPL_KEY(Parser2, false), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), do_parse_directive(Rest, Line, Col1, Delta1, Parser3, Ctx). do_parse_directive([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser, Ctx) when ?IS_NEWLINE(C) orelse ?IS_SPACE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> parse_directive2(Chars, Line, Col, Delta, Parser, Ctx); do_parse_directive([C | Rest] = Chars, Line, Col, Delta, Parser, Ctx) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#directive_ctx{ name = [C | Ctx#directive_ctx.name] }, do_parse_directive(Rest, Line, Col1, Delta1, Parser1, Ctx1); do_parse_directive([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> parse_directive2(Chars, Line, Col, Delta, Parser, Ctx); do_parse_directive([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_directive/6, Ctx). parse_directive2(Chars, Line, Col, Delta, Parser, Ctx) -> Name = lists:reverse(Ctx#directive_ctx.name), Ctx1 = Ctx#directive_ctx{ name = Name }, case Name of "YAML" -> parse_yaml_directive(Chars, Line, Col, Delta, Parser, Ctx1); "TAG" -> parse_tag_directive(Chars, Line, Col, Delta, Parser, Ctx1); _ -> parse_reserved_directive(Chars, Line, Col, Delta, Parser, Ctx1) end. skip_directive_trailing_ws([C | Rest], Line, Col, Delta, Parser) when ?IS_SPACE(C) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), skip_directive_trailing_ws(Rest, Line, Col1, Delta1, Parser); skip_directive_trailing_ws([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser) when ?IS_NEWLINE(C) orelse C == $# orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> find_next_token(Chars, Line, Col, Delta, Parser); skip_directive_trailing_ws([] = Chars, Line, Col, Delta, Parser) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun skip_directive_trailing_ws/5); skip_directive_trailing_ws([_ | _] = Chars, Line, Col, Delta, Parser) -> Error = #yamerl_parsing_error{ name = unexpected_directive_extra_params, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected directive extra parameters", []), return(Chars, Line, Col, Delta, Parser1). %% %% YAML directive. %% parse_yaml_directive(Chars, Line, Col, Delta, Parser, #directive_ctx{line = Dir_Line, col = Dir_Col}) -> Ctx = #yaml_directive_ctx{ line = Dir_Line, col = Dir_Col }, parse_yaml_directive_major(Chars, Line, Col, Delta, Parser, Ctx). %% Major version number. parse_yaml_directive_major([C | Rest], Line, Col, Delta, Parser, #yaml_directive_ctx{major = Major} = Ctx) when C >= $0 andalso C =< $9 -> Major1 = case Major of undefined -> C - $0; _ -> Major * 10 + C - $0 end, {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#yaml_directive_ctx{ major = Major1 }, parse_yaml_directive_major(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_yaml_directive_major([$. | Rest], Line, Col, Delta, Parser, #yaml_directive_ctx{major = Major} = Ctx) when is_integer(Major) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), %% End of the major part. Continue with the minor version number. parse_yaml_directive_minor(Rest, Line, Col1, Delta1, Parser, Ctx); parse_yaml_directive_major([C | Rest], Line, Col, Delta, Parser, #yaml_directive_ctx{major = undefined} = Ctx) when ?IS_SPACE(C) -> %% Skip leading white spaces. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_yaml_directive_major(Rest, Line, Col1, Delta1, Parser, Ctx); parse_yaml_directive_major([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun parse_yaml_directive_major/6, Ctx); parse_yaml_directive_major([_ | _] = Chars, Line, Col, Delta, Parser, #yaml_directive_ctx{line = Dir_Line, col = Dir_Col}) -> %% Invalid character (or end of directive) while parsing major %% version number. Token = #yamerl_yaml_directive{ line = Dir_Line, column = Dir_Col }, Error = #yamerl_parsing_error{ name = invalid_yaml_directive, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Invalid major version number in YAML directive", []), return(Chars, Line, Col, Delta, Parser1); parse_yaml_directive_major([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #yaml_directive_ctx{line = Dir_Line, col = Dir_Col}) -> %% Invalid end-of-stream while parsing major version number. Token = #yamerl_yaml_directive{ line = Dir_Line, column = Dir_Col }, Error = #yamerl_parsing_error{ name = invalid_yaml_directive, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected end-of-stream while parsing YAML directive", []), return(Chars, Line, Col, Delta, Parser1). %% Minor version number. parse_yaml_directive_minor([C | Rest], Line, Col, Delta, Parser, #yaml_directive_ctx{minor = Minor} = Ctx) when C >= $0 andalso C =< $9 -> Minor1 = case Minor of undefined -> C - $0; _ -> Minor * 10 + C - $0 end, {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#yaml_directive_ctx{ minor = Minor1 }, parse_yaml_directive_minor(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_yaml_directive_minor([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser, #yaml_directive_ctx{minor = Minor} = Ctx) when is_integer(Minor) andalso (?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C))) -> %% Directive end. queue_yaml_directive(Chars, Line, Col, Delta, Parser, Ctx); parse_yaml_directive_minor([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> %% Directive end. queue_yaml_directive(Chars, Line, Col, Delta, Parser, Ctx); parse_yaml_directive_minor([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun parse_yaml_directive_minor/6, Ctx); parse_yaml_directive_minor([_ | _] = Chars, Line, Col, Delta, Parser, #yaml_directive_ctx{line = Dir_Line, col = Dir_Col}) -> %% Invalid character while parsing minor version number. Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), Token = #yamerl_yaml_directive{ line = Dir_Line, column = Dir_Col }, Error = #yamerl_parsing_error{ name = invalid_yaml_directive, token = Token, line = Line, column = Col }, Parser2 = add_error(Parser1, Error, "Invalid minor version number in YAML directive", []), return(Chars, Line, Col, Delta, Parser2). %% Queue token. queue_yaml_directive(Chars, Line, Col, Delta, #yamerl_parser{doc_version = undefined} = Parser, #yaml_directive_ctx{major = Major, minor = Minor, line = Dir_Line, col = Dir_Col}) -> Version = {Major, Minor}, Token = #yamerl_yaml_directive{ version = Version, line = Dir_Line, column = Dir_Col }, Parser1 = queue_token(Parser, Token), Parser2 = Parser1#yamerl_parser{ doc_version = Version }, skip_directive_trailing_ws(Chars, Line, Col, Delta, Parser2); queue_yaml_directive(Chars, Line, Col, Delta, Parser, #yaml_directive_ctx{major = Major, minor = Minor, line = Dir_Line, col = Dir_Col} = Ctx) -> %% Warning: repeated YAML directive. Version = {Major, Minor}, Token = #yamerl_yaml_directive{ version = Version, line = Dir_Line, column = Dir_Col }, Error = #yamerl_parsing_error{ type = warning, name = multiple_yaml_directives, token = Token, line = Dir_Line, column = Dir_Col }, Parser1 = add_error(Parser, Error, "Multiple YAML directives found: the last one will be used", []), Parser2 = Parser1#yamerl_parser{ doc_version = undefined }, queue_yaml_directive(Chars, Line, Col, Delta, Parser2, Ctx). %% %% TAG directive. %% parse_tag_directive(Chars, Line, Col, Delta, Parser, #directive_ctx{line = Dir_Line, col = Dir_Col}) -> Ctx = #tag_directive_ctx{ line = Dir_Line, col = Dir_Col }, parse_tag_directive_handle(Chars, Line, Col, Delta, Parser, Ctx). %% Tag handle. parse_tag_directive_handle([$! | Rest], Line, Col, Delta, Parser, #tag_directive_ctx{handle = undefined} = Ctx) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_directive_ctx{ handle = "!" }, parse_tag_directive_handle(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_tag_directive_handle([C | Rest], Line, Col, Delta, Parser, #tag_directive_ctx{handle = Handle} = Ctx) when is_list(Handle) andalso ((C >= $a andalso C =< $z) orelse (C >= $A andalso C =< $Z) orelse (C >= $0 andalso C =< $9) orelse (C == $-)) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_directive_ctx{ handle = [C | Handle] }, parse_tag_directive_handle(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_tag_directive_handle([$! | Rest], Line, Col, Delta, Parser, #tag_directive_ctx{handle = Handle} = Ctx) when is_list(Handle) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_directive_ctx{ handle = [$! | Handle] }, parse_tag_directive_prefix(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_tag_directive_handle([C | _] = Chars, Line, Col, Delta, Parser, #tag_directive_ctx{handle = "!"} = Ctx) when ?IS_SPACE(C) -> parse_tag_directive_prefix(Chars, Line, Col, Delta, Parser, Ctx); parse_tag_directive_handle([C | Rest], Line, Col, Delta, Parser, #tag_directive_ctx{handle = undefined} = Ctx) when ?IS_SPACE(C) -> %% Skip leading white spaces. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_tag_directive_handle(Rest, Line, Col1, Delta1, Parser, Ctx); parse_tag_directive_handle([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun parse_tag_directive_handle/6, Ctx); parse_tag_directive_handle([_ | _] = Chars, Line, Col, Delta, Parser, #tag_directive_ctx{handle = Handle, line = Dir_Line, col = Dir_Col}) -> %% Invalid character (or end of directive) while parsing tag handle. Handle1 = case Handle of undefined -> Handle; _ -> lists:reverse(Handle) end, Token = #yamerl_tag_directive{ handle = Handle1, line = Dir_Line, column = Dir_Col }, Error = #yamerl_parsing_error{ name = invalid_tag_directive, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Invalid tag handle in TAG directive", []), return(Chars, Line, Col, Delta, Parser1); parse_tag_directive_handle([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #tag_directive_ctx{handle = Handle, line = Dir_Line, col = Dir_Col}) -> %% Invalid end-of-stream while parsing major version number. Handle1 = case Handle of undefined -> Handle; _ -> lists:reverse(Handle) end, Token = #yamerl_tag_directive{ handle = Handle1, line = Dir_Line, column = Dir_Col }, Error = #yamerl_parsing_error{ name = invalid_tag_directive, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected end-of-stream while parsing TAG directive", []), return(Chars, Line, Col, Delta, Parser1). %% Tag prefix. parse_tag_directive_prefix([C | Rest], Line, Col, Delta, Parser, #tag_directive_ctx{prefix = Prefix} = Ctx) when is_list(Prefix) andalso ?IS_URI_CHAR(C) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_directive_ctx{ prefix = [C | Prefix] }, parse_tag_directive_prefix(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_tag_directive_prefix([C | Rest], Line, Col, Delta, Parser, #tag_directive_ctx{prefix = undefined} = Ctx) when ?IS_SPACE(C) -> %% Skip leading white spaces. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_directive_ctx{ prefix = "" }, parse_tag_directive_prefix(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_tag_directive_prefix([C | Rest], Line, Col, Delta, Parser, #tag_directive_ctx{prefix = ""} = Ctx) when ?IS_SPACE(C) -> %% Skip leading white spaces. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_tag_directive_prefix(Rest, Line, Col1, Delta1, Parser, Ctx); parse_tag_directive_prefix([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser, #tag_directive_ctx{prefix = Prefix} = Ctx) when is_list(Prefix) andalso Prefix /= "" andalso (?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C))) -> queue_tag_directive(Chars, Line, Col, Delta, Parser, Ctx); parse_tag_directive_prefix([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> queue_tag_directive(Chars, Line, Col, Delta, Parser, Ctx); parse_tag_directive_prefix([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun parse_tag_directive_prefix/6, Ctx); parse_tag_directive_prefix([_ | _] = Chars, Line, Col, Delta, Parser, #tag_directive_ctx{handle = Handle, prefix = Prefix, line = Dir_Line, col = Dir_Col}) -> %% Invalid character while parsing tag prefix. Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), Token = #yamerl_tag_directive{ handle = lists:reverse(Handle), prefix = lists:reverse(Prefix), line = Dir_Line, column = Dir_Col }, Error = #yamerl_parsing_error{ name = invalid_tag_directive, token = Token, line = Line, column = Col }, Parser2 = add_error(Parser1, Error, "Invalid tag prefix in TAG directive", []), return(Chars, Line, Col, Delta, Parser2). %% Queue token. queue_tag_directive(Chars, Line, Col, Delta, #yamerl_parser{tags = Tags} = Parser, #tag_directive_ctx{handle = Handle, prefix = Prefix, line = Dir_Line, col = Dir_Col}) -> Handle1 = lists:reverse(Handle), Prefix1 = lists:reverse(Prefix), Token = #yamerl_tag_directive{ handle = Handle1, prefix = Prefix1, line = Dir_Line, column = Dir_Col }, Parser1 = is_uri_valid(Parser, Token), Parser2 = case dict:is_key(Handle1, Tags) of false -> Parser1; true -> Error = #yamerl_parsing_error{ type = warning, name = multiple_tag_handle_declarations, token = Token, line = Dir_Line, column = Dir_Col }, add_error(Parser1, Error, "Multiple declarations of the same handle found: " "the last one will be used", []) end, Parser3 = queue_token(Parser2, Token), Tags1 = dict:store(Handle1, Prefix1, Tags), Parser4 = Parser3#yamerl_parser{ tags = Tags1 }, skip_directive_trailing_ws(Chars, Line, Col, Delta, Parser4). %% %% Reserved directive. %% parse_reserved_directive(Chars, Line, Col, Delta, Parser, #directive_ctx{name = Name, line = Dir_Line, col = Dir_Col}) -> Ctx = #reserved_directive_ctx{ name = Name, line = Dir_Line, col = Dir_Col }, parse_reserved_directive_arg(Chars, Line, Col, Delta, Parser, Ctx). parse_reserved_directive_arg([C | Rest], Line, Col, Delta, Parser, #reserved_directive_ctx{current = undefined} = Ctx) when ?IS_SPACE(C) -> %% Skip leading white spaces. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_reserved_directive_arg(Rest, Line, Col1, Delta1, Parser, Ctx); parse_reserved_directive_arg([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser, #reserved_directive_ctx{current = undefined} = Ctx) when ?IS_NEWLINE(C) orelse C == $# orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> %% End of directive. queue_reserved_directive(Chars, Line, Col, Delta, Parser, Ctx); parse_reserved_directive_arg([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser, #reserved_directive_ctx{current = Current, args = Args, args_count = Count} = Ctx) when is_list(Current) andalso (?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C))) -> %% Current argument finished. Current1 = lists:reverse(Current), Ctx1 = Ctx#reserved_directive_ctx{ current = undefined, args = [Current1 | Args], args_count = Count + 1 }, parse_reserved_directive_arg(Chars, Line, Col, Delta, Parser, Ctx1); parse_reserved_directive_arg([C | Rest] = Chars, Line, Col, Delta, Parser, #reserved_directive_ctx{current = Current} = Ctx) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Current1 = case Current of undefined -> [C]; _ -> [C | Current] end, Ctx1 = Ctx#reserved_directive_ctx{ current = Current1 }, parse_reserved_directive_arg(Rest, Line, Col1, Delta1, Parser1, Ctx1); parse_reserved_directive_arg([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> %% End of directive. queue_reserved_directive(Chars, Line, Col, Delta, Parser, Ctx); parse_reserved_directive_arg([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun parse_reserved_directive_arg/6, Ctx). queue_reserved_directive(Chars, Line, Col, Delta, Parser, #reserved_directive_ctx{name = Name, current = Current, args = Args, args_count = Count, line = Dir_Line, col = Dir_Col}) -> {Args1, Count1} = case Current of undefined -> {Args, Count}; _ -> {[lists:reverse(Current) | Args], Count + 1} end, Token = #yamerl_reserved_directive{ name = Name, args = lists:reverse(Args1), args_count = Count1, line = Dir_Line, column = Dir_Col }, Error = #yamerl_parsing_error{ type = warning, name = reserved_directive, token = Token, line = Dir_Line, column = Dir_Col }, Parser1 = add_error(Parser, Error, "Reserved directive \"~s\" ignored", [Name]), Parser2 = queue_token(Parser1, Token), skip_directive_trailing_ws(Chars, Line, Col, Delta, Parser2). %% ------------------------------------------------------------------- %% Block sequences. %% ------------------------------------------------------------------- %% We found a new block sequence entry. parse_block_entry(Chars, Line, Col, Delta, #yamerl_parser{ik_allowed = true} = Parser) when ?IN_BLOCK_CTX(Parser) -> queue_block_sequence_entry_token(Chars, Line, Col, Delta, Parser); parse_block_entry(Chars, Line, Col, Delta, Parser) when ?IN_BLOCK_CTX(Parser) -> Error = #yamerl_parsing_error{ name = block_sequence_entry_not_allowed, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Block sequence entry not allowed here", []), return(Chars, Line, Col, Delta, Parser1); parse_block_entry(Chars, Line, Col, Delta, Parser) when ?IN_FLOW_CTX(Parser) -> Error = #yamerl_parsing_error{ name = block_collection_in_flow_context, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Block collection not allowed inside flow collection", []), return(Chars, Line, Col, Delta, Parser1). queue_block_sequence_entry_token([_ | Rest], Line, Col, Delta, Parser) when ?IN_BLOCK_CTX(Parser) -> Parser1 = remove_impl_key_pos(Parser), Parser2 = ?ALLOW_IMPL_KEY(Parser1, true), Token = #yamerl_sequence_entry{ line = Line, column = Col }, Parser3 = queue_token(Parser2, Token), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser3). %% ------------------------------------------------------------------- %% Flow collections. %% ------------------------------------------------------------------- parse_flow_collection_start([_ | Rest] = Chars, Line, Col, Delta, #yamerl_parser{cur_coll = Cur_Coll, parent_colls = Colls} = Parser, Kind) -> Parser1 = save_impl_key_pos(Chars, Line, Col, Delta, Parser), Parser2 = ?PUSH_FAKE_IMPL_KEY(Parser1), Parser3 = ?ALLOW_IMPL_KEY(Parser2, true), Token = #yamerl_collection_start{ style = flow, kind = Kind, line = Line, column = Col, tag = ?COLL_SCALAR_DEFAULT_TAG(Line, Col) }, Parser4 = queue_token(Parser3, Token), New_Coll = #fcoll{kind = Kind}, Parser5 = case Kind of sequence -> Parser4#yamerl_parser{ cur_coll = New_Coll, parent_colls = [Cur_Coll | Colls], pending_entry = true }; mapping -> Parser4#yamerl_parser{ cur_coll = New_Coll, parent_colls = [Cur_Coll | Colls], waiting_for_kvpair = true } end, {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser5). parse_flow_collection_end(Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #fcoll{kind = single_mapping}} = Parser, Kind) -> Parser1 = finish_incomplete_flow_entries(Line, Col, Delta, Parser), parse_flow_collection_end(Chars, Line, Col, Delta, Parser1, Kind); parse_flow_collection_end([_ | Rest], Line, Col, Delta, #yamerl_parser{ cur_coll = #fcoll{kind = Kind}, parent_colls = [Coll | Colls]} = Parser, Kind) -> Parser1 = finish_incomplete_flow_entries(Line, Col, Delta, Parser), Parser2 = remove_impl_key_pos(Parser1), Parser3 = ?POP_IMPL_KEY(Parser2), Parser4 = Parser3#yamerl_parser{ cur_coll = Coll, parent_colls = Colls, pending_entry = false, waiting_for_kvpair = false }, Parser5 = ?ALLOW_IMPL_KEY(Parser4, false), Token = #yamerl_collection_end{ style = flow, kind = Kind, line = Line, column = Col }, Parser6 = queue_token(Parser5, Token), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser6); parse_flow_collection_end(Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #fcoll{kind = Expected}} = Parser, Kind) -> %% Closing a different-type collection. Error = #yamerl_parsing_error{ name = closing_non_matching_flow_collection_type, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "A ~s closing character is used to close a ~s collection", [Kind, Expected]), parse_flow_collection_end(Chars, Line, Col, Delta, Parser1, Expected); parse_flow_collection_end([_ | Rest], Line, Col, Delta, #yamerl_parser{cur_coll = #bcoll{}} = Parser, Kind) -> %% Closing a never-opened collection. Error = #yamerl_parsing_error{ name = closing_never_opened_flow_collection, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "The ~s closing character doesn't match any opening character", [Kind]), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser1). parse_flow_entry(Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #fcoll{kind = single_mapping}} = Parser) -> Parser1 = finish_incomplete_flow_entries(Line, Col, Delta, Parser), parse_flow_entry(Chars, Line, Col, Delta, Parser1); parse_flow_entry([_ | Rest], Line, Col, Delta, #yamerl_parser{cur_coll = #fcoll{kind = Kind}} = Parser) when (Kind == sequence andalso ?MISSING_ENTRY(Parser)) orelse (Kind == mapping andalso ?MISSING_KVPAIR(Parser)) -> %% In a flow collection, the "," entry indicator immediatly follows a %% collection-start or a previous entry indicator. Error = #yamerl_parsing_error{ name = flow_collection_entry_not_allowed, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Empty flow collection entry not allowed", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser1); parse_flow_entry([_ | Rest], Line, Col, Delta, #yamerl_parser{cur_coll = #fcoll{kind = Kind}} = Parser) -> Parser1 = finish_incomplete_flow_entries(Line, Col, Delta, Parser), Parser2 = remove_impl_key_pos(Parser1), Parser3 = ?ALLOW_IMPL_KEY(Parser2, true), Parser4 = case Kind of sequence -> Parser3#yamerl_parser{ pending_entry = true }; mapping -> Parser3#yamerl_parser{ waiting_for_kvpair = true } end, {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser4); parse_flow_entry(Chars, Line, Col, Delta, Parser) when ?IN_BLOCK_CTX(Parser) -> Error = #yamerl_parsing_error{ name = flow_collection_entry_not_allowed, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Flow collection entry not allowed outside any flow collection", []), return(Chars, Line, Col, Delta, Parser1). %% ------------------------------------------------------------------- %% Block or flow mapping key. %% ------------------------------------------------------------------- parse_mapping_key(Chars, Line, Col, Delta, #yamerl_parser{ik_allowed = true} = Parser) when ?IN_BLOCK_CTX(Parser) -> %% A mapping key is allowed here. Parser1 = finish_incomplete_block_entries(Line, Col, Parser), queue_mapping_key_token(Chars, Line, Col, Delta, Parser1); parse_mapping_key(Chars, Line, Col, Delta, Parser) when ?IN_FLOW_CTX(Parser) -> %% A mapping key is always allowed in flow context. queue_mapping_key_token(Chars, Line, Col, Delta, Parser); parse_mapping_key(Chars, Line, Col, Delta, Parser) -> %% A mapping key is NOT allowed here. Error = #yamerl_parsing_error{ name = block_mapping_key_not_allowed, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Block mapping key not allowed here", []), return(Chars, Line, Col, Delta, Parser1). queue_mapping_key_token([_ | Rest], Line, Col, Delta, Parser) -> Parser1 = remove_impl_key_pos(Parser), Parser2 = ?ALLOW_IMPL_KEY(Parser1, ?IN_BLOCK_CTX(Parser1)), Token = #yamerl_mapping_key{ line = Line, column = Col }, Parser3 = queue_token(Parser2, Token), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser3). %% ------------------------------------------------------------------- %% Block or flow mapping value. %% ------------------------------------------------------------------- %% We found a new block mapping value. We must check if an implicit key %% is pending. parse_mapping_value(Chars, Line, Col, Delta, #yamerl_parser{ik_stack = [#impl_key{possible = true, line = Impl_Line} | _], last_token_endline = Endline} = Parser) when ?IN_BLOCK_CTX(Parser) andalso Impl_Line < Endline andalso Endline == Line -> %% The key of this mapping is an implicit key spanning over several lines. %% This will raise a warning. Parser1 = queue_impl_key(Delta, Parser), queue_mapping_value_token(Chars, Line, Col, Delta, Parser1); parse_mapping_value(Chars, Line, Col, Delta, #yamerl_parser{ik_stack = [#impl_key{possible = true, line = Impl_Line} = Impl_Key | Rest]} = Parser) when ?IN_BLOCK_CTX(Parser) andalso Impl_Line < Line -> %% This is not an implicit key. Impl_Key1 = Impl_Key#impl_key{possible = false}, Parser1 = Parser#yamerl_parser{ ik_stack = [Impl_Key1 | Rest] }, parse_mapping_value(Chars, Line, Col, Delta, Parser1); parse_mapping_value(Chars, Line, Col, Delta, #yamerl_parser{ik_stack = [#impl_key{possible = true} | _]} = Parser) -> %% The key of this mapping is an implicit key. Parser1 = queue_impl_key(Delta, Parser), queue_mapping_value_token(Chars, Line, Col, Delta, Parser1); parse_mapping_value(Chars, Line, Col, Delta, #yamerl_parser{ik_allowed = true, cur_coll = #bcoll{kind = mapping, kidx = KIdx, vidx = VIdx}} = Parser) when ?IN_BLOCK_CTX(Parser) andalso KIdx > VIdx -> %% The key of this mapping is an explicit key, already queued. %% In block context, an implicit key may follow. Parser1 = ?ALLOW_IMPL_KEY(Parser, true), queue_mapping_value_token(Chars, Line, Col, Delta, Parser1); parse_mapping_value(Chars, Line, Col, Delta, #yamerl_parser{ik_allowed = true, cur_coll = #bcoll{kind = mapping, kidx = KIdx, vidx = VIdx}} = Parser) when ?IN_BLOCK_CTX(Parser) andalso KIdx =< VIdx -> %% The key of this mapping is an empty node. We queue a mapping-key %% token followed; the empty scalar will be automatically queued. Token = #yamerl_mapping_key{ line = Line, column = Col }, Parser1 = queue_token(Parser, Token), %% In block context, an implicit key may follow. Parser2 = ?ALLOW_IMPL_KEY(Parser1, true), queue_mapping_value_token(Chars, Line, Col, Delta, Parser2); parse_mapping_value(Chars, Line, Col, Delta, #yamerl_parser{ik_allowed = false} = Parser) when ?IN_BLOCK_CTX(Parser) -> Error = #yamerl_parsing_error{ name = block_mapping_value_not_allowed, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Block mapping value not allowed here", []), return(Chars, Line, Col, Delta, Parser1); parse_mapping_value(Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #fcoll{kind = Kind, kidx = KIdx, vidx = VIdx}} = Parser) when ?IN_FLOW_CTX(Parser) andalso (Kind == mapping orelse Kind == single_mapping) andalso KIdx > VIdx -> %% The key of this mapping is an explicit key, already queued. %% In flow context, an implicit key may not follow. Parser1 = ?ALLOW_IMPL_KEY(Parser, false), queue_mapping_value_token(Chars, Line, Col, Delta, Parser1); parse_mapping_value(Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #fcoll{kind = Kind, kidx = KIdx, vidx = VIdx}} = Parser) when ?IN_FLOW_CTX(Parser) andalso (((Kind == mapping orelse Kind == single_mapping) andalso KIdx =< VIdx) orelse Kind == sequence) -> %% The key of this mapping is an empty node. Token = #yamerl_mapping_key{ line = Line, column = Col }, Parser1 = queue_token(Parser, Token), %% In flow context, an implicit key may not follow. Parser2 = ?ALLOW_IMPL_KEY(Parser1, false), queue_mapping_value_token(Chars, Line, Col, Delta, Parser2). queue_mapping_value_token(Chars, Line, Col, Delta, #yamerl_parser{last_token_endline = Endline, cur_coll = #bcoll{indent = Indent}} = Parser) when ?IN_BLOCK_CTX(Parser) andalso Line > Endline andalso Col > Indent -> Token = #yamerl_mapping_value{ line = Line, column = Col }, Error = #yamerl_parsing_error{ name = invalid_block_mapping_value_indentation, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Block mapping value's indentation (column #~b) " "greater than expected (column #~b)", [Col, Indent]), queue_mapping_value_token2(Chars, Line, Col, Delta, Parser1); queue_mapping_value_token(Chars, Line, Col, Delta, Parser) -> queue_mapping_value_token2(Chars, Line, Col, Delta, Parser). queue_mapping_value_token2([_ | Rest], Line, Col, Delta, Parser) -> Token = #yamerl_mapping_value{ line = Line, column = Col }, Parser1 = queue_token(Parser, Token), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), find_next_token(Rest, Line, Col1, Delta1, Parser1). finish_incomplete_block_entries(Line, Col, #yamerl_parser{cur_coll = #bcoll{kind = mapping, kidx = KIdx, vidx = VIdx}} = Parser) when KIdx > VIdx -> %% The last block mapping key has an empty node as value (and the %% ":" value indicator was never used). Queue a value token. The %% empty scalar will be automatically queued. Token = #yamerl_mapping_value{ line = Line, column = Col }, queue_token(Parser, Token); finish_incomplete_block_entries(_, _, Parser) -> Parser. finish_incomplete_flow_entries(Line, Col, _, #yamerl_parser{cur_coll = #fcoll{kind = single_mapping}, parent_colls = [Coll | Colls]} = Parser) -> %% Close single key: value pair. Token = #yamerl_collection_end{ style = flow, kind = mapping, line = Line, column = Col }, Parser1 = queue_token(Parser, Token), Parser1#yamerl_parser{ cur_coll = Coll, parent_colls = Colls }; finish_incomplete_flow_entries(Line, Col, Delta, #yamerl_parser{cur_coll = #fcoll{kind = mapping}, ik_stack = [#impl_key{possible = true} | _]} = Parser) -> %% Queue implicit key token. Parser1 = queue_impl_key(Delta, Parser), finish_incomplete_flow_entries(Line, Col, Delta, Parser1); finish_incomplete_flow_entries(Line, Col, Delta, #yamerl_parser{ cur_coll = #fcoll{kind = Kind, kidx = KIdx, vidx = VIdx}} = Parser) when (Kind == mapping orelse Kind == single_mapping) andalso KIdx > VIdx -> %% In a flow mapping, the last entry was a key without the ":" value %% indicator. Queue the value token and the implicit empty node. If %% the key was an empty node (ie. only a tag node property), it'll %% be added automatically by queue_token/2. Token = #yamerl_mapping_value{ line = Line, column = Col }, Parser1 = queue_token(Parser, Token), finish_incomplete_flow_entries(Line, Col, Delta, Parser1); finish_incomplete_flow_entries(_, _, _, Parser) -> Parser. %% ------------------------------------------------------------------- %% Anchors and aliases. %% ------------------------------------------------------------------- parse_anchor_or_alias([_ | Rest] = Chars, Line, Col, Delta, Parser, Type) -> Ctx = #anchor_ctx{ type = Type, line = Line, col = Col }, Parser1 = save_impl_key_pos(Chars, Line, Col, Delta, Parser), Parser2 = ?ALLOW_IMPL_KEY(Parser1, false), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), do_parse_anchor_or_alias(Rest, Line, Col1, Delta1, Parser2, Ctx). %% White spaces and flow indicators are forbidden inside anchor or alias %% names. do_parse_anchor_or_alias([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser, Ctx) when ?IS_NEWLINE(C) orelse ?IS_SPACE(C) orelse ?IS_FLOW_INDICATOR(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> queue_anchor_or_alias_token(Chars, Line, Col, Delta, Parser, Ctx); do_parse_anchor_or_alias([C | Rest] = Chars, Line, Col, Delta, Parser, Ctx) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#anchor_ctx{ output = [C | Ctx#anchor_ctx.output] }, do_parse_anchor_or_alias(Rest, Line, Col1, Delta1, Parser1, Ctx1); do_parse_anchor_or_alias([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> queue_anchor_or_alias_token(Chars, Line, Col, Delta, Parser, Ctx); do_parse_anchor_or_alias([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_anchor_or_alias/6, Ctx). queue_anchor_or_alias_token(Chars, Line, Col, Delta, Parser, #anchor_ctx{type = Type, line = Anc_Line, col = Anc_Col, output = Name}) -> Token = case Type of anchor -> #yamerl_anchor{ name = lists:reverse(Name), line = Anc_Line, column = Anc_Col }; alias -> #yamerl_alias{ name = lists:reverse(Name), line = Anc_Line, column = Anc_Col } end, Parser1 = queue_token(Parser, Token), find_next_token(Chars, Line, Col, Delta, Parser1). %% ------------------------------------------------------------------- %% Tags. %% ------------------------------------------------------------------- parse_tag([_ | Rest] = Chars, Line, Col, Delta, #yamerl_parser{last_tag = undefined} = Parser) -> Ctx = #tag_ctx{ line = Line, col = Col, prefix = "!", suffix = "" }, Parser1 = save_impl_key_pos(Chars, Line, Col, Delta, Parser), Parser2 = ?ALLOW_IMPL_KEY(Parser1, false), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), determine_tag_type(Rest, Line, Col1, Delta1, Parser2, Ctx); parse_tag(Chars, Line, Col, Delta, #yamerl_parser{last_tag = Tag} = Parser) when Tag /= undefined -> Error = #yamerl_parsing_error{ name = multiple_tag_properties, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Multiple tag properties attached to one node: " "the last one will be used", []), Parser2 = Parser1#yamerl_parser{ last_tag = undefined }, parse_tag(Chars, Line, Col, Delta, Parser2). %% Determine token type: verbatim tag or tag shorthand. determine_tag_type([$< | Rest], Line, Col, Delta, Parser, Ctx) -> %% Verbatim tag. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_ctx{ prefix = undefined }, parse_verbatim_tag(Rest, Line, Col1, Delta1, Parser, Ctx1); determine_tag_type([_ | _] = Chars, Line, Col, Delta, Parser, Ctx) -> parse_tag_shorthand(Chars, Line, Col, Delta, Parser, Ctx); determine_tag_type([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> expand_tag(Chars, Line, Col, Delta, Parser, Ctx); determine_tag_type([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun determine_tag_type/6, Ctx). %% %% Verbatim tag %% parse_verbatim_tag([$> | Rest], Line, Col, Delta, Parser, Ctx) -> %% End of the verbatim tag. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), expand_tag(Rest, Line, Col1, Delta1, Parser, Ctx); parse_verbatim_tag([$! | Rest], Line, Col, Delta, Parser, #tag_ctx{suffix = ""} = Ctx) -> %% Local tag. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_ctx{ suffix = "!" }, parse_verbatim_tag(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_verbatim_tag([$! | Rest], Line, Col, Delta, Parser, #tag_ctx{suffix = Suffix, line = Tag_Line, col = Tag_Col} = Ctx) -> %% "!" forbidden in verbatim tag. Token = #yamerl_tag{ uri = lists:reverse(Suffix), line = Tag_Line, column = Tag_Col }, Error = #yamerl_parsing_error{ name = invalid_tag_handle, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Invalid character in tag handle", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_verbatim_tag(Rest, Line, Col1, Delta1, Parser1, Ctx); parse_verbatim_tag([C | Rest], Line, Col, Delta, Parser, Ctx) when ?IS_URI_CHAR(C) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_ctx{ suffix = [C | Ctx#tag_ctx.suffix] }, parse_verbatim_tag(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_verbatim_tag([_ | Rest], Line, Col, Delta, Parser, #tag_ctx{suffix = Suffix, line = Tag_Line, col = Tag_Col} = Ctx) -> %% Character not allowed in a URI. Token = #yamerl_tag{ uri = lists:reverse(Suffix), line = Tag_Line, column = Tag_Col }, Error = #yamerl_parsing_error{ name = invalid_tag_handle, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Invalid character in tag handle", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_verbatim_tag(Rest, Line, Col1, Delta1, Parser1, Ctx); parse_verbatim_tag([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun parse_verbatim_tag/6, Ctx); parse_verbatim_tag([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #tag_ctx{suffix = Suffix, line = Tag_Line, col = Tag_Col}) -> %% Unexpected end-of-stream Token = #yamerl_tag{ uri = lists:reverse(Suffix), line = Tag_Line, column = Tag_Col }, Error = #yamerl_parsing_error{ name = unexpected_eos, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected end-of-stream while parsing tag handle", []), return(Chars, Line, Col, Delta, Parser1). %% %% Tag shorthand. %% %% Tag prefix. parse_tag_shorthand([$! | Rest], Line, Col, Delta, Parser, #tag_ctx{prefix = "!", suffix = Suffix} = Ctx) -> %% Separator between the prefix and the suffix. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_ctx{ prefix = "!" ++ lists:reverse(Suffix) ++ "!", suffix = "" }, parse_tag_shorthand(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_tag_shorthand([$! | Rest], Line, Col, Delta, Parser, #tag_ctx{prefix = Prefix, suffix = Suffix, line = Tag_Line, col = Tag_Col} = Ctx) -> %% "!" forbidden in tag. Token = #yamerl_tag{ uri = Prefix ++ lists:reverse(Suffix), line = Tag_Line, column = Tag_Col }, Error = #yamerl_parsing_error{ name = invalid_tag_handle, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Invalid character in tag handle", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_tag_shorthand(Rest, Line, Col1, Delta1, Parser1, Ctx); %% Tag suffix. parse_tag_shorthand([C | _] = Chars, Line, Col, Delta, Parser, Ctx) when ?IS_FLOW_INDICATOR(C) -> %% The next character starts another token. expand_tag(Chars, Line, Col, Delta, Parser, Ctx); parse_tag_shorthand([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser, Ctx) when ?IS_NEWLINE(C) orelse ?IS_SPACE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> expand_tag(Chars, Line, Col, Delta, Parser, Ctx); parse_tag_shorthand([C | Rest], Line, Col, Delta, Parser, #tag_ctx{suffix = Suffix} = Ctx) when ?IS_URI_CHAR(C) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#tag_ctx{ suffix = [C | Suffix] }, parse_tag_shorthand(Rest, Line, Col1, Delta1, Parser, Ctx1); parse_tag_shorthand([_ | Rest] = Chars, Line, Col, Delta, Parser, #tag_ctx{prefix = Prefix, suffix = Suffix, line = Tag_Line, col = Tag_Col} = Ctx) -> %% Character not allowed in a URI. Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), Token = #yamerl_tag{ uri = Prefix ++ lists:reverse(Suffix), line = Tag_Line, column = Tag_Col }, Error = #yamerl_parsing_error{ name = invalid_tag_handle, token = Token, line = Line, column = Col }, Parser2 = add_error(Parser1, Error, "Invalid character in tag handle", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_tag_shorthand(Rest, Line, Col1, Delta1, Parser2, Ctx); parse_tag_shorthand([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> expand_tag(Chars, Line, Col, Delta, Parser, Ctx); parse_tag_shorthand([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun parse_tag_shorthand/6, Ctx). %% Verbatim tag. expand_tag(Chars, Line, Col, Delta, Parser, #tag_ctx{prefix = undefined, suffix = Suffix} = Ctx) -> Ctx1 = Ctx#tag_ctx{ suffix = lists:reverse(Suffix) }, queue_tag_token(Chars, Line, Col, Delta, Parser, Ctx1); %% Non-specific tag. expand_tag(Chars, Line, Col, Delta, Parser, #tag_ctx{prefix = "!", suffix = ""} = Ctx) -> Ctx1 = Ctx#tag_ctx{ prefix = undefined, suffix = {non_specific, "!"} }, queue_tag_token(Chars, Line, Col, Delta, Parser, Ctx1); %% Tag shorthand. expand_tag(Chars, Line, Col, Delta, Parser, #tag_ctx{line = Tag_Line, col = Tag_Col, prefix = Prefix, suffix = ""} = Ctx) -> Token = #yamerl_tag{ uri = Prefix, line = Tag_Line, column = Tag_Col }, Error1 = #yamerl_parsing_error{ name = invalid_tag_handle, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error1, "Tag suffix mandatory", []), expand_tag2(Chars, Line, Col, Delta, Parser1, Ctx); expand_tag(Chars, Line, Col, Delta, Parser, Ctx) -> expand_tag2(Chars, Line, Col, Delta, Parser, Ctx). expand_tag2(Chars, Line, Col, Delta, #yamerl_parser{tags = Tags} = Parser, #tag_ctx{prefix = Prefix, suffix = Suffix, line = Tag_Line, col = Tag_Col} = Ctx) -> Suffix1 = lists:reverse(Suffix), {Parser1, URI} = try case dict:is_key(Prefix, Tags) of true -> {Parser, dict:fetch(Prefix, Tags) ++ Suffix1}; false -> {Parser, dict:fetch({default, Prefix}, Tags) ++ Suffix1} end catch _:_ -> Bad_URI = Prefix ++ Suffix1, Token = #yamerl_tag{ uri = Bad_URI, line = Tag_Line, column = Tag_Col }, Error = #yamerl_parsing_error{ name = undeclared_tag_handle, token = Token, line = Line, column = Col }, Parser0 = add_error(Parser, Error, "Tag handle \"~s\" never declared", [Prefix]), {Parser0, Bad_URI} end, Ctx1 = Ctx#tag_ctx{ prefix = undefined, suffix = URI }, queue_tag_token(Chars, Line, Col, Delta, Parser1, Ctx1). queue_tag_token(Chars, Line, Col, Delta, Parser, #tag_ctx{suffix = "!", line = Tag_Line, col = Tag_Col} = Ctx) -> Token = #yamerl_tag{ uri = "!", line = Tag_Line, column = Tag_Col }, Error = #yamerl_parsing_error{ name = invalid_tag_handle, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Local tag suffix mandatory", []), queue_tag_token2(Chars, Line, Col, Delta, Parser1, Ctx); queue_tag_token(Chars, Line, Col, Delta, Parser, Ctx) -> queue_tag_token2(Chars, Line, Col, Delta, Parser, Ctx). queue_tag_token2(Chars, Line, Col, Delta, Parser, #tag_ctx{suffix = URI, line = Tag_Line, col = Tag_Col}) -> Token = #yamerl_tag{ uri = URI, line = Tag_Line, column = Tag_Col }, Parser1 = is_uri_valid(Parser, Token), Parser2 = queue_token(Parser1, Token), find_next_token(Chars, Line, Col, Delta, Parser2). %% ------------------------------------------------------------------- %% Block scalars. %% ------------------------------------------------------------------- parse_block_scalar([_ | Rest], Line, Col, Delta, Parser, Style) -> Ctx = #block_scalar_hd_ctx{ style = Style, line = Line, col = Col }, Parser1 = remove_impl_key_pos(Parser), Parser2 = ?ALLOW_IMPL_KEY(Parser1, true), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser2, Ctx). %% %% Header parsing. %% %% Newline, header termination. do_parse_block_scalar_header([$\r] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> %% Can't be sure it's a newline. It may be followed by a LF. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_block_scalar_header/6, Ctx); do_parse_block_scalar_header([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), prepare_parse_block_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx); %% Comments. do_parse_block_scalar_header([$# | Rest], Line, Col, Delta, Parser, Ctx) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#block_scalar_hd_ctx{ in_comment = true }, Ctx2 = final_indent(Parser, Ctx1), do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser, Ctx2); do_parse_block_scalar_header([_ | Rest], Line, Col, Delta, Parser, #block_scalar_hd_ctx{in_comment = true} = Ctx) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser, Ctx); %% Chomping indicator. do_parse_block_scalar_header([C | Rest], Line, Col, Delta, Parser, #block_scalar_hd_ctx{chomp = undefined} = Ctx) when C == $- orelse C == $+ -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Chomp = case C of $- -> strip; $+ -> keep end, Ctx1 = Ctx#block_scalar_hd_ctx{ chomp = Chomp }, Ctx2 = final_indent(Parser, Ctx1), do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser, Ctx2); do_parse_block_scalar_header([C | Rest], Line, Col, Delta, Parser, #block_scalar_hd_ctx{style = Style, line = Sc_Line, col = Sc_Col} = Ctx) when C == $- orelse C == $+ -> Token = #yamerl_scalar{ style = block, substyle = Style, text = "", line = Sc_Line, column = Sc_Col, tag = ?BLOCK_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = multiple_chomping_indicators, type = warning, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Multiple chomping indicators specified: the last one will be used", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Chomp = case C of $- -> strip; $+ -> keep end, Ctx1 = Ctx#block_scalar_hd_ctx{ chomp = Chomp }, Ctx2 = final_indent(Parser1, Ctx1), do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser1, Ctx2); %% Explicit indentation indicator. do_parse_block_scalar_header([C | Rest], Line, Col, Delta, Parser, #block_scalar_hd_ctx{indent = undefined} = Ctx) when C >= $1 andalso C =< $9 -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#block_scalar_hd_ctx{ indent = {tmp, C - $0} }, do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser, Ctx1); do_parse_block_scalar_header([C | Rest], Line, Col, Delta, Parser, #block_scalar_hd_ctx{indent = {tmp, Indent}} = Ctx) when C >= $1 andalso C =< $9 -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#block_scalar_hd_ctx{ indent = {tmp, Indent * 10 + C - $0} }, do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser, Ctx1); do_parse_block_scalar_header([C | Rest], Line, Col, Delta, Parser, #block_scalar_hd_ctx{style = Style, line = Sc_Line, col = Sc_Col} = Ctx) when C >= $1 andalso C =< $9 -> Token = #yamerl_scalar{ style = block, substyle = Style, text = "", line = Sc_Line, column = Sc_Col, tag = ?BLOCK_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = multiple_indent_indicators, type = warning, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Multiple indent indicators specified: the last one will be used", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#block_scalar_hd_ctx{ indent = {tmp, C - $0} }, Ctx2 = final_indent(Parser1, Ctx1), do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser1, Ctx2); %% Trailing spaces. do_parse_block_scalar_header([C | Rest], Line, Col, Delta, Parser, Ctx) when ?IS_SPACE(C) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = final_indent(Parser, Ctx), do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser, Ctx1); %% Invalid characters. do_parse_block_scalar_header([_ | Rest] = Chars, Line, Col, Delta, Parser, #block_scalar_hd_ctx{style = Style, line = Sc_Line, col = Sc_Col} = Ctx) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), Token = #yamerl_scalar{ style = block, substyle = Style, text = "", line = Sc_Line, column = Sc_Col, tag = ?BLOCK_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = invalid_block_scalar_header, token = Token, line = Line, column = Col }, Parser2 = add_error(Parser1, Error, "Invalid character in block scalar header", []), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = final_indent(Parser2, Ctx), do_parse_block_scalar_header(Rest, Line, Col1, Delta1, Parser2, Ctx1); do_parse_block_scalar_header([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> %% End-of-stream reached while parsing block scalar header. Assume %% an empty string. prepare_parse_block_scalar(Chars, Line, Col, Delta, Parser, Ctx); do_parse_block_scalar_header([] = Chars, Line, Col, Delta, Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_block_scalar_header/6, Ctx). final_indent( #yamerl_parser{cur_coll = #bcoll{kind = root}}, #block_scalar_hd_ctx{indent = {tmp, Add_Indent}} = Ctx) -> Ctx#block_scalar_hd_ctx{indent = 1 + Add_Indent}; final_indent( #yamerl_parser{cur_coll = #bcoll{indent = Indent}}, #block_scalar_hd_ctx{indent = {tmp, Add_Indent}} = Ctx) -> Ctx#block_scalar_hd_ctx{indent = Indent + Add_Indent}; final_indent(_, Ctx) -> Ctx. prepare_parse_block_scalar(Chars, Line, Col, Delta, Parser, Ctx) -> Ctx1 = final_indent(Parser, Ctx), Chomp = case Ctx1#block_scalar_hd_ctx.chomp of undefined -> clip; C -> C end, Next_Ctx = #block_scalar_ctx{ style = Ctx1#block_scalar_hd_ctx.style, line = Ctx1#block_scalar_hd_ctx.line, col = Ctx1#block_scalar_hd_ctx.col, endline = Line, endcol = Col, chomp = Chomp, indent = Ctx1#block_scalar_hd_ctx.indent, newline = Ctx1#block_scalar_hd_ctx.indent /= undefined }, do_parse_block_scalar(Chars, Line, Col, Delta, Parser, Next_Ctx). %% %% Newlines. %% %% Can't be sure it's a newline. It may be followed by a LF. do_parse_block_scalar([$\r] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_block_scalar/6, Ctx); %% This is an empty line just after the header. do_parse_block_scalar([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, #block_scalar_ctx{newline = false, spaces = Spaces, output = ""} = Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Ctx1 = Ctx#block_scalar_ctx{ newline = true, spaces = [$\n | Spaces] }, do_parse_block_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx1); %% Literal style: no line folding. do_parse_block_scalar([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, #block_scalar_ctx{spaces = Spaces, style = literal} = Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Ctx1 = Ctx#block_scalar_ctx{ newline = true, spaces = [$\n | Spaces] }, do_parse_block_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx1); %% Folded style: a newline at the end of a normal-indented line. do_parse_block_scalar([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, #block_scalar_ctx{spaces = Spaces, newline = false, more_indent = false} = Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Ctx1 = Ctx#block_scalar_ctx{ newline = true, spaces = [$\s | Spaces] }, do_parse_block_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx1); %% Folded style: an empty line after a normal-indented line. do_parse_block_scalar([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, #block_scalar_ctx{spaces = Spaces, newline = true, more_indent = false} = Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Spaces1 = case Spaces of [$\s | S] -> S; _ -> Spaces end, Ctx1 = Ctx#block_scalar_ctx{ newline = true, spaces = [$\n | Spaces1] }, do_parse_block_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx1); %% Folded style: a newline in a more-indented paragraph. do_parse_block_scalar([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, #block_scalar_ctx{spaces = Spaces, more_indent = true} = Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Ctx1 = Ctx#block_scalar_ctx{ newline = true, spaces = [$\n | Spaces] }, do_parse_block_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx1); %% %% Indentation. %% %% First non-space character: set indentation. do_parse_block_scalar([C | _] = Chars, Line, Col, Delta, Parser, #block_scalar_ctx{indent = undefined, longest_empty = Longest} = Ctx) when C /= $\s andalso Longest < Col -> Ctx1 = Ctx#block_scalar_ctx{ indent = Col, newline = true }, do_parse_block_scalar(Chars, Line, Col, Delta, Parser, Ctx1); do_parse_block_scalar([C | _] = Chars, Line, Col, Delta, Parser, #block_scalar_ctx{indent = undefined, longest_empty = Longest, style = Style, line = Sc_Line, col = Sc_Col} = Ctx) when C /= $\s andalso Longest >= Col -> Token = #yamerl_scalar{ style = block, substyle = Style, line = Sc_Line, column = Sc_Col, tag = ?BLOCK_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ type = warning, name = leading_empty_lines_too_long, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "A leading all-space line has too many spaces (~b) " "compared to detected indentation (~b)", [Longest, Col - 1]), Ctx1 = Ctx#block_scalar_ctx{ longest_empty = 0 }, do_parse_block_scalar(Chars, Line, Col, Delta, Parser1, Ctx1); do_parse_block_scalar([$\s | Rest], Line, Col, Delta, Parser, #block_scalar_ctx{indent = undefined, longest_empty = Longest} = Ctx) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = if Col > Longest -> Ctx#block_scalar_ctx{longest_empty = Col}; true -> Ctx end, do_parse_block_scalar(Rest, Line, Col1, Delta1, Parser, Ctx1); %% Skip indentation spaces. do_parse_block_scalar([$\s | Rest], Line, Col, Delta, Parser, #block_scalar_ctx{indent = Indent, newline = true} = Ctx) when Indent == undefined orelse Col < Indent -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), do_parse_block_scalar(Rest, Line, Col1, Delta1, Parser, Ctx); %% The next line is less indented than the block scalar: end it. do_parse_block_scalar([C | _] = Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #bcoll{indent = Indent}} = Parser, Ctx) when C /= $\s andalso Col =< Indent -> queue_block_scalar_token(Chars, Line, Col, Delta, Parser, Ctx); %% The next line is less indented than the block scalar, but more than %% the parent node. However, it's a comment, so we end the scalar. do_parse_block_scalar([$# | _] = Chars, Line, Col, Delta, Parser, #block_scalar_ctx{indent = Indent} = Ctx) when Col < Indent -> queue_block_scalar_token(Chars, Line, Col, Delta, Parser, Ctx); %% The next line is less indented than the block scalar, but more than %% the parent node: it's an error. do_parse_block_scalar([C | _] = Chars, Line, Col, Delta, Parser, #block_scalar_ctx{indent = Indent, style = Style, line = Token_Line, col = Token_Col, output = Output}) when C /= $\s andalso Col < Indent -> Token = #yamerl_scalar{ style = block, substyle = Style, text = lists:reverse(Output), line = Token_Line, column = Token_Col, tag = ?BLOCK_SCALAR_DEFAULT_TAG(Token_Line, Token_Col) }, Error = #yamerl_parsing_error{ name = invalid_block_scalar_indentation, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Invalid block scalar indentation", []), return(Chars, Line, Col, Delta, Parser1); %% The next line has a directives end or document end marker: end the %% scalar. do_parse_block_scalar([C | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{chars_len = Len, raw_eos = false} = Parser, Ctx) when (C == $- orelse C == $.) andalso (Len - Delta) < 4 -> %% We don't have enough data to determine if it's the end of the %% plain scalar. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_block_scalar/6, Ctx); do_parse_block_scalar([$-, $-, $-, C | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{doc_version = Version} = Parser, Ctx) when ?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> queue_block_scalar_token(Chars, Line, Col, Delta, Parser, Ctx); do_parse_block_scalar([$., $., $., C | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{doc_version = Version} = Parser, Ctx) when ?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> queue_block_scalar_token(Chars, Line, Col, Delta, Parser, Ctx); do_parse_block_scalar([$-, $-, $-] = Chars, Line, 1 = Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> queue_block_scalar_token(Chars, Line, Col, Delta, Parser, Ctx); do_parse_block_scalar([$., $., $.] = Chars, Line, 1 = Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> queue_block_scalar_token(Chars, Line, Col, Delta, Parser, Ctx); %% %% Content. %% %% Literal style: everything after the indentation spaces is kept. do_parse_block_scalar([C | Rest] = Chars, Line, Col, Delta, Parser, #block_scalar_ctx{style = literal, spaces = Spaces} = Ctx) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Output1 = case Spaces of "" -> [C | Ctx#block_scalar_ctx.output]; _ -> [C | Spaces ++ Ctx#block_scalar_ctx.output] end, Ctx1 = Ctx#block_scalar_ctx{ spaces = "", newline = false, output = Output1, endline = Line, endcol = Col1 }, do_parse_block_scalar(Rest, Line, Col1, Delta1, Parser1, Ctx1); %% Folded style: a normal-indented line. do_parse_block_scalar([C | _] = Chars, Line, Col, Delta, Parser, #block_scalar_ctx{style = folded, more_indent = true, indent = Indent} = Ctx) when not ?IS_SPACE(C) andalso Col == Indent -> %% This line uses the default indentation: end the more indented %% paragraph. Ctx1 = Ctx#block_scalar_ctx{ more_indent = false }, do_parse_block_scalar(Chars, Line, Col, Delta, Parser, Ctx1); do_parse_block_scalar([C | Rest], Line, Col, Delta, Parser, #block_scalar_ctx{style = folded, newline = Newline, spaces = Spaces, output = Output} = Ctx) when not ?IS_SPACE(C) orelse (?IS_SPACE(C) andalso (not Newline orelse Output == "")) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Output1 = case Spaces of "" -> [C | Ctx#block_scalar_ctx.output]; _ -> [C | Spaces ++ Ctx#block_scalar_ctx.output] end, Ctx1 = Ctx#block_scalar_ctx{ spaces = "", newline = false, output = Output1, endline = Line, endcol = Col1 }, do_parse_block_scalar(Rest, Line, Col1, Delta1, Parser, Ctx1); %% Folded style: a more-indented line. do_parse_block_scalar([C | Rest], Line, Col, Delta, Parser, #block_scalar_ctx{style = folded, newline = true, spaces = Spaces, more_indent = More_Indented} = Ctx) when ?IS_SPACE(C) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Spaces1 = case Spaces of [$\s | S] -> [$\n | S]; _ when More_Indented -> Spaces; _ -> [$\n | Spaces] end, Ctx1 = Ctx#block_scalar_ctx{ spaces = "", newline = false, more_indent = true, output = [C | Spaces1 ++ Ctx#block_scalar_ctx.output], endline = Line, endcol = Col1 }, do_parse_block_scalar(Rest, Line, Col1, Delta1, Parser, Ctx1); do_parse_block_scalar([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, Ctx) -> %% End-of-stream reached. queue_block_scalar_token(Chars, Line, Col, Delta, Parser, Ctx); do_parse_block_scalar([] = Chars, Line, Col, Delta, Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_block_scalar/6, Ctx). queue_block_scalar_token(Chars, Line, Col, Delta, Parser, #block_scalar_ctx{style = Style, output = Output, spaces = Spaces, chomp = Chomp, newline = Newline, line = Sc_Line, col = Sc_Col, endline = Endline, endcol = Endcol}) -> {Text, Endline1, Endcol1} = case Chomp of strip -> {Output, Endline, Endcol}; clip when Output == "" -> {Output, Endline, Endcol}; clip when Spaces == "" -> {Output, Endline, Endcol}; clip -> {[$\n | Output], Endline + 1, 1}; keep -> {Spaces ++ Output, Line, 1} end, Token = #yamerl_scalar{ style = block, substyle = Style, text = lists:reverse(Text), line = Sc_Line, column = Sc_Col, tag = ?BLOCK_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Parser1 = queue_token(Parser, Token), Parser2 = Parser1#yamerl_parser{ endpos_set_by_token = true, last_token_endline = Endline1, last_token_endcol = Endcol1, missed_nl = Newline }, find_next_token(Chars, Line, Col, Delta, Parser2). %% ------------------------------------------------------------------- %% Flow scalars. %% ------------------------------------------------------------------- parse_flow_scalar([C | Rest] = Chars, Line, Col, Delta, Parser, Style) -> %% We start a flow scalar parsing: initialize the context. Ctx = #flow_scalar_ctx{ style = Style, line = Line, col = Col }, Parser1 = save_impl_key_pos(Chars, Line, Col, Delta, Parser), Parser2 = ?ALLOW_IMPL_KEY(Parser1, false), {Rest1, {Col1, Delta1}} = case C of $' -> {Rest, ?NEXT_COL(Col, Delta, 1)}; $" -> {Rest, ?NEXT_COL(Col, Delta, 1)}; _ -> {Chars, {Col, Delta}} end, do_parse_flow_scalar(Rest1, Line, Col1, Delta1, Parser2, Ctx). do_parse_flow_scalar([$\r] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> %% Can't be sure it's a newline. It may be followed by a LF. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar/6, Ctx); %% %% Leading white spaces (plain scalar). %% do_parse_flow_scalar([C | Rest], Line, Col, Delta, Parser, #flow_scalar_ctx{style = plain, output = "", surrogate = undefined} = Ctx) when ?IS_SPACE(C) -> %% Skip leading white spaces in a plain scalar. We must update the %% position of beginning of the scalar and thus the implicit key. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Parser1 = save_impl_key_pos(Rest, Line, Col1, Delta1, Parser), Ctx1 = Ctx#flow_scalar_ctx{ line = Line, col = Col1 }, do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser1, Ctx1); %% %% Escaped characters [62]. %% Only supported by double-quoted strings. %% %% The next character is escaped. do_parse_flow_scalar([$\\ | Rest], Line, Col, Delta, Parser, #flow_scalar_ctx{style = double_quoted, spaces = Spaces} = Ctx) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Output1 = case Spaces of "" -> Ctx#flow_scalar_ctx.output; _ -> Spaces ++ Ctx#flow_scalar_ctx.output end, Ctx1 = Ctx#flow_scalar_ctx{ spaces = "", newline = false, output = Output1 }, do_parse_flow_scalar_escaped(Rest, Line, Col1, Delta1, Parser, Ctx1); %% Invalid surrogate pair. do_parse_flow_scalar([_ | _] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{surrogate = High, style = Style, line = Sc_Line, col = Sc_Col, output = Output} = Ctx) when High /= undefined -> %% The next character isn't the expected low surrogate. Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = invalid_surrogate_pair, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Invalid UTF-16 surrogate pair", []), Ctx1 = Ctx#flow_scalar_ctx{ surrogate = undefined }, do_parse_flow_scalar(Chars, Line, Col, Delta, Parser1, Ctx1); %% In a single-quoted string, a single quote is escaped by doubling %% it. do_parse_flow_scalar([$'] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, #flow_scalar_ctx{style = single_quoted} = Ctx) -> %% Can't be sure it's an escaped single quote. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar/6, Ctx); do_parse_flow_scalar([$', $' | Rest], Line, Col, Delta, Parser, #flow_scalar_ctx{style = single_quoted, spaces = Spaces} = Ctx) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 2), Output1 = case Spaces of "" -> Ctx#flow_scalar_ctx.output; _ -> Spaces ++ Ctx#flow_scalar_ctx.output end, Ctx1 = Ctx#flow_scalar_ctx{ spaces = "", newline = false, output = [$' | Output1] }, do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser, Ctx1); %% %% Line folding. %% Leading and trailing white spaces are dropped, except when the %% newline character is escaped, a previous white spaces is escaped or %% at the beginning of the first line. %% do_parse_flow_scalar([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, #flow_scalar_ctx{newline = false} = Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Ctx1 = Ctx#flow_scalar_ctx{ spaces = " ", newline = true }, do_parse_flow_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx1); do_parse_flow_scalar([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, #flow_scalar_ctx{newline = true, spaces = Spaces} = Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Spaces1 = case Spaces of [$\s | S] -> S; _ -> Spaces end, Ctx1 = Ctx#flow_scalar_ctx{ spaces = [$\n | Spaces1] }, do_parse_flow_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx1); do_parse_flow_scalar([C | Rest], Line, Col, Delta, Parser, #flow_scalar_ctx{spaces = Spaces, newline = false} = Ctx) when ?IS_SPACE(C) -> %% Keep white spaces in a separate buffer. If we find content later, %% this buffer will be merged with the result buffer. Otherwise, the %% white spaces buffer may be trimmed or dropped. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#flow_scalar_ctx{ spaces = [C | Spaces] }, do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser, Ctx1); do_parse_flow_scalar([C | Rest], Line, Col, Delta, Parser, #flow_scalar_ctx{newline = true} = Ctx) when ?IS_SPACE(C) -> %% Drop leading white spaces when not on the first line. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser, Ctx); %% %% Flow scalar end character. %% do_parse_flow_scalar([C | Rest], Line, Col, Delta, Parser, #flow_scalar_ctx{style = Style} = Ctx) when (Style == double_quoted andalso C == $") orelse (Style == single_quoted andalso C == $') -> %% Found the end of this flow scalar. Next step: find the next %% token. {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#flow_scalar_ctx{ endline = Line, endcol = Col1 }, queue_flow_scalar_token(Rest, Line, Col1, Delta1, Parser, Ctx1); do_parse_flow_scalar([$# | _] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{style = plain, spaces = Spaces} = Ctx) when Spaces /= [] -> %% A '#' character preceeded by white spaces is a comment. The plain %% scalar terminates with the first white spaces because trailing %% white spaces are ignored. [130] queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([$:] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #flow_scalar_ctx{style = plain} = Ctx) -> %% We consider the end-of-stream as a "white space" and use the ':' %% character as the end character for this plain scalar. [130] queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([$:] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, #flow_scalar_ctx{style = plain} = Ctx) -> %% We don't have enough data to determine if it's the end of the %% plain scalar. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar/6, Ctx); do_parse_flow_scalar([$:, C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser, #flow_scalar_ctx{style = plain} = Ctx) when ?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> %% A ':' character followed by white spaces is not allowed in a %% plain scalar: end it. Only one character is available but it's %% enough to take a decision. The next state will handle the newline %% properly. [130] queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([$:, C | _] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{style = plain} = Ctx) when ?IN_FLOW_CTX(Parser) andalso ?IS_FLOW_INDICATOR(C) -> %% A ':' character followed by an flow indicator character in flow %% context ends the plain scalar. queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([C | _] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{style = plain} = Ctx) when ?IN_FLOW_CTX(Parser) andalso ?IS_FLOW_INDICATOR(C) -> %% The characters '[', ']', '{', '}' and ',' are forbidden in plain %% scalar because they are used as flow collection separation %% characters. [129] queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([C | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{chars_len = Len, raw_eos = false} = Parser, #flow_scalar_ctx{style = plain} = Ctx) when (C == $- orelse C == $.) andalso (Len - Delta) < 4 -> %% We don't have enough data to determine if it's the end of the %% plain scalar. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar/6, Ctx); do_parse_flow_scalar([$-, $-, $-, C | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{doc_version = Version} = Parser, #flow_scalar_ctx{style = plain} = Ctx) when ?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> %% A directives end indicator puts an end to the plain scalar. queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([$., $., $., C | _] = Chars, Line, 1 = Col, Delta, #yamerl_parser{doc_version = Version} = Parser, #flow_scalar_ctx{style = plain} = Ctx) when ?IS_SPACE(C) orelse ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> %% A document end indicator puts an end to the plain scalar. queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([$-, $-, $-] = Chars, Line, 1 = Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #flow_scalar_ctx{style = plain} = Ctx) -> %% A directives end indicator puts an end to the plain scalar. queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([$., $., $.] = Chars, Line, 1 = Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #flow_scalar_ctx{style = plain} = Ctx) -> %% A document end indicator puts an end to the plain scalar. queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([_ | _] = Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #bcoll{indent = Indent}} = Parser, #flow_scalar_ctx{style = plain, newline = true} = Ctx) when ?IN_BLOCK_CTX(Parser) andalso Col =< Indent -> %% The continuation line is as or less indented than the current %% block collection. Therefore, it's not a continuation line and we %% end the flow scalar. queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); do_parse_flow_scalar([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #flow_scalar_ctx{style = plain} = Ctx) -> %% End of stream = end of plain scalar. queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, Ctx#flow_scalar_ctx{spaces = ""}); %% %% JSON acceptable characters range [2]. %% do_parse_flow_scalar([C | Rest] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{spaces = Spaces} = Ctx) when C == 16#9 orelse (C >= 16#20 andalso C =< 16#10FFFF) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Output1 = case Spaces of "" -> [C | Ctx#flow_scalar_ctx.output]; _ -> [C | Spaces ++ Ctx#flow_scalar_ctx.output] end, Ctx1 = Ctx#flow_scalar_ctx{ spaces = "", newline = false, output = Output1, endline = Line, endcol = Col1 }, do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser1, Ctx1); do_parse_flow_scalar([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #flow_scalar_ctx{style = Style, line = Sc_Line, col = Sc_Col, output = Output}) -> Tag = case Style of plain -> ?PLAIN_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col); _ -> ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) end, Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = Tag }, Error = #yamerl_parsing_error{ name = unexpected_eos, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected end-of-stream while parsing flow scalar", []), return(Chars, Line, Col, Delta, Parser1); do_parse_flow_scalar([] = Chars, Line, Col, Delta, Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar/6, Ctx). %% %% Escaped characters [62]. %% Only supported by double-quoted strings. %% %% Escaped 16-bit Unicode character, \uFFFF [60]. do_parse_flow_scalar_escaped([$u | _] = Chars, Line, Col, Delta, #yamerl_parser{chars_len = Len, raw_eos = true} = Parser, #flow_scalar_ctx{style = Style, line = Sc_Line, col = Sc_Col, output = Output}) when (Len - Delta) < 5 -> %% Unexpected enf-of-stream. Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = unexpected_eos, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected end-of-stream while parsing flow scalar", []), return(Chars, Line, Col, Delta, Parser1); do_parse_flow_scalar_escaped([$u | _] = Chars, Line, Col, Delta, #yamerl_parser{chars_len = Len} = Parser, Ctx) when (Len - Delta) < 5 -> %% Can't be sure it's an escaped Unicode character. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar_escaped/6, Ctx); do_parse_flow_scalar_escaped([$u, O1, O2, O3, O4 | Rest], Line, Col, Delta, Parser, #flow_scalar_ctx{surrogate = High} = Ctx) when ?IS_HEXADECIMAL(O1) andalso ?IS_HEXADECIMAL(O2) andalso ?IS_HEXADECIMAL(O3) andalso ?IS_HEXADECIMAL(O4) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 5), C = hex_to_dec([O1, O2, O3, O4], 0), Ctx1 = case High of undefined -> if ?IS_HIGH_SURROGATE(C) -> %% This is the high part of a UTF-16 surrogate pair. Ctx#flow_scalar_ctx{ surrogate = C }; true -> %% Normal character. Ctx#flow_scalar_ctx{ output = [C | Ctx#flow_scalar_ctx.output] } end; _ -> if ?IS_LOW_SURROGATE(C) -> %% This is the low part of a UTF-16 surrogate pair. C1 = 16#10000 + (High - 16#d800) * 16#400 + (C - 16#dc00), Ctx#flow_scalar_ctx{ output = [C1 | Ctx#flow_scalar_ctx.output], surrogate = undefined }; true -> %% Error: high surrogate without a low surrogate. %% The error is generated by the next clause. Ctx end end, do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser, Ctx1); do_parse_flow_scalar_escaped([$u | _] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{line = Sc_Line, col = Sc_Col, style = Style, output = Output} = Ctx) -> %% Invalid escaped character. Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = invalid_escaped_character, type = warning, token = Token, line = Line, column = Col - 1 }, Parser1 = add_error(Parser, Error, "Invalid escaped character", []), do_parse_flow_scalar(Chars, Line, Col, Delta, Parser1, Ctx); %% Escaped 32-bit Unicode character, \UFFFFFFFF [61]. do_parse_flow_scalar_escaped([$U | _] = Chars, Line, Col, Delta, #yamerl_parser{chars_len = Len, raw_eos = true} = Parser, #flow_scalar_ctx{style = Style, line = Sc_Line, col = Sc_Col, output = Output}) when (Len - Delta) < 9 -> %% Unexpected enf-of-stream. Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = unexpected_eos, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected end-of-stream while parsing flow scalar", []), return(Chars, Line, Col, Delta, Parser1); do_parse_flow_scalar_escaped([$U | _] = Chars, Line, Col, Delta, #yamerl_parser{chars_len = Len} = Parser, Ctx) when (Len - Delta) < 9 -> %% Can't be sure it's an escaped Unicode character. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar_escaped/6, Ctx); do_parse_flow_scalar_escaped([$U, O1, O2, O3, O4, O5, O6, O7, O8 | Rest], Line, Col, Delta, Parser, #flow_scalar_ctx{surrogate = High} = Ctx) when ?IS_HEXADECIMAL(O1) andalso ?IS_HEXADECIMAL(O2) andalso ?IS_HEXADECIMAL(O3) andalso ?IS_HEXADECIMAL(O4) andalso ?IS_HEXADECIMAL(O5) andalso ?IS_HEXADECIMAL(O6) andalso ?IS_HEXADECIMAL(O7) andalso ?IS_HEXADECIMAL(O8) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 9), C = hex_to_dec([O1, O2, O3, O4, O5, O6, O7, O8], 0), Ctx1 = case High of undefined -> if ?IS_HIGH_SURROGATE(C) -> %% This is the high part of a UTF-16 surrogate pair. Ctx#flow_scalar_ctx{ surrogate = C }; true -> %% Normal character. Ctx#flow_scalar_ctx{ output = [C | Ctx#flow_scalar_ctx.output] } end; _ -> if ?IS_LOW_SURROGATE(C) -> %% This is the low part of a UTF-16 surrogate pair. C1 = 16#10000 + (High - 16#d800) * 16#400 + (C - 16#dc00), Ctx#flow_scalar_ctx{ output = [C1 | Ctx#flow_scalar_ctx.output], surrogate = undefined }; true -> %% Error: high surrogate without a low surrogate. %% The error is generated by the next clause. Ctx end end, do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser, Ctx1); do_parse_flow_scalar_escaped([$U | _] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{line = Sc_Line, col = Sc_Col, style = Style, output = Output} = Ctx) -> %% Invalid escaped character. Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = invalid_escaped_character, type = warning, token = Token, line = Line, column = Col - 1 }, Parser1 = add_error(Parser, Error, "Invalid escaped character", []), do_parse_flow_scalar(Chars, Line, Col, Delta, Parser1, Ctx); %% Escaped 8-bit Unicode character, \xFF [59]. do_parse_flow_scalar_escaped([$x | _] = Chars, Line, Col, Delta, #yamerl_parser{chars_len = Len, raw_eos = true} = Parser, #flow_scalar_ctx{style = Style, line = Sc_Line, col = Sc_Col, output = Output}) when (Len - Delta) < 3 -> %% Unexpected enf-of-stream. Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = unexpected_eos, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected end-of-stream while parsing flow scalar", []), return(Chars, Line, Col, Delta, Parser1); do_parse_flow_scalar_escaped([$x | _] = Chars, Line, Col, Delta, #yamerl_parser{chars_len = Len} = Parser, Ctx) when (Len - Delta) < 3 -> %% Can't be sure it's an escaped Unicode character. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar_escaped/6, Ctx); do_parse_flow_scalar_escaped([$x, O1, O2 | Rest], Line, Col, Delta, Parser, Ctx) when ?IS_HEXADECIMAL(O1) andalso ?IS_HEXADECIMAL(O2) -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 3), C = hex_to_dec([O1, O2], 0), Ctx1 = Ctx#flow_scalar_ctx{ output = [C | Ctx#flow_scalar_ctx.output] }, do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser, Ctx1); do_parse_flow_scalar_escaped([$x | _] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{line = Sc_Line, col = Sc_Col, style = Style, output = Output} = Ctx) -> %% Invalid escaped character. Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = invalid_escaped_character, type = warning, token = Token, line = Line, column = Col - 1 }, Parser1 = add_error(Parser, Error, "Invalid escaped character", []), do_parse_flow_scalar(Chars, Line, Col, Delta, Parser1, Ctx); %% Escaped newline. %% All trailing whitespaces are kept as content before an escaped %% newline: this is handled in the $\\ clause above. do_parse_flow_scalar_escaped([$\r] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser, Ctx) -> %% Can't be sure it's a newline. It may be followed by a LF. suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar_escaped/6, Ctx); do_parse_flow_scalar_escaped([C | _] = Chars, Line, _, Delta, #yamerl_parser{doc_version = Version} = Parser, #flow_scalar_ctx{style = double_quoted} = Ctx) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> {Rest, Line1, Col1, Delta1} = ?NEXT_LINE(Chars, Line, Delta, Parser), Ctx1 = Ctx#flow_scalar_ctx{ newline = true }, do_parse_flow_scalar(Rest, Line1, Col1, Delta1, Parser, Ctx1); %% Other escaped characters. do_parse_flow_scalar_escaped([C | Rest] = Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{line = Sc_Line, col = Sc_Col, style = Style, output = Output} = Ctx) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), case unescape_char(C) of undefined -> %% Invalid escaped character. Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = invalid_escaped_character, type = warning, token = Token, line = Line, column = Col - 1 }, Parser2 = add_error(Parser1, Error, "Invalid escaped character", []), do_parse_flow_scalar(Chars, Line, Col, Delta, Parser2, Ctx); C1 -> {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), Ctx1 = Ctx#flow_scalar_ctx{ output = [C1 | Ctx#flow_scalar_ctx.output] }, do_parse_flow_scalar(Rest, Line, Col1, Delta1, Parser1, Ctx1) end; do_parse_flow_scalar_escaped([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser, #flow_scalar_ctx{style = Style, line = Sc_Line, col = Sc_Col, output = Output}) -> Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output), line = Sc_Line, column = Sc_Col, tag = ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) }, Error = #yamerl_parsing_error{ name = unexpected_eos, token = Token, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Unexpected end-of-stream while parsing flow scalar", []), return(Chars, Line, Col, Delta, Parser1); do_parse_flow_scalar_escaped([] = Chars, Line, Col, Delta, Parser, Ctx) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun do_parse_flow_scalar_escaped/6, Ctx). queue_flow_scalar_token(Chars, Line, Col, Delta, Parser, #flow_scalar_ctx{style = Style, output = Output, spaces = Spaces, newline = Newline, line = Sc_Line, col = Sc_Col, endline = Endline, endcol = Endcol}) -> Output1 = case Spaces of "" -> Output; _ -> Spaces ++ Output end, Tag = case Style of plain -> ?PLAIN_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col); _ -> ?FLOW_SCALAR_DEFAULT_TAG(Sc_Line, Sc_Col) end, Token = #yamerl_scalar{ style = flow, substyle = Style, text = lists:reverse(Output1), line = Sc_Line, column = Sc_Col, tag = Tag }, Parser1 = queue_token(Parser, Token), Parser2 = Parser1#yamerl_parser{ endpos_set_by_token = true, last_token_endline = Endline, last_token_endcol = Endcol, missed_nl = (Style == plain andalso Newline) }, find_next_token(Chars, Line, Col, Delta, Parser2). unescape_char($0) -> 16#0; %% \0 = NUL [42] unescape_char($a) -> 16#7; %% \a = BELL [43] unescape_char($b) -> $\b; %% \b = BS [44] unescape_char($t) -> $\t; %% \t = TAB [45] unescape_char($n) -> $\n; %% \n = LF [46] unescape_char($v) -> $\v; %% \v = VT [47] unescape_char($f) -> $\f; %% \f = FF [48] unescape_char($r) -> $\r; %% \r = CR [49] unescape_char($e) -> $\e; %% \e = ESC [50] unescape_char($N) -> 16#85; %% \N = Unicode next line [55] unescape_char($_) -> 16#A0; %% \_ = Unicode non-breaking space [56] unescape_char($L) -> 16#2028; %% \L = Unicode line sep. [57] unescape_char($P) -> 16#2029; %% \P = Unicode paragraph sep. [58] unescape_char($\s) -> $\s; %% \ = SPC [51] unescape_char($") -> $"; %% \" = " [52] unescape_char($/) -> $/; %% \/ = / [53] unescape_char($\\) -> $\\; %% \\ = \ [54] unescape_char(_) -> undefined. hex_to_dec([C | Rest], Number) -> C_Dec = if C >= $0, C =< $9 -> C - $0; C >= $a, C =< $f -> C - $a + 10; C >= $A, C =< $F -> C - $A + 10 end, hex_to_dec(Rest, Number * 16 + C_Dec); hex_to_dec([], Number) -> Number. %% ------------------------------------------------------------------- %% Comments. %% ------------------------------------------------------------------- parse_comment([C | _] = Chars, Line, Col, Delta, #yamerl_parser{doc_version = Version} = Parser) when ?IS_NEWLINE(C) orelse (Version == {1,1} andalso ?IS_NEWLINE_11(C)) -> %% A comment ends at the end of the line. %% This clause also takes care of DOS newline (even if the buffer %% contains only \r and not \n yet). It doesn't matter because we %% let the next state handle the newline properly; the cursor is not %% moved forward. find_next_token(Chars, Line, Col, Delta, Parser); parse_comment([_ | Rest] = Chars, Line, Col, Delta, Parser) -> Parser1 = ?WARN_IF_NON_ASCII_LINE_BREAK(Chars, Line, Col, Parser), {Col1, Delta1} = ?NEXT_COL(Col, Delta, 1), parse_comment(Rest, Line, Col1, Delta1, Parser1); parse_comment([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = false} = Parser) -> suspend_parsing(Chars, Line, Col, Delta, Parser, fun parse_comment/5); parse_comment([] = Chars, Line, Col, Delta, #yamerl_parser{raw_eos = true} = Parser) -> find_next_token(Chars, Line, Col, Delta, Parser). %% ------------------------------------------------------------------- %% Implicit key handling. %% ------------------------------------------------------------------- save_impl_key_pos(Chars, Line, Col, Delta, #yamerl_parser{chars_idx = Chars_Index, tks_first_idx = First, tks_queued = Queued, cur_coll = Cur_Coll, ik_stack = [_ | Rest]} = Parser) -> Required = ?IN_BLOCK_CTX(Parser) andalso Cur_Coll#bcoll.indent == Col, if Parser#yamerl_parser.ik_allowed -> Impl_Key = #impl_key{ possible = true, required = Required, line = Line, col = Col, chars_idx = Chars_Index + Delta, token_idx = First + Queued }, Parser#yamerl_parser{ik_stack = [Impl_Key | Rest]}; Required -> Error = #yamerl_parsing_error{ name = required_implicit_key_not_allowed, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Required implicit key not allowed here", []), return(Chars, Line, Col, Delta, Parser1); true -> Parser end. queue_impl_key(_, #yamerl_parser{last_token_endline = Line, ik_stack = [#impl_key{line = Impl_Line} = Impl_Key | _]} = Parser) when Line > Impl_Line andalso ?IN_BLOCK_CTX(Parser) -> %% An implicit key must not span several lines. Error = #yamerl_parsing_error{ name = invalid_implicit_key, type = warning, line = Impl_Key#impl_key.line, column = Impl_Key#impl_key.col }, Parser1 = add_error(Parser, Error, "An implicit key must not span several lines", []), queue_impl_key2(Parser1); queue_impl_key(_, #yamerl_parser{last_token_endline = Line, ik_stack = [#impl_key{line = Impl_Line} = Impl_Key | _]} = Parser) when Line > Impl_Line andalso ?IN_FLOW_CTX(Parser) -> %% An implicit key must not span several lines. Error = #yamerl_parsing_error{ name = invalid_implicit_key, type = warning, line = Impl_Key#impl_key.line, column = Impl_Key#impl_key.col }, Parser1 = add_error(Parser, Error, "An implicit key must not span several lines", []), queue_impl_key2(Parser1); queue_impl_key(Delta, #yamerl_parser{chars_idx = Index, ik_stack = [#impl_key{chars_idx = Impl_Index} = Impl_Key | _]} = Parser) when (Index + Delta) > Impl_Index + 1024 -> %% An implicit key must not take more than 1024 characters. Error = #yamerl_parsing_error{ name = invalid_implicit_key, type = warning, line = Impl_Key#impl_key.line, column = Impl_Key#impl_key.col }, Parser1 = add_error(Parser, Error, "An implicit key must not take more than 1024 characters", []), queue_impl_key2(Parser1); queue_impl_key(_, Parser) -> queue_impl_key2(Parser). queue_impl_key2( #yamerl_parser{ik_stack = [Impl_Key | Rest]} = Parser) -> Token = #yamerl_mapping_key{ line = Impl_Key#impl_key.line, column = Impl_Key#impl_key.col }, Parser1 = queue_token(Parser, Token, Impl_Key#impl_key.token_idx), Parser1#yamerl_parser{ ik_stack = [?FAKE_IMPL_KEY | Rest], ik_allowed = false }. remove_impl_key_pos( #yamerl_parser{ik_stack = [ #impl_key{required = true, line = Line, col = Col} | _]} = Parser) -> %% This error is raised with the following examples: %% %% - entry %% unexpected-scalar %% %% ? key %% unexpected-scalar Error = #yamerl_parsing_error{ name = expected_sequence_entry_or_mapping_key_not_found, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Expected sequence entry or mapping implicit key not found", []), do_return(Parser1); remove_impl_key_pos( #yamerl_parser{ik_stack = [_ | Rest]} = Parser) -> Parser#yamerl_parser{ik_stack = [?FAKE_IMPL_KEY | Rest]}. %% ------------------------------------------------------------------- %% Tokens queueing. %% ------------------------------------------------------------------- -spec queue_token(Parser, Token) -> New_Parser when Parser :: yamerl_parser(), Token :: yamerl_token(), New_Parser :: yamerl_parser(). queue_token(Parser, Token) -> queue_token_check_doc(Parser, Token, tail). -spec queue_token(Parser, Token, Insert_At) -> New_Parser when Parser :: yamerl_parser(), Token :: yamerl_token(), Insert_At :: position() | tail, New_Parser :: yamerl_parser(). queue_token(Parser, Token, Insert_At) -> queue_token_check_doc(Parser, Token, Insert_At). %% %% Handle document start/end. %% queue_token_check_doc( #yamerl_parser{doc_started = false} = Parser, Token, Insert_At) when is_record(Token, yamerl_stream_start) orelse is_record(Token, yamerl_stream_end) orelse is_record(Token, yamerl_doc_end) orelse is_record(Token, yamerl_yaml_directive) orelse is_record(Token, yamerl_tag_directive) orelse is_record(Token, yamerl_reserved_directive) -> %% Directives token outside a document are perfectly allowed. queue_token_pending_entry(Parser, Token, Insert_At); queue_token_check_doc( #yamerl_parser{doc_started = false, last_tag = Tag} = Parser, Token, Insert_At) -> %% Other tokens starts the document automatically. {Line, Col} = case Tag of #yamerl_tag{line = L, column = C} -> %% A tag is pending: use its position instead of the %% position of the token about to be queued. {L, C}; _ -> {?TOKEN_LINE(Token), ?TOKEN_COLUMN(Token)} end, %% We may need to shift the implicit key's index. Parser1 = update_impl_key_index(Parser, Line, Col), Parser2 = start_doc(Parser1, Line, Col, Insert_At), queue_token_pending_entry(Parser2, Token, next_insert_at(Insert_At, 1)); queue_token_check_doc( #yamerl_parser{doc_started = true} = Parser, Token, Insert_At) when is_record(Token, yamerl_stream_end) -> %% A document is automatically ended when we reach the end of the stream. Parser1 = end_doc(Parser, ?TOKEN_LINE(Token), ?TOKEN_COLUMN(Token), Insert_At), queue_token_check_doc(Parser1, Token, Insert_At); queue_token_check_doc( #yamerl_parser{doc_started = true} = Parser, Token, Insert_At) -> %% Document already started. queue_token_pending_entry(Parser, Token, Insert_At). %% %% Pending entries. %% queue_token_pending_entry( #yamerl_parser{pending_entry = false} = Parser, Token, Insert_At) -> queue_token_check_collection_start(Parser, Token, Insert_At); queue_token_pending_entry( #yamerl_parser{pending_entry = true, last_tag = Tag} = Parser, Token, Insert_At) -> %% There's a pending entry: queue it now. Parser1 = Parser#yamerl_parser{ pending_entry = false }, {Line, Col} = case Tag of #yamerl_tag{line = L, column = C} -> %% A tag is pending: use its position instead of the %% position of the token about to be queued. {L, C}; _ -> {?TOKEN_LINE(Token), ?TOKEN_COLUMN(Token)} end, Entry = #yamerl_sequence_entry{ line = Line, column = Col }, %% We may need to shift the implicit key's index. Parser2 = update_impl_key_index(Parser1, ?TOKEN_LINE(Token), ?TOKEN_COLUMN(Token)), Parser3 = queue_token(Parser2, Entry, Insert_At), queue_token_check_collection_start(Parser3, Token, next_insert_at(Insert_At, 1)). %% %% Handle collection start. %% queue_token_check_collection_start( #yamerl_parser{ cur_coll = #bcoll{kind = Kind, indent = Indent} = Cur_Coll, parent_colls = Colls} = Parser, #yamerl_sequence_entry{line = Line, column = Col} = Token, Insert_At) when ?IN_BLOCK_CTX(Parser) andalso (Col > Indent orelse (Kind == mapping andalso Col == Indent)) -> %% This is the first entry of a block sequence collection. Queue a %% collection-start token. Collection_Start = #yamerl_collection_start{ style = block, kind = sequence, line = Line, column = Col, tag = ?COLL_SCALAR_DEFAULT_TAG(Line, Col) }, Parser1 = queue_token(Parser, Collection_Start, Insert_At), %% Record the new block indent. New_Coll = #bcoll{kind = sequence, indent = Col}, Parser2 = Parser1#yamerl_parser{ cur_coll = New_Coll, parent_colls = [Cur_Coll | Colls] }, queue_token_keep_last_pos(Parser2, Token, next_insert_at(Insert_At, 1)); queue_token_check_collection_start( #yamerl_parser{ cur_coll = #bcoll{indent = Indent} = Cur_Coll, parent_colls = Colls} = Parser, #yamerl_mapping_key{line = Line, column = Col} = Token, Insert_At) when ?IN_BLOCK_CTX(Parser) andalso Col > Indent -> %% This is the first key: value pair of a block mapping collection. Queue %% a collection-start token. Collection_Start = #yamerl_collection_start{ style = block, kind = mapping, line = Line, column = Col, tag = ?COLL_SCALAR_DEFAULT_TAG(Line, Col) }, Parser1 = queue_token(Parser, Collection_Start, Insert_At), %% Record the new block indent. New_Coll = #bcoll{kind = mapping, indent = Col}, Parser2 = Parser1#yamerl_parser{ cur_coll = New_Coll, parent_colls = [Cur_Coll | Colls] }, queue_token_keep_last_pos(Parser2, Token, next_insert_at(Insert_At, 1)); queue_token_check_collection_start( #yamerl_parser{ cur_coll = #fcoll{kind = sequence} = Cur_Coll, parent_colls = Colls} = Parser, #yamerl_mapping_key{line = Line, column = Col} = Token, Insert_At) when ?IN_FLOW_CTX(Parser) -> %% This is a single key: value pair inside a flow sequence. Queue %% a collection-start token. Collection_Start = #yamerl_collection_start{ style = flow, kind = mapping, line = Line, column = Col, tag = ?COLL_SCALAR_DEFAULT_TAG(Line, Col) }, Parser1 = queue_token(Parser, Collection_Start, Insert_At), %% Flag this mapping as single pair inside flow sequence. New_Coll = #fcoll{kind = single_mapping}, Parser2 = Parser1#yamerl_parser{ cur_coll = New_Coll, parent_colls = [Cur_Coll | Colls] }, queue_token_keep_last_pos(Parser2, Token, next_insert_at(Insert_At, 1)); queue_token_check_collection_start(Parser, Token, Insert_At) -> queue_token_keep_last_pos(Parser, Token, Insert_At). %% %% Remember last sequence entry, mapping key and mapping value %% positions. %% queue_token_keep_last_pos( #yamerl_parser{cur_coll = Coll, tks_first_idx = First, tks_queued = Queued} = Parser, #yamerl_sequence_entry{line = Line, column = Col} = Token, Insert_At) -> Index = case Insert_At of tail -> First + Queued; _ -> Insert_At + 1 end, Coll1 = if ?IN_BLOCK_CTX(Parser) -> Coll#bcoll{kidx = Index, kline = Line, kcol = Col}; ?IN_FLOW_CTX(Parser) -> Coll#fcoll{kidx = Index, kline = Line, kcol = Col} end, Parser1 = Parser#yamerl_parser{ cur_coll = Coll1 }, queue_token_json_like(Parser1, Token, Insert_At); queue_token_keep_last_pos( #yamerl_parser{cur_coll = Coll, tks_first_idx = First, tks_queued = Queued} = Parser, #yamerl_mapping_key{line = Line, column = Col} = Token, Insert_At) -> %% While we're handling a mapping key, tell that we're not waiting %% for a key: value pair anymore. Index = case Insert_At of tail -> First + Queued; _ -> Insert_At + 1 end, Coll1 = if ?IN_BLOCK_CTX(Parser) -> Coll#bcoll{kidx = Index, kline = Line, kcol = Col}; ?IN_FLOW_CTX(Parser) -> Coll#fcoll{kidx = Index, kline = Line, kcol = Col} end, Parser1 = Parser#yamerl_parser{ cur_coll = Coll1, waiting_for_kvpair = false }, queue_token_json_like(Parser1, Token, Insert_At); queue_token_keep_last_pos( #yamerl_parser{cur_coll = Coll, tks_first_idx = First, tks_queued = Queued} = Parser, #yamerl_mapping_value{line = Line, column = Col} = Token, Insert_At) -> Index = case Insert_At of tail -> First + Queued; _ -> Insert_At + 1 end, Coll1 = if ?IN_BLOCK_CTX(Parser) -> Coll#bcoll{vidx = Index, vline = Line, vcol = Col}; ?IN_FLOW_CTX(Parser) -> Coll#fcoll{vidx = Index, vline = Line, vcol = Col} end, Parser1 =Parser#yamerl_parser{ cur_coll = Coll1 }, queue_token_json_like(Parser1, Token, Insert_At); queue_token_keep_last_pos(Parser, Token, Insert_At) -> queue_token_json_like(Parser, Token, Insert_At). %% %% JSON-like tokens. %% queue_token_json_like(Parser, Token, tail) when ?IS_JSON_LIKE(Token) -> Parser1 = Parser#yamerl_parser{ last_is_json_like = true }, do_queue_token(Parser1, Token, tail); queue_token_json_like(Parser, Token, tail) -> Parser1 = Parser#yamerl_parser{ last_is_json_like = false }, do_queue_token(Parser1, Token, tail); queue_token_json_like(Parser, Token, Insert_At) -> do_queue_token(Parser, Token, Insert_At). %% %% Insert the token at the end of the queue or a given index. %% do_queue_token(#yamerl_parser{tokens = Tokens, tks_queued = Queued} = Parser, Token, tail) -> Tokens1 = [Token | Tokens], Parser1 = Parser#yamerl_parser{ tokens = Tokens1, tks_queued = Queued + 1 }, emit_tokens(Parser1); do_queue_token(#yamerl_parser{tokens = Tokens, tks_queued = Queued, tks_first_idx = First} = Parser, Token, Insert_At) -> Split = Queued - (Insert_At - First), {Head, Tail} = lists:split(Split, Tokens), Parser1 = Parser#yamerl_parser{ tokens = Head ++ [Token] ++ Tail, tks_queued = Queued + 1 }, emit_tokens(Parser1). %% %% Emit tokens which are ready using the callback function. %% At this point, all tokens coming in are in the final order. We may %% only do some checks, add empty nodes and set nodes' tag property. %% emit_tokens( #yamerl_parser{tokens = Tokens, tks_first_idx = First} = Parser) -> Max = max_token_idx_ready(Parser), emit_tokens2(Parser, lists:reverse(Tokens), First, Max). emit_tokens2( #yamerl_parser{last_tag = undefined, tks_queued = Queued, tks_first_idx = First} = Parser, [#yamerl_tag{} = Tag | Rest], Idx, Max) when Idx =< Max -> %% Keep the tag outside of the token queue. It'll be attached to a %% following node. Parser1 = Parser#yamerl_parser{ tks_queued = Queued - 1, tks_first_idx = First + 1, last_tag = Tag }, emit_tokens2(Parser1, Rest, Idx + 1, Max); emit_tokens2( #yamerl_parser{tks_queued = Queued, tks_first_idx = First} = Parser, [#yamerl_tag{line = Line, column = Col} = Tag | Rest], Idx, Max) when Idx =< Max -> %% Error: several tags for the same node. Error = #yamerl_parsing_error{ name = multiple_tag_properties, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Multiple tag properties attached to one node: " "the last one will be used", []), Parser2 = Parser1#yamerl_parser{ tks_queued = Queued - 1, tks_first_idx = First + 1, last_tag = Tag }, emit_tokens2(Parser2, Rest, Idx + 1, Max); emit_tokens2(#yamerl_parser{last_anchor = undefined, tks_queued = Queued, tks_first_idx = First} = Parser, [#yamerl_anchor{} = Anchor | Rest], Idx, Max) when Idx =< Max -> %% Keep the anchor outside of the token queue. It'll be emitted with %% its attached node. We also use this to check if multiple anchors %% are attached to the same node. Parser1 = Parser#yamerl_parser{ tks_queued = Queued - 1, tks_first_idx = First + 1, last_anchor = Anchor }, emit_tokens2(Parser1, Rest, Idx + 1, Max); emit_tokens2( #yamerl_parser{tks_queued = Queued, tks_first_idx = First} = Parser, [#yamerl_anchor{line = Line, column = Col} = Anchor | Rest], Idx, Max) when Idx =< Max -> %% Error: several tags for the same node. Error = #yamerl_parsing_error{ name = multiple_anchor_properties, line = Line, column = Col }, Parser1 = add_error(Parser, Error, "Multiple anchor properties attached to one node: " "the last one will be used", []), Parser2 = Parser1#yamerl_parser{ tks_queued = Queued - 1, tks_first_idx = First + 1, last_anchor = Anchor }, emit_tokens2(Parser2, Rest, Idx + 1, Max); emit_tokens2(#yamerl_parser{last_token = Last} = Parser, [Token | Rest], Idx , Max) when Idx =< Max -> %% Run some checks: %% o Can "Last" and "Token" be in a raw? %% o Do we need to insert an empty scalar? Parser1 = check_tokens_in_a_row(Parser, Last, Token), %% Handle properties and execute the specified callback function (or %% queue the token). Parser2 = handle_anchor_property(Parser1, Token), emit_tokens2(Parser2, Rest, Idx + 1, Max); emit_tokens2(Parser, Tokens, _, _) -> Tokens1 = lists:reverse(Tokens), Parser#yamerl_parser{ tokens = Tokens1 }. %% Check if a token can follow another and insert empty node if %% necessary. check_tokens_in_a_row( #yamerl_parser{last_tag = Tag, last_anchor = Anchor, tks_queued = Queued} = Parser, Token1, Token2) when %% Empty sequence entry. (is_record(Token1, yamerl_sequence_entry) andalso (?IN_BLOCK_CTX(Parser) orelse Tag /= undefined orelse Anchor /= undefined) andalso (is_record(Token2, yamerl_sequence_entry) orelse is_record(Token2, yamerl_collection_end))) orelse %% Empty mapping key. (is_record(Token1, yamerl_mapping_key) andalso (is_record(Token2, yamerl_mapping_value) orelse is_record(Token2, yamerl_collection_end))) orelse %% Empty mapping value. (is_record(Token1, yamerl_mapping_value) andalso (is_record(Token2, yamerl_mapping_key) orelse is_record(Token2, yamerl_collection_end))) orelse %% Empty mapping value. (is_record(Token1, yamerl_mapping_value) andalso (is_record(Token2, yamerl_mapping_key) orelse is_record(Token2, yamerl_collection_end))) orelse %% Empty document. (is_record(Token1, yamerl_doc_start) andalso is_record(Token2, yamerl_doc_end)) orelse %% Anchor alone. (is_record(Token1, yamerl_anchor) andalso (is_record(Token2, yamerl_mapping_value) orelse is_record(Token2, yamerl_collection_end) orelse is_record(Token2, yamerl_doc_end))) -> %% Token1 is followed by an empty scalar. {Line, Col} = case Tag of #yamerl_tag{line = L, column = C} -> %% A tag is pending: use its position instead of the %% position of the token about to be queued. {L, C}; undefined -> case Token1 of #yamerl_doc_start{} -> {?TOKEN_LINE(Token2), ?TOKEN_COLUMN(Token2)}; _ -> {?TOKEN_LINE(Token1), ?TOKEN_COLUMN(Token1)} end end, Empty = empty_scalar(Line, Col), Parser1 = Parser#yamerl_parser{ tks_queued = Queued + 1 }, handle_anchor_property(Parser1, Empty); check_tokens_in_a_row(Parser, Token1, Token2) when (is_record(Token1, yamerl_scalar) orelse is_record(Token1, yamerl_collection_end)) andalso (is_record(Token2, yamerl_scalar) orelse is_record(Token2, yamerl_collection_start)) -> %% Token2 can't follow Token1. Error = #yamerl_parsing_error{ name = unpected_token, token = Token2, line = ?TOKEN_LINE(Token2), column = ?TOKEN_COLUMN(Token2) }, Parser1 = add_error(Parser, Error, "Unexpected \"~s\" token following a \"~s\" token", [?TOKEN_NAME(Token2), ?TOKEN_NAME(Token1)]), do_return(Parser1); check_tokens_in_a_row(Parser, _, _) -> Parser. handle_anchor_property( #yamerl_parser{last_anchor = undefined} = Parser, Token) -> handle_tag_property(Parser, Token); handle_anchor_property( #yamerl_parser{last_anchor = Anchor} = Parser, Token) when (is_record(Token, yamerl_collection_start) andalso Anchor#yamerl_anchor.line < Token#yamerl_collection_start.line) orelse (is_record(Token, yamerl_scalar) andalso (Anchor#yamerl_anchor.line < Token#yamerl_scalar.line orelse (Anchor#yamerl_anchor.line == Token#yamerl_scalar.line andalso Anchor#yamerl_anchor.column =< Token#yamerl_scalar.column))) -> Parser1 = do_emit_token(Parser, Anchor), Parser2 = Parser1#yamerl_parser{ last_anchor = undefined }, handle_tag_property(Parser2, Token); handle_anchor_property(Parser, Token) -> handle_tag_property(Parser, Token). handle_tag_property( #yamerl_parser{last_tag = undefined} = Parser, Token) -> do_emit_token(Parser, Token); handle_tag_property( #yamerl_parser{last_tag = Tag} = Parser, Token) when (is_record(Token, yamerl_collection_start) andalso Tag#yamerl_tag.line < Token#yamerl_collection_start.line) orelse (is_record(Token, yamerl_scalar) andalso (Tag#yamerl_tag.line < Token#yamerl_scalar.line orelse (Tag#yamerl_tag.line == Token#yamerl_scalar.line andalso Tag#yamerl_tag.column =< Token#yamerl_scalar.column))) -> %% The tag property is attached to this token. Token1 = case Token of #yamerl_scalar{} -> Token#yamerl_scalar{ tag = Tag }; #yamerl_collection_start{} -> Token#yamerl_collection_start{ tag = Tag } end, %% Clear the pending tag property. Parser1 = Parser#yamerl_parser{ last_tag = undefined }, do_emit_token(Parser1, Token1); handle_tag_property(Parser, Token) -> do_emit_token(Parser, Token). do_emit_token( #yamerl_parser{token_fun = Not_Fun, tks_queued = Queued, tks_first_idx = First, tks_emitted = Emitted, tks_ready = Ready} = Parser, Token) when Not_Fun == acc orelse Not_Fun == drop -> %% The anchor was already counted when first removed from the queue. {Queued1, First1} = case ?TOKEN_NAME(Token) of yamerl_anchor -> {Queued, First}; _ -> {Queued - 1, First + 1} end, Ready1 = case Not_Fun of acc -> [Token | Ready]; drop -> Ready end, Parser#yamerl_parser{ tks_queued = Queued1, tks_first_idx = First1, tks_emitted = Emitted + 1, last_token = Token, tks_ready = Ready1 }; do_emit_token( #yamerl_parser{token_fun = Fun, tks_queued = Queued, tks_first_idx = First, tks_emitted = Emitted} = Parser, Token) -> %% The anchor was already counted when first removed from the queue. {Queued1, First1} = case ?TOKEN_NAME(Token) of yamerl_anchor -> {Queued, First}; _ -> {Queued - 1, First + 1} end, try Fun1 = case Fun(Token) of ok -> Fun; {ok, F1} -> F1 end, Parser#yamerl_parser{ token_fun = Fun1, tks_queued = Queued1, tks_first_idx = First1, tks_emitted = Emitted + 1, last_token = Token } catch throw:Error when is_record(Error, yamerl_parsing_error) -> Parser1 = add_error(Parser, Error), Parser2 = Parser1#yamerl_parser{ tks_queued = Queued1, tks_first_idx = First1, tks_emitted = Emitted + 1, last_token = Token }, if Error#yamerl_parsing_error.type == error -> do_return(Parser2); true -> Parser2 end; throw:{Fun2, Error} when is_record(Error, yamerl_parsing_error) -> Parser1 = add_error(Parser, Error), Parser2 = Parser1#yamerl_parser{ token_fun = Fun2, tks_queued = Queued1, tks_first_idx = First1, tks_emitted = Emitted + 1, last_token = Token }, if Error#yamerl_parsing_error.type == error -> do_return(Parser2); true -> Parser2 end end. next_insert_at(tail, _) -> tail; next_insert_at(Insert_At, N) -> Insert_At + N. max_token_idx_ready(#yamerl_parser{ik_stack = Stack, tks_first_idx = First, tks_queued = Queued}) -> max_token_idx_ready2(First + Queued - 1, lists:reverse(Stack)). max_token_idx_ready2(_, [#impl_key{possible = true, token_idx = Idx} | _]) -> Idx - 1; max_token_idx_ready2(All, [_ | Rest]) -> max_token_idx_ready2(All, Rest); max_token_idx_ready2(All, []) -> All. %% A token was inserted before the potential implicit key: move the %% key's index. update_impl_key_index(#yamerl_parser{ik_stack = Stack} = Parser, Line, Col) -> update_impl_key_index2(Parser, Stack, Line, Col, []). update_impl_key_index2(Parser, [#impl_key{token_idx = Index, line = Key_L, col = Key_C} = Impl_Key | Rest], Line, Col, Result) when is_integer(Key_L) andalso is_integer(Key_C) andalso (Line < Key_L orelse (Line == Key_L andalso Col =< Key_C)) -> Impl_Key1 = Impl_Key#impl_key{ token_idx = Index + 1 }, Result1 = [Impl_Key1 | Result], update_impl_key_index2(Parser, Rest, Line, Col, Result1); update_impl_key_index2(Parser, [#impl_key{line = Key_L, col = Key_C} | _] = Rest, Line, Col, Result) when is_integer(Key_L) andalso is_integer(Key_C) andalso (Line > Key_L orelse (Line == Key_L andalso Col > Key_C)) -> Parser#yamerl_parser{ ik_stack = lists:reverse(Result) ++ Rest }; update_impl_key_index2(Parser, [Impl_Key | Rest], Line, Col, Result) -> Result1 = [Impl_Key | Result], update_impl_key_index2(Parser, Rest, Line, Col, Result1); update_impl_key_index2(Parser, [], _, _, Result) -> Parser#yamerl_parser{ ik_stack = lists:reverse(Result) }. %% ------------------------------------------------------------------- %% Tag resolution. %% ------------------------------------------------------------------- setup_default_tags(#yamerl_parser{options = Options} = Parser) -> Tags = dict:new(), %% By default, "!" is resolved as "!" and the tag is considered %% local. Tags1 = dict:store({default, "!"}, "!", Tags), %% By default, "!!" is resolved as "tag:yaml.org,2002:" and is used %% by the YAML tags repository. Tags2 = dict:store({default, "!!"}, "tag:yaml.org,2002:", Tags1), %% Non-specific tags are associated to nodes which don't have an %% explicit tag or to those with the "!" explicit non-specific tag. %% The non-specific tags are resolved using a schema. Tags3 = dict:store({default, {non_specific, "!"}}, "tag:yaml.org,2002:", Tags2), Tags4 = dict:store({default, {non_specific, "?"}}, "tag:yaml.org,2002:", Tags3), Tags5 = case proplists:get_value(default_tags, Options) of undefined -> Tags4; List -> Fun = fun({Prefix, Value}, T) -> dict:store({default, Prefix}, Value, T) end, lists:foldl(Fun, Tags4, List) end, Parser#yamerl_parser{ tags = Tags5 }. %% ------------------------------------------------------------------- %% Internal functions. %% ------------------------------------------------------------------- option_names() -> [ default_tags, doc_version, io_blocksize, token_fun ]. check_options([Option | Rest]) -> case is_option_valid(Option) of true -> check_options(Rest); false -> invalid_option(Option) end; check_options([]) -> ok. is_option_valid({default_tags, List}) when is_list(List) -> %% This fun() returns true for any invalid entries, to keep only %% those. Fun = fun ({{non_specific, A}, B}) -> not (io_lib:char_list(A) andalso io_lib:char_list(B)); ({A, B}) -> not (io_lib:char_list(A) andalso io_lib:char_list(B)); (_) -> true end, case lists:filter(Fun, List) of [] -> true; _ -> false end; is_option_valid({doc_version, {Major, Minor}}) when is_integer(Major) andalso Major >= 0 andalso is_integer(Minor) andalso Minor >= 0 -> true; is_option_valid({io_blocksize, BS}) when is_integer(BS) andalso BS >= 1 -> true; is_option_valid({token_fun, acc}) -> true; is_option_valid({token_fun, drop}) -> true; is_option_valid({token_fun, Fun}) when is_function(Fun, 1) -> true; is_option_valid(_) -> false. invalid_option(Option) -> Error = #yamerl_invalid_option{ option = Option }, Error1 = case Option of {default_tags, _} -> Error#yamerl_invalid_option{ text = "Invalid value for option \"default_tags\": " "it must be a list of {Prefix, Prefix_Value}" }; {doc_version, _} -> Error#yamerl_invalid_option{ text = "Invalid value for option \"doc_version\": " "it must be a tuple of the form {Major, Minor} " "where Major and Minor are positive integers" }; {io_blocksize, _} -> Error#yamerl_invalid_option{ text = "Invalid value for option \"io_blocksize\": " "it must be a positive interger, expressed in bytes" }; {token_fun, _} -> Error#yamerl_invalid_option{ text = "Invalid value for option \"token_fun\": " "it must be a function taking the next token as " "its sole argument, or the atom 'acc' or 'drop'" }; _ -> yamerl_errors:format(Error, "Unknown option \"~w\"", [Option]) end, yamerl_errors:throw(Error1). empty_scalar(Line, Col) -> #yamerl_scalar{ style = flow, substyle = plain, text = "", line = Line, column = Col, tag = ?PLAIN_SCALAR_DEFAULT_TAG(Line, Col) }. check_for_closed_block_collections([C | _] = Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #bcoll{kind = sequence, indent = At_Col}, parent_colls = [#bcoll{kind = mapping, indent = At_Col} = Parent_Coll | Colls]} = Parser, At_Col) when C /= $- -> %% The sequence has the same indentation level than its parent %% mapping. The next token has this same indentation but is not a %% sequence entry (denoted by the '-' character). Let's close it but %% not the parent mapping. Token = #yamerl_collection_end{ style = block, kind = sequence, line = Parser#yamerl_parser.last_token_endline, column = Parser#yamerl_parser.last_token_endcol }, Parser1 = queue_token(Parser, Token), %% Remove its indentation from the stack. Parser2 = Parser1#yamerl_parser{ cur_coll = Parent_Coll, parent_colls = Colls }, check_for_closed_block_collections(Chars, Line, Col, Delta, Parser2, At_Col); check_for_closed_block_collections(Chars, Line, Col, Delta, #yamerl_parser{cur_coll = #bcoll{kind = Kind, indent = Indent}, parent_colls = [Parent_Coll | Colls]} = Parser, At_Col) when At_Col < Indent -> Parser1 = finish_incomplete_block_entries(Line, Col, Parser), %% Emit a token to signal the end of the block collection. Token = #yamerl_collection_end{ style = block, kind = Kind, line = Parser1#yamerl_parser.last_token_endline, column = Parser1#yamerl_parser.last_token_endcol }, Parser2 = queue_token(Parser1, Token), %% Remove its indentation from the stack. Parser3 = Parser2#yamerl_parser{ cur_coll = Parent_Coll, parent_colls = Colls }, check_for_closed_block_collections(Chars, Line, Col, Delta, Parser3, At_Col); check_for_closed_block_collections(_, _, _, _, Parser, _) -> Parser. is_uri_valid(Parser, #yamerl_tag{uri = {non_specific, _}}) -> Parser; is_uri_valid(Parser, #yamerl_tag{uri = [$! | _]}) -> Parser; is_uri_valid(Parser, #yamerl_tag{uri = URI} = Tag) -> is_uri_scheme_valid1(Parser, Tag, URI); is_uri_valid(Parser, #yamerl_tag_directive{prefix = [$! | _]}) -> Parser; is_uri_valid(Parser, #yamerl_tag_directive{prefix = URI} = Directive) -> is_uri_scheme_valid1(Parser, Directive, URI). is_uri_scheme_valid1(Parser, Token, [C | Rest]) when (C >= $a andalso C =< $z) orelse (C >= $A andalso C =< $Z) -> is_uri_scheme_valid2(Parser, Token, Rest); is_uri_scheme_valid1(Parser, Token, [_ | _]) -> Error = #yamerl_parsing_error{ name = invalid_uri, type = warning, token = Token, line = ?TOKEN_LINE(Token), column = ?TOKEN_COLUMN(Token) }, add_error(Parser, Error, "Invalid character in URI scheme", []); is_uri_scheme_valid1(Parser, Token, []) -> Error = #yamerl_parsing_error{ name = invalid_uri, type = warning, token = Token, line = ?TOKEN_LINE(Token), column = ?TOKEN_COLUMN(Token) }, add_error(Parser, Error, "Unexpected end of URI", []). is_uri_scheme_valid2(Parser, Token, [C | Rest]) when (C >= $a andalso C =< $z) orelse (C >= $A andalso C =< $Z) orelse (C >= $0 andalso C =< $9) orelse C == $+ orelse C == $. orelse C == $- -> is_uri_scheme_valid2(Parser, Token, Rest); is_uri_scheme_valid2(Parser, Token, [$: | Rest]) -> is_uri_hier_part_valid(Parser, Token, Rest); is_uri_scheme_valid2(Parser, Token, [_ | _]) -> Error = #yamerl_parsing_error{ name = invalid_uri, type = warning, token = Token, line = ?TOKEN_LINE(Token), column = ?TOKEN_COLUMN(Token) }, add_error(Parser, Error, "Invalid character in URI scheme", []); is_uri_scheme_valid2(Parser, Token, []) -> Error = #yamerl_parsing_error{ name = invalid_uri, type = warning, token = Token, line = ?TOKEN_LINE(Token), column = ?TOKEN_COLUMN(Token) }, add_error(Parser, Error, "Unexpected end of URI", []). is_uri_hier_part_valid(Parser, Token, [C | Rest]) when ?IS_URI_CHAR(C) -> is_uri_hier_part_valid(Parser, Token, Rest); is_uri_hier_part_valid(Parser, _, []) -> Parser; is_uri_hier_part_valid(Parser, Token, [_ | _]) -> Error = #yamerl_parsing_error{ name = invalid_uri, type = warning, token = Token, line = ?TOKEN_LINE(Token), column = ?TOKEN_COLUMN(Token) }, add_error(Parser, Error, "Invalid character in URI scheme", []). add_error(Parser, Error, Format, Args) -> %% Format error message. Error1 = yamerl_errors:format(Error, Format, Args), add_error(Parser, Error1). add_error( #yamerl_parser{has_errors = Has_Errors, errors = Errors} = Parser, Error) -> %% Update has_errors flag. Has_Errors1 = if Has_Errors -> Has_Errors; true -> Error#yamerl_parsing_error.type == error end, Parser#yamerl_parser{ has_errors = Has_Errors1, errors = [Error | Errors] }. -spec suspend_parsing(Chars, Line, Col, Delta, Parser, Fun) -> Ret | no_return() when Chars :: unicode_string(), Line :: position(), Col :: position(), Delta :: non_neg_integer(), Parser :: yamerl_parser(), Fun :: stream_state_fun(), Ret :: {continue, New_Parser} | Parser, New_Parser :: yamerl_parser(). suspend_parsing(Chars, Line, Col, Delta, Parser, Fun) -> Parser1 = ?FLUSH_TO_PARSER(Chars, Line, Col, Delta, Parser), Parser2 = Parser1#yamerl_parser{ stream_state = Fun }, do_return(Parser2). suspend_parsing(Chars, Line, Col, Delta, Parser, Fun, Ctx) -> Fun1 = fun(Ch, Li, Co, De, P) -> Fun(Ch, Li, Co, De, P, Ctx) end, suspend_parsing(Chars, Line, Col, Delta, Parser, Fun1). return(Chars, Line, Col, Delta, Parser) -> Parser1 = ?FLUSH_TO_PARSER(Chars, Line, Col, Delta, Parser), do_return(Parser1). -spec do_return(Parser) -> {continue, Parser} | Parser | no_return() when Parser :: yamerl_parser(). do_return(#yamerl_parser{has_errors = true, errors = Errors}) -> yamerl_errors:throw(Errors); do_return(#yamerl_parser{raw_eos = true, chars_len = 0} = Parser) -> Parser; do_return(Parser) -> {continue, Parser}.