سؤال

كيف تكتب أ تحليل قواعد التعبير في أي من مولدات المحلل التالي (PEG.JS, الحمضيات, Treetop) التي يمكنها التعامل مع المسافة البادئة على نمط Python/Haskell/Coffescript:

أمثلة على لغة البرمجة التي لا تتواجد بعد:

square x =
    x * x

cube x =
    x * square x

fib n =
  if n <= 1
    0
  else
    fib(n - 2) + fib(n - 1) # some cheating allowed here with brackets

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

foo
  bar = 1
  baz = 2
tap
  zap = 3

# should yield (ruby style hashmap):
# {:foo => { :bar => 1, :baz => 2}, :tap => { :zap => 3 } }
هل كانت مفيدة؟

المحلول

الوتد النقي لا يمكن تحليل المسافة البادئة.

لكن PEG.JS تستطيع.

لقد قمت بتجربة سريعة ودني (مستوحاة من تعليق إيرا باكستر حول الغش) وكتبت رمزًا بسيطًا.

للحصول على حل أكثر اكتمالا (محلل كامل) يرجى الاطلاع على هذا السؤال: تحليل المسافة البادئة مع PEG.JS

/* Initializations */
{
  function start(first, tail) {
    var done = [first[1]];
    for (var i = 0; i < tail.length; i++) {
      done = done.concat(tail[i][1][0])
      done.push(tail[i][1][1]);
    }
    return done;
  }

  var depths = [0];

  function indent(s) {
    var depth = s.length;

    if (depth == depths[0]) return [];

    if (depth > depths[0]) {
      depths.unshift(depth);
      return ["INDENT"];
    }

    var dents = [];
    while (depth < depths[0]) {
      depths.shift();
      dents.push("DEDENT");
    }

    if (depth != depths[0]) dents.push("BADDENT");

    return dents;
  }
}

/* The real grammar */
start   = first:line tail:(newline line)* newline? { return start(first, tail) }
line    = depth:indent s:text                      { return [depth, s] }
indent  = s:" "*                                   { return indent(s) }
text    = c:[^\n]*                                 { return c.join("") }
newline = "\n"                                     {}

depths هو كومة من المسافات البادئة. يعيد المسافة البادئة () مجموعة من الرموز المميزة للمسافة وبدء () قم بفك الصفيف لجعل المحلل يتصرف إلى حد ما مثل تيار.

PEG.JS ينتج للنص:

alpha
  beta
  gamma
    delta
epsilon
    zeta
  eta
theta
  iota

هذه النتائج:

[
   "alpha",
   "INDENT",
   "beta",
   "gamma",
   "INDENT",
   "delta",
   "DEDENT",
   "DEDENT",
   "epsilon",
   "INDENT",
   "zeta",
   "DEDENT",
   "BADDENT",
   "eta",
   "theta",
   "INDENT",
   "iota",
   "DEDENT",
   "",
   ""
]

هذا الرمز المميز يمسك حتى عن المسافات البادئة.

نصائح أخرى

أعتقد أن لغة حساسة للمسافة البادئة مثل هذه هي حساسة للسياق. أعتقد أن PEG يمكن أن تفعل فقط langauges خالية من السياق.

لاحظ أنه على الرغم من أن إجابة Nalply صحيحة بالتأكيد أن Peg.js يمكنه القيام بذلك عبر الحالة الخارجية (أي المتغيرات العالمية المخيفة) ، فقد يكون طريقًا خطيرًا للسير (أسوأ من المشكلات المعتادة في المتغيرات العالمية). يمكن أن تتطابق بعض القواعد في البداية (ثم تدير تصرفاتها) ولكن يمكن أن تفشل قواعد الوالدين وبالتالي التسبب في تشغيل الإجراء. إذا تم تغيير الحالة الخارجية في مثل هذا الإجراء ، فيمكنك أن ينتهي بك الأمر بحالة غير صالحة. هذا فظيع للغاية ، ويمكن أن يؤدي إلى الهزات والقيء والموت. بعض القضايا والحلول لهذا في التعليقات هنا: https://github.com/dmajda/pegjs/issues/45

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

