Pegjs: تطبيق القواعد البارامترية

تم إنشاؤها على ٢٥ أغسطس ٢٠١١  ·  29تعليقات  ·  مصدر: pegjs/pegjs

سيكون من الرائع أن تكون قادرًا على تحديد معايير للقواعد باستخدام المتغيرات ؛

   = '\"' parse_contents '\"' ->
   / '\'' parse_contents('\'') '\'' ->
   / '+' parse_contents('+') '+' -> /* sure why not :) */

parse_contents(terminator='\"')
    = ('\\' terminator / !terminator .)+ -> return stuff
feature

التعليق الأكثر فائدة

شكرا لك على وقتك في الشرح

ربما سيكون لدي سؤال آخر لك بعد عشر سنوات. أتمنى لك عام 2020 جيدًا

ال 29 كومينتر

هل لديك حالة استخدام محددة حيث سيوفر لك ذلك قدرًا كبيرًا من العمل أو يجعل شيئًا مستحيلًا حاليًا؟

إنه يجعل تحليل مستويات المسافة البادئة أسهل بكثير من خلال استدعاء القواعد التي تم تمرير المستوى لها كوسيطة.

أيضًا ، في منطق جاف خالص ، عند القيام بأشياء مثل "أشياء محددة بواسطة هذه الشخصية مع تسلسل هروب مثل" ، يكون من الأجمل استدعاء شيء مثل delimited('\'', '\\') من مجرد استخدام القاعدة (وأفعالها!) ثلاث مرات .

كان ينبغي أن يكون أكثر وضوحا. بكلمة "محددة" كنت أبحث عن شيء مثل "كنت أعمل على قواعد اللغة س وهناك 5 قواعد يمكن دمجها في واحدة ، ها هي:" أي أنني أردت أن أرى العالم الحقيقي استخدام حالة ورمز العالم الحقيقي. من هذا المنطلق يمكنني الحكم بشكل أفضل في الحالات التي ستكون فيها هذه الميزة مفيدة ولعدد الأشخاص.

من فضلك لا تأخذ هذا لأنني أعارض هذه الميزة في حد ذاتها. لا أرغب عمومًا في تنفيذ ميزات مفيدة فقط لجزء ضئيل من اللغات أو المطورين بسبب التعقيد وتكلفة التنفيذ. وفي هذه الحالة تكون التكلفة مرتفعة نسبيًا.

بمجرد كتابة المحلل اللغوي لجافا سكريبت ، كان بإمكاني الحصول على string = delimited_by('\'') / delimited_by('\"') ثم لاحقًا regexp = delimited_by('/') .

مؤخرًا ، كنت أكتب محللًا للغة خاصة بي. لدي شيء من هذا القبيل في إطار عمل PEG كتبته لـ Python:

LeftAssociative(op, subrule): l:subrule rest:(op subrule)* -> a_recursive_function_that_reverses_rest_and_makes_bintrees(l, op, subrule)

ويمكنني بعد ذلك كتابة:

...
exp_mult = LeftAssociative(/[\*\/%]/, exp_paren)
exp_add = LeftAssociative(/[\+-]/, exp_mult)

نظرًا لأن لدي العديد من مستويات الأولوية كما هو الحال في C ++ (جميع مشغليها بالإضافة إلى عدد قليل آخر) ، سأسمح لك بتخيل مدى الفائدة التي يمكن أن تكون عليها. لم أنتهي بعد من تعبيرات التحليل ، لكنني استخدمها بالفعل 12 مرة.

سيكون هذا رائعًا إذا تم دمجه مع ميزة "استيراد"

require(CommaSeparatedList.pegjs)
require(StringLiteral.pegjs)
require(IntegerLiteral.pegjs)

...

Function
 = name:FuncName "(" args:CommaSeparatedList(Literal)  ")" 

Hash
= "{"   props:CommaSeparatedList(Prop)   "}"

Prop
= Key ":" Literal

Literal =
  StringLiteral / IntegerLiteral

(هذا أكثر تعقيدًا قليلاً من طلب OP ، لكنه بدا قريبًا جدًا من تبرير موضوعه الخاص.)

أقوم بإنشاء محلل مخطط R5RS بمساعدة PEG.js. كل شيء وردية باستثناء quasiquotations ، والتي تتطلب تحليلاً مدركًا للسياق. قد يكون من المفيد أن تكون قادرًا على وضع معايير للقواعد من أجل إنشاء قاعدة سريعة من النماذج ، وتجنب قدر كبير من المعالجة اللاحقة المحرجة. على سبيل المثال ، قد تبدو القواعد النحوية المبسطة مثل:

    quasiquotation = qq[1]
    qq[n] = "`" qq_template[n]
    qq_template[0] = expression
    qq_template[n] = simple_datum / list_qq_template[n] / unquotation[n]
    list_qq_template[n] = "(" qq_template[n]* ")" / qq[n+1]
    unquotation[n] = "," qq_template[n-1]

أنا مهتم بالمساهمة في تطوير هذه الميزة إذا كان هناك أي اهتمام بإضافتها إلى الأداة.

السبب الرئيسي للقيام بذلك هو دعم القواعد النحوية الحساسة للسياق ، والتي إذا لم أكن مخطئًا ، فإن معظم اللغات الشائعة هي (أعرف على وجه اليقين أن لغة C و python تحتويان على أشياء خاصة بالسياق). وفقًا لتريفور جيم ، فإن Haskell أيضًا ليست خالية من السياق ، وتؤكد أن معظم مقاييس اللغة ليست:

http://trevorjim.com/haskell-is-not-context-free/
http://trevorjim.com/how-to-prove-that-a-programming-language-is-context-free/

يعد استخدام الحالة الخارجية في المحلل اللغوي الذي يمكنه التراجع (مثل PEG) أمرًا خطيرًا ، ويمكن أن ينتج عنه مشكلات مثل التي يمكن رؤيتها في هذا المحلل اللغوي:

{   var countCs = 0;
}

start = ((x/y) ws*)* { return countCs }

x = "ab" c "d"
y = "a" bc "e"

c = "c" { countCs++; }
bc = "bc" { countCs++; }

ws = " " / "\n"

ما ورد أعلاه 2 بدلاً من الإجابة الصحيحة 1. يمكن أن يكون من الصعب التفكير في مثل هذه المشكلات ، ويمكن أن تخلق أخطاء خبيثة يصعب العثور عليها ، وعندما يكون من الصعب جدًا حلها على الإطلاق ، ناهيك عن القيام بذلك بأناقة . لم يتضح لي كيف أقوم بذلك حتى بدون إجراء معالجة لاحقة للبيانات التي يتم إرجاعها بواسطة PEG. إذا كان المحلل اللغوي نفسه يحتاج إلى العد بطريقة أو بأخرى ، فهذا ببساطة لم يحالفه الحظ.

حاليًا ، (بشكل خطير) استخدام الحالة الخارجية هو الطريقة الوحيدة لتحليل القواعد النحوية الحساسة للسياق. باستخدام القواعد ذات المعلمات ، يمكن للمحلل تحليل ذلك دون المخاطرة بحالة غير صالحة:

start = countCs:((x<0>/y<0>) ws*)* { return countCs.reduce(function(a,b){return a+b[0];}, 0); }

x<count> = "ab" theCount:c<count> "d" { return theCount; }
y<count> = "a" theCount:bc<count> "e" { return theCount; }

c<count> = "c" { return count++; }
bc<count> = "bc" { return count++; }

ws = " " / "\n"

ديفيد ، لقد طلبت مواقف حقيقية ، ومن الواضح أن صيغة المسافة البادئة للثعبان هي مثال هنا. أريد أن أقوم بتركيب نفس المسافة البادئة في ليما (لغة البرمجة التي أصنعها باستخدام PEG). لكني لا أرغب في تنفيذ أي شيء من هذا القبيل في الوقت الذي يمكنني فيه إنشاء حالة غير صالحة دون قصد تضرب كل شيء في الجحيم. يمكنني تسمية أي بنية تحليل تتطلب سياق ، مثل C's x * y (هل يتم تعريف x في y أو y كمؤشر لقيمة من النوع x؟).

لاحظ أنه لكي تكون القواعد النحوية الحساسة للسياق قابلة للتحليل ، يحتاج المرء بالضرورة إلى تمرير المعلومات التي يتم إرجاعها من التعبيرات الفرعية المطابقة بالفعل في قاعدة ذات معلمات - وإلا فلن يتمكن المحلل اللغوي بالفعل من استخدام أي سياق. في ما يلي مثال حقيقي لنوع السلسلة الذي أفكر فيه لـ Lima والذي لا يعمل إلا إذا كان التحليل ذي المعلمات متاحًا ويمكنه الوصول (كمتغيرات) إلى تسميات التعبيرات المطابقة مسبقًا:

