ربط عن المسافة البادئة على نمط بيثون
-
25-09-2019 - |
سؤال
كيف تكتب أ تحليل قواعد التعبير في أي من مولدات المحلل التالي (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"