يمكنك القيام بذلك في قمة Treetop باستخدام المتنبئين الدلاليين. في هذه الحالة ، تحتاج إلى مسند دلالي يكتشف إغلاق كتلة مسافة بادئة للمساحة البيضاء بسبب حدوث خط آخر له نفس المسافة البادئة أو الأقل. يجب أن يحسب المسافة المسافة البادئة من خط الفتح ، وإرجاع True (الكتلة مغلقة) إذا انتهى المسافة البادئة للخط الحالي بنفس الطول أو أقصر. لأن حالة الإغلاق تعتمد على السياق ، يجب ألا يتم تمييزها. إليك رمز المثال الذي أنا على وشك إضافته إلى وثائق Treetop. لاحظ أنني تجاوزت طريقة تفتيش SyntaxNode الخاصة بـ Treetop لتسهيل تصور النتيجة.

grammar IndentedBlocks
  rule top
    # Initialise the indent stack with a sentinel:
    &{|s| @indents = [-1] }
    nested_blocks
    {
      def inspect
        nested_blocks.inspect
      end
    }
  end

  rule nested_blocks
    (
      # Do not try to extract this semantic predicate into a new rule.
      # It will be memo-ized incorrectly because @indents.last will change.
      !{|s|
        # Peek at the following indentation:
        save = index; i = _nt_indentation; index = save
        # We're closing if the indentation is less or the same as our enclosing block's:
        closing = i.text_value.length <= @indents.last
      }
      block
    )*
    {
      def inspect
        elements.map{|e| e.block.inspect}*"\n"
      end
    }
  end

 rule block
    indented_line       # The block's opening line
    &{|s|               # Push the indent level to the stack
      level = s[0].indentation.text_value.length
      @indents << level
      true
    }
    nested_blocks       # Parse any nested blocks
    &{|s|               # Pop the indent stack
      # Note that under no circumstances should "nested_blocks" fail, or the stack will be mis-aligned
      @indents.pop
      true
    }
    {
      def inspect
        indented_line.inspect +
          (nested_blocks.elements.size > 0 ? (
            "\n{\n" +
            nested_blocks.elements.map { |content|
              content.block.inspect+"\n"
            }*'' +
            "}"
          )
          : "")
      end
    }
  end

  rule indented_line
    indentation text:((!"\n" .)*) "\n"
    {
      def inspect
        text.text_value
      end
    }
  end

  rule indentation
    ' '*
  end
end

إليك برنامج برنامج تشغيل اختبار صغير حتى تتمكن من تجربته بسهولة:

require 'polyglot'
require 'treetop'
require 'indented_blocks'

parser = IndentedBlocksParser.new

input = <<END
def foo
  here is some indented text
    here it's further indented
    and here the same
      but here it's further again
      and some more like that
    before going back to here
      down again
  back twice
and start from the beginning again
  with only a small block this time
END 

parse_tree = parser.parse input

p parse_tree

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

ملاحظة: تأكد من أن لديك علامات تبويب بدلاً من المساحات!

{ 
    var indentStack = [], 
        rootScope = { 
            value: "PROGRAM",
            values: [], 
            scopes: [] 
        };

    function addToRootScope(text) {
        // Here we wiggle with the form and append the new
        // scope to the rootScope.

        if (!text) return;

        if (indentStack.length === 0) {
            rootScope.scopes.unshift({
                text: text,
                statements: []
            });
        }
        else {
            rootScope.scopes[0].statements.push(text);
        }
    }
}

/* Add some grammar */

start
  = lines: (line EOL+)*
    { 
        return rootScope;
    }


line
  = line: (samedent t:text { addToRootScope(t); }) &EOL
  / line: (indent t:text { addToRootScope(t); }) &EOL
  / line: (dedent t:text { addToRootScope(t); }) &EOL
  / line: [ \t]* &EOL
  / EOF

samedent
  = i:[\t]* &{ return i.length === indentStack.length; }
    {
        console.log("s:", i.length, " level:", indentStack.length);
    }

indent
  = i:[\t]+ &{ return i.length > indentStack.length; }
    {
        indentStack.push(""); 
        console.log("i:", i.length, " level:", indentStack.length);
    }

dedent
    = i:[\t]* &{ return i.length < indentStack.length; }
      {
          for (var j = 0; j < i.length + 1; j++) {
              indentStack.pop();
          } 
          console.log("d:", i.length + 1, " level:", indentStack.length);  
      }

text
    = numbers: number+  { return numbers.join(""); } 
    / txt: character+   { return txt.join(""); }


number
    = $[0-9] 

character 
    = $[ a-zA-Z->+]  
__
    = [ ]+

_ 
    = [ ]*

EOF 
    = !.

EOL
    = "\r\n" 
    / "\n" 
    / "\r"
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top