تحليل لغة VKScript: JavaScript ، هل أنت؟

TL ؛ د




VKScript ليس JavaScript. تختلف دلالات هذه اللغة اختلافًا جوهريًا عن دلالات JavaScript. انظر الاستنتاج .


ما هو VKScript؟




VKScript هي لغة برمجة نصية تشبه جافا سكريبت والتي يتم استخدامها في طريقة execute واجهة برمجة تطبيقات VKontakte ، والتي تمكن العملاء من تنزيل المعلومات التي يحتاجون إليها بالضبط. في جوهرها ، VKScript هو تناظرية لـ GraphQL يستخدمه Facebook لنفس الغرض.


قارن بين GraphQL و VKScript:


GraphQLVKScript
تطبيقالعديد من التطبيقات مفتوحة المصدر بلغات البرمجة المختلفةالتطبيق الوحيد داخل واجهة برمجة تطبيقات VK
بناء علىلغة جديدةجافا سكريبت
الاحتمالاتطلب البيانات ، تصفية محدودة. لا يمكن استخدام وسيطات الاستعلام نتائج الاستعلامات السابقةأي مرحلة ما بعد معالجة البيانات حسب تقدير العميل ؛ يتم تقديم طلبات API في شكل طرق ويمكنها استخدام أي بيانات من الطلبات السابقة

وصف VKScript من صفحة الطريقة في وثائق VK API (وثائق اللغة الرسمية الوحيدة):


قانونرمز الخوارزمية في VKScript - تنسيق مشابه لجافا سكريبت أو أكشن (من المفترض التوافق مع ECMAScript ) . يجب أن تنتهي الخوارزمية بإرجاع الأمر ٪ تعبير٪ . يجب فصل المشغلين بفواصل منقوطة.
صف

التالية مدعومة:


  • العمليات الحسابية
  • العمليات المنطقية
  • إنشاء المصفوفات والقوائم ([س ، ص])
  • parseInt و parseDouble
  • سلسلة (+)
  • إذا بناء
  • تصفية مجموعة حسب المعلمة (@.)
  • مكالمات طريقة API ، معلمة الطول
  • حلقات باستخدام بيان الوقت
  • طرق جافا سكريبت: شريحة ، ودفع ، البوب ، والتحول ، unshift ، لصق ، substr ، انقسام
  • حذف المشغل
  • تعيين عناصر الصفيف ، على سبيل المثال: row.user.action = "test"؛
  • البحث في الصفيف أو السلسلة هو indexOf ، على سبيل المثال: "123" .indexOf (2) = 1 ، [1 ، 2 ، 3] .indexOf (3) = 2. إرجاع -1 إذا لم يتم العثور على العنصر.

إنشاء الوظيفة غير مدعوم حاليًا.



تنص الوثائق المذكورة أن "ECMAScript التوافق هو المخطط." لكن هل هذا صحيح؟ دعنا نحاول معرفة كيف تعمل هذه اللغة من الداخل.



محتوى




  1. VKScript الجهاز الظاهري
  2. دلالات كائنات VKScript
  3. استنتاج

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; //    1; //       "Too many operations" 

وبالتالي ، 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 .


بعض نتائج القياس:


قانونعدد العمليات
1;2
~1;3
1+1;4
1+1+1;6
(true?1:1);5
(false?1:1);4
if(0)1;2
if(1)1;4
if(0)1;else 1;4
if(1)1;else 1;5
while(0);2
i=1;3
i=i+1;5
var j = 1;1
var j = 0;while(j < 1)j=j+1;15


تحديد نوع الجهاز الظاهري




تحتاج أولاً إلى فهم كيفية عمل مترجم 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 عملية) هي نسخة صغيرة من الدورة التي استخدمت للقياسات:


قانونعدد العمليات
 var i = 0; while(i < 1) i = i + 1; 
15
 var i = 0; while(i < 999) i = i + 1; 
15 + 998 * 10 = 9995
 var i = 0; while(i < 999) i = i + 1; ~1; 

(الحد)
9998

توقف ماذا؟ هل هناك حد 9998 تعليمات؟ من الواضح أننا نفتقد شيئًا ...


لاحظ أن رمز return 1; هو return 1; أجريت وفقا لقياسات 0 تعليمات. يتم تفسير ذلك بسهولة: المحول البرمجي يضيف return null; ضمني return null; في نهاية التعليمات البرمجية return null; ، وعند إضافة عودته فشل. على افتراض أن الحد الأقصى هو 10000 ، فإننا نستنتج أن العملية return null; يأخذ 2 تعليمات (ربما هذا شيء مثل push null; return; ).



كتل رمز المتداخلة




لنأخذ بعض القياسات:


قانونعدد العمليات
{};0
{var j = 1;};2
{var j = 1, k = 2;};3
{var j = 1; var k = 2;};3
var j = 1; var j = 1;4
{var j = 1;}; var j = 1;3

دعنا ننتبه للحقائق التالية:


  • إضافة متغير إلى كتلة يأخذ عملية واحدة إضافية.
  • عند "إعلان متغير مرة أخرى" ، يكون الإعلان الثاني بمثابة مهمة عادية.
  • ولكن في الوقت نفسه ، فإن المتغير الموجود داخل الكتلة غير مرئي من الخارج (انظر المثال الأخير).

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



كائنات ، طرق ، مكالمات API




قانونعدد العمليات
"";2
"abcdef";2
{};2
[];2
[1, 2, 3];5
{a: 1, b: 2, c: 3};5
API.users.isAppUser(1);3
"".substr(0, 0);6
var j={};jx=1;6
var j={x:1};delete jx;6

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


من خلال استدعاء طريقة 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()); - على ما يبدو ، تم حفظ القيمة القديمة للصفيف في الحزمة ، من حيث تم نقلها لاحقًا.


ومع ذلك ، كيف يتم تخزين البيانات في الكائن إذا كانت الطريقة قد عدلت ذلك؟ من الواضح أن التعليمة "الإضافية" عند استدعاء الطريقة مصممة خصيصًا لكتابة التغييرات مرة أخرى على الكائن.



أساليب الصفيف




طريقةتأثير
push
  • فرز مفاتيح رقمية حسب القيمة
  • خذ الحد الأقصى للمفتاح الرقمي ، أضف واحدًا
  • إرسال الحجة إلى مجموعة
  • إضافة مفاتيح غير رقمية إلى نهاية الصفيف
popإزالة العنصر الأخير من الصفيف (وليس بالضرورة باستخدام مفتاح رقمي) والعودة.
الباقي
  • فرز مفاتيح رقمية حسب القيمة ، وإزالة "الثقوب" في مجموعة
  • أداء عملية جافا سكريبت المناسبة
  • إضافة مفاتيح غير رقمية إلى نهاية الصفيف

عند استخدام طريقة الشريحة ، لا يتم حفظ التغييرات



استنتاج




VKScript ليس JavaScript. بخلاف JavaScript ، يتم تخزين الكائنات الموجودة فيه حسب القيمة ، وليس حسب المرجع ، وله دلالات مختلفة تمامًا. ومع ذلك ، عند استخدام VKScript للغرض المخصص له ، يكون الفرق غير ملحوظ.



PS دلالات المشغلين




التعليقات المذكورة تجمع بين الأشياء عن طريق + . في هذا الصدد ، قررت إضافة معلومات حول عمل المشغلين.


عاملالإجراءات
+
  • إذا كانت كلتا الوسيطتين كائنات ، فقم بإنشاء نسخة من الكائن الأول وأضف المفاتيح من الثانية (مع الاستبدال) إليها.
  • إذا كانت كلتا الوسيطتين أرقامًا ، فأضفها كأرقام.
  • خلاف ذلك ، يتم تحويل كلا المعاملتين إلى سلسلة وإضافتهما كسلسلة.
عوامل حسابية أخرىيتم تحويل كلا المعاملين إلى رقم ، ويتم تنفيذ العملية المقابلة. بالنسبة لعمليات البت ، يتم استخدام المعامِلات بالإضافة إلى int .
مشغلي المقارنةإذا تم مقارنة سلسلتين أو رقمين ، تتم مقارنتهما مباشرةً. إذا تمت مقارنة سلسلة ورقم ، وكانت السلسلة تدوينًا صحيحًا للرقم ، يتم تحويل السلسلة إلى رقم. خلاف ذلك ، يتم إرجاع Comparing values of different or unsupported types خطأ غير Comparing values of different or unsupported types .
يلقي سلسلةيتم إعطاء الأرقام والسلاسل كما في JavaScript. يتم سرد الكائنات كقائمة مفصولة بفواصل في ترتيب المفاتيح. يتم إلقاء "" false و " null "" كـ "" ، true الإدلاء بـ "1" .
يلقي لإذا كانت الوسيطة سلسلة تدوين رقم صحيح ، يتم إرجاع الرقم. خلاف ذلك ، يتم إرجاع Numeric arguments expected خطأ Numeric arguments expected .

بالنسبة للعمليات التي تحتوي على أرقام (باستثناء البت) ، إذا كانت المعاملتان int و double ، int double int . إذا كان كلا المعاملين int ، يتم تنفيذ عملية على أعداد صحيحة 32 بت موقعة (مع تجاوز السعة).

Source: https://habr.com/ru/post/ar464099/


All Articles