literalStringWithExplicitLength = "string[" n:number ":" characters<n> "]"
number = n:[0-9]* {return parseInt(n.join(''));}
characters<n> = c:. { // base case
  if(n>0) return null; // don't match base case unless n is 0
  else return c;
}
/ c:. cs:characters<n-1> {
  ret c+cs
}

سيكون هذا قادرًا على تحليل سلسلة مثل السلسلة [10: abcdefghij]. لا يمكنك فعل ذلك باستخدام PEG.js النقي اللطيف كما هو. لقد فعلت شيئًا فظيعًا مثل:

{ var literalStringLengthLeft=undefined;
}
literalStringWithExplicitLength = "string[" n:specialNumber ":" characters "]"
specialNumber = n:number {
  literalStringLengthLeft = n;
  return n;
}
number = n:[0-9]* {return parseInt(n.join(''));}
characters = c:character cs:characters? {
  return c + cs
}
character = c:. {
  if(literalStringLengthLeft > 0) {
    literalStringLengthLeft--;
    return c;
  } else {
    literalStringLengthLeft = undefined;
    return null; // doesn't match
  }
}

تحتوي العديد من البروتوكولات على هذا النوع من الحاجة إلى التحليل - على سبيل المثال ، تحتوي حزم IPv4 على حقل يصف طولها الإجمالي. أنت بحاجة إلى هذا السياق لتحليل بقية الحزمة بشكل صحيح. وينطبق الشيء نفسه على IPv6 و UDP وربما أي بروتوكول آخر قائم على الحزمة. ستحتاج معظم البروتوكولات التي تستخدم TCP أيضًا إلى شيء من هذا القبيل ، حيث يحتاج المرء إلى أن يكون قادرًا على اجتياز عدة كائنات منفصلة تمامًا باستخدام نفس دفق الأحرف المفاهيمي.

على أي حال ، آمل أن أكون قد أعطيت بعض الأمثلة الجيدة والأسباب التي تجعلني أعتقد أن هذه ليست ميزة رائعة فحسب ، وليست ميزة قوية فحسب ، بل هي أيضًا ميزة أساسية يفتقدها العديد من المحللين (بما في ذلك ، في الوقت الحالي ، PEG.js ).

Pegasus (مشروع يشارك معظم تركيبه مع peg.js) يحل هذا من خلال وجود تعبير #STATE{} يُمنح القدرة على تغيير قاموس الحالة. يتراجع قاموس الدولة هذا عندما يتم التراجع عن القواعد. هذا يسمح لها بدعم تحليل كبير للمسافات البيضاء (راجع إدخال wiki حول المسافات البيضاء الكبيرة للحصول على التفاصيل).

أيضًا ، من خلال التراجع عن الحالة جنبًا إلى جنب مع مؤشر التحليل ، يمكن إجراء حفظ المذكرات لقواعد الحالة أيضًا.

أعتقد أن Peg.js يمكن أن تفعل الشيء نفسه بسهولة.

كيف تدير Pegasus حالة التراجع عندما تتراجع القواعد؟ يمكنني أن أتخيل أنه يمكنك الاحتفاظ بلقطة لحالة البرنامج بالكامل التي تغيرت ، وإعادتها مرة أخرى ، لكن ذلك سيكون مكلفًا. يمكنني أن أتخيل الاحتفاظ بلقطة من المتغيرات التي تغيرت فقط ، لكن ذلك يتطلب إما أن يحددها المستخدم مما قد يضيف تعقيدًا إلى إنشاء المحللون ، أو قد يتطلب من المحلل اللغوي أن يكتشف بطريقة أو بأخرى كل الحالات التي تم تغييرها في جزء من التعليمات البرمجية. لا شيء من هذه الأشياء يبدو مثاليًا ، فكيف يفعل ذلك بيغاسوس؟

نظريًا ، يمكن للمحلل تجنب الإجراءات التي تم تنفيذها بشكل غير صحيح إذا تم وضع الإجراءات A. ربما هذا المخطط سيكون أفضل من تراجع الدولة في بيغاسوس؟

