Source: script.js

/** This module implements a server function for [node.js](https://en.wikipedia.org/wiki/Node.js)
    to script the {@link module:Practice~Model practice page model}, i.e.,
    to run the examples from the command line.

  @module Script
  @author © 2023 Axel T. Schreiner <axel@schreiner-family.net>
  @version 2024-02-13
*/

import * as readline from 'readline';
import * as fs from 'fs';
import * as Practice from './practice.js';

/** The server function.
    Blank-separated words, read from standard input, have the effects described below.
    Any other word is interpreted as a path from which an example
    is merged into the global strings.

| word | effect |
| ---- | ------ |
| `model` | resets all flags and variables. |
| `load` | resets global strings. |
| `ebnf`, `stack`, `bnf` | clear all flags and set a mode. |
| `actions`, `build`, `deep`, `error`, `follow`, `greedy`,<br>`noargs`, `lookahead`, `parser`, `sets`, `shallow`, `states` | toggle flags. |
| `new` | calls {@linkcode module:Practice~Model#doNew model.doNew()}. |
| `scan` | calls {@linkcode module:Practice~Model#doScan model.doScan()}. |
| `parse` | calls {@linkcode module:Practice~Model#doParse model.doParse()}. |
| `run` | calls {@linkcode module:Practice~Model#doRun model.doRun()}. |
| `1` | calls {@linkcode module:Practice~Model#doStep model.doStep(1)}. |
| `10` | calls {@linkcode module:Practice~Model#doStep model.doStep(10)}. |
| `100` | calls {@linkcode module:Practice~Model#doStep model.doStep(100)}. |
| exit | terminates the script |

@example <caption> Load an example, compile and run with each parser, overwrite parts, and repeat. </caption>
$ node script.js << 'end'
  model eg/06/06.eg      new parse run   stack new parse run
  ebnf  test/06-06a.eg   new parse run   stack new parse run
end
*/
async function script () {
  let model;
  const newModel = () => model = new Practice.Model(globalThis);
  newModel();
  
  for await (const line of readline.createInterface({ input: process.stdin })) {
    const words = line.split(/\s+/);
    words.forEach(word => {
      if (!word.length) return;
      console.log('>', word);
      switch (word) {
      case 'model':             // model -- reset all
        newModel();
        return;
    
      case 'load':              // load -- reset text areas
        model.grammar = '';     // %% grammar
        model.tokens = '';      // %% tokens
        model.actions = '';     // %% actions
        model.program = '';     // %% program
        return;
    
      default:                  // path (cannot contain blanks)
        try {
          const text = fs.readFileSync(word, 'utf8');
          if (!text.length) {
            console.log('no text:', word);
            return;
          }

          text.split(/%%/).forEach(part => {
            try {
              const id = part.match(/\s+(grammar|tokens|actions|program)\s+/)[1],
                value = part.replace(/^[^\n]*\n/, '').replace(/\n*$/, '');
              switch (id) {
              case 'grammar':   model.grammar = value; return;
              case 'tokens':    model.tokens = value; return;
              case 'actions':   model.actions = value; return;
              case 'program':   model.program = value; return;
              }
            } catch (e) { }
          });      
          return;
        } catch (e) {
          console.log(word + ':', e instanceof Error ? e.message : e);
          process.exit(1);
        }
    
      case 'ebnf':              // ebnf -- set mode, clear all flags
      case 'stack':             // stack -- set mode, clear all flags
      case 'bnf':               // bnf -- set mode, clear all flags
        model.mode = word;
        console.log('> model.mode =', model.mode);
        return;

      case 'greedy':            // greedy -- toggle flag
      case 'error':             // error -- toggle flag
      case 'build':             // build -- toggle flag
        model[word] = !model[word];
        console.log(`> model.${word} = ${model[word]}`);
        return;
      
      case 'shallow':           // shallow -- toggle flag
      case 'deep':              // deep -- toggle flag
      case 'follow':            // follow -- toggle flag
      case 'lookahead':         // lookahead -- toggle flag
      case 'parser':            // parser -- toggle flag
      case 'actions':           // actions -- toggle flag
      case 'noargs':            // noargs -- toggle flag
        { const name = 't' + word[0].toUpperCase() + word.substr(1);
          model[name] = !model[name];
          console.log(`> model.${name} = ${model[name]}`);
          return;
        }
      
      case 'sets':              // sets -- toggle flag
      case 'states':            // states -- toggle flag
        { const name = 'd' + word[0].toUpperCase() + word.substr(1);
          model[name] = !model[name];
          console.log(`> model.${name} = ${model[name]}`);
          return;
        }
    
      case 'new':
        model.doNew();
        return;
      
      case 'scan':
        model.doScan();
        return;

      case 'parse':
        model.doParse();
        return;

      case 'run':
        model.doRun();
        return;

      case '1':
      case '10':
      case '100':
        model.doStep(parseInt(word, 10));
        return;
    
      case 'exit':
        process.exit(0);
      }
    });
  }
}
script()

export { script };