ometa-base.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. /*
  2. Copyright (c) 2007-2010 Alessandro Warth <awarth@cs.ucla.edu>
  3. Permission is hereby granted, free of charge, to any person
  4. obtaining a copy of this software and associated documentation
  5. files (the "Software"), to deal in the Software without
  6. restriction, including without limitation the rights to use,
  7. copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the
  9. Software is furnished to do so, subject to the following
  10. conditions:
  11. The above copyright notice and this permission notice shall be
  12. included in all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  14. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  15. OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  16. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  17. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  18. WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  20. OTHER DEALINGS IN THE SOFTWARE.
  21. */
  22. /*
  23. new syntax:
  24. #foo and `foo match the string object 'foo' (it's also accepted in my JS)
  25. 'abc' match the string object 'abc'
  26. 'c' match the string object 'c'
  27. ``abc'' match the sequence of string objects 'a', 'b', 'c'
  28. "abc" token('abc')
  29. [1 2 3] match the array object [1, 2, 3]
  30. foo(bar) apply rule foo with argument bar
  31. -> ... semantic actions written in JS (see OMetaParser's atomicHostExpr rule)
  32. */
  33. /*
  34. ometa M {
  35. number = number:n digit:d -> { n * 10 + d.digitValue() }
  36. | digit:d -> { d.digitValue() }
  37. }
  38. translates to...
  39. M = objectThatDelegatesTo(OMeta, {
  40. number: function() {
  41. return this._or(function() {
  42. var n = this._apply("number"),
  43. d = this._apply("digit")
  44. return n * 10 + d.digitValue()
  45. },
  46. function() {
  47. var d = this._apply("digit")
  48. return d.digitValue()
  49. }
  50. )
  51. }
  52. })
  53. M.matchAll("123456789", "number")
  54. */
  55. // the failure exception
  56. fail = { toString: function() { return "match failed" } }
  57. // streams and memoization
  58. function OMInputStream(hd, tl) {
  59. this.memo = { }
  60. this.lst = tl.lst
  61. this.idx = tl.idx
  62. this.hd = hd
  63. this.tl = tl
  64. }
  65. OMInputStream.prototype.head = function() { return this.hd }
  66. OMInputStream.prototype.tail = function() { return this.tl }
  67. OMInputStream.prototype.type = function() { return this.lst.constructor }
  68. OMInputStream.prototype.upTo = function(that) {
  69. var r = [], curr = this
  70. while (curr != that) {
  71. r.push(curr.head())
  72. curr = curr.tail()
  73. }
  74. return this.type() == String ? r.join('') : r
  75. }
  76. function OMInputStreamEnd(lst, idx) {
  77. this.memo = { }
  78. this.lst = lst
  79. this.idx = idx
  80. }
  81. OMInputStreamEnd.prototype = objectThatDelegatesTo(OMInputStream.prototype)
  82. OMInputStreamEnd.prototype.head = function() { throw fail }
  83. OMInputStreamEnd.prototype.tail = function() { throw fail }
  84. // This is necessary b/c in IE, you can't say "foo"[idx]
  85. Array.prototype.at = function(idx) { return this[idx] }
  86. String.prototype.at = String.prototype.charAt
  87. function ListOMInputStream(lst, idx) {
  88. this.memo = { }
  89. this.lst = lst
  90. this.idx = idx
  91. this.hd = lst.at(idx)
  92. }
  93. ListOMInputStream.prototype = objectThatDelegatesTo(OMInputStream.prototype)
  94. ListOMInputStream.prototype.head = function() { return this.hd }
  95. ListOMInputStream.prototype.tail = function() { return this.tl || (this.tl = makeListOMInputStream(this.lst, this.idx + 1)) }
  96. function makeListOMInputStream(lst, idx) { return new (idx < lst.length ? ListOMInputStream : OMInputStreamEnd)(lst, idx) }
  97. Array.prototype.toOMInputStream = function() { return makeListOMInputStream(this, 0) }
  98. String.prototype.toOMInputStream = function() { return makeListOMInputStream(this, 0) }
  99. function makeOMInputStreamProxy(target) {
  100. return objectThatDelegatesTo(target, {
  101. memo: { },
  102. target: target,
  103. tail: function() { return makeOMInputStreamProxy(target.tail()) }
  104. })
  105. }
  106. // Failer (i.e., that which makes things fail) is used to detect (direct) left recursion and memoize failures
  107. function Failer() { }
  108. Failer.prototype.used = false
  109. // the OMeta "class" and basic functionality
  110. OMeta = {
  111. _apply: function(rule) {
  112. var memoRec = this.input.memo[rule]
  113. if (memoRec == undefined) {
  114. var origInput = this.input,
  115. failer = new Failer()
  116. if (this[rule] === undefined)
  117. throw 'tried to apply undefined rule "' + rule + '"'
  118. this.input.memo[rule] = failer
  119. this.input.memo[rule] = memoRec = {ans: this[rule].call(this), nextInput: this.input}
  120. if (failer.used) {
  121. var sentinel = this.input
  122. while (true) {
  123. try {
  124. this.input = origInput
  125. var ans = this[rule].call(this)
  126. if (this.input == sentinel)
  127. throw fail
  128. memoRec.ans = ans
  129. memoRec.nextInput = this.input
  130. }
  131. catch (f) {
  132. if (f != fail)
  133. throw f
  134. break
  135. }
  136. }
  137. }
  138. }
  139. else if (memoRec instanceof Failer) {
  140. memoRec.used = true
  141. throw fail
  142. }
  143. this.input = memoRec.nextInput
  144. return memoRec.ans
  145. },
  146. // note: _applyWithArgs and _superApplyWithArgs are not memoized, so they can't be left-recursive
  147. _applyWithArgs: function(rule) {
  148. for (var idx = arguments.length - 1; idx > 0; idx--)
  149. this._prependInput(arguments[idx])
  150. return this[rule].call(this)
  151. },
  152. _superApplyWithArgs: function(recv, rule) {
  153. for (var idx = arguments.length - 1; idx > 1; idx--)
  154. recv._prependInput(arguments[idx])
  155. return this[rule].call(recv)
  156. },
  157. _prependInput: function(v) {
  158. this.input = new OMInputStream(v, this.input)
  159. },
  160. // if you want your grammar (and its subgrammars) to memoize parameterized rules, invoke this method on it:
  161. memoizeParameterizedRules: function() {
  162. this._prependInput = function(v) {
  163. var newInput
  164. if (isImmutable(v)) {
  165. newInput = this.input[getTag(v)]
  166. if (!newInput) {
  167. newInput = new OMInputStream(v, this.input)
  168. this.input[getTag(v)] = newInput
  169. }
  170. }
  171. else newInput = new OMInputStream(v, this.input)
  172. this.input = newInput
  173. }
  174. this._applyWithArgs = function(rule) {
  175. for (var idx = arguments.length - 1; idx > 0; idx--)
  176. this._prependInput(arguments[idx])
  177. return this._apply(rule)
  178. }
  179. },
  180. _pred: function(b) {
  181. if (b)
  182. return true
  183. throw fail
  184. },
  185. _not: function(x) {
  186. var origInput = this.input
  187. try { x.call(this) }
  188. catch (f) {
  189. if (f != fail)
  190. throw f
  191. this.input = origInput
  192. return true
  193. }
  194. throw fail
  195. },
  196. _lookahead: function(x) {
  197. var origInput = this.input,
  198. r = x.call(this)
  199. this.input = origInput
  200. return r
  201. },
  202. _or: function() {
  203. var origInput = this.input
  204. for (var idx = 0; idx < arguments.length; idx++)
  205. try { this.input = origInput; return arguments[idx].call(this) }
  206. catch (f) {
  207. if (f != fail)
  208. throw f
  209. }
  210. throw fail
  211. },
  212. _xor: function(ruleName) {
  213. var origInput = this.input, idx = 1, newInput, ans
  214. while (idx < arguments.length) {
  215. try {
  216. this.input = origInput
  217. ans = arguments[idx].call(this)
  218. if (newInput)
  219. throw 'more than one choice matched by "exclusive-OR" in ' + ruleName
  220. newInput = this.input
  221. }
  222. catch (f) {
  223. if (f != fail)
  224. throw f
  225. }
  226. idx++
  227. }
  228. if (newInput) {
  229. this.input = newInput
  230. return ans
  231. }
  232. else
  233. throw fail
  234. },
  235. disableXORs: function() {
  236. this._xor = function(ruleName) {
  237. var origInput = this.input
  238. for (var idx = 1; idx < arguments.length; idx++)
  239. try { this.input = origInput; return arguments[idx].call(this) }
  240. catch (f) {
  241. if (f != fail)
  242. throw f
  243. }
  244. throw fail
  245. }
  246. },
  247. _opt: function(x) {
  248. var origInput = this.input, ans
  249. try { ans = x.call(this) }
  250. catch (f) {
  251. if (f != fail)
  252. throw f
  253. this.input = origInput
  254. }
  255. return ans
  256. },
  257. _many: function(x) {
  258. var ans = arguments[1] != undefined ? [arguments[1]] : []
  259. while (true) {
  260. var origInput = this.input
  261. try { ans.push(x.call(this)) }
  262. catch (f) {
  263. if (f != fail)
  264. throw f
  265. this.input = origInput
  266. break
  267. }
  268. }
  269. return ans
  270. },
  271. _many1: function(x) { return this._many(x, x.call(this)) },
  272. _form: function(x) {
  273. var v = this._apply("anything")
  274. if (!isSequenceable(v))
  275. throw fail
  276. var origInput = this.input
  277. this.input = v.toOMInputStream()
  278. var r = x.call(this)
  279. this._apply("end")
  280. this.input = origInput
  281. return v
  282. },
  283. _consumedBy: function(x) {
  284. var origInput = this.input
  285. x.call(this)
  286. return origInput.upTo(this.input)
  287. },
  288. _idxConsumedBy: function(x) {
  289. var origInput = this.input
  290. x.call(this)
  291. return {fromIdx: origInput.idx, toIdx: this.input.idx}
  292. },
  293. _interleave: function(mode1, part1, mode2, part2 /* ..., moden, partn */) {
  294. var currInput = this.input, ans = []
  295. for (var idx = 0; idx < arguments.length; idx += 2)
  296. ans[idx / 2] = (arguments[idx] == "*" || arguments[idx] == "+") ? [] : undefined
  297. while (true) {
  298. var idx = 0, allDone = true
  299. while (idx < arguments.length) {
  300. if (arguments[idx] != "0")
  301. try {
  302. this.input = currInput
  303. switch (arguments[idx]) {
  304. case "*": ans[idx / 2].push(arguments[idx + 1].call(this)); break
  305. case "+": ans[idx / 2].push(arguments[idx + 1].call(this)); arguments[idx] = "*"; break
  306. case "?": ans[idx / 2] = arguments[idx + 1].call(this); arguments[idx] = "0"; break
  307. case "1": ans[idx / 2] = arguments[idx + 1].call(this); arguments[idx] = "0"; break
  308. default: throw "invalid mode '" + arguments[idx] + "' in OMeta._interleave"
  309. }
  310. currInput = this.input
  311. break
  312. }
  313. catch (f) {
  314. if (f != fail)
  315. throw f
  316. // if this (failed) part's mode is "1" or "+", we're not done yet
  317. allDone = allDone && (arguments[idx] == "*" || arguments[idx] == "?")
  318. }
  319. idx += 2
  320. }
  321. if (idx == arguments.length) {
  322. if (allDone)
  323. return ans
  324. else
  325. throw fail
  326. }
  327. }
  328. },
  329. _currIdx: function() { return this.input.idx },
  330. // some basic rules
  331. anything: function() {
  332. var r = this.input.head()
  333. this.input = this.input.tail()
  334. return r
  335. },
  336. end: function() {
  337. return this._not(function() { return this._apply("anything") })
  338. },
  339. pos: function() {
  340. return this.input.idx
  341. },
  342. empty: function() { return true },
  343. apply: function() {
  344. var r = this._apply("anything")
  345. return this._apply(r)
  346. },
  347. foreign: function() {
  348. var g = this._apply("anything"),
  349. r = this._apply("anything"),
  350. gi = objectThatDelegatesTo(g, {input: makeOMInputStreamProxy(this.input)})
  351. gi.initialize();
  352. var ans = gi._apply(r)
  353. this.input = gi.input.target
  354. return ans
  355. },
  356. // some useful "derived" rules
  357. exactly: function() {
  358. var wanted = this._apply("anything")
  359. if (wanted === this._apply("anything"))
  360. return wanted
  361. throw fail
  362. },
  363. "true": function() {
  364. var r = this._apply("anything")
  365. this._pred(r === true)
  366. return r
  367. },
  368. "false": function() {
  369. var r = this._apply("anything")
  370. this._pred(r === false)
  371. return r
  372. },
  373. "undefined": function() {
  374. var r = this._apply("anything")
  375. this._pred(r === undefined)
  376. return r
  377. },
  378. number: function() {
  379. var r = this._apply("anything")
  380. this._pred(typeof r === "number")
  381. return r
  382. },
  383. string: function() {
  384. var r = this._apply("anything")
  385. this._pred(typeof r === "string")
  386. return r
  387. },
  388. "char": function() {
  389. var r = this._apply("anything")
  390. this._pred(typeof r === "string" && r.length == 1)
  391. return r
  392. },
  393. space: function() {
  394. var r = this._apply("char")
  395. this._pred(r.charCodeAt(0) <= 32)
  396. return r
  397. },
  398. spaces: function() {
  399. return this._many(function() { return this._apply("space") })
  400. },
  401. digit: function() {
  402. var r = this._apply("char")
  403. this._pred(r >= "0" && r <= "9")
  404. return r
  405. },
  406. lower: function() {
  407. var r = this._apply("char")
  408. this._pred(r >= "a" && r <= "z")
  409. return r
  410. },
  411. upper: function() {
  412. var r = this._apply("char")
  413. this._pred(r >= "A" && r <= "Z")
  414. return r
  415. },
  416. letter: function() {
  417. return this._or(function() { return this._apply("lower") },
  418. function() { return this._apply("upper") })
  419. },
  420. letterOrDigit: function() {
  421. return this._or(function() { return this._apply("letter") },
  422. function() { return this._apply("digit") })
  423. },
  424. firstAndRest: function() {
  425. var first = this._apply("anything"),
  426. rest = this._apply("anything")
  427. return this._many(function() { return this._apply(rest) }, this._apply(first))
  428. },
  429. seq: function() {
  430. var xs = this._apply("anything")
  431. for (var idx = 0; idx < xs.length; idx++)
  432. this._applyWithArgs("exactly", xs.at(idx))
  433. return xs
  434. },
  435. notLast: function() {
  436. var rule = this._apply("anything"),
  437. r = this._apply(rule)
  438. this._lookahead(function() { return this._apply(rule) })
  439. return r
  440. },
  441. initialize: function() { },
  442. // match and matchAll are a grammar's "public interface"
  443. _genericMatch: function(input, rule, args, matchFailed) {
  444. if (args == undefined)
  445. args = []
  446. var realArgs = [rule]
  447. for (var idx = 0; idx < args.length; idx++)
  448. realArgs.push(args[idx])
  449. var m = objectThatDelegatesTo(this, {input: input})
  450. m.initialize()
  451. try { return realArgs.length == 1 ? m._apply.call(m, realArgs[0]) : m._applyWithArgs.apply(m, realArgs) }
  452. catch (f) {
  453. if (f == fail && matchFailed != undefined) {
  454. var input = m.input
  455. if (input.idx != undefined) {
  456. while (input.tl != undefined && input.tl.idx != undefined)
  457. input = input.tl
  458. input.idx--
  459. }
  460. return matchFailed(m, input.idx)
  461. }
  462. throw f
  463. }
  464. },
  465. match: function(obj, rule, args, matchFailed) {
  466. return this._genericMatch([obj].toOMInputStream(), rule, args, matchFailed)
  467. },
  468. matchAll: function(listyObj, rule, args, matchFailed) {
  469. return this._genericMatch(listyObj.toOMInputStream(), rule, args, matchFailed)
  470. },
  471. createInstance: function() {
  472. var m = objectThatDelegatesTo(this)
  473. m.initialize()
  474. m.matchAll = function(listyObj, aRule) {
  475. m.input = listyObj.toOMInputStream()
  476. return m._apply(aRule)
  477. }
  478. return m
  479. }
  480. }