Pegjs: Inkonsistentes Verhalten des generierten Parsers

Erstellt am 8. MĂ€rz 2018  Â·  15Kommentare  Â·  Quelle: pegjs/pegjs

Problemtyp

  • Fehlerbericht:

Voraussetzungen

  • Können Sie das Problem reproduzieren?: _ja_
  • Haben Sie die Repository-Probleme durchsucht
  • Hast du in den Foren nachgesehen
  • Haben Sie eine Websuche durchgefĂŒhrt (Google, Yahoo

Beschreibung

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

Schritte zum Reproduzieren

  1. Verschiebe die Grammatik in eine eigene Datei grammar.pegjs
  2. Generieren Sie den Parser mit der CLI
pegjs -o parser.js grammar.pegjs
  1. Entferne das peg.generate('...') und ersetze es durch den neuen Parser
const parser = require('./parser');
parser.parse('...');
  1. FĂŒhren Sie die Tests durch

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) 

Software

  • PEG.js: 0.10.0
  • Node.js: 8.9.1
  • NPM oder Garn: [email protected]
  • Browser: Chrome
  • Betriebssystem: OSX
  • Herausgeber: VSCode
question

Alle 15 Kommentare

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:

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

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

  • Sie erhalten die gleiche Fehlermeldung oder einen anderen Fehler
  • Sie tun etwas anderes (oder zusĂ€tzliche Schritte) als das, was ich erwĂ€hnt habe
  • verwenden Sie Optionen, wenn Sie die PEG.js-API verwenden?

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

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen