0

I want to implement a meta-circular evaluator in JS with support to functional programming.

How can I parse this?

[1..10]

I want to receive 1 and 10

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
NrBanMex
  • 305
  • 2
  • 8
  • 2
    `const [min, max] = "[1..10]".match(/\[(\d+)\.\.(\d+)\]/).slice(1).map(Number)` – user120242 Jun 13 '20 at 02:21
  • 5
    _"I want to implement a meta-circular evaluator in JS with support to functional programming"_ ... probably regex will not be sufficient for this task. I suggest writing a grammar and using a parser like antlr. You may use small regexes of some flavor or other to the grammar to tokenize, but that's implementation-specific depending on the grammar. – ggorlen Jun 13 '20 at 02:33

3 Answers3

2

This is a basic implementation of generating a range.
The goal you are talking about is going to be too complex for regex (firmly tongue in cheek).

Using tagged template literal syntax.
Regex finds two digit sequences, converts to numbers. Fills an array.

range = (str,...args) =>
(([,min,max])=>
  Array(Math.abs(max-min)+1).fill(+min).map((_,i)=>_+i*(min>max?-1:1)))
    ((Array.isArray(str) ? str.map((s,i)=>s+args[i]).join('') : str)
      .match(/\[\s*(-?\d+)\s*\.\.\s*(-?\d+)\s*\]/))

x=-3, y=0
console.log(
range`[5..1]`,
range`[1..10]`,
range("[ 5 .. -2 ]"),
range`[${x}.. ${y}]`
)
user120242
  • 14,918
  • 3
  • 38
  • 52
  • 1
    I was going to ask what does a tagged template function offer that a regular function doesn't if you're not leveraging the args array? As far as I can see you're rebuilding a string out of it. You could _simply_ assume that if `str` is an array then `args[0]` is `min` and `args[1]` is `max`. Otherwise fallback to the regex. – customcommander Jun 13 '20 at 09:19
  • That's true. I really only meant this as a toy function just to show basic extraction using the regex, and a reasonably easy to read syntax for creating the range (2nd nicer than 3rd). Probably won't even end up being used by OP :\ Probably if you really wanted something like this, I'd think you'd go for an iterable convertable range object and/or generator, JS syntax ought to be plenty expressive enough (you might even be able to simulate `..` directly using some kind of getter on the Number prototype)? And for actual syntax parsing of your language in a string use a grammar parser. – user120242 Jun 13 '20 at 09:31
2

I've been longing to try out nearley.js for so long. This may or may not be what you wanted!


Please do note that I have assumed that what you want to get out of [1..10] is all the numbers from 1 to 10 (included), e.g. [1,2,3,4,5,6,7,8,9,10].


Let's define the grammar for this mini language

grammar.ne

# Given the string "[1..10]", AST is ['[', 1, '..', 10, ']']
# We define a postprocessor that immediately interprets the expression
range -> "[" number ".." number "]" {%
  function range(ast) {
    const [,a,,b,] = ast; // extracts the number from the ast
    const len = Math.abs(a - b) + 1;
    const inc = a < b ? (_, i) => a + i : (_, i) => a - i;
    return Array.from(Array(len), inc);
  }
%}

# Given the string "1", AST is [['1']]
# Given the string "10", AST is [['1','0']]
# We define a postprocessor that joins the characters together and coerce them into a number.
number -> [0-9]:+ {% ([chars]) => Number(chars.join('')) %}

What I like about nearley.js is that it allows you to embed post processors for your rules right into the grammar. It may looks ugly but I find it pretty neat actually!

Another cool thing, is that nearley.js comes with a suite of useful tools. One of them generates a diagram for your grammar:

yarn run -s nearley-railroad grammar.ne > grammar.html

Here's the output:

As you can see a range is a sequence of:

  1. Starts with "["
  2. Followed by a number
  3. Followed by ".."
  4. Followed by a number
  5. Ends with "]"

enter image description here

Now we need to compile that grammar

yarn run -s nearleyc grammar.ne > grammar.js

This code needs to be loaded into a parser. I'm just showing the compiled grammar for illustration purpose:

grammar.js

// Generated automatically by nearley, version 2.19.3
// http://github.com/Hardmath123/nearley
(function () {
function id(x) { return x[0]; }
var grammar = {
    Lexer: undefined,
    ParserRules: [
    {"name": "range$string$1", "symbols": [{"literal":"."}, {"literal":"."}], "postprocess": function joiner(d) {return d.join('');}},
    {"name": "range", "symbols": [{"literal":"["}, "number", "range$string$1", "number", {"literal":"]"}], "postprocess": 
        function range(ast) {
          const [,a,,b,] = ast; // extracts the number from the ast
          const len = Math.abs(a - b) + 1;
          const inc = a < b ? (_, i) => a + i : (_, i) => a - i;
          return Array.from(Array(len), inc);
        }
        },
    {"name": "number$ebnf$1", "symbols": [/[0-9]/]},
    {"name": "number$ebnf$1", "symbols": ["number$ebnf$1", /[0-9]/], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
    {"name": "number", "symbols": ["number$ebnf$1"], "postprocess": ([chars]) => Number(chars.join(''))}
]
  , ParserStart: "range"
}
if (typeof module !== 'undefined'&& typeof module.exports !== 'undefined') {
   module.exports = grammar;
} else {
   window.grammar = grammar;
}
})();

Now let's build a parser and use it!

index.js

const nearley = require("nearley");
const grammar = require("./grammar"); // loads the compiled grammar!

const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar));

parser.feed("[1..10]");

console.log(parser.results[0]);
//=> [1,2,3,4,5,6,7,8,9,10]
customcommander
  • 17,580
  • 5
  • 58
  • 84
0

Regex: \[([0-9]+)\.\.([0-9]+)\]

I confirmed the regex via regex101.com and let it generate the sample code.

const regex = /\[([0-9]+)\.\.([0-9]+)\]/gm;
const str = `[1..10]`;
let m;

while ((m = regex.exec(str)) !== null) {
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
        regex.lastIndex++;
    }
    
    // The result can be accessed through the `m`-variable.
    m.forEach((match, groupIndex) => {
        console.log(`Found match, group ${groupIndex}: ${match}`);
    });
}

Result:

Found match, group 0: 1..10
Found match, group 1: 1
Found match, group 2: 10
selbie
  • 100,020
  • 15
  • 103
  • 173