سؤال

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

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

وهنا هو القاعدة التسلل في شبة الكود لجعل tokenizers أن المسافة البادئة القبض في شكل قابل للاستعمال، وأنا لا أريد للحد من الإجابات باللغة:

token NEWLINE
    matches r"\n\ *"
    increase line count
    pick up and store the indentation level
    remember to also record the current level of parenthesis

procedure layout tokens
    level = stack of indentation levels
    push 0 to level
    last_newline = none
    per each token
        if it is NEWLINE put it to last_newline and get next token
        if last_newline contains something
            extract new_level and parenthesis_count from last_newline
            - if newline was inside parentheses, do nothing
            - if new_level > level.top
                push new_level to level
                emit last_newline as INDENT token and clear last_newline
            - if new_level == level.top
                emit last_newline and clear last_newline
            - otherwise
                while new_level < level.top
                    pop from level
                    if new_level > level.top
                        freak out, indentation is broken.
                    emit last_newline as DEDENT token
                clear last_newline
        emit token
    while level.top != 0
        emit token as DEDENT token
        pop from level

comments are ignored before they are getting into the layouter
layouter lies between a lexer and a parser

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

وأثناء استخدام هذا لبعض الوقت، لقد لاحظت أنه بعد DEDENTs قد يكون من الجميل أن تنبعث منها السطر الجديد على أي حال، وبهذه الطريقة يمكنك فصل عبارات مع NEWLINE مع الحفاظ على INDENT DEDENT كما مقطورة للتعبير.

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

المحلول

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

وتحويل NEWLINE NEWLINE INDENT لمجرد INDENT قبل أن يضرب محلل بالتأكيد يبدو أن الطريقة الصحيحة للقيام الأشياء - انها الألم (IME) ليكون دائما تطل المقبلة لأنه في محلل! لقد فعلت فعلا تلك الخطوة باعتبارها طبقة منفصلة في ما انتهى عملية من ثلاث خطوات: الأولى مجتمعة ما lexer وlayouter تفعل ناقص كل lookahead الاشياء NEWLINE (التي جعلت من السهل جدا)، والثاني (أيضا بسيطة جدا ) طبقة أسطر جديدة مطوية على التوالي وتحويلها NEWLINE INDENT لمجرد INDENT (أو، في الواقع، COLON NEWLINE INDENT إلى INDENT، لأنه في هذه الحالة جميع الكتل بادئة وقد سبق دائما كولون)، ثم كان محلل المرحلة الثالثة على رأس ذلك. ولكنه أيضا يجعل الكثير من المنطقي بالنسبة لي أن تفعل أشياء الطريقة التي وصفته لهم، خاصة إذا كنت تريد فصل lexer من layouter، وهو ما يفترض أن كنت تريد أن تفعل إذا كنت تستخدم أداة جيل رمز لجعل lexer الخاصة بك، على سبيل المثال، كما هو شائع.

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

this line introduces an indented block of literal text:
    this line of the block is indented four spaces
  but this line is only indented two spaces

والذي لا يعمل بشكل جيد بشكل رهيب مع الرموز INDENT / DEDENT، منذ كنت في نهاية المطاف تحتاج إلى توليد INDENT واحد لكل عمود من المسافة البادئة وعدد مماثل من DEDENTs في طريق العودة، إلا إذا نظرتم الطريق إلى الأمام لمعرفة أين مستويات المسافة البادئة تسير في نهاية المطاف الوجود، والتي لا يبدو مثل كنت تريد tokenizer للقيام به. في هذه الحالة حاولت بضعة أشياء مختلفة، وانتهى الأمر مجرد تخزين العداد في كل رمز NEWLINE الذي أعطى التغير في المسافة البادئة (إيجابية أو سلبية) للخط المنطقي التالي. (كل رمز أيضا تخزين كافة بيضاء زائدة، في حال حاجة المحافظة؛ لNEWLINE، تضمن بيضاء تخزين موسوعة الحياة نفسها، أي أسطر فارغة التدخل، والمسافة البادئة على الخط المنطقي التالي) لا INDENT أو DEDENT الرموز منفصلة على الإطلاق. الحصول على محلل للتعامل مع ذلك كان أكثر قليلا من العمل البادئة وDEDENTs التعشيش فقط، وربما كانت الجحيم مع القواعد المعقدة التي تحتاج إلى مولد محلل يتوهم، ولكن لم يكن بالسوء الذي كنت قد يخشى، إما. مرة أخرى، لا حاجة للمحلل أن نتطلع إلى الأمام من NEWLINE لمعرفة ما إذا كان هناك مسافة بادئة القادمة في هذا المخطط.

