Pegjs: рдЙрддреНрдкрдиреНрди рдкрд╛рд░реНрд╕рд░ рдХрд╛ рдЕрд╕рдВрдЧрдд рд╡реНрдпрд╡рд╣рд╛рд░

рдХреЛ рдирд┐рд░реНрдорд┐рдд 8 рдорд╛рд░реНрдЪ 2018  ┬╖  15рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ  ┬╖  рд╕реНрд░реЛрдд: pegjs/pegjs

рд╡рд┐рд╖рдп рд╡рд░реНрдЧ

  • рдмрдЧ рд░рд┐рдкреЛрд░реНрдЯ:

рдЖрд╡рд╢реНрдпрдХ рд╢рд░реНрддреЗрдВ

  • рдХреНрдпрд╛ рдЖрдк рдЗрд╕ рдореБрджреНрджреЗ рдХреЛ рдкреБрди: рдкреЗрд╢ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ?: _yes_
  • рдХреНрдпрд╛ рдЖрдкрдиреЗ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХреЗ рдореБрджреНрджреЛрдВ рдХреА рдЦреЛрдЬ рдХреА ?: _yes_
  • рдХреНрдпрд╛ рдЖрдкрдиреЗ рдордВрдЪреЛрдВ рдХреА рдЬрд╛рдВрдЪ рдХреА ?: _no_
  • рдХреНрдпрд╛ рдЖрдкрдиреЗ рд╡реЗрдм рдЦреЛрдЬ рдХреА рдереА (рдЧреВрдЧрд▓, рдпрд╛рд╣реВ, рдЖрджрд┐)?: _yes_

рд╡рд┐рд╡рд░рдг

рдлрд┐рд▓рд╣рд╛рд▓ рдореИрдВ рд░рдирдЯрд╛рдЗрдо рдкрд░ рдкрд╛рд░реНрд╕рд░ рдЬреЗрдирд░реЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЬреЗрдПрд╕ рдПрдкреАрдЖрдИ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣рд╛ рд╣реВрдВред рдпрд╣ рдареАрдХ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред

рдлрд┐рд░ рдореИрдВрдиреЗ рд░рдирдЯрд╛рдЗрдо рдХреЗ рджреМрд░рд╛рди рдЗрд╕реЗ рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП, рд╕реАрдПрд▓рдЖрдИ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкрд╛рд░реНрд╕рд░ рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд┐рдпрд╛ред рдЬрдм рдореИрдВ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реВрдВ, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдореБрдЭреЗ рддреНрд░реБрдЯрд┐рдпрд╛рдВ рдорд┐рд▓рддреА рд╣реИрдВ (~ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдереНрд░реЛ рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХреЛ рдкрд╛рд░реНрд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдореЗрд░реЗ рдЖрдзреЗ рдкрд░реАрдХреНрд╖рдг)ред

рдкреНрд░рдЬрдирди рдХрд░рдиреЗ рдХрд┐ рдкреНрд░рдХреНрд░рд┐рдпрд╛

  1. рд╡реНрдпрд╛рдХрд░рдг рдХреЛ рдЙрд╕рдХреА рдЕрдкрдиреА рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рд▓реЗ рдЬрд╛рдПрдБ grammar.pegjs
  2. CLI рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкрд╛рд░реНрд╕рд░ рдЙрддреНрдкрдиреНрди рдХрд░реЗрдВ
pegjs -o parser.js grammar.pegjs
  1. peg.generate('...') рдирд┐рдХрд╛рд▓реЗрдВ рдФрд░ рдЗрд╕реЗ рдирдП рдкрд╛рд░реНрд╕рд░ рд╕реЗ рдмрджрд▓реЗрдВ
const parser = require('./parser');
parser.parse('...');
  1. рдкрд░реАрдХреНрд╖рдг рдЪрд▓рд╛рдПрдВ

