TL ؛ د
VKScript ليس JavaScript. تختلف دلالات هذه اللغة اختلافًا جوهريًا عن دلالات JavaScript. انظر الاستنتاج .
ما هو VKScript؟
VKScript هي لغة برمجة نصية تشبه جافا سكريبت والتي يتم استخدامها في طريقة execute
واجهة برمجة تطبيقات VKontakte ، والتي تمكن العملاء من تنزيل المعلومات التي يحتاجون إليها بالضبط. في جوهرها ، VKScript هو تناظرية لـ GraphQL يستخدمه Facebook لنفس الغرض.
قارن بين GraphQL و VKScript:
وصف VKScript من صفحة الطريقة في وثائق VK API (وثائق اللغة الرسمية الوحيدة):
التالية مدعومة:
- العمليات الحسابية
- العمليات المنطقية
- إنشاء المصفوفات والقوائم ([س ، ص])
- parseInt و parseDouble
- سلسلة (+)
- إذا بناء
- تصفية مجموعة حسب المعلمة (@.)
- مكالمات طريقة API ، معلمة الطول
- حلقات باستخدام بيان الوقت
- طرق جافا سكريبت: شريحة ، ودفع ، البوب ، والتحول ، unshift ، لصق ، substr ، انقسام
- حذف المشغل
- تعيين عناصر الصفيف ، على سبيل المثال: row.user.action = "test"؛
- البحث في الصفيف أو السلسلة هو indexOf ، على سبيل المثال: "123" .indexOf (2) = 1 ، [1 ، 2 ، 3] .indexOf (3) = 2. إرجاع -1 إذا لم يتم العثور على العنصر.
إنشاء الوظيفة غير مدعوم حاليًا.
تنص الوثائق المذكورة أن "ECMAScript التوافق هو المخطط." لكن هل هذا صحيح؟ دعنا نحاول معرفة كيف تعمل هذه اللغة من الداخل.
محتوى
- VKScript الجهاز الظاهري
- دلالات كائنات VKScript
- استنتاج
VKScript الجهاز الظاهري
كيف يمكن تحليل البرنامج في غياب نسخة محلية؟ هذا صحيح - إرسال الطلبات إلى نقطة النهاية العامة وتحليل الإجابات. دعونا نحاول ، على سبيل المثال ، تنفيذ التعليمات البرمجية التالية:
while(1);
حصلنا على Runtime error occurred during code invocation: Too many operations
. يشير هذا إلى أنه عند تنفيذ اللغة ، يوجد حد لعدد الإجراءات المنفذة. دعنا نحاول تعيين قيمة الحد الدقيق:
var i = 0; while(i < 1000) i = i + 1;
Runtime error occurred during code invocation: Too many operations
.
var i = 0; while(i < 999) i = i + 1;
{"response": null}
- تم تنفيذ الشفرة بنجاح.
وبالتالي ، فإن الحد الأقصى لعدد العمليات هو حوالي 1000 دورة "خاملة". ولكن ، في الوقت نفسه ، من الواضح أن مثل هذه الدورة هي على الأرجح ليست عملية "وحدوية". دعونا نحاول العثور على عملية لا يتم تقسيمها بواسطة المترجم إلى عدة عمليات أصغر.
المرشح الأكثر وضوحًا لدور مثل هذه العملية هو ما يسمى بالبيان الفارغ ( ;
). ومع ذلك ، بعد إضافة الرمز إلى i < 999
50 حرفًا ;
، لا يتم تجاوز الحد. هذا يعني أنه إما أن يتم إلقاء العبارة الفارغة من قبل المترجم ولا تهدر العمليات ، أو أن تكرارًا واحدًا للحلقة يستغرق أكثر من 50 عملية (والتي ، على الأرجح ، ليست كذلك).
الشيء التالي الذي يتبادر إلى الذهن بعد ;
- حساب بعض التعبيرات البسيطة (على سبيل المثال ، مثل هذا: 1;
). دعونا نحاول إضافة بعض هذه التعبيرات إلى الكود الخاص بنا:
var i = 0; while(i < 999) i = i + 1; 1;
وبالتالي ، 2 عمليات 1;
تنفق أكثر من 50 عملية ;
. هذا يؤكد الفرضية القائلة بأن العبارة الفارغة لا تضيع التعليمات.
دعنا نحاول تقليل عدد التكرارات من الدورة وإضافة 1;
إضافية 1;
. من السهل ملاحظة أنه لكل تكرار هناك 5 نقاط إضافية 1;
لذلك ، فإن عملية تكرار واحدة للدورة تنفق 5 مرات عمليات أكثر من عملية واحدة 1;
.
ولكن هل هناك عملية أبسط؟ على سبيل المثال ، لا تتطلب إضافة عامل التشغيل الأحادي ~
حساب تعبيرات إضافية ، ويتم تنفيذ العملية نفسها على المعالج. من المنطقي افتراض أن إضافة هذه العملية إلى التعبير تزيد من إجمالي عدد العمليات بمقدار 1.
أضف هذا المشغل إلى الكود الخاص بنا:
var i = 0; while(i < 999) i = i + 1; ~1;
ونعم ، يمكننا إضافة مشغل واحد من هذا القبيل ، وتعبير آخر 1;
- لم يعد. لذلك ، 1;
حقا ليس مشغل وحدوي.
مماثلة للمشغل 1;
، سنقوم بتقليل عدد مرات تكرار الحلقة ونضيف العوامل ~
. تبين أن التكرار واحد يعادل 10 عمليات أحادية ~
، لذلك ، التعبير 1;
تنفق 2 العمليات.
لاحظ أن الحد الأقصى هو 1000 تكرار ، أي ما يقرب من 10000 عملية فردية. نحن نفترض أن الحد الأقصى هو بالضبط 10000 عملية.
قياس عدد العمليات في التعليمات البرمجية
لاحظ أنه يمكننا الآن قياس عدد العمليات في أي كود. للقيام بذلك ، قم بإضافة هذا الرمز بعد الحلقة وإضافة / إزالة التكرارات ، ~
عوامل التشغيل ، أو السطر الأخير بأكمله ، حتى يختفي خطأ Too many operations
.
بعض نتائج القياس:
تحديد نوع الجهاز الظاهري
تحتاج أولاً إلى فهم كيفية عمل مترجم VKScript. يوجد خياران معقولان أو أكثر:
- يترجم المترجم بشكل متكرر شجرة بناء الجملة ويقوم بإجراء عملية على كل عقدة.
- يترجم المترجم شجرة بناء الجملة إلى سلسلة من الإرشادات التي ينفذها المترجم.
من السهل أن نفهم أن VKScript يستخدم الخيار الثاني. النظر في التعبير (true?1:1);
(5 عمليات) و (false?1:1);
(4 عمليات). في حالة التنفيذ المتتالي للتعليمات ، يتم شرح عملية إضافية عن طريق انتقال "يتخطى" الخيار الخطأ ، وفي حالة تجاوز AST تكراري ، يكون كلا الخيارين معادلين للمترجم الفوري. ويلاحظ وجود تأثير مماثل في حالة / إذا مع حالة مختلفة.
ومن الجدير أيضًا الانتباه إلى الزوج i = 1;
(3 عمليات) و var j = 1;
(1 عملية). إنشاء متغير جديد يكلف عملية واحدة فقط ، وتعيين واحد موجود يكلف 3؟ حقيقة أن إنشاء متغير يكلف عملية واحدة (والأرجح أنها عملية تحميل مستمرة) ، كما يقول شيئان:
- عند إنشاء متغير جديد ، لا يوجد تخصيص ذاكرة صريح للمتغير.
- عند إنشاء متغير جديد ، لا يتم تحميل القيمة في خلية الذاكرة. هذا يعني أن مساحة المتغير الجديد يتم تخصيصها حيث تم حساب قيمة التعبير ، وبعد ذلك تعتبر هذه الذاكرة مخصصة. هذا يشير إلى استخدام آلة مكدس.
باستخدام المكدس يشرح أيضًا أن التعبير var j = 1;
يعمل أسرع من التعبير 1;
: التعبير الأخير ينفق تعليمات إضافية حول إزالة القيمة المحسوبة من المكدس.
تحديد قيمة الحد الدقيق
لاحظ أن الدورة var j=0;while(j < 1)j=j+1;
(15 عملية) هي نسخة صغيرة من الدورة التي استخدمت للقياسات:
توقف ماذا؟ هل هناك حد 9998 تعليمات؟ من الواضح أننا نفتقد شيئًا ...
لاحظ أن رمز return 1;
هو return 1;
أجريت وفقا لقياسات 0 تعليمات. يتم تفسير ذلك بسهولة: المحول البرمجي يضيف return null;
ضمني return null;
في نهاية التعليمات البرمجية return null;
، وعند إضافة عودته فشل. على افتراض أن الحد الأقصى هو 10000 ، فإننا نستنتج أن العملية return null;
يأخذ 2 تعليمات (ربما هذا شيء مثل push null; return;
).
كتل رمز المتداخلة
لنأخذ بعض القياسات:
دعنا ننتبه للحقائق التالية:
- إضافة متغير إلى كتلة يأخذ عملية واحدة إضافية.
- عند "إعلان متغير مرة أخرى" ، يكون الإعلان الثاني بمثابة مهمة عادية.
- ولكن في الوقت نفسه ، فإن المتغير الموجود داخل الكتلة غير مرئي من الخارج (انظر المثال الأخير).
من السهل أن نفهم أن هناك عملية إضافية تنفق على إزالة المتغيرات المحلية المعلنة في الكتلة من المكدس. وفقًا لذلك ، عند عدم وجود متغيرات محلية ، لا يجب حذف أي شيء.
كائنات ، طرق ، مكالمات API
دعونا نحلل النتائج. قد تلاحظ أن إنشاء سلسلة ومصفوفة / كائن فارغ يتطلب عمليتين ، وكذلك تحميل رقم. عند إنشاء صفيف أو كائن غير فارغ ، تتم إضافة العمليات التي يتم إنفاقها على تحميل عناصر الصفيف / الكائن. هذا يشير إلى أن إنشاء كائن مباشرة يحدث في عملية واحدة. في الوقت نفسه ، لا يضيع الوقت في تنزيل أسماء الممتلكات ؛ وبالتالي ، فإن تنزيلها يعد جزءًا من عملية إنشاء الكائن.
من خلال استدعاء طريقة API ، يكون كل شيء شائعًا جدًا - تحميل وحدة ، واستدعاء الأسلوب فعليًا ، pop
النتيجة (يمكنك ملاحظة أن اسم الطريقة قد تمت معالجته بالكامل ، وليس كأخذ خصائص). لكن الأمثلة الثلاثة الأخيرة تبدو مثيرة للاهتمام.
"".substr(0, 0);
- تحميل سلسلة ، تحميل الصفر ، تحميل الصفر ، نتيجة pop
. لسبب ما ، هناك تعلمتان للاتصال بالطريقة (لسبب ما ، انظر أدناه).var j={};jx=1;
- إنشاء كائن ، تحميل كائن ، تحميل وحدة ، وحدة pop
بعد التعيين. مرة أخرى ، هناك 2 تعليمات للمهمة.var j={x:1};delete jx;
- تحميل وحدة ، إنشاء كائن ، تحميل كائن ، حذف. هناك 3 تعليمات لكل عملية حذف.
دلالات كائنات VKScript
الأرقام
عودة إلى السؤال الأصلي: هل VKScript مجموعة فرعية من JavaScript أو لغة أخرى؟ لنقم باختبار بسيط:
return 1000000000 + 2000000000;
{"response": -1294967296};
كما نرى ، تؤدي إضافة عدد صحيح إلى تجاوز سعة ، على الرغم من أن جافا سكريبت لا تحتوي على أعداد صحيحة على هذا النحو. من السهل أيضًا التحقق من أن القسمة على 0 تؤدي إلى حدوث خطأ ، ولا تُرجع Infinity
.
الكائنات
return {};
{"response": []}
توقف ماذا؟ نعيد كائن والحصول على مجموعة ؟ نعم هو كذلك. في VKScript ، يتم تمثيل المصفوفات والكائنات بنفس النوع ، على وجه الخصوص ، الكائن الفارغ والمصفوفة الفارغة واحد. في هذه الحالة ، تعمل خاصية length
الكائن وتقوم بإرجاع عدد الخصائص.
من المثير للاهتمام أن نرى كيف تتصرف طرق القائمة إذا قمت بالاتصال بها على كائن ما؟
return {a:1, b:2, c:3}.pop();
3
إرجاع الأسلوب pop
آخر خاصية تم إعلانها ، والتي ، مع ذلك ، منطقية. تغيير ترتيب الخصائص:
return {b:1, c:2, a:3}.pop();
3
على ما يبدو ، تتذكر الكائنات الموجودة في VKScript الترتيب الذي تم به تعيين الخصائص. دعنا نحاول استخدام الخصائص الرقمية:
return {'2':1,'1':2,'0':3}.pop();
3
الآن دعونا نرى كيف يعمل الدفع:
var a = {'2':'a','1':'b','x':'c'}; a.push('d'); return a;
{"1": "b", "2": "a", "3": "d", "x": "c"};
كما ترون ، تقوم طريقة الدفع بفرز المفاتيح الرقمية وإضافة قيمة جديدة بعد آخر مفتاح رقمي. لا يتم ملء "الثقوب" في هذه الحالة.
حاول الآن الجمع بين هاتين الطريقتين:
var a = {'2':'a','1':'b','x':'c'}; a.push(a.pop()); return a;
{"1": "b", "2": "a", "3": "c", "x": "c"};
كما نرى ، لم يتم حذف العنصر من الصفيف. ومع ذلك ، إذا وضعنا push
pop
في خطوط مختلفة ، فسوف تختفي الأخطاء. نحن بحاجة للذهاب أعمق!
تخزين الأشياء
var x = {}; var y = x; xy = 'z'; return y;
{"response": []}
كما اتضح فيما بعد ، يتم تخزين الكائنات في VKScript حسب القيمة ، على عكس JavaScript. الآن نرى السلوك الغريب للسلسلة a.push(a.pop());
- على ما يبدو ، تم حفظ القيمة القديمة للصفيف في الحزمة ، من حيث تم نقلها لاحقًا.
ومع ذلك ، كيف يتم تخزين البيانات في الكائن إذا كانت الطريقة قد عدلت ذلك؟ من الواضح أن التعليمة "الإضافية" عند استدعاء الطريقة مصممة خصيصًا لكتابة التغييرات مرة أخرى على الكائن.
أساليب الصفيف
استنتاج
VKScript ليس JavaScript. بخلاف JavaScript ، يتم تخزين الكائنات الموجودة فيه حسب القيمة ، وليس حسب المرجع ، وله دلالات مختلفة تمامًا. ومع ذلك ، عند استخدام VKScript للغرض المخصص له ، يكون الفرق غير ملحوظ.
PS دلالات المشغلين
التعليقات المذكورة تجمع بين الأشياء عن طريق +
. في هذا الصدد ، قررت إضافة معلومات حول عمل المشغلين.
بالنسبة للعمليات التي تحتوي على أرقام (باستثناء البت) ، إذا كانت المعاملتان int
و double
، int
double
int
. إذا كان كلا المعاملين int
، يتم تنفيذ عملية على أعداد صحيحة 32 بت موقعة (مع تجاوز السعة).