سؤال

في هذا مقال عن تعزيز الأفعال الدلالية وذكر ذلك

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

كل شيء على ما يرام ، لكنني كنت أحاول العثور على مثال يمرر كائن وظيفة كعمل دلالي يستخدم المعلمات الأخرى (سياق المحلل وضرب منطقية) لكنني لم أجد أي شيء. أحب أن أرى مثالًا باستخدام وظائف منتظمة أو كائنات وظيفية ، حيث بالكاد أستطيع أن أتطرق إلى Voenix Voodoo

هل كانت مفيدة؟

المحلول

هذا سؤال جيد حقًا (وأيضًا علبة من الديدان) لأنه يحصل على واجهة Qi و Phoenix. لم أر مثالاً أيضًا ، لذلك سأقوم بتمديد المقالة قليلاً في هذا الاتجاه.

كما تقول ، وظائف ل الإجراءات الدلالية يمكن أن تأخذ ما يصل إلى ثلاثة معلمات

  1. سمة متطابقة - مغطاة في المقالة
  2. السياق - يحتوي على واجهة QI -Phoenix
  3. علم المباراة - معالجة حالة المباراة

المباراة

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

#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>

void f(int attribute, const boost::fusion::unused_type& it, bool& mFlag){
    //output parameters
    std::cout << "matched integer: '" << attribute << "'" << std::endl
              << "match flag: " << mFlag << std::endl;

    //fiddle with match flag
    mFlag = false;
}

namespace qi = boost::spirit::qi;

int main(void){
   std::string input("1234 6543");
   std::string::const_iterator begin = input.begin(), end = input.end();

   bool returnVal = qi::phrase_parse(begin, end, qi::int_[f], qi::space);

   std::cout << "return: " << returnVal << std::endl;
   return 0;
}

الذي يخرج:

matched integer: '1234'
match flag: 1
return: 0

كل هذا المثال هو تبديل المطابقة إلى غير المباراة ، وهو ما ينعكس في إخراج المحلل. وفقًا لـ HKAISER ، في Boost 1.44 وما فوق ، سيؤدي إعداد علامة المباراة إلى فشل المباراة بالطريقة العادية. إذا تم تعريف البدائل ، فسيتم تراجع المحلل المحلل ويحاول مطابقتها كما يتوقع المرء. ومع ذلك ، في التعزيز <= 1.43 ، يمنع حشرة الروح التراجع ، مما يسبب سلوكًا غريبًا. للاطلاع على هذا ، تضمين Phoenix boost/spirit/include/phoenix.hpp وتغيير التعبير إلى

qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]

كنت تتوقع ذلك ، عندما يفشل Qi :: int int ، فإن Qi :: البديل Qi :: لمطابقة بداية المدخلات في "1" ، ولكن الإخراج هو:

matched integer: '1234'
match flag: 1
6
return: 1

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

بمجرد انتهاء Boost 1.44 ، سيكون علامة المطابقة مفيدة لتطبيق معايير المطابقة التي قد يكون من الصعب التعبير عنها في تسلسل المحلل. لاحظ أنه يمكن معالجة علامة المطابقة في تعبيرات فينيكس باستخدام _pass عنصر نائب.

معلمة السياق

المعلمة الأكثر إثارة للاهتمام هي المعلمة الثانية ، التي تحتوي على واجهة QI-Phoenix ، أو في لغة QI ، سياق العمل الدلالي. لتوضيح هذا ، قم أولاً بفحص القاعدة:

rule<Iterator, Attribute(Arg1,Arg2,...), qi::locals<Loc1,Loc2,...>, Skipper>

تجسد معلمة السياق السمة ، Arg1 ، ... Argn ، و Qi :: Tompalate Paramters ، ملفوفة في دفعة :: Spirit :: Context Type. تختلف هذه السمة عن معلمة الوظيفة: سمة معلمة الوظيفة هي القيمة المحلية ، في حين أن هذه السمة هي قيمة القاعدة نفسها. يجب أن يخطط الإجراء الدلالي الأول إلى الأخير. فيما يلي مثال على نوع السياق المحتمل (المشار إليها معادلات تعبير فينيكس):

using namespace boost;
spirit::context<              //context template
    fusion::cons<             
        int&,                 //return int attribute (phoenix: _val)
        fusion::cons<
            char&,            //char argument1       (phoenix: _r1)
            fusion::cons<
                float&,       //float argument2      (phoenix: _r2) 
                fusion::nil   //end of cons list
            >,
        >,
    >,
    fusion::vector2<          //locals container
        char,                 //char local           (phoenix: _a)
        unsigned int          //unsigned int local   (phoenix: _b)
    > 
>

لاحظ أن قائمة سمة الإرجاع وقائمة الوسيطة تأخذ شكل قائمة على غرار LISP (أ قائمة السلبيات). للوصول إلى هذه المتغيرات داخل وظيفة ، والوصول إلى attribute أو locals أعضاء context القالب بنية مع الانصهار :: at <> (). على سبيل المثال ، لمتغير السياق con

