سؤال

أنا أتطلع إلى تزيين تعبيرات Java / JavaScript تشبه كود JavaScript. ستكون مدخلاتي سلسلة تحتوي على التعبير، ويجب أن يكون الإخراج صفيفا من الرموز.

ما هي أفضل الممارسات للقيام بشيء مثل هذا؟ هل أحتاج إلى تكرار السلسلة أم هل هناك تعبير منتظم سيفعل هذا بالنسبة لي؟

أحتاج إلى أن أكون قادرا على الدعم:

  • عدد حرفي السلسلة (واحد ونقلت مزدوج، مع Quote Escaping)
  • مشغلي ومقارنات رياضي ومقنوعات أساسية (+، -، *، /،!، وليس، <،>، إلخ)
  • نقطة تدوين النقطة والقوس للوصول إلى الكائن مع العودية (FOO.BAR، FOO ['BAR']، FOO [2] [الدعامة])
  • قوس مع التعشيش
  • مشغل ثلاثي (FOO؟ BAR: "BAZ")
  • مكالمات الدالة (FOO (BAR))

أنا تريد خصيصا تجنب استخدام eval() أو أي شيء من النوع لأسباب أمنية. بجانب، eval() لن يلغي التعبير عني على أي حال.

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

المحلول

تعلم كتابة محلل النسب المتراكم. بمجرد فهم المفاهيم، يمكنك القيام بذلك بأي لغة: Java، C ++، JavaScript، SystemVerilog، ... أيا كان. إذا كنت تستطيع التعامل مع السلاسل، فيمكنك تحليلها.

تحليل النسب العودية هي تقنية أساسية لتحليلها يمكن ترميزها بسهولة باليد. هذا مفيد إذا لم يكن لديك إمكانية الوصول إلى (أو لا ترغب في خداعها) مولد محلل محلل.

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

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

expr :: = مصطلح | EXPR "+" مصطلح المصطلح :: = عامل | مصطلح "*" عامل عامل :: = / [0-9 / + (أنا أستخدم RegexP هنا)

الآن لكتابة المحلل (الذي يتضمن Lexer؛ مع النسب المتراكم، يمكنك رمي الاثنين معا). أنا لم أستخدم جافا سكريبت، لذلك دعونا نحاول ذلك في جافا (بلدي الصدئة):

class Parser {
  string str;
  int idx; // index into string

  Node parseExpr() throws ParseException
  {
    Node op1 = parseTerm();
    Node op2;

    while (idx < str.size() && str.charAt(idx) == '+') {
      idx++;
      op2 = parseTerm();
      op1 = new AddNode(op1, op2);
    }
    return op1;
  }

  Node parseTerm() throws ParseException
  {
    Node op1 = parseFactor();
    Node op2;

    while (idx < str.size() && str.charAt(idx) == '*') {
      idx++;
      op2 = parseFactor();
      op1 = new MultNode(op1, op2);
    }
    return op1;
  }

  Node parseFactor() throws ParseException
  {
    StringBuffer sb = new StringBuffer();
    int old_idx = idx;

    while (idx < str.size() && str.charAt(idx) >= '0' && str.charAt(idx) <= '9') {
      sb.append(str.charAt(idx));
      idx++;
    }
    if (idx == old_idx) {
      throw new ParseException();
    }
    return new NumberNode(sb.toString());
  }
}

يمكنك أن ترى كيف يترجم كل قاعدة قواعد اللغة إلى إجراءات. لم أختبر هذا؛ هذا تمرين للقارئ.

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

لمزيد من المعلومات، ابحث عن "محلل محلل" و "محلل النزول العسكري" في ويكيبيديا. كما قلت في البداية، إذا كنت تستطيع فهم المفاهيم (وهي بسيطة مقارنة بالمفاهيم وراء إغلاق تكوين آلة LALR (1)، فأنت تمكين لكتابة محلل للمهام الصغيرة بأي لغة، طالما كما لديك بعض قدرة السلسلة البدائية. استمتع بالسلطة.

نصائح أخرى

بالنسبة لجهاز Lexters البسيطة حيث ليست السرعة أمرا حاسما، عادة ما أكتب Regex لكل نوع من الرمز المميز وحاولت مرارا وتكرارا بمطابقة كل واحد بدوره مع بداية الإدخال. (تأكد من أنك لا تنتهي من خوارزمية O (n ^ 2)!) أداة مثل LEX سوف تسفر عن Lexer أكثر كفاءة لأنها تجمع بين Regexes في جهاز حالة واحدة.

تحتاج إلى تنفيذ محلل معجمي. يمكنك استخدام JS / CC. للقيام بذلك أو يمكنك تنفيذ Automata محدود بنفسك.

منذ، رسميا، اللغة التي ستتلاعب بها منتظم، يمكنك استخدام تعبير منتظم. لكنني لا أوصي بك لك.

Althougth أنا لم أستخدم js / cc أبدا، وأود أن أحاول معها أولا، وإذا كان لا يعمل، سأحاول بناء محلل معجمي بنفسي.

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