Pegjs: Comportement incohérent de l'analyseur généré

Créé le 8 mars 2018  ·  15Commentaires  ·  Source: pegjs/pegjs

Type de probleme

  • Rapport d'erreur:

Conditions préalables

  • Pouvez-vous reproduire le problème ? : _oui_
  • Avez-vous recherché les problèmes du référentiel ? : _oui_
  • As-tu consulté
  • Avez-vous effectué une recherche sur le Web (google, yahoo, etc.) ? : _oui_

La description

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).

Étapes pour reproduire

  1. Déplacer la grammaire dans son propre fichier grammar.pegjs
  2. Générer l'analyseur à l'aide de la CLI
pegjs -o parser.js grammar.pegjs
  1. Supprimez le peg.generate('...') et remplacez-le par le nouveau parseur
const parser = require('./parser');
parser.parse('...');
  1. Exécuter les tests

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) 

Logiciel

  • PEG.js : 0.10.0
  • Node.js : 8.9.1
  • NPM ou fil : [email protected]
  • Navigateur : Chrome
  • OS : OS X
  • Editeur : VSCode
question

Tous les 15 commentaires

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 :

  1. https://pegjs.org/online
  2. API PEG.js
  3. pegjs CLI

Tous 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 :

  • vous obtenez le même message d'erreur ou une erreur différente
  • vous faites quelque chose de différent (ou des étapes supplémentaires) de ce que j'ai mentionné
  • utilisez-vous des options lors de l'utilisation de l'API PEG.js

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

Cette page vous a été utile?
0 / 5 - 0 notes