أيضًا ، يعد إصلاح مشكلة الحالة غير الصالحة أمرًا رائعًا حقًا ، لكنه لا يحل مشكلة التعبير التي أشرت إليها فيما يتعلق بسلسلة نصية حرفية مثل السلسلة [10: abcdefghij] ، لكنني بالتأكيد مهتم بكيفية عملها

لا يتراجع عن حالة البرنامج بالكامل. يحتفظ بقاموس ثابت للدولة. يقوم بحفظ قاموس الحالة الحالية مع المؤشر وكلما تم إرجاع المؤشر ، يتراجع قاموس الحالة معه. القاموس غير قابل للتغيير في أي مكان خارج إجراءات #STATE{} ، ويتم نسخه قبل تغيير كل حالة.

هناك عقوبة أداء صغيرة لتعيين متغير إضافي في كل مرة تقوم فيها بتقدم المؤشر ، ولكن هذا تفوقه بكثير القدرة على حفظ القواعد ذات الحالة. أيضًا ، هذا لا يؤدي إلى تخصيص الكثير من الذاكرة ، لأن الطبيعة الثابتة لقاموس الحالة تسمح بمشاركتها حتى يتم تحويرها. على سبيل المثال ، إذا لم يكن لديك حالة في المحلل اللغوي ، فسيكون هناك تخصيص واحد فقط: قاموس حالة واحد (فارغ).

لا تمتلك JavaScript (على حد علمي) القدرة على جعل الكائن غير قابل للتغيير ، ولكن هذا كان في الغالب ميزة أمان. سيحتاج Peg.js فقط إلى نسخ قاموس الحالة قبل معالجة كل مقطع رمز #STATE{} والتراجع كلما تراجع المؤشر.

حسنًا ، لذلك يتعين على المستخدم تحديد الحالة التي يغيرها. هذا رائع. لكنني ما زلت لا أعتقد أنها تغطي حقًا نفس الفوائد التي توفرها المعلمات. يبدو أنه من المحتمل أن يكون مفيدًا في حد ذاته لأشياء أخرى.

لقد كتبت للتو شوكة توفر بيئة يمكن الوصول إليها باستخدام المتغير env : https://github.com/tebbi/pegjs
هذا هو نفس الكائن #STATE{} المقترح أعلاه.
إنه اختراق سريع ، باستخدام متغير عام (حزمة) ، والذي يتم استعادته كلما تركت وظيفة التحليل. يتم نسخ env باستخدام Object.create ().

إليك مثال على القواعد النحوية التي تستخدمها لتحليل الكتل المعرفة بمسافة بيضاء على غرار لغة بايثون:

{
  env.indLevel = -1
}

block =
  empty
  ind:ws* &{
    if (ind.length <= env.indLevel) return false;
    env.indLevel = ind.length;
    return true;
  }
  first:statement? rest:indStatement*
  {
    if (first) rest.unshift(first);
    return rest;
  }

indStatement =
  "\n" empty ind:ws* &{ return env.indLevel === ind.length; }
  stm:statement
  {return stm; }

statement =
    id:identifier ws* ":" ws* "\n"
    bl:block { return [id, bl]; }
  / identifier

identifier = s:[a-z]* { return s.join(""); }

empty = (ws* "\n")*

ws = [ \t\r]

فيما يلي مثال لإدخال المحلل اللغوي الناتج:

b:
   c
   d:
       e
   f
g

لدي انطباع بأن PEG.js لا يدعم معلمات من أي نوع في القواعد - وهو أمر يثير الدهشة. هذه الميزة مهمة جدا بالنسبة لي.

ما أحتاجه هو أبسط من طلب OP - يريد OP تعديل القواعد نفسها اعتمادًا على المعلمة ، لكن على الأقل أحتاج فقط إلى تمرير عدد صحيح إلى قاعدة. بشكل أساسي ، أريد ترجمة قاعدة LLLPG تبدو هكذا (حيث PrefixExpr هو تعبير ذو أولوية عالية مثل تعبير البادئة مثل -x ، أو معرّف ...):

