An谩lisis del lenguaje VKScript: JavaScript, 驴y t煤?

TL; DR




VKScript no es JavaScript. La sem谩ntica de este lenguaje es fundamentalmente diferente de la sem谩ntica de JavaScript. Ver la conclusi贸n .


驴Qu茅 es VKScript?




VKScript es un lenguaje de programaci贸n de secuencias de comandos similar a JavaScript que se utiliza en el m茅todo de execute API VKontakte, que permite a los clientes descargar exactamente la informaci贸n que necesitan. En esencia, VKScript es un an谩logo de GraphQL utilizado por Facebook con el mismo prop贸sito.


Compare GraphQL y VKScript:


GraphQLVKScript
ImplementacionesMuchas implementaciones de c贸digo abierto en diferentes lenguajes de programaci贸n.La 煤nica implementaci贸n dentro de la API de VK
Basado enNuevo lenguajeJavascript
Las posibilidadesSolicitud de datos, filtrado limitado; los argumentos de consulta no pueden usar los resultados de consultas anterioresCualquier procesamiento posterior de datos a discreci贸n del cliente; Las solicitudes de API se presentan en forma de m茅todos y pueden usar cualquier dato de solicitudes anteriores

Descripci贸n de VKScript desde la p谩gina del m茅todo en la documentaci贸n de la API de VK (la 煤nica documentaci贸n oficial del idioma):


codigoc贸digo de algoritmo en VKScript : un formato similar a JavaScript o ActionScript ( se supone compatibilidad con ECMAScript ) . El algoritmo debe terminar con el comando return% expression% . Los operadores deben estar separados por punto y coma.
cuerda

Los siguientes son compatibles:


  • operaciones aritm茅ticas
  • operaciones l贸gicas
  • creaci贸n de matrices y listas ([X, Y])
  • parseInt y parseDouble
  • concatenaci贸n (+)
  • si construye
  • filtro de matriz por par谩metro (@.)
  • Llamadas al m茅todo API , par谩metro de longitud
  • bucles utilizando la instrucci贸n while
  • M茅todos Javascript: corte , inserci贸n , pop , shift , unshift , empalme , substr , divisi贸n
  • eliminar operador
  • asignaci贸n a elementos de matriz, por ejemplo: row.user.action = "prueba";
  • la b煤squeda en la matriz o cadena es indexOf , por ejemplo: "123" .indexOf (2) = 1, [1, 2, 3] .indexOf (3) = 2. Devuelve -1 si no se encuentra el elemento.

La creaci贸n de funciones no es compatible actualmente.



La documentaci贸n citada establece que "la compatibilidad con ECMAScript est谩 planificada". 驴Pero es as铆? Tratemos de descubrir c贸mo funciona este lenguaje desde adentro.



Contenido




  1. M谩quina virtual VKScript
  2. Sem谩ntica de objetos VKScript
  3. Conclusi贸n

M谩quina virtual VKScript




驴C贸mo se puede analizar un programa en ausencia de una copia local? As铆 es: env铆e solicitudes al punto final p煤blico y analice las respuestas. Intentemos, por ejemplo, ejecutar el siguiente c贸digo:



 while(1); 

Recibimos un Runtime error occurred during code invocation: Too many operations . Esto sugiere que en la implementaci贸n del lenguaje hay un l铆mite en el n煤mero de acciones realizadas. Intentemos establecer el valor l铆mite exacto:


 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} : el c贸digo se ejecut贸 correctamente.

Por lo tanto, el l铆mite en el n煤mero de operaciones es de aproximadamente 1000 ciclos "inactivos". Pero, al mismo tiempo, est谩 claro que ese ciclo probablemente no sea una operaci贸n "unitaria". Intentemos encontrar una operaci贸n que no est茅 dividida por el compilador en varias m谩s peque帽as.


El candidato m谩s obvio para el papel de tal operaci贸n es la llamada declaraci贸n vac铆a ( ; ). Sin embargo, despu茅s de agregar al c贸digo con i < 999 50 caracteres ; , el l铆mite no se supera. Esto significa que el compilador arroja la declaraci贸n vac铆a y no desperdicia operaciones, o una iteraci贸n del bucle requiere m谩s de 50 operaciones (lo cual, muy probablemente, no es as铆).


Lo siguiente que viene a la mente despu茅s ; - c谩lculo de alguna expresi贸n simple (por ejemplo, as铆: 1; ). Intentemos agregar algunas de estas expresiones a nuestro c贸digo:


 var i = 0; while(i < 999) i = i + 1; 1; //    1; //       "Too many operations" 

