Pegjs: نفذ طريقة أبسط للتعبير عن القوائم بفاصل

تم إنشاؤها على ٢١ سبتمبر ٢٠١٢  ·  25تعليقات  ·  مصدر: pegjs/pegjs

عندما يكون لدينا التكرار الصحيح ، يتعين علينا القيام بشيء مثل هذا:

Statements
  = head:Statement tail:(__ Statement)* {
      var result = [head];
      for (var i = 0; i < tail.length; i++) {
        result.push(tail[i][1]);
      }
      return result;
    }

قد أجد من المفيد أن أكون قادرًا على فعل الشيء نفسه على النحو التالي:

Statements
  = st:Statement (__ st:Statement)* { return st; /*where st is an array*/ }
feature

ال 25 كومينتر

ذات صلة: # 69

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

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

النحو الخاص

تخيل شيئًا كهذا:

Args = args:Arg % ","

المعنى سيكون "قائمة Arg s مفصولة ب "," s. سيحتوي المتغير args على مصفوفة من أي شيء ينتج عنه Arg ، ستفصل الفواصل تنسى.

يتمثل أحد الأسئلة في كيفية التمييز بين القوائم المنفصلة التي تسمح بصفر أو أكثر من العناصر التي تسمح بعنصر واحد أو أكثر. تظهر التجربة كلاهما مطلوب. الإجابة المحتملة هي إلحاق أو إرفاق * أو + إلى عامل التشغيل % :

Args0 = args:Arg %* ","
Args1 = args:Arg %+ ","
Args0 = args:Arg *% ","
Args1 = args:Arg +% ","

يمكن أيضًا تنفيذ عامل التشغيل % كمعدِّل لمشغلي * و + الحاليين:

Args0 = args:Arg* % ","
Args1 = args:Arg+ % ","

الإيجابيات: سهل التنفيذ ، لا يقدم مفاهيم جديدة.
السلبيات: حل محدد لمشكلة معينة وليس عام.

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

الحل الثاني هو استخدام القواعد البارامترية ، المقترحة بالفعل في رقم 45. فكرتي الحالية حول بناء الجملة:

// Template definition
List<item, separator> = head:item tail:(separator item)* { ... boilerplate ... }

// Template use
Args = List<Arg, ",">

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

الإيجابيات: عام ، يمكنه القضاء على الأنواع الأخرى من النماذج القياسية أيضًا.
السلبيات: معقد للتنفيذ ، يقدم مفهومًا جديدًا.


لم أقرر أي طريق أذهب بعد. أود أن أسمع أي أفكار / اقتراحات / مقترحات بديلة.

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

