'use strict';

/* eslint-env node */

let es5 = require('ohm-js/examples/ecmascript/es5');
let fs = require('fs');
let ohm = require('ohm-js');
let path = require('path');

const GRAMMAR_PATH = path.join(__dirname, 'joinjs.ohm');

let grammar = ohm.grammar(fs.readFileSync(GRAMMAR_PATH), {ES5: es5.grammar});
let semantics = grammar.extendSemantics(es5.semantics);

// Implement `modifiedSource`, which returns the JavaScript source code for
// any new language constructs.
/* eslint-disable camelcase */
semantics.extendAttribute('modifiedSource', {
  Declaration_channels(asynckw, channelkw, ids, _) {
    let ctor = asynckw.numChildren > 0 ? 'AsyncChannel()' : 'Channel()';
    let initializers = ids.ast.map(id => id + ' = joins.' + ctor);
    return 'var ' + initializers.join(', ') + ';';
  },
  Declaration_join(_, defs, body) {
    let chans = defs.ast;
    let src = 'joins.when(' + chans[0].id + ')';
    let paramList = chans[0].params;
    chans.slice(1).forEach(c => {
      c.params.forEach(p => {
        if (paramList.indexOf(p) >= 0) {
          throw new Error("Duplicate parameter '" + p + "' in join pattern");
        }
      });
      paramList.push.apply(paramList, c.params);
      src += '.and(' + c.id + ')';
    });
    src += '.do(function(' + paramList.join(', ') + ') ' + body.asES5 + ');';
    return src;
  }
});

// A helper attribute used by our implementation of `modifiedSource`.
semantics.addAttribute('ast', {
  NonemptyListOf(first, _, rest) {
    return [first.ast].concat(rest.ast);
  },
  EmptyListOf() {
    return [];
  },
  ChannelDefinition(id, open, params, close) {
    return {
      id: id.ast,
      params: params.ast
    };
  },
  FormalParameterList_many(first, rest) {
    return [first.ast].concat(rest.ast);
  },
  FormalParameterList_zero() {
    return [];
  },
  CommaFormalParameter(_, param) {
    return param.ast;
  },
  identifier(_) {
    return this.interval.contents;
  }
});
/* eslint-enable camelcase */

// Parse the JoinJS code given by `src`, and return the ES5 object code.
function translate(src) {
  let r = grammar.match(src);
  if (r.failed()) {
    throw new Error(r.message);
  }
  return semantics(r).asES5;
}

module.exports = { translate, grammar, semantics };

@@ -1445,127 +1445,8 @@
 , %7B%0A
-  NonemptyListOf(first, _, rest) %7B%0A    return %5Bfirst.ast%5D.concat(rest.ast);%0A  %7D,%0A  EmptyListOf() %7B%0A    return %5B%5D;%0A  %7D,%0A
   Ch
@@ -1500,23 +1500,16 @@
 return %7B
-%0A      
 id: id.a
@@ -1511,22 +1511,16 @@
  id.ast,
-%0A     
  params:
@@ -1530,21 +1530,16 @@
 rams.ast
-%0A    
 %7D;%0A  %7D,%0A
@@ -1564,16 +1564,18 @@
 ist_many
+: 
 (first, 
@@ -1580,28 +1580,18 @@
 , rest) 
-%7B%0A    return
+=%3E
  %5Bfirst.
@@ -1611,21 +1611,16 @@
 est.ast)
-;%0A  %7D
 ,%0A  Form
@@ -1643,31 +1643,18 @@
 zero
+: 
 () 
-%7B%0A    return %5B%5D;%0A  %7D
+=%3E %5B%5D
 ,%0A  
@@ -1673,16 +1673,18 @@
 arameter
+: 
 (_, para
@@ -1686,28 +1686,18 @@
  param) 
-%7B%0A    return
+=%3E
  param.a
@@ -1698,21 +1698,16 @@
 aram.ast
-;%0A  %7D
 ,%0A  iden
@@ -1752,24 +1752,117 @@
 ontents;%0A  %7D
+,%0A  NonemptyListOf: (first, _, rest) =%3E %5Bfirst.ast%5D.concat(rest.ast),%0A  EmptyListOf: () =%3E %5B%5D
 %0A%7D);%0A/* esli
@@ -2120,17 +2120,16 @@
 orts = %7B
- 
 translat
@@ -2149,12 +2149,11 @@
 emantics
- 
 %7D;%0A

Use arrow functions to make things a bit terser.