Por lo tanto, 2 operaciones 1; gastar m谩s operaciones que 50 operaciones ; . Esto confirma la hip贸tesis de que la declaraci贸n vac铆a no desperdicia instrucciones.


Intentemos reducir el n煤mero de iteraciones del ciclo y agreguemos 1; adicional 1; . Es f谩cil notar que para cada iteraci贸n hay 5 1; adicionales 1; por lo tanto, una iteraci贸n del ciclo gasta 5 veces m谩s operaciones que una operaci贸n 1; .


驴Pero hay una operaci贸n a煤n m谩s simple? Por ejemplo, agregar el operador unario ~ no requiere el c谩lculo de expresiones adicionales, y la operaci贸n misma se realiza en el procesador. Es l贸gico suponer que agregar esta operaci贸n a la expresi贸n aumenta el n煤mero total de operaciones en 1.


Agregue este operador a nuestro c贸digo:


 var i = 0; while(i < 999) i = i + 1; ~1; 

Y s铆, podemos agregar uno de esos operadores y una expresi贸n m谩s 1; Ya no. Por lo tanto, 1; Realmente no es un operador unitario.


Similar al operador 1; , reduciremos el n煤mero de iteraciones del bucle y agregaremos los operadores ~ . Una iteraci贸n result贸 ser equivalente a 10 operaciones unitarias ~ , por lo tanto, expresi贸n 1; Gasta 2 operaciones.


Tenga en cuenta que el l铆mite es de aproximadamente 1000 iteraciones, es decir, aproximadamente 10,000 operaciones individuales. Suponemos que el l铆mite es exactamente 10.000 operaciones.



Medir el n煤mero de operaciones en c贸digo




Tenga en cuenta que ahora podemos medir el n煤mero de operaciones en cualquier c贸digo. Para hacer esto, agregue este c贸digo despu茅s del ciclo y agregue / elimine iteraciones, operadores ~ o toda la 煤ltima l铆nea, hasta que desaparezca el error Too many operations .


Algunos resultados de medici贸n:


C贸digoN煤mero de operaciones
1;2
~1;3
1+1;4 4
1+1+1;6 6
(true?1:1);5 5
(false?1:1);4 4
if(0)1;2
if(1)1;4 4
if(0)1;else 1;4 4
if(1)1;else 1;5 5
while(0);2
i=1;3
i=i+1;5 5
var j = 1;1
var j = 0;while(j < 1)j=j+1;15


Determinar el tipo de m谩quina virtual




Primero debe comprender c贸mo funciona el int茅rprete de VKScript. Hay dos opciones m谩s o menos plausibles:


  • El int茅rprete atraviesa recursivamente el 谩rbol de sintaxis y realiza una operaci贸n en cada nodo.
  • El compilador traduce el 谩rbol de sintaxis en una secuencia de instrucciones que ejecuta el int茅rprete.

Es f谩cil entender que VKScript usa la segunda opci贸n. Considere la expresi贸n (true?1:1); (5 operaciones) y (false?1:1); (4 operaciones). En el caso de la ejecuci贸n secuencial de instrucciones, una operaci贸n adicional se explica por una transici贸n que "omite" la opci贸n incorrecta, y en el caso de una omisi贸n AST recursiva, ambas opciones son equivalentes para el int茅rprete. Se observa un efecto similar en if / else con una condici贸n diferente.


Tambi茅n vale la pena prestar atenci贸n al par i = 1; (3 operaciones) y var j = 1; (1 operaci贸n). 驴Crear una nueva variable cuesta solo 1 operaci贸n y asignarla a una existente cuesta 3? El hecho de que crear una operaci贸n de costo variable 1 (y, muy probablemente, es una operaci贸n de carga constante), dice dos cosas:


  • Al crear una nueva variable, no hay asignaci贸n de memoria expl铆cita para la variable.
  • Al crear una nueva variable, el valor no se carga en la celda de memoria. Esto significa que el espacio para la nueva variable se asigna donde se calcul贸 el valor de la expresi贸n, y despu茅s de eso, esta memoria se considera asignada. Esto sugiere el uso de una m谩quina apiladora.

El uso de la pila tambi茅n explica que la expresi贸n var j = 1; corre m谩s r谩pido que la expresi贸n 1; : la 煤ltima expresi贸n gasta instrucciones adicionales para eliminar el valor calculado de la pila.



Determinar el valor l铆mite exacto