@[LL(1)]
rule Expr(context::Precedence)::LNode @{
    {prec::Precedence;}
    e:PrefixExpr(context)
    greedy
    (   // Infix operator
        &{context.CanParse(prec=InfixPrecedenceOf(LT($LI)))}
        t:(NormalOp|BQString|Dot|Assignment)
        rhs:Expr(prec)
        { ... }
    |   // Method_calls(with arguments), indexers[with indexes], generic!arguments
        &{context.CanParse(P.Primary)}
        e=FinishPrimaryExpr(e)
    |   // Suffix operator
        ...
    )*
    {return e;}
};
// Helper rule that parses one of the syntactically special primary expressions
@[private] rule FinishPrimaryExpr(e::LNode)::LNode @{
(   // call(function)
    "(" list:ExprList(ref endMarker) ")"
    { ... }
    |   // ! operator (generic #of)
        "!" ...
    |   // Indexer / square brackets
        {var args = (new VList!LNode { e });}
        "[" args=ExprList(args) "]"
        { ... }
    )
    {return e;}
};

تحتوي لغتي على 25 مستوى أسبقية ، وبهذه القواعد ، قمت بطي جميع هذه المستويات تقريبًا لتتم معالجتها بواسطة قاعدة واحدة (يمكنك التفكير في Precedence كغلاف حول زوجين من الأعداد الصحيحة التي تصف أسبقية المشغل أو العامل). علاوة على ذلك ، تحتوي لغتي على عدد لا حصر له من العوامل (أساسًا أي تسلسل من علامات الترقيم) ويتم تحديد أسبقية عامل التشغيل بعد تحليله. في حين أنه سيكون من الممكن - من الناحية الفنية - تحليل اللغة بالطريقة المعتادة ، مع وجود قاعدة منفصلة لكل نوع من أنواع العمليات الـ 25 ، إلا أن هذه ستكون طريقة مروعة للقيام بذلك.

يمكنك أيضًا أن ترى هنا أن القاعدة الداخلية FinishPrimaryExpr تبني شجرة بناء جملة تتضمن معلمة تم تمريرها من قاعدة التضمين.

إذن ... هل هناك أي طريقة لتمرير المعلمات إلى قاعدة PEG.js؟

+1! في حالتي ، أريد ببساطة إنشاء محلل لغوي لبناء جملة ، حيث تكون بعض المحددات قابلة للتكوين بشكل عام. في هذه الحالة ، يمكنني تحقيق ذلك عن طريق استبدال القيم الحرفية بمطابقة أي تعبيرات مدمجة مع توقع ، ولكن سيكون الأمر أكثر أناقة (وأكثر كفاءة أيضًا) إذا كان من الممكن ببساطة استبدال كل شيء بمُتغير.

+1 ، هل هناك فرصة لرؤية تنفيذ ذلك في المستقبل المنظور؟

حالة استخدام أخرى. هذا من مثال javascript.pegjs الخاص بك:

(...)

RelationalExpression
  = head:ShiftExpression
    tail:(__ RelationalOperator __ ShiftExpression)*
    { return buildBinaryExpression(head, tail); }

RelationalOperator
  = "<="
  / ">="
  / $("<" !"<")
  / $(">" !">")
  / $InstanceofToken
  / $InToken

RelationalExpressionNoIn
  = head:ShiftExpression
    tail:(__ RelationalOperatorNoIn __ ShiftExpression)*
    { return buildBinaryExpression(head, tail); }

RelationalOperatorNoIn
  = "<="
  / ">="
  / $("<" !"<")
  / $(">" !">")
  / $InstanceofToken

(...)

  (...)
  / ForToken __
    "(" __
    init:(ExpressionNoIn __)? ";" __
    test:(Expression __)? ";" __
    update:(Expression __)?
    ")" __
    body:Statement
  (...)

(...)

كل هذه القواعد ...NoIn (وهناك الكثير منها) مطلوبة ببساطة بسبب عبارة for in . لن يكون النهج الأفضل لهذا كثيرًا مثل:

(...)

RelationalExpression<allowIn>
  = head:ShiftExpression
    tail:(__ RelationalOperator<allowIn> __ ShiftExpression)*
    { return buildBinaryExpression(head, tail); }

RelationalOperator<allowIn>
  = "<="
  / ">="
  / $("<" !"<")
  / $(">" !">")
  / $InstanceofToken
  / &{ return allowIn; } InToken
    {return "in";}

(...)

  (...)
  / ForToken __
    "(" __
    init:(Expression<false> __)? ";" __
    test:(Expression<true> __)? ";" __
    update:(Expression<true> __)?
    ")" __
    body:Statement
  (...)

(...)

