+/*
+ * Eat whitespaces and comments until the next token is found.
+ */
+
+static int
+yaml_parser_scan_to_next_token(yaml_parser_t *parser)
+{
+ /* Until the next token is not found. */
+
+ while (1)
+ {
+ /* Allow the BOM mark to start a line. */
+
+ if (!CACHE(parser, 1)) return 0;
+
+ if (parser->mark.column == 0 && IS_BOM(parser->buffer))
+ SKIP(parser);
+
+ /*
+ * Eat whitespaces.
+ *
+ * Tabs are allowed:
+ *
+ * - in the flow context;
+ * - in the block context, but not at the beginning of the line or
+ * after '-', '?', or ':' (complex value).
+ */
+
+ if (!CACHE(parser, 1)) return 0;
+
+ while (CHECK(parser->buffer,' ') ||
+ ((parser->flow_level || !parser->simple_key_allowed) &&
+ CHECK(parser->buffer, '\t'))) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) return 0;
+ }
+
+ /* Eat a comment until a line break. */
+
+ if (CHECK(parser->buffer, '#')) {
+ while (!IS_BREAKZ(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) return 0;
+ }
+ }
+
+ /* If it is a line break, eat it. */
+
+ if (IS_BREAK(parser->buffer))
+ {
+ if (!CACHE(parser, 2)) return 0;
+ SKIP_LINE(parser);
+
+ /* In the block context, a new line may start a simple key. */
+
+ if (!parser->flow_level) {
+ parser->simple_key_allowed = 1;
+ }
+ }
+ else
+ {
+ /* We have found a token. */
+
+ break;
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token.
+ *
+ * Scope:
+ * %YAML 1.1 # a comment \n
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * %TAG !yaml! tag:yaml.org,2002: \n
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ */
+
+int
+yaml_parser_scan_directive(yaml_parser_t *parser, yaml_token_t *token)
+{
+ yaml_mark_t start_mark, end_mark;
+ yaml_char_t *name = NULL;
+ int major, minor;
+ yaml_char_t *handle = NULL, *prefix = NULL;
+
+ /* Eat '%'. */
+
+ start_mark = parser->mark;
+
+ SKIP(parser);
+
+ /* Scan the directive name. */
+
+ if (!yaml_parser_scan_directive_name(parser, start_mark, &name))
+ goto error;
+
+ /* Is it a YAML directive? */
+
+ if (strcmp((char *)name, "YAML") == 0)
+ {
+ /* Scan the VERSION directive value. */
+
+ if (!yaml_parser_scan_version_directive_value(parser, start_mark,
+ &major, &minor))
+ goto error;
+
+ end_mark = parser->mark;
+
+ /* Create a VERSION-DIRECTIVE token. */
+
+ VERSION_DIRECTIVE_TOKEN_INIT(*token, major, minor,
+ start_mark, end_mark);
+ }
+
+ /* Is it a TAG directive? */
+
+ else if (strcmp((char *)name, "TAG") == 0)
+ {
+ /* Scan the TAG directive value. */
+
+ if (!yaml_parser_scan_tag_directive_value(parser, start_mark,
+ &handle, &prefix))
+ goto error;
+
+ end_mark = parser->mark;
+
+ /* Create a TAG-DIRECTIVE token. */
+
+ TAG_DIRECTIVE_TOKEN_INIT(*token, handle, prefix,
+ start_mark, end_mark);
+ }
+
+ /* Unknown directive. */
+
+ else
+ {
+ yaml_parser_set_scanner_error(parser, "while scanning a directive",
+ start_mark, "found uknown directive name");
+ goto error;
+ }
+
+ /* Eat the rest of the line including any comments. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (IS_BLANK(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ if (CHECK(parser->buffer, '#')) {
+ while (!IS_BREAKZ(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) goto error;
+ }
+ }
+
+ /* Check if we are at the end of the line. */
+
+ if (!IS_BREAKZ(parser->buffer)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a directive",
+ start_mark, "did not found expected comment or line break");
+ goto error;
+ }
+
+ /* Eat a line break. */
+
+ if (IS_BREAK(parser->buffer)) {
+ if (!CACHE(parser, 2)) goto error;
+ SKIP_LINE(parser);
+ }
+
+ yaml_free(name);
+
+ return 1;
+
+error:
+ yaml_free(prefix);
+ yaml_free(handle);
+ yaml_free(name);
+ return 0;
+}
+
+/*
+ * Scan the directive name.
+ *
+ * Scope:
+ * %YAML 1.1 # a comment \n
+ * ^^^^
+ * %TAG !yaml! tag:yaml.org,2002: \n
+ * ^^^
+ */
+
+static int
+yaml_parser_scan_directive_name(yaml_parser_t *parser,
+ yaml_mark_t start_mark, yaml_char_t **name)
+{
+ yaml_string_t string = NULL_STRING;
+
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error;
+
+ /* Consume the directive name. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (IS_ALPHA(parser->buffer))
+ {
+ if (!READ(parser, string)) goto error;
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ /* Check if the name is empty. */
+
+ if (string.start == string.pointer) {
+ yaml_parser_set_scanner_error(parser, "while scanning a directive",
+ start_mark, "cannot found expected directive name");
+ goto error;
+ }
+
+ /* Check for an blank character after the name. */
+
+ if (!IS_BLANKZ(parser->buffer)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a directive",
+ start_mark, "found unexpected non-alphabetical character");
+ goto error;
+ }
+
+ *name = string.start;
+
+ return 1;
+
+error:
+ STRING_DEL(parser, string);
+ return 0;
+}
+
+/*
+ * Scan the value of VERSION-DIRECTIVE.
+ *
+ * Scope:
+ * %YAML 1.1 # a comment \n
+ * ^^^^^^
+ */
+
+static int
+yaml_parser_scan_version_directive_value(yaml_parser_t *parser,
+ yaml_mark_t start_mark, int *major, int *minor)
+{
+ /* Eat whitespaces. */
+
+ if (!CACHE(parser, 1)) return 0;
+
+ while (IS_BLANK(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) return 0;
+ }
+
+ /* Consume the major version number. */
+
+ if (!yaml_parser_scan_version_directive_number(parser, start_mark, major))
+ return 0;
+
+ /* Eat '.'. */
+
+ if (!CHECK(parser->buffer, '.')) {
+ return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive",
+ start_mark, "did not find expected digit or '.' character");
+ }
+
+ SKIP(parser);
+
+ /* Consume the minor version number. */
+
+ if (!yaml_parser_scan_version_directive_number(parser, start_mark, minor))
+ return 0;
+
+ return 1;
+}
+
+#define MAX_NUMBER_LENGTH 9
+
+/*
+ * Scan the version number of VERSION-DIRECTIVE.
+ *
+ * Scope:
+ * %YAML 1.1 # a comment \n
+ * ^
+ * %YAML 1.1 # a comment \n
+ * ^
+ */
+
+static int
+yaml_parser_scan_version_directive_number(yaml_parser_t *parser,
+ yaml_mark_t start_mark, int *number)
+{
+ int value = 0;
+ size_t length = 0;
+
+ /* Repeat while the next character is digit. */
+
+ if (!CACHE(parser, 1)) return 0;
+
+ while (IS_DIGIT(parser->buffer))
+ {
+ /* Check if the number is too long. */
+
+ if (++length > MAX_NUMBER_LENGTH) {
+ return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive",
+ start_mark, "found extremely long version number");
+ }
+
+ value = value*10 + AS_DIGIT(parser->buffer);
+
+ SKIP(parser);
+
+ if (!CACHE(parser, 1)) return 0;
+ }
+
+ /* Check if the number was present. */
+
+ if (!length) {
+ return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive",
+ start_mark, "did not find expected version number");
+ }
+
+ *number = value;
+
+ return 1;
+}
+
+/*
+ * Scan the value of a TAG-DIRECTIVE token.
+ *
+ * Scope:
+ * %TAG !yaml! tag:yaml.org,2002: \n
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ */
+
+static int
+yaml_parser_scan_tag_directive_value(yaml_parser_t *parser,
+ yaml_mark_t start_mark, yaml_char_t **handle, yaml_char_t **prefix)
+{
+ yaml_char_t *handle_value = NULL;
+ yaml_char_t *prefix_value = NULL;
+
+ /* Eat whitespaces. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (IS_BLANK(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ /* Scan a handle. */
+
+ if (!yaml_parser_scan_tag_handle(parser, 1, start_mark, &handle_value))
+ goto error;
+
+ /* Expect a whitespace. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ if (!IS_BLANK(parser->buffer)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive",
+ start_mark, "did not find expected whitespace");
+ goto error;
+ }
+
+ /* Eat whitespaces. */
+
+ while (IS_BLANK(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ /* Scan a prefix. */
+
+ if (!yaml_parser_scan_tag_uri(parser, 1, NULL, start_mark, &prefix_value))
+ goto error;
+
+ /* Expect a whitespace or line break. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ if (!IS_BLANKZ(parser->buffer)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive",
+ start_mark, "did not find expected whitespace or line break");
+ goto error;
+ }
+
+ *handle = handle_value;
+ *prefix = prefix_value;
+
+ return 1;
+
+error:
+ yaml_free(handle_value);
+ yaml_free(prefix_value);
+ return 0;
+}
+
+static int
+yaml_parser_scan_anchor(yaml_parser_t *parser, yaml_token_t *token,
+ yaml_token_type_t type)
+{
+ int length = 0;
+ yaml_mark_t start_mark, end_mark;
+ yaml_string_t string = NULL_STRING;
+
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error;
+
+ /* Eat the indicator character. */
+
+ start_mark = parser->mark;
+
+ SKIP(parser);
+
+ /* Consume the value. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (IS_ALPHA(parser->buffer)) {
+ if (!READ(parser, string)) goto error;
+ if (!CACHE(parser, 1)) goto error;
+ length ++;
+ }
+
+ end_mark = parser->mark;
+
+ /*
+ * Check if length of the anchor is greater than 0 and it is followed by
+ * a whitespace character or one of the indicators:
+ *
+ * '?', ':', ',', ']', '}', '%', '@', '`'.
+ */
+
+ if (!length || !(IS_BLANKZ(parser->buffer) || CHECK(parser->buffer, '?')
+ || CHECK(parser->buffer, ':') || CHECK(parser->buffer, ',')
+ || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '}')
+ || CHECK(parser->buffer, '%') || CHECK(parser->buffer, '@')
+ || CHECK(parser->buffer, '`'))) {
+ yaml_parser_set_scanner_error(parser, type == YAML_ANCHOR_TOKEN ?
+ "while scanning an anchor" : "while scanning an alias", start_mark,
+ "did not find expected alphabetic or numeric character");
+ goto error;
+ }
+
+ /* Create a token. */
+
+ if (type == YAML_ANCHOR_TOKEN) {
+ ANCHOR_TOKEN_INIT(*token, string.start, start_mark, end_mark);
+ }
+ else {
+ ALIAS_TOKEN_INIT(*token, string.start, start_mark, end_mark);
+ }
+
+ return 1;
+
+error:
+ STRING_DEL(parser, string);
+ return 0;
+}
+
+/*
+ * Scan a TAG token.
+ */
+
+static int
+yaml_parser_scan_tag(yaml_parser_t *parser, yaml_token_t *token)
+{
+ yaml_char_t *handle = NULL;
+ yaml_char_t *suffix = NULL;
+ yaml_mark_t start_mark, end_mark;
+
+ start_mark = parser->mark;
+
+ /* Check if the tag is in the canonical form. */
+
+ if (!CACHE(parser, 2)) goto error;
+
+ if (CHECK_AT(parser->buffer, '<', 1))
+ {
+ /* Set the handle to '' */
+
+ handle = yaml_malloc(1);
+ if (!handle) goto error;
+ handle[0] = '\0';
+
+ /* Eat '!<' */
+
+ SKIP(parser);
+ SKIP(parser);
+
+ /* Consume the tag value. */
+
+ if (!yaml_parser_scan_tag_uri(parser, 0, NULL, start_mark, &suffix))
+ goto error;
+
+ /* Check for '>' and eat it. */
+
+ if (!CHECK(parser->buffer, '>')) {
+ yaml_parser_set_scanner_error(parser, "while scanning a tag",
+ start_mark, "did not find the expected '>'");
+ goto error;
+ }
+
+ SKIP(parser);
+ }
+ else
+ {
+ /* The tag has either the '!suffix' or the '!handle!suffix' form. */
+
+ /* First, try to scan a handle. */
+
+ if (!yaml_parser_scan_tag_handle(parser, 0, start_mark, &handle))
+ goto error;
+
+ /* Check if it is, indeed, handle. */
+
+ if (handle[0] == '!' && handle[1] != '\0' && handle[strlen((char *)handle)-1] == '!')
+ {
+ /* Scan the suffix now. */
+
+ if (!yaml_parser_scan_tag_uri(parser, 0, NULL, start_mark, &suffix))
+ goto error;
+ }
+ else
+ {
+ /* It wasn't a handle after all. Scan the rest of the tag. */
+
+ if (!yaml_parser_scan_tag_uri(parser, 0, handle, start_mark, &suffix))
+ goto error;
+
+ /* Set the handle to '!'. */
+
+ yaml_free(handle);
+ handle = yaml_malloc(2);
+ if (!handle) goto error;
+ handle[0] = '!';
+ handle[1] = '\0';
+
+ /*
+ * A special case: the '!' tag. Set the handle to '' and the
+ * suffix to '!'.
+ */
+
+ if (suffix[0] == '\0') {
+ yaml_char_t *tmp = handle;
+ handle = suffix;
+ suffix = tmp;
+ }
+ }
+ }
+
+ /* Check the character which ends the tag. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ if (!IS_BLANKZ(parser->buffer)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a tag",
+ start_mark, "did not found expected whitespace or line break");
+ goto error;
+ }
+
+ end_mark = parser->mark;
+
+ /* Create a token. */
+
+ TAG_TOKEN_INIT(*token, handle, suffix, start_mark, end_mark);
+
+ return 1;
+
+error:
+ yaml_free(handle);
+ yaml_free(suffix);
+ return 0;
+}
+
+/*
+ * Scan a tag handle.
+ */
+
+static int
+yaml_parser_scan_tag_handle(yaml_parser_t *parser, int directive,
+ yaml_mark_t start_mark, yaml_char_t **handle)
+{
+ yaml_string_t string = NULL_STRING;
+
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error;
+
+ /* Check the initial '!' character. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ if (!CHECK(parser->buffer, '!')) {
+ yaml_parser_set_scanner_error(parser, directive ?
+ "while scanning a tag directive" : "while scanning a tag",
+ start_mark, "did not find expected '!'");
+ goto error;
+ }
+
+ /* Copy the '!' character. */
+
+ if (!READ(parser, string)) goto error;
+
+ /* Copy all subsequent alphabetical and numerical characters. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (IS_ALPHA(parser->buffer))
+ {
+ if (!READ(parser, string)) goto error;
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ /* Check if the trailing character is '!' and copy it. */
+
+ if (CHECK(parser->buffer, '!'))
+ {
+ if (!READ(parser, string)) goto error;
+ }
+ else
+ {
+ /*
+ * It's either the '!' tag or not really a tag handle. If it's a %TAG
+ * directive, it's an error. If it's a tag token, it must be a part of
+ * URI.
+ */
+
+ if (directive && !(string.start[0] == '!' && string.start[1] == '\0')) {
+ yaml_parser_set_scanner_error(parser, "while parsing a tag directive",
+ start_mark, "did not find expected '!'");
+ goto error;
+ }
+ }
+
+ *handle = string.start;
+
+ return 1;
+
+error:
+ STRING_DEL(parser, string);
+ return 0;
+}
+
+/*
+ * Scan a tag.
+ */
+
+static int
+yaml_parser_scan_tag_uri(yaml_parser_t *parser, int directive,
+ yaml_char_t *head, yaml_mark_t start_mark, yaml_char_t **uri)
+{
+ size_t length = head ? strlen((char *)head) : 0;
+ yaml_string_t string = NULL_STRING;
+
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error;
+
+ /* Resize the string to include the head. */
+
+ while (string.end - string.start <= length) {
+ if (!yaml_string_extend(&string.start, &string.pointer, &string.end)) {
+ parser->error = YAML_MEMORY_ERROR;
+ goto error;
+ }
+ }
+
+ /*
+ * Copy the head if needed.
+ *
+ * Note that we don't copy the leading '!' character.
+ */
+
+ if (length > 1) {
+ memcpy(string.start, head+1, length-1);
+ string.pointer += length-1;
+ }
+
+ /* Scan the tag. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ /*
+ * The set of characters that may appear in URI is as follows:
+ *
+ * '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&',
+ * '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']',
+ * '%'.
+ */
+
+ while (IS_ALPHA(parser->buffer) || CHECK(parser->buffer, ';')
+ || CHECK(parser->buffer, '/') || CHECK(parser->buffer, '?')
+ || CHECK(parser->buffer, ':') || CHECK(parser->buffer, '@')
+ || CHECK(parser->buffer, '&') || CHECK(parser->buffer, '=')
+ || CHECK(parser->buffer, '+') || CHECK(parser->buffer, '$')
+ || CHECK(parser->buffer, ',') || CHECK(parser->buffer, '.')
+ || CHECK(parser->buffer, '!') || CHECK(parser->buffer, '~')
+ || CHECK(parser->buffer, '*') || CHECK(parser->buffer, '\'')
+ || CHECK(parser->buffer, '(') || CHECK(parser->buffer, ')')
+ || CHECK(parser->buffer, '[') || CHECK(parser->buffer, ']')
+ || CHECK(parser->buffer, '%'))
+ {
+ /* Check if it is a URI-escape sequence. */
+
+ if (CHECK(parser->buffer, '%')) {
+ if (!yaml_parser_scan_uri_escapes(parser,
+ directive, start_mark, &string)) goto error;
+ }
+ else {
+ if (!READ(parser, string)) goto error;
+ }
+
+ length ++;
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ /* Check if the tag is non-empty. */
+
+ if (!length) {
+ if (!STRING_EXTEND(parser, string))
+ goto error;
+
+ yaml_parser_set_scanner_error(parser, directive ?
+ "while parsing a %TAG directive" : "while parsing a tag",
+ start_mark, "did not find expected tag URI");
+ goto error;
+ }
+
+ *uri = string.start;
+
+ return 1;
+
+error:
+ STRING_DEL(parser, string);
+ return 0;
+}
+
+/*
+ * Decode an URI-escape sequence corresponding to a single UTF-8 character.
+ */
+
+static int
+yaml_parser_scan_uri_escapes(yaml_parser_t *parser, int directive,
+ yaml_mark_t start_mark, yaml_string_t *string)
+{
+ int width = 0;
+
+ /* Decode the required number of characters. */
+
+ do {
+
+ unsigned char octet = 0;
+
+ /* Check for a URI-escaped octet. */
+
+ if (!CACHE(parser, 3)) return 0;
+
+ if (!(CHECK(parser->buffer, '%')
+ && IS_HEX_AT(parser->buffer, 1)
+ && IS_HEX_AT(parser->buffer, 2))) {
+ return yaml_parser_set_scanner_error(parser, directive ?
+ "while parsing a %TAG directive" : "while parsing a tag",
+ start_mark, "did not find URI escaped octet");
+ }
+
+ /* Get the octet. */
+
+ octet = (AS_HEX_AT(parser->buffer, 1) << 4) + AS_HEX_AT(parser->buffer, 2);
+
+ /* If it is the leading octet, determine the length of the UTF-8 sequence. */
+
+ if (!width)
+ {
+ width = (octet & 0x80) == 0x00 ? 1 :
+ (octet & 0xE0) == 0xC0 ? 2 :
+ (octet & 0xF0) == 0xE0 ? 3 :
+ (octet & 0xF8) == 0xF0 ? 4 : 0;
+ if (!width) {
+ return yaml_parser_set_scanner_error(parser, directive ?
+ "while parsing a %TAG directive" : "while parsing a tag",
+ start_mark, "found an incorrect leading UTF-8 octet");
+ }
+ }
+ else
+ {
+ /* Check if the trailing octet is correct. */
+
+ if ((octet & 0xC0) != 0x80) {
+ return yaml_parser_set_scanner_error(parser, directive ?
+ "while parsing a %TAG directive" : "while parsing a tag",
+ start_mark, "found an incorrect trailing UTF-8 octet");
+ }
+ }
+
+ /* Copy the octet and move the pointers. */
+
+ *(string->pointer++) = octet;
+ SKIP(parser);
+ SKIP(parser);
+ SKIP(parser);
+
+ } while (--width);
+
+ return 1;
+}
+
+/*
+ * Scan a block scalar.
+ */
+
+static int
+yaml_parser_scan_block_scalar(yaml_parser_t *parser, yaml_token_t *token,
+ int literal)
+{
+ yaml_mark_t start_mark;
+ yaml_mark_t end_mark;
+ yaml_string_t string = NULL_STRING;
+ yaml_string_t leading_break = NULL_STRING;
+ yaml_string_t trailing_breaks = NULL_STRING;
+ int chomping = 0;
+ int increment = 0;
+ int indent = 0;
+ int leading_blank = 0;
+ int trailing_blank = 0;
+
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error;
+ if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error;
+ if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error;
+
+ /* Eat the indicator '|' or '>'. */
+
+ start_mark = parser->mark;
+
+ SKIP(parser);
+
+ /* Scan the additional block scalar indicators. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ /* Check for a chomping indicator. */
+
+ if (CHECK(parser->buffer, '+') || CHECK(parser->buffer, '-'))
+ {
+ /* Set the chomping method and eat the indicator. */
+
+ chomping = CHECK(parser->buffer, '+') ? +1 : -1;
+
+ SKIP(parser);
+
+ /* Check for an indentation indicator. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ if (IS_DIGIT(parser->buffer))
+ {
+ /* Check that the intendation is greater than 0. */
+
+ if (CHECK(parser->buffer, '0')) {
+ yaml_parser_set_scanner_error(parser, "while scanning a block scalar",
+ start_mark, "found an intendation indicator equal to 0");
+ goto error;
+ }
+
+ /* Get the intendation level and eat the indicator. */
+
+ increment = AS_DIGIT(parser->buffer);
+
+ SKIP(parser);
+ }
+ }
+
+ /* Do the same as above, but in the opposite order. */
+
+ else if (IS_DIGIT(parser->buffer))
+ {
+ if (CHECK(parser->buffer, '0')) {
+ yaml_parser_set_scanner_error(parser, "while scanning a block scalar",
+ start_mark, "found an intendation indicator equal to 0");
+ goto error;
+ }
+
+ increment = AS_DIGIT(parser->buffer);
+
+ SKIP(parser);
+
+ if (!CACHE(parser, 1)) goto error;
+
+ if (CHECK(parser->buffer, '+') || CHECK(parser->buffer, '-')) {
+ chomping = CHECK(parser->buffer, '+') ? +1 : -1;
+
+ SKIP(parser);
+ }
+ }
+
+ /* Eat whitespaces and comments to the end of the line. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (IS_BLANK(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ if (CHECK(parser->buffer, '#')) {
+ while (!IS_BREAKZ(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) goto error;
+ }
+ }
+
+ /* Check if we are at the end of the line. */
+
+ if (!IS_BREAKZ(parser->buffer)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a block scalar",
+ start_mark, "did not found expected comment or line break");
+ goto error;
+ }
+
+ /* Eat a line break. */
+
+ if (IS_BREAK(parser->buffer)) {
+ if (!CACHE(parser, 2)) goto error;
+ SKIP_LINE(parser);
+ }
+
+ end_mark = parser->mark;
+
+ /* Set the intendation level if it was specified. */
+
+ if (increment) {
+ indent = parser->indent >= 0 ? parser->indent+increment : increment;
+ }
+
+ /* Scan the leading line breaks and determine the indentation level if needed. */
+
+ if (!yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks,
+ start_mark, &end_mark)) goto error;
+
+ /* Scan the block scalar content. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (parser->mark.column == indent && !IS_Z(parser->buffer))
+ {
+ /*
+ * We are at the beginning of a non-empty line.
+ */
+
+ /* Is it a trailing whitespace? */
+
+ trailing_blank = IS_BLANK(parser->buffer);
+
+ /* Check if we need to fold the leading line break. */
+
+ if (!literal && (*leading_break.start == '\n')
+ && !leading_blank && !trailing_blank)
+ {
+ /* Do we need to join the lines by space? */
+
+ if (*trailing_breaks.start == '\0') {
+ if (!STRING_EXTEND(parser, string)) goto error;
+ *(string.pointer ++) = ' ';
+ }
+
+ CLEAR(parser, leading_break);
+ }
+ else {
+ if (!JOIN(parser, string, leading_break)) goto error;
+ CLEAR(parser, leading_break);
+ }
+
+ /* Append the remaining line breaks. */
+
+ if (!JOIN(parser, string, trailing_breaks)) goto error;
+ CLEAR(parser, trailing_breaks);
+
+ /* Is it a leading whitespace? */
+
+ leading_blank = IS_BLANK(parser->buffer);
+
+ /* Consume the current line. */
+
+ while (!IS_BREAKZ(parser->buffer)) {
+ if (!READ(parser, string)) goto error;
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ /* Consume the line break. */
+
+ if (!CACHE(parser, 2)) goto error;
+
+ if (!READ_LINE(parser, leading_break)) goto error;
+
+ /* Eat the following intendation spaces and line breaks. */
+
+ if (!yaml_parser_scan_block_scalar_breaks(parser,
+ &indent, &trailing_breaks, start_mark, &end_mark)) goto error;
+ }
+
+ /* Chomp the tail. */
+
+ if (chomping != -1) {
+ if (!JOIN(parser, string, leading_break)) goto error;
+ }
+ if (chomping == 1) {
+ if (!JOIN(parser, string, trailing_breaks)) goto error;
+ }
+
+ /* Create a token. */
+
+ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start,
+ literal ? YAML_LITERAL_SCALAR_STYLE : YAML_FOLDED_SCALAR_STYLE,
+ start_mark, end_mark);
+
+ STRING_DEL(parser, leading_break);
+ STRING_DEL(parser, trailing_breaks);
+
+ return 1;
+
+error:
+ STRING_DEL(parser, string);
+ STRING_DEL(parser, leading_break);
+ STRING_DEL(parser, trailing_breaks);
+
+ return 0;
+}
+
+/*
+ * Scan intendation spaces and line breaks for a block scalar. Determine the
+ * intendation level if needed.
+ */
+
+static int
+yaml_parser_scan_block_scalar_breaks(yaml_parser_t *parser,
+ int *indent, yaml_string_t *breaks,
+ yaml_mark_t start_mark, yaml_mark_t *end_mark)
+{
+ int max_indent = 0;
+
+ *end_mark = parser->mark;
+
+ /* Eat the intendation spaces and line breaks. */
+
+ while (1)
+ {
+ /* Eat the intendation spaces. */
+
+ if (!CACHE(parser, 1)) return 0;
+
+ while ((!*indent || parser->mark.column < *indent)
+ && IS_SPACE(parser->buffer)) {
+ SKIP(parser);
+ if (!CACHE(parser, 1)) return 0;
+ }
+
+ if (parser->mark.column > max_indent)
+ max_indent = parser->mark.column;
+
+ /* Check for a tab character messing the intendation. */
+
+ if ((!*indent || parser->mark.column < *indent)
+ && IS_TAB(parser->buffer)) {
+ return yaml_parser_set_scanner_error(parser, "while scanning a block scalar",
+ start_mark, "found a tab character where an intendation space is expected");
+ }
+
+ /* Have we found a non-empty line? */
+
+ if (!IS_BREAK(parser->buffer)) break;
+
+ /* Consume the line break. */
+
+ if (!CACHE(parser, 2)) return 0;
+ if (!READ_LINE(parser, *breaks)) return 0;
+ *end_mark = parser->mark;
+ }
+
+ /* Determine the indentation level if needed. */
+
+ if (!*indent) {
+ *indent = max_indent;
+ if (*indent < parser->indent + 1)
+ *indent = parser->indent + 1;
+ if (*indent < 1)
+ *indent = 1;
+ }
+
+ return 1;
+}
+
+/*
+ * Scan a quoted scalar.
+ */
+
+static int
+yaml_parser_scan_flow_scalar(yaml_parser_t *parser, yaml_token_t *token,
+ int single)
+{
+ yaml_mark_t start_mark;
+ yaml_mark_t end_mark;
+ yaml_string_t string = NULL_STRING;
+ yaml_string_t leading_break = NULL_STRING;
+ yaml_string_t trailing_breaks = NULL_STRING;
+ yaml_string_t whitespaces = NULL_STRING;
+ int leading_blanks;
+
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error;
+ if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error;
+ if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error;
+ if (!STRING_INIT(parser, whitespaces, INITIAL_STRING_SIZE)) goto error;
+
+ /* Eat the left quote. */
+
+ start_mark = parser->mark;
+
+ SKIP(parser);
+
+ /* Consume the content of the quoted scalar. */
+
+ while (1)
+ {
+ /* Check that there are no document indicators at the beginning of the line. */
+
+ if (!CACHE(parser, 4)) goto error;
+
+ if (parser->mark.column == 0 &&
+ ((CHECK_AT(parser->buffer, '-', 0) &&
+ CHECK_AT(parser->buffer, '-', 1) &&
+ CHECK_AT(parser->buffer, '-', 2)) ||
+ (CHECK_AT(parser->buffer, '.', 0) &&
+ CHECK_AT(parser->buffer, '.', 1) &&
+ CHECK_AT(parser->buffer, '.', 2))) &&
+ IS_BLANKZ_AT(parser->buffer, 3))
+ {
+ yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar",
+ start_mark, "found unexpected document indicator");
+ goto error;
+ }
+
+ /* Check for EOF. */
+
+ if (IS_Z(parser->buffer)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar",
+ start_mark, "found unexpected end of stream");
+ goto error;
+ }
+
+ /* Consume non-blank characters. */
+
+ if (!CACHE(parser, 2)) goto error;
+
+ leading_blanks = 0;
+
+ while (!IS_BLANKZ(parser->buffer))
+ {
+ /* Check for an escaped single quote. */
+
+ if (single && CHECK_AT(parser->buffer, '\'', 0)
+ && CHECK_AT(parser->buffer, '\'', 1))
+ {
+ if (!STRING_EXTEND(parser, string)) goto error;
+ *(string.pointer++) = '\'';
+ SKIP(parser);
+ SKIP(parser);
+ }
+
+ /* Check for the right quote. */
+
+ else if (CHECK(parser->buffer, single ? '\'' : '"'))
+ {
+ break;
+ }
+
+ /* Check for an escaped line break. */
+
+ else if (!single && CHECK(parser->buffer, '\\')
+ && IS_BREAK_AT(parser->buffer, 1))
+ {
+ if (!CACHE(parser, 3)) goto error;
+ SKIP(parser);
+ SKIP_LINE(parser);
+ leading_blanks = 1;
+ break;
+ }
+
+ /* Check for an escape sequence. */
+
+ else if (!single && CHECK(parser->buffer, '\\'))
+ {
+ int code_length = 0;
+
+ if (!STRING_EXTEND(parser, string)) goto error;
+
+ /* Check the escape character. */
+
+ switch (parser->buffer.pointer[1])
+ {
+ case '0':
+ *(string.pointer++) = '\0';
+ break;
+
+ case 'a':
+ *(string.pointer++) = '\x07';
+ break;
+
+ case 'b':
+ *(string.pointer++) = '\x08';
+ break;
+
+ case 't':
+ case '\t':
+ *(string.pointer++) = '\x09';
+ break;
+
+ case 'n':
+ *(string.pointer++) = '\x0A';
+ break;
+
+ case 'v':
+ *(string.pointer++) = '\x0B';
+ break;
+
+ case 'f':
+ *(string.pointer++) = '\x0C';
+ break;
+
+ case 'r':
+ *(string.pointer++) = '\x0D';
+ break;
+
+ case 'e':
+ *(string.pointer++) = '\x1B';
+ break;
+
+ case ' ':
+ *(string.pointer++) = '\x20';
+ break;
+
+ case '"':
+ *(string.pointer++) = '"';
+ break;
+
+ case '\'':
+ *(string.pointer++) = '\'';
+ break;
+
+ case '\\':
+ *(string.pointer++) = '\\';
+ break;
+
+ case 'N': /* NEL (#x85) */
+ *(string.pointer++) = '\xC2';
+ *(string.pointer++) = '\x85';
+ break;
+
+ case '_': /* #xA0 */
+ *(string.pointer++) = '\xC2';
+ *(string.pointer++) = '\xA0';
+ break;
+
+ case 'L': /* LS (#x2028) */
+ *(string.pointer++) = '\xE2';
+ *(string.pointer++) = '\x80';
+ *(string.pointer++) = '\xA8';
+ break;
+
+ case 'P': /* PS (#x2029) */
+ *(string.pointer++) = '\xE2';
+ *(string.pointer++) = '\x80';
+ *(string.pointer++) = '\xA9';
+ break;
+
+ case 'x':
+ code_length = 2;
+ break;
+
+ case 'u':
+ code_length = 4;
+ break;
+
+ case 'U':
+ code_length = 8;
+ break;
+
+ default:
+ yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar",
+ start_mark, "found unknown escape character");
+ goto error;
+ }
+
+ SKIP(parser);
+ SKIP(parser);
+
+ /* Consume an arbitrary escape code. */
+
+ if (code_length)
+ {
+ unsigned int value = 0;
+ int k;
+
+ /* Scan the character value. */
+
+ if (!CACHE(parser, code_length)) goto error;
+
+ for (k = 0; k < code_length; k ++) {
+ if (!IS_HEX_AT(parser->buffer, k)) {
+ yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar",
+ start_mark, "did not find expected hexdecimal number");
+ goto error;
+ }
+ value = (value << 4) + AS_HEX_AT(parser->buffer, k);
+ }
+
+ /* Check the value and write the character. */
+
+ if ((value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) {
+ yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar",
+ start_mark, "found invalid Unicode character escape code");
+ goto error;
+ }
+
+ if (value <= 0x7F) {
+ *(string.pointer++) = value;
+ }
+ else if (value <= 0x7FF) {
+ *(string.pointer++) = 0xC0 + (value >> 6);
+ *(string.pointer++) = 0x80 + (value & 0x3F);
+ }
+ else if (value <= 0xFFFF) {
+ *(string.pointer++) = 0xE0 + (value >> 12);
+ *(string.pointer++) = 0x80 + ((value >> 6) & 0x3F);
+ *(string.pointer++) = 0x80 + (value & 0x3F);
+ }
+ else {
+ *(string.pointer++) = 0xF0 + (value >> 18);
+ *(string.pointer++) = 0x80 + ((value >> 12) & 0x3F);
+ *(string.pointer++) = 0x80 + ((value >> 6) & 0x3F);
+ *(string.pointer++) = 0x80 + (value & 0x3F);
+ }
+
+ /* Advance the pointer. */
+
+ for (k = 0; k < code_length; k ++) {
+ SKIP(parser);
+ }
+ }
+ }
+
+ else
+ {
+ /* It is a non-escaped non-blank character. */
+
+ if (!READ(parser, string)) goto error;
+ }
+
+ if (!CACHE(parser, 2)) goto error;
+ }
+
+ /* Check if we are at the end of the scalar. */
+
+ if (CHECK(parser->buffer, single ? '\'' : '"'))
+ break;
+
+ /* Consume blank characters. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer))
+ {
+ if (IS_BLANK(parser->buffer))
+ {
+ /* Consume a space or a tab character. */
+
+ if (!leading_blanks) {
+ if (!READ(parser, whitespaces)) goto error;
+ }
+ else {
+ SKIP(parser);
+ }
+ }
+ else
+ {
+ if (!CACHE(parser, 2)) goto error;
+
+ /* Check if it is a first line break. */
+
+ if (!leading_blanks)
+ {
+ CLEAR(parser, whitespaces);
+ if (!READ_LINE(parser, leading_break)) goto error;
+ leading_blanks = 1;
+ }
+ else
+ {
+ if (!READ_LINE(parser, trailing_breaks)) goto error;
+ }
+ }
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ /* Join the whitespaces or fold line breaks. */
+
+ if (leading_blanks)
+ {
+ /* Do we need to fold line breaks? */
+
+ if (leading_break.start[0] == '\n') {
+ if (trailing_breaks.start[0] == '\0') {
+ if (!STRING_EXTEND(parser, string)) goto error;
+ *(string.pointer++) = ' ';
+ }
+ else {
+ if (!JOIN(parser, string, trailing_breaks)) goto error;
+ CLEAR(parser, trailing_breaks);
+ }
+ CLEAR(parser, leading_break);
+ }
+ else {
+ if (!JOIN(parser, string, leading_break)) goto error;
+ if (!JOIN(parser, string, trailing_breaks)) goto error;
+ CLEAR(parser, leading_break);
+ CLEAR(parser, trailing_breaks);
+ }
+ }
+ else
+ {
+ if (!JOIN(parser, string, whitespaces)) goto error;
+ CLEAR(parser, whitespaces);
+ }
+ }
+
+ /* Eat the right quote. */
+
+ SKIP(parser);
+
+ end_mark = parser->mark;
+
+ /* Create a token. */
+
+ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start,
+ single ? YAML_SINGLE_QUOTED_SCALAR_STYLE : YAML_DOUBLE_QUOTED_SCALAR_STYLE,
+ start_mark, end_mark);
+
+ STRING_DEL(parser, leading_break);
+ STRING_DEL(parser, trailing_breaks);
+ STRING_DEL(parser, whitespaces);
+
+ return 1;
+
+error:
+ STRING_DEL(parser, string);
+ STRING_DEL(parser, leading_break);
+ STRING_DEL(parser, trailing_breaks);
+ STRING_DEL(parser, whitespaces);
+
+ return 0;
+}
+
+/*
+ * Scan a plain scalar.
+ */
+
+static int
+yaml_parser_scan_plain_scalar(yaml_parser_t *parser, yaml_token_t *token)
+{
+ yaml_mark_t start_mark;
+ yaml_mark_t end_mark;
+ yaml_string_t string = NULL_STRING;
+ yaml_string_t leading_break = NULL_STRING;
+ yaml_string_t trailing_breaks = NULL_STRING;
+ yaml_string_t whitespaces = NULL_STRING;
+ int leading_blanks = 0;
+ int indent = parser->indent+1;
+
+ if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error;
+ if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error;
+ if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error;
+ if (!STRING_INIT(parser, whitespaces, INITIAL_STRING_SIZE)) goto error;
+
+ start_mark = end_mark = parser->mark;
+
+ /* Consume the content of the plain scalar. */
+
+ while (1)
+ {
+ /* Check for a document indicator. */
+
+ if (!CACHE(parser, 4)) goto error;
+
+ if (parser->mark.column == 0 &&
+ ((CHECK_AT(parser->buffer, '-', 0) &&
+ CHECK_AT(parser->buffer, '-', 1) &&
+ CHECK_AT(parser->buffer, '-', 2)) ||
+ (CHECK_AT(parser->buffer, '.', 0) &&
+ CHECK_AT(parser->buffer, '.', 1) &&
+ CHECK_AT(parser->buffer, '.', 2))) &&
+ IS_BLANKZ_AT(parser->buffer, 3)) break;
+
+ /* Check for a comment. */
+
+ if (CHECK(parser->buffer, '#'))
+ break;
+
+ /* Consume non-blank characters. */
+
+ while (!IS_BLANKZ(parser->buffer))
+ {
+ /* Check for 'x:x' in the flow context. TODO: Fix the test "spec-08-13". */
+
+ if (parser->flow_level
+ && CHECK(parser->buffer, ':')
+ && !IS_BLANKZ_AT(parser->buffer, 1)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a plain scalar",
+ start_mark, "found unexpected ':'");
+ goto error;
+ }
+
+ /* Check for indicators that may end a plain scalar. */
+
+ if ((CHECK(parser->buffer, ':') && IS_BLANKZ_AT(parser->buffer, 1))
+ || (parser->flow_level &&
+ (CHECK(parser->buffer, ',') || CHECK(parser->buffer, ':')
+ || CHECK(parser->buffer, '?') || CHECK(parser->buffer, '[')
+ || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '{')
+ || CHECK(parser->buffer, '}'))))
+ break;
+
+ /* Check if we need to join whitespaces and breaks. */
+
+ if (leading_blanks || whitespaces.start != whitespaces.pointer)
+ {
+ if (leading_blanks)
+ {
+ /* Do we need to fold line breaks? */
+
+ if (leading_break.start[0] == '\n') {
+ if (trailing_breaks.start[0] == '\0') {
+ if (!STRING_EXTEND(parser, string)) goto error;
+ *(string.pointer++) = ' ';
+ }
+ else {
+ if (!JOIN(parser, string, trailing_breaks)) goto error;
+ CLEAR(parser, trailing_breaks);
+ }
+ CLEAR(parser, leading_break);
+ }
+ else {
+ if (!JOIN(parser, string, leading_break)) goto error;
+ if (!JOIN(parser, string, trailing_breaks)) goto error;
+ CLEAR(parser, leading_break);
+ CLEAR(parser, trailing_breaks);
+ }
+
+ leading_blanks = 0;
+ }
+ else
+ {
+ if (!JOIN(parser, string, whitespaces)) goto error;
+ CLEAR(parser, whitespaces);
+ }
+ }
+
+ /* Copy the character. */
+
+ if (!READ(parser, string)) goto error;
+
+ end_mark = parser->mark;
+
+ if (!CACHE(parser, 2)) goto error;
+ }
+
+ /* Is it the end? */
+
+ if (!(IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer)))
+ break;
+
+ /* Consume blank characters. */
+
+ if (!CACHE(parser, 1)) goto error;
+
+ while (IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer))
+ {
+ if (IS_BLANK(parser->buffer))
+ {
+ /* Check for tab character that abuse intendation. */
+
+ if (leading_blanks && parser->mark.column < indent
+ && IS_TAB(parser->buffer)) {
+ yaml_parser_set_scanner_error(parser, "while scanning a plain scalar",
+ start_mark, "found a tab character that violate intendation");
+ goto error;
+ }
+
+ /* Consume a space or a tab character. */
+
+ if (!leading_blanks) {
+ if (!READ(parser, whitespaces)) goto error;
+ }
+ else {
+ SKIP(parser);
+ }
+ }
+ else
+ {
+ if (!CACHE(parser, 2)) goto error;
+
+ /* Check if it is a first line break. */
+
+ if (!leading_blanks)
+ {
+ CLEAR(parser, whitespaces);
+ if (!READ_LINE(parser, leading_break)) goto error;
+ leading_blanks = 1;
+ }
+ else
+ {
+ if (!READ_LINE(parser, trailing_breaks)) goto error;
+ }
+ }
+ if (!CACHE(parser, 1)) goto error;
+ }
+
+ /* Check intendation level. */
+
+ if (!parser->flow_level && parser->mark.column < indent)
+ break;
+ }
+
+ /* Create a token. */
+
+ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start,
+ YAML_PLAIN_SCALAR_STYLE, start_mark, end_mark);
+
+ /* Note that we change the 'simple_key_allowed' flag. */
+
+ if (leading_blanks) {
+ parser->simple_key_allowed = 1;
+ }
+
+ STRING_DEL(parser, leading_break);
+ STRING_DEL(parser, trailing_breaks);
+ STRING_DEL(parser, whitespaces);
+
+ return 1;
+
+error:
+ STRING_DEL(parser, string);
+ STRING_DEL(parser, leading_break);
+ STRING_DEL(parser, trailing_breaks);
+ STRING_DEL(parser, whitespaces);
+
+ return 0;
+}
+