Tenga en cuenta que el ciclo var j=0;while(j < 1)j=j+1; (15 operaciones) es una copia peque帽a del ciclo que se utiliz贸 para las mediciones:


C贸digoN煤mero de operaciones
 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; 

(l铆mite)
9998

驴Detener qu茅? 驴Hay un l铆mite de 9998 instrucciones? Claramente nos falta algo ...


Tenga en cuenta que el c贸digo de return 1; es return 1; realizado seg煤n mediciones para 0 instrucciones. Esto se explica f谩cilmente: el compilador agrega un return null; impl铆cito return null; al final del c贸digo return null; , y al agregar su retorno falla. Suponiendo que el l铆mite es 10000, concluimos que la operaci贸n return null; toma 2 instrucciones (probablemente esto es algo as铆 como push null; return; ).



Bloques de c贸digo anidados




Tomemos algunas medidas m谩s:


C贸digoN煤mero de operaciones
{};0 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 4
{var j = 1;}; var j = 1;3

Prestemos atenci贸n a los siguientes hechos:


  • Agregar una variable a un bloque requiere una operaci贸n adicional.
  • Cuando se "declara una variable nuevamente", la segunda declaraci贸n se cumple como una asignaci贸n normal.
  • Pero al mismo tiempo, la variable dentro del bloque no es visible desde el exterior (ver el 煤ltimo ejemplo).

Es f谩cil entender que se gasta una operaci贸n adicional para eliminar de la pila las variables locales declaradas en el bloque. En consecuencia, cuando no hay variables locales, no es necesario eliminar nada.



Objetos, M茅todos, Llamadas API




C贸digoN煤mero de operaciones
"";2
"abcdef";2
{};2
[];2
[1, 2, 3];5 5
{a: 1, b: 2, c: 3};5 5
API.users.isAppUser(1);3
"".substr(0, 0);6 6
var j={};jx=1;6 6
var j={x:1};delete jx;6 6

Analicemos los resultados. Puede notar que crear una cadena y una matriz / objeto vac铆o requiere 2 operaciones, al igual que cargar un n煤mero. Al crear una matriz u objeto no vac铆o, se agregan las operaciones dedicadas a cargar elementos de la matriz / objeto. Esto sugiere que la creaci贸n directa de un objeto ocurre en una operaci贸n. Al mismo tiempo, no se pierde tiempo descargando nombres de propiedades; por lo tanto, descargarlos es parte de la operaci贸n de creaci贸n del objeto.


Con la llamada al m茅todo API, todo tambi茅n es bastante com煤n: cargar una unidad, realmente llamar al m茅todo, pop resultado (puede notar que el nombre del m茅todo se procesa como un todo y no como propiedades de toma). Pero los 煤ltimos tres ejemplos parecen interesantes.


  • "".substr(0, 0); - cargando una cadena, cargando cero, cargando cero, resultado pop . Por una raz贸n, hay 2 instrucciones para llamar a un m茅todo (por alguna raz贸n, ver m谩s abajo).
  • var j={};jx=1; - crear un objeto, cargar un objeto, cargar una unidad, pop unit despu茅s de la asignaci贸n. Nuevamente, hay 2 instrucciones para la asignaci贸n.
  • var j={x:1};delete jx; - cargar una unidad, crear un objeto, cargar un objeto, eliminar. Hay 3 instrucciones por operaci贸n de eliminaci贸n.



Sem谩ntica de objetos VKScript


Los numeros




Volviendo a la pregunta original: 驴VKScript es un subconjunto de JavaScript u otro idioma? Hagamos una prueba simple:


 return 1000000000 + 2000000000; 

 {"response": -1294967296}; 

Como podemos ver, la suma de enteros conduce al desbordamiento, a pesar de que JavaScript no tiene enteros como tales. Tambi茅n es f谩cil verificar que dividir por 0 conduce a un error y no devuelve Infinity .



Los objetos




 return {}; 

 {"response": []} 

驴Detener qu茅? 驴Devolvemos un objeto y obtenemos una matriz ? Si lo es. En VKScript, las matrices y los objetos est谩n representados por el mismo tipo, en particular, un objeto vac铆o y una matriz vac铆a son uno y lo mismo. En este caso, la propiedad de length del objeto funciona y devuelve el n煤mero de propiedades.


驴Es interesante ver c贸mo se comportan los m茅todos de lista si los llama a un objeto?


 return {a:1, b:2, c:3}.pop(); 

 3 

