Some checks failed
Lint / pre-commit Linting (push) Has been cancelled
Deploy js code to an instance of screeps. Some debugging tools are implemented. Reviewed-on: #6 Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de> Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
489 lines
18 KiB
CoffeeScript
489 lines
18 KiB
CoffeeScript
|
|
Pattern = require './Pattern'
|
|
Unescaper = require './Unescaper'
|
|
Escaper = require './Escaper'
|
|
Utils = require './Utils'
|
|
ParseException = require './Exception/ParseException'
|
|
ParseMore = require './Exception/ParseMore'
|
|
DumpException = require './Exception/DumpException'
|
|
|
|
# Inline YAML parsing and dumping
|
|
class Inline
|
|
|
|
# Quoted string regular expression
|
|
@REGEX_QUOTED_STRING: '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')'
|
|
|
|
# Pre-compiled patterns
|
|
#
|
|
@PATTERN_TRAILING_COMMENTS: new Pattern '^\\s*#.*$'
|
|
@PATTERN_QUOTED_SCALAR: new Pattern '^'+@REGEX_QUOTED_STRING
|
|
@PATTERN_THOUSAND_NUMERIC_SCALAR: new Pattern '^(-|\\+)?[0-9,]+(\\.[0-9]+)?$'
|
|
@PATTERN_SCALAR_BY_DELIMITERS: {}
|
|
|
|
# Settings
|
|
@settings: {}
|
|
|
|
|
|
# Configure YAML inline.
|
|
#
|
|
# @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
|
|
# @param [Function] objectDecoder A function to deserialize custom objects, null otherwise
|
|
#
|
|
@configure: (exceptionOnInvalidType = null, objectDecoder = null) ->
|
|
# Update settings
|
|
@settings.exceptionOnInvalidType = exceptionOnInvalidType
|
|
@settings.objectDecoder = objectDecoder
|
|
return
|
|
|
|
|
|
# Converts a YAML string to a JavaScript object.
|
|
#
|
|
# @param [String] value A YAML string
|
|
# @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
|
|
# @param [Function] objectDecoder A function to deserialize custom objects, null otherwise
|
|
#
|
|
# @return [Object] A JavaScript object representing the YAML string
|
|
#
|
|
# @throw [ParseException]
|
|
#
|
|
@parse: (value, exceptionOnInvalidType = false, objectDecoder = null) ->
|
|
# Update settings from last call of Inline.parse()
|
|
@settings.exceptionOnInvalidType = exceptionOnInvalidType
|
|
@settings.objectDecoder = objectDecoder
|
|
|
|
if not value?
|
|
return ''
|
|
|
|
value = Utils.trim value
|
|
|
|
if 0 is value.length
|
|
return ''
|
|
|
|
# Keep a context object to pass through static methods
|
|
context = {exceptionOnInvalidType, objectDecoder, i: 0}
|
|
|
|
switch value.charAt(0)
|
|
when '['
|
|
result = @parseSequence value, context
|
|
++context.i
|
|
when '{'
|
|
result = @parseMapping value, context
|
|
++context.i
|
|
else
|
|
result = @parseScalar value, null, ['"', "'"], context
|
|
|
|
# Some comments are allowed at the end
|
|
if @PATTERN_TRAILING_COMMENTS.replace(value[context.i..], '') isnt ''
|
|
throw new ParseException 'Unexpected characters near "'+value[context.i..]+'".'
|
|
|
|
return result
|
|
|
|
|
|
# Dumps a given JavaScript variable to a YAML string.
|
|
#
|
|
# @param [Object] value The JavaScript variable to convert
|
|
# @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
|
|
# @param [Function] objectEncoder A function to serialize custom objects, null otherwise
|
|
#
|
|
# @return [String] The YAML string representing the JavaScript object
|
|
#
|
|
# @throw [DumpException]
|
|
#
|
|
@dump: (value, exceptionOnInvalidType = false, objectEncoder = null) ->
|
|
if not value?
|
|
return 'null'
|
|
type = typeof value
|
|
if type is 'object'
|
|
if value instanceof Date
|
|
return value.toISOString()
|
|
else if objectEncoder?
|
|
result = objectEncoder value
|
|
if typeof result is 'string' or result?
|
|
return result
|
|
return @dumpObject value
|
|
if type is 'boolean'
|
|
return (if value then 'true' else 'false')
|
|
if Utils.isDigits(value)
|
|
return (if type is 'string' then "'"+value+"'" else String(parseInt(value)))
|
|
if Utils.isNumeric(value)
|
|
return (if type is 'string' then "'"+value+"'" else String(parseFloat(value)))
|
|
if type is 'number'
|
|
return (if value is Infinity then '.Inf' else (if value is -Infinity then '-.Inf' else (if isNaN(value) then '.NaN' else value)))
|
|
if Escaper.requiresDoubleQuoting value
|
|
return Escaper.escapeWithDoubleQuotes value
|
|
if Escaper.requiresSingleQuoting value
|
|
return Escaper.escapeWithSingleQuotes value
|
|
if '' is value
|
|
return '""'
|
|
if Utils.PATTERN_DATE.test value
|
|
return "'"+value+"'";
|
|
if value.toLowerCase() in ['null','~','true','false']
|
|
return "'"+value+"'"
|
|
# Default
|
|
return value;
|
|
|
|
|
|
# Dumps a JavaScript object to a YAML string.
|
|
#
|
|
# @param [Object] value The JavaScript object to dump
|
|
# @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
|
|
# @param [Function] objectEncoder A function do serialize custom objects, null otherwise
|
|
#
|
|
# @return string The YAML string representing the JavaScript object
|
|
#
|
|
@dumpObject: (value, exceptionOnInvalidType, objectSupport = null) ->
|
|
# Array
|
|
if value instanceof Array
|
|
output = []
|
|
for val in value
|
|
output.push @dump val
|
|
return '['+output.join(', ')+']'
|
|
|
|
# Mapping
|
|
else
|
|
output = []
|
|
for key, val of value
|
|
output.push @dump(key)+': '+@dump(val)
|
|
return '{'+output.join(', ')+'}'
|
|
|
|
|
|
# Parses a scalar to a YAML string.
|
|
#
|
|
# @param [Object] scalar
|
|
# @param [Array] delimiters
|
|
# @param [Array] stringDelimiters
|
|
# @param [Object] context
|
|
# @param [Boolean] evaluate
|
|
#
|
|
# @return [String] A YAML string
|
|
#
|
|
# @throw [ParseException] When malformed inline YAML string is parsed
|
|
#
|
|
@parseScalar: (scalar, delimiters = null, stringDelimiters = ['"', "'"], context = null, evaluate = true) ->
|
|
unless context?
|
|
context = exceptionOnInvalidType: @settings.exceptionOnInvalidType, objectDecoder: @settings.objectDecoder, i: 0
|
|
{i} = context
|
|
|
|
if scalar.charAt(i) in stringDelimiters
|
|
# Quoted scalar
|
|
output = @parseQuotedScalar scalar, context
|
|
{i} = context
|
|
|
|
if delimiters?
|
|
tmp = Utils.ltrim scalar[i..], ' '
|
|
if not(tmp.charAt(0) in delimiters)
|
|
throw new ParseException 'Unexpected characters ('+scalar[i..]+').'
|
|
|
|
else
|
|
# "normal" string
|
|
if not delimiters
|
|
output = scalar[i..]
|
|
i += output.length
|
|
|
|
# Remove comments
|
|
strpos = output.indexOf ' #'
|
|
if strpos isnt -1
|
|
output = Utils.rtrim output[0...strpos]
|
|
|
|
else
|
|
joinedDelimiters = delimiters.join('|')
|
|
pattern = @PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters]
|
|
unless pattern?
|
|
pattern = new Pattern '^(.+?)('+joinedDelimiters+')'
|
|
@PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] = pattern
|
|
if match = pattern.exec scalar[i..]
|
|
output = match[1]
|
|
i += output.length
|
|
else
|
|
throw new ParseException 'Malformed inline YAML string ('+scalar+').'
|
|
|
|
|
|
if evaluate
|
|
output = @evaluateScalar output, context
|
|
|
|
context.i = i
|
|
return output
|
|
|
|
|
|
# Parses a quoted scalar to YAML.
|
|
#
|
|
# @param [String] scalar
|
|
# @param [Object] context
|
|
#
|
|
# @return [String] A YAML string
|
|
#
|
|
# @throw [ParseMore] When malformed inline YAML string is parsed
|
|
#
|
|
@parseQuotedScalar: (scalar, context) ->
|
|
{i} = context
|
|
|
|
unless match = @PATTERN_QUOTED_SCALAR.exec scalar[i..]
|
|
throw new ParseMore 'Malformed inline YAML string ('+scalar[i..]+').'
|
|
|
|
output = match[0].substr(1, match[0].length - 2)
|
|
|
|
if '"' is scalar.charAt(i)
|
|
output = Unescaper.unescapeDoubleQuotedString output
|
|
else
|
|
output = Unescaper.unescapeSingleQuotedString output
|
|
|
|
i += match[0].length
|
|
|
|
context.i = i
|
|
return output
|
|
|
|
|
|
# Parses a sequence to a YAML string.
|
|
#
|
|
# @param [String] sequence
|
|
# @param [Object] context
|
|
#
|
|
# @return [String] A YAML string
|
|
#
|
|
# @throw [ParseMore] When malformed inline YAML string is parsed
|
|
#
|
|
@parseSequence: (sequence, context) ->
|
|
output = []
|
|
len = sequence.length
|
|
{i} = context
|
|
i += 1
|
|
|
|
# [foo, bar, ...]
|
|
while i < len
|
|
context.i = i
|
|
switch sequence.charAt(i)
|
|
when '['
|
|
# Nested sequence
|
|
output.push @parseSequence sequence, context
|
|
{i} = context
|
|
when '{'
|
|
# Nested mapping
|
|
output.push @parseMapping sequence, context
|
|
{i} = context
|
|
when ']'
|
|
return output
|
|
when ',', ' ', "\n"
|
|
# Do nothing
|
|
else
|
|
isQuoted = (sequence.charAt(i) in ['"', "'"])
|
|
value = @parseScalar sequence, [',', ']'], ['"', "'"], context
|
|
{i} = context
|
|
|
|
if not(isQuoted) and typeof(value) is 'string' and (value.indexOf(': ') isnt -1 or value.indexOf(":\n") isnt -1)
|
|
# Embedded mapping?
|
|
try
|
|
value = @parseMapping '{'+value+'}'
|
|
catch e
|
|
# No, it's not
|
|
|
|
|
|
output.push value
|
|
|
|
--i
|
|
|
|
++i
|
|
|
|
throw new ParseMore 'Malformed inline YAML string '+sequence
|
|
|
|
|
|
# Parses a mapping to a YAML string.
|
|
#
|
|
# @param [String] mapping
|
|
# @param [Object] context
|
|
#
|
|
# @return [String] A YAML string
|
|
#
|
|
# @throw [ParseMore] When malformed inline YAML string is parsed
|
|
#
|
|
@parseMapping: (mapping, context) ->
|
|
output = {}
|
|
len = mapping.length
|
|
{i} = context
|
|
i += 1
|
|
|
|
# {foo: bar, bar:foo, ...}
|
|
shouldContinueWhileLoop = false
|
|
while i < len
|
|
context.i = i
|
|
switch mapping.charAt(i)
|
|
when ' ', ',', "\n"
|
|
++i
|
|
context.i = i
|
|
shouldContinueWhileLoop = true
|
|
when '}'
|
|
return output
|
|
|
|
if shouldContinueWhileLoop
|
|
shouldContinueWhileLoop = false
|
|
continue
|
|
|
|
# Key
|
|
key = @parseScalar mapping, [':', ' ', "\n"], ['"', "'"], context, false
|
|
{i} = context
|
|
|
|
# Value
|
|
done = false
|
|
|
|
while i < len
|
|
context.i = i
|
|
switch mapping.charAt(i)
|
|
when '['
|
|
# Nested sequence
|
|
value = @parseSequence mapping, context
|
|
{i} = context
|
|
# Spec: Keys MUST be unique; first one wins.
|
|
# Parser cannot abort this mapping earlier, since lines
|
|
# are processed sequentially.
|
|
if output[key] == undefined
|
|
output[key] = value
|
|
done = true
|
|
when '{'
|
|
# Nested mapping
|
|
value = @parseMapping mapping, context
|
|
{i} = context
|
|
# Spec: Keys MUST be unique; first one wins.
|
|
# Parser cannot abort this mapping earlier, since lines
|
|
# are processed sequentially.
|
|
if output[key] == undefined
|
|
output[key] = value
|
|
done = true
|
|
when ':', ' ', "\n"
|
|
# Do nothing
|
|
else
|
|
value = @parseScalar mapping, [',', '}'], ['"', "'"], context
|
|
{i} = context
|
|
# Spec: Keys MUST be unique; first one wins.
|
|
# Parser cannot abort this mapping earlier, since lines
|
|
# are processed sequentially.
|
|
if output[key] == undefined
|
|
output[key] = value
|
|
done = true
|
|
--i
|
|
|
|
++i
|
|
|
|
if done
|
|
break
|
|
|
|
throw new ParseMore 'Malformed inline YAML string '+mapping
|
|
|
|
|
|
# Evaluates scalars and replaces magic values.
|
|
#
|
|
# @param [String] scalar
|
|
#
|
|
# @return [String] A YAML string
|
|
#
|
|
@evaluateScalar: (scalar, context) ->
|
|
scalar = Utils.trim(scalar)
|
|
scalarLower = scalar.toLowerCase()
|
|
|
|
switch scalarLower
|
|
when 'null', '', '~'
|
|
return null
|
|
when 'true'
|
|
return true
|
|
when 'false'
|
|
return false
|
|
when '.inf'
|
|
return Infinity
|
|
when '.nan'
|
|
return NaN
|
|
when '-.inf'
|
|
return Infinity
|
|
else
|
|
firstChar = scalarLower.charAt(0)
|
|
switch firstChar
|
|
when '!'
|
|
firstSpace = scalar.indexOf(' ')
|
|
if firstSpace is -1
|
|
firstWord = scalarLower
|
|
else
|
|
firstWord = scalarLower[0...firstSpace]
|
|
switch firstWord
|
|
when '!'
|
|
if firstSpace isnt -1
|
|
return parseInt @parseScalar(scalar[2..])
|
|
return null
|
|
when '!str'
|
|
return Utils.ltrim scalar[4..]
|
|
when '!!str'
|
|
return Utils.ltrim scalar[5..]
|
|
when '!!int'
|
|
return parseInt(@parseScalar(scalar[5..]))
|
|
when '!!bool'
|
|
return Utils.parseBoolean(@parseScalar(scalar[6..]), false)
|
|
when '!!float'
|
|
return parseFloat(@parseScalar(scalar[7..]))
|
|
when '!!timestamp'
|
|
return Utils.stringToDate(Utils.ltrim(scalar[11..]))
|
|
else
|
|
unless context?
|
|
context = exceptionOnInvalidType: @settings.exceptionOnInvalidType, objectDecoder: @settings.objectDecoder, i: 0
|
|
{objectDecoder, exceptionOnInvalidType} = context
|
|
|
|
if objectDecoder
|
|
# If objectDecoder function is given, we can do custom decoding of custom types
|
|
trimmedScalar = Utils.rtrim scalar
|
|
firstSpace = trimmedScalar.indexOf(' ')
|
|
if firstSpace is -1
|
|
return objectDecoder trimmedScalar, null
|
|
else
|
|
subValue = Utils.ltrim trimmedScalar[firstSpace+1..]
|
|
unless subValue.length > 0
|
|
subValue = null
|
|
return objectDecoder trimmedScalar[0...firstSpace], subValue
|
|
|
|
if exceptionOnInvalidType
|
|
throw new ParseException 'Custom object support when parsing a YAML file has been disabled.'
|
|
|
|
return null
|
|
when '0'
|
|
if '0x' is scalar[0...2]
|
|
return Utils.hexDec scalar
|
|
else if Utils.isDigits scalar
|
|
return Utils.octDec scalar
|
|
else if Utils.isNumeric scalar
|
|
return parseFloat scalar
|
|
else
|
|
return scalar
|
|
when '+'
|
|
if Utils.isDigits scalar
|
|
raw = scalar
|
|
cast = parseInt(raw)
|
|
if raw is String(cast)
|
|
return cast
|
|
else
|
|
return raw
|
|
else if Utils.isNumeric scalar
|
|
return parseFloat scalar
|
|
else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
|
|
return parseFloat(scalar.replace(',', ''))
|
|
return scalar
|
|
when '-'
|
|
if Utils.isDigits(scalar[1..])
|
|
if '0' is scalar.charAt(1)
|
|
return -Utils.octDec(scalar[1..])
|
|
else
|
|
raw = scalar[1..]
|
|
cast = parseInt(raw)
|
|
if raw is String(cast)
|
|
return -cast
|
|
else
|
|
return -raw
|
|
else if Utils.isNumeric scalar
|
|
return parseFloat scalar
|
|
else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
|
|
return parseFloat(scalar.replace(',', ''))
|
|
return scalar
|
|
else
|
|
if date = Utils.stringToDate(scalar)
|
|
return date
|
|
else if Utils.isNumeric(scalar)
|
|
return parseFloat scalar
|
|
else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
|
|
return parseFloat(scalar.replace(',', ''))
|
|
return scalar
|
|
|
|
module.exports = Inline
|