Parser.coffee 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. Inline = require './Inline'
  2. Pattern = require './Pattern'
  3. Utils = require './Utils'
  4. ParseException = require './Exception/ParseException'
  5. ParseMore = require './Exception/ParseMore'
  6. # Parser parses YAML strings to convert them to JavaScript objects.
  7. #
  8. class Parser
  9. # Pre-compiled patterns
  10. #
  11. PATTERN_FOLDED_SCALAR_ALL: new Pattern '^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$'
  12. PATTERN_FOLDED_SCALAR_END: new Pattern '(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$'
  13. PATTERN_SEQUENCE_ITEM: new Pattern '^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$'
  14. PATTERN_ANCHOR_VALUE: new Pattern '^&(?<ref>[^ ]+) *(?<value>.*)'
  15. PATTERN_COMPACT_NOTATION: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$'
  16. PATTERN_MAPPING_ITEM: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$'
  17. PATTERN_DECIMAL: new Pattern '\\d+'
  18. PATTERN_INDENT_SPACES: new Pattern '^ +'
  19. PATTERN_TRAILING_LINES: new Pattern '(\n*)$'
  20. PATTERN_YAML_HEADER: new Pattern '^\\%YAML[: ][\\d\\.]+.*\n', 'm'
  21. PATTERN_LEADING_COMMENTS: new Pattern '^(\\#.*?\n)+', 'm'
  22. PATTERN_DOCUMENT_MARKER_START: new Pattern '^\\-\\-\\-.*?\n', 'm'
  23. PATTERN_DOCUMENT_MARKER_END: new Pattern '^\\.\\.\\.\\s*$', 'm'
  24. PATTERN_FOLDED_SCALAR_BY_INDENTATION: {}
  25. # Context types
  26. #
  27. CONTEXT_NONE: 0
  28. CONTEXT_SEQUENCE: 1
  29. CONTEXT_MAPPING: 2
  30. # Constructor
  31. #
  32. # @param [Integer] offset The offset of YAML document (used for line numbers in error messages)
  33. #
  34. constructor: (@offset = 0) ->
  35. @lines = []
  36. @currentLineNb = -1
  37. @currentLine = ''
  38. @refs = {}
  39. # Parses a YAML string to a JavaScript value.
  40. #
  41. # @param [String] value A YAML string
  42. # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
  43. # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise
  44. #
  45. # @return [Object] A JavaScript value
  46. #
  47. # @throw [ParseException] If the YAML is not valid
  48. #
  49. parse: (value, exceptionOnInvalidType = false, objectDecoder = null) ->
  50. @currentLineNb = -1
  51. @currentLine = ''
  52. @lines = @cleanup(value).split "\n"
  53. data = null
  54. context = @CONTEXT_NONE
  55. allowOverwrite = false
  56. while @moveToNextLine()
  57. if @isCurrentLineEmpty()
  58. continue
  59. # Tab?
  60. if "\t" is @currentLine[0]
  61. throw new ParseException 'A YAML file cannot contain tabs as indentation.', @getRealCurrentLineNb() + 1, @currentLine
  62. isRef = mergeNode = false
  63. if values = @PATTERN_SEQUENCE_ITEM.exec @currentLine
  64. if @CONTEXT_MAPPING is context
  65. throw new ParseException 'You cannot define a sequence item when in a mapping'
  66. context = @CONTEXT_SEQUENCE
  67. data ?= []
  68. if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value
  69. isRef = matches.ref
  70. values.value = matches.value
  71. # Array
  72. if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0
  73. if @currentLineNb < @lines.length - 1 and not @isNextLineUnIndentedCollection()
  74. c = @getRealCurrentLineNb() + 1
  75. parser = new Parser c
  76. parser.refs = @refs
  77. data.push parser.parse(@getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder)
  78. else
  79. data.push null
  80. else
  81. if values.leadspaces?.length and matches = @PATTERN_COMPACT_NOTATION.exec values.value
  82. # This is a compact notation element, add to next block and parse
  83. c = @getRealCurrentLineNb()
  84. parser = new Parser c
  85. parser.refs = @refs
  86. block = values.value
  87. indent = @getCurrentLineIndentation()
  88. if @isNextLineIndented(false)
  89. block += "\n"+@getNextEmbedBlock(indent + values.leadspaces.length + 1, true)
  90. data.push parser.parse block, exceptionOnInvalidType, objectDecoder
  91. else
  92. data.push @parseValue values.value, exceptionOnInvalidType, objectDecoder
  93. else if (values = @PATTERN_MAPPING_ITEM.exec @currentLine) and values.key.indexOf(' #') is -1
  94. if @CONTEXT_SEQUENCE is context
  95. throw new ParseException 'You cannot define a mapping item when in a sequence'
  96. context = @CONTEXT_MAPPING
  97. data ?= {}
  98. # Force correct settings
  99. Inline.configure exceptionOnInvalidType, objectDecoder
  100. try
  101. key = Inline.parseScalar values.key
  102. catch e
  103. e.parsedLine = @getRealCurrentLineNb() + 1
  104. e.snippet = @currentLine
  105. throw e
  106. if '<<' is key
  107. mergeNode = true
  108. allowOverwrite = true
  109. if values.value?.indexOf('*') is 0
  110. refName = values.value[1..]
  111. unless @refs[refName]?
  112. throw new ParseException 'Reference "'+refName+'" does not exist.', @getRealCurrentLineNb() + 1, @currentLine
  113. refValue = @refs[refName]
  114. if typeof refValue isnt 'object'
  115. throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine
  116. if refValue instanceof Array
  117. # Merge array with object
  118. for value, i in refValue
  119. data[String(i)] ?= value
  120. else
  121. # Merge objects
  122. for key, value of refValue
  123. data[key] ?= value
  124. else
  125. if values.value? and values.value isnt ''
  126. value = values.value
  127. else
  128. value = @getNextEmbedBlock()
  129. c = @getRealCurrentLineNb() + 1
  130. parser = new Parser c
  131. parser.refs = @refs
  132. parsed = parser.parse value, exceptionOnInvalidType
  133. unless typeof parsed is 'object'
  134. throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine
  135. if parsed instanceof Array
  136. # If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
  137. # and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
  138. # in the sequence override keys specified in later mapping nodes.
  139. for parsedItem in parsed
  140. unless typeof parsedItem is 'object'
  141. throw new ParseException 'Merge items must be objects.', @getRealCurrentLineNb() + 1, parsedItem
  142. if parsedItem instanceof Array
  143. # Merge array with object
  144. for value, i in parsedItem
  145. k = String(i)
  146. unless data.hasOwnProperty(k)
  147. data[k] = value
  148. else
  149. # Merge objects
  150. for key, value of parsedItem
  151. unless data.hasOwnProperty(key)
  152. data[key] = value
  153. else
  154. # If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
  155. # current mapping, unless the key already exists in it.
  156. for key, value of parsed
  157. unless data.hasOwnProperty(key)
  158. data[key] = value
  159. else if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value
  160. isRef = matches.ref
  161. values.value = matches.value
  162. if mergeNode
  163. # Merge keys
  164. else if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0
  165. # Hash
  166. # if next line is less indented or equal, then it means that the current value is null
  167. if not(@isNextLineIndented()) and not(@isNextLineUnIndentedCollection())
  168. # Spec: Keys MUST be unique; first one wins.
  169. # But overwriting is allowed when a merge node is used in current block.
  170. if allowOverwrite or data[key] is undefined
  171. data[key] = null
  172. else
  173. c = @getRealCurrentLineNb() + 1
  174. parser = new Parser c
  175. parser.refs = @refs
  176. val = parser.parse @getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder
  177. # Spec: Keys MUST be unique; first one wins.
  178. # But overwriting is allowed when a merge node is used in current block.
  179. if allowOverwrite or data[key] is undefined
  180. data[key] = val
  181. else
  182. val = @parseValue values.value, exceptionOnInvalidType, objectDecoder
  183. # Spec: Keys MUST be unique; first one wins.
  184. # But overwriting is allowed when a merge node is used in current block.
  185. if allowOverwrite or data[key] is undefined
  186. data[key] = val
  187. else
  188. # 1-liner optionally followed by newline
  189. lineCount = @lines.length
  190. if 1 is lineCount or (2 is lineCount and Utils.isEmpty(@lines[1]))
  191. try
  192. value = Inline.parse @lines[0], exceptionOnInvalidType, objectDecoder
  193. catch e
  194. e.parsedLine = @getRealCurrentLineNb() + 1
  195. e.snippet = @currentLine
  196. throw e
  197. if typeof value is 'object'
  198. if value instanceof Array
  199. first = value[0]
  200. else
  201. for key of value
  202. first = value[key]
  203. break
  204. if typeof first is 'string' and first.indexOf('*') is 0
  205. data = []
  206. for alias in value
  207. data.push @refs[alias[1..]]
  208. value = data
  209. return value
  210. else if Utils.ltrim(value).charAt(0) in ['[', '{']
  211. try
  212. return Inline.parse value, exceptionOnInvalidType, objectDecoder
  213. catch e
  214. e.parsedLine = @getRealCurrentLineNb() + 1
  215. e.snippet = @currentLine
  216. throw e
  217. throw new ParseException 'Unable to parse.', @getRealCurrentLineNb() + 1, @currentLine
  218. if isRef
  219. if data instanceof Array
  220. @refs[isRef] = data[data.length-1]
  221. else
  222. lastKey = null
  223. for key of data
  224. lastKey = key
  225. @refs[isRef] = data[lastKey]
  226. if Utils.isEmpty(data)
  227. return null
  228. else
  229. return data
  230. # Returns the current line number (takes the offset into account).
  231. #
  232. # @return [Integer] The current line number
  233. #
  234. getRealCurrentLineNb: ->
  235. return @currentLineNb + @offset
  236. # Returns the current line indentation.
  237. #
  238. # @return [Integer] The current line indentation
  239. #
  240. getCurrentLineIndentation: ->
  241. return @currentLine.length - Utils.ltrim(@currentLine, ' ').length
  242. # Returns the next embed block of YAML.
  243. #
  244. # @param [Integer] indentation The indent level at which the block is to be read, or null for default
  245. #
  246. # @return [String] A YAML string
  247. #
  248. # @throw [ParseException] When indentation problem are detected
  249. #
  250. getNextEmbedBlock: (indentation = null, includeUnindentedCollection = false) ->
  251. @moveToNextLine()
  252. if not indentation?
  253. newIndent = @getCurrentLineIndentation()
  254. unindentedEmbedBlock = @isStringUnIndentedCollectionItem @currentLine
  255. if not(@isCurrentLineEmpty()) and 0 is newIndent and not(unindentedEmbedBlock)
  256. throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine
  257. else
  258. newIndent = indentation
  259. data = [@currentLine[newIndent..]]
  260. unless includeUnindentedCollection
  261. isItUnindentedCollection = @isStringUnIndentedCollectionItem @currentLine
  262. # Comments must not be removed inside a string block (ie. after a line ending with "|")
  263. # They must not be removed inside a sub-embedded block as well
  264. removeCommentsPattern = @PATTERN_FOLDED_SCALAR_END
  265. removeComments = not removeCommentsPattern.test @currentLine
  266. while @moveToNextLine()
  267. indent = @getCurrentLineIndentation()
  268. if indent is newIndent
  269. removeComments = not removeCommentsPattern.test @currentLine
  270. if removeComments and @isCurrentLineComment()
  271. continue
  272. if @isCurrentLineBlank()
  273. data.push @currentLine[newIndent..]
  274. continue
  275. if isItUnindentedCollection and not @isStringUnIndentedCollectionItem(@currentLine) and indent is newIndent
  276. @moveToPreviousLine()
  277. break
  278. if indent >= newIndent
  279. data.push @currentLine[newIndent..]
  280. else if Utils.ltrim(@currentLine).charAt(0) is '#'
  281. # Don't add line with comments
  282. else if 0 is indent
  283. @moveToPreviousLine()
  284. break
  285. else
  286. throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine
  287. return data.join "\n"
  288. # Moves the parser to the next line.
  289. #
  290. # @return [Boolean]
  291. #
  292. moveToNextLine: ->
  293. if @currentLineNb >= @lines.length - 1
  294. return false
  295. @currentLine = @lines[++@currentLineNb];
  296. return true
  297. # Moves the parser to the previous line.
  298. #
  299. moveToPreviousLine: ->
  300. @currentLine = @lines[--@currentLineNb]
  301. return
  302. # Parses a YAML value.
  303. #
  304. # @param [String] value A YAML value
  305. # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types false otherwise
  306. # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise
  307. #
  308. # @return [Object] A JavaScript value
  309. #
  310. # @throw [ParseException] When reference does not exist
  311. #
  312. parseValue: (value, exceptionOnInvalidType, objectDecoder) ->
  313. if 0 is value.indexOf('*')
  314. pos = value.indexOf '#'
  315. if pos isnt -1
  316. value = value.substr(1, pos-2)
  317. else
  318. value = value[1..]
  319. if @refs[value] is undefined
  320. throw new ParseException 'Reference "'+value+'" does not exist.', @currentLine
  321. return @refs[value]
  322. if matches = @PATTERN_FOLDED_SCALAR_ALL.exec value
  323. modifiers = matches.modifiers ? ''
  324. foldedIndent = Math.abs(parseInt(modifiers))
  325. if isNaN(foldedIndent) then foldedIndent = 0
  326. val = @parseFoldedScalar matches.separator, @PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent
  327. if matches.type?
  328. # Force correct settings
  329. Inline.configure exceptionOnInvalidType, objectDecoder
  330. return Inline.parseScalar matches.type+' '+val
  331. else
  332. return val
  333. # Value can be multiline compact sequence or mapping or string
  334. if value.charAt(0) in ['[', '{', '"', "'"]
  335. while true
  336. try
  337. return Inline.parse value, exceptionOnInvalidType, objectDecoder
  338. catch e
  339. if e instanceof ParseMore and @moveToNextLine()
  340. value += "\n" + Utils.trim(@currentLine, ' ')
  341. else
  342. e.parsedLine = @getRealCurrentLineNb() + 1
  343. e.snippet = @currentLine
  344. throw e
  345. else
  346. if @isNextLineIndented()
  347. value += "\n" + @getNextEmbedBlock()
  348. return Inline.parse value, exceptionOnInvalidType, objectDecoder
  349. return
  350. # Parses a folded scalar.
  351. #
  352. # @param [String] separator The separator that was used to begin this folded scalar (| or >)
  353. # @param [String] indicator The indicator that was used to begin this folded scalar (+ or -)
  354. # @param [Integer] indentation The indentation that was used to begin this folded scalar
  355. #
  356. # @return [String] The text value
  357. #
  358. parseFoldedScalar: (separator, indicator = '', indentation = 0) ->
  359. notEOF = @moveToNextLine()
  360. if not notEOF
  361. return ''
  362. isCurrentLineBlank = @isCurrentLineBlank()
  363. text = ''
  364. # Leading blank lines are consumed before determining indentation
  365. while notEOF and isCurrentLineBlank
  366. # newline only if not EOF
  367. if notEOF = @moveToNextLine()
  368. text += "\n"
  369. isCurrentLineBlank = @isCurrentLineBlank()
  370. # Determine indentation if not specified
  371. if 0 is indentation
  372. if matches = @PATTERN_INDENT_SPACES.exec @currentLine
  373. indentation = matches[0].length
  374. if indentation > 0
  375. pattern = @PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation]
  376. unless pattern?
  377. pattern = new Pattern '^ {'+indentation+'}(.*)$'
  378. Parser::PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern
  379. while notEOF and (isCurrentLineBlank or matches = pattern.exec @currentLine)
  380. if isCurrentLineBlank
  381. text += @currentLine[indentation..]
  382. else
  383. text += matches[1]
  384. # newline only if not EOF
  385. if notEOF = @moveToNextLine()
  386. text += "\n"
  387. isCurrentLineBlank = @isCurrentLineBlank()
  388. else if notEOF
  389. text += "\n"
  390. if notEOF
  391. @moveToPreviousLine()
  392. # Remove line breaks of each lines except the empty and more indented ones
  393. if '>' is separator
  394. newText = ''
  395. for line in text.split "\n"
  396. if line.length is 0 or line.charAt(0) is ' '
  397. newText = Utils.rtrim(newText, ' ') + line + "\n"
  398. else
  399. newText += line + ' '
  400. text = newText
  401. if '+' isnt indicator
  402. # Remove any extra space or new line as we are adding them after
  403. text = Utils.rtrim(text)
  404. # Deal with trailing newlines as indicated
  405. if '' is indicator
  406. text = @PATTERN_TRAILING_LINES.replace text, "\n"
  407. else if '-' is indicator
  408. text = @PATTERN_TRAILING_LINES.replace text, ''
  409. return text
  410. # Returns true if the next line is indented.
  411. #
  412. # @return [Boolean] Returns true if the next line is indented, false otherwise
  413. #
  414. isNextLineIndented: (ignoreComments = true) ->
  415. currentIndentation = @getCurrentLineIndentation()
  416. EOF = not @moveToNextLine()
  417. if ignoreComments
  418. while not(EOF) and @isCurrentLineEmpty()
  419. EOF = not @moveToNextLine()
  420. else
  421. while not(EOF) and @isCurrentLineBlank()
  422. EOF = not @moveToNextLine()
  423. if EOF
  424. return false
  425. ret = false
  426. if @getCurrentLineIndentation() > currentIndentation
  427. ret = true
  428. @moveToPreviousLine()
  429. return ret
  430. # Returns true if the current line is blank or if it is a comment line.
  431. #
  432. # @return [Boolean] Returns true if the current line is empty or if it is a comment line, false otherwise
  433. #
  434. isCurrentLineEmpty: ->
  435. trimmedLine = Utils.trim(@currentLine, ' ')
  436. return trimmedLine.length is 0 or trimmedLine.charAt(0) is '#'
  437. # Returns true if the current line is blank.
  438. #
  439. # @return [Boolean] Returns true if the current line is blank, false otherwise
  440. #
  441. isCurrentLineBlank: ->
  442. return '' is Utils.trim(@currentLine, ' ')
  443. # Returns true if the current line is a comment line.
  444. #
  445. # @return [Boolean] Returns true if the current line is a comment line, false otherwise
  446. #
  447. isCurrentLineComment: ->
  448. # Checking explicitly the first char of the trim is faster than loops or strpos
  449. ltrimmedLine = Utils.ltrim(@currentLine, ' ')
  450. return ltrimmedLine.charAt(0) is '#'
  451. # Cleanups a YAML string to be parsed.
  452. #
  453. # @param [String] value The input YAML string
  454. #
  455. # @return [String] A cleaned up YAML string
  456. #
  457. cleanup: (value) ->
  458. if value.indexOf("\r") isnt -1
  459. value = value.split("\r\n").join("\n").split("\r").join("\n")
  460. # Strip YAML header
  461. count = 0
  462. [value, count] = @PATTERN_YAML_HEADER.replaceAll value, ''
  463. @offset += count
  464. # Remove leading comments
  465. [trimmedValue, count] = @PATTERN_LEADING_COMMENTS.replaceAll value, '', 1
  466. if count is 1
  467. # Items have been removed, update the offset
  468. @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n")
  469. value = trimmedValue
  470. # Remove start of the document marker (---)
  471. [trimmedValue, count] = @PATTERN_DOCUMENT_MARKER_START.replaceAll value, '', 1
  472. if count is 1
  473. # Items have been removed, update the offset
  474. @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n")
  475. value = trimmedValue
  476. # Remove end of the document marker (...)
  477. value = @PATTERN_DOCUMENT_MARKER_END.replace value, ''
  478. # Ensure the block is not indented
  479. lines = value.split("\n")
  480. smallestIndent = -1
  481. for line in lines
  482. continue if Utils.trim(line, ' ').length == 0
  483. indent = line.length - Utils.ltrim(line).length
  484. if smallestIndent is -1 or indent < smallestIndent
  485. smallestIndent = indent
  486. if smallestIndent > 0
  487. for line, i in lines
  488. lines[i] = line[smallestIndent..]
  489. value = lines.join("\n")
  490. return value
  491. # Returns true if the next line starts unindented collection
  492. #
  493. # @return [Boolean] Returns true if the next line starts unindented collection, false otherwise
  494. #
  495. isNextLineUnIndentedCollection: (currentIndentation = null) ->
  496. currentIndentation ?= @getCurrentLineIndentation()
  497. notEOF = @moveToNextLine()
  498. while notEOF and @isCurrentLineEmpty()
  499. notEOF = @moveToNextLine()
  500. if false is notEOF
  501. return false
  502. ret = false
  503. if @getCurrentLineIndentation() is currentIndentation and @isStringUnIndentedCollectionItem(@currentLine)
  504. ret = true
  505. @moveToPreviousLine()
  506. return ret
  507. # Returns true if the string is un-indented collection item
  508. #
  509. # @return [Boolean] Returns true if the string is un-indented collection item, false otherwise
  510. #
  511. isStringUnIndentedCollectionItem: ->
  512. return @currentLine is '-' or @currentLine[0...2] is '- '
  513. module.exports = Parser