Utiliza este documento como referencia rápida para todas las funciones del lenguaje de programación Verse y su sintaxis. Sigue los enlaces para obtener más información.
Expresiones
Una expresión es la unidad más pequeña de código que produce un valor cuando se evalúa. Un ejemplo es una expresión if... else
, que en Verse evalúa a un valor que depende del contenido de los bloques de expresión.
El siguiente código evalúa a un valor de cadena que contiene "¡Grande!"
o "¡Pequeño!"
, dependiendo de si MyNumber
era mayor que 5:
if (MyNumber > 5):
"Big!"
else:
"Small!"
Comentarios del código
Un comentario del código explica algo sobre el código, o el motivo, razón, causa del programador de por qué algo está programado de la forma que está. Cuando el programa se ejecuta, los comentarios del código se ignoran.
Verse
| comentario de una sola línea: todo lo que aparece entre |
Verse
| comentario de bloque en línea: todo lo que aparece entre |
Verse
| comentario de bloque de varias líneas: todo lo que aparece entre |
Verse
| comentario de bloque anidado: todo lo que aparece entre |
Verse
| comentario con sangría: todo lo que aparece en líneas nuevas después de |
Constantes y variables
Las constantes y las variables pueden almacenar información (o valores) que utiliza el programa y asociar estos valores a un nombre. El nombre es el identificador.
Para crear tu propia variable o constante, tienes que decírselo a Verse. Esto se denomina declaración. Aunque es recomendable especificar un valor inicial (llamado inicialización) para las variables, esto es opcional. No obstante, es obligatorio para las constantes.
Puedes cambiar el valor de una variable en cualquier momento. Esto recibe el nombre formal de asignación porque estás asignando un valor a la variable, pero a veces también se denomina definir la variable.
Verse
| Crear una constante: el valor de una constante no puede modificarse mientras se ejecuta el programa. Una constante se crea especificando su nombre, tipo y valor. | Haz clic en la imagen para ampliarla. |
Verse
| Crear una variable: el valor de una variable puede modificarse mientras se ejecuta el programa. Una variable se crea añadiendo la palabra clave | Haz clic en la imagen para ampliarla. |
Verse
| Cambiar el valor de una variable: puedes cambiar el valor de una variable mientras se ejecuta el programa utilizando la palabra clave | Haz clic en la imagen para ampliarla. |
Tipos
Verse es un lenguaje de programación de tipado estático, lo que significa que se asigna un tipo a cada identificador.
Hay instancias en las que el tipo no se requiere explícitamente, como cuando se crea una constante en una función. En el ejemplo MyConstant := 0
, el tipo de MyConstant
se infiere porque se le asigna el valor 0.
Tipos comunes
Verse tiene tipos incorporados que permiten las operaciones fundamentales que la mayoría de los programas necesitan realizar. Puedes crear tus propios tipos combinándolos en estructuras más grandes, pero es importante entender estos tipos comunes como base para el uso de variables y constantes en Verse.
Verse
| logic: |
Verse
| int: un
|
Verse
| float: un
|
Verse
| string: Puedes inyectar el resultado de una expresión en una cadena utilizando {} dentro de "". Esto recibe el nombre de interpolación de cadenas. Para obtener más información sobre el tipo |
Verse
| message: Puedes convertir el texto en una cadena visualizable en tiempo de ejecución utilizando la función Actualmente, el único texto que puede devolver la función |
Verse
| locale: este tipo representa en qué contexto debe localizarse un valor de Actualmente, el tipo |
Verse
| rational: el tipo
|
Verse
| void: el tipo
Para obtener más información sobre el tipo |
Verse
| any: el tipo |
Verse
| comparable: el tipo |
Para obtener más información sobre los tipos comunes en Verse, consulta Tipos comunes.
Tipos de contenedores
Puedes almacenar varios valores juntos utilizando un tipo de contenedor. Verse dispone de varios tipos de contenedores donde almacenar valores. Para obtener más información sobre los tipos de contenedores en Verse, consulta Tipos de contenedores.
Opción
El tipo option
puede contener un valor o estar vacío.
En el siguiente ejemplo, MaybeANumber
es un entero opcional ?int
que no contiene ningún valor. Un nuevo valor para MaybeANumber
se establece entonces en 42
.
var MaybeANumber : ?int = false # unset optional value
set MaybeANumber := option{42} # assigned the value 42
Haz clic en la imagen para ampliarla.
Verse
| Crear una opción: puedes inicializar una opción con uno de los valores siguientes:
Especifica el tipo añadiendo |
Verse
| Acceder a un elemento en una opción: utiliza el operador de consulta |
A continuación se muestra un ejemplo de uso de un tipo de opción para guardar una referencia a un jugador generado y, cuando se genera un jugador, hacer que el dispositivo activador reaccione:
my_device := class<concrete>(creative_device):
var SavedPlayer : ?player = false # unset optional value
@editable
PlayerSpawn : player_spawner_device = player_spawner_device{}
@editable
Trigger : trigger_device = trigger_device{}
OnBegin<override>() : void =
Rango
La expresión range contiene todos los números de un rango determinado, y solo puede utilizarse en expresiones específicas, como la expresión for. Los valores del rango solo pueden ser enteros.
Haz clic en la imagen para ampliarla.
Verse
| Crear un rango: el inicio del rango es el primer valor de la expresión, y el final del rango es el valor que sigue a |
Verse
| Iterar sobre un rango: puedes utilizar la expresión |
Para obtener más información, consulta Range.
Matriz
Una matriz (array) es un contenedor donde se pueden almacenar elementos del mismo tipo, y acceder a los elementos por su posición (denominada índice) en la matriz. El primer índice de la matriz es 0 y el último índice es uno menos que el número de elementos de la matriz.
Players : []player = array{Player1, Player2}
Haz clic en la imagen para ampliarla.
Verse
| Crear una matriz: utiliza la palabra clave |
Verse
| Acceder a un elemento de una matriz: utiliza |
Verse
| Cambiar un elemento de una matriz: puedes cambiar el valor almacenado en una variable de una matriz en un índice utilizando la palabra clave |
Verse
| Iterar sobre una matriz: puedes acceder a cada elemento de una matriz, en orden, del primero al último, utilizando la expresión for. Con la expresión |
Verse
| Obtener el número de elementos de una matriz: utiliza |
Verse
| Concatenar matrices: puedes fusionar matrices utilizando el operador |
Para obtener más información, consulta Array.
tupla
Una tupla es una agrupación de dos o más expresiones que se trata como una única expresión. El orden de los elementos de la expresión es importante. La misma expresión puede estar en varias posiciones de una tupla: Las expresiones de tupla pueden ser de cualquier tipo y pueden contener varios tipos (a diferencia de las matrices, que solo pueden tener elementos de un tipo):
Las tuplas son especialmente útiles para:
Cómo devolver varios valores desde una función.
Cómo pasar varios valores a una función.
* Una agrupación en línea es más concisa que la sobrecarga de hacer una estructura de datos reutilizable (como una estructura o una clase).
VerseExampleTuple : tuple(int, float, string) = (1, 2.0, "three")
Haz clic en la imagen para ampliarla.
Verse
| Crear una tupla: utiliza |
Acceder a un elemento de una tupla: utiliza | |
Expansión de tuplas: cuando utilizas una tupla como argumento de una función en lugar de especificar cada argumento individual, la función se llamará con cada elemento de la tupla utilizado como argumento en el orden en que estén especificados en la tupla. | |
Coerción de matriz de tuplas: las tuplas pueden pasarse allí donde se espera una matriz, siempre que el tipo de los elementos de la tupla coincidan con el tipo de la matriz. No se pueden pasar matrices donde se espera una tupla. |
Para obtener más información, consulta Tuple.
Mapa
Un mapa es un contenedor en el que se pueden almacenar valores asociados a otro valor, denominados pares clave-valor, y acceder a elementos por sus claves únicas.
WordCount : [string]int = map {"apple" => 11, "pear" => 7}
Haz clic en la imagen para ampliarla.
Crear un mapa: utiliza la palabra clave | |
Acceder a un elemento de un mapa: utiliza | |
Iterar sobre un mapa: puedes acceder a cada elemento de un mapa, en orden, desde el primer elemento insertado hasta el último, utilizando la expresión for. Con la expresión | |
Obtener el número de pares clave-valor en un mapa: utiliza |
Para más información, consulta Mapa.
Tipos compuestos
Un tipo compuesto es cualquier tipo que puede estar formado por campos y elementos (normalmente con nombre) de tipos primitivos o de otros tipos. Los tipos compuestos suelen tener un número fijo de campos o elementos a lo largo de su duración. Para obtener más información, consulta Tipos compuestos.
Enum
Enum es la abreviatura de enumeración, que significa nombrar o enumerar una serie de cosas, llamadas enumeradores. Se trata de un tipo en Verse que puede utilizarse para cosas como los días de la semana o las indicaciones de una brújula.
Haz clic en la imagen para ampliarla.
Crear una enumeración: utiliza la palabra clave | |
Acceder a un enumerador: utiliza |
Estructura
Struct es la abreviatura de estructura, y es una forma de agrupar varias variables relacionadas. Las variables pueden ser de cualquier tipo.
Haz clic en la imagen para ampliarla.
Haz clic en la imagen para ampliarla.
Crear una estructura: utiliza la palabra clave | |
Crear una instancia de estructura: puedes crear una instancia de una estructura a partir de un arquetipo. Un arquetipo define los valores de los campos de una estructura. | |
Acceder a los campos de una estructura: puedes acceder a los campos de una estructura para obtener su valor añadiendo |
Clase
Una clase es una plantilla para crear objetos con comportamientos y propiedades (variables y métodos) similares, y debe ser instanciada para crear un objeto con valores reales. Las clases son jerárquicas, lo que significa que una clase puede heredar información de elemento principal (superclase) y compartir su información con sus elementos secundarios (subclases). Una clase puede ser un tipo personalizado definido por el usuario.
Haz clic en la imagen para ampliarla.
Haz clic en la imagen para ampliarla.
Crear una clase: las definiciones que se anidan dentro de la clase definen campos de la clase. Las funciones definidas como campos de una clase también se llaman métodos. | |
Crear una instancia de clase: puedes crear una instancia de clase utilizando el nombre de la clase seguido de | |
Acceder a los campos de una clase: puedes acceder a los campos de una clase para obtener su valor añadiendo | |
Acceder a los métodos de una clase: puedes acceder a los métodos de una clase para llamarlos añadiendo | |
Self: puedes utilizar |
También hay especificadores que son exclusivos de las clases y que cambian su comportamiento. Consulta Especificadores de clase para obtener más información.
Para obtener más información sobre las clases, consulta Clase.
Subclase
Una subclase es una clase que amplía la definición de otra, añadiendo o modificando los campos y métodos de la primera (denominada la superclase).
Haz clic en la imagen para ampliarla.
Crear una subclase: especifica una clase como subclase de otra clase añadiendo la otra clase entre los | |
Anular campos de la superclase: puedes cambiar los valores de un campo definido en la superclase para la subclase solo añadiendo el especificador | |
Anular métodos de la superclase: [INCLUDE:#overriding_methods_on_superclass_description] | |
Super: de forma parecida a |
Para más información, consulta Subclase.
Constructor
Un constructor es una función especial que crea una instancia de la clase a la que está asociado. Puede utilizarse para establecer los valores iniciales del nuevo objeto.
Definir un constructor para una clase: para añadir un constructor a una clase, añade el especificador | |
Añadir variables y ejecutar código en el constructor: puedes ejecutar expresiones dentro de un constructor con la expresión block, e introducir nuevas variables con la palabra clave | |
Llamar a otros constructores en un constructor: desde un constructor, puedes llamar a otros. Desde un constructor de la clase, también puedes llamar a los constructores para la superclase de dicha clase, siempre que todos los campos estén inicializados. Cuando un constructor llama a otro y ambos inicializan campos, solo se utilizan los valores proporcionados al primer constructor para los campos. El orden de evaluación de las expresiones entre los dos constructores será en el orden en que se escriban las expresiones (en lo que respecta a los efectos secundarios), pero solo se utilizan los valores proporcionados al primer constructor. |
Interfaz
El tipo `interface` proporciona un contrato sobre cómo interactuar con cualquier clase que implemente la interfaz. Una interfaz no puede ser instanciada, pero una clase puede heredar de la interfaz e implementar sus métodos. Una interfaz es similar a una clase abstract, excepto que no permite una implementación parcial o campos como parte de la definición.
Haz clic en la imagen para ampliarla.
Haz clic en la imagen para ampliarla.
Crear una interfaz: una interfaz se define de forma similar a una clase, pero con la palabra clave | |
Ampliar una interfaz: una interfaz puede ampliar la definición de otra interfaz especificando la interfaz que se quiere ampliar entre los | |
Implementar una interfaz: puedes implementar una interfaz con una clase especificando la interfaz entre los | |
Implementar varias interfaces: una clase puede implementar varias interfaces. Las interfaces están separadas por | |
Heredar de una interfaz y de otra clase: una clase puede implementar una interfaz y ser una subclase de otra clase. La interfaz y la superclase están separadas por |
Para obtener más información, consulta Interfaz.
Cómo trabajar con tipos
Verse permite trabajar con tipos de forma sencilla.
Alias de tipos: puedes dar a un tipo un nombre diferente en tu código y hacer referencia al tipo con ese nuevo nombre. La sintaxis es similar a la inicialización de constantes. También puedes dar un alias de tipo a un tipo de función. Para obtener más información, consulta Alias de tipos. | |
Tipo paramétrico como argumento de tipo explícito: Verse admite tipos paramétricos (tipos esperados como argumentos). Esto solo funciona con clases y funciones. Para obtener más información, consulta Tipos paramétricos. | |
Tipo paramétrico como argumento de tipo implícito a las funciones: la razón de utilizar tipos paramétricos implícitos con las funciones es que permite escribir código que es invariante de un tipo concreto una sola vez, en lugar de para cada tipo con el que se utiliza la función. Para obtener más información, consulta Tipos paramétricos. | |
Macro de tipo: Verse tiene una construcción especial que puede utilizarse para obtener el tipo de una expresión arbitraria. Se puede utilizar en cualquier lugar donde se pueda utilizar un tipo. Para obtener más información, consulta Macro de tipo. | |
Subtipo: puedes utilizar |
Para obtener más información, consulta Trabajar con tipos de Verse.
Operadores
Los operadores son funciones especiales definidas en el lenguaje de programación Verse que realizan acciones, como operaciones matemáticas, sobre sus operandos.
Cuando se utilizan varios operadores en la misma expresión, se evalúan en orden de mayor a menor precedencia. Si hay operadores con la misma precedencia en la misma expresión, se evalúan de izquierda a derecha.
La siguiente tabla enumera todos los operadores incorporados en Verse, en orden de mayor a menor precedencia.
Operador | Descripción | Formato del operador | precedencia de operadores | Ejemplo |
---|---|---|---|---|
Consulta | El operador | posfijo | 9 |
|
No | El operador | Prefijo | 8 |
|
Positivo | Puedes usar el operador | Prefijo | 8 |
|
Negativo | Puedes utilizar el operador | Prefijo | 8 |
|
Multiplicación |
| infijo | 7 |
|
División | El operador | infijo | 7 |
|
Suma | El operador `+` suma dos valores numéricos. Cuando se utiliza con cadenas y matrices, los dos valores se concatenan. Consulta Matemáticas para obtener más información. | infijo | 6 |
|
Resta | El operador | infijo | 6 |
|
Asignación de suma | Con este operador, puedes combinar la suma y la asignación en la misma operación para actualizar el valor de una variable. Consulta Matemáticas para obtener más información. | infijo | 5 |
|
Asignación de resta | Con este operador, puedes combinar la resta y la asignación en la misma operación para actualizar el valor de una variable. Consulta Matemáticas para obtener más información. | infijo | 5 |
|
Asignación de multiplicación | Con este operador, puedes combinar la multiplicación y la asignación en la misma operación para actualizar el valor de una variable. Consulta Matemáticas para obtener más información. | infijo | 5 |
|
Asignación de división | Con este operador, puedes combinar la división y la asignación en la misma operación para actualizar el valor de una variable, a menos que la variable sea un número entero. Consulta Matemáticas para obtener más información. | infijo | 5 |
|
Igual a | El operador | infijo | 4 |
|
No es igual a | El operador | infijo | 4 |
|
Menor que | El operador | infijo | 4 |
|
Menor o igual que | El operador | infijo | 4 |
|
Mayor que | El operador | infijo | 4 |
|
Mayor o igual que | El operador | infijo | 4 |
|
Y | El operador | infijo | 3 |
|
O | El operador | infijo | 2 |
|
Inicialización de variables y constantes | Con este operador, puedes almacenar valores en una constante o en una variable. Para obtener más información, consulta Constantes y variables. | infijo | 1 |
|
Asignación de variables | Con este operador puedes actualizar los valores almacenados en una variable. Para obtener más información, consulta Constantes y variables. | infijo | 1 |
|
Para obtener más información, consulta Operadores.
Agrupación
Puedes cambiar el orden de evaluación de los operadores agrupando las expresiones con ()
. Por ejemplo, (1+2)*3
y 1+(2*3)
no se evalúan con el mismo valor porque las expresiones agrupadas en () se evaluarían primero.
El siguiente ejemplo muestra cómo utilizar la agrupación para calcular una explosión en el juego que escala su daño en función de la distancia al jugador, pero en la que la armadura del jugador puede reducir el daño total:
BaseDamage : float = 100.0
Armor : float = 15.0
DistanceScaling : float = Max(1.0, Pow(PlayerDistance, 2.0))
ExplosionDamage : float = Max(0.0, (BaseDamage / DistanceScaling) - Armor)
Consulta Agrupación para obtener más información.
Bloques de código
Un bloque de código es un grupo de cero o más expresiones, e introduce un nuevo cuerpo con ámbito. Un bloque de código debe seguir a un identificador. Puede ser un identificador de función o un identificador de flujo de control como if y for, por ejemplo.
Con espacios: comienza con | |
Multilínea con llaves: encerrados entre | |
Una sola línea con llaves: encerrados entre |
También es posible utilizar ;
para poner más de una expresión en una línea. En un formato que tiene cada expresión en una nueva línea, los caracteres {}
no tienen que estar en sus propias líneas.
La última expresión de un bloque de código es el resultado del mismo. En el ejemplo siguiente, el bloque de código de la expresión if
da lugar a false
si IsLightOn?
tiene éxito, o true
, si IsLightOn?
falla. El resultado de logic
se almacena entonces en NewLightState
.
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
false
else:
Light.TurnOn()
true
Para obtener más información, consulta Bloques de código.
Alcance
Ámbito hace referencia al código dentro de un programa Verse en el que la asociación de un identificador (nombre) a un valor es válida, y en el que ese identificador puede utilizarse para referirse al valor.
Por ejemplo, cualquier constante o variable que crees dentro de un bloque de código existe solo en el contexto de ese bloque de código. Esto significa que el tiempo de vida útil de los objetos está limitada al ámbito en el que se crean, y no pueden utilizarse fuera de ese bloque de código.
El siguiente ejemplo muestra cómo calcular el número máximo de flechas que se pueden comprar con el número de monedas que tiene el jugador. La constante MaxArrowsYouCanBuy
se crea dentro del bloque if
y por lo tanto su ámbito se limita al bloque if
. Cuando se utiliza la constante MaxArrowsYouCanBuy
en la cadena de impresión, se produce un error porque el nombre MaxArrowsYouCanBuy
no existe fuera del ámbito de la expresión if
.
CoinsPerQuiver : int = 100
ArrowsPerQuiver : int = 15
var Coins : int = 225
if (MaxQuiversYouCanBuy : int = Floor(Coins / CoinsPerQuiver)):
MaxArrowsYouCanBuy : int = MaxQuiversYouCanBuy * ArrowsPerQuiver
Print("You can buy at most {MaxArrowsYouCanBuy} arrows with your coins.") # Error: Unknown identifier MaxArrowsYouCanBuy
Haz clic en la imagen para ampliarla.
Verse no admite la reutilización de un identificador aunque esté declarado en un ámbito diferente, a menos que califiques el identificador añadiendo (qualifying_scope:)
antes del identificador, donde qualifying_scope
es el nombre de un módulo, una clase o una interfaz del identificador. Siempre que definas y utilices el identificador, debe añadir también el calificador al identificador.
Funciones
Una función es una secuencia de expresiones con nombre que puedes reutilizar. Una función proporciona instrucciones para realizar una acción o crear una salida basada en una entrada.
Definiciones de funciones
Para definir tu propia función, debes proporcionar tres partes clave: un nombre único (identificador), el tipo de información que puedes esperar como resultado y lo que hará la función cuando la llames. A continuación puedes ver la sintaxis básica de una función:
name() : type =
codeblock
nombre() y tipo separados por dos puntos: esta es la firma de la función, que es la forma en que debes llamar y utilizar la función, y el valor que debe devolver la función es del tipo que proporcionas. Este formato es similar a la forma de crear constantes, excepto por el () después del nombre, que imita la forma de llamar a la función en el código.
El bloque de código de la función: define lo que hará la función cuando sea llamada proporcionando
=codeblock
, dondecodeblock
es cualquier secuencia de una o más expresiones. Siempre que llames a la función, se ejecutarán las expresiones del bloque de código.
Haz clic en la imagen para ampliarla.
Resultados de las funciones
Cuando tu función tiene un tipo de devolución especificado, el cuerpo de la función debe producir un resultado de ese tipo o el código no se compilará.
Última expresión devuelta con un valor: de forma predeterminada, la última expresión del bloque de código de la función es el resultado de la misma y cuyo valor debe coincidir con el tipo de retorno de la función. | |
Devolución explícita con un valor: también puedes definir explícitamente lo que devolverá la función utilizando |
Cuando creas una función que no necesita producir un resultado, puedes establecer el tipo de retorno de la función como void, lo que significa que no se espera que la función produzca un resultado útil y, por tanto, la última expresión del bloque de código de la función puede ser de cualquier tipo.
Puedes salir de una función cuyo tipo de retorno es void
utilizando la expresión return
por sí misma. Esta expresión sale de la función inmediatamente, aunque haya más expresiones después de ella en el bloque de código.
Parámetros de funciones
La introducción de datos en una función se define con parámetros. Un parámetro es una constante que se declara en la firma de la función entre los paréntesis y que luego puedes utilizar en el cuerpo de la función.
A continuación puedes ver la sintaxis de una función con dos parámetros:
name(parameter1name : type, parameter2name : type) : type =
codeblock
Haz clic en la imagen para ampliarla.
En el siguiente ejemplo, la función IncreaseScore()
tiene un parámetro entero llamado Points
, que la función utiliza para aumentar el valor de MyScore
:
var MyScore : int = 100
IncreaseScore(Points : int) : void =
# Increase MyScore by Points.
set MyScore = MyScore + Points
llamadas a función
Cuando quieras utilizar la secuencia de expresiones con nombre (la función) en el código, llamarás a la función por su nombre, por ejemplo, GetRandomInt(1, 10)
, que devuelve un entero aleatorio entre 1 y 10, ambos inclusive.
Hay dos formas de llamar a una función dependiendo de si la llamada de la función es falible:
Llamada a una función no falible: una llamada de función que no puede fallar tiene la forma | |
Llamada a una función falible: una llamada de función que puede fallar tiene la forma |
Argumentos de funciones
Cuando llamas a una función que espera parámetros, debes asignar valores a los parámetros, igual que necesitas asignar valores a las constantes para poder utilizarlas. Los valores asignados se llaman argumentos de la función.
A continuación puedes ver la sintaxis para llamar a una función con dos argumentos:
name(parameter1name := value, parameter2name := value)
En el siguiente ejemplo, se llama a la función IncreaseScore()
tres veces, con diferentes argumentos cada vez (10, 5 y 20), para aumentar el valor de MyScore
:
# After this call, MyScore is 110
IncreaseScore(Points := 10)
# After this call, MyScore is 115
IncreaseScore(Points := 5)
# After this call, MyScore is 135
IncreaseScore(Points := 20)
Métodos de extensión
Los métodos de extensión son un tipo de función que actúan como miembros de una clase o tipo existente, pero no requieren la creación de un nuevo tipo o subclase.
A continuación se muestra cómo crear un método de extensión para matrices de tipo int
. El método suma todos los números de la matriz y devuelve el total.
# Sum extension method for type []int
(Arr : []int).Sum<public>() : int =
var Total : int = 0
for (Number : Arr):
set Total = Total + Number
return Total
El método se puede llamar sobre cualquier matriz de tipo int
.
SumTotal := array{4, 3, 7}.Sum()
Print("The SumTotal is { SumTotal }")
# "The SumTotal is 14"
Fallo
A diferencia de otros lenguajes de programación que utilizan los valores booleanos true y false para cambiar el flujo de un programa, Verse utiliza expresiones que pueden tener éxito o fallar. Estas expresiones se denominan expresiones falibles, y solo pueden ejecutarse en un contexto de fallo.
Expresiones falibles
Una expresión falible es una expresión que puede tener éxito y producir un valor, o fallar y no producir ningún valor.
La siguiente lista incluye todas las expresiones falibles en Verse:
Llamadas de función: solo cuando la llamada a la función tiene la forma | |
Comparación: una expresión de comparación compara dos cosas mediante uno de los operadores de comparación. Para obtener más información, consulta operadores. | |
División de números enteros: para los números enteros, el operador de división | |
Decisión: una expresión de decisión utiliza los operadores | |
Consulta: una expresión de consulta utiliza el operador | |
Acceder a un elemento de una matriz: acceder al valor almacenado en una matriz es una expresión falible porque puede no haber un elemento en dicho índice, y por tanto debe utilizarse en un contexto de fallo. Para obtener más información, consulta matriz. |
Contextos de fallo
Un contexto de fallo es un contexto en el que se permite ejecutar expresiones falibles. El contexto define lo que ocurre si la expresión falla. Cualquier error en un contexto de fallo hará que todo el contexto falle.
Un contexto de fallo permite que las expresiones anidadas sean expresiones de fallo, como expresiones o argumentos de función en una expresión block.
La siguiente lista incluye todos los contextos de fallo en Verse:
La condición en expresiones | |
Las expresiones de iteración y de filtro en las expresiones | |
El cuerpo de una función o método que tiene el especificador de efecto | |
El operando para el operador | |
El operando izquierdo del operador | |
Inicialización de una variable que tiene el tipo |
ejecución especulativa
Un aspecto útil de los contextos de fallo en Verse es que son una forma de ejecución especulativa, lo que significa que puedes probar acciones sin confirmar. Cuando una expresión tiene éxito, los efectos de la expresión se confirman, como cambiar el valor de una variable. Si la expresión falla, los efectos de la expresión se revierten, como si la expresión nunca hubiera ocurrido.
De esta manera, puedes ejecutar una serie de acciones que acumulen cambios, pero esas acciones se desharán si fallan en algún lugar del contexto de fallo.
Para que esto funcione, todas las funciones llamadas en el contexto de fallo deben tener el especificador de efecto transacts
.
Especificadores
Los especificadores en Verse describen el comportamiento relacionado con la semántica y pueden añadirse a los identificadores y a ciertas palabras clave. La sintaxis de los especificadores utiliza <
y >
, con la palabra clave en medio, por ejemplo, IsPuzzleSolved()<decides><transacts> : void
.
Las siguientes secciones describen todos los especificadores de Verse y cuándo puedes utilizarlos.
Especificadores de efectos
Los efectos en Verse indican categorías de comportamiento que puede mostrar una función. Puedes añadir especificadores de efectos a:
Al par de paréntesis () después del nombre en una definición de función:
name()<specifier> : type = codeblock
.La palabra clave
class
:name := class<specifier>():
.
Los especificadores de efectos se dividen en dos categorías:
Exclusivos: puedes tener solo uno o ninguno de los especificadores de efecto exclusivo añadidos a una función o a la palabra clave
class
. Si no se añade ningún especificador de efecto exclusivo, el efecto predeterminado esno_rollback
.Aditivos: es posible añadir todos, algunos o ninguno de los especificadores de efecto aditivos a una función o a la palabra clave
class
.
Ejemplo | Efecto |
---|---|
no_rollback: este es el efecto predeterminado cuando no se especifica ningún efecto exclusivo. El efecto | |
Efectos exclusivos | |
transacts: este efecto indica que es posible revertir cualquier acción realizada por la función. El efecto `transacts` es necesario cada vez que se escribe una variable mutable ( | |
varies: este efecto indica que las mismas entradas a la función pueden no producir siempre la misma salida. El efecto | |
computes: este efecto requiere que la función no tenga efectos secundarios, y no se garantiza que se complete. Hay un requisito no comprobado de que la función genere el mismo resultado dados los mismos argumentos. Cualquier función que no tenga el especificador native que, de otro modo, tendría el efecto | |
converges: este efecto garantiza que no solo no hay ningún efecto secundario de la ejecución de la función relacionada, sino que la función se completa en un punto definido (no es infinitamente recursiva). Este efecto solo puede aparecer en funciones que tengan el especificador native, pero el compilador no lo comprueba. El código que se utiliza para proporcionar valores predeterminados de clases o valores para las variables globales debe tener este efecto. Las funciones | |
Efectos aditivos | |
decides: este efecto indica que la función puede fallar y que llamar a esta función es una expresión falible. Las definiciones de funciones con el efecto | |
suspends: indica que la función es asíncrona. Crea un contexto asíncrono para el cuerpo de la función. |
En todos los casos, llamar a una función que tiene un efecto específico requerirá que el autor de la llamada tenga también ese efecto.
Especificadores de acceso
Los especificadores de acceso definen qué puede interactuar con un miembro y cómo. Los especificadores de acceso pueden aplicarse a lo siguiente:
El identificador de un miembro:
name<specifier> : type = value
La palabra clave
var
de un miembro:var<specifier> name : type = value
Especificadores de clase
Los especificadores de clase definen ciertas características de las clases o de sus miembros, por ejemplo, si es posible crear una subclase de una clase.
abstract: cuando una clase o un método de una clase tiene el especificador `abstract`, no puedes crear una instancia de la clase. Las clases abstractas están pensadas para ser utilizadas como una superclase con implementación parcial o como una interfaz común. Esto es útil cuando no tiene sentido tener instancias de una superclase pero tampoco se desea duplicar propiedades y comportamientos en clases similares. | |
concrete: cuando una clase tiene el especificador `concrete`, debe ser posible construirla con un arquetipo vacío, lo que significa que cada campo de la clase debe tener un valor predeterminado. Todas las subclases de una clase `concrete` son implícitamente `concrete`. Una clase `concrete` solo puede heredar directamente de una clase abstracta si ambas clases están definidas en el mismo módulo. | |
unique: el especificador `unique` puede aplicarse a una clase para convertirla en una clase única. Para construir una instancia de una clase única, Verse asigna una identidad única para la instancia resultante. Esto permite comparar instancias de clases únicas para la igualdad mediante la comparación de sus identidades. Las clases sin el especificador `unique` no tienen esta identidad, así que solo se pueden comparar para determinar su igualdad sobre la base de los valores de sus campos. Esto significa que las clases `unique` se pueden comparar con los operadores = y <>, y que son subtipos del tipo comparable. |
Especificadores de implementación
No es posible utilizar los especificadores de implementación al escribir el código, pero los verás al consultar las API de UEFN.
native_callable: indica que un método de instancia es nativo (implementado en C++) y puede llamarse desde otro código C++. Es posible ver este especificador utilizado en un método de instancia. Este especificador no se propaga a subclases y, en consecuencia, no es necesario añadirlo a una definición para anular otro método que sí lo tiene. |
Especificador de localización
Debes utilizar el especificador localizes
para definir un nuevo mensaje. Específicamente, esto sucede cuando la variable tiene el tipo message e inicializas esa variable con un valor string.
# The winning player's name:
PlayerName<localizes> : message = "Player One"
# Build a message announcing the winner.
Announcement<localizes>(WinningPlayerName : string) : message = "...And the winner is: {WinningPlayerName}"
Billboard.SetText(Announcement("Player One"))
No es necesario utilizar el especificador localizes
al inicializar un valor de miembro con un mensaje ya creado, porque el especificador localizes
sirve solo para definir nuevos mensajes.
PlayerOne<localizes> : message = "Player One"
# The winning player's name:
PlayerName : message = PlayerOne
Atributos
Los atributos en Verse describen un comportamiento que se utiliza fuera del lenguaje de Verse (a diferencia de los especificadores, que describen la semántica de Verse). Los atributos pueden añadirse en la línea de código anterior a las definiciones.
La sintaxis de los atributos utiliza @
seguida de la palabra clave.
flujo de control
El flujo de control es el orden en que un ordenador ejecuta instrucciones. Verse tiene diferentes expresiones que puedes utilizar para controlar este flujo dentro del programa.
Block
Como Verse requiere un identificador antes de un bloque de código, las expresiones block
son la forma de anidar bloques de código. Un bloque de código anidado se comporta de forma similar a un bloque de código. Al igual que con los bloques de código, una expresión block
introduce un nuevo cuerpo de ámbito anidado.
bloque |
---|
Resultado: última expresión del bloque de código |
Para obtener más información, consulta Block.
Si
Con la expresión de condición if
, puedes tomar decisiones que cambian el flujo del programa. Al igual que en otros lenguajes de programación, la expresión de condición if
de Verse admite la ejecución condicional, pero en Verse, las condiciones utilizan el éxito y el fracaso para impulsar la decisión.
Si | if... Entonces |
---|---|
Resultado: última expresión del bloque de código | Resultado: última expresión del bloque de código |
if... else | if... else if... else |
---|
En el ejemplo siguiente, el bloque de código de la expresión if
da lugar a false
si IsLightOn?
tiene éxito, o true
, si IsLightOn?
falla. El resultado de logic
se almacena entonces en NewLightState
.
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
false
else:
Light.TurnOn()
true
Un caso útil de la expresión if
en Verse es que puedes probar expresiones falibles. Si fallan, las acciones se revierten como si nunca hubieran ocurrido. Para obtener más información, consulta Ejecución especulativa.
Para obtener más información, consulta If.
Case
Con las expresiones case
, puedes controlar el flujo de un programa a partir de una lista de opciones. La declaración `case` en Verse permite probar un valor frente a múltiples valores posibles (como si usaras =), y ejecutar el código en función de cuál coincida.
funda |
---|
Para obtener más información, consulta Case.
Loop
Con la expresión loop
, las expresiones del bloque loop se repiten en cada iteración del bucle.
bucle |
---|
Resultado: el resultado de una expresión |
En el siguiente ejemplo, una plataforma aparece y desaparece cada ToggleDelay
segundos durante el tiempo que dure el juego.
loop:
Sleep(ToggleDelay) # Sleep(ToggleDelay) waits for ToggleDelay seconds before proceeding to the next instruction.
Platform.Hide()
Sleep(ToggleDelay)
Platform.Show() # The loop restarts immediately, calling Sleep(ToggleDelay) again.
Para obtener más información, consulta Loop.
Cómo detener bucles
Un bloque `loop` se repetirá eternamente, así que para detener el bucle, puedes salir de él con break
o con la expresión return de una función.
Expresiones loop y break | Expresiones loop y return |
---|---|
Resultado: el resultado de una expresión | Resultado: el resultado de una expresión |
Expresiones loop y break anidadas |
---|
Resultado: el resultado de una expresión |
Para obtener más información, consulta Loop y Break.
para
Una expresión for
, en ocasiones denominada bucle for, es lo mismo que una expresión loop, salvo que las expresiones for
utilizan un generador para producir una secuencia de valores de uno en uno y dar un nombre a cada valor.
Por ejemplo, la expresión for(Value : 1..3)
produce la secuencia 1, 2, 3, y cada número de la secuencia recibe el nombre de Value
en cada iteración, de modo que el bucle for
se ejecuta tres veces.
La expresión for
contiene dos partes:
Especificación de la iteración: las expresiones entre los paréntesis. La primera expresión debe ser un generador, pero las otras expresiones pueden ser una inicialización de constante o un filtro.
Cuerpo: las expresiones del bloque de código después de los paréntesis.
para | for con un filtro |
---|---|
Resultado: el resultado de una expresión | Resultado: el resultado de una expresión |
`for` con varios generadores | bloques `for` anidados |
---|---|
Resultado: el resultado de una expresión | Resultado: el resultado de una expresión |
Para obtener más información, consulta For.
defer
Una expresión defer
se ejecuta solo antes de transferir el control del programa fuera del ámbito en el que aparece la expresión defer
, incluida cualquier expresión de resultado, como en un return
. No importa cómo se transfiera el control del programa.
defer | `defer` antes de salir |
---|---|
Resultado: la expresión | Resultado: la expresión |
Una expresión defer
no se ejecutará si se produce una salida anticipada antes de encontrarla.
`defer` con `return` anticipado | `defer` con una tarea simultánea cancelada |
---|---|
Resultado: la expresión | Resultado: la expresión |
Se acumulan varias expresiones defer
que aparecen en el mismo ámbito. El orden en que se ejecutan es el orden inverso en que se encuentran: orden de entrada y salida (FILO). Dado que el último defer
encontrado en un ámbito determinado se ejecuta primero, las expresiones dentro de ese último defer
pueden referirse a un contexto que será limpiado por otras expresiones defer
encontradas anteriormente y ejecutadas después.
Múltiples expresiones `defer` en un bloque de código | Múltiples expresiones `defer`en diferentes bloques de código |
---|---|
Resultado: la expresión | Resultado: la expresión |
Flujo de tiempo y simultaneidad
El control del flujo de tiempo es el núcleo del lenguaje de programación Verse, y se consigue con expresiones simultáneas. Para obtener más información sobre la simultaneidad, consulta Resumen de simultaneidad.
simultaneidad estructurada
Las expresiones de simultaneidad estructurada se utilizan para especificar el flujo de tiempo asíncrono, y para modificar la naturaleza de bloqueo de las expresiones asíncronas con una duración que se limita a un ámbito de contexto asíncrono específico (como el cuerpo de una función asíncrona).
Es similar a los flujos de control estructurados como block
, if
, for
y loop
que se limitan a sus ámbitos asociados.
sync | rama |
---|---|
Ejecuta bloque de código de forma simultánea y espera a que terminen todas antes de ejecutar la siguiente expresión después de | El cuerpo de la expresión |
Resultado: el resultado de una expresión | Resultado: una expresión |
race | rush |
---|---|
Similar a | Similar a |
Resultado: el resultado de una expresión | Resultado: el resultado de una expresión |
simultaneidad no estructurada
Las expresiones de simultaneidad no estructuradas, de las cuales `spawn` es la única, tienen un tiempo de vida no limitado a un ámbito de contexto asíncrono específico, extendiéndose potencialmente más allá del ámbito en el que se ejecutó.
Generar |
---|
El cuerpo de una expresión |
Resultado: una acción |
Tarea
Una tarea es un objeto que se utiliza para representar el estado de una función asíncrona actualmente en ejecución. Los objetos de tarea se utilizan para identificar dónde se suspende una función asíncrona, y los valores de las variables locales en ese punto de suspensión.
# Get task to query / give commands to
# starts and continues independently
Task2 := spawn{Player.MoveTo(Target1)}
Task2.Await() # wait until MoveTo() completed
Módulos y rutas
Un módulo en Verse es una unidad atómica de código que puede redistribuirse y depender de él, y puede evolucionar con el tiempo sin romper las dependencias. Puedes importar un módulo a tu archivo de Verse para utilizar las definiciones de código de otros archivos de Verse.
Uso: para poder utilizar el contenido de un módulo de Verse, debes especificar el módulo por su ruta. | |
Módulo: aparte de los módulos introducidos por carpetas en un proyecto, los módulos pueden introducirse dentro de un archivo .verse utilizando la palabra clave |
Para obtener más información, consulta Módulos y rutas.