//assign return attribute
fusion::at_c<0>(con.attributes) = 1;

//get the second rule argument
float arg2 = fusion::at_c<2>(con.attributes);

//assign the first local
fusion::at_c<1>(con.locals) = 42;

لتعديل مثال المقالة لاستخدام الوسيطة الثانية ، قم بتغيير تعريف الوظيفة ومكالمات phrase_parse:

...
typedef 
    boost::spirit::context<
        boost::fusion::cons<int&, boost::fusion::nil>, 
        boost::fusion::vector0<> 
    > f_context;
void f(int attribute, const f_context& con, bool& mFlag){
   std::cout << "matched integer: '" << attribute << "'" << std::endl
             << "match flag: " << mFlag << std::endl;

   //assign output attribute from parsed value    
   boost::fusion::at_c<0>(con.attributes) = attribute;
}
...
int matchedInt;
qi::rule<std::string::const_iterator,int(void),ascii::space_type> 
    intRule = qi::int_[f];
qi::phrase_parse(begin, end, intRule, ascii::space, matchedInt);
std::cout << "matched: " << matchedInt << std::endl;
....

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

qi::rule<std::string::const_iterator,int(void),ascii::space_type> 
    intRule %= qi::int_;

IMHO ، فإن كتابة وظيفة لكل إجراء ستكون مملة إلى حد ما ، مقارنة بمكافئات تعبير Phoenix المختصرة والقابلة للقراءة. أنا أتعاطف مع وجهة نظر الفودو ، ولكن بمجرد أن تعمل مع Phoenix لفترة قصيرة ، فإن الدلالات وبناء الجملة ليست صعبة للغاية.

تحرير: الوصول إلى سياق القاعدة مع Phoenix

يتم تعريف متغير السياق فقط عندما يكون المحلل جزءًا من القاعدة. فكر في المحلل المحلل على أنه أي تعبير يستهلك المدخلات ، حيث تترجم القاعدة قيم المحلل (Qi :: _ 1) إلى قيمة قاعدة (Qi :: _ val). غالبًا ما يكون الاختلاف غير تافهة ، على سبيل المثال عندما يكون لدى Qi :: Val نوع فئة يجب بناؤه من القيم المحسورة. أدناه مثال بسيط.

دعنا نقول أن جزءًا من مدخلاتنا هو سلسلة من ثلاثة أعداد صحيحة CSV (x1, x2, x3) ، ونحن نهتم فقط بوظيفة حسابية لهذه الأعداد الصحيحة الثلاثة (F = x0 + (x1 + x2)*x3) ، حيث x0 هي قيمة تم الحصول عليها في مكان آخر. أحد الخيارات هو القراءة في الأعداد الصحيحة وحساب الوظيفة ، أو استخدام Phoenix بدلاً من ذلك.

على سبيل المثال ، استخدم قاعدة واحدة مع سمة الإخراج (قيمة الوظيفة) ، والإدخال (x0) ، ومحلية (لتمرير المعلومات بين المحللين الفرديين مع القاعدة). هذا هو المثال الكامل.

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>
#include <iostream>

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

int main(void){
   std::string input("1234, 6543, 42");
   std::string::const_iterator begin = input.begin(), end = input.end();

   qi::rule<
      std::string::const_iterator,
      int(int),                    //output (_val) and input (_r1)
      qi::locals<int>,             //local int (_a)
      ascii::space_type
   >
      intRule =
            qi::int_[qi::_a = qi::_1]             //local = x1
         >> ","
         >> qi::int_[qi::_a += qi::_1]            //local = x1 + x2
         >> ","
         >> qi::int_
            [
               qi::_val = qi::_a*qi::_1 + qi::_r1 //output = local*x3 + x0
            ];

   int ruleValue, x0 = 10;
   qi::phrase_parse(begin, end, intRule(x0), ascii::space, ruleValue);
   std::cout << "rule value: " << ruleValue << std::endl;
   return 0;
}

بدلاً من ذلك ، يمكن تحليل جميع ints كمتجه ، وتقييم الوظيفة بعمل دلالي واحد ( % فيما يلي مشغل القائمة ويتم الوصول إلى عناصر المتجه مع Phoenix :: AT):

namespace ph = boost::phoenix;
...
    qi::rule<
        std::string::const_iterator,
        int(int),
        ascii::space_type
    >
    intRule =
        (qi::int_ % ",")
        [
            qi::_val = (ph::at(qi::_1,0) + ph::at(qi::_1,1))
                      * ph::at(qi::_1,2) + qi::_r1
        ];
....

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

(qi::int_ >> "," >> qi::int_ >> "," >> qi::int_)
[
    qi::_val = (qi::_1 + qi::_2) * qi::_3 + qi::_r1
];

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top