Use este documento como uma referência rápida para todas as funcionalidades da linguagem de programação Verse e sua sintaxe. Siga os links para saber mais.
Expressões
Uma expressão é a menor unidade de código que produz um valor quando avaliada. Um exemplo é uma expressão if ... else, que em Verse é avaliada como um valor que depende do conteúdo dos blocos de expressão.
O código a seguir é avaliado como um valor de string que contém "Big!" ou "Small!", dependendo se MyNumber era maior que 5:
if (MyNumber > 5):
"Big!"
else:
"Small!"
Comentários de código
Um comentário de código explica algo sobre o código ou o raciocínio que o programador usou durante a programação. Quando o programa é executado, os comentários de código são ignorados.
Verse | Comentário de linha única: tudo o que aparece entre |
Verse | Comentário de bloco na linha: tudo o que aparece entre |
Verse | Comentário de bloco multilinha: tudo o que aparece entre |
Verse | Comentário de bloco aninhado: tudo o que aparece entre |
Verse | Comentário recuado: tudo o que aparece em linhas novas após |
Constantes e variáveis
Constantes e variáveis podem armazenar informações, ou valores, que o seu programa usa e associar esses valores a um nome. O nome é o identificador.
Para criar sua própria variável ou constante, você precisa informar ao Verse sobre elas. Isso é chamado de declaração. Especificar um valor inicial, chamado inicialização, é opcional para variáveis (embora recomendado), mas obrigatório para constantes.
Você pode alterar o valor de uma variável a qualquer momento. Isso é formalmente chamado de atribuição, porque você está atribuindo um valor à variável, mas às vezes também é chamado de configuração da variável.
Verse | Como criar uma constante: o valor de uma constante não pode ser alterado durante a execução do programa. Você cria uma constante especificando seu nome, tipo e valor. | Clique na imagem para ampliar. |
Verse | Como criar uma variável: o valor de uma variável pode ser alterado durante a execução do programa. Você cria uma variável adicionando a palavra-chave | Clique na imagem para ampliar. |
Verse | Como alterar o valor de uma variável: você pode alterar o valor de uma variável enquanto o programa está em execução usando a palavra-chave | Clique na imagem para ampliar. |
Tipos
Verse é uma linguagem de programação de tipo estático, o que significa que um tipo é atribuído a cada identificador.
Há instâncias em que o tipo não é explicitamente necessário, como ao criar uma constante em uma função. No exemplo MyConstant := 0, o tipo para MyConstant é inferido porque o valor 0 é atribuído a ele.
Tipos comuns
O Verse possui tipos integrados que oferecem suporte às operações fundamentais que a maioria dos programas precisa realizar. Você pode criar seus próprios tipos combinando-os em estruturas maiores, mas é importante entender os tipos comuns como a base para o uso de variáveis e constantes no Verse.
Verse | logic: um tipo |
Verse | int: um
|
Verse | float: um
|
Verse | string: uma Você pode injetar o resultado de uma expressão em uma string usando {} dentro de "". Isso é chamado de interpolação de strings. Para saber mais sobre o tipo |
Verse | message: um tipo Você pode transformar o texto em uma string que pode ser exibida no tempo de execução usando a função Atualmente, o único texto que pode ser retornado pela função |
Verse | locale: este tipo representa em qual contexto um valor Atualmente, o tipo |
Verse | rational: o tipo
|
Verse | void: o tipo
Para saber mais sobre o tipo |
Verse | any: o tipo |
Verse | comparable: o tipo |
Para saber mais sobre tipos comuns em Verse, consulte Tipos comuns.
Tipos contêiner
Você pode armazenar vários valores juntos usando um tipo container. O Verse tem uma quantidade de tipos contêiner para armazenar os valores. Para saber mais sobre os tipos container em Verse, consulte Tipos container.
Option
O tipo option pode conter um valor ou pode estar vazio.
No exemplo a seguir, MaybeANumber é um inteiro opcional ?int que não contém valor. Um novo valor para MaybeANumber é então definido como 42.
var MaybeANumber : ?int = false # unset optional value
set MaybeANumber := option{42} # assigned the value 42
Clique na imagem para ampliar.
Verse | Como criar uma opção: você pode inicializar uma opção com uma das seguintes opções:
Especifique o tipo ao adicionar |
Verse | Como acessar um elemento em uma opção: use o operador de consulta |
Veja a seguir um exemplo de uso de um tipo de opção para salvar uma referência a um jogador gerado e, quando um jogador é gerado, para que o dispositivo de acionamento reaja:
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 =
Intervalo
A expressão range, contém todos os números em um intervalo especificado e só pode ser usada em expressões específicas, como a expressão for. Os valores de intervalo podem ser apenas números inteiros.
Clique na imagem para ampliar.
Verse | Como criar um intervalo: o início do intervalo é o primeiro valor na expressão, e o final do intervalo é o valor após |
Verse | Como iterar em um intervalo: você pode usar a expressão |
Consulte mais informações em Range.
Matriz
Uma matriz é um contêiner em que você pode armazenar elementos do mesmo tipo e acessar o elemento conforme a posição dele, chamada de índice, na matriz. O primeiro índice na matriz é 0 e o último índice é um a menos que o número de elementos na matriz.
Players : []player = array{Player1, Player2}
Clique na imagem para ampliar.
Verse | Como criar uma matriz: use a palavra-chave |
Verse | Como acessar um elemento em uma matriz: use |
Verse | Como alterar um elemento na matriz: você pode alterar o valor armazenado em uma variável de matriz em um índice usando a palavra-chave |
Verse | Como iterar sobre uma matriz: você pode acessar cada elemento em uma matriz, em ordem, do primeiro ao último, usando a expressão for. Com a expressão |
Verse | Como obter o número de elementos em uma matriz: use |
Verse | Como concatenar matrizes: você pode mesclar matrizes usando o operador |
Consulte mais informações em Matriz.
tupla
A tupla é um agrupamento de duas ou mais expressões tratadas como uma única expressão. A ordem dos elementos na expressão é importante. A mesma expressão pode estar em vários locais em uma tupla. As expressões de tupla podem ser de qualquer tipo e podem ter tipos mistos (ao contrário de matrizes que só podem ter elementos de um tipo).
Tuplas são especialmente úteis quando:
Retornando vários valores de uma função.
Passando vários valores para uma função.
O agrupamento no local é mais conciso do que a sobrecarga de criar uma estrutura de dados reutilizável (como uma estrutura ou uma classe).
VerseExampleTuple : tuple(int, float, string) = (1, 2.0, "three")
Clique na imagem para ampliar.
Verse | Como criar uma tupla: use |
Como acessar um elemento na tupla: use | |
Expansão de tupla: quando você usa uma tupla como argumento para uma função em vez de especificar cada argumento individual, a função será chamada com cada elemento da tupla usado como argumento na ordem especificada nela. | |
Coerção de matriz de tupla: tuplas podem ser transmitidas sempre que uma matriz é esperada, desde que os tipos dos elementos da tupla sejam todos do mesmo tipo que a matriz. As matrizes não podem ser passadas onde uma tupla é esperada. |
Consulte mais informações em Tupla.
Mapa
Um mapa é um contêiner onde você pode armazenar valores associados a outro valor, chamados de pares chave-valor, e acessar os elementos por suas chaves exclusivas.
WordCount : [string]int = map {"apple" => 11, "pear" => 7}
Clique na imagem para ampliar.
Como criar um mapa: use a palavra-chave | |
Como acessar um elemento em um mapa: use | |
Como iterar sobre um mapa: você pode acessar cada elemento em um mapa, em ordem, do primeiro ao último elemento inserido, usando a expressão for. Com a expressão | |
Como obter o número de pares chave-valor no mapa: use |
Consulte mais informações em Mapa.
Tipos compostos
Um tipo composto é qualquer tipo que pode ser composto de campos e elementos (geralmente nomeados) da primitiva ou de outros tipos. Tipos compostos geralmente têm um número fixo de campos ou elementos durante sua vida útil. Consulte mais informações em Tipos compostos.
Enum
Enum é a abreviação de enumeração, que significa nomear ou listar uma série de coisas, chamadas de enumeradores. Este é um tipo no Verse que pode ser usado para coisas como dias da semana ou direções da bússola.
Clique na imagem para ampliar.
Como criar enumeração: use a palavra-chave | |
Como acessar um enumerador: use |
Struct
Struct é a abreviação de structure (estrutura, em inglês) e é uma maneira de agrupar várias variáveis relacionadas. As variáveis podem ser de qualquer tipo.
Clique na imagem para ampliar.
Clique na imagem para ampliar.
Como criar uma estrutura: use a palavra-chave | |
Como instanciar uma estrutura: você pode construir uma instância de uma estrutura a partir de um arquétipo. Um arquétipo define os valores dos campos de uma estrutura. | |
Como acessar campos em uma estrutura: você pode acessar os campos de uma estrutura para obter seu valor adicionando |
Classe
Uma classe é um modelo para criar objetos com comportamentos e propriedades semelhantes (variáveis e métodos) e deve ser instanciada para criar um objeto com valores reais. As classes são hierárquicas, o que significa que uma classe pode herdar informações de seu pai (superclasse) e compartilhar suas informações com seus filhos (subclasses). Uma classe pode ser um tipo personalizado definido pelo usuário.
Clique na imagem para ampliar.
Clique na imagem para ampliar.
Como criar uma classe: as definições aninhadas dentro de uma classe definem os campos dela. As funções definidas como campos de uma classe também são chamadas de métodos. | |
Como instanciar uma classe: você pode construir uma instância da classe usando o nome da classe seguido de | |
Como acessar os campos de uma classe: você pode acessar os campos de uma classe para obter seu valor adicionando | |
Como acessar os métodos em uma classe: você pode acessar os métodos de uma classe para chamá-la adicionando | |
Self: você pode usar |
Também existem especificadores que são exclusivos das classes e que alteram seu comportamento. Consulte mais detalhes em Especificadores de classe.
Para saber mais sobre a classe, consulte Classe.
Subclasse
Uma subclasse é uma classe que estende a definição de outra classe adicionando ou modificando o campo e o método da outra classe (chamada de superclasse).
Clique na imagem para ampliar.
Como criar uma subclasse: especifique uma classe como uma subclasse de outra classe adicionando a outra classe entre | |
Como substituir campos na superclasse: você pode alterar os valores de um campo definido na superclasse para a subclasse simplesmente adicionando o especificador | |
Como substituir métodos na subclasse: [INCLUDE:#overriding_methods_on_superclass_description] | |
Super: de forma semelhante a |
Consulte mais informações em Subclasse.
Constructor
Um construtor é uma função especial que cria uma instância da classe à qual ele está associado. Ele pode ser usado para definir valores iniciais para o novo objeto.
Como definir um construtor para uma classe: você pode adicionar um construtor para uma classe adicionando o especificador | |
Como adicionar variáveis e executar código no construtor: você pode executar expressões dentro de um construtor com a expressão block e introduzir novas variáveis com a palavra-chave | |
Como chamar outros construtores em um construtor: você pode chamar outros construtores a partir de um construtor. Também é possível chamar construtores para a superclasse da classe a partir de um construtor da classe, desde que todos os campos sejam inicializados. Quando um construtor chama outro construtor e ambos inicializam campos, apenas os valores fornecidos ao primeiro construtor são usados para esses campos. A ordem de avaliação para expressões entre os dois construtores será a ordem em que as expressões são escritas (no que diz respeito aos efeitos colaterais), mas apenas os valores fornecidos ao primeiro construtor são usados. |
Interface
O tipo interface fornece um contrato de como interagir com qualquer classe que implementa essa interface. Uma interface não pode ser instanciada, mas uma classe pode herdar dela e implementar seus métodos. Uma interface é semelhante a uma classe abstrata, com a exceção de que não permite implementação parcial ou campos como parte da definição.
Clique na imagem para ampliar.
Clique na imagem para ampliar.
Como criar uma interface: você define uma interface de forma semelhante a uma classe. A diferença é que essa chamada é feita usando a palavra-chave | |
Como estender uma interface: uma interface pode estender a definição de outra interface especificando a interface a ser estendida entre | |
Como implementar uma interface: você pode implementar uma interface com uma classe especificando essa interface entre | |
Como implementar várias interfaces: uma classe pode implementar várias interfaces. As interfaces são separadas por | |
Como herdar de uma interface e outra classe: uma classe pode implementar uma interface e ser uma subclasse de outra classe. A interface e a superclasse são separadas por |
Consulte mais informações em Interface.
Como trabalhar com tipos
O Verse fornece algumas maneiras de facilitar o trabalho com tipos.
Alias de tipo: você pode dar um nome diferente para um tipo no seu código e fazer referência a esse botão por esse novo nome. A sintaxe é semelhante à inicialização constante. Você também pode fornecer um alias de tipo para um tipo de função. Para obter mais informações, consulte Alias de tipo. | |
Tipo paramétrico como argumento de número explícito: a linguagem Verse oferece suporte a tipos paramétricos (tipos esperados como argumento). Isso apenas funciona com classes e funções. Consulte mais informações em Tipos paramétricos. | |
Tipo paramétrico como argumentos de tipo implícito para funções: o motivo de usar tipos paramétricos implícitos com funções é que eles permitem que você escreva código que é invariável de um tipo específico uma vez, em vez de para cada índice com o qual a função é usada. Consulte mais informações em Tipos paramétricos. | |
Macro de tipo: a linguagem Verse tem uma construção especial que pode ser usada para obter o tipo de uma expressão arbitrária. Ela pode ser usada em qualquer lugar em que um tipo possa ser usado. Consulte mais informações em Macro de tipo. | |
Subtipo: você pode usar |
Consulte mais informações em Como trabalhar com tipos em Verse.
Operadores
Operadores são funções especiais definidas na linguagem de programação Verse que executam ações, como operações matemáticas, em seus operandos.
Quando vários operadores são usados na mesma expressão, eles são avaliados na ordem de precedência da mais alta para a mais baixa. Se houver operadores com a mesma precedência na mesma expressão, eles serão avaliados da esquerda para a direita.
A tabela abaixo lista todos os operadores integrados no Verse, na ordem da precedência mais alta para a mais baixa.
| Operador | Descrição | Formato do operador | Precedência de operadores | Exemplo |
|---|---|---|---|---|
Consulta | O operador | pós-fixação | 9 |
|
Não | O operador | Prefixo | 8 |
|
Positivo | Você pode usar o operador | Prefixo | 8 |
|
Negativo | Você pode usar o operador | Prefixo | 8 |
|
Multiplicação | O | infixo | 7 |
|
Divisão | O operador | infixo | 7 |
|
Adição | O operador "+" soma dois valores numéricos. Quando usado com strings e matrizes, os dois valores são concatenados. Consulte mais detalhes em Matemática. | infixo | 6 |
|
Subtração | O operador | infixo | 6 |
|
Atribuição de adição | Com este operador, você pode combinar adição e atribuição na mesma operação para atualizar o valor de uma variável. Consulte mais detalhes em Matemática. | infixo | 5 |
|
Atribuição de subtração | Com esse operador, você pode combinar subtração e atribuição na mesma operação para atualizar o valor de uma variável. Consulte mais detalhes em Matemática. | infixo | 5 |
|
Atribuição de multiplicação | Com este operador, você pode combinar multiplicação e atribuição na mesma operação para atualizar o valor de uma variável. Consulte mais detalhes em Matemática. | infixo | 5 |
|
Atribuição de divisão | Com este operador, você pode combinar divisão e atribuição na mesma operação para atualizar o valor de uma variável, a menos que a variável seja um inteiro. Consulte mais detalhes em Matemática. | infixo | 5 |
|
Igual a | O operador | infixo | 4 |
|
Diferente de | O operador | infixo | 4 |
|
Menor que | O operador | infixo | 4 |
|
Menor ou igual a | O operador | infixo | 4 |
|
Maior que | O operador | infixo | 4 |
|
Maior ou igual a | O operador | infixo | 4 |
|
E | O operador | infixo | 3 |
|
Ou | O operador | infixo | 2 |
|
Inicialização de variável e constante | Com este operador, você pode armazenar valores em uma constante ou variável. Consulte mais detalhes em Constantes e variáveis. | infixo | 1 |
|
Atribuição de variável | Com este operador, você pode atualizar os valores armazenados em uma variável. Consulte mais detalhes em Constantes e variáveis. | infixo | 1 |
|
Para saber mais, consulte Operadores.
Agrupamento
Você pode alterar a ordem na qual os operadores são avaliados agrupando expressões com (). Por exemplo, (1+2)*3 e 1+(2*3) não são avaliados com o mesmo valor porque as expressões agrupadas entre () seriam avaliadas primeiro.
O exemplo a seguir mostra como usar o agrupamento para calcular uma explosão no jogo que expande os danos com base na distância do jogador, mas na qual a armadura do jogador pode reduzir os danos totais:
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)
Consulte mais detalhes em Agrupamento.
Blocos de código
Um bloco de código é um grupo de zero ou mais expressões e apresenta um novo corpo com escopo. Um bloco de código deve seguir um identificador. Pode ser um identificador de função ou um identificador de fluxo de controle como if e for, por exemplo.
Espaçado: começa com | |
Várias linhas entre chaves: delimitação por | |
Uma linha com chaves: delimitação por |
Também é possível usar ; para incluir mais de uma expressão em uma linha. Em um formato que tem cada expressão em uma nova linha, os caracteres {} não precisam estar em suas próprias linhas.
A última expressão em um bloco de código é o resultado desse bloco de código. No exemplo a seguir, o bloco de código da expressão if resulta em false, se IsLightOn? for bem-sucedida, ou true, se IsLightOn? falhar. O resultado de logic é então armazenado em NewLightState.
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
false
else:
Light.TurnOn()
true
Consulte mais informações em Blocos de código.
Escopo
Escopo se refere ao código dentro de um programa em Verse em que a associação de um identificador (nome) a um valor é válida e em que ele pode ser usado para fazer referência ao valor.
Por exemplo, as constantes ou variáveis criadas em um bloco de código existem apenas no contexto desse bloco de código. Isso significa que o tempo de vida dos objetos é limitado ao escopo em que é criado e não pode ser usado fora desse bloco de código.
O exemplo a seguir mostra como calcular a quantidade máxima de flechas que podem ser compradas com o número de moedas que o jogador possui. A constante MaxArrowsYouCanBuy é criada no bloco if, por isso, seu escopo é limitado ao bloco if. Quando a constante MaxArrowsYouCanBuy é usada na string exibida para o usuário, um erro é gerado porque o nome MaxArrowsYouCanBuy não existe no escopo fora da expressão 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
Clique na imagem para ampliar.
A linguagem Verse não é compatível com a reutilização de um identificador mesmo que seja declarado em um escopo diferente, a menos que você qualifique o identificador adicionando (qualifying_scope:) antes do identificador, no qual qualifying_scope é o nome do módulo de um identificador, classe ou interface. Ao definir ou usar o identificador, você também deve adicionar o qualificador a esse identificador.
Funções
Uma função é uma sequência nomeada de expressões que você pode reutilizar. Ela fornece instruções para executar uma ação ou criar uma saída com base na entrada.
Definições de função
Para definir sua própria função, você deve fornecer três partes principais: um nome exclusivo (identificador), o tipo de informação que você pode esperar como resultado e o que a função fará quando for chamada. A seguir está a sintaxe básica de uma função:
name() : type =
codeblock
nome() e tipo separados por dois pontos: esta é a assinatura da função, que é como você deve chamar e usar a função, e o valor que deve ser retornado pela função é do tipo fornecido. Esse formato é semelhante a como você cria constantes, exceto pelo () após o nome, que imita como você chama a função em seu código.
O bloco de código da função: você define o que a função executará quando for chamada, fornecendo
=codeblock, em quecodeblocké qualquer sequência de uma ou mais expressões. Sempre que você chama a função, as expressões no bloco de código são executadas.
Clique na imagem para ampliar.
Resultados da função
Quando sua função tem um tipo de retorno especificado, o corpo da função deve produzir um resultado desse tipo ou o código não será compilado.
A última expressão retornada com um valor: por padrão, a última expressão no bloco de código da função é o resultado dessa função, e seu valor deve corresponder ao tipo de retorno da função. | |
Retorno explícito com um valor: você também pode definir explicitamente o que a função retornará usando |
Ao criar uma função que não precisa gerar um resultado, você pode definir o tipo de retorno dessa função como void, o que significa que não se espera que essa função gere um resultado útil e, portanto, a última expressão no bloco de código da função pode ser de qualquer tipo.
Você pode sair de uma função cujo tipo de retorno é void usando a expressão return sozinha. Essa expressão sai da função imediatamente, mesmo que haja mais expressões depois dela no bloco de código.
Parâmetros de função
O valor de entrada de uma função é definido usando parâmetros. Um parâmetro é uma constante declarada na assinatura da função entre parênteses que você pode usar no corpo da função.
Veja a seguir está a sintaxe de uma função com dois parâmetros:
name(parameter1name : type, parameter2name : type) : type =
codeblock
Clique na imagem para ampliar.
No exemplo a seguir, a função IncreaseScore() tem um parâmetro inteiro chamado Points, que a função utiliza para aumentar o valor de MyScore:
var MyScore : int = 100
IncreaseScore(Points : int) : void =
# Increase MyScore by Points.
set MyScore = MyScore + Points
Chamadas de Função
Quando quiser usar a sequência nomeada de expressões (a função) no seu código, chame a função pelo nome, como GetRandomInt(1, 10), que retornará um número inteiro aleatório entre 1 e 10, inclusive.
Existem duas maneiras de chamar uma função, dependendo de a chamada de função ser ou não falível:
Chamada de função não falível: uma chamada de função que não pode falhar segue o formato | |
Chamada de função falível: uma chamada de função falível tem o formato |
Argumentos de função
Quando você chama uma função que espera parâmetros, deve atribuir valores aos parâmetros, assim como precisa atribuir valores a constantes para poder usá-los. Os valores atribuídos são chamados de argumentos de função.
Veja a seguir está a sintaxe para chamar uma função com dois argumentos:
name(parameter1name := value, parameter2name := value)
No exemplo a seguir, a função IncreaseScore() é chamada três vezes, cada vez com argumentos diferentes (10, 5 e 20), para aumentar o 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 extensão
Métodos de extensão são um tipo de função que age como membros de uma classe ou tipo existente, mas não requer a criação de um novo tipo ou subclasse.
O código a seguir mostra como criar um método de extensão para matrizes do tipo int. O método soma todos os números na matriz e retorna o total.
# Sum extension method for type []int
(Arr : []int).Sum<public>() : int =
var Total : int = 0
for (Number : Arr):
set Total = Total + Number
return TotalO método pode então ser chamado em qualquer matriz do tipo int.
SumTotal := array{4, 3, 7}.Sum()
Print("The SumTotal is { SumTotal }")
# "The SumTotal is 14"
Falha
Ao contrário de outras linguagens de programação que usam os valores booleanos true e false para alterar o fluxo de um programa, o Verse usa expressões que podem ser bem-sucedidas ou falhar. Essas expressões são chamadas de expressões falíveis e só podem ser executadas em um contexto de falha.
Expressões falíveis
Uma expressão falível é uma expressão que pode ser bem-sucedida e produzir um valor ou falhar e não produzir nenhum valor.
A lista a seguir inclui todas as expressões passíveis de falha no Verse:
Chamadas de função: somente quando a chamada de função tiver o formato | |
Comparação: uma expressão de comparação compara dois elementos usando um dos operadores de comparação. Consulte mais informações em Operadores. | |
Divisão por inteiro: para números inteiros, o operador de divisão | |
Decisão: uma expressão de decisão usa o operador | |
Consulta: uma expressão de consulta usa o operador | |
Como acessar um elemento em uma matriz: acessar o valor armazenado em uma matriz é uma expressão falível porque pode não haver um elemento nesse índice, por isso ele deve ser usado em um contexto de falha. Consulte mais detalhes em Matriz. |
Contextos de falha
Um contexto de falha é um contexto em que é permitido executar expressões falíveis. O contexto define o que acontece se a expressão falhar. Qualquer falha dentro de um contexto de falha fará com que todo o contexto falhe.
Um contexto de falha permite que expressões aninhadas sejam expressões de falha, como argumentos de função ou expressões em uma expressão block.
A lista a seguir inclui todos os contextos de falha no Verse:
A condição em expressões | |
As expressões de iteração e as expressões de filtro em expressões | |
O corpo de uma função que tem o especificador de efeito | |
O operando para o operador | |
O operando esquerdo para o operador | |
Inicializando uma variável que tem o tipo |
execução especulativa
Um aspecto útil dos contextos de falha em Verse é que eles são uma forma de execução especulativa, ou seja, você pode testar ações sem confirmá-las. Quando uma expressão é bem-sucedida, os efeitos dela são confirmados, como a alteração no valor de uma variável. Se a expressão falhar, os efeitos da expressão serão revertidos, como se a expressão nunca tivesse acontecido.
Dessa forma, você pode executar uma série de ações que acumulam alterações, mas essas ações serão desfeitas se falharem em qualquer lugar no contexto de falha.
Para que isso funcione, todas as funções chamadas no contexto de falha devem ter o especificador de efeitos transacts.
Especificadores
Especificadores no Verse descrevem o comportamento relacionado à semântica e podem ser adicionados a identificadores e determinadas palavras-chave. A sintaxe do especificador usa < e >, com a palavra-chave no meio, como IsPuzzleSolved()<decides><transacts> : void.
As seções a seguir descrevem todos os especificadores no Verse e quando eles podem ser usados.
Especificadores de efeito
Os efeitos no Verse indicam categorias de comportamento que uma função pode exibir. Você pode adicionar especificadores de efeito:
Os () após o nome em uma definição de função:
name()<specifier> : type = codeblock.A palavra-chave
class:name := class<specifier>():.
Especificadores de efeito estão divididos em duas categorias:
Exclusivo: você pode ter apenas um ou nenhum dos especificador de efeitos exclusivos adicionado a uma função ou à palavra-chave
class. Se nenhum especificador de efeito exclusivo for adicionado, o efeito padrão seráno_rollback.Aditivo: você pode adicionar todos, alguns ou nenhum dos especificadores de efeito aditivos a uma função ou à palavra-chave
class.
| Exemplo | Efeito |
|---|---|
no_rollback: esse é o efeito padrão quando nenhum efeito exclusivo é especificado. O efeito | |
Efeitos exclusivos | |
transacts: esse efeito indica que qualquer ação executada pela função pode ser revertida. O efeito "transacts" é necessário sempre que uma variável mutável ( | |
varies: esse efeito indica que a mesma entrada para a função pode nem sempre produzir a mesma saída. O efeito | |
computes: esse efeito requer que a função não tenha efeito colateral e não há garantias de que ela será concluída. Há um requisito não verificado de que a função, quando fornecida com os mesmos argumentos, produza o mesmo resultado. Qualquer função que não tenha o especificador native que, de outra forma, teria o efeito | |
converges: além de garantir que não haverá efeito colateral da execução da função relacionada, esse efeito também assegura que a função será concluída de forma definitiva (não recursiva infinitamente). Esse efeito só pode aparecer em funções que tenham o especificador native, mas isso não é verificado pelo compilador. O código usado para fornecer valores padrão de classe ou valores para variáveis globais devem ter esse efeito. As funções | |
Efeitos aditivos | |
decides: indica que a função pode falhar e que chamar essa função é uma expressão falível. As definições de função com o efeito | |
suspends: indica que a função é assíncrona. Cria um contexto assíncrono para o corpo da função. |
Em todos os casos, chamar uma função que tenha um efeito específico exigirá que o chamador também tenha esse efeito.
Especificadores de acesso
Especificadores de acesso definem o que pode interagir com um membro e como. Eles podem ser aplicados ao seguinte:
O identificador de um membro:
name<specifier> : type = valueA palavra-chave
varde um membro:var<specifier> name : type = value
Especificadores de classe
Especificadores de classe definem certas características de classes ou seus membros, por exemplo, se você pode criar uma subclasse de uma classe.
abstract: quando uma classe ou um método de classe tem o especificador "abstract", você não pode criar uma instância dessa classe. Classes abstratas devem ser usadas como uma superclasse com implementação parcial ou como uma interface comum. Isso é útil quando não faz sentido ter instâncias de uma superclasse, mas você não deseja duplicar propriedades e comportamentos em classes semelhantes. | |
concrete: quando uma classe tem o especificador "concrete", deve ser possível construí-la com um arquétipo vazio, ou seja, cada campo da classe deve ter um valor padrão. Cada subclasse de uma classe concreta é implicitamente concreta. Uma classe concrete só poderá herdar diretamente de uma classe abstract se ambas as classes estiverem definidas no mesmo módulo. | |
unique: o especificador "unique" pode ser aplicado a uma classe para torná-la uma classe única. Para construir uma instância de uma classe única, o Verse aloca uma identidade exclusiva para a instância resultante. Assim, é possível comparar instâncias de classes exclusivas em termos de igualdade ao comparar suas identidades. Classes sem o especificador unique não têm essa identidade, por isso só podem ser comparadas em termos de igualdade baseadas nos valores dos seus campos. Isso significa que classes exclusivas podem ser comparadas com os operadores = e <> e são subtipos do tipo comparável. |
Especificadores de implementação
Não é possível usar especificadores de implementação ao escrever código, mas você os verá ao observar as APIs do UEFN.
native_callable: indica que uma instância do método é nativa (implementada em C++) e pode ser chamada por outro código C++. É possível ver esse especificador usado em um método de instância. Esse especificador não se propaga para subclasses, por isso, não é necessário adicioná-lo a uma definição ao substituir um método que tenha esse especificador. |
Especificador de localização
Você deve usar o especificador localizes quando estiver definindo uma nova mensagem. Especificamente, é quando a variável tem o tipo message e você está inicializando a variável com um valor de 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"))Você não precisa usar o especificador localizes ao inicializar um valor de membro com uma mensagem já criada porque o especificador localizes serve apenas para definir novas mensagens.
PlayerOne<localizes> : message = "Player One"
# The winning player's name:
PlayerName : message = PlayerOneAtributos
Atributos em Verse descrevem o comportamento que é usado fora da linguagem Verse (ao contrário dos especificadores, que descrevem a semântica em Verse). Atributos podem ser adicionados na linha de código antes de definições.
A sintaxe do atributo usa @ seguido da palavra-chave.
Fluxo de controle
O fluxo de controle é a ordem na qual um computador executa instruções. O Verse tem diferentes expressões que você pode usar para controlar o fluxo do seu programa.
Bloco
Como Verse requer um identificador antes de um bloco de código, as expressões block são como você aninha blocos de código. Um bloco de código aninhado se comporta de maneira semelhante a um bloco de código. Assim como os blocos de código, a expressão block introduz um novo corpo de escopo aninhado.
| bloco |
|---|
Resultado: a última expressão no bloco de código de |
Consulte mais informações em Bloco.
If
Com a expressão if, você pode tomar decisões que alteram o fluxo do programa. Como em outras linguagens de programação, a expressão if em Verse é compatível com a execução condicional. Mas, em Verse, as condições utilizam sucesso e falha para orientar as decisões.
| Se | if ... Então |
|---|---|
Resultado: a última expressão no bloco de código | Resultado: a última expressão no bloco de código |
| if ... else | if ... else if ... else |
|---|
No exemplo a seguir, o bloco de código da expressão if resulta em false, se IsLightOn? for bem-sucedida, ou true, se IsLightOn? falhar. O resultado de logic é então armazenado em NewLightState.
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
false
else:
Light.TurnOn()
true
Um caso útil para a expressão if em Verse é que você pode testar expressões falíveis e, se elas falharem, as ações serão revertidas como se nunca tivessem acontecido. Consulte mais detalhes sobre essa funcionalidade em Execução especulativa.
Consulte mais informações em If.
Caso
Com expressões case, é possível controlar o fluxo de um programa a partir de uma lista de opções. A instrução case no Verse permite testar um valor em relação a vários valores possíveis (como se você estivesse usando =) e executar o código com base em qual deles for correspondido.
| estojo |
|---|
Consulte mais informações em Caso.
"Loop"
Com a expressão loop, as expressões no bloco de loop são repetidas para cada iteração dele.
| loop |
|---|
Resultado: o resultado de uma expressão |
No exemplo a seguir, uma plataforma aparece e desaparece a cada ToggleDelay segundos durante a execução do jogo.
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.
Consulte mais informações em loop.
Como interromper loops
Um bloco de loop será repetido indefinidamente. Portanto, para interrompê-lo, você pode sair dele com break ou com a expressão return da função.
| Loop e break | loop e return |
|---|---|
Resultado: o resultado de uma expressão | Resultado: o resultado de uma expressão |
| loop aninhado e break |
|---|
Resultado: o resultado de uma expressão |
Consulte mais informações em Loop e break.
For
Uma expressão for, às vezes chamada de for loop, é igual a uma expressão loop, exceto que as expressões for usam um gerador para produzir uma sequência de valores, um de cada vez, e dão um nome para cada valor.
Por exemplo, a expressão for(Value : 1..3) produz a sequência 1, 2, 3, e cada número na sequência recebe o nome Value para cada iteração, então o for loop é executado três vezes.
A expressão for contém duas partes:
Especificação de iteração: as expressões entre parênteses. A primeira expressão deve ser um gerador, mas as outras podem ser uma inicialização constante ou um filtro.
Corpo: as expressões no bloco de código após os parênteses.
| para | for com um filtro |
|---|---|
Resultado: o resultado de uma expressão | Resultado: o resultado de uma expressão |
| for com vários geradores | aninhado para blocos |
|---|---|
Resultado: o resultado de uma expressão | Resultado: o resultado de uma expressão |
Consulte mais informações em For.
defer
Uma expressão defer é executada antes de transferir o controle do programa fora do escopo em que a expressão defer aparece, incluindo qualquer expressão de resultado, como em return. Não importa como o controle do programa é transferido.
| defer | defer antes de uma saída |
|---|---|
Resultado: a expressão | Resultado: a expressão |
Uma expressão defer não será executada se uma saída antecipada ocorrer antes que o adiamento seja encontrado.
| defer com retorno antecipado | defer com uma tarefa simultânea cancelada |
|---|---|
Resultado: a expressão | Resultado: a expressão |
Várias expressões defer que aparecerem no mesmo escopo se acumulam. A ordem em que são executadas é a ordem inversa em que são encontradas: a primeira a entrar e a última a sair. Como a última defer encontrada em um determinado escopo é executada primeiro, as expressões dentro dessa última defer encontrada podem se referir ao contexto que será removido por outras expressões defer encontradas antes e executadas depois.
| Várias expressões defer em um bloco de código | Várias expressões defer em diferentes blocos de código |
|---|---|
Resultado: a expressão | Resultado: a expressão |
Fluxo de tempo e simultaneidade
O controle de fluxo de tempo está no centro da linguagem de programação Verse e é feito com expressões simultâneas. Para saber mais sobre o que é simultaneidade, consulte Visão geral sobre simultaneidade.
simultaneidade estruturada
Expressões de simultaneidade estruturada são usadas para especificar o fluxo de tempo assíncrono e para modificar a natureza de bloqueio de expressões assíncronas com uma duração restrita a um escopo de contexto assíncrono específico (como um corpo de função assíncrona).
Isso é semelhante ao fluxo de controle estruturado, como block, if, for e loop que restringem seus respectivos escopos.
| sync | galho |
|---|---|
Executa todas as expressões em seu bloco de código simultaneamente e espera que todas terminem antes de executar a próxima expressão após a expressão | O corpo da expressão |
Resultado: o resultado de uma expressão | Resultado: uma expressão |
| race | rush |
|---|---|
Semelhante a | Semelhante a |
Resultado: o resultado de uma expressão | Resultado: o resultado de uma expressão |
simultaneidade não estruturada
Expressões de simultaneidade não estruturadas — das quais “spawn” é a única — têm um tempo de vida não restrito a um escopo de contexto assíncrono específico — estendendo-se potencialmente além do escopo em que foram executadas.
| Surgimento |
|---|
O corpo de |
Resultado: uma |
Tarefa
Uma tarefa é um objeto usado para representar o estado de uma função assíncrona atualmente em execução. Objetos de tarefas são usados para identificar onde uma função assíncrona é suspensa e os valores das variáveis locais nesse ponto de suspensão.
# Get task to query / give commands to
# starts and continues independently
Task2 := spawn{Player.MoveTo(Target1)}
Task2.Await() # wait until MoveTo() completed
Módulos e caminhos
Um módulo do Verse é uma unidade atômica de código que pode ser redistribuída e dependente, e pode evoluir ao longo do tempo sem quebrar dependências. Você pode importar um módulo para seu arquivo Verse para usar definições de código de outros arquivos Verse.
using: para poder usar o conteúdo de um módulo Verse, você deve especificar o módulo por seu caminho. | |
module: fora dos módulos introduzidos por pastas em um projeto, os módulos podem ser introduzidos em um arquivo .verse usando a palavra-chave |
Consulte mais informações em Módulos e caminhos.