Procesadores de Lenguajes
Curso 2024/2025
JavaScript es un lenguaje de programación ideado en 1995 en Netscape a partir de los lenguajes C, C++ y Java.
Este resumen presenta las principales características de la variante de JavaScript denominada JS-- que es la que hay que utilizar para la práctica de la asignatura. No hay que considerar los elementos de JavaScript no mencionados en este resumen y se deben considerar los elementos y características tal como aparecen aquí descritos. Con el fin de facilitar la implementación de la Práctica, las características mostradas en esta página pueden no coincidir al 100% con el estándar del lenguaje JavaScript, por lo que, en caso de duda, se deberá implementar siempre el comportamiento aquí descrito. Entre corchetes [] se dan indicaciones sobre la obligatoriedad u opcionalidad de algunas partes del lenguaje en cuanto a su implementación.
JS-- es un lenguaje en el que se diferencian las minúsculas y las mayúsculas (es case sensitive).
JS-- es un lenguaje de formato libre, es decir, que se admiten espacios, tabuladores, saltos de línea y comentarios en cualquier parte del código. Las sentencias simples y las declaraciones de variables finalizan en punto y coma.
Las palabras clave que tiene el lenguaje son reservadas (se escriben siempre en minúsculas). Cada grupo de prácticas sólo ha de tener en cuenta las palabras asignadas a su grupo.
JS-- es un lenguaje con estructura de bloques que se definen mediante la utilización de las llaves { }
y, por tanto, maneja los conceptos de identificadores globales y locales. Los identificadores declarados fuera de cualquier función son globales y pueden ser utilizados desde cualquier función definida con posterioridad. Los declarados en el interior de una función son locales a dicha función.
En JS-- no es obligatorio declarar todos los identificadores antes de que se utilicen; en este caso, un uso de un identificador no declarado se considera como una variable global entera. Además, hay que realizar la implementación considerando que es un lenguaje con recursividad, por lo que cualquier función puede ser recursiva. El lenguaje no permite la definición de funciones anidadas.
Debe considerarse que un programa en JS-- estará compuesto por un único fichero que puede tener declaraciones de variables globales, sentencias y declaración de funciones, en cualquier orden.
En este enlace se muestra un ejemplo de un fichero válido según el lenguaje descrito en esta asignatura.
El programa principal (por donde comenzará la ejecución del programa) estará formado por todas las sentencias ubicadas fuera de las funciones.
Por tanto, la ejecución comenzaría por la primera sentencia que se encuentre en el fuente (fuera de una función) y proseguiría secuencialmente hasta el final del fichero ejecutando todas las sentencias situadas fuera de las funciones. Hay que tener en cuenta que una función se ejecuta únicamente cuando es invocada.
En JS-- hay dos tipos de comentarios [cada grupo deberá implementar obligatoriamente solo el que le corresponda]:
/*
para abrir el comentario, y */
para cerrarlo. No se permiten comentarios anidados. Los comentarios pueden ocupar más de una línea y pueden ir colocados en cualquier parte del código:/* ¡Comentario con apertura y cierre! */
//
y finalizan al acabar la línea. Este tipo de comentario sólo ocupa una línea y puede ir colocado en cualquier parte del código:// Comentario de línea...
El lenguaje dispone de varios tipos de constantes [implementación obligatoria de las constantes enteras y cadenas]:
Para representar las constantes enteras se utilizan los dígitos decimales. Por ejemplo: 378
.
Los números enteros se tienen que poder representar con una palabra (16 bits, incluido el signo), por lo que el máximo entero válido será el 32767.
Las constantes cadena van encerradas entre comillas dobles ("¡Hola, mundo!"
) o entre comillas simples ('¡Hola, mundo!'
) [cada grupo deberá implementar obligatoriamente solo la que le corresponda]. Se utiliza internamente el carácter nulo (cuyo código ASCII es 0) como carácter de fin de cadena. Puede aparecer cualquier carácter imprimible en la cadena (por tanto, no es válido un salto de línea como tal; para usar el salto de línea, se pueden emplear las secuencias de escape).
Para representar caracteres especiales dentro de una cadena se utiliza una secuencia de escape. Una secuencia de escape se representa mediante el carácter barra inversa seguido de un determinado carácter. Algunos de estos caracteres son: el salto de línea (\n
) o el tabulador (\t
) [la implementación de estos caracteres especiales es opcional].
Una cadena puede estar vacía o contener hasta un máximo de 64 caracteres.
En JS-- existen dos constantes lógicas para representar verdadero y falso: true
y false
[es opcional implementar las constantes lógicas, que son las palabras reservadas true
y false
].
Este lenguaje presenta un conjunto de operadores con los que escribir distintas expresiones. Además, se pueden utilizar los paréntesis para agrupar subexpresiones [es obligatorio implementar los paréntesis]. Las expresiones pueden tener varios operadores, varios operandos, paréntesis... Las expresiones se pueden utilizar en multitud de construcciones del lenguaje: asignaciones, condiciones, parámetros de una función, instrucciones de salida, instrucciones de retorno...
Son los operadores que permiten realizar la suma, resta, producto, división y módulo: +
, -
, *
, /
y %
[obligatorio implementar al menos uno y dos como máximo]. Se aplican sobre datos enteros, proporcionando un resultado entero (en el caso de la división, redondeando el valor si es necesario).
También existen los operadores más y menos unarios: +
, -
[implementación opcional]. Estos operadores se pueden utilizar delante de una constante entera, una variable o una expresión.
Son los operadores que permiten realizar las comparaciones de igual, distinto, menor, mayor, menor o igual, mayor o igual: ==
, !=
, <
, >
, <=
y >=
[obligatorio implementar al menos uno de los operadores y dos como máximo]. Se aplican sobre datos enteros y proporcionan un resultado lógico.
Representan las operaciones de conjunción, disyunción y negación: &&
, ||
y !
[obligatorio implementar al menos uno y dos como máximo]. Se aplican sobre datos lógicos y devuelven un resultado lógico.
Permiten auto-incrementar o auto-decrementar el valor de una variable entera: ++
y --
(pueden actuar como prefijos o como sufijos) [algunos grupos tienen que implementar uno de estos operadores]. Se aplican sobre variables enteras y devuelven un resultado entero modificando también el valor de la variable. Ejemplo:
a = j++; /* si j valía 5, ahora a == 5 y j == 6 */
a = ++j; /* si j valía 5, ahora a == 6 y j == 6 */
Permiten realizar asignaciones simples o realizando simultáneamente una operación: =
(asignación), +=
(asignación con suma), -=
(asignación con resta), *=
(asignación con producto), /=
(asignación con división), %=
(asignación con módulo), &=
(asignación con y lógico) y |=
(asignación con o lógico) [todos los grupos tienen que implementar la asignación simple (=
) y algunos grupos deberán implementar uno de los operadores de asignación con operación]. Ejemplo:
n += m; /* es equivalente a n = n + m */
b1 &= b2; /* es equivalente a b1 = b1 && b2 */
No se permite usar estos operadores de asignación como parte de las expresiones.
En la tabla siguiente se muestra la precedencia de los operadores con el siguiente significado: los operadores del mismo grupo tienen la misma precedencia y, conforme se desciende por la tabla, la precedencia aumenta. La asociatividad de cada grupo de operadores se indica también en la tabla. En cualquier caso, el uso de paréntesis permite alterar el orden de evaluación de las expresiones [es obligatorio para todos los grupos tener en cuenta la precedencia y asociatividad de los operadores utilizados].
Operadores | Significado | Asociatividad |
---|---|---|
|| |
O lógico | Izquierda a derecha |
&& |
Y lógico | Izquierda a derecha |
== |
Igual Distinto |
Izquierda a derecha |
> |
Mayor Mayor o igual Menor Menor o igual |
Izquierda a derecha |
+ |
Suma Resta |
Izquierda a derecha |
* |
Producto División Módulo |
Izquierda a derecha |
! |
Negación lógica Autoincremento Autodecremento Más unario Menos unario |
Derecha a izquierda |
Los nombres de identificadores están formados por cualquier cantidad de letras, dígitos y subrayados (_
), siendo el primero siempre una letra. Ejemplos: a
, a3
, A3
, Sueldo_de_Trabajador
, z__9_9__
...
Como ya se ha dicho, el lenguaje diferencia minúsculas y mayúsculas, por lo que los nombres a3
y A3
son identificadores distintos.
El lenguaje JS-- no exige declaración explícita de las variables que se utilicen. En el caso de que se use un nombre de variable que no ha sido declarado previamente, se considera que dicha variable es global y entera (declaración implícita).
Para realizar una declaración explícita de una variable, se coloca la palabra var
seguida del nombre de la variable y del tipo (que deberá ser entero, lógico o cadena).
var Tipo var0;
Pueden realizarse declaraciones explícitas en cualquier lugar dentro de una función; en este caso, la variable será local y visible desde ese punto hasta el final de la función. También pueden realizarse declaraciones explícitas fuera de las funciones en cualquier parte del código (variables globales), pero esas variables solo pueden utilizarse a partir de dicha línea.
Opcionalmente, puede inicializarse una variable en la misma instrucción de la declaración, colocando el operador de asignación (=
) seguido de una expresión [es opcional implementar la inicialización de variables].
var Tipo var4 = expresión4;
Si una variable no se inicializa cuando se declara, se realiza una inicialización por omisión basándose en su tipo: 0
si es entera, falso si es lógica y la cadena vacía (""
o ''
) si es cadena.
En resumen, el ámbito de una variable será global si se declara fuera de cualquier función (o si no se declara), y será local si se declara dentro del cuerpo de una función. Cualquier otro bloque (por ejemplo, los usados por las sentencias de control) no define un ámbito nuevo. Por otro lado, el nombre de las funciones siempre estará en el ámbito global. No se admite la redeclaración del mismo identificador en un mismo ámbito.
El lenguaje dispone de distintos tipos de datos básicos. Se deben considerar sólo los siguientes tipos de datos básicos: entero, lógico, cadena y vacío.
El tipo entero se refiere a un número entero que debe ocupa un tamaño de 1 palabra (16 bits). Se representa con la palabra int
.
El tipo lógico se refiere a un valor lógico. Se representa con la palabra boolean
. El tipo lógico se almacena como un entero, por lo que ocupa también un tamaño de 1 palabra (16 bits). Las expresiones relacionales y lógicas devuelven un valor lógico.
El tipo cadena se refiere a una secuencia de caracteres. Se representa mediante string
y una variable de tipo cadena ocupa 64 palabras (128 bytes), es decir, un máximo de 64 caracteres.
El tipo vacío permite indicar la ausencia de tipo. Se representa mediante la palabra void
y solamente tiene sentido como el tipo de retorno de una función o para indicar ausencia de parámetros en una función. No se pueden declarar variables de tipo vacío.
El lenguaje no tiene conversiones automáticas entre tipos.
Ejemplos:
var int i = 11; // variable entera
var string st; // variable cadena
var boolean b; // variable lógica
var int c = 66+i; // variable entera
b = i != c + 1; // i y c+1 son enteros; b valdrá verdadero
c = c + i; // i y c son enteras; c valdrá 88
i = b + i; // Error: no se puede sumar un lógico con un entero
b = ! i; // Error: el operador de negación solo puede aplicarse a lógicos
Las instrucciones de entrada/salida disponibles en el lenguaje son dos. Su uso tiene la sintaxis de una sentencia.
La instrucción output expresión
evalúa la expresión
e imprime el resultado por pantalla. La expresión
puede ser de tipo cadena o entera. Por ejemplo:
c= 50; output c * 2 + 16 ; /** imprime: 116 **/
a= 'Adiós';
output 'Hola'; output a; // imprime HolaAdiós
La instrucción input nombre
lee un número o una cadena del teclado y lo almacena en la variable nombre
, que tiene que ser, respectivamente, de tipo entero o cadena. Por ejemplo:
var int a;
var string c;
input a; /* lee un número */
output a * a; // imprime el cuadrado del número leído
output"Pon tu nombre";
input c; /* lee una cadena */
output ("Hola, ");
output(c); // imprime las cadenas
De todo el grupo de sentencias del lenguaje JavaScript, se han seleccionado para ser implementadas las que aparecen a continuación [opcional u obligatoriamente, según se indique en cada caso]. Además de las sentencias aquí indicadas, también se consideran sentencias en este lenguaje las instrucciones de entrada/salida (output
y input
), así como las declaraciones de variables.
Existe una sentencia de asignación en JS--, que se construye mediante el símbolo de asignación =
[es obligatorio implementar la sentencia de asignación por todos los grupos; los grupos que tengan el operador de asignación con operación deberán implementar también la sentencia de asignación con operación con el operador asignado]. Su sintaxis general es la siguiente: variable, operador-asignación y expresión. Esta sentencia asigna a la variable el resultado de evaluar la expresión:
i= 8 + 6;
Como ya se ha indicado, no hay conversiones entre tipos, por lo que tanto el identificador como la expresión han de ser del mismo tipo. Seguidamente se muestra un ejemplo de código:
i = 123; // i es una variable entera
var string cad;
output i; // imprime el valor entero 123
cad= 'hola';
output cad; // imprime el valor cadena "hola"
i = i > 88; // Error: no se puede asignar un lógico a un entero
Esta sentencia permite invocar la ejecución de una función que debe estar previamente definida [implementación obligatoria].
La llamada a una función se realiza mediante el nombre de la función seguido de los parámetros actuales (separados por comas) entre paréntesis (si no tiene parámetros, hay que poner los paréntesis vacíos). Los parámetros pueden ser cualquier expresión válida en el lenguaje:
p1 (5); /* llamada a una función con un argumento entero */
p2 (); /* llamada sin parámetros a una función */
p3 ("", i - 8); /* llamada con dos argumentos a una función */
Los parámetros actuales en la llamada tienen que coincidir en número y tipo con los parámetros formales de la declaración de la función.
Si una función devuelve un valor, podrá incluirse una llamada a dicha función dentro de cualquier expresión. Si la llamada se realiza como una sentencia (no se realiza en una expresión), se invocará a la función pero el valor devuelto se perderá:
if (fun1 (9)) /* llamada a una función con un argumento entero */
c= b + fun2 (b, fun3() != 8); /* llamada con dos argumentos a una función,
siendo fun3, una llamada a otra función sin argumentos */
fun2 (c, true); /* el valor devuelto por fun2 se pierde */
JS-- dispone de la sentencia return
para finalizar la ejecución de una función y volver al punto desde el que fue llamada [implementación obligatoria].
Una función finalizará su ejecución cuando se ejecute una instrucción return
o al llegar al final del cuerpo de la función (es decir, no es obligatorio que aparezca la instrucción return
).
Si una función tiene tipo devuelto, sus sentencias return
deberán contener una expresión. El tipo de la expresión retornada deberá coincidir con el tipo devuelto de la función.
Si una función no tiene tipo devuelto (void
), sus sentencias return
no deberán contener una expresión.
No es necesario que todas las funciones tengan instrucción de retorno (independientemente del tipo devuelto con que se haya declarado la función) ni tampoco hay ninguna restricción a la cantidad o ubicación de las instrucciones de retorno dentro de una función.
function int ProductoDoble (int a, int b)
{
j= a * b;
return j * 2;
// La función finaliza y devuelve el valor entero de la expresión
}
function void pro (int x)
{
x= ProductoDoble (x - 1, x);
/* x contendrá el valor devuelto por la función: 2*x*(x-1) */
if (x > (194/2)) return; // finaliza la ejecución si se ejecuta
output ProductoDoble (x, x);
} /* finaliza la ejecución si antes no se ejecutó el return */
Selecciona la ejecución de una sentencia, dependiendo del valor correspondiente de una condición de tipo lógico [implementación obligatoria para todos los grupos]:
if (condición) sentencia
Si la condición
lógica se evalúa como cierta, se ejecuta la sentencia
que puede ser cualquier sentencia simple del lenguaje, es decir, asignación, instrucción de entrada/salida, llamada a función o retorno (también break
o sentencias de auto-incremento o auto-decremento [para los grupos que tengan dichas opciones]); en caso contrario, se finaliza su ejecución:
if (a > b) c= b;
if (fin) output("adiós");
Selecciona la ejecución de una de las secuencias de sentencias que encierra, dependiendo del valor correspondiente de una condición de tipo lógico. Tiene dos formatos [implementación obligatoria de ambos para los grupos que les corresponda]:
if (condición)
{
cuerpo1
}
if (condición)
{
cuerpo1
}
else
{
cuerpo2
}
Si la condición
lógica se evalúa como cierta, se ejecuta el cuerpo1
; en caso contrario, se ejecuta el cuerpo2
(si el programador lo ha escrito). Cada uno de estos cuerpos contendrán cero, una o más sentencias o declaraciones, siempre entre llaves.
if (a > b)
{
c= b;
}
else
{
c= a;
if (fin)
{
output"adiós";
}
}
while
Esta sentencia permite repetir la ejecución de unas sentencias basándose en el resultado de una expresión lógica [implementación obligatoria para algunos grupos]. La sintaxis es:
while (condición)
{
cuerpo
}
Se evalúa la condición
lógica y, si resulta ser cierta, se ejecuta el cuerpo
(que será un bloque de sentencias o declaraciones de variables entre llaves). Este proceso se repite hasta que la condición
sea falsa:
while (n <= 10)
{
n= n + 1;
output n;
} // mientras que n sea menor o igual que 10...
do while
Esta sentencia permite repetir la ejecución de las sentencias del bucle mientras se cumpla una condición [implementación obligatoria para algunos grupos]. La sintaxis es:
do {
cuerpo
} while (condición);
En esta instrucción se ha de colocar un bloque de sentencias o declaraciones de variables encerradas entre llaves. Primero, se ejecuta el cuerpo
; seguidamente, se evalúa la condición
lógica y, si resulta ser cierta, se ejecuta de nuevo el cuerpo
. Este proceso se repite hasta que la condición
sea falsa:
do {
c= a++;
c*= b;
} while (a < b);
for
Esta sentencia for
permite ejecutar un bucle según una condición [implementación obligatoria para algunos grupos]. La sintaxis es:
for (inicialización; condición; actualización)
{
cuerpo
}
La inicialización
debe ser una sentencia de asignación sencilla o nada; la condición
debe ser una expresión lógica; y la actualización
puede ser una asignación (sencilla o con operación [para los grupos que tengan esta opción]), un autoincremento o autodecremento [para los grupos que tengan esta opción] o estar vacía. El cuerpo
(con cero, una o más sentencias o declaraciones de variables) está en un bloque delimitado por llaves.
El funcionamiento de este bucle es como sigue:
inicialización
condición
condición
lógica es falsa, se abandona la ejecución del buclecuerpo
actualización
for (i = 1; i < 10; i++)
{
f *= i;
}
Esta sentencia selecciona y ejecuta unas sentencias basándose en el resultado de una expresión [implementación obligatoria para algunos grupos]. La sintaxis de la sentencia de selección múltiple es:
switch (expresión)
{
case valor1: cuerpo1
case valor2: cuerpo2
/* . . . */
default: cuerpon
}
Su funcionamiento es como sigue: Se evalúa la expresión
(que debe ser de tipo entero), se busca el valor que coincida con el resultado (los valores tienen que ser constantes enteras) y se ejecuta su cuerpo asociado (cada cuerpo puede estar formado por cero, una o más sentencias o declaraciones de variables). Una vez ejecutado, se continúa la ejecución de todos los cuerpos asociados a todos los valores que se encuentren a continuación hasta el final del switch
. Si no se encuentra el valor, se ejecutan las sentencias asociadas a default
, si el programador la ha incluido [el default
es opcional para la implementación]:
switch (dia)
{
case 1: output 'lunes';
case 2: output 'martes';
case 3: output 'miércoles';
case 4: output 'jueves';
case 5:
output 'viernes';
default: output'fiesta';
} /* si dia == 4, se imprimirá: jueves viernes fiesta */
break
Esta sentencia aborta la ejecución de un switch
[implementación obligatoria para los grupos que tengan switch
]. No hay ninguna restricción en cuanto al lugar o número de veces que se puede poner la sentencia break
en cada case
:
switch (dia)
{
case 0: break;
case 1: output "lunes"; break;
case 2: output "martes";
case 3: output "miércoles";
break;
case 4: output "jueves"; break;
case 5: output "viernes"; break;
case 6: if (error) break; output("sábado");
default: if (dia < 8) output "fiesta";
} /* si dia == 4, se imprimirá: jueves */
Es necesario definir cada función antes de poder utilizarla. La definición de una función se realiza indicando la palabra function
, el tipo de retorno, el nombre y, entre paréntesis, los parámetros (si existen) con sus tipos. Tras esta cabecera va un bloque (delimitado por llaves) con el cuerpo de la función (que puede tener cualquier cantidad de sentencias o declaraciones de variables):
function Tipo nombre (lista de argumentos)
{
sentencias | declaración de variables
}
Las funciones pueden devolver un valor de uno de los tipos básicos del lenguaje (entero, lógico o cadena) o no retornar nada (en este caso, se debe indicar que la función retorna void
). El tipo de retorno de la función se determina según el Tipo
que aparezca en su declaración. En caso de que las instrucciones return
de una función tengan expresiones de un tipo distinto al declarado, será un error. No es obligatorio que una función tenga una instrucción return
en su cuerpo ni que tenga que ser la última instrucción.
La lista de argumentos (que puede estar vacía y, en este caso, se ponen los paréntesis con la palabra void
en su interior) consta del tipo y del nombre de cada parámetro formal. Si hay más de un argumento, se separan por comas. Los parámetros se pasan siempre por valor.
Las funciones pueden recibir como parámetros cualquiera de los tipos básicos del lenguaje (entero, lógico o cadena).
JS-- admite recursividad. Todos los grupos de prácticas han de considerarla en su implementación. Cualquier función puede ser recursiva, es decir, puede llamarse a sí misma.
El lenguaje JS-- no permite la definición de funciones anidadas. Esto implica que dentro de una función no se puede declarar ni definir otra función.
Dentro de una función se tiene acceso a las variables globales, a sus argumentos y a sus variables locales. Si en una función se declara una variable local o un argumento con el mismo nombre que un identificador global, este último no es accesible desde dicha función.
function void hola (void) // función sin parámetros
{
output"Hello!";
}
var int x; // global
function int factorial (int x)
/* se define la función recursiva con un parámetro,
que oculta a la variable global de igual nombre */
{
hola();
if (x > 1)
return x * factorial (x - 1);
return 1;
} // la función devuelve un entero
function boolean Suma (int aux, int fin, boolean b)
/* se define la función Suma que recibe
dos enteros y un lógico **/
/* usa la variable global x */
{
for (x= 1; (x < fin) && b; x= x + 2)
{
aux += factorial (aux-1);
}
return aux > 10000;
} // la función devuelve un lógico
function void Imprime (int a)
{
output (a);
return; // esta instrucción se podría omitir
} // la función no devuelve nada
input x;
Imprime (factorial (Suma (x, 3, true)));
// se llama a las tres funciones
Hay que tener en cuenta que para esta práctica no es necesario tener un conocimiento total del lenguaje JavaScript. Tan solo hay que incorporar el funcionamiento del pequeño subconjunto del lenguaje que se ha explicado aquí, necesario para implementar las partes comunes a todos los grupos, así como las partes obligatorias de cada grupo. Por tanto, no se deben incluir todas las características, variantes y elementos que tiene el lenguaje JavaScript y, en caso de inconsistencia, deberá elegirse el comportamiento aquí descrito.
Aquí se tiene un ejemplo de un fichero que incorpora la mayoría de las funcionalidades descritas en esta página, a modo de ejemplo adicional para facilitar su compresión.
En cualquier caso, se puede encontrar más información sobre JavaScript en la página de referencias, en los libros que hay en la Biblioteca de la Escuela (se puede usar el buscador de la UPM), así como en cualquier libro, manual o documentación del lenguaje (buscando en librerías como Amazon). También se pueden encontrar otras fuentes de información realizando búsquedas en la Web o en los siguientes enlaces: