yaml.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. // YAML - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
  2. /**
  3. * Version triplet.
  4. */
  5. exports.version = '0.2.1'
  6. // --- Helpers
  7. /**
  8. * Return 'near "context"' where context
  9. * is replaced by a chunk of _str_.
  10. *
  11. * @param {string} str
  12. * @return {string}
  13. * @api public
  14. */
  15. function context(str) {
  16. if (typeof str !== 'string') return ''
  17. str = str
  18. .slice(0, 25)
  19. .replace(/\n/g, '\\n')
  20. .replace(/"/g, '\\\"')
  21. return 'near "' + str + '"'
  22. }
  23. // --- Lexer
  24. /**
  25. * YAML grammar tokens.
  26. */
  27. var tokens = [
  28. ['comment', /^#[^\n]*/],
  29. ['indent', /^\n( *)/],
  30. ['space', /^ +/],
  31. ['true', /^\b(enabled|true|yes|on)\b/],
  32. ['false', /^\b(disabled|false|no|off)\b/],
  33. ['string', /^"(.*?)"/],
  34. ['string', /^'(.*?)'/],
  35. ['timestamp', /^((\d{4})-(\d\d?)-(\d\d?)(?:(?:[ \t]+)(\d\d?):(\d\d)(?::(\d\d))?)?)/],
  36. ['float', /^(\d+\.\d+)/],
  37. ['int', /^(\d+)/],
  38. ['doc', /^---/],
  39. [',', /^,/],
  40. ['{', /^\{(?![^\n\}]*\}[^\n]*[^\s\n\}])/],
  41. ['}', /^\}/],
  42. ['[', /^\[(?![^\n\]]*\][^\n]*[^\s\n\]])/],
  43. [']', /^\]/],
  44. ['-', /^\-/],
  45. [':', /^[:]/],
  46. ['string', /^(?![^:\n\s]*:[^\/]{2})(([^:,\]\}\n\s]|(?!\n)\s(?!\s*?\n)|:\/\/|,(?=[^\n]*\s*[^\]\}\s\n]\s*\n)|[\]\}](?=[^\n]*\s*[^\]\}\s\n]\s*\n))*)(?=[,:\]\}\s\n]|$)/],
  47. ['id', /^([\w ]+)/]
  48. ]
  49. /**
  50. * Tokenize the given _str_.
  51. *
  52. * @param {string} str
  53. * @return {array}
  54. * @api private
  55. */
  56. exports.tokenize = function (str) {
  57. var token, captures, ignore, input,
  58. indents = lastIndents = 0,
  59. stack = []
  60. while (str.length) {
  61. for (var i = 0, len = tokens.length; i < len; ++i)
  62. if (captures = tokens[i][1].exec(str)) {
  63. token = [tokens[i][0], captures],
  64. str = str.replace(tokens[i][1], '')
  65. switch (token[0]) {
  66. case 'comment':
  67. ignore = true
  68. break
  69. case 'indent':
  70. lastIndents = indents
  71. indents = token[1][1].length / 2
  72. if (indents === lastIndents)
  73. ignore = true
  74. else if (indents > lastIndents + 1)
  75. throw new SyntaxError('invalid indentation, got ' + indents + ' instead of ' + (lastIndents + 1))
  76. else if (indents < lastIndents) {
  77. input = token[1].input
  78. token = ['dedent']
  79. token.input = input
  80. while (--lastIndents > indents)
  81. stack.push(token)
  82. }
  83. }
  84. break
  85. }
  86. if (!ignore)
  87. if (token)
  88. stack.push(token),
  89. token = null
  90. else
  91. throw new SyntaxError(context(str))
  92. ignore = false
  93. }
  94. return stack
  95. }
  96. // --- Parser
  97. /**
  98. * Initialize with _tokens_.
  99. */
  100. function Parser(tokens) {
  101. this.tokens = tokens
  102. }
  103. /**
  104. * Look-ahead a single token.
  105. *
  106. * @return {array}
  107. * @api public
  108. */
  109. Parser.prototype.peek = function() {
  110. return this.tokens[0]
  111. }
  112. /**
  113. * Advance by a single token.
  114. *
  115. * @return {array}
  116. * @api public
  117. */
  118. Parser.prototype.advance = function() {
  119. return this.tokens.shift()
  120. }
  121. /**
  122. * Advance and return the token's value.
  123. *
  124. * @return {mixed}
  125. * @api private
  126. */
  127. Parser.prototype.advanceValue = function() {
  128. return this.advance()[1][1]
  129. }
  130. /**
  131. * Accept _type_ and advance or do nothing.
  132. *
  133. * @param {string} type
  134. * @return {bool}
  135. * @api private
  136. */
  137. Parser.prototype.accept = function(type) {
  138. if (this.peekType(type))
  139. return this.advance()
  140. }
  141. /**
  142. * Expect _type_ or throw an error _msg_.
  143. *
  144. * @param {string} type
  145. * @param {string} msg
  146. * @api private
  147. */
  148. Parser.prototype.expect = function(type, msg) {
  149. if (this.accept(type)) return
  150. throw new Error(msg + ', ' + context(this.peek()[1].input))
  151. }
  152. /**
  153. * Return the next token type.
  154. *
  155. * @return {string}
  156. * @api private
  157. */
  158. Parser.prototype.peekType = function(val) {
  159. return this.tokens[0] &&
  160. this.tokens[0][0] === val
  161. }
  162. /**
  163. * space*
  164. */
  165. Parser.prototype.ignoreSpace = function() {
  166. while (this.peekType('space'))
  167. this.advance()
  168. }
  169. /**
  170. * (space | indent | dedent)*
  171. */
  172. Parser.prototype.ignoreWhitespace = function() {
  173. while (this.peekType('space') ||
  174. this.peekType('indent') ||
  175. this.peekType('dedent'))
  176. this.advance()
  177. }
  178. /**
  179. * block
  180. * | doc
  181. * | list
  182. * | inlineList
  183. * | hash
  184. * | inlineHash
  185. * | string
  186. * | float
  187. * | int
  188. * | true
  189. * | false
  190. */
  191. Parser.prototype.parse = function() {
  192. switch (this.peek()[0]) {
  193. case 'doc':
  194. return this.parseDoc()
  195. case '-':
  196. return this.parseList()
  197. case '{':
  198. return this.parseInlineHash()
  199. case '[':
  200. return this.parseInlineList()
  201. case 'id':
  202. return this.parseHash()
  203. case 'string':
  204. return this.advanceValue()
  205. case 'timestamp':
  206. return this.parseTimestamp()
  207. case 'float':
  208. return parseFloat(this.advanceValue())
  209. case 'int':
  210. return parseInt(this.advanceValue())
  211. case 'true':
  212. this.advanceValue(); return true
  213. case 'false':
  214. this.advanceValue(); return false
  215. }
  216. }
  217. /**
  218. * '---'? indent expr dedent
  219. */
  220. Parser.prototype.parseDoc = function() {
  221. this.accept('doc')
  222. this.expect('indent', 'expected indent after document')
  223. var val = this.parse()
  224. this.expect('dedent', 'document not properly dedented')
  225. return val
  226. }
  227. /**
  228. * ( id ':' - expr -
  229. * | id ':' - indent expr dedent
  230. * )+
  231. */
  232. Parser.prototype.parseHash = function() {
  233. var id, hash = {}
  234. while (this.peekType('id') && (id = this.advanceValue())) {
  235. this.expect(':', 'expected semi-colon after id')
  236. this.ignoreSpace()
  237. if (this.accept('indent'))
  238. hash[id] = this.parse(),
  239. this.expect('dedent', 'hash not properly dedented')
  240. else
  241. hash[id] = this.parse()
  242. this.ignoreSpace()
  243. }
  244. return hash
  245. }
  246. /**
  247. * '{' (- ','? ws id ':' - expr ws)* '}'
  248. */
  249. Parser.prototype.parseInlineHash = function() {
  250. var hash = {}, id, i = 0
  251. this.accept('{')
  252. while (!this.accept('}')) {
  253. this.ignoreSpace()
  254. if (i) this.expect(',', 'expected comma')
  255. this.ignoreWhitespace()
  256. if (this.peekType('id') && (id = this.advanceValue())) {
  257. this.expect(':', 'expected semi-colon after id')
  258. this.ignoreSpace()
  259. hash[id] = this.parse()
  260. this.ignoreWhitespace()
  261. }
  262. ++i
  263. }
  264. return hash
  265. }
  266. /**
  267. * ( '-' - expr -
  268. * | '-' - indent expr dedent
  269. * )+
  270. */
  271. Parser.prototype.parseList = function() {
  272. var list = []
  273. while (this.accept('-')) {
  274. this.ignoreSpace()
  275. if (this.accept('indent'))
  276. list.push(this.parse()),
  277. this.expect('dedent', 'list item not properly dedented')
  278. else
  279. list.push(this.parse())
  280. this.ignoreSpace()
  281. }
  282. return list
  283. }
  284. /**
  285. * '[' (- ','? - expr -)* ']'
  286. */
  287. Parser.prototype.parseInlineList = function() {
  288. var list = [], i = 0
  289. this.accept('[')
  290. while (!this.accept(']')) {
  291. this.ignoreSpace()
  292. if (i) this.expect(',', 'expected comma')
  293. this.ignoreSpace()
  294. list.push(this.parse())
  295. this.ignoreSpace()
  296. ++i
  297. }
  298. return list
  299. }
  300. /**
  301. * yyyy-mm-dd hh:mm:ss
  302. *
  303. * For full format: http://yaml.org/type/timestamp.html
  304. */
  305. Parser.prototype.parseTimestamp = function() {
  306. var token = this.advance()[1]
  307. var date = new Date
  308. var year = token[2]
  309. , month = token[3]
  310. , day = token[4]
  311. , hour = token[5] || 0
  312. , min = token[6] || 0
  313. , sec = token[7] || 0
  314. date.setUTCFullYear(year, month-1, day)
  315. date.setUTCHours(hour)
  316. date.setUTCMinutes(min)
  317. date.setUTCSeconds(sec)
  318. return date
  319. }
  320. /**
  321. * Evaluate a _str_ of yaml.
  322. *
  323. * @param {string} str
  324. * @return {mixed}
  325. * @api public
  326. */
  327. exports.eval = function(str) {
  328. return (new Parser(exports.tokenize(str))).parse()
  329. }