يبدو هذا مشابهًا جدًا لكيفية تحديد قواعد JavaScript ، على سبيل المثال: https://tc39.github.io/ecma262/#prod -IterationStatement (لاحظ ~In )

اللغة التي أقوم بتطويرها حاليًا بها هذه المشكلة بالضبط: أود تعطيل / تمكين بعض القواعد في نقاط معينة فقط. أرغب بشدة في الامتناع عن تكرار كل قاعدة متأثرة مثلما فعلت مع قواعد JavaScript.

هل هناك طريقة بديلة لتحقيق ذلك دون تكرار القواعد؟

+1 ، هل هناك فرصة لرؤية تنفيذ ذلك في المستقبل المنظور؟

هذه المشكلة لها معلم معين (post-1.0.0). الإصدار الحالي من PEG.js هو 0.10.0. من الواضح أنه سيتم التعامل مع مشكلات ما بعد 1.0.0 بعد إصدار 1.0.0 ، والذي سيحدث في مرحلة ما بعد إصدار 0.11.0 وفقًا لخارطة الطريق .

وهذا ينبغي أن أجيب على سؤالك. أفضل طريقة لتسريع العملية برمتها هي المساعدة في المشكلات المستهدفة 0.11.0 و 1.0.0 .

هل هناك طريقة بديلة لتحقيق ذلك دون تكرار القواعد؟

من الطرق الممكنة تتبع الحالة يدويًا ثم استخدام المسندات الدلالية. لكن هذا النهج به مشكلات في التراجع ولن أوصي به (بعبارة أخرى ، عندما يُتاح لي الاختيار بين تكرار القواعد وتتبع الحالة يدويًا ، سأختار التكرار).

هناك نوعان من الوسيطات التي يمكن تمريرها إلى المحللون:

  1. قيم. تعتمد القواعد النحوية للغات مثل Python و Nim و Haskell (وأيضًا النظام بطريقة مختلفة) على "عمق" التعبير داخل الشجرة. تتطلب كتابة القواعد الصحيحة تمرير هذا السياق بطريقة ما.
  2. موزعي. من الأمثلة الجيدة على ذلك leftAssociative(element, separator) و escapedString(quote) و withPosition(parser) .

يجب أن تكون هناك طريقة لتحديد ما إذا كانت الوسيطة محللًا أم قيمة. عندما حاولت اكتشاف النهج الصحيح ، انتهى بي الأمر باستخدام متغيرات عالمية للسياق ، ومن الواضح أن هذا طريق مسدود. هل لدى أي شخص أي فكرة عن ذلك؟

ماذا عن وحدات الماكرو ؟

معطى:

Add <Expression, Add>
  = left:Expression _ '+' _ right:Add
    { return { type: 'add', left, right } }
  / Expression

متى:

  = Add <MyExpression, MyAdd>

MyExpression
  = [0-9]+

ثم:

  = left:MyExpression _ '+' _ right:MyAdd
    { return { type: 'add', left, right } }
  / MyExpression

MyExpression
  = [0-9]+

هذا يسمح لنا ببناء القواعد من الأسفل إلى الأعلى: الابتسامة المتكلفة:

أوافق ، أوصي المطورين بإضافة هذه الميزة :)

أحتاج حقًا إلى هذه الميزة لقواعد JavaScript محدثة أقوم بكتابتها ، لذلك يحتل هذا المرتبة الأولى في قائمة أمنياتي. سوف يمنحك فرصة ونرى كيف يعمل.

samvv لقد صادفت هذا من مسار مختلف تمامًا ، ولم أقرأ الموضوع بالكامل بعد.
ومع ذلك ، في # 572 ، الذي أشرت إليه هنا ، أعرض تقنية يمكنك من خلالها محاكاة القواعد ذات المعلمات.

وهذا هو ، في جوهره: وظائف العودة كنتائج تحليل وسيطة.

هذه "الحيلة" ليست بأي حال من الأحوال اختراعي ، وربما عديمة الجدوى بالنسبة لغرضك على ما أعتقد. ولكن قد يكون حلًا مناسبًا لك. أعني حتى "نشر الإصدار 1.0" ... :)

@ meisl رائع ، شكرًا على النصيحة! سأجربها عندما أجد بعض الوقت.

samvv أوه ، آه ... أخشى أنني قد أغفلت شيئًا مهمًا إلى حد ما:

