| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 | /*  Copyright (c) 2007-2010 Alessandro Warth <awarth@cs.ucla.edu>  Permission is hereby granted, free of charge, to any person  obtaining a copy of this software and associated documentation  files (the "Software"), to deal in the Software without  restriction, including without limitation the rights to use,  copy, modify, merge, publish, distribute, sublicense, and/or sell  copies of the Software, and to permit persons to whom the  Software is furnished to do so, subject to the following  conditions:  The above copyright notice and this permission notice shall be  included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.*//*  new syntax:    #foo and `foo	match the string object 'foo' (it's also accepted in my JS)    'abc'		match the string object 'abc'    'c'			match the string object 'c'    ``abc''		match the sequence of string objects 'a', 'b', 'c'    "abc"		token('abc')    [1 2 3]		match the array object [1, 2, 3]    foo(bar)		apply rule foo with argument bar    -> ...		semantic actions written in JS (see OMetaParser's atomicHostExpr rule)*//*ometa M {  number = number:n digit:d -> { n * 10 + d.digitValue() }         | digit:d          -> { d.digitValue() }}translates to...M = objectThatDelegatesTo(OMeta, {  number: function() {            return this._or(function() {                              var n = this._apply("number"),                                  d = this._apply("digit")                              return n * 10 + d.digitValue()                            },                            function() {                              var d = this._apply("digit")                              return d.digitValue()                            }                           )          }})M.matchAll("123456789", "number")*/// the failure exceptionfail = { toString: function() { return "match failed" } }// streams and memoizationfunction OMInputStream(hd, tl) {  this.memo = { }  this.lst  = tl.lst  this.idx  = tl.idx  this.hd   = hd  this.tl   = tl}OMInputStream.prototype.head = function() { return this.hd }OMInputStream.prototype.tail = function() { return this.tl }OMInputStream.prototype.type = function() { return this.lst.constructor }OMInputStream.prototype.upTo = function(that) {  var r = [], curr = this  while (curr != that) {    r.push(curr.head())    curr = curr.tail()  }  return this.type() == String ? r.join('') : r}function OMInputStreamEnd(lst, idx) {  this.memo = { }  this.lst = lst  this.idx = idx}OMInputStreamEnd.prototype = objectThatDelegatesTo(OMInputStream.prototype)OMInputStreamEnd.prototype.head = function() { throw fail }OMInputStreamEnd.prototype.tail = function() { throw fail }// This is necessary b/c in IE, you can't say "foo"[idx]Array.prototype.at  = function(idx) { return this[idx] }String.prototype.at = String.prototype.charAtfunction ListOMInputStream(lst, idx) {  this.memo = { }  this.lst  = lst  this.idx  = idx  this.hd   = lst.at(idx)}ListOMInputStream.prototype = objectThatDelegatesTo(OMInputStream.prototype)ListOMInputStream.prototype.head = function() { return this.hd }ListOMInputStream.prototype.tail = function() { return this.tl || (this.tl = makeListOMInputStream(this.lst, this.idx + 1)) }function makeListOMInputStream(lst, idx) { return new (idx < lst.length ? ListOMInputStream : OMInputStreamEnd)(lst, idx) }Array.prototype.toOMInputStream  = function() { return makeListOMInputStream(this, 0) }String.prototype.toOMInputStream = function() { return makeListOMInputStream(this, 0) }function makeOMInputStreamProxy(target) {  return objectThatDelegatesTo(target, {    memo:   { },    target: target,    tail:   function() { return makeOMInputStreamProxy(target.tail()) }  })}// Failer (i.e., that which makes things fail) is used to detect (direct) left recursion and memoize failuresfunction Failer() { }Failer.prototype.used = false// the OMeta "class" and basic functionalityOMeta = {  _apply: function(rule) {    var memoRec = this.input.memo[rule]    if (memoRec == undefined) {      var origInput = this.input,          failer    = new Failer()      if (this[rule] === undefined)        throw 'tried to apply undefined rule "' + rule + '"'      this.input.memo[rule] = failer      this.input.memo[rule] = memoRec = {ans: this[rule].call(this), nextInput: this.input}      if (failer.used) {        var sentinel = this.input        while (true) {          try {            this.input = origInput            var ans = this[rule].call(this)            if (this.input == sentinel)              throw fail            memoRec.ans       = ans            memoRec.nextInput = this.input          }          catch (f) {            if (f != fail)              throw f            break          }        }      }    }    else if (memoRec instanceof Failer) {      memoRec.used = true      throw fail    }    this.input = memoRec.nextInput    return memoRec.ans  },  // note: _applyWithArgs and _superApplyWithArgs are not memoized, so they can't be left-recursive  _applyWithArgs: function(rule) {    for (var idx = arguments.length - 1; idx > 0; idx--)      this._prependInput(arguments[idx])    return this[rule].call(this)  },  _superApplyWithArgs: function(recv, rule) {    for (var idx = arguments.length - 1; idx > 1; idx--)      recv._prependInput(arguments[idx])    return this[rule].call(recv)  },  _prependInput: function(v) {    this.input = new OMInputStream(v, this.input)  },  // if you want your grammar (and its subgrammars) to memoize parameterized rules, invoke this method on it:  memoizeParameterizedRules: function() {    this._prependInput = function(v) {      var newInput      if (isImmutable(v)) {        newInput = this.input[getTag(v)]        if (!newInput) {          newInput = new OMInputStream(v, this.input)          this.input[getTag(v)] = newInput        }      }      else newInput = new OMInputStream(v, this.input)      this.input = newInput    }    this._applyWithArgs = function(rule) {      for (var idx = arguments.length - 1; idx > 0; idx--)        this._prependInput(arguments[idx])      return this._apply(rule)    }  },  _pred: function(b) {    if (b)      return true    throw fail  },  _not: function(x) {    var origInput = this.input    try { x.call(this) }    catch (f) {      if (f != fail)        throw f      this.input = origInput      return true    }    throw fail  },  _lookahead: function(x) {    var origInput = this.input,        r         = x.call(this)    this.input = origInput    return r  },  _or: function() {    var origInput = this.input    for (var idx = 0; idx < arguments.length; idx++)      try { this.input = origInput; return arguments[idx].call(this) }      catch (f) {        if (f != fail)          throw f      }    throw fail  },  _xor: function(ruleName) {    var origInput = this.input, idx = 1, newInput, ans    while (idx < arguments.length) {      try {        this.input = origInput        ans = arguments[idx].call(this)        if (newInput)          throw 'more than one choice matched by "exclusive-OR" in ' + ruleName        newInput = this.input      }      catch (f) {        if (f != fail)          throw f      }      idx++    }    if (newInput) {      this.input = newInput      return ans    }    else      throw fail  },  disableXORs: function() {    this._xor = function(ruleName) {      var origInput = this.input      for (var idx = 1; idx < arguments.length; idx++)        try { this.input = origInput; return arguments[idx].call(this) }        catch (f) {          if (f != fail)            throw f        }      throw fail    }  },  _opt: function(x) {    var origInput = this.input, ans    try { ans = x.call(this) }    catch (f) {      if (f != fail)        throw f      this.input = origInput    }    return ans  },  _many: function(x) {    var ans = arguments[1] != undefined ? [arguments[1]] : []    while (true) {      var origInput = this.input      try { ans.push(x.call(this)) }      catch (f) {        if (f != fail)          throw f        this.input = origInput        break      }    }    return ans  },  _many1: function(x) { return this._many(x, x.call(this)) },  _form: function(x) {    var v = this._apply("anything")    if (!isSequenceable(v))      throw fail    var origInput = this.input    this.input = v.toOMInputStream()    var r = x.call(this)    this._apply("end")    this.input = origInput    return v  },  _consumedBy: function(x) {    var origInput = this.input    x.call(this)    return origInput.upTo(this.input)  },  _idxConsumedBy: function(x) {    var origInput = this.input    x.call(this)    return {fromIdx: origInput.idx, toIdx: this.input.idx}  },  _interleave: function(mode1, part1, mode2, part2 /* ..., moden, partn */) {    var currInput = this.input, ans = []    for (var idx = 0; idx < arguments.length; idx += 2)      ans[idx / 2] = (arguments[idx] == "*" || arguments[idx] == "+") ? [] : undefined    while (true) {      var idx = 0, allDone = true      while (idx < arguments.length) {        if (arguments[idx] != "0")          try {            this.input = currInput            switch (arguments[idx]) {              case "*": ans[idx / 2].push(arguments[idx + 1].call(this));                       break              case "+": ans[idx / 2].push(arguments[idx + 1].call(this)); arguments[idx] = "*"; break              case "?": ans[idx / 2] =    arguments[idx + 1].call(this);  arguments[idx] = "0"; break              case "1": ans[idx / 2] =    arguments[idx + 1].call(this);  arguments[idx] = "0"; break              default:  throw "invalid mode '" + arguments[idx] + "' in OMeta._interleave"            }            currInput = this.input            break          }          catch (f) {            if (f != fail)              throw f            // if this (failed) part's mode is "1" or "+", we're not done yet            allDone = allDone && (arguments[idx] == "*" || arguments[idx] == "?")          }        idx += 2      }      if (idx == arguments.length) {        if (allDone)          return ans        else          throw fail      }    }  },  _currIdx: function() { return this.input.idx },  // some basic rules  anything: function() {    var r = this.input.head()    this.input = this.input.tail()    return r  },  end: function() {    return this._not(function() { return this._apply("anything") })  },  pos: function() {    return this.input.idx  },  empty: function() { return true },  apply: function() {    var r = this._apply("anything")    return this._apply(r)  },  foreign: function() {    var g   = this._apply("anything"),        r   = this._apply("anything"),        gi  = objectThatDelegatesTo(g, {input: makeOMInputStreamProxy(this.input)})        gi.initialize();    var ans = gi._apply(r)    this.input = gi.input.target    return ans  },  //  some useful "derived" rules  exactly: function() {    var wanted = this._apply("anything")    if (wanted === this._apply("anything"))      return wanted    throw fail  },  "true": function() {    var r = this._apply("anything")    this._pred(r === true)    return r  },  "false": function() {    var r = this._apply("anything")    this._pred(r === false)    return r  },  "undefined": function() {    var r = this._apply("anything")    this._pred(r === undefined)    return r  },  number: function() {    var r = this._apply("anything")    this._pred(typeof r === "number")    return r  },  string: function() {    var r = this._apply("anything")    this._pred(typeof r === "string")    return r  },  "char": function() {    var r = this._apply("anything")    this._pred(typeof r === "string" && r.length == 1)    return r  },  space: function() {    var r = this._apply("char")    this._pred(r.charCodeAt(0) <= 32)    return r  },  spaces: function() {    return this._many(function() { return this._apply("space") })  },  digit: function() {    var r = this._apply("char")    this._pred(r >= "0" && r <= "9")    return r  },  lower: function() {    var r = this._apply("char")    this._pred(r >= "a" && r <= "z")    return r  },  upper: function() {    var r = this._apply("char")    this._pred(r >= "A" && r <= "Z")    return r  },  letter: function() {    return this._or(function() { return this._apply("lower") },                    function() { return this._apply("upper") })  },  letterOrDigit: function() {    return this._or(function() { return this._apply("letter") },                    function() { return this._apply("digit")  })  },  firstAndRest: function()  {    var first = this._apply("anything"),        rest  = this._apply("anything")     return this._many(function() { return this._apply(rest) }, this._apply(first))  },  seq: function() {    var xs = this._apply("anything")    for (var idx = 0; idx < xs.length; idx++)      this._applyWithArgs("exactly", xs.at(idx))    return xs  },  notLast: function() {    var rule = this._apply("anything"),        r    = this._apply(rule)    this._lookahead(function() { return this._apply(rule) })    return r  },  initialize: function() { },  // match and matchAll are a grammar's "public interface"  _genericMatch: function(input, rule, args, matchFailed) {    if (args == undefined)      args = []    var realArgs = [rule]    for (var idx = 0; idx < args.length; idx++)      realArgs.push(args[idx])    var m = objectThatDelegatesTo(this, {input: input})    m.initialize()    try { return realArgs.length == 1 ? m._apply.call(m, realArgs[0]) : m._applyWithArgs.apply(m, realArgs) }    catch (f) {      if (f == fail && matchFailed != undefined) {        var input = m.input        if (input.idx != undefined) {          while (input.tl != undefined && input.tl.idx != undefined)            input = input.tl          input.idx--        }        return matchFailed(m, input.idx)      }      throw f    }  },  match: function(obj, rule, args, matchFailed) {    return this._genericMatch([obj].toOMInputStream(),    rule, args, matchFailed)  },  matchAll: function(listyObj, rule, args, matchFailed) {    return this._genericMatch(listyObj.toOMInputStream(), rule, args, matchFailed)  },  createInstance: function() {    var m = objectThatDelegatesTo(this)    m.initialize()    m.matchAll = function(listyObj, aRule) {      m.input = listyObj.toOMInputStream()      return m._apply(aRule)    }    return m  }}
 |