web-dev-qa-db-ja.com

パーサーコンビネーターを使用して単純な数式を解析する

単純な数式を解析するために parsimmon を使用していますが、演算の順序に従う単純な数式を解析できません(つまり、*/は、 +-)。

このライブラリに慣れていない場合でも、左再帰や無限再帰を行わずに優先順位の問題を解決してください。

ありがとうございました。

私はTypeScriptを使用しました:

"use strict";

// Run me with Node to see my output!

import * as P from "parsimmon";
import {Parser} from "parsimmon";
// @ts-ignore
import util from "util";


///////////////////////////////////////////////////////////////////////

// Use the JSON standard's definition of whitespace rather than Parsimmon's.
let whitespace = P.regexp(/\s*/m);

// JSON is pretty relaxed about whitespace, so let's make it easy to ignore
// after most text.
function token(parser: Parser<string>) {
  return parser.skip(whitespace);
}

// Several parsers are just strings with optional whitespace.
function Word(str: string) {
  return P.string(str).thru(token);
}

let MathParser = P.createLanguage({
  expr: r => P.alt(r.sExpr2, r.sExpr1, r.number),
  sExpr1: r => P.seqMap(r.iExpr, P.optWhitespace, r.plusOrMinus, P.optWhitespace, r.expr, (a, s1, b, s2, c) => [a, b, c]),
  sExpr2: r => P.seqMap(r.iExpr, P.optWhitespace, r.multiplyOrDivide, P.optWhitespace, r.expr, (a, s1, b, s2, c) => [a, b, c]),
  iExpr: r => P.alt(r.iExpr, r.number),    // Issue here! this causes infinite recursion
  // iExpr: r => r.number  // this will fix infinite recursion but yields invalid parse

  number: () =>
    token(P.regexp(/[0-9]+/))
      .map(Number)
      .desc("number"),

  plus: () => Word("+"),
  minus: () => Word("-"),
  plusOrMinus: r => P.alt(r.plus, r.minus),

  multiply: () => Word("*"),
  divide: () => Word("/"),
  multiplyOrDivide: r => P.alt(r.multiply, r.divide),

  operator: r => P.alt(r.plusOrMinus, r.multiplyOrDivide)
});

///////////////////////////////////////////////////////////////////////

let text = "3 / 4 - 5 * 6 + 5";

let ast = MathParser.expr.tryParse(text);
console.log(util.inspect(ast, {showHidden: false, depth: null}));

これは私のリポジトリです

7
Node.JS

左再帰の処理方法を学びたい場合は、 https://en.wikipedia.org/wiki/Parsing_expression_grammar から始めるか、より正確には https://en.wikipedia .org/wiki/Parsing_expression_grammar#Indirect_left_recursion

そして、オンラインでPEGの詳細を読んでください。しかし、基本的には標準的な方法はサイクルを使用することです:

Expr    ← Sum
Sum     ← Product (('+' / '-') Product)*
Product ← Value (('*' / '/') Value)*
Value   ← [0-9]+ / '(' Expr ')'

この文法の例はどこにでもあります。

より快適な左再帰文法を使いたい場合は、Packratパーサーについて読むことができます。そして、自分用の別のパーサーを見つけてください。なぜなら、確かではありませんが、parsimmonはそれらの1つではないようです。

あなたがちょうど何の機能するコードなら、あなたは行くことができます https://repl.it/repls/ObviousNavyblueFiletype

parsimmon AP​​Iを使用して上記の文法を実装しました

Expr        : r => r.AdditiveExpr,
AdditiveExpr: r => P.seqMap(
  r.MultExpr, P.seq(r.plus.or(r.minus), r.MultExpr).many(),
  left_association
),
MultExpr    : r => P.seqMap(
  r.UnaryExpr, P.seq(r.multiply.or(r.divide), r.UnaryExpr).many(),
  left_association
),
UnaryExpr   : r => P.seq(r.minus, r.UnaryExpr).or(r.PrimaryExpr),
PrimaryExpr : r => P.seqMap(
  r.LPAREN, r.Expr, r.RPAREN, // without parens it won't work
  (lp,ex,rp) => ex // to remove "(" and ")" from the resulting AST
).or(P.digits),

plus        : () => P.string('+').thru(p => p.skip(P.optWhitespace)),
minus       : () => P.string('-').thru(p => p.skip(P.optWhitespace)),
multiply    : () => P.string('*').thru(p => p.skip(P.optWhitespace)),
divide      : () => P.string('/').thru(p => p.skip(P.optWhitespace)),

また、括弧を使用する必要があります。そうしないと、なぜ再帰が必要になるのでしょうか。 Value ← [0-9]+ 十分であろう。括弧がない場合、文法内でExprを参照する必要はありません。そうすると、入力を消費せず、まったく意味がなく、無限再帰でハングします。

だから追加しましょう:

LPAREN      : () => P.string('(').thru(p => p.skip(P.optWhitespace)),
RPAREN      : () => P.string(')').thru(p => p.skip(P.optWhitespace)),

完全を期すために、単項式も追加しました。

そして今、最も興味深い部分-生産機能。それらの結果がなければ、言ってみましょう:

(3+4+6+(-7*5))

このようになります:

[[["(",[["3",[]],[["+",["4",[]]],["+",["6",[]]],["+",[["(",[[["-","7"],[["*","5"]]],[]],")"],[]]]]],")"],[]],[]]

それらを使用すると、次のようになります。

[[[["3","+","4"],"+","6"],"+",[["-","7"],"*","5"]]]

ずっといい。

左結合演算子の場合、これが必要になります。

// input (expr1, [[op1, expr2],[op2, expr3],[op3, expr4]])
// -->
// output [[[expr1, op1, expr2], op2, expr3], op3, expr4]
const left_association  = (ex, rest) => {
  // console.log("input", ex, JSON.stringify(rest))
  if( rest.length === 0 ) return ex
  if( rest.length === 1 ) return [ex, rest[0][0], rest[0][1]]
  let node = [ex]
  rest.forEach(([op, ex]) => {
    node[1] = op;
    node[2] = ex;
    node = [node]
  })
  // console.log("output", JSON.stringify(node))
  return node
}
0
x00