Im Moment verwende ich die JS-API, um den Parser zur Laufzeit zu generieren. Dies funktioniert gut.
Ich habe dann versucht, den Parser mit der CLI zu generieren, um eine Generierung wÀhrend der Laufzeit zu vermeiden. Wenn ich es verwende, erhalte ich jedoch Fehler (~ die HÀlfte meiner Tests zum Analysieren der String-Throw-Fehler).
grammar.pegjs
pegjs -o parser.js grammar.pegjs
peg.generate('...')
und ersetze es durch den neuen Parserconst parser = require('./parser');
parser.parse('...');
Erwartetes Verhalten:
Ich wĂŒrde erwarten, dass der generierte Parser von der CLI genauso funktioniert wie der generierte Parser von der JS-API.
TatsÀchliches Verhalten:
Wenn ich die JS-API verwende, wenn ich diese Zeichenfolge ( 'foo = "bar"'
) an den Parser ĂŒbergebe, erhalte ich die folgende AST:
{
kind: 'condition',
target: 'foo',
operator: '=',
value: 'bar',
valueType: 'string',
attributeType: undefined
}
Wenn ich jedoch den "generierten" Parser mit der CLI verwende und die gleiche Zeichenfolge ĂŒbergebe ( 'foo = "bar"'
), erhalte ich die folgende Fehlermeldung:
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]
Schön, du hast es richtig ausgefĂŒllt đ, jetzt brauchen wir nur noch die Grammatik und ich kann dir helfen đ
Bitte schön:
// 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
Eine kleine verwandte ErgÀnzung zum Fehler. Ich richte pegjs
ĂŒber den pegjs-loader ein . Es arbeitet auf der JS-API unter der Haube und ruft parser.generate
, was ebenfalls zum gleichen Fehler fĂŒhrt.
Vielen Dank ĂŒbrigens fĂŒr das Projekt!
@emmenko Ich weiĂ nicht, warum Ihre Grammatik mit der API funktioniert hat (werde weiterhin versuchen herauszufinden, warum), aber Ihre Grammatik war falsch, die Regel unescaped
sollte lauten:
unescaped = !'"' [^\\0-\\x1F\\x22\\x5C]
Sagen Sie mir, ob dies das Problem von Ihrer Seite behebt
@tdeekens Wenn es der gleiche Fehler ist (zB Expected ... but "\"" found.
), dann ĂŒberprĂŒfe, ob deine Grammatik korrekt ist, oder poste sie hier
@futagoza ich und @tdeekens sind im selben Team, also ist es das gleiche Problem đ
Wir halten Sie auf dem Laufenden! Danke fĂŒr eure bisherige UnterstĂŒtzung
Ich weiĂ nicht, warum deine Grammatik mit der API funktioniert hat
Damit hatten wir ehrlich gesagt nie ein Problem. Danke trotzdem fĂŒr den Hinweis!
Funktioniert es jetzt?
Hat leider nicht geholfen âčïž
Mit Ihrer Grammatik, PEG.js 0.10, Node 8.9.0 und der Eingabe foo = "bar"
ich dies ĂŒber 3 Routen versucht:
pegjs
CLIAlle 3 zeigten den gleichen Fehler: Line 1, column 7: Expected "(", boolean, date, datetime, number, string, or time but "\"" found.
Wenn ich Ihre Grammatik Ă€ndere, behebt es diesen Fehler fĂŒr alle 3 Routen:
// orignal
unescaped = [^\\0-\\x1F\\x22\\x5C]
// fixed
unescaped = !'"' [^\\0-\\x1F\\x22\\x5C]
Nachdem Sie die feste Regel angewendet haben, können Sie ĂŒberprĂŒfen, ob:
Nachdem ich die Eingabe ein wenig optimiert hatte, stellte ich auĂerdem fest, dass Ihre Grammatik ZeilenumbrĂŒche nicht korrekt als Leerzeichen berĂŒcksichtigt, dies liegt höchstwahrscheinlich an Ihrer ws
Regel.
EDIT: Hier ist mein Testskript:
/* 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 );
}
Vielen Dank fĂŒr die RĂŒckmeldung! Wir werden es morgen mit Ihrem Vorschlag versuchen und Ihnen so schnell wie möglich mitteilen, ob das geholfen hat. đ
Danke fĂŒr die RĂŒckmeldung. Entschuldigung erstmal fĂŒr die Verwirrung. Ich wollte nur darauf hinweisen, dass das Problem auch im Webpack-Loader lag. Es tut mir leid, dass das zu Verwirrung in diesem Thema gefĂŒhrt hat.
Wir haben die Verbesserung ausprobiert. Es behebt den Parser im Allgemeinen, aber wir stoĂen auf ein neues Problem, jetzt können wir den Grund nur schwer verstehen.
Ein Beispiel fĂŒr einen Test ist (mehr unten)
Object {
+ "attributeType": undefined,
"kind": "condition",
"operator": "=",
"target": "foo",
- "value": "bar",
+ "value": ",b,a,r",
"valueType": "string",
}
Wir denken, dass der Fehler wahrscheinlich auf unserer Seite liegt, wir sind uns nur noch nicht sicher, wo. Dies geschieht zB mit der folgenden Eingabe
categories.id != ("b33f8e3a-f8d1-476f-a595-2615c4b57556")
was wird
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")
wenn geparst.
Wir wĂ€ren natĂŒrlich super dankbar fĂŒr einen Hinweis, haben aber auch VerstĂ€ndnis, wenn wir uns dort unterstĂŒtzen können.
Hoppla, mein Fehler đš, das sollte das beheben
unescaped = !'"' value:[^\\0-\\x1F\\x22\\x5C] { return value; }
Danke fĂŒr die superschnelle Antwort. Es hilft, aber nicht, wenn Sie die CLI oder den Webpack-Loader verwenden, die oft den anfĂ€nglichen Fehler SyntaxError: Expected "(", boolean, date, datetime, number, string, or time but "\"" found.
. Etwas, das zum Beispiel mit einem not(sku = "123")
oder einem komplexeren Beispiel lineItemTotal(sku = "SKU1" or list contains all (1,2,3), field.name, "third arg") = "10 EUR"
passiert. Hat das vielleicht noch etwas mit der Flucht zu tun?
Ja, es stellt sich heraus, dass es an der doppelten Flucht liegt. Hier die festen Regeln:
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; }
BEARBEITEN: Es scheint, dass Sie an den Regeln arbeiten möchten, die das komplexe Beispiel analysieren: lineItemTotal(sku = "SKU1" or list contains all (1,2,3), field.name, "third arg") = "10 EUR"
, es gibt derzeit einen seltsamen "kind":"condition"
Knoten aus
Vielen Dank fĂŒr die Hilfe und RatschlĂ€ge. Es scheint die Probleme zu lösen, die wir hatten. Wir werden uns die Hinweise zum Knoten "Bedingung" ansehen.
Gern geschehen