ruby.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. export var conf = {
  6. comments: {
  7. lineComment: '#',
  8. blockComment: ['=begin', '=end']
  9. },
  10. brackets: [
  11. ['(', ')'],
  12. ['{', '}'],
  13. ['[', ']']
  14. ],
  15. autoClosingPairs: [
  16. { open: '{', close: '}' },
  17. { open: '[', close: ']' },
  18. { open: '(', close: ')' },
  19. { open: '"', close: '"' },
  20. { open: "'", close: "'" }
  21. ],
  22. surroundingPairs: [
  23. { open: '{', close: '}' },
  24. { open: '[', close: ']' },
  25. { open: '(', close: ')' },
  26. { open: '"', close: '"' },
  27. { open: "'", close: "'" }
  28. ],
  29. indentationRules: {
  30. increaseIndentPattern: new RegExp('^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|while|case)|([^#]*\\sdo\\b)|([^#]*=\\s*(case|if|unless)))\\b([^#\\{;]|("|\'|/).*\\4)*(#.*)?$'),
  31. decreaseIndentPattern: new RegExp('^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif|when)\\b)')
  32. }
  33. };
  34. /*
  35. * Ruby language definition
  36. *
  37. * Quite a complex language due to elaborate escape sequences
  38. * and quoting of literate strings/regular expressions, and
  39. * an 'end' keyword that does not always apply to modifiers like until and while,
  40. * and a 'do' keyword that sometimes starts a block, but sometimes is part of
  41. * another statement (like 'while').
  42. *
  43. * (1) end blocks:
  44. * 'end' may end declarations like if or until, but sometimes 'if' or 'until'
  45. * are modifiers where there is no 'end'. Also, 'do' sometimes starts a block
  46. * that is ended by 'end', but sometimes it is part of a 'while', 'for', or 'until'
  47. * To do proper brace matching we do some elaborate state manipulation.
  48. * some examples:
  49. *
  50. * until bla do
  51. * work until tired
  52. * list.each do
  53. * something if test
  54. * end
  55. * end
  56. *
  57. * or
  58. *
  59. * if test
  60. * something (if test then x end)
  61. * bar if bla
  62. * end
  63. *
  64. * or, how about using class as a property..
  65. *
  66. * class Test
  67. * def endpoint
  68. * self.class.endpoint || routes
  69. * end
  70. * end
  71. *
  72. * (2) quoting:
  73. * there are many kinds of strings and escape sequences. But also, one can
  74. * start many string-like things as '%qx' where q specifies the kind of string
  75. * (like a command, escape expanded, regular expression, symbol etc.), and x is
  76. * some character and only another 'x' ends the sequence. Except for brackets
  77. * where the closing bracket ends the sequence.. and except for a nested bracket
  78. * inside the string like entity. Also, such strings can contain interpolated
  79. * ruby expressions again (and span multiple lines). Moreover, expanded
  80. * regular expression can also contain comments.
  81. */
  82. export var language = {
  83. tokenPostfix: '.ruby',
  84. keywords: [
  85. '__LINE__',
  86. '__ENCODING__',
  87. '__FILE__',
  88. 'BEGIN',
  89. 'END',
  90. 'alias',
  91. 'and',
  92. 'begin',
  93. 'break',
  94. 'case',
  95. 'class',
  96. 'def',
  97. 'defined?',
  98. 'do',
  99. 'else',
  100. 'elsif',
  101. 'end',
  102. 'ensure',
  103. 'for',
  104. 'false',
  105. 'if',
  106. 'in',
  107. 'module',
  108. 'next',
  109. 'nil',
  110. 'not',
  111. 'or',
  112. 'redo',
  113. 'rescue',
  114. 'retry',
  115. 'return',
  116. 'self',
  117. 'super',
  118. 'then',
  119. 'true',
  120. 'undef',
  121. 'unless',
  122. 'until',
  123. 'when',
  124. 'while',
  125. 'yield'
  126. ],
  127. keywordops: ['::', '..', '...', '?', ':', '=>'],
  128. builtins: [
  129. 'require',
  130. 'public',
  131. 'private',
  132. 'include',
  133. 'extend',
  134. 'attr_reader',
  135. 'protected',
  136. 'private_class_method',
  137. 'protected_class_method',
  138. 'new'
  139. ],
  140. // these are closed by 'end' (if, while and until are handled separately)
  141. declarations: [
  142. 'module',
  143. 'class',
  144. 'def',
  145. 'case',
  146. 'do',
  147. 'begin',
  148. 'for',
  149. 'if',
  150. 'while',
  151. 'until',
  152. 'unless'
  153. ],
  154. linedecls: ['def', 'case', 'do', 'begin', 'for', 'if', 'while', 'until', 'unless'],
  155. operators: [
  156. '^',
  157. '&',
  158. '|',
  159. '<=>',
  160. '==',
  161. '===',
  162. '!~',
  163. '=~',
  164. '>',
  165. '>=',
  166. '<',
  167. '<=',
  168. '<<',
  169. '>>',
  170. '+',
  171. '-',
  172. '*',
  173. '/',
  174. '%',
  175. '**',
  176. '~',
  177. '+@',
  178. '-@',
  179. '[]',
  180. '[]=',
  181. '`',
  182. '+=',
  183. '-=',
  184. '*=',
  185. '**=',
  186. '/=',
  187. '^=',
  188. '%=',
  189. '<<=',
  190. '>>=',
  191. '&=',
  192. '&&=',
  193. '||=',
  194. '|='
  195. ],
  196. brackets: [
  197. { open: '(', close: ')', token: 'delimiter.parenthesis' },
  198. { open: '{', close: '}', token: 'delimiter.curly' },
  199. { open: '[', close: ']', token: 'delimiter.square' }
  200. ],
  201. // we include these common regular expressions
  202. symbols: /[=><!~?:&|+\-*\/\^%\.]+/,
  203. // escape sequences
  204. escape: /(?:[abefnrstv\\"'\n\r]|[0-7]{1,3}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4})/,
  205. escapes: /\\(?:C\-(@escape|.)|c(@escape|.)|@escape)/,
  206. decpart: /\d(_?\d)*/,
  207. decimal: /0|@decpart/,
  208. delim: /[^a-zA-Z0-9\s\n\r]/,
  209. heredelim: /(?:\w+|'[^']*'|"[^"]*"|`[^`]*`)/,
  210. regexpctl: /[(){}\[\]\$\^|\-*+?\.]/,
  211. regexpesc: /\\(?:[AzZbBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})?/,
  212. // The main tokenizer for our languages
  213. tokenizer: {
  214. // Main entry.
  215. // root.<decl> where decl is the current opening declaration (like 'class')
  216. root: [
  217. // identifiers and keywords
  218. // most complexity here is due to matching 'end' correctly with declarations.
  219. // We distinguish a declaration that comes first on a line, versus declarations further on a line (which are most likey modifiers)
  220. [
  221. /^(\s*)([a-z_]\w*[!?=]?)/,
  222. [
  223. 'white',
  224. {
  225. cases: {
  226. 'for|until|while': {
  227. token: 'keyword.$2',
  228. next: '@dodecl.$2'
  229. },
  230. '@declarations': {
  231. token: 'keyword.$2',
  232. next: '@root.$2'
  233. },
  234. end: { token: 'keyword.$S2', next: '@pop' },
  235. '@keywords': 'keyword',
  236. '@builtins': 'predefined',
  237. '@default': 'identifier'
  238. }
  239. }
  240. ]
  241. ],
  242. [
  243. /[a-z_]\w*[!?=]?/,
  244. {
  245. cases: {
  246. 'if|unless|while|until': {
  247. token: 'keyword.$0x',
  248. next: '@modifier.$0x'
  249. },
  250. for: { token: 'keyword.$2', next: '@dodecl.$2' },
  251. '@linedecls': { token: 'keyword.$0', next: '@root.$0' },
  252. end: { token: 'keyword.$S2', next: '@pop' },
  253. '@keywords': 'keyword',
  254. '@builtins': 'predefined',
  255. '@default': 'identifier'
  256. }
  257. }
  258. ],
  259. [/[A-Z][\w]*[!?=]?/, 'constructor.identifier'],
  260. [/\$[\w]*/, 'global.constant'],
  261. [/@[\w]*/, 'namespace.instance.identifier'],
  262. [/@@@[\w]*/, 'namespace.class.identifier'],
  263. // here document
  264. [/<<[-~](@heredelim).*/, { token: 'string.heredoc.delimiter', next: '@heredoc.$1' }],
  265. [
  266. /[ \t\r\n]+<<(@heredelim).*/,
  267. { token: 'string.heredoc.delimiter', next: '@heredoc.$1' }
  268. ],
  269. [/^<<(@heredelim).*/, { token: 'string.heredoc.delimiter', next: '@heredoc.$1' }],
  270. // whitespace
  271. { include: '@whitespace' },
  272. // strings
  273. [/"/, { token: 'string.d.delim', next: '@dstring.d."' }],
  274. [/'/, { token: 'string.sq.delim', next: '@sstring.sq' }],
  275. // % literals. For efficiency, rematch in the 'pstring' state
  276. [/%([rsqxwW]|Q?)/, { token: '@rematch', next: 'pstring' }],
  277. // commands and symbols
  278. [/`/, { token: 'string.x.delim', next: '@dstring.x.`' }],
  279. [/:(\w|[$@])\w*[!?=]?/, 'string.s'],
  280. [/:"/, { token: 'string.s.delim', next: '@dstring.s."' }],
  281. [/:'/, { token: 'string.s.delim', next: '@sstring.s' }],
  282. // regular expressions. Lookahead for a (not escaped) closing forwardslash on the same line
  283. [/\/(?=(\\\/|[^\/\n])+\/)/, { token: 'regexp.delim', next: '@regexp' }],
  284. // delimiters and operators
  285. [/[{}()\[\]]/, '@brackets'],
  286. [
  287. /@symbols/,
  288. {
  289. cases: {
  290. '@keywordops': 'keyword',
  291. '@operators': 'operator',
  292. '@default': ''
  293. }
  294. }
  295. ],
  296. [/[;,]/, 'delimiter'],
  297. // numbers
  298. [/0[xX][0-9a-fA-F](_?[0-9a-fA-F])*/, 'number.hex'],
  299. [/0[_oO][0-7](_?[0-7])*/, 'number.octal'],
  300. [/0[bB][01](_?[01])*/, 'number.binary'],
  301. [/0[dD]@decpart/, 'number'],
  302. [
  303. /@decimal((\.@decpart)?([eE][\-+]?@decpart)?)/,
  304. {
  305. cases: {
  306. $1: 'number.float',
  307. '@default': 'number'
  308. }
  309. }
  310. ]
  311. ],
  312. // used to not treat a 'do' as a block opener if it occurs on the same
  313. // line as a 'do' statement: 'while|until|for'
  314. // dodecl.<decl> where decl is the declarations started, like 'while'
  315. dodecl: [
  316. [/^/, { token: '', switchTo: '@root.$S2' }],
  317. [
  318. /[a-z_]\w*[!?=]?/,
  319. {
  320. cases: {
  321. end: { token: 'keyword.$S2', next: '@pop' },
  322. do: { token: 'keyword', switchTo: '@root.$S2' },
  323. '@linedecls': {
  324. token: '@rematch',
  325. switchTo: '@root.$S2'
  326. },
  327. '@keywords': 'keyword',
  328. '@builtins': 'predefined',
  329. '@default': 'identifier'
  330. }
  331. }
  332. ],
  333. { include: '@root' }
  334. ],
  335. // used to prevent potential modifiers ('if|until|while|unless') to match
  336. // with 'end' keywords.
  337. // modifier.<decl>x where decl is the declaration starter, like 'if'
  338. modifier: [
  339. [/^/, '', '@pop'],
  340. [
  341. /[a-z_]\w*[!?=]?/,
  342. {
  343. cases: {
  344. end: { token: 'keyword.$S2', next: '@pop' },
  345. 'then|else|elsif|do': {
  346. token: 'keyword',
  347. switchTo: '@root.$S2'
  348. },
  349. '@linedecls': {
  350. token: '@rematch',
  351. switchTo: '@root.$S2'
  352. },
  353. '@keywords': 'keyword',
  354. '@builtins': 'predefined',
  355. '@default': 'identifier'
  356. }
  357. }
  358. ],
  359. { include: '@root' }
  360. ],
  361. // single quote strings (also used for symbols)
  362. // sstring.<kind> where kind is 'sq' (single quote) or 's' (symbol)
  363. sstring: [
  364. [/[^\\']+/, 'string.$S2'],
  365. [/\\\\|\\'|\\$/, 'string.$S2.escape'],
  366. [/\\./, 'string.$S2.invalid'],
  367. [/'/, { token: 'string.$S2.delim', next: '@pop' }]
  368. ],
  369. // double quoted "string".
  370. // dstring.<kind>.<delim> where kind is 'd' (double quoted), 'x' (command), or 's' (symbol)
  371. // and delim is the ending delimiter (" or `)
  372. dstring: [
  373. [/[^\\`"#]+/, 'string.$S2'],
  374. [/#/, 'string.$S2.escape', '@interpolated'],
  375. [/\\$/, 'string.$S2.escape'],
  376. [/@escapes/, 'string.$S2.escape'],
  377. [/\\./, 'string.$S2.escape.invalid'],
  378. [
  379. /[`"]/,
  380. {
  381. cases: {
  382. '$#==$S3': { token: 'string.$S2.delim', next: '@pop' },
  383. '@default': 'string.$S2'
  384. }
  385. }
  386. ]
  387. ],
  388. // literal documents
  389. // heredoc.<close> where close is the closing delimiter
  390. heredoc: [
  391. [
  392. /^(\s*)(@heredelim)$/,
  393. {
  394. cases: {
  395. '$2==$S2': [
  396. 'string.heredoc',
  397. { token: 'string.heredoc.delimiter', next: '@pop' }
  398. ],
  399. '@default': ['string.heredoc', 'string.heredoc']
  400. }
  401. }
  402. ],
  403. [/.*/, 'string.heredoc']
  404. ],
  405. // interpolated sequence
  406. interpolated: [
  407. [/\$\w*/, 'global.constant', '@pop'],
  408. [/@\w*/, 'namespace.class.identifier', '@pop'],
  409. [/@@@\w*/, 'namespace.instance.identifier', '@pop'],
  410. [
  411. /[{]/,
  412. {
  413. token: 'string.escape.curly',
  414. switchTo: '@interpolated_compound'
  415. }
  416. ],
  417. ['', '', '@pop'] // just a # is interpreted as a #
  418. ],
  419. // any code
  420. interpolated_compound: [
  421. [/[}]/, { token: 'string.escape.curly', next: '@pop' }],
  422. { include: '@root' }
  423. ],
  424. // %r quoted regexp
  425. // pregexp.<open>.<close> where open/close are the open/close delimiter
  426. pregexp: [
  427. { include: '@whitespace' },
  428. // turns out that you can quote using regex control characters, aargh!
  429. // for example; %r|kgjgaj| is ok (even though | is used for alternation)
  430. // so, we need to match those first
  431. [
  432. /[^\(\{\[\\]/,
  433. {
  434. cases: {
  435. '$#==$S3': { token: 'regexp.delim', next: '@pop' },
  436. '$#==$S2': { token: 'regexp.delim', next: '@push' },
  437. '~[)}\\]]': '@brackets.regexp.escape.control',
  438. '~@regexpctl': 'regexp.escape.control',
  439. '@default': 'regexp'
  440. }
  441. }
  442. ],
  443. { include: '@regexcontrol' }
  444. ],
  445. // We match regular expression quite precisely
  446. regexp: [
  447. { include: '@regexcontrol' },
  448. [/[^\\\/]/, 'regexp'],
  449. ['/[ixmp]*', { token: 'regexp.delim' }, '@pop']
  450. ],
  451. regexcontrol: [
  452. [
  453. /(\{)(\d+(?:,\d*)?)(\})/,
  454. [
  455. '@brackets.regexp.escape.control',
  456. 'regexp.escape.control',
  457. '@brackets.regexp.escape.control'
  458. ]
  459. ],
  460. [
  461. /(\[)(\^?)/,
  462. [
  463. '@brackets.regexp.escape.control',
  464. { token: 'regexp.escape.control', next: '@regexrange' }
  465. ]
  466. ],
  467. [/(\()(\?[:=!])/, ['@brackets.regexp.escape.control', 'regexp.escape.control']],
  468. [/\(\?#/, { token: 'regexp.escape.control', next: '@regexpcomment' }],
  469. [/[()]/, '@brackets.regexp.escape.control'],
  470. [/@regexpctl/, 'regexp.escape.control'],
  471. [/\\$/, 'regexp.escape'],
  472. [/@regexpesc/, 'regexp.escape'],
  473. [/\\\./, 'regexp.invalid'],
  474. [/#/, 'regexp.escape', '@interpolated']
  475. ],
  476. regexrange: [
  477. [/-/, 'regexp.escape.control'],
  478. [/\^/, 'regexp.invalid'],
  479. [/\\$/, 'regexp.escape'],
  480. [/@regexpesc/, 'regexp.escape'],
  481. [/[^\]]/, 'regexp'],
  482. [/\]/, '@brackets.regexp.escape.control', '@pop']
  483. ],
  484. regexpcomment: [
  485. [/[^)]+/, 'comment'],
  486. [/\)/, { token: 'regexp.escape.control', next: '@pop' }]
  487. ],
  488. // % quoted strings
  489. // A bit repetitive since we need to often special case the kind of ending delimiter
  490. pstring: [
  491. [/%([qws])\(/, { token: 'string.$1.delim', switchTo: '@qstring.$1.(.)' }],
  492. [/%([qws])\[/, { token: 'string.$1.delim', switchTo: '@qstring.$1.[.]' }],
  493. [/%([qws])\{/, { token: 'string.$1.delim', switchTo: '@qstring.$1.{.}' }],
  494. [/%([qws])</, { token: 'string.$1.delim', switchTo: '@qstring.$1.<.>' }],
  495. [/%([qws])(@delim)/, { token: 'string.$1.delim', switchTo: '@qstring.$1.$2.$2' }],
  496. [/%r\(/, { token: 'regexp.delim', switchTo: '@pregexp.(.)' }],
  497. [/%r\[/, { token: 'regexp.delim', switchTo: '@pregexp.[.]' }],
  498. [/%r\{/, { token: 'regexp.delim', switchTo: '@pregexp.{.}' }],
  499. [/%r</, { token: 'regexp.delim', switchTo: '@pregexp.<.>' }],
  500. [/%r(@delim)/, { token: 'regexp.delim', switchTo: '@pregexp.$1.$1' }],
  501. [/%(x|W|Q?)\(/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.(.)' }],
  502. [/%(x|W|Q?)\[/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.[.]' }],
  503. [/%(x|W|Q?)\{/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.{.}' }],
  504. [/%(x|W|Q?)</, { token: 'string.$1.delim', switchTo: '@qqstring.$1.<.>' }],
  505. [/%(x|W|Q?)(@delim)/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.$2.$2' }],
  506. [/%([rqwsxW]|Q?)./, { token: 'invalid', next: '@pop' }],
  507. [/./, { token: 'invalid', next: '@pop' }] // recover
  508. ],
  509. // non-expanded quoted string.
  510. // qstring.<kind>.<open>.<close>
  511. // kind = q|w|s (single quote, array, symbol)
  512. // open = open delimiter
  513. // close = close delimiter
  514. qstring: [
  515. [/\\$/, 'string.$S2.escape'],
  516. [/\\./, 'string.$S2.escape'],
  517. [
  518. /./,
  519. {
  520. cases: {
  521. '$#==$S4': { token: 'string.$S2.delim', next: '@pop' },
  522. '$#==$S3': { token: 'string.$S2.delim', next: '@push' },
  523. '@default': 'string.$S2'
  524. }
  525. }
  526. ]
  527. ],
  528. // expanded quoted string.
  529. // qqstring.<kind>.<open>.<close>
  530. // kind = Q|W|x (double quote, array, command)
  531. // open = open delimiter
  532. // close = close delimiter
  533. qqstring: [[/#/, 'string.$S2.escape', '@interpolated'], { include: '@qstring' }],
  534. // whitespace & comments
  535. whitespace: [
  536. [/[ \t\r\n]+/, ''],
  537. [/^\s*=begin\b/, 'comment', '@comment'],
  538. [/#.*$/, 'comment']
  539. ],
  540. comment: [
  541. [/[^=]+/, 'comment'],
  542. [/^\s*=begin\b/, 'comment.invalid'],
  543. [/^\s*=end\b.*/, 'comment', '@pop'],
  544. [/[=]/, 'comment']
  545. ]
  546. }
  547. };