ometa BSOMetaParser <: Parser {
  fromTo :x :y   = seq(x) (~seq(y) char)* seq(y), 
  space          = ^space | fromTo('//', '\n') | fromTo('/*', '*/'),
  nameFirst      = '_' | '$' | letter,
  nameRest       = nameFirst | digit,
  tsName         = firstAndRest(#nameFirst, #nameRest):xs              -> xs.join(''),
  name           = spaces tsName,
  eChar          = '\\' char:c                                         -> unescape('\\' +c)
                 | char,
  tsString       = '\'' (~'\'' eChar)*:xs '\''                         -> xs.join(''),
  characters     = '`' '`' (~('\'' '\'') eChar)*:xs '\'' '\''          -> [#App, #seq,     xs.join('').toProgramString()],
  sCharacters    = '"'     (~'"'         eChar)*:xs '"'                -> [#App, #token,   xs.join('').toProgramString()],
  string         = (('#' | '`') tsName | tsString):xs                  -> [#App, #exactly, xs.toProgramString()],
  number         = ('-' | empty -> ''):sign digit+:ds                  -> [#App, #exactly, sign + ds.join('')],
  keyword :xs    = token(xs) ~letterOrDigit                            -> xs,
  args           = '(' listOf(#hostExpr, ','):xs ")"                   -> xs
                 | empty                                               -> [],
  application    = "^"          name:rule args:as                      -> [#App, "super",        "'" + rule + "'"].concat(as)
                 | name:grm "." name:rule args:as                      -> [#App, "foreign", grm, "'" + rule + "'"].concat(as)
                 |              name:rule args:as                      -> [#App, rule].concat(as),
  hostExpr       = BSSemActionParser.expr:r                               BSJSTranslator.trans(r),
  curlyHostExpr  = BSSemActionParser.curlySemAction:r                     BSJSTranslator.trans(r),
  primHostExpr   = BSSemActionParser.semAction:r                          BSJSTranslator.trans(r),
  atomicHostExpr = curlyHostExpr | primHostExpr,
  semAction      = curlyHostExpr:x                                     -> [#Act, x]
                 | "!"  atomicHostExpr:x                               -> [#Act, x],
  arrSemAction   = "->" atomicHostExpr:x                               -> [#Act, x],
  semPred        = "?"  atomicHostExpr:x                               -> [#Pred, x],
  expr           = expr5(true):x ("|"  expr5(true))+:xs                -> [#Or,  x].concat(xs)
                 | expr5(true):x ("||" expr5(true))+:xs                -> [#XOr, x].concat(xs)
                 | expr5(false),
  expr5 :ne      = interleavePart:x ("&&" interleavePart)+:xs          -> [#Interleave, x].concat(xs)
                 | expr4(ne),
  interleavePart = "(" expr4(true):part ")"                            -> ["1", part]
                 | expr4(true):part modedIPart(part),
  modedIPart     = [#And [#Many  :part]]                               -> ["*", part]
                 | [#And [#Many1 :part]]                               -> ["+", part]
                 | [#And [#Opt   :part]]                               -> ["?", part]
                 | :part                                               -> ["1", part],
  expr4 :ne      =                expr3*:xs arrSemAction:act           -> [#And].concat(xs).concat([act])
                 | ?ne            expr3+:xs                            -> [#And].concat(xs)
                 | ?(ne == false) expr3*:xs                            -> [#And].concat(xs),
  optIter :x     = '*'                                                 -> [#Many,  x]
                 | '+'                                                 -> [#Many1, x]
                 | '?'                                                 -> [#Opt,   x]
                 | empty                                               -> x,
  optBind :x     = ':' name:n                                          -> { this.locals.push(n); [#Set, n, x] }
                 | empty                                               -> x,
  expr3          = ":" name:n                                          -> { this.locals.push(n); [#Set, n, [#App, #anything]] }
                 | (expr2:x optIter(x) | semAction):e optBind(e)
                 | semPred,
  expr2          = "~" expr2:x                                         -> [#Not,       x]
                 | "&" expr1:x                                         -> [#Lookahead, x]
                 | expr1,
  expr1          = application 
                 | ( keyword('undefined') | keyword('nil')
                   | keyword('true')      | keyword('false') ):x       -> [#App, #exactly, x]
                 | spaces (characters | sCharacters | string | number)
                 | "["  expr:x "]"                                     -> [#Form,      x]
                 | "<"  expr:x ">"                                     -> [#ConsBy,    x]
                 | "@<" expr:x ">"                                     -> [#IdxConsBy, x]
                 | "("  expr:x ")"                                     -> x,
  ruleName       = name
                 | spaces tsString,
  rule           = &(ruleName:n) !(this.locals = ['$elf=this', '_fromIdx=this.input.idx'])
                     rulePart(n):x ("," rulePart(n))*:xs               -> [#Rule, n, this.locals, [#Or, x].concat(xs)],
  rulePart :rn   = ruleName:n ?(n == rn) expr4:b1 ( "=" expr:b2        -> [#And, b1, b2]
                                                  | empty              -> b1
                                                  ),
  grammar        = keyword('ometa') name:n
                     ( "<:" name | empty -> 'OMeta' ):sn
                     "{" listOf(#rule, ','):rs "}"                        BSOMetaOptimizer.optimizeGrammar(
                                                                            [#Grammar, n, sn].concat(rs)
                                                                          )
}

// By dispatching on the head of a list, the following idiom allows translators to avoid doing a linear search.
// (Note that the "=" in a rule definition is optional, so you can give your rules an "ML feel".)
ometa BSOMetaTranslator {
  App        'super' anything+:args        -> [this.sName, '._superApplyWithArgs(this,', args.join(','), ')']      .join(''),
  App        :rule   anything+:args        -> ['this._applyWithArgs("', rule, '",',      args.join(','), ')']      .join(''),
  App        :rule                         -> ['this._apply("', rule, '")']                                        .join(''),
  Act        :expr                         -> expr,
  Pred       :expr                         -> ['this._pred(', expr, ')']                                           .join(''),
  Or         transFn*:xs                   -> ['this._or(',  xs.join(','), ')']                                    .join(''),
  XOr        transFn*:xs                       {xs.unshift((this.name + "." + this.rName).toProgramString())}
                                           -> ['this._xor(', xs.join(','), ')']                                    .join(''),
  And        notLast(#trans)*:xs trans:y
             {xs.push('return ' + y)}      -> ['(function(){', xs.join(';'), '}).call(this)']                      .join(''),
  And                                      -> 'undefined',
  Opt        transFn:x                     -> ['this._opt(',           x, ')']                                     .join(''),
  Many       transFn:x                     -> ['this._many(',          x, ')']                                     .join(''),
  Many1      transFn:x                     -> ['this._many1(',         x, ')']                                     .join(''),
  Set        :n trans:v                    -> [n, '=', v]                                                          .join(''),
  Not        transFn:x                     -> ['this._not(',           x, ')']                                     .join(''),
  Lookahead  transFn:x                     -> ['this._lookahead(',     x, ')']                                     .join(''),
  Form       transFn:x                     -> ['this._form(',          x, ')']                                     .join(''),
  ConsBy     transFn:x                     -> ['this._consumedBy(',    x, ')']                                     .join(''),
  IdxConsBy  transFn:x                     -> ['this._idxConsumedBy(', x, ')']                                     .join(''),
  JumpTable  jtCase*:cases                 -> this.jumpTableCode(cases),
  Interleave intPart*:xs                   -> ['this._interleave(', xs.join(','), ')']                             .join(''),
  
  Rule       :name {this.rName = name}
             locals:ls trans:body          -> ['\n"', name, '":function(){', ls, 'return ', body, '}']             .join(''),
  Grammar    :name :sName
             {this.name = name}
             {this.sName = sName}
             trans*:rules                  -> [name, '=objectThatDelegatesTo(', sName, ',{', rules.join(','), '})'].join(''),
  intPart  = [:mode transFn:part]          -> (mode.toProgramString()  + "," + part),
  jtCase   = [:x trans:e]                  -> [x.toProgramString(), e],
  locals   = [string+:vs]                  -> ['var ', vs.join(','), ';']                                          .join('')
           | []                            -> '',
  trans    = [:t apply(t):ans]             -> ans,
  transFn  = trans:x                       -> ['(function(){return ', x, '})']                                     .join('')
}
BSOMetaTranslator.jumpTableCode = function(cases) {
  var buf = new StringBuffer()
  buf.nextPutAll("(function(){switch(this._apply('anything')){")
  for (var i = 0; i < cases.length; i += 1)
    buf.nextPutAll("case " + cases[i][0] + ":return " + cases[i][1] + ";")
  buf.nextPutAll("default: throw fail}}).call(this)")
  return buf.contents()
}

