في الوقت الحالي ، أستخدم JS API لإنشاء المحلل اللغوي في وقت التشغيل. هذا يعمل بشكل جيد.
ثم حاولت إنشاء المحلل اللغوي باستخدام CLI لتجنب إنشائه أثناء وقت التشغيل. عندما أستخدمه على الرغم من أنني أحصل على أخطاء (حوالي نصف اختباراتي لتحليل أخطاء رمي السلسلة).
grammar.pegjs
pegjs -o parser.js grammar.pegjs
peg.generate('...')
واستبدله بالمحلل اللغوي الجديدconst parser = require('./parser');
parser.parse('...');
سلوك متوقع:
أتوقع أن المحلل اللغوي الذي تم إنشاؤه من CLI يعمل مثل المحلل اللغوي الذي تم إنشاؤه من JS API.
السلوك الفعلي:
باستخدام JS API ، عندما أمرر هذه السلسلة ( 'foo = "bar"'
) إلى المحلل اللغوي أحصل على AST التالي:
{
kind: 'condition',
target: 'foo',
operator: '=',
value: 'bar',
valueType: 'string',
attributeType: undefined
}
ومع ذلك ، عند استخدام المحلل اللغوي "الذي تم إنشاؤه" باستخدام CLI ، وتمرير نفس السلسلة ( 'foo = "bar"'
) أحصل على الخطأ التالي:
SyntaxError: Expected "(", boolean, date, datetime, number, string, or time but "\"" found.
at peg$buildStructuredError (/Users/emmenko/xxx/parser.js:446:12)
at Object.peg$parse [as parse] (/Users/emmenko/xxx/parser.js:2865:11)
at repl:1:7
at ContextifyScript.Script.runInThisContext (vm.js:50:33)
at REPLServer.defaultEval (repl.js:240:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval] (domain.js:314:12)
at REPLServer.onLine (repl.js:441:10)
at emitOne (events.js:121:20)
at REPLServer.emit (events.js:211:7)
0.10.0
8.9.1
[email protected]
جميل ، لقد ملأتها بشكل صحيح 👍 ، الآن نحتاج فقط إلى القواعد ويمكنني مساعدتك 😄
ها أنت ذا:
// GRAMMAR
const parser = peg.generate(`
{
function getFlattenedValue (value) {
if (!value) return undefined
return Array.isArray(value)
? value.map(function(v){return v.value})
: value.value
}
function getValueType (value) {
if (!value) return undefined
var rawType = value.type
if (Array.isArray(value))
rawType = value[0].type
switch (rawType) {
case 'string':
case 'number':
case 'boolean':
return rawType
default:
return 'string'
}
}
function getAttributeType (target, op, val) {
if (typeof target === 'string' && target.indexOf('attributes.') === 0) {
if (!val)
return undefined
switch (op) {
case 'in':
case 'not in':
return val[0].type;
case 'contains':
return 'set-' + val.type
default:
return Array.isArray(val) ? 'set-' + val[0].type : val.type;
}
}
}
function transformToCondition (target, op, val) {
return {
kind: "condition",
target: target,
operator: op,
value: getFlattenedValue(val),
valueType: getValueType(val),
attributeType: getAttributeType(target, op, val),
}
}
function createIdentifier (body) {
return body
.map(identifiers => identifiers.filter(identifier => (identifier && identifier !== '.'))) // gets raw_identifiers without dots and empty identifiers
.filter(identifiers => identifiers.length > 0) // filter out empty identifiers arrays
.map(identifiers => identifiers.join('.'))
.join('.') // join back to construct the path
}
}
// ----- DSL Grammar -----
predicate
= ws exp:expression ws { return exp; }
expression
= head:term tail:("or" term)*
{
if (tail.length === 0) {
return head;
}
return {
kind: "logical",
logical: "or",
conditions: [head].concat(tail.map(function(el){return el[1];})),
};
}
term
= head:factor tail:("and" factor)*
{
if (tail.length === 0) {
return head;
}
return {
kind: "logical",
logical: "and",
conditions: [head].concat(tail.map(function(el){return el[1];})),
};
}
factor
= ws negation:"not" ws primary:primary ws
{
return {
kind: "negation",
condition: primary,
};
}
/ ws primary:primary ws { return primary; }
primary
= basic_comparison
/ list_comparison
/ empty_comparison
/ parens
// ----- Comparators -----
basic_comparison
= target:val_expression ws op:single_operators ws val:value
{ return transformToCondition(target, op, val); }
list_comparison
= target:val_expression ws op:list_operators ws val:list_of_values
{ return transformToCondition(target, op, val); }
empty_comparison
= target:val_expression ws op:empty_operators
{ return transformToCondition(target, op); }
// ----- Operators -----
single_operators
= "!="
/ "="
/ "<>"
/ ">="
/ ">"
/ "<="
/ "<"
/ "contains"
list_operators
= "!="
/ "="
/ "<>"
/ "not in"
/ "in"
/ "contains all"
/ "contains any"
empty_operators
= "is not empty"
/ "is empty"
/ "is not defined"
/ "is defined"
list_of_values
= ws "(" ws head:value tail:(ws "," ws value)* ws ")" ws
{
if (tail.length === 0) {
return [head];
}
return [head].concat(tail.map(function(el){ return el[el.length -1];}));
}
// ----- Expressions -----
val_expression
= application_expression
/ constant_expression
/ field_expression
application_expression
= identifier ws "(" ws function_argument (ws "," ws function_argument)* ws ")"
constant_expression = ws val:value ws { return val; }
field_expression = ws i:identifier ws { return i; }
function_argument
= expression
/ constant_expression
/ field_expression
value
= v:boolean { return { type: 'boolean', value: v }; }
/ v:datetime { return { type: 'datetime', value: v }; }
/ v:date { return { type: 'date', value: v }; }
/ v:time { return { type: 'time', value: v }; }
/ v:number { return { type: 'number', value: v }; }
/ v:string { return { type: 'string', value: v }; }
// ----- Common rules -----
parens
= ws "(" ws ex:expression ws ")" ws { return ex; }
identifier
= body:((raw_identifier "." escaped_identifier)+ / (raw_identifier "." raw_identifier)+)
{
return createIdentifier(body)
}
/ i:raw_identifier { return i; }
escaped_identifier
= "\`" head:raw_identifier tail:("-" raw_identifier)* "\`"
{ return [head].concat(tail.map(function(el){return el.join('');})).join(''); }
raw_identifier = i:[a-zA-Z0-9_]* { return i.join(''); }
ws "whitespace" = [ \\t\\n\\r]*
// ----- Types: booleans -----
boolean "boolean"
= "false" { return false; }
/ "true" { return true; }
// ----- Types: datetime -----
datetime "datetime"
= quotation_mark datetime:datetime_format quotation_mark
{ return datetime.map(function(el){return Array.isArray(el) ? el.join('') : el;}).join(''); }
datetime_format = date_format time_mark time_format zulu_mark
time_mark = "T"
zulu_mark = "Z"
// ----- Types: date -----
date "date"
= quotation_mark date:date_format quotation_mark { return date.join("");}
date_format = [0-9][0-9][0-9][0-9] minus [0-9][0-9] minus [0-9][0-9]
// ----- Types: time -----
time "time"
= quotation_mark time:time_format quotation_mark { return time.join("");}
time_format = [0-2][0-9] colon [0-5][0-9] colon [0-5][0-9] decimal_point [0-9][0-9][0-9]
colon = ":"
// ----- Types: numbers -----
number "number"
= minus? int frac? exp? { return parseFloat(text()); }
decimal_point = "."
digit1_9 = [1-9]
e = [eE]
exp = e (minus / plus)? DIGIT+
frac = decimal_point DIGIT+
int = zero / (digit1_9 DIGIT*)
minus = "-"
plus = "+"
zero = "0"
// ----- Types: strings -----
string "string"
= quotation_mark chars:char* quotation_mark { return chars.join(""); }
char
= unescaped
/ escape
sequence:(
'"'
/ "\\\\"
/ "/"
/ "b" { return "\\b"; }
/ "f" { return "\\f"; }
/ "n" { return "\\n"; }
/ "r" { return "\\r"; }
/ "t" { return "\\t"; }
/ "u" digits:$(HEXDIG HEXDIG HEXDIG HEXDIG)
{ return String.fromCharCode(parseInt(digits, 16)); }
)
{ return sequence; }
escape = "\\\\"
quotation_mark = '"'
unescaped = [^\\0-\\x1F\\x22\\x5C]
// See RFC 4234, Appendix B (http://tools.ietf.org/html/rfc4234).
DIGIT = [0-9]
HEXDIG = [0-9a-f]i
إضافة صغيرة ذات صلة إلى الخطأ. قمت بإعداد pegjs
عبر أداة تحميل pegjs . إنه يعمل على JS API تحت الغطاء الذي يستدعي parser.generate
كما أنه يؤدي أيضًا إلى نفس الخطأ.
بالمناسبة شكرا جزيلا للمشروع!
emmenko لا أعرف سبب عمل القواعد الخاصة بك مع واجهة برمجة التطبيقات (سأستمر في محاولة معرفة السبب) ، ولكن قواعدك كانت غير صحيحة ، يجب أن تكون قاعدة unescaped
يلي:
unescaped = !'"' [^\\0-\\x1F\\x22\\x5C]
أخبرني إذا كان هذا سيؤدي إلى حل المشكلة من جانبك
tdeekens إذا كان الخطأ نفسه (على سبيل المثال Expected ... but "\"" found.
) ، فتحقق مما إذا كانت القواعد النحوية صحيحة أو انشرها هنا
futagoza me و tdeekens في نفس الفريق ، لذا فهي نفس المشكلة 😅
سنبقيك على اطلاع! شكرا لدعمكم حتى الآن 🙏
لا أعرف لماذا كانت قواعدك تعمل مع API
لم نواجه مشكلة في ذلك لنكون صادقين. شكرا لتوضيح ذلك على أي حال!
وأنها تعمل الآن؟
للأسف لم يساعد ☹️
باستخدام القواعد النحوية ، PEG.js 0.10 ، Node 8.9.0 والإدخال foo = "bar"
، جربت ذلك عبر 3 طرق:
pegjs
CLIأظهر الثلاثة الخطأ نفسه: Line 1, column 7: Expected "(", boolean, date, datetime, number, string, or time but "\"" found.
إذا قمت بتغيير القواعد النحوية الخاصة بك ، فسيتم إصلاح هذا الخطأ لجميع المسارات الثلاثة:
// orignal
unescaped = [^\\0-\\x1F\\x22\\x5C]
// fixed
unescaped = !'"' [^\\0-\\x1F\\x22\\x5C]
بعد تطبيق القاعدة الثابتة ، هل يمكنك التحقق مما يلي:
أيضًا ، بعد التغيير والتبديل في الإدخال قليلاً ، أدركت أن قواعدك لا تأخذ في الاعتبار الأسطر الجديدة مثل المسافات البيضاء بشكل صحيح ، وهذا على الأرجح بسبب قاعدة ws
.
تحرير: هنا نص الاختبار الخاص بي:
/* eslint node/no-unsupported-features: 0 */
"use strict";
const { exec } = require( "child_process" );
const { readFileSync } = require( "fs" );
const { join } = require( "path" );
const { generate } = require( "pegjs" );
function test( parser ) {
try {
console.log( parser.parse( `foo = "bar"` ) );
} catch ( error ) {
if ( error.name !== "SyntaxError" ) throw error;
const loc = error.location.start;
console.log( `Line ${ loc.line }, column ${ loc.column }: ${ error.message }` );
}
}
const COMMAND = process.argv[ 2 ];
switch ( COMMAND ) {
case "api":
test( generate( readFileSync( join( __dirname, "grammar.pegjs" ), "utf8" ) ) );
break;
case "cli":
exec( "node node_modules/pegjs/bin/pegjs -o parser.js grammar.pegjs", error => {
if ( error ) console.error( error ), process.exit( 1 );
test( require( "./parser" ) );
} );
break;
default:
console.error( `Invalid command "${ COMMAND }" passed to test script.` );
process.exit( 1 );
}
شكرا جزيلا لردود الفعل! سنحاول غدًا اقتراحك وسنخبرك في أقرب وقت ممكن إذا كان ذلك مفيدًا. 🙏
شكرا على ملاحظاتك. نعتذر أولاً عن اللبس. أردت فقط أن أشير إلى أن المشكلة كانت موجودة أيضًا في أداة تحميل حزمة الويب. آسف أنه تسبب في ارتباك حول هذه المسألة.
لقد جربنا التحسين. إنه يعمل على إصلاح المحلل اللغوي بشكل عام ولكننا نواجه مشكلة جديدة الآن نواجه صعوبة في فهم سبب ذلك.
مثال على الاختبار هو (المزيد أدناه)
Object {
+ "attributeType": undefined,
"kind": "condition",
"operator": "=",
"target": "foo",
- "value": "bar",
+ "value": ",b,a,r",
"valueType": "string",
}
نعتقد أن الخطأ قد يكون على الأرجح من جانبنا ، فنحن لسنا متأكدين من مكانه بعد. يحدث هذا على سبيل المثال مع المدخلات التالية
categories.id != ("b33f8e3a-f8d1-476f-a595-2615c4b57556")
الذي يصبح
categories.id != (",b,3,3,f,8,e,3,a,-,f,8,d,1,-,4,7,6,f,-,a,5,9,5,-,2,6,1,5,c,4,b,5,7,5,5,6")
عند التحليل.
من الواضح أننا سنكون ممتنين للغاية للحصول على دليل ولكننا نفهم أيضًا ما إذا كان بإمكاننا دعمنا هناك.
عفوًا ، خطئي 😨 ، هذا يجب أن يصلح ذلك
unescaped = !'"' value:[^\\0-\\x1F\\x22\\x5C] { return value; }
شكرا للاستجابة الفائقة السرعة. إنه يساعد ولكن ليس عند استخدام CLI أو أداة تحميل webpack التي غالبًا ما تُرجع الخطأ الأولي SyntaxError: Expected "(", boolean, date, datetime, number, string, or time but "\"" found.
. شيء يحدث على سبيل المثال مع not(sku = "123")
أو مثال أكثر تعقيدًا lineItemTotal(sku = "SKU1" or list contains all (1,2,3), field.name, "third arg") = "10 EUR"
. هل لا يزال هناك شيء يجب القيام به مع الهارب؟
نعم ، اتضح أنه بسبب الهروب المزدوج. فيما يلي القواعد الثابتة:
ws "whitespace" = [ \t\n\r]*
char
= unescaped
/ escape
sequence:(
'"'
/ "\\"
/ "/"
/ "b" { return "\b"; }
/ "f" { return "\f"; }
/ "n" { return "\n"; }
/ "r" { return "\r"; }
/ "t" { return "\t"; }
/ "u" digits:$(HEXDIG HEXDIG HEXDIG HEXDIG)
{ return String.fromCharCode(parseInt(digits, 16)); }
)
{ return sequence; }
escape = "\\"
unescaped = !'"' value:[^\0-\x1F\x22\x5C] { return value; }
تحرير: يبدو أنك قد ترغب في العمل على القواعد التي تحلل المثال المعقد: lineItemTotal(sku = "SKU1" or list contains all (1,2,3), field.name, "third arg") = "10 EUR"
، تقوم حاليًا بإخراج عقدة غريبة "kind":"condition"
شكرا جزيلا للمساعدة والمشورة. يبدو أنه يحل المشاكل التي كانت لدينا. سننظر في النصيحة المتعلقة بالعقدة "الشرطية".
على الرحب والسعة 😄