لدي مشروع شقيق لـ peg.js (otac0n / pegasus ، إنه في الأساس منفذ لـ C #) يستخدم بالفعل أقواس زاوية في هذا الموضع لنوع بيانات القاعدة ، ولكن يبدو أنه يمكنني اكتشاف شيء ما إذا ذهبت مع هذا.

جمعية البناء الخيرية:

النحو الخاص

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

أيضًا ، قد لا يتم دائمًا إسقاط الفاصل:

complexSelector = simpleSelectors:simpleSelector % (ws* [>+~] ws* / ws+)

هذا مثال على محدد CSS ، والذي من المحتمل أنك تريد أن تعرف فيه المجمعات المستخدمة.

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

تعجبني هذه الفكرة ، لكن يبدو أن ما يقدمه النموذج لا يزال محدودًا جدًا: التعبيرات فقط هي التي يمكن تحديد معلماتها. إذا كنت تريد فقط مبادلة * بـ + ، فعليك استخدام قالب مختلف. يمكنك بالطبع استخدام قوالب متداخلة مثل هذا:

AbstractList<head, tail> = head:head tail:tail { tail.unshift(head); return tail;  }
List<item, separator> = AbstractList<item, (separator item)*>
List2<item, separator> = AbstractList<item, (separator item)+>

لكن:

  • تسمية قوالب صعبة

    • الاسم List2 يقول القليل عن خصائصه ، ومع تعريفه مجردًا ، فإن الوضع يزداد سوءًا.

    • أنت بالتأكيد لا تريد استخدام ListWithTwoOrMoreItems

  • هل هي حقا أفضل من:

{ var list = function(head, tail) { tail.unshift(head); return tail; } } args = head:arg tail:(',' a:arg {return a})* { return list(head, tail) } args2 = head:arg tail:(',' a:arg {return a})+ { return list(head, tail) }

أنا شخصياً أجد هذا أكثر وضوحًا وبالتالي أكثر قابلية للقراءة.

  • على الرغم من أنه يمكن تحديد معلمات للتعبيرات ، فمن المحتمل أن يفترض الإجراء أن لها خصائص معينة. على سبيل المثال ، إذا كان head مصفوفة ، فإن AbstractList سيفشل. لذلك من المحتمل أنه بالرغم من أن التعبيرات الموجودة في قالب تطابق القاعدة الحالية ، إلا أنك لن تستخدمها بالفعل.

المشكلة الحقيقية

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

أتساءل عما إذا كان يتم إعادة استخدام الملصقات بهذه الطريقة ، هل توضح أن المستخدمين يريدون دمج قيم معينة؟

args = args:arg args:(',' a:arg {return a})* { // args is an array of "arg"s }
args2 = args:arg args:((',' / ';') a:arg)* { //args is an array of "arg"s and separators "," or ";"

لاحظ أن القاعدة الثانية تقوم بتسوية القيمة الثانية وهي args

تشير القاعدة الأولى إلى أن pegjs تحتاج إلى اختبار أنواع القيم

items = items:item1 items:item2

إذا لم يكن أي من item1 و item2 مصفوفة ، فإن items هو [item1, item2] ، وإلا فإن items هو سلسلة الاثنين.

ومع ذلك ، فإن القاعدة الثانية تتضمن سلوكًا غريبًا قد يحتاج إلى تغيير.

items = items:item1 items:item2

إذا كان أي من item1 و item2 عبارة عن مصفوفة من المصفوفات ، فيجب تسويتها ، ولكن تظل كما هي عندما تكون تسميتها فريدة

items = items:item1 other:item2

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

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

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

"هل هو حقًا أفضل من ..." - نعم هو ، أنظف بكثير ، وأسهل في القراءة ، وأكثر قابلية للصيانة. يصبح الموقف أكثر وضوحًا حتى مع وجود قواعد أكثر تعقيدًا بشكل طفيف

ديفيد ، أنا لا أفهم الحاجة إلى بناء جملة خاص هنا. هذه:
Args = args:Arg % ","
يمكن القيام به على النحو التالي:
Args = args:(Arg ",")* { return args.map(function(v){v[0]}) }

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

يبدو أن النقطتين الأوليين لديك تنبعان من فكرة أن النمطية والتجريد بطريقة ما تجعل الأشياء أكثر صعوبة في القراءة أو الفهم.

لا ، ليس هذا ما قصدته. يحتوي PEG بالفعل على آلية تجريد رائعة تسمى القواعد

    = number operator number

في هذه الحالة ، كلا من number و operator هما تجريدان ، وأنا بالتأكيد أحبه.

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

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

يصبح الموقف أكثر وضوحًا مع وجود قواعد أكثر تعقيدًا

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

ديفيد ، أنا لا أفهم الحاجة إلى بناء جملة خاص هنا.

Args = args:(Arg ",")* يختلف عن Args = args:Arg % "," . السابق يسمح للقاعدة بأن تنتهي بـ , ، والأخيرة لا تنتهي.

المعامل تعني ببساطة الاستبدال هنا.

ألا يمكنك قول الشيء نفسه بالنسبة للوظائف في أي لغة برمجة؟ أنا متأكد من أننا نتفق على أن الوظائف مفيدة ، فلماذا لا يتم ذلك في المحلل اللغوي؟

هل يمكنك تقديم بعض حالات الاستخدام الواقعية

لقد كتبت بالفعل بعض الأمثلة في المشكلة الرئيسية هنا: https://github.com/dmajda/pegjs/issues/45

السابق يسمح للقاعدة بأن تنتهي بـ ,

آه فهمت ، أنت على حق. على أي حال ، ما زالت وجهة نظري قائمة بأنه يمكن القيام بذلك على النحو التالي:
Args = first:Arg rest:("," Arg)* { return [first].concat(rest.map(function(v){v[0]})) }

الشيء الجميل في الدوال هو أنك إذا كنت تفعل هذا كثيرًا ، يمكنك إنشاء وظيفة لها وتحطيمها من أجل:
Args = first:Arg rest:("," Arg)* { return yourFancyFunction(first,rest) }

وإذا كانت لديك قوالب قواعد ، يمكنك الحصول على أبسط: Args = list<Arg,",">

يبدو أنك تتحدث عن ميزة مختلفة تمامًا.

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

ما تقترحه هو استخدامها في الإجراء (إذا فهمتها بشكل صحيح) ، لكنني لست متأكدًا من كيفية عملها. مثال "العد" الأول في # 45 لا يوضح من أين تأتي القيمة الأولية لـ count ، أو أنك تفترض فقط أن جميع المعلمات لها قيمة افتراضية 0 ؟

ربما يجب عليك فتح قضية جديدة لها.

dmajda ، أفكر في بناء جملة آخر لحل مشكلة الدمج ، والتي تشبه إلى حد كبير بناء الجملة $ expression .

يُرجع بناء الجملة $ expression السلسلة المطابقة ، بغض النظر عن كيفية بناء التعبير. وبالمثل ، ماذا عن تقديم بناء جملة ، على سبيل المثال # expression (أو أي شيء من هذا النوع) ، والذي يعرض مصفوفة من التعبير الفرعي المتطابق ، بغض النظر عن كيفية بناء التعبير:

args = #(arg (',' a:arg {return a})*) // args is [arg, arg...]

args = #(arg (',' arg)*) // args is [arg, ',', arg, ',', ...]

ومع ذلك ، إذا كنت تكتبها بهذه الطريقة

args = #(arg restArgs) // args is [arg, [',', arg, ',', arg, ...]]

restArgs = #(',' arg)* // restArgs is [',', arg, ',', arg, ...]

لا يتصرف تمامًا مثل $ expression

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

كنت محقا بشأن مثال العد الخاص بي. لقد قمت بتحديث تعليقي ليكون (على أمل) بنية صحيحة. شكرا.

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

يقوم Perl's Regexp :: Grammars بهذا أيضًا مع عامل المعامل:

# a list of one or more "item", separated by "separator", returning an array:
<rule: list>
        <[item]>+ % <separator>

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

بدلاً من جعله عامل تشغيل مضمن ، قمت للتو بإنشاء محللات يمكن تحديد معلمات بواسطة المحللون.
Metagrammar هنا.

futagoza هل هناك تذكرة لموزعي المعلمات؟ أعتقد أنه كان هناك واحد ، لكنني لم أتمكن من العثور عليه.

أكثر ما يمكن أن أجده صلة هو # 45 لكن OP الخاص بهذه المشكلة يقترح صيغة مختلفة (على غرار ما قمت بتطبيقه في metagrammar @ polkovnikov-ph) ، لكنني أخطط للذهاب مع القواعد البارامترية (مثل @ dmajda اقترح أعلاه) التي تستخدم الصيغة الأكثر شيوعًا وهي < .. > (قوالب ، أدوية ، إلخ).

@ futagoza Syntax لا يهم حقًا. أيا كان ما تفعله في نهاية المطاف مع هذه المشكلة ، فأنا أقدر ذلك كثيرًا.

يعد اختيار الرمز أمرًا مهمًا للغاية ، حيث يمكن أن يتداخل ذلك مع ميزات أخرى ذات أولوية عالية ، مثل نطاق Mingun

لا يوجد بديل معقول بشكل خاص للنطاقات ، لذا فإن ميولي هو الحفاظ على أقواس الزاوية لتلك

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

fname = "bob" / "dan" / "charlie"
namelist = (WS? fname ","?)+

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

لذا ربما لا يكون الحل الذي أقدمه جيدًا بما يكفي ، لكني أرغب في معرفة السبب

لذا ربما لا يكون الحل الذي أقدمه جيدًا بما يكفي ، لكني أرغب في معرفة السبب

في الغالب لأنه يقبل بعض المدخلات ، يجب أن يكون ذلك في الحياة الواقعية غير مقبول. على سبيل المثال ، يوزع bobdan .

معرض. كان ذلك من السذاجة مني.

هذا يسمح bob ، dan ، و bob,dan ، لكن ليس bobdan . ماذا افتقد هنا؟

Document = names
WS = [ \r\n]+

fname = "bob" / "dan" / "charlie"

nameitem = (WS? fname ",")+ 
namelastitem = (WS? fname)

namelist = nameitem? namelastitem

@ polkovnikov-ph - هناك أيضًا رقم 36 ، وهو مشابه للرقم 45 ولكنه غير متطابق

يبدو أن تفسير المؤلف يشير إلى أن 36 أقرب إلى ما قصدته

الآن يوزع ما هو متوقع فقط ، لكن النتائج ليست مصفوفة واحدة. هذا هو الدافع الرئيسي لوجود بنية خاصة لتحليل البيانات المحددة

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

ماذا عن

Document = names

WS = [ \r\n]+

fname = "bob" / "dan" / "charlie"

namelist = nl:(namelast ",")+ { return nl[0][0]; }
namelast = WS? fn:fname       { return fn; }

names = nl:namelist? na:namelast { return [].concat(nl, na); } 

أو أوكراني أو أيا كان. أعتذر: رأيت اسمًا سيريليًا. لا يجب أن أخمن هكذا. قد يكون فهم هذا الخطأ مسيئًا.

يعمل بالطبع ، ولكن هنا نتناول السؤال المطروح في موضوع العنوان - طريقة أبسط. يتم استخدام البيانات المنفصلة في كثير من الأحيان للحصول على بناء جملة منفصل لها. في ممارستي ، هذا هو المكان الثاني الذي أستخدم النطاقات من أجله ، الأول هو التكرار مع البيانات المتغيرة ( peg = len:number someData|len|; في بناء جملة النطاق الخاص بي)

ونعم ، أنا من روسيا. لا تقلق ، بلا إهانة

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

هنا نتناول السؤال المطروح في موضوع العنوان - بطريقة أبسط .

نعم ، أوافقك الرأي الآن. الأمر فقط أن محاولتي الأولى الخاطئة بدت بسيطة حقًا ، وإذا لم تكن خاطئة ، فسيكون ذلك بسيطًا بما يكفي لعدم الإزعاج

ولكن الآن بعد أن رأيت المطلوب ، أوافق

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