Pour le moment, j'utilise l'API JS pour générer l'analyseur lors de l'exécution. Cela fonctionne bien.
J'ai ensuite essayé de générer l'analyseur à l'aide de la CLI, pour éviter de le générer pendant l'exécution. Lorsque je l'utilise, j'obtiens des erreurs (~ la moitié de mes tests d'analyse des erreurs de lancement de chaîne).
grammar.pegjs
pegjs -o parser.js grammar.pegjs
peg.generate('...')
et remplacez-le par le nouveau parseurconst parser = require('./parser');
parser.parse('...');
Comportement attendu:
Je m'attendrais à ce que l'analyseur généré à partir de la CLI fonctionne de la même manière que l'analyseur généré à partir de l'API JS.
Comportement réel :
En utilisant l'API JS, lorsque je passe cette chaîne ( 'foo = "bar"'
) à l'analyseur, j'obtiens l'AST suivant :
{
kind: 'condition',
target: 'foo',
operator: '=',
value: 'bar',
valueType: 'string',
attributeType: undefined
}
Cependant, lorsque j'utilise l'analyseur "généré" à l'aide de la CLI et que je transmets la même chaîne ( 'foo = "bar"'
), j'obtiens l'erreur suivante :
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]
Sympa, tu l'as bien rempli , maintenant nous avons juste besoin de la grammaire et je peux t'aider
Voici:
// 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
Un petit ajout lié au bug. J'ai configuré pegjs
via le pegjs-loader . Il fonctionne sur l'API JS sous le capot en appelant parser.generate
cela conduit également à la même erreur.
Merci beaucoup pour le projet d'ailleurs !
@emmenko Je ne sais pas pourquoi votre grammaire fonctionnait avec l'API (je continuerai d'essayer de savoir pourquoi), mais votre grammaire était incorrecte, la règle unescaped
devrait être :
unescaped = !'"' [^\\0-\\x1F\\x22\\x5C]
Dites-moi si cela résout le problème de votre côté
@tdeekens Si c'est la même erreur (par exemple Expected ... but "\"" found.
), alors vérifiez si votre grammaire est correcte, ou postez-la ici
@futagoza moi et @tdeekens sommes dans la même équipe, c'est donc le même problème 😅
Nous vous tiendrons au courant! Merci pour votre soutien jusqu'à présent
Je ne sais pas pourquoi votre grammaire fonctionnait avec l'API
Nous n'avons jamais eu de problème avec ça pour être honnête. Merci de l'avoir signalé en tout cas !
Est ce que ça marche maintenant?
Malheureusement, cela n'a pas aidé ☹️
En utilisant votre grammaire, PEG.js 0.10, Node 8.9.0 et l'entrée foo = "bar"
, j'ai essayé ceci via 3 routes :
pegjs
CLITous les 3 ont montré la même erreur : Line 1, column 7: Expected "(", boolean, date, datetime, number, string, or time but "\"" found.
Si je change votre grammaire, cela corrige cette erreur pour les 3 routes :
// orignal
unescaped = [^\\0-\\x1F\\x22\\x5C]
// fixed
unescaped = !'"' [^\\0-\\x1F\\x22\\x5C]
Après avoir appliqué la règle fixe, pouvez-vous vérifier si :
De plus, après avoir légèrement modifié l'entrée, j'ai réalisé que votre grammaire ne prend pas correctement en compte les nouvelles lignes en tant qu'espaces blancs, c'est probablement à cause de votre règle ws
.
EDIT : voici mon script de test :
/* 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 );
}
Merci beaucoup pour les retours ! Nous essaierons demain avec votre suggestion et nous vous ferons savoir dès que possible si cela vous a aidé. 🙏
Merci pour les commentaires. Mes excuses d'abord pour la confusion. Je voulais juste souligner que le problème était également dans le webpack-loader. Désolé, cela a causé de la confusion sur ce problème.
Nous avons essayé l'amélioration. Il corrige l'analyseur en général, mais nous rencontrons un nouveau problème maintenant dont nous avons du mal à comprendre la raison.
Un exemple est d'un test est (plus ci-dessous)
Object {
+ "attributeType": undefined,
"kind": "condition",
"operator": "=",
"target": "foo",
- "value": "bar",
+ "value": ",b,a,r",
"valueType": "string",
}
Nous pensons que l'erreur pourrait probablement être de notre côté, nous ne savons tout simplement pas encore où. Cela se produit par exemple avec l'entrée suivante
categories.id != ("b33f8e3a-f8d1-476f-a595-2615c4b57556")
qui devient
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")
lors de l'analyse.
Nous serions évidemment super reconnaissants pour un indice mais comprenons également si peut nous y soutenir.
Oups, mon erreur , ça devrait arranger ça
unescaped = !'"' value:[^\\0-\\x1F\\x22\\x5C] { return value; }
Merci pour la réponse ultra rapide. Cela aide, mais pas lors de l'utilisation de la CLI ou du chargeur Webpack qui renvoient souvent l'erreur initiale de SyntaxError: Expected "(", boolean, date, datetime, number, string, or time but "\"" found.
. Quelque chose qui arrive par exemple avec un not(sku = "123")
ou un exemple plus complexe lineItemTotal(sku = "SKU1" or list contains all (1,2,3), field.name, "third arg") = "10 EUR"
. Cela pourrait-il encore avoir quelque chose à voir avec l'évasion ?
Oui, il s'avère que c'est à cause de la double évasion. Voici les règles fixes :
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; }
EDIT : il semble que vous vouliez travailler sur les règles qui analysent l'exemple complexe : lineItemTotal(sku = "SKU1" or list contains all (1,2,3), field.name, "third arg") = "10 EUR"
, il génère actuellement un nœud "kind":"condition"
étrange
Merci beaucoup pour l'aide et les conseils. Cela semble résoudre les problèmes que nous avions. Nous examinerons les conseils concernant le nœud "condition".
De rien