рдЕрдкреЗрдХреНрд╖рд┐рдд рд╡реНрдпрд╡рд╣рд╛рд░:
рдореИрдВ рдЙрдореНрдореАрдж рдХрд░рддрд╛ рд╣реВрдВ рдХрд┐ рд╕реАрдПрд▓рдЖрдИ рд╕реЗ рдЬреЗрдирд░реЗрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдкрд╛рд░реНрд╕рд░ рдЬреЗрдПрд╕ рдПрдкреАрдЖрдИ рд╕реЗ рдЬреЗрдирд░реЗрдЯ рдХрд┐рдП рдЧрдП рдкрд╛рд░реНрд╕рд░ рдХреЗ рд╕рдорд╛рди рд╣реА рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред

рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╡реНрдпрд╡рд╣рд╛рд░:
рдЬреЗрдПрд╕ рдПрдкреАрдЖрдИ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП, рдЬрдм рдореИрдВ рдЗрд╕ рд╕реНрдЯреНрд░рд┐рдВрдЧ ( 'foo = "bar"' ) рдХреЛ рдкрд╛рд░реНрд╕рд░ рдореЗрдВ рдкрд╛рд╕ рдХрд░рддрд╛ рд╣реВрдВ рддреЛ рдореБрдЭреЗ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдПрдПрд╕рдЯреА рдорд┐рд▓рддрд╛ рд╣реИ:

{
  kind: 'condition',
  target: 'foo',
  operator: '=',
  value: 'bar',
  valueType: 'string',
  attributeType: undefined
}

рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЬрдм рдореИрдВ рд╕реАрдПрд▓рдЖрдИ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ "рдЬреЗрдирд░реЗрдЯреЗрдб" рдкрд╛рд░реНрд╕рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реВрдВ, рдФрд░ рдЙрд╕реА рд╕реНрдЯреНрд░рд┐рдВрдЧ рдХреЛ рдкрд╛рд╕ рдХрд░рддрд╛ рд╣реВрдВ ( '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
  • Node.js: 8.9.1
  • рдПрдирдкреАрдПрдо рдпрд╛ рдпрд╛рд░реНрди: [email protected]
  • рдмреНрд░рд╛рдЙрдЬрд╝рд░: рдХреНрд░реЛрдо
  • рдУрдПрд╕: рдУрдПрд╕рдПрдХреНрд╕
  • рд╕рдВрдкрд╛рджрдХ: рд╡реАрдПрд╕рд╕реАрдУрдбреА

рд╕рднреА 15 рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

рдмрдврд╝рд┐рдпрд╛, рдЖрдкрдиреЗ рд╕рд╣реА рднрд░рд╛ , рдЕрдм рд╣рдореЗрдВ рдХреЗрд╡рд▓ рд╡реНрдпрд╛рдХрд░рдг рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдФрд░ рдореИрдВ рдЖрдкрдХреА рд╕рд╣рд╛рдпрддрд╛ рдХрд░ рд╕рдХрддрд╛ рд╣реВрдБ

рд╣реЗрдпрд░ рдпреВ рдЧреЛ:

// 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-loader рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ pegjs рд╕реЗрдЯрдЕрдк рдХрд░рддрд╛ рд╣реВрдВ ред рдпрд╣ рдЬреЗрдПрд╕ рдПрдкреАрдЖрдИ рдкрд░ parser.generate рдХреЙрд▓рд┐рдВрдЧ рд╣реБрдб рдХреЗ рддрд╣рдд рд╕рдВрдЪрд╛рд▓рд┐рдд рд╣реЛрддрд╛ рд╣реИ, рдпрд╣ рднреА рдЙрд╕реА рддреНрд░реБрдЯрд┐ рдХреА рдУрд░ рдЬрд╛рддрд╛ рд╣реИред

рд╡реИрд╕реЗ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд▓рд┐рдП рдмрд╣реБрдд рдзрдиреНрдпрд╡рд╛рдж!

@emmenko рдореБрдЭреЗ рдирд╣реАрдВ рдкрддрд╛ рдХрд┐ рдЖрдкрдХрд╛ рд╡реНрдпрд╛рдХрд░рдг рдПрдкреАрдЖрдИ рдХреЗ рд╕рд╛рде рдХреНрдпреЛрдВ рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рдерд╛ (рдХреНрдпреЛрдВ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рдирд╛ рдЬрд╛рд░реА рд░рдЦреЗрдЧрд╛), рд▓реЗрдХрд┐рди рдЖрдкрдХрд╛ рд╡реНрдпрд╛рдХрд░рдг рдЧрд▓рдд рдерд╛, unescaped рдирд┐рдпрдо рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП:

unescaped = !'"' [^\\0-\\x1F\\x22\\x5C]

рдореБрдЭреЗ рдмрддрд╛рдПрдВ рдХрд┐ рдХреНрдпрд╛ рдпрд╣ рдЖрдкрдХреА рддрд░рдл рд╕реЗ рд╕рдорд╕реНрдпрд╛ рдХреЛ рдареАрдХ рдХрд░рддрд╛ рд╣реИ

@tdeekens рдпрджрд┐ рдпрд╣ рд╡рд╣реА рддреНрд░реБрдЯрд┐ рд╣реИ (рдЬреИрд╕реЗ Expected ... but "\"" found. ), рддреЛ рдЬрд╛рдВрдЪреЗрдВ рдХрд┐ рдХреНрдпрд╛ рдЖрдкрдХрд╛ рд╡реНрдпрд╛рдХрд░рдг рд╕рд╣реА рд╣реИ, рдпрд╛ рдЗрд╕реЗ рдпрд╣рд╛рдВ рдкреЛрд╕реНрдЯ рдХрд░реЗрдВ

@futagoza me рдФрд░ @tdeekens рдПрдХ рд╣реА рдЯреАрдо рдореЗрдВ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рдпрд╣ рд╡рд╣реА рдореБрджреНрджрд╛ рд╣реИ

рд╣рдо рдЖрдкрдХреЛ рддреИрдирд╛рдд рд░рдЦреЗрдВрдЧреЗ! рдЕрдм рддрдХ рдЖрдкрдХреЗ рд╕рдорд░реНрдерди рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж ЁЯЩП

рдореБрдЭреЗ рдирд╣реАрдВ рдкрддрд╛ рдХрд┐ рдЖрдкрдХрд╛ рд╡реНрдпрд╛рдХрд░рдг рдПрдкреАрдЖрдИ рдХреЗ рд╕рд╛рде рдХреНрдпреЛрдВ рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рдерд╛

рдИрдорд╛рдирджрд╛рд░ рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдореЗрдВ рдЗрд╕рд╕реЗ рдХрднреА рдХреЛрдИ рд╕рдорд╕реНрдпрд╛ рдирд╣реАрдВ рдереАред рд╡реИрд╕реЗ рднреА рдЗрд╕реЗ рдЗрдВрдЧрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж!

рдХреНрдпрд╛ рдЕрдм рдпрд╣ рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реИ?

рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ рдЗрд╕рдиреЗ рдорджрдж рдирд╣реАрдВ рдХреА я╕П

рдЖрдкрдХреЗ рд╡реНрдпрд╛рдХрд░рдг, PEG.js 0.10, Node 8.9.0 рдФрд░ рдЗрдирдкреБрдЯ foo = "bar" , рдореИрдВрдиреЗ рдЗрд╕реЗ 3 рдорд╛рд░реНрдЧреЛрдВ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЖрдЬрдорд╛рдпрд╛:

  1. https://pegjs.org/online
  2. рдЦреВрдВрдЯреА.рдЬреЗрдПрд╕ рдПрдкреАрдЖрдИ
  3. pegjs рд╕реАрдПрд▓рдЖрдИ

рд╕рднреА 3 рдиреЗ рдПрдХ рд╣реА рддреНрд░реБрдЯрд┐ рджрд┐рдЦрд╛рдИ: Line 1, column 7: Expected "(", boolean, date, datetime, number, string, or time but "\"" found.

рдЕрдЧрд░ рдореИрдВ рдЖрдкрдХрд╛ рд╡реНрдпрд╛рдХрд░рдг рдмрджрд▓рддрд╛ рд╣реВрдВ, рддреЛ рдпрд╣ рд╕рднреА 3 рдорд╛рд░реНрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдЗрд╕ рддреНрд░реБрдЯрд┐ рдХреЛ рдареАрдХ рдХрд░рддрд╛ рд╣реИ:

// orignal
unescaped = [^\\0-\\x1F\\x22\\x5C]

// fixed
unescaped = !'"' [^\\0-\\x1F\\x22\\x5C]

рдирд┐рд╢реНрдЪрд┐рдд рдирд┐рдпрдо рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдХреНрдпрд╛ рдЖрдк рдЬрд╛рдБрдЪ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐:

  • рдЖрдкрдХреЛ рд╡рд╣реА рддреНрд░реБрдЯрд┐ рд╕рдВрджреЗрд╢ рдпрд╛ рдХреЛрдИ рднрд┐рдиреНрди рддреНрд░реБрдЯрд┐ рдорд┐рд▓ рд░рд╣реА рд╣реИ
  • рдореИрдВрдиреЗ рдЬреЛ рдЙрд▓реНрд▓реЗрдЦ рдХрд┐рдпрд╛ рд╣реИ рдЙрд╕рд╕реЗ рдЖрдк рдХреБрдЫ рдЕрд▓рдЧ (рдпрд╛ рдЕрддрд┐рд░рд┐рдХреНрдд рдХрджрдо) рдХрд░ рд░рд╣реЗ рд╣реИрдВ
  • рдХреНрдпрд╛ рдЖрдк PEG.js API рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╕рдордп рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ

рд╕рд╛рде рд╣реА, рдЗрдирдкреБрдЯ рдореЗрдВ рдереЛрдбрд╝рд╛ рдмрджрд▓рд╛рд╡ рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдореИрдВрдиреЗ рдорд╣рд╕реВрд╕ рдХрд┐рдпрд╛ рдХрд┐ рдЖрдкрдХрд╛ рд╡реНрдпрд╛рдХрд░рдг рдиреНрдпреВрд▓рд╛рдЗрдиреНрд╕ рдХреЗ рд▓рд┐рдП рд╡реНрд╣рд╛рдЗрдЯрд╕реНрдкреЗрд╕ рдХреЗ рд░реВрдк рдореЗрдВ рд╕рд╣реА рдврдВрдЧ рд╕реЗ рдЦрд╛рддрд╛ рдирд╣реАрдВ рд╣реИ, рдпрд╣ рдЖрдкрдХреЗ 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; }