يحدث فرقًا كبيرًا فيما إذا كنت تريد القاعدة ذات المعلمات

  • تكون قادرة فقط على إنتاج القيم التي تعتمد على المعلمة
  • أو (أيضًا) أن تعتمد قرارات التحليل الخاصة بها على المعلمة

ما كنت أقترحه يساعد فقط في الأول - بينما الأخير هو المشكلة الفعلية لـ OP ...
اسف على ذلك.

ومع ذلك ، هناك حل بديل حتى بالنسبة لهذا الأخير ، وإن كان أكثر صعوبة.
وجزء "القرارات التابعة" ليس له أي علاقة بإعادة الوظائف ...

أنا ألحق مثالاً لك لتجربته في https://pegjs.org/online

الفكرة الأساسية هي: استخدام الحالة العالمية لتذكر "المنهي" الحالي. هذا هو الاختراق ، والاعتراف به ، والمتكرر.
لكن: إضافة مُحدِّد آخر ، لنفترض أن | يعني إضافة بديل آخر إلى str :

  / (t:'|' {term = t}) c:conts t:.&{ return isT(t) }  { return c }

والذي يختلف عن الآخرين فقط في ذلك الحرف |


{
  var term;
  function isT(ch) { return ch === term }
  function isE(ch) { return ch === '\\' }
}
start = str*

str
  = (t:'\"' {term = t}) c:conts t:.&{ return isT(t) }  { return c }
  / (t:'\'' {term = t}) c:conts t:.&{ return isT(t) }  { return c }

conts
  = c:(
        '\\' t:.&{ return isT(t) || isE(t) } { return t }
      /      t:.!{ return isT(t)           } { return t }
    )*
    { return c.join('') }

... على المدخلات

  • "abc" -> ["abc"]
  • "a\"bc" -> ["a\"bc"]
  • "a\\bc" -> ["a\bc"]
  • "a\\b\"c"'a\\b\'' -> ["a\b\"c", "a\b'c"]

ملاحظة: هذا في الحقيقة ليس شيئًا قد يرغب المرء في كتابته يدويًا ، أعرف. لكن مهلا ، تخيل أنه سيتم إنشاؤه من أجلك ... أعتقد من حيث المبدأ أن هذه هي الطريقة .

ceymard - أدركت أنه بعد عشر سنوات ، لكنني أشعر بالفضول كيف يختلف هذا عن # 36

واو استغرق مني بعض الوقت لأتذكر. 10 سنوات !

في هذا العلاقات العامة ، تأخذ القواعد الحجج ويمكن تحديد معلمات. من المفترض أن يتم استخدام هذا بواسطة القواعد نفسها لتجنب تكرار قواعد متشابهة ولكنها مختلفة.

في # 36 ، تم تحديد القواعد خارج القواعد نفسها. وهكذا فإن القواعد نفسها هي معلمات.

أعتقد أن النطاق مختلف ، على الرغم من أنه يمكن للمرء أن يجادل في أن القواعد نفسها هي قاعدة ، وبالتالي فهذه هي نفس المشكلة. أعتقد أن الأمر ليس كذلك ، لأن رقم 36 قد يعني على الأرجح بعض التغييرات الطفيفة في واجهة برمجة التطبيقات ، في حين أن هذا العلاقات العامة لا يعني ذلك.

لذا ، لإساءة استخدام مصطلحات C ++ العشوائية بطريقة غير صحيحة تمامًا ، فالأولى هي إحصائيات القالب ، في حين أن الأخيرة عبارة عن استدعاءات منشئ؟

أعتقد أن هذا التشبيه يعمل إلى حد ما ، نعم.

شكرا لك على وقتك في الشرح

ربما سيكون لدي سؤال آخر لك بعد عشر سنوات. أتمنى لك عام 2020 جيدًا

سيكون مفيدًا حقًا في إزالة التكرار في تعريف المحلل اللغوي الخاص بي. لدي قواعد قواعد مخصصة مريحة جدًا عن قصد ، ويجب تطبيق بعض القواعد في سياقات مختلفة قليلاً.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

doersino picture doersino  ·  15تعليقات

brettz9 picture brettz9  ·  8تعليقات

futagoza picture futagoza  ·  13تعليقات

richb-hanover picture richb-hanover  ·  7تعليقات

dmajda picture dmajda  ·  15تعليقات