How to implement a meta-circular evaluator to parse `[1..10]` in JavaScript?

大城市里の小女人 提交于 2020-08-10 19:15:09

问题


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


回答1:


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}]`
)



回答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 "]"

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]



回答3:


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


来源:https://stackoverflow.com/questions/62354767/how-to-implement-a-meta-circular-evaluator-to-parse-1-10-in-javascript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!