Search code examples
javascriptredofor-of-loop

How do I redo a for...of loop in JavaScript?


In Ruby, the redo keyword can be used to go back to the beginning of a loop without consuming input. I'd like to do the same with a for...of loop in JavaScript.

const scan = lexer => function* (string) {
  let [token, table] = lexer;

  for (const character of string) {
    const next = table.get(character);

    if (next) {
      [token, table] = next.value;
    } else if (token) {
      yield token.value;
      [token, table] = lexer;
      // redo the current iteration without consuming input
    } else {
      throw new SyntaxError("Unexpected character", character);
    }
  }

  if (token) yield token.value;
  else throw new SyntaxError("Unexpected end of input");
}

Normally, you'd do this by simply not incrementing the index of a regular for loop. However, I have to use a for...of loop because it loops over the Unicode code points of the string, whereas the regular for loop would loop over the UTF-16 code units of the string.

How do I go back to the beginning of the loop without duplication of code?


Solution

  • Just use an inner loop:

     for (const character of string) {
       while(true) {
         const next = table.get(character);       
         if (next) {
           [token, table] = next.value;
           break;
         } else if (token) {
           yield token.value;
           [token, table] = lexer;      
           // don't break, redo
         } else {
           throw new SyntaxError("Unexpected character", character);
           break;
        }
      }
    }
    

    To restart the whole loop:

    In your case it's actually quite straightforward:

     yield* scan(lexer)(string);
     return;
    

    For sure if you don't want to restart the whole function, add an IIFE and recall that:

     yield* (function redo*() {
       //...
      yield* redo();
      return;
    })();
    

    If you really need jumps, use labels (please don't):

     restart: while(true) {
      // do stuff
      continue restart;
      //...
      break;
    }