(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ohmExtras = {}));
})(this, (function (exports) { 'use strict';

  // --------------------------------------------------------------------
  // Private Stuff
  // --------------------------------------------------------------------

  // Helpers

  const escapeStringFor = {};
  for (let c = 0; c < 128; c++) {
    escapeStringFor[c] = String.fromCharCode(c);
  }
  escapeStringFor["'".charCodeAt(0)] = "\\'";
  escapeStringFor['"'.charCodeAt(0)] = '\\"';
  escapeStringFor['\\'.charCodeAt(0)] = '\\\\';
  escapeStringFor['\b'.charCodeAt(0)] = '\\b';
  escapeStringFor['\f'.charCodeAt(0)] = '\\f';
  escapeStringFor['\n'.charCodeAt(0)] = '\\n';
  escapeStringFor['\r'.charCodeAt(0)] = '\\r';
  escapeStringFor['\t'.charCodeAt(0)] = '\\t';
  escapeStringFor['\u000b'.charCodeAt(0)] = '\\v';

  // --------------------------------------------------------------------
  // Exports
  // --------------------------------------------------------------------

  function abstract(optMethodName) {
    const methodName = optMethodName || '';
    return function() {
      throw new Error(
          'this method ' +
          methodName +
          ' is abstract! ' +
          '(it has no implementation in class ' +
          this.constructor.name +
          ')',
      );
    };
  }

  function assert(cond, message) {
    if (!cond) {
      throw new Error(message || 'Assertion failed');
    }
  }

  // Define a lazily-computed, non-enumerable property named `propName`
  // on the object `obj`. `getterFn` will be called to compute the value the
  // first time the property is accessed.
  function defineLazyProperty(obj, propName, getterFn) {
    let memo;
    Object.defineProperty(obj, propName, {
      get() {
        if (!memo) {
          memo = getterFn.call(this);
        }
        return memo;
      },
    });
  }

  function clone(obj) {
    if (obj) {
      return Object.assign({}, obj);
    }
    return obj;
  }

  function repeatFn(fn, n) {
    const arr = [];
    while (n-- > 0) {
      arr.push(fn());
    }
    return arr;
  }

  function repeatStr(str, n) {
    return new Array(n + 1).join(str);
  }

  function repeat(x, n) {
    return repeatFn(() => x, n);
  }

  function getDuplicates(array) {
    const duplicates = [];
    for (let idx = 0; idx < array.length; idx++) {
      const x = array[idx];
      if (array.lastIndexOf(x) !== idx && duplicates.indexOf(x) < 0) {
        duplicates.push(x);
      }
    }
    return duplicates;
  }

  function copyWithoutDuplicates(array) {
    const noDuplicates = [];
    array.forEach(entry => {
      if (noDuplicates.indexOf(entry) < 0) {
        noDuplicates.push(entry);
      }
    });
    return noDuplicates;
  }

  function isSyntactic(ruleName) {
    const firstChar = ruleName[0];
    return firstChar === firstChar.toUpperCase();
  }

  function isLexical(ruleName) {
    return !isSyntactic(ruleName);
  }

  function padLeft(str, len, optChar) {
    const ch = optChar || ' ';
    if (str.length < len) {
      return repeatStr(ch, len - str.length) + str;
    }
    return str;
  }

  // StringBuffer

  function StringBuffer() {
    this.strings = [];
  }

  StringBuffer.prototype.append = function(str) {
    this.strings.push(str);
  };

  StringBuffer.prototype.contents = function() {
    return this.strings.join('');
  };

  const escapeUnicode = str => String.fromCodePoint(parseInt(str, 16));

  function unescapeCodePoint(s) {
    if (s.charAt(0) === '\\') {
      switch (s.charAt(1)) {
        case 'b':
          return '\b';
        case 'f':
          return '\f';
        case 'n':
          return '\n';
        case 'r':
          return '\r';
        case 't':
          return '\t';
        case 'v':
          return '\v';
        case 'x':
          return escapeUnicode(s.slice(2, 4));
        case 'u':
          return s.charAt(2) === '{' ?
            escapeUnicode(s.slice(3, -1)) :
            escapeUnicode(s.slice(2, 6));
        default:
          return s.charAt(1);
      }
    } else {
      return s;
    }
  }

  // Helper for producing a description of an unknown object in a safe way.
  // Especially useful for error messages where an unexpected type of object was encountered.
  function unexpectedObjToString(obj) {
    if (obj == null) {
      return String(obj);
    }
    const baseToString = Object.prototype.toString.call(obj);
    try {
      let typeName;
      if (obj.constructor && obj.constructor.name) {
        typeName = obj.constructor.name;
      } else if (baseToString.indexOf('[object ') === 0) {
        typeName = baseToString.slice(8, -1); // Extract e.g. "Array" from "[object Array]".
      } else {
        typeName = typeof obj;
      }
      return typeName + ': ' + JSON.stringify(String(obj));
    } catch (e) {
      return baseToString;
    }
  }

  var common = /*#__PURE__*/Object.freeze({
    __proto__: null,
    abstract: abstract,
    assert: assert,
    defineLazyProperty: defineLazyProperty,
    clone: clone,
    repeatFn: repeatFn,
    repeatStr: repeatStr,
    repeat: repeat,
    getDuplicates: getDuplicates,
    copyWithoutDuplicates: copyWithoutDuplicates,
    isSyntactic: isSyntactic,
    isLexical: isLexical,
    padLeft: padLeft,
    StringBuffer: StringBuffer,
    unescapeCodePoint: unescapeCodePoint,
    unexpectedObjToString: unexpectedObjToString
  });

  // Helpers

  function getProp(name, thing, fn) {
    return fn(thing[name]);
  }

  function mapProp(name, thing, fn) {
    return thing[name].map(fn);
  }

  // Returns a function that will walk a single property of a node.
  // `descriptor` is a string indicating the property name, optionally ending
  // with '[]' (e.g., 'children[]').
  function getPropWalkFn(descriptor) {
    const parts = descriptor.split(/ ?\[\]/);
    if (parts.length === 2) {
      return mapProp.bind(null, parts[0]);
    }
    return getProp.bind(null, descriptor);
  }

  function getProps(walkFns, thing, fn) {
    return walkFns.map(walkFn => walkFn(thing, fn));
  }

  function getWalkFn(shape) {
    if (typeof shape === 'string') {
      return getProps.bind(null, [getPropWalkFn(shape)]);
    } else if (Array.isArray(shape)) {
      return getProps.bind(null, shape.map(getPropWalkFn));
    } else {
      assert(typeof shape === 'function', 'Expected a string, Array, or function');
      assert(shape.length === 2, 'Expected a function of arity 2, got ' + shape.length);
      return shape;
    }
  }

  function isRestrictedIdentifier(str) {
    return /^[a-zA-Z_][0-9a-zA-Z_]*$/.test(str);
  }

  function trim(s) {
    return s.trim();
  }

  function parseSignature(sig) {
    const parts = sig.split(/[()]/).map(trim);
    if (parts.length === 3 && parts[2] === '') {
      const name = parts[0];
      let params = [];
      if (parts[1].length > 0) {
        params = parts[1].split(',').map(trim);
      }
      if (isRestrictedIdentifier(name) && params.every(isRestrictedIdentifier)) {
        return {name, formals: params};
      }
    }
    throw new Error('Invalid operation signature: ' + sig);
  }

  /*
    A VisitorFamily contains a set of recursive operations that are defined over some kind of
    tree structure. The `config` parameter specifies how to walk the tree:
    - 'getTag' is function which, given a node in the tree, returns the node's 'tag' (type)
    - 'shapes' an object that maps from a tag to a value that describes how to recursively
      evaluate the operation for nodes of that type. The value can be:
      * a string indicating the property name that holds that node's only child
      * an Array of property names (or an empty array indicating a leaf type), or
      * a function taking two arguments (node, fn), and returning an Array which is the result
        of apply `fn` to each of the node's children.
   */
  class VisitorFamily {
    constructor(config) {
      this._shapes = config.shapes;
      this._getTag = config.getTag;

      this.Adapter = function(thing, family) {
        this._adaptee = thing;
        this._family = family;
      };
      this.Adapter.prototype.valueOf = function() {
        throw new Error('heeey!');
      };
      this.operations = {};

      this._arities = Object.create(null);
      this._getChildren = Object.create(null);

      Object.keys(this._shapes).forEach(k => {
        const shape = this._shapes[k];
        this._getChildren[k] = getWalkFn(shape);

        // A function means the arity isn't fixed, so don't put an entry in the arity map.
        if (typeof shape !== 'function') {
          this._arities[k] = Array.isArray(shape) ? shape.length : 1;
        }
      });
      this._wrap = thing => new this.Adapter(thing, this);
    }

    wrap(thing) {
      return this._wrap(thing);
    }

    _checkActionDict(dict) {
      Object.keys(dict).forEach(k => {
        assert(k in this._getChildren, "Unrecognized action name '" + k + "'");
        const action = dict[k];
        assert(
            typeof action === 'function',
            "Key '" + k + "': expected function, got " + action,
        );
        if (k in this._arities) {
          const expected = this._arities[k];
          const actual = dict[k].length;
          assert(
              actual === expected,
              "Action '" + k + "' has the wrong arity: expected " + expected + ', got ' + actual,
          );
        }
      });
    }

    addOperation(signature, actions) {
      const sig = parseSignature(signature);
      const {name} = sig;
      this._checkActionDict(actions);
      this.operations[name] = {
        name,
        formals: sig.formals,
        actions,
      };

      const family = this;
      this.Adapter.prototype[name] = function(...args) {
        const tag = family._getTag(this._adaptee);
        assert(tag in family._getChildren, "getTag returned unrecognized tag '" + tag + "'");
        assert(tag in actions, "No action for '" + tag + "' in operation '" + name + "'");

        // Create an "arguments object" from the arguments that were passed to this
        // operation / attribute.
        const argsObj = Object.create(null);
        for (const [i, val] of Object.entries(args)) {
          argsObj[sig.formals[i]] = val;
        }

        const oldArgs = this.args;
        this.args = argsObj;
        const ans = actions[tag].apply(
            this,
            family._getChildren[tag](this._adaptee, family._wrap),
        );
        this.args = oldArgs;
        return ans;
      };
      return this;
    }
  }

  function handleListOf(child) {
    return child.toAST(this.args.mapping);
  }

  function handleEmptyListOf() {
    return [];
  }

  function handleNonemptyListOf(first, sep, rest) {
    return [first.toAST(this.args.mapping)].concat(rest.toAST(this.args.mapping));
  }

  const defaultMapping = {
    listOf: handleListOf,
    ListOf: handleListOf,

    emptyListOf: handleEmptyListOf,
    EmptyListOf: handleEmptyListOf,

    nonemptyListOf: handleNonemptyListOf,
    NonemptyListOf: handleNonemptyListOf,
  };

  const defaultOperation = {
    _terminal() {
      return this.sourceString;
    },

    _nonterminal(...children) {
      const {ctorName} = this._node;
      const {mapping} = this.args;

      // without customization
      if (!Object.prototype.hasOwnProperty.call(mapping, ctorName)) {
        // lexical rule
        if (this.isLexical()) {
          return this.sourceString;
        }

        // singular node (e.g. only surrounded by literals or lookaheads)
        const realChildren = children.filter(child => !child.isTerminal());
        if (realChildren.length === 1) {
          return realChildren[0].toAST(mapping);
        }

        // rest: terms with multiple children
      }
      // direct forward
      if (typeof mapping[ctorName] === 'number') {
        return children[mapping[ctorName]].toAST(mapping);
      }

      // named/mapped children or unnamed children ('0', '1', '2', ...)
      const propMap = mapping[ctorName] || children;
      const node = {
        type: ctorName,
      };
      // eslint-disable-next-line guard-for-in
      for (const prop in propMap) {
        const mappedProp = mapping[ctorName] && mapping[ctorName][prop];
        if (typeof mappedProp === 'number') {
          // direct forward
          node[prop] = children[mappedProp].toAST(mapping);
        } else if (
          typeof mappedProp === 'string' ||
          typeof mappedProp === 'boolean' ||
          mappedProp === null
        ) {
          // primitive value
          node[prop] = mappedProp;
        } else if (typeof mappedProp === 'object' && mappedProp instanceof Number) {
          // primitive number (must be unboxed)
          node[prop] = Number(mappedProp);
        } else if (typeof mappedProp === 'function') {
          // computed value
          node[prop] = mappedProp.call(this, children);
        } else if (mappedProp === undefined) {
          if (children[prop] && !children[prop].isTerminal()) {
            node[prop] = children[prop].toAST(mapping);
          } else {
            // delete predefined 'type' properties, like 'type', if explicitely removed
            delete node[prop];
          }
        }
      }
      return node;
    },

    _iter(...children) {
      if (this._node.isOptional()) {
        if (this.numChildren === 0) {
          return null;
        } else {
          return children[0].toAST(this.args.mapping);
        }
      }

      return children.map(c => c.toAST(this.args.mapping));
    },
  };

  // Returns a plain JavaScript object that includes an abstract syntax tree (AST)
  // for the given match result `res` containg a concrete syntax tree (CST) and grammar.
  // The optional `mapping` parameter can be used to customize how the nodes of the CST
  // are mapped to the AST (see /doc/extras.md#toastmatchresult-mapping).
  function toAST(res, mapping) {
    if (typeof res.failed !== 'function' || res.failed()) {
      throw new Error('toAST() expects a succesful MatchResult as first parameter');
    }

    mapping = Object.assign({}, defaultMapping, mapping);
    const operation = Object.assign({}, defaultOperation);
    for (const termName in mapping) {
      if (typeof mapping[termName] === 'function') {
        operation[termName] = mapping[termName];
        delete mapping[termName];
      }
    }
    const g = res._cst.grammar;
    const s = g.createSemantics().addOperation('toAST(mapping)', operation);
    return s(res).toAST(mapping);
  }

  // Returns a semantics containg the toAST(mapping) operation for the given grammar g.
  function semanticsForToAST(g) {
    if (typeof g.createSemantics !== 'function') {
      throw new Error('semanticsToAST() expects a Grammar as parameter');
    }

    return g.createSemantics().addOperation('toAST(mapping)', defaultOperation);
  }

  // --------------------------------------------------------------------
  // Private stuff
  // --------------------------------------------------------------------

  // Given an array of numbers `arr`, return an array of the numbers as strings,
  // right-justified and padded to the same length.
  function padNumbersToEqualLength(arr) {
    let maxLen = 0;
    const strings = arr.map(n => {
      const str = n.toString();
      maxLen = Math.max(maxLen, str.length);
      return str;
    });
    return strings.map(s => padLeft(s, maxLen));
  }

  // Produce a new string that would be the result of copying the contents
  // of the string `src` onto `dest` at offset `offest`.
  function strcpy(dest, src, offset) {
    const origDestLen = dest.length;
    const start = dest.slice(0, offset);
    const end = dest.slice(offset + src.length);
    return (start + src + end).substr(0, origDestLen);
  }

  // Casts the underlying lineAndCol object to a formatted message string,
  // highlighting `ranges`.
  function lineAndColumnToMessage(...ranges) {
    const lineAndCol = this;
    const {offset} = lineAndCol;
    const {repeatStr} = common;

    const sb = new StringBuffer();
    sb.append('Line ' + lineAndCol.lineNum + ', col ' + lineAndCol.colNum + ':\n');

    // An array of the previous, current, and next line numbers as strings of equal length.
    const lineNumbers = padNumbersToEqualLength([
      lineAndCol.prevLine == null ? 0 : lineAndCol.lineNum - 1,
      lineAndCol.lineNum,
      lineAndCol.nextLine == null ? 0 : lineAndCol.lineNum + 1,
    ]);

    // Helper for appending formatting input lines to the buffer.
    const appendLine = (num, content, prefix) => {
      sb.append(prefix + lineNumbers[num] + ' | ' + content + '\n');
    };

    // Include the previous line for context if possible.
    if (lineAndCol.prevLine != null) {
      appendLine(0, lineAndCol.prevLine, '  ');
    }
    // Line that the error occurred on.
    appendLine(1, lineAndCol.line, '> ');

    // Build up the line that points to the offset and possible indicates one or more ranges.
    // Start with a blank line, and indicate each range by overlaying a string of `~` chars.
    const lineLen = lineAndCol.line.length;
    let indicationLine = repeatStr(' ', lineLen + 1);
    for (let i = 0; i < ranges.length; ++i) {
      let startIdx = ranges[i][0];
      let endIdx = ranges[i][1];
      assert(startIdx >= 0 && startIdx <= endIdx, 'range start must be >= 0 and <= end');

      const lineStartOffset = offset - lineAndCol.colNum + 1;
      startIdx = Math.max(0, startIdx - lineStartOffset);
      endIdx = Math.min(endIdx - lineStartOffset, lineLen);

      indicationLine = strcpy(indicationLine, repeatStr('~', endIdx - startIdx), startIdx);
    }
    const gutterWidth = 2 + lineNumbers[1].length + 3;
    sb.append(repeatStr(' ', gutterWidth));
    indicationLine = strcpy(indicationLine, '^', lineAndCol.colNum - 1);
    sb.append(indicationLine.replace(/ +$/, '') + '\n');

    // Include the next line for context if possible.
    if (lineAndCol.nextLine != null) {
      appendLine(2, lineAndCol.nextLine, '  ');
    }
    return sb.contents();
  }

  // Return an object with the line and column information for the given
  // offset in `str`.
  function getLineAndColumn(str, offset) {
    let lineNum = 1;
    let colNum = 1;

    let currOffset = 0;
    let lineStartOffset = 0;

    let nextLine = null;
    let prevLine = null;
    let prevLineStartOffset = -1;

    while (currOffset < offset) {
      const c = str.charAt(currOffset++);
      if (c === '\n') {
        lineNum++;
        colNum = 1;
        prevLineStartOffset = lineStartOffset;
        lineStartOffset = currOffset;
      } else if (c !== '\r') {
        colNum++;
      }
    }

    // Find the end of the target line.
    let lineEndOffset = str.indexOf('\n', lineStartOffset);
    if (lineEndOffset === -1) {
      lineEndOffset = str.length;
    } else {
      // Get the next line.
      const nextLineEndOffset = str.indexOf('\n', lineEndOffset + 1);
      nextLine =
        nextLineEndOffset === -1 ?
          str.slice(lineEndOffset) :
          str.slice(lineEndOffset, nextLineEndOffset);
      // Strip leading and trailing EOL char(s).
      nextLine = nextLine.replace(/^\r?\n/, '').replace(/\r$/, '');
    }

    // Get the previous line.
    if (prevLineStartOffset >= 0) {
      // Strip trailing EOL char(s).
      prevLine = str.slice(prevLineStartOffset, lineStartOffset).replace(/\r?\n$/, '');
    }

    // Get the target line, stripping a trailing carriage return if necessary.
    const line = str.slice(lineStartOffset, lineEndOffset).replace(/\r$/, '');

    return {
      offset,
      lineNum,
      colNum,
      line,
      prevLine,
      nextLine,
      toString: lineAndColumnToMessage,
    };
  }

  // Return a nicely-formatted string describing the line and column for the
  // given offset in `str` highlighting `ranges`.
  function getLineAndColumnMessage(str, offset, ...ranges) {
    return getLineAndColumn(str, offset).toString(...ranges);
  }

  exports.VisitorFamily = VisitorFamily;
  exports.getLineAndColumn = getLineAndColumn;
  exports.getLineAndColumnMessage = getLineAndColumnMessage;
  exports.semanticsForToAST = semanticsForToAST;
  exports.toAST = toAST;

  Object.defineProperty(exports, '__esModule', { value: true });

}));
