単純な数式を解析するために 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}));
左再帰の処理方法を学びたい場合は、 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
APIを使用して上記の文法を実装しました
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
}