ومع ذلك، كنت أعتقد أن توافق على أن يسمح والحفاظ على جميع أنواع بيضاء أبحث مجنون في tokenizer / layouter والسماح للمحلل أن تقرر ما هو حرفي وما هو كود قليلا من شرط غير عادي! أنت بالتأكيد لا تريد محلل ليتم مثقلة هذا العداد المسافة البادئة إذا كنت تريد فقط أن تكون قادرة على تحليل كود بايثون، على سبيل المثال. الطريق تفعلونه الامور هي من شبه المؤكد النهج الصحيح للتطبيق الخاص بك وغيرها الكثير بالإضافة. ولو كان أي شخص آخر لديه أفكار حول أفضل السبل لقيام هذا النوع من الشيء، وأنا أحب من الواضح أن نسمع منهم ....

نصائح أخرى

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

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

ولدي lexer أن ينهار خطوط فارغة (بما في ذلك خطوط التعليق الوحيد) وتنبعث عربون خط واحد مع معلومات حول المسافة البادئة من السطر الأخير. ثم يأخذ بلدي وظيفة تجهيزها هذا التيار رمز ويضيف INDENT أو DEDENT "بين" أي خطوط حيث التغييرات المسافة البادئة. لذلك

line1
    line2
    line3
line4

ومن شأنه أن يعطي تيار رمزية

LINE "line1" INDENT LINE "line2" LINE "line3" DEDENT LINE "line4" EOF

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

وهنا هو جوهر المعالج، وكتب في O'Caml:

  match next_token () with
      LINE indentation ->
        if indentation > !current_indentation then
          (
            Stack.push !current_indentation indentation_stack;
            current_indentation := indentation;
            INDENT
          )
        else if indentation < !current_indentation then
          (
            let prev = Stack.pop indentation_stack in
              if indentation > prev then
                (
                  current_indentation := indentation;
                  BAD_DEDENT
                )
              else
                (
                  current_indentation := prev;
                  DEDENT
                )
          )
        else (* indentation = !current_indentation *)
          let  token = remove_next_token () in
            if next_token () = EOF then
              remove_next_token ()
            else
              token
    | _ ->
        remove_next_token ()

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

وTokenizer في روبي للمتعة:

def tokenize(input)
  result, prev_indent, curr_indent, line = [""], 0, 0, ""
  line_started = false

  input.each_char do |char|

    case char
    when ' '
      if line_started
        # Content already started, add it.
        line << char
      else
        # No content yet, just count.
        curr_indent += 1
      end
    when "\n"
      result.last << line + "\n"
      curr_indent, line = 0, ""
      line_started = false
    else
      # Check if we are at the first non-space character.
      unless line_started
        # Insert indent and dedent tokens if indentation changed.
        if prev_indent > curr_indent
          # 2 spaces dedentation
          ((prev_indent - curr_indent) / 2).times do
            result << :DEDENT
          end
          result << ""
        elsif prev_indent < curr_indent
          result << :INDENT
          result << ""
        end

        prev_indent = curr_indent
      end

      # Mark line as started and add char to line.
      line_started = true; line << char
    end

  end

  result
end

هل يعمل فقط لمدة الفضاء المسافة البادئة. النتيجة هي شيء من هذا القبيل ["Hello there from level 0\n", :INDENT, "This\nis level\ntwo\n", :DEDENT, "This is level0 again\n"].

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