рд╕реБрдкрд░ рддреНрд╡рд░рд┐рдд рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред рдпрд╣ рдорджрдж рдХрд░рддрд╛ рд╣реИ рд▓реЗрдХрд┐рди рд╕реАрдПрд▓рдЖрдИ рдпрд╛ рд╡реЗрдмрдкреИрдХ-рд▓реЛрдбрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╕рдордп рдирд╣реАрдВ рдЬреЛ рдЕрдХреНрд╕рд░ 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" рдиреЛрдб рдХрд╛ рдЙрддреНрдкрд╛рджрди рдХрд░ рд░рд╣рд╛ рд╣реИ

рдорджрдж рдФрд░ рд╕рд▓рд╛рд╣ рдХреЗ рд▓рд┐рдП рдмрд╣реБрдд-рдмрд╣реБрдд рдзрдиреНрдпрд╡рд╛рджред рдРрд╕рд╛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рд╣рдорд╛рд░реА рд╕рдорд╕реНрдпрд╛рдУрдВ рдХрд╛ рд╕рдорд╛рдзрд╛рди рд╣реЛ рдЧрдпрд╛ рд╣реИред рд╣рдо "рд╣рд╛рд▓рдд" рдиреЛрдб рдХреЗ рд╕рдВрдмрдВрдз рдореЗрдВ рд╕рд▓рд╛рд╣ рджреЗрдЦреЗрдВрдЧреЗред

рдЖрдкрдХрд╛ рд╕реНрд╡рд╛рдЧрдд рд╣реИ

рдХреНрдпрд╛ рдпрд╣ рдкреГрд╖реНрда рдЙрдкрдпреЛрдЧреА рдерд╛?
0 / 5 - 0 рд░реЗрдЯрд┐рдВрдЧреНрд╕

рд╕рдВрдмрдВрдзрд┐рдд рдореБрджреНрджреЛрдВ

dmajda picture dmajda  ┬╖  15рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

richb-hanover picture richb-hanover  ┬╖  7рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

marek-baranowski picture marek-baranowski  ┬╖  6рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

alanmimms picture alanmimms  ┬╖  10рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

futagoza picture futagoza  ┬╖  13рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