Parser

AWS Region Parser

function* ParseAWSRegion() {
  const primary = yield [
    "us-gov",
    "us",
    "af",
    "ap",
    "ca",
    "eu",
    "cn",
    "me",
    "sa"
  ];
  yield "-";
  const secondary = yield [
    "northeast",
    "northwest",
    "southeast",
    "central",
    "north",
    "east",
    "west",
    "south"
  ];
  yield "-";
  const digit = yield [1, 2, 3];
  return {
    primary,
    secondary,
    digit
  };
}

Results

parseString(
  "us-west-1",
  ParseAWSRegion
)
{
  "success": true,
  "result": {
    "primary": "us",
    "secondary": "west",
    "digit": 1
  },
  "remaining": ""
}
parseString(
  "ap-southeast-2",
  ParseAWSRegion
)
{
  "success": true,
  "result": {
    "primary": "ap",
    "secondary": "southeast",
    "digit": 2
  },
  "remaining": ""
}
parseString(
  "xx-east-1",
  ParseAWSRegion
)
{
  "success": false,
  "failedOnMessage": [
    "us-gov",
    "us",
    "af",
    "ap",
    "ca",
    "eu",
    "cn",
    "me",
    "sa"
  ],
  "remaining": "xx-east-1"
}
parseString(
  "eu-west-3",
  ParseAWSRegion
)
{
  "success": true,
  "result": {
    "primary": "eu",
    "secondary": "west",
    "digit": 3
  },
  "remaining": ""
}
parseString(
  "us-gov-west-1",
  ParseAWSRegion
)
{
  "success": true,
  "result": {
    "primary": "us-gov",
    "secondary": "west",
    "digit": 1
  },
  "remaining": ""
}

Parser Processor

function parseString(input, generator) {
  let reply;
  const gen = generator();
  mainLoop:
    while (true) {
      const result = gen.next(reply);
      if (result.done) {
        return Object.freeze({
          success: true,
          result: result.value,
          remaining: input
        });
      }
      const message = result.value;
      const matchers = new Array().concat(message);
      for (let matcher of matchers) {
        if (typeof matcher === "string" || typeof matcher === "number") {
          const searchString = matcher.toString();
          let found = false;
          const newInput = input.replace(searchString, (_1, offset) => {
            found = offset === 0;
            return "";
          });
          if (found) {
            reply = matcher;
            input = newInput;
            continue mainLoop;
          }
        } else if (matcher instanceof RegExp) {
          if (["^", "$"].includes(matcher.source[0]) === false) {
            throw new Error(`Regex must be from start: ${matcher}`);
          }
          const match = input.match(matcher);
          if (match) {
            reply = match;
            input = input.slice(match[0].length);
            continue mainLoop;
          }
        }
      }
      return Object.freeze({
        success: false,
        failedOnMessage: message,
        remaining: input
      });
    }
}