javascript - como - Precedencia del operador de arriba abajo Crockfords
title html (3)
Al analizar un idioma, dos cosas importan: semántica y sintaxis.
Semánticamente , var x=5;
y var x;x=5
parecen muy cercanos si no idénticos (dado que en ambos casos primero se declara una variable y luego se le asigna un valor a esa variable declarada. Esto es lo que ha observado y es correcto en su mayor parte.
Sintacticamente , sin embargo, los dos difieren (lo cual es claramente visible).
En lenguaje natural, un análogo sería:
- El chico tiene una manzana
- Hay una manzana, el chico la tiene.
¡Ahora para ser conciso! Veamos los dos ejemplos.
Mientras que los dos (más o menos) significan lo mismo, claramente no son la misma oración. Volver a JavaScript!
El primero: var x=5
se lee de la siguiente manera :
var x = 5
-----------------------VariableStatement--------------------
var ------------------- VariableDeclarationList
var ------------------- VariableDeclaration
var Identifier ------- Initialiser(opt)
var ------------------- x = AssignmentExpression
var ------------------- x ------------ = LogicalORExpression
var ------------------- x ------------ = LogicalANDExpression
var ------------------- x ------------ = BitwiseORExpression
var ------------------- x ------------ = BitwiseXORExpression
var ------------------- x ------------ = BitwiseANDExpression
var ------------------- x ------------ = EqualityExpression
var ------------------- x ------------ = ShiftExpression
var ------------------- x ------------ = AdditiveExpression
var ------------------- x ------------ = MultiplicativeExpression
var ------------------- x ------------ = UnaryExpression
var ------------------- x ------------ = PostfixExpression
var ------------------- x ------------ = NewExpression
var ------------------- x ------------ = MemberExpression
var ------------------- x ------------ = PrimaryExpression
var ------------------- x ------------ = Literal
var ------------------- x ------------ = NumericLiteral
var ------------------- x ------------ = DecimalLiteral
var ------------------- x ------------ = DecimalDigit
var ------------------- x ------------ = 5
¡Uf! Todo esto tuvo que suceder sintácticamente para analizar var x = 5
, claro, muchas de ellas manejan expresiones, pero es lo que es, revisemos la otra versión.
Esto se divide en dos declaraciones. var x; x = 5
var x; x = 5
El primero es:
var x
--------VariableStatement---
var ---- VariableDeclarationList
var ---- VariableDeclaration
var Idenfifier (optional initializer not present)
var x
La segunda parte es x=5
que es una declaración de asignación. Puedo seguir con la misma expresión de locura, pero es más o menos lo mismo.
Entonces, en conclusión, mientras que los dos producen el mismo resultado semánticamente, sintácticamente como especifica la gramática del lenguaje oficial, son diferentes. El resultado, en este caso, es de hecho el mismo.
Fuera de interés, quiero aprender cómo escribir un analizador sintáctico para un lenguaje simple, para finalmente escribir un intérprete para mi propio código pequeño: el lenguaje del golf, tan pronto como entiendo cómo funcionan estas cosas en general.
Así que comencé a leer el artículo de Douglas Crockfords Prioridad del operador Top Down .
Nota: Probablemente deberías leer el artículo si quieres una comprensión más profunda del contexto de los fragmentos de código a continuación.
Tengo problemas para entender cómo la instrucción var
y el operador de asignación =
deberían funcionar juntos.
DC define un operador de asignación como
var assignment = function (id) {
return infixr(id, 10, function (left) {
if (left.id !== "." && left.id !== "[" &&
left.arity !== "name") {
left.error("Bad lvalue.");
}
this.first = left;
this.second = expression(9);
this.assignment = true;
this.arity = "binary";
return this;
});
};
assignment("=");
Nota: [[valor]] se refiere a un token, simplificado a su valor
Ahora, si la función de expresión alcanza, por ejemplo, [[t]],[[=]],[[2]]
, el resultado de [[=]].led
es algo como esto.
{
"arity": "binary",
"value": "=",
"assignment": true, //<-
"first": {
"arity": "name",
"value": "t"
},
"second": {
"arity": "literal",
"value": "2"
}
}
DC hace la función de assignment
porque
queremos que haga dos negocios adicionales: examine el operando de la izquierda para asegurarse de que sea un valor l apropiado y establezca un miembro de asignación para que luego podamos identificar rápidamente las declaraciones de asignación.
Lo cual tiene sentido para mí hasta el punto donde introduce la declaración var
, que se define de la siguiente manera.
La instrucción var define una o más variables en el bloque actual. Cada nombre puede ser seguido opcionalmente por = y una expresión de inicialización.
stmt("var", function () {
var a = [], n, t;
while (true) {
n = token;
if (n.arity !== "name") {
n.error("Expected a new variable name.");
}
scope.define(n);
advance();
if (token.id === "=") {
t = token;
advance("=");
t.first = n;
t.second = expression(0);
t.arity = "binary";
a.push(t);
}
if (token.id !== ",") {
break;
}
advance(",");
}
advance(";");
return a.length === 0 ? null : a.length === 1 ? a[0] : a;
});
Ahora, si el analizador llega a un conjunto de tokens como [[var]],[[t]],[[=]],[[1]]
el árbol generado se vería como algo así.
{
"arity": "binary",
"value": "=",
"first": {
"arity": "name",
"value": "t"
},
"second": {
"arity": "literal",
"value": "1"
}
}
La parte clave de mi pregunta es la parte if (token.id === "=") {...}
.
No entiendo por qué llamamos
t = token;
advance("=");
t.first = n;
t.second = expression(0);
t.arity = "binary";
a.push(t);
más bien que
t = token;
advance("=");
t.led (n);
a.push(t);
en la ...
parte.
que llamaría a nuestra función [[=]]
operadores led
(la función de asignación) , que hace
asegúrese de que sea un valor l apropiado, y configure un miembro de asignación para que luego podamos identificar rápidamente las declaraciones de asignación. p.ej
{
"arity": "binary",
"value": "=",
"assignment": true,
"first": {
"arity": "name",
"value": "t"
},
"second": {
"arity": "literal",
"value": "1"
}
}
ya que no hay un operador con un lbp
entre 0 y 10, la expression(0) vs. expression (9)
llamada expression(0) vs. expression (9)
no hace diferencia. ( !(0<0) && !(9<0) && 0<10 && 9<10)
)
Y la token.id === "="
impide asignaciones a un miembro de objeto como token.id
sería ''[''
o ''.''
y t.led
no se llamaría.
Mi pregunta en resumen es:
¿Por qué no llamamos a la función led
disponible de los operadores de asignación, opcionalmente después de la declaración de una variable? Pero en su lugar, configure manualmente el first
y second
miembro de la declaración, pero no el miembro de la assignment
.
Aquí hay dos viñetas que analizan una cadena simple. Usando el código original y uno usando los operadores de assignment led
.
No tengo tiempo para leer todo el artículo, así que no estoy cien por ciento seguro. En mi opinión, la razón es porque el operador de asignación en var
es un poco especial. No acepta todos los valores de izquierda posibles: no se permiten miembros de un objeto (no u [
operadores]). Solo se permiten nombres de variables simples.
Entonces no podemos usar la función de assignment
normal porque permite todos los valores a la izquierda.
Estoy bastante seguro de esto, pero lo siguiente es solo una suposición:
Tendríamos que llamar a la función de assignment
manera opcional y solo después de verificar que consumimos el operador de asignación.
advance();
if (token.id === "=") {
// OK, Now we know that there is an assignment.
Pero la assignment
función asume que el token actual es un valor izquierdo, no operador =
.
No tengo idea de por qué el miembro de la assignment
no está configurado como true
. Depende de lo que quieras hacer con el árbol generado. De nuevo, la asignación en la sentencia var
es un poco especial y puede que no sea factible configurarlo.
Assignment (por ejemplo, var t; t = 1;
) es conceptualmente diferente de la initialization (p. Ej., var t = 1;
), aunque ambos dan como resultado un cambio de estado de la memoria. No es conveniente usar el mismo código para implementar ambos, ya que uno podría cambiar independientemente del otro en una versión futura del idioma.
La diferencia conceptual se puede mostrar en C ++ cuando se habla de la sobrecarga del operador de asignación y los constructores de copia. La inicialización puede invocar el constructor de copia, la asignación podría invocar la sobrecarga del operador de asignación. La asignación nunca desencadena el constructor de copia, la inicialización nunca hace uso de la sobrecarga del operador de asignación. Consulte el tutorial sobre el constructor de copias y la sobrecarga del operador de asignación .
Otro ejemplo es el de Strix: de lejos, no todos los l-values se pueden usar después de var
en JavaScript. Creo que esta es la mayor diferencia entre ellos en JavaScript, si no el único. Ignorando el cambio de alcance obvio en var, por supuesto.
Uno podría pensar en el uso del signo igual para ambos como una coincidencia. Pascal usa :=
para asignación y =
para inicialización. JavaScript también podría usar algo como var t : 1;
.