كيف يمكنك أن تذهب نحو تنفيذ الحكم خارج جانبية؟
سؤال
ولقد كتبت بالفعل المولدات التي لا حيلة، ولكن أود أن أعرف أفضل طريقة ممكنة لتنفيذ حكم خارج الجانب.
وبوقت قصير: حكم خارج الجانب يعني في هذا السياق هو الحصول على الاعتراف بأن المسافة البادئة كما عنصر النحوي.
وهنا هو القاعدة التسلل في شبة الكود لجعل 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"]
.