El m茅todo pop devuelve la 煤ltima propiedad declarada, que, sin embargo, es l贸gica. Cambiar el orden de las propiedades:


 return {b:1, c:2, a:3}.pop(); 

 3 

Aparentemente, los objetos en VKScript recuerdan el orden en que se asignan las propiedades. Intentemos usar propiedades num茅ricas:


 return {'2':1,'1':2,'0':3}.pop(); 

 3 

Ahora veamos c贸mo funciona push:


 var a = {'2':'a','1':'b','x':'c'}; a.push('d'); return a; 

 {"1": "b", "2": "a", "3": "d", "x": "c"}; 

Como puede ver, el m茅todo de inserci贸n ordena las teclas num茅ricas y agrega un nuevo valor despu茅s de la 煤ltima tecla num茅rica. Los "agujeros" no se rellenan en este caso.


Ahora intente combinar estos dos m茅todos:


 var a = {'2':'a','1':'b','x':'c'}; a.push(a.pop()); return a; 

 {"1": "b", "2": "a", "3": "c", "x": "c"}; 

Como vemos, el elemento no se ha eliminado de la matriz. Sin embargo, si ponemos push y pop en diferentes l铆neas, el error desaparecer谩. 隆Necesitamos ir m谩s profundo!



Almacenamiento de objetos




 var x = {}; var y = x; xy = 'z'; return y; 

 {"response": []} 

Al final result贸 que, los objetos en VKScript se almacenan por valor, a diferencia de JavaScript. Ahora vemos el extra帽o comportamiento de la cadena a.push(a.pop()); - aparentemente, el valor anterior de la matriz se guard贸 en la pila, desde donde se tom贸 m谩s tarde.


Sin embargo, 驴c贸mo se almacenan los datos en el objeto si el m茅todo lo modifica? Aparentemente, la instrucci贸n "extra" cuando se llama al m茅todo est谩 dise帽ada espec铆ficamente para escribir cambios en el objeto.



M茅todos de matriz




M茅todoAcci贸n
push
  • ordenar claves num茅ricas por valor
  • tome la clave num茅rica m谩xima, agregue una
  • escribir argumento para la matriz
  • agregar claves no num茅ricas al final de la matriz
popElimine el 煤ltimo elemento de la matriz (no necesariamente con una clave num茅rica) y regrese.
el resto
  • ordenar claves num茅ricas por valor, eliminar "agujeros" en la matriz
  • realizar la operaci贸n javascript adecuada
  • agregar claves no num茅ricas al final de la matriz

Cuando se usa el m茅todo de divisi贸n, los cambios no se guardan



Conclusi贸n




VKScript no es JavaScript. A diferencia de JavaScript, los objetos que contiene se almacenan por valor, no por referencia, y tienen una sem谩ntica completamente diferente. Sin embargo, cuando se utiliza VKScript para el prop贸sito para el que est谩 destinado, la diferencia no se nota.



PS Sem谩ntica de operadores




Los comentarios mencionaron la combinaci贸n de objetos a trav茅s de + . En este sentido, decid铆 agregar informaci贸n sobre el trabajo de los operadores.


OperadorAcciones
+
  • Si ambos argumentos son objetos, cree una copia del primer objeto y agregue las claves del segundo (con reemplazo).
  • Si ambos argumentos son n煤meros, agregue como n煤meros.
  • De lo contrario, ambos operandos se convierten en una cadena y se agregan como cadenas.
Otros operadores aritm茅ticosAmbos operandos se emiten a un n煤mero y se realiza la operaci贸n correspondiente. Para operaciones de bit, los operandos se convierten adicionalmente en int .
Operadores de comparaci贸nSi se comparan dos cadenas o dos n煤meros, se comparan directamente. Si se comparan una cadena y un n煤mero, y la cadena es una notaci贸n correcta para el n煤mero, la cadena se convierte en un n煤mero. De lo contrario, se devuelve un error de Comparing values of different or unsupported types .
Cast to stringLos n煤meros y las cadenas se dan como en JavaScript. Los objetos se enumeran como una lista de valores separados por comas en el orden de las claves. false y null se lanzan como "" , true lanza como "1" .
Fundido aSi el argumento es una cadena que es una notaci贸n de n煤mero v谩lida, se devuelve el n煤mero. De lo contrario, se devuelve un error Numeric arguments expected .

Para operaciones con n煤meros (excepto bit), si los operandos son int y double , int se double en double . Si ambos operandos son int , se realiza una operaci贸n en enteros de 32 bits con signo (con desbordamiento).

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


All Articles