Files
haxework/src/main/yaml/Parser.hx
2020-02-13 17:20:50 +03:00

2018 lines
47 KiB
Haxe

package yaml;
import Type;
import yaml.util.ObjectMap;
import yaml.util.StringMap;
import yaml.util.IntMap;
import haxe.Utf8;
import haxe.PosInfos;
import yaml.schema.DefaultSchema;
import yaml.schema.SafeSchema;
import yaml.YamlType;
import yaml.util.Strings;
import yaml.util.Ints;
class ParserOptions
{
public var schema:Schema;
public var resolve:Bool;
public var validation:Bool;
public var strict:Bool;
public var maps:Bool;
/**
@param schema Defines the schema to use while parsing. Defaults to yaml.schema.DefaultSchema.
*/
public function new(?schema:Schema = null)
{
this.schema = (schema == null) ? new DefaultSchema() : schema;
strict = false;
resolve = true;
validation = true;
maps = true;
}
/**
Use yaml.util.ObjectMap as the key => value container.
Allows for complex key values.
*/
public function useMaps():ParserOptions
{
maps = true;
return this;
}
/**
Use Dynamic objects as the key => value container.
All keys will become Strings.
*/
public function useObjects():ParserOptions
{
maps = false;
return this;
}
/**
Defines the schema to use while parsing.
See yaml.Schema
*/
public function setSchema(schema:Schema):ParserOptions
{
this.schema = schema;
return this;
}
/**
Warnings will be thrown as exceptions instead of traced.
*/
public function strictMode(?value:Bool = true):ParserOptions
{
strict = value;
return this;
}
/**
Disables validation of yaml document while parsing.
*/
public function validate(?value:Bool = true):ParserOptions
{
validation = value;
return this;
}
}
class Parser
{
/**
Utility method to create ParserOptions for configuring a Parser instance.
*/
public static function options():ParserOptions
{
return new ParserOptions();
}
var schema:Schema;
var resolve:Bool;
var validate:Bool;
var strict:Bool;
var usingMaps:Bool;
var directiveHandlers:StringMap<String->Array<String>->Void>;
var implicitTypes:Array<AnyYamlType>;
var typeMap:StringMap<AnyYamlType>;
var length:Int;
var position:Int;
var line:Int;
var lineStart:Int;
var lineIndent:Int;
var character:Null<Int>;
var version:String;
var checkLineBreaks:Bool;
var tagMap:StringMap<String>;
var anchorMap:StringMap<Dynamic>;
var tag:String;
var anchor:String;
var kind:String;
var result:Dynamic;
var input:String;
var output:Dynamic->Void;
public function new()
{}
public function safeParseAll(input:String, output:Dynamic->Void, options:ParserOptions):Void
{
options.schema = new SafeSchema();
parseAll(input, output, options);
}
public function safeParse(input:String, options:ParserOptions)
{
options.schema = new SafeSchema();
return parse(input, options);
}
public function parse(input:String, options:ParserOptions):Dynamic
{
var result:Dynamic = null;
var received = false;
var responder = function (data:Dynamic)
{
if (!received)
{
result = data;
received = true;
}
else
{
throw new YamlException('expected a single document in the stream, but found more');
}
}
parseAll(input, responder, options);
return result;
}
public function parseAll(input:String, output:Dynamic->Void, options:ParserOptions):Void
{
#if (neko || cpp)
this.input = Utf8.encode(input);
#else
this.input = input;
#end
this.output = output;
schema = options.schema;
resolve = options.resolve;
validate = options.validation;
strict = options.strict;
usingMaps = options.maps;
directiveHandlers = new StringMap();
implicitTypes = schema.compiledImplicit;
typeMap = schema.compiledTypeMap;
length = Utf8.length(this.input);//.length;
position = 0;
line = 0;
lineStart = 0;
lineIndent = 0;
character = Utf8.charCodeAt(this.input, position);
directiveHandlers.set('YAML', function(name:String, args:Array<String>)
{
#if (cpp || neko)
for (i in 0...args.length)
args[i] = Utf8.encode(args[i]);
#end
if (null != version)
throwError('duplication of %YAML directive');
if (1 != args.length)
throwError('YAML directive accepts exactly one argument');
// match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]);
var regex = ~/^([0-9]+)\.([0-9]+)$/u;
if (!regex.match(args[0]))
throwError('ill-formed argument of the YAML directive');
var major = Ints.parseInt(regex.matched(1), 10);
var minor = Ints.parseInt(regex.matched(2), 10);
if (1 != major)
throwError('unacceptable YAML version of the document');
version = args[0];
checkLineBreaks = (minor < 2);
if (1 != minor && 2 != minor)
throwWarning('unsupported YAML version of the document');
});
directiveHandlers.set('TAG', function(name:String, args:Array<String>)
{
#if (cpp || neko)
for (i in 0...args.length)
args[i] = Utf8.encode(args[i]);
#end
var handle:String;
var prefix:String;
if (2 != args.length)
throwError('TAG directive accepts exactly two arguments');
handle = args[0];
prefix = args[1];
if (!PATTERN_TAG_HANDLE.match(handle))
throwError('ill-formed tag handle (first argument) of the TAG directive');
if (tagMap.exists(handle))
throwError('there is a previously declared suffix for "' + handle + '" tag handle');
if (!PATTERN_TAG_URI.match(prefix))
throwError('ill-formed tag prefix (second argument) of the TAG directive');
tagMap.set(handle, prefix);
});
if (validate && PATTERN_NON_PRINTABLE.match(this.input))
{
throwError('the stream contains non-printable characters');
}
while (CHAR_SPACE == character)
{
lineIndent += 1;
character = Utf8.charCodeAt(input, ++position);
}
while (position < length)
{
readDocument();
}
}
function generateError(message:String, ?info:PosInfos)
{
return new YamlException(message, info);
}
function throwError(message:String, ?info:PosInfos)
{
throw generateError(message, info);
}
function throwWarning(message:String, ?info:PosInfos)
{
var error = generateError(message, info);
if (strict) {
throw error;
} else {
trace("Warning : " + error.toString());
}
}
function captureSegment(start:Int, end:Int, checkJson:Bool)
{
var _result:String;
if (start < end)
{
_result = yaml.util.Utf8.substring(input, start, end);
if (checkJson && validate)
{
for (pos in 0...Utf8.length(_result))//.length)
{
var char = Utf8.charCodeAt(_result, pos);
if (!(0x09 == char || 0x20 <= char && char <= 0x10FFFF))
throwError('expected valid JSON character');
}
}
result += _result;
}
}
// when create dynamic object graph
function mergeObjectMappings(destination:Dynamic, source:Dynamic)
{
if (Type.typeof(source) != ValueType.TObject) {
throwError('cannot merge mappings; the provided source object is unacceptable');
}
for (key in Reflect.fields(source))
if (!Reflect.hasField(destination, key))
Reflect.setField(destination, key, Reflect.field(source, key));
}
// when creating map based graph
function mergeMappings(destination:AnyObjectMap, source:AnyObjectMap)
{
if (!Std.is(source, AnyObjectMap)) {
throwError('cannot merge mappings; the provided source object is unacceptable');
}
for (key in source.keys())
if (!destination.exists(key))
destination.set(key, source.get(key));
}
function storeObjectMappingPair(_result:Dynamic, keyTag:String, keyNode:Dynamic, valueNode:Dynamic):Dynamic
{
if (null == _result)
_result = {};
if ('tag:yaml.org,2002:merge' == keyTag)
{
if (Std.is(valueNode, Array))
{
var list:Array<Dynamic> = cast valueNode;
for (member in list)
mergeObjectMappings(_result, member);
}
else
{
mergeObjectMappings(_result, valueNode);
}
}
else
{
Reflect.setField(_result, Std.string(keyNode), valueNode);
}
return _result;
}
function storeMappingPair(_result:AnyObjectMap, keyTag:String, keyNode:Dynamic, valueNode:Dynamic):AnyObjectMap
{
if (null == _result)
{
_result = new AnyObjectMap();
}
if ('tag:yaml.org,2002:merge' == keyTag)
{
if (Std.is(valueNode, Array))
{
var list:Array<AnyObjectMap> = cast valueNode;
for (member in list)
mergeMappings(_result, member);
}
else
{
mergeMappings(_result, valueNode);
}
}
else
{
_result.set(keyNode, valueNode);
}
return _result;
}
function readLineBreak()
{
if (CHAR_LINE_FEED == character)
{
position += 1;
}
else if (CHAR_CARRIAGE_RETURN == character)
{
if (CHAR_LINE_FEED == Utf8.charCodeAt(input, (position + 1)))
{
position += 2;
}
else
{
position += 1;
}
}
else
{
throwError('a line break is expected');
}
line += 1;
lineStart = position;
if (position < length)
character = Utf8.charCodeAt(input, position);
else
character = null;
}
function skipSeparationSpace(allowComments:Bool, checkIndent:Int)
{
var lineBreaks = 0;
while (position < length)
{
while (CHAR_SPACE == character || CHAR_TAB == character)
{
character = Utf8.charCodeAt(input, ++position);
}
if (allowComments && CHAR_SHARP == character)
{
do { character = Utf8.charCodeAt(input, ++position); }
while (position < length && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character);
}
if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character)
{
readLineBreak();
lineBreaks += 1;
lineIndent = 0;
while (CHAR_SPACE == character)
{
lineIndent += 1;
character = Utf8.charCodeAt(input, ++position);
}
if (lineIndent < checkIndent)
{
throwWarning('deficient indentation');
}
}
else
{
break;
}
}
return lineBreaks;
}
function testDocumentSeparator()
{
if (position == lineStart &&
(CHAR_MINUS == character || CHAR_DOT == character) &&
Utf8.charCodeAt(input, (position + 1)) == character &&
Utf8.charCodeAt(input, (position + 2)) == character)
{
var pos = position + 3;
var char = Utf8.charCodeAt(input, pos);
if (pos >= length || CHAR_SPACE == char || CHAR_TAB == char ||
CHAR_LINE_FEED == char || CHAR_CARRIAGE_RETURN == char)
{
return true;
}
}
return false;
}
function writeFoldedLines(count:Int)
{
if (1 == count)
{
result += ' ';
}
else if (count > 1)
{
#if (cpp || neko)
result += Utf8.encode(Strings.repeat('\n', count - 1));
#else
result += Strings.repeat('\n', count - 1);
#end
}
}
function readPlainScalar(nodeIndent:Int, withinFlowCollection:Bool)
{
var preceding:Int;
var following:Int;
var captureStart:Int;
var captureEnd:Int;
var hasPendingContent;
var _line:Int = 0;
var _kind = kind;
var _result = result;
if (CHAR_SPACE == character ||
CHAR_TAB == character ||
CHAR_LINE_FEED == character ||
CHAR_CARRIAGE_RETURN == character ||
CHAR_COMMA == character ||
CHAR_LEFT_SQUARE_BRACKET == character ||
CHAR_RIGHT_SQUARE_BRACKET == character ||
CHAR_LEFT_CURLY_BRACKET == character ||
CHAR_RIGHT_CURLY_BRACKET == character ||
CHAR_SHARP == character ||
CHAR_AMPERSAND == character ||
CHAR_ASTERISK == character ||
CHAR_EXCLAMATION == character ||
CHAR_VERTICAL_LINE == character ||
CHAR_GREATER_THAN == character ||
CHAR_SINGLE_QUOTE == character ||
CHAR_DOUBLE_QUOTE == character ||
CHAR_PERCENT == character ||
CHAR_COMMERCIAL_AT == character ||
CHAR_GRAVE_ACCENT == character)
{
return false;
}
if (CHAR_QUESTION == character || CHAR_MINUS == character)
{
following = Utf8.charCodeAt(input, position + 1);
if (CHAR_SPACE == following ||
CHAR_TAB == following ||
CHAR_LINE_FEED == following ||
CHAR_CARRIAGE_RETURN == following ||
withinFlowCollection &&
(CHAR_COMMA == following ||
CHAR_LEFT_SQUARE_BRACKET == following ||
CHAR_RIGHT_SQUARE_BRACKET == following ||
CHAR_LEFT_CURLY_BRACKET == following ||
CHAR_RIGHT_CURLY_BRACKET == following))
{
return false;
}
}
kind = KIND_STRING;
result = '';
captureStart = captureEnd = position;
hasPendingContent = false;
while (position < length)
{
if (CHAR_COLON == character)
{
following = Utf8.charCodeAt(input, position + 1);
if (CHAR_SPACE == following ||
CHAR_TAB == following ||
CHAR_LINE_FEED == following ||
CHAR_CARRIAGE_RETURN == following ||
withinFlowCollection &&
(CHAR_COMMA == following ||
CHAR_LEFT_SQUARE_BRACKET == following ||
CHAR_RIGHT_SQUARE_BRACKET == following ||
CHAR_LEFT_CURLY_BRACKET == following ||
CHAR_RIGHT_CURLY_BRACKET == following))
{
break;
}
}
else if (CHAR_SHARP == character)
{
preceding = Utf8.charCodeAt(input, position - 1);
if (CHAR_SPACE == preceding ||
CHAR_TAB == preceding ||
CHAR_LINE_FEED == preceding ||
CHAR_CARRIAGE_RETURN == preceding)
{
break;
}
}
else if ((position == lineStart && testDocumentSeparator()) ||
withinFlowCollection &&
(CHAR_COMMA == character ||
CHAR_LEFT_SQUARE_BRACKET == character ||
CHAR_RIGHT_SQUARE_BRACKET == character ||
CHAR_LEFT_CURLY_BRACKET == character ||
CHAR_RIGHT_CURLY_BRACKET == character))
{
break;
}
else if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character)
{
_line = line;
var _lineStart = lineStart;
var _lineIndent = lineIndent;
skipSeparationSpace(false, -1);
if (lineIndent >= nodeIndent)
{
hasPendingContent = true;
continue;
}
else
{
position = captureEnd;
line = _line;
lineStart = _lineStart;
lineIndent = _lineIndent;
character = Utf8.charCodeAt(input, position);
break;
}
}
if (hasPendingContent)
{
captureSegment(captureStart, captureEnd, false);
writeFoldedLines(line - _line);
captureStart = captureEnd = position;
hasPendingContent = false;
}
if (CHAR_SPACE != character && CHAR_TAB != character)
{
captureEnd = position + 1;
}
if (++position >= length)
break;
character = Utf8.charCodeAt(input, position);
}
captureSegment(captureStart, captureEnd, false);
if (result != null)
{
#if sys
result = Utf8.decode(result); // convert back into native encoding
#end
return true;
}
else
{
kind = _kind;
result = _result;
return false;
}
}
function readSingleQuotedScalar(nodeIndent:Int)
{
var captureStart:Int;
var captureEnd:Int;
if (CHAR_SINGLE_QUOTE != character)
{
return false;
}
kind = KIND_STRING;
result = '';
character = Utf8.charCodeAt(input, ++position);
captureStart = captureEnd = position;
while (position < length)
{
if (CHAR_SINGLE_QUOTE == character)
{
captureSegment(captureStart, position, true);
character = Utf8.charCodeAt(input, ++position);
if (CHAR_SINGLE_QUOTE == character)
{
captureStart = captureEnd = position;
character = Utf8.charCodeAt(input, ++position);
}
else
{
#if sys
result = Utf8.decode(result);
#end
return true;
}
}
else if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character)
{
captureSegment(captureStart, captureEnd, true);
writeFoldedLines(skipSeparationSpace(false, nodeIndent));
captureStart = captureEnd = position;
character = Utf8.charCodeAt(input, position);
}
else if (position == lineStart && testDocumentSeparator())
{
throwError('unexpected end of the document within a single quoted scalar');
}
else
{
character = Utf8.charCodeAt(input, ++position);
captureEnd = position;
}
}
throwError('unexpected end of the stream within a single quoted scalar');
return false;
}
function readDoubleQuotedScalar(nodeIndent:Int)
{
var captureStart:Int;
var captureEnd:Int;
if (CHAR_DOUBLE_QUOTE != character)
return false;
kind = KIND_STRING;
result = '';
character = Utf8.charCodeAt(input, ++position);
captureStart = captureEnd = position;
while (position < length)
{
if (CHAR_DOUBLE_QUOTE == character)
{
captureSegment(captureStart, position, true);
character = Utf8.charCodeAt(input, ++position);
#if sys
result = Utf8.decode(result);
#end
return true;
}
else if (CHAR_BACKSLASH == character)
{
captureSegment(captureStart, position, true);
character = Utf8.charCodeAt(input, ++position);
if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character)
{
skipSeparationSpace(false, nodeIndent);
}
else if (SIMPLE_ESCAPE_SEQUENCES.exists(character))
{
result += SIMPLE_ESCAPE_SEQUENCES.get(character);
character = Utf8.charCodeAt(input, ++position);
}
else if (HEXADECIMAL_ESCAPE_SEQUENCES.exists(character))
{
var hexLength = HEXADECIMAL_ESCAPE_SEQUENCES.get(character);
var hexResult = 0;
for (hexIndex in 1...hexLength)
{
var hexOffset = (hexLength - hexIndex) * 4;
character = Utf8.charCodeAt(input, ++position);
if (CHAR_DIGIT_ZERO <= character && character <= CHAR_DIGIT_NINE)
{
hexResult |= (character - CHAR_DIGIT_ZERO) << hexOffset;
}
else if (CHAR_CAPITAL_A <= character && character <= CHAR_CAPITAL_F)
{
hexResult |= (character - CHAR_CAPITAL_A + 10) << hexOffset;
}
else if (CHAR_SMALL_A <= character && character <= CHAR_SMALL_F)
{
hexResult |= (character - CHAR_SMALL_A + 10) << hexOffset;
}
else {
throwError('expected hexadecimal character');
}
}
result += String.fromCharCode(hexResult);
character = Utf8.charCodeAt(input, ++position);
}
else
{
throwError('unknown escape sequence');
}
captureStart = captureEnd = position;
}
else if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character)
{
captureSegment(captureStart, captureEnd, true);
writeFoldedLines(skipSeparationSpace(false, nodeIndent));
captureStart = captureEnd = position;
character = Utf8.charCodeAt(input, position);
}
else if (position == lineStart && testDocumentSeparator())
{
throwError('unexpected end of the document within a double quoted scalar');
}
else
{
character = Utf8.charCodeAt(input, ++position);
captureEnd = position;
}
}
throwError('unexpected end of the stream within a double quoted scalar');
return false;
}
function composeNode(parentIndent:Int, nodeContext:Int, allowToSeek:Bool, allowCompact:Bool)
{
var allowBlockStyles:Bool;
var allowBlockScalars:Bool;
var allowBlockCollections:Bool;
var atNewLine = false;
var isIndented = true;
var hasContent = false;
tag = null;
anchor = null;
kind = null;
result = null;
allowBlockCollections = (CONTEXT_BLOCK_OUT == nodeContext || CONTEXT_BLOCK_IN == nodeContext);
allowBlockStyles = allowBlockScalars = allowBlockCollections;
if (allowToSeek)
{
if (skipSeparationSpace(true, -1) != 0)
{
atNewLine = true;
if (lineIndent == parentIndent)
{
isIndented = false;
}
else if (lineIndent > parentIndent)
{
isIndented = true;
}
else
{
return false;
}
}
}
if (isIndented)
{
while (readTagProperty() || readAnchorProperty())
{
if (skipSeparationSpace(true, -1) != 0)
{
atNewLine = true;
if (lineIndent > parentIndent)
{
isIndented = true;
allowBlockCollections = allowBlockStyles;
}
else if (lineIndent == parentIndent)
{
isIndented = false;
allowBlockCollections = allowBlockStyles;
}
else
{
return true;
}
}
else
{
allowBlockCollections = false;
}
}
}
if (allowBlockCollections)
allowBlockCollections = atNewLine || allowCompact;
if (isIndented || CONTEXT_BLOCK_OUT == nodeContext)
{
var flowIndent:Int;
var blockIndent:Int;
if (CONTEXT_FLOW_IN == nodeContext || CONTEXT_FLOW_OUT == nodeContext)
{
flowIndent = parentIndent;
}
else
{
flowIndent = parentIndent + 1;
}
blockIndent = position - lineStart;
if (isIndented)
{
if (allowBlockCollections &&
(readBlockSequence(blockIndent) ||
readBlockMapping(blockIndent)) ||
readFlowCollection(flowIndent))
{
hasContent = true;
}
else
{
if ((allowBlockScalars && readBlockScalar(flowIndent)) ||
readSingleQuotedScalar(flowIndent) ||
readDoubleQuotedScalar(flowIndent))
{
hasContent = true;
}
else if (readAlias())
{
hasContent = true;
if (null != tag || null != anchor)
{
throwError('alias node should not have any properties');
}
}
else if (readPlainScalar(flowIndent, CONTEXT_FLOW_IN == nodeContext))
{
hasContent = true;
if (null == tag)
tag = '?';
}
if (null != anchor)
{
anchorMap.set(anchor, result);
}
}
}
else
{
hasContent = allowBlockCollections && readBlockSequence(blockIndent);
}
}
if (null != tag && '!' != tag)
{
var _result:Dynamic = null;
if ('?' == tag)
{
if (resolve)
{
for (typeIndex in 0...implicitTypes.length)
{
var type = implicitTypes[typeIndex];
// Implicit resolving is not allowed for non-scalar types, and '?'
// non-specific tag is only assigned to plain scalars. So, it isn't
// needed to check for 'kind' conformity.
var resolvedType = false;
try
{
_result = type.resolve(result, usingMaps, false);
#if sys
if (Std.is(_result, String))
_result = Utf8.decode(_result);
#end
tag = type.tag;
result = _result;
resolvedType = true;
}
catch (e:ResolveTypeException) {}
if (resolvedType) break;
}
}
}
else if (typeMap.exists(tag))
{
var t = typeMap.get(tag);
if (null != result && t.loader.kind != kind)
{
throwError('unacceptable node kind for !<' + tag + '> tag; it should be "' + t.loader.kind + '", not "' + kind + '"');
}
if (!t.loader.skip)
{
try
{
_result = t.resolve(result, usingMaps, true);
#if sys
if (Std.is(_result, String))
_result = Utf8.decode(_result);
#end
result = _result;
}
catch(e:ResolveTypeException)
{
throwError('cannot resolve a node with !<' + tag + '> explicit tag');
}
}
}
else
{
throwWarning('unknown tag !<' + tag + '>');
}
}
return (null != tag || null != anchor || hasContent);
}
function readFlowCollection(nodeIndent:Int)
{
var readNext = true;
var _tag = tag;
var _result:Dynamic;
var terminator:Int;
var isPair:Bool;
var isExplicitPair:Bool;
var isMapping:Bool;
var keyNode:Dynamic;
var keyTag:String;
var valueNode:Dynamic;
switch (character)
{
case CHAR_LEFT_SQUARE_BRACKET:
terminator = CHAR_RIGHT_SQUARE_BRACKET;
isMapping = false;
_result = [];
case CHAR_LEFT_CURLY_BRACKET:
terminator = CHAR_RIGHT_CURLY_BRACKET;
isMapping = true;
_result = usingMaps ? new ObjectMap<{}, Dynamic>() : {};
default:
return false;
}
if (null != anchor)
anchorMap.set(anchor, _result);
character = Utf8.charCodeAt(input, ++position);
while (position < length)
{
skipSeparationSpace(true, nodeIndent);
if (character == terminator)
{
character = Utf8.charCodeAt(input, ++position);
tag = _tag;
kind = isMapping ? KIND_OBJECT : KIND_ARRAY;
result = _result;
return true;
}
else if (!readNext)
{
throwError('missed comma between flow collection entries');
}
keyTag = keyNode = valueNode = null;
isPair = isExplicitPair = false;
if (CHAR_QUESTION == character)
{
var following = Utf8.charCodeAt(input, position + 1);
if (CHAR_SPACE == following ||
CHAR_TAB == following ||
CHAR_LINE_FEED == following ||
CHAR_CARRIAGE_RETURN == following)
{
isPair = isExplicitPair = true;
position += 1;
character = following;
skipSeparationSpace(true, nodeIndent);
}
}
var _line = line;
composeNode(nodeIndent, CONTEXT_FLOW_IN, false, true);
keyTag = tag;
keyNode = result;
if ((isExplicitPair || line == _line) && CHAR_COLON == character)
{
isPair = true;
character = Utf8.charCodeAt(input, ++position);
skipSeparationSpace(true, nodeIndent);
composeNode(nodeIndent, CONTEXT_FLOW_IN, false, true);
valueNode = result;
}
if (isMapping)
{
if (usingMaps)
storeMappingPair(_result, keyTag, keyNode, valueNode);
else
storeObjectMappingPair(_result, keyTag, keyNode, valueNode);
}
else if (isPair)
{
if (usingMaps)
_result.push(storeMappingPair(null, keyTag, keyNode, valueNode));
else
_result.push(storeObjectMappingPair(null, keyTag, keyNode, valueNode));
}
else
{
_result.push(keyNode);
}
skipSeparationSpace(true, nodeIndent);
if (CHAR_COMMA == character)
{
readNext = true;
character = Utf8.charCodeAt(input, ++position);
}
else
{
readNext = false;
}
}
throwError('unexpected end of the stream within a flow collection');
return false;
}
function readBlockScalar(nodeIndent:Int)
{
var captureStart:Int;
var folding:Bool;
var chomping = CHOMPING_CLIP;
var detectedIndent = false;
var textIndent = nodeIndent;
var emptyLines = -1;
switch (character)
{
case CHAR_VERTICAL_LINE:
folding = false;
case CHAR_GREATER_THAN:
folding = true;
default:
return false;
}
kind = KIND_STRING;
result = '';
while (position < length)
{
character = Utf8.charCodeAt(input, ++position);
if (CHAR_PLUS == character || CHAR_MINUS == character)
{
if (CHOMPING_CLIP == chomping)
{
chomping = (CHAR_PLUS == character) ? CHOMPING_KEEP : CHOMPING_STRIP;
}
else
{
throwError('repeat of a chomping mode identifier');
}
}
else if (CHAR_DIGIT_ZERO <= character && character <= CHAR_DIGIT_NINE)
{
if (CHAR_DIGIT_ZERO == character)
{
throwError('bad explicit indentation width of a block scalar; it cannot be less than one');
}
else if (!detectedIndent)
{
textIndent = nodeIndent + (character - CHAR_DIGIT_ONE);
detectedIndent = true;
}
else
{
throwError('repeat of an indentation width identifier');
}
}
else
{
break;
}
}
if (CHAR_SPACE == character || CHAR_TAB == character)
{
do { character = Utf8.charCodeAt(input, ++position); }
while (CHAR_SPACE == character || CHAR_TAB == character);
if (CHAR_SHARP == character)
{
do { character = Utf8.charCodeAt(input, ++position); }
while (position < length && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character);
}
}
while (position < length)
{
readLineBreak();
lineIndent = 0;
while ((!detectedIndent || lineIndent < textIndent) && (CHAR_SPACE == character))
{
lineIndent += 1;
character = Utf8.charCodeAt(input, ++position);
}
if (!detectedIndent && lineIndent > textIndent)
{
textIndent = lineIndent;
}
if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character)
{
emptyLines += 1;
continue;
}
// End of the scalar. Perform the chomping.
if (lineIndent < textIndent)
{
if (CHOMPING_KEEP == chomping)
{
#if sys
result += Utf8.encode(Strings.repeat('\n', emptyLines + 1));
#else
result += Strings.repeat('\n', emptyLines + 1);
#end
}
else if (CHOMPING_CLIP == chomping)
{
result += '\n';
}
break;
}
detectedIndent = true;
if (folding)
{
if (CHAR_SPACE == character || CHAR_TAB == character)
{
#if sys
result += Utf8.encode(Strings.repeat('\n', emptyLines + 1));
#else
result += Strings.repeat('\n', emptyLines + 1);
#end
emptyLines = 1;
}
else if (0 == emptyLines)
{
#if sys
result += Utf8.encode(' ');
#else
result += ' ';
#end
emptyLines = 0;
}
else
{
#if sys
result += Utf8.encode(Strings.repeat('\n', emptyLines));
#else
result += Strings.repeat('\n', emptyLines);
#end
emptyLines = 0;
}
}
else
{
#if sys
result += Utf8.encode(Strings.repeat('\n', emptyLines + 1));
#else
result += Strings.repeat('\n', emptyLines + 1);
#end
emptyLines = 0;
}
captureStart = position;
do { character = Utf8.charCodeAt(input, ++position); }
while (position < length && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character);
captureSegment(captureStart, position, false);
}
#if sys
result = Utf8.decode(result);
#end
return true;
}
function readBlockSequence(nodeIndent:Int)
{
var _line:Int;
var _tag = tag;
var _result:Array<Dynamic> = [];
var following:Int;
var detected = false;
if (null != anchor)
anchorMap.set(anchor, _result);
while (position < length)
{
if (CHAR_MINUS != character)
break;
following = Utf8.charCodeAt(input, position + 1);
if (CHAR_SPACE != following &&
CHAR_TAB != following &&
CHAR_LINE_FEED != following &&
CHAR_CARRIAGE_RETURN != following)
{
break;
}
detected = true;
position += 1;
character = following;
if (skipSeparationSpace(true, -1) != 0)
{
if (lineIndent <= nodeIndent)
{
_result.push(null);
continue;
}
}
_line = line;
composeNode(nodeIndent, CONTEXT_BLOCK_IN, false, true);
_result.push(result);
skipSeparationSpace(true, -1);
if ((line == _line || lineIndent > nodeIndent) && position < length)
{
throwError('bad indentation of a sequence entry');
}
else if (lineIndent < nodeIndent)
{
break;
}
}
if (detected)
{
tag = _tag;
kind = KIND_ARRAY;
result = _result;
return true;
}
else
{
return false;
}
}
function readBlockMapping(nodeIndent:Int)
{
var following:Int;
var allowCompact = false;
var _line:Int;
var _tag = tag;
var _result:Dynamic = usingMaps ? new ObjectMap<{}, Dynamic>() : {};
var keyTag:Dynamic = null;
var keyNode:Dynamic = null;
var valueNode:Dynamic = null;
var atExplicitKey = false;
var detected = false;
if (null != anchor)
anchorMap.set(anchor, _result);
while (position < length)
{
following = Utf8.charCodeAt(input, position + 1);
_line = line; // Save the current line.
if ((CHAR_QUESTION == character ||
CHAR_COLON == character) &&
(CHAR_SPACE == following ||
CHAR_TAB == following ||
CHAR_LINE_FEED == following ||
CHAR_CARRIAGE_RETURN == following))
{
if (CHAR_QUESTION == character)
{
if (atExplicitKey)
{
if (usingMaps)
storeMappingPair(_result, keyTag, keyNode, null);
else
storeObjectMappingPair(_result, keyTag, keyNode, null);
keyTag = keyNode = valueNode = null;
}
detected = true;
atExplicitKey = true;
allowCompact = true;
}
else if (atExplicitKey)
{
// i.e. CHAR_COLON == character after the explicit key.
atExplicitKey = false;
allowCompact = true;
}
else
{
throwError('incomplete explicit mapping pair; a key node is missed');
}
position += 1;
character = following;
}
else if (composeNode(nodeIndent, CONTEXT_FLOW_OUT, false, true))
{
if (line == _line)
{
// TODO: Remove this cycle when the flow readers will consume
// trailing whitespaces like the block readers.
while (CHAR_SPACE == character || CHAR_TAB == character)
{
character = Utf8.charCodeAt(input, ++position);
}
if (CHAR_COLON == character)
{
character = Utf8.charCodeAt(input, ++position);
if (CHAR_SPACE != character &&
CHAR_TAB != character &&
CHAR_LINE_FEED != character &&
CHAR_CARRIAGE_RETURN != character)
{
throwError('a whitespace character is expected after the key-value separator within a block mapping');
}
if (atExplicitKey)
{
if (usingMaps)
storeMappingPair(_result, keyTag, keyNode, null);
else
storeObjectMappingPair(_result, keyTag, keyNode, null);
keyTag = keyNode = valueNode = null;
}
detected = true;
atExplicitKey = false;
allowCompact = false;
keyTag = tag;
keyNode = result;
}
else if (detected)
{
throwError('can not read an implicit mapping pair; a colon is missed');
}
else
{
tag = _tag;
return true; // Keep the result of `composeNode`.
}
}
else if (detected)
{
throwError('can not read a block mapping entry; a multiline key may not be an implicit key');
}
else
{
tag = _tag;
return true; // Keep the result of `composeNode`.
}
}
else
{
break;
}
if (line == _line || lineIndent > nodeIndent)
{
if (composeNode(nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact))
{
if (atExplicitKey)
keyNode = result;
else
valueNode = result;
}
if (!atExplicitKey)
{
if (usingMaps)
storeMappingPair(_result, keyTag, keyNode, valueNode);
else
storeObjectMappingPair(_result, keyTag, keyNode, valueNode);
keyTag = keyNode = valueNode = null;
}
// TODO: It is needed only for flow node readers. It should be removed
// when the flow readers will consume trailing whitespaces as well as
// the block readers.
skipSeparationSpace(true, -1);
}
if (lineIndent > nodeIndent && position < length)
{
throwError('bad indentation of a mapping entry');
}
else if (lineIndent < nodeIndent)
{
break;
}
}
if (atExplicitKey)
{
if (usingMaps)
storeMappingPair(_result, keyTag, keyNode, null);
else
storeObjectMappingPair(_result, keyTag, keyNode, null);
}
if (detected)
{
tag = _tag;
kind = KIND_OBJECT;
result = _result;
}
return detected;
}
function readTagProperty()
{
var _position:Int;
var isVerbatim = false;
var isNamed = false;
var tagHandle:String = null;
var tagName:String = null;
if (CHAR_EXCLAMATION != character)
return false;
if (null != tag)
throwError('duplication of a tag property');
character = Utf8.charCodeAt(input, ++position);
if (CHAR_LESS_THAN == character)
{
isVerbatim = true;
character = Utf8.charCodeAt(input, ++position);
}
else if (CHAR_EXCLAMATION == character)
{
isNamed = true;
tagHandle = '!!';
character = Utf8.charCodeAt(input, ++position);
}
else
{
tagHandle = '!';
}
_position = position;
if (isVerbatim)
{
do { character = Utf8.charCodeAt(input, ++position); }
while (position < length && CHAR_GREATER_THAN != character);
if (position < length)
{
tagName = yaml.util.Utf8.substring(input, _position, position);
character = Utf8.charCodeAt(input, ++position);
}
else
{
throwError('unexpected end of the stream within a verbatim tag');
}
}
else
{
while (position < length &&
CHAR_SPACE != character &&
CHAR_TAB != character &&
CHAR_LINE_FEED != character &&
CHAR_CARRIAGE_RETURN != character)
{
if (CHAR_EXCLAMATION == character)
{
if (!isNamed)
{
tagHandle = yaml.util.Utf8.substring(input, _position - 1, position + 1);
if (validate && !PATTERN_TAG_HANDLE.match(tagHandle))
{
throwError('named tag handle cannot contain such characters');
}
isNamed = true;
_position = position + 1;
}
else
{
throwError('tag suffix cannot contain exclamation marks');
}
}
character = Utf8.charCodeAt(input, ++position);
}
tagName = yaml.util.Utf8.substring(input, _position, position);
if (validate && PATTERN_FLOW_INDICATORS.match(tagName))
{
throwError('tag suffix cannot contain flow indicator characters');
}
}
if (validate && tagName != null && tagName != "" && !PATTERN_TAG_URI.match(tagName))
{
throwError('tag name cannot contain such characters: ' + tagName);
}
if (isVerbatim)
{
tag = tagName;
}
else if (tagMap.exists(tagHandle))
{
tag = tagMap.get(tagHandle) + tagName;
}
else if ('!' == tagHandle)
{
tag = '!' + tagName;
}
else if ('!!' == tagHandle)
{
tag = 'tag:yaml.org,2002:' + tagName;
}
else
{
throwError('undeclared tag handle "' + tagHandle + '"');
}
return true;
}
function readAnchorProperty()
{
var _position:Int;
if (CHAR_AMPERSAND != character)
return false;
if (null != anchor)
throwError('duplication of an anchor property');
character = Utf8.charCodeAt(input, ++position);
_position = position;
while (position < length &&
CHAR_SPACE != character &&
CHAR_TAB != character &&
CHAR_LINE_FEED != character &&
CHAR_CARRIAGE_RETURN != character &&
CHAR_COMMA != character &&
CHAR_LEFT_SQUARE_BRACKET != character &&
CHAR_RIGHT_SQUARE_BRACKET != character &&
CHAR_LEFT_CURLY_BRACKET != character &&
CHAR_RIGHT_CURLY_BRACKET != character)
{
character = Utf8.charCodeAt(input, ++position);
}
if (position == _position)
throwError('name of an anchor node must contain at least one character');
anchor = yaml.util.Utf8.substring(input, _position, position);
return true;
}
function readAlias()
{
var _position:Int;
var alias:String;
if (CHAR_ASTERISK != character)
return false;
character = Utf8.charCodeAt(input, ++position);
_position = position;
while (position < length &&
CHAR_SPACE != character &&
CHAR_TAB != character &&
CHAR_LINE_FEED != character &&
CHAR_CARRIAGE_RETURN != character &&
CHAR_COMMA != character &&
CHAR_LEFT_SQUARE_BRACKET != character &&
CHAR_RIGHT_SQUARE_BRACKET != character &&
CHAR_LEFT_CURLY_BRACKET != character &&
CHAR_RIGHT_CURLY_BRACKET != character)
{
character = Utf8.charCodeAt(input, ++position);
}
if (position == _position)
throwError('name of an alias node must contain at least one character');
alias = yaml.util.Utf8.substring(input, _position, position);
if (!anchorMap.exists(alias))
throwError('unidentified alias "' + alias + '"');
result = anchorMap.get(alias);
skipSeparationSpace(true, -1);
return true;
}
function readDocument()
{
var documentStart = position;
var _position:Int;
var directiveName:String;
var directiveArgs:Array<String>;
var hasDirectives = false;
version = null;
checkLineBreaks = false;
tagMap = new StringMap();
anchorMap = new StringMap();
while (position < length)
{
skipSeparationSpace(true, -1);
if (lineIndent > 0 || CHAR_PERCENT != character)
break;
hasDirectives = true;
character = Utf8.charCodeAt(input, ++position);
_position = position;
while (position < length &&
CHAR_SPACE != character &&
CHAR_TAB != character &&
CHAR_LINE_FEED != character &&
CHAR_CARRIAGE_RETURN != character)
{
character = Utf8.charCodeAt(input, ++position);
}
directiveName = yaml.util.Utf8.substring(input, _position, position);
directiveArgs = [];
if (Utf8.length(directiveName) < 1)
throwError('directive name must not be less than one character in length');
while (position < length)
{
while (CHAR_SPACE == character || CHAR_TAB == character)
{
character = Utf8.charCodeAt(input, ++position);
}
if (CHAR_SHARP == character)
{
do { character = Utf8.charCodeAt(input, ++position); }
while (position < length && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character);
break;
}
if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character)
break;
_position = position;
while (position < length &&
CHAR_SPACE != character &&
CHAR_TAB != character &&
CHAR_LINE_FEED != character &&
CHAR_CARRIAGE_RETURN != character)
{
character = Utf8.charCodeAt(input, ++position);
}
directiveArgs.push(yaml.util.Utf8.substring(input, _position, position));
}
if (position < length)
{
readLineBreak();
}
if (directiveHandlers.exists(directiveName))
{
directiveHandlers.get(directiveName)(directiveName, directiveArgs);
}
else
{
throwWarning('unknown document directive "' + directiveName + '"');
}
}
skipSeparationSpace(true, -1);
if (0 == lineIndent &&
CHAR_MINUS == character &&
CHAR_MINUS == Utf8.charCodeAt(input, position + 1) &&
CHAR_MINUS == Utf8.charCodeAt(input, position + 2))
{
position += 3;
character = Utf8.charCodeAt(input, position);
skipSeparationSpace(true, -1);
}
else if (hasDirectives)
{
throwError('directives end mark is expected');
}
composeNode(lineIndent - 1, CONTEXT_BLOCK_OUT, false, true);
skipSeparationSpace(true, -1);
if (validate && checkLineBreaks && PATTERN_NON_ASCII_LINE_BREAKS.match(yaml.util.Utf8.substring(input, documentStart, position)))
{
throwWarning('non-ASCII line breaks are interpreted as content');
}
output(result);
if (position == lineStart && testDocumentSeparator())
{
if (CHAR_DOT == character)
{
position += 3;
character = Utf8.charCodeAt(input, position);
skipSeparationSpace(true, -1);
}
return;
}
if (position < length)
{
throwError('end of the stream or a document separator is expected');
}
else
{
return;
}
}
public static inline var KIND_STRING = 'string';
public static inline var KIND_ARRAY = 'array';
public static inline var KIND_OBJECT = 'object';
public static inline var CONTEXT_FLOW_IN = 1;
public static inline var CONTEXT_FLOW_OUT = 2;
public static inline var CONTEXT_BLOCK_IN = 3;
public static inline var CONTEXT_BLOCK_OUT = 4;
public static inline var CHOMPING_CLIP = 1;
public static inline var CHOMPING_STRIP = 2;
public static inline var CHOMPING_KEEP = 3;
public static inline var CHAR_TAB = 0x09; /* Tab */
public static inline var CHAR_LINE_FEED = 0x0A; /* LF */
public static inline var CHAR_CARRIAGE_RETURN = 0x0D; /* CR */
public static inline var CHAR_SPACE = 0x20; /* Space */
public static inline var CHAR_EXCLAMATION = 0x21; /* ! */
public static inline var CHAR_DOUBLE_QUOTE = 0x22; /* " */
public static inline var CHAR_SHARP = 0x23; /* # */
public static inline var CHAR_PERCENT = 0x25; /* % */
public static inline var CHAR_AMPERSAND = 0x26; /* & */
public static inline var CHAR_SINGLE_QUOTE = 0x27; /* ' */
public static inline var CHAR_ASTERISK = 0x2A; /* * */
public static inline var CHAR_PLUS = 0x2B; /* + */
public static inline var CHAR_COMMA = 0x2C; /* , */
public static inline var CHAR_MINUS = 0x2D; /* - */
public static inline var CHAR_DOT = 0x2E; /* . */
public static inline var CHAR_SLASH = 0x2F; /* / */
public static inline var CHAR_DIGIT_ZERO = 0x30; /* 0 */
public static inline var CHAR_DIGIT_ONE = 0x31; /* 1 */
public static inline var CHAR_DIGIT_NINE = 0x39; /* 9 */
public static inline var CHAR_COLON = 0x3A; /* : */
public static inline var CHAR_LESS_THAN = 0x3C; /* < */
public static inline var CHAR_GREATER_THAN = 0x3E; /* > */
public static inline var CHAR_QUESTION = 0x3F; /* ? */
public static inline var CHAR_COMMERCIAL_AT = 0x40; /* @ */
public static inline var CHAR_CAPITAL_A = 0x41; /* A */
public static inline var CHAR_CAPITAL_F = 0x46; /* F */
public static inline var CHAR_CAPITAL_L = 0x4C; /* L */
public static inline var CHAR_CAPITAL_N = 0x4E; /* N */
public static inline var CHAR_CAPITAL_P = 0x50; /* P */
public static inline var CHAR_CAPITAL_U = 0x55; /* U */
public static inline var CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */
public static inline var CHAR_BACKSLASH = 0x5C; /* \ */
public static inline var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */
public static inline var CHAR_UNDERSCORE = 0x5F; /* _ */
public static inline var CHAR_GRAVE_ACCENT = 0x60; /* ` */
public static inline var CHAR_SMALL_A = 0x61; /* a */
public static inline var CHAR_SMALL_B = 0x62; /* b */
public static inline var CHAR_SMALL_E = 0x65; /* e */
public static inline var CHAR_SMALL_F = 0x66; /* f */
public static inline var CHAR_SMALL_N = 0x6E; /* n */
public static inline var CHAR_SMALL_R = 0x72; /* r */
public static inline var CHAR_SMALL_T = 0x74; /* t */
public static inline var CHAR_SMALL_U = 0x75; /* u */
public static inline var CHAR_SMALL_V = 0x76; /* v */
public static inline var CHAR_SMALL_X = 0x78; /* x */
public static inline var CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */
public static inline var CHAR_VERTICAL_LINE = 0x7C; /* | */
public static inline var CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */
public static var SIMPLE_ESCAPE_SEQUENCES:IntMap<String> =
{
var hash = new IntMap<String>();
hash.set(CHAR_DIGIT_ZERO, createUtf8Char(0x00));// '\x00');
hash.set(CHAR_SMALL_A, createUtf8Char(0x07));//'\x07');
hash.set(CHAR_SMALL_B, createUtf8Char(0x08));//'\x08');
hash.set(CHAR_SMALL_T, createUtf8Char(0x09));//'\x09');
hash.set(CHAR_TAB, createUtf8Char(0x09));//'\x09');
hash.set(CHAR_SMALL_N, createUtf8Char(0x0A));//'\x0A');
hash.set(CHAR_SMALL_V, createUtf8Char(0x0B));//'\x0B');
hash.set(CHAR_SMALL_F, createUtf8Char(0x0C));//'\x0C');
hash.set(CHAR_SMALL_R, createUtf8Char(0x0D));//'\x0D');
hash.set(CHAR_SMALL_E, createUtf8Char(0x1B));//'\x1B');
hash.set(CHAR_SPACE, createUtf8Char(0x20));//' ');
hash.set(CHAR_DOUBLE_QUOTE, createUtf8Char(0x22));//'\x22');
hash.set(CHAR_SLASH, createUtf8Char(0x2f));//'/');
hash.set(CHAR_BACKSLASH, createUtf8Char(0x5C));//'\x5C');
hash.set(CHAR_CAPITAL_N, createUtf8Char(0x85));//'\x85');
hash.set(CHAR_UNDERSCORE, createUtf8Char(0xA0));//'\xA0');
hash.set(CHAR_CAPITAL_L, createUtf8Char(0x2028));//'\u2028');
hash.set(CHAR_CAPITAL_P, createUtf8Char(0x2029));//'\u2029');
hash;
};
static function createUtf8Char(hex:Int):String
{
var utf8 = new Utf8(1);
utf8.addChar(hex);
return utf8.toString();
}
public static var HEXADECIMAL_ESCAPE_SEQUENCES:IntMap<Int> =
{
var hash = new IntMap<Int>();
hash.set(CHAR_SMALL_X, 2);
hash.set(CHAR_SMALL_U, 4);
hash.set(CHAR_CAPITAL_U, 8);
hash;
};
#if (eval || neko || cpp || display)
public static var PATTERN_NON_PRINTABLE = ~/[\x{00}-\x{08}\x{0B}\x{0C}\x{0E}-\x{1F}\x{7F}-\x{84}\x{86}-\x{9F}\x{FFFE}\x{FFFF}]/u;
#elseif (js || flash9 || java)
public static var PATTERN_NON_PRINTABLE = ~/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uD800-\uDFFF\uFFFE\uFFFF]/u;
#else
#error "Compilation target not supported due to lack of Unicode RegEx support."
#end
#if (eval || neko || cpp || display)
public static var PATTERN_NON_ASCII_LINE_BREAKS = ~/[\x{85}\x{2028}\x{2029}]/u;
#elseif (js || flash9 || java)
public static var PATTERN_NON_ASCII_LINE_BREAKS = ~/[\x85\u2028\u2029]/u;
#else
#error "Compilation target not supported due to lack of Unicode RegEx support."
#end
public static var PATTERN_FLOW_INDICATORS = ~/[,\[\]\{\}]/u;
public static var PATTERN_TAG_HANDLE = ~/^(?:!|!!|![a-z\-]+!)$/iu;
public static var PATTERN_TAG_URI = ~/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/iu;
}