Utilizza questo documento come riferimento rapido per tutte le funzionalità del linguaggio di programmazione Verse e della sua sintassi. Per saperne di più, segui i link.
Espressioni
Un'espressione è la più piccola unità di codice che produce un valore, quando viene valutata. Un esempio è un'espressione if ... else, che in Verse valuta un valore che dipende dal contenuto dei blocchi espressione.
Il codice che segue valuta un valore string che contiene "Big!" o "Small!", a seconda che MyNumber sia maggiore di 5:
if (MyNumber > 5):
"Big!"
else:
"Small!"
Commenti sul codice
Un commento sul codice spiega qualcosa sul codice o il motivo per cui il programmatore ha programmato qualcosa in un determinato modo. Quando il programma viene eseguito, i commenti del codice vengono ignorati.
Verse | commento a riga singola: tutto ciò che viene visualizzato tra |
Verse | commento di blocco in linea: tutto ciò che viene visualizzato tra |
Verse | commento di blocco su più righe: tutto ciò che viene visualizzato tra |
Verse | commento di blocco annidato: tutto ciò che viene visualizzato tra |
Verse | commento rientrato: tutto ciò viene visualizzato sulle nuove righe dopo |
Costanti e variabili
Le costanti e le variabili possono memorizzare informazioni o valori utilizzati dal programma e associarli a un nome. Il nome utilizzato è identificatore.
Per creare una variabile o una costante personalizzata, devi comunicarlo a Verse. Questa operazione è chiamata dichiarazione. L'indicazione di un valore iniziale, denominato inizializzazione, è facoltativa per le variabili (anche se consigliata), ma obbligatoria per le costanti.
Puoi modificare il valore di una variabile in qualsiasi momento. Questa operazione è formalmente chiamata assegnazione, perché stai assegnando un valore alla variabile, ma a volte è anche detta impostazione della variabile.
Verse | Creazione di una costante: il valore di una costante non può essere modificato mentre il programma è in esecuzione. Puoi creare una costante identificandone nome, tipo e valore. | Fai clic sull'immagine per ingrandirla. |
Verse | Creazione di una variabile: il valore di una variabile può essere modificato mentre il programma è in esecuzione. Per creare una variabile devi aggiungere la parola chiave | Fai clic sull'immagine per ingrandirla. |
Verse | Modifica del valore di una variabile: è possibile modificare il valore di una variabile mentre il programma è in esecuzione utilizzando la parola chiave | Fai clic sull'immagine per ingrandirla. |
Tipi
Verse è un linguaggio di programmazione staticamente tipizzato, il che significa che a ogni identificatore viene assegnato un tipo.
Ci sono casi in cui il tipo non è richiesto esplicitamente, ad esempio quando si crea una costante in una funzione. Nell'esempio MyConstant := 0, il tipo di MyConstant è dedotto perché gli è stato assegnato il valore 0.
Tipi comuni
Verse include tipi integrati che supportano le operazioni fondamentali che la maggior parte dei programmi deve eseguire. Puoi creare i tuoi tipi combinandoli in strutture più grandi, ma questi tipi comuni sono importanti da comprendere come base per l'utilizzo di variabili e costanti all'interno di Verse.
Verse | logic: |
Verse | int: un
|
Verse | float: un
|
Verse | string: una Puoi inserire il risultato di un'espressione in una stringa utilizzando {} all'interno di "". Questa operazione è chiamata interpolazione delle stringhe. Per saperne di più sul tipo |
Verse | message: un Puoi trasformare il testo in una stringa visualizzabile in fase di runtime utilizzando la funzione Attualmente, l'unico testo che può essere restituito dalla funzione |
Verse | locale: questo tipo rappresenta il contesto in cui un valore di Attualmente, il tipo |
Verse | rational: il tipo
|
Verse | void: il tipo
Per saperne di più sul tipo |
Verse | any: il tipo |
Verse | comparable: il tipo |
Per saperne di più sui tipi comuni in Verse, vedi Tipi comuni.
Tipi di contenitore
Puoi memorizzare più valori insieme utilizzando un tipo di contenitore. Verse dispone di diversi tipi di contenitori in cui memorizzare i valori. Per saperne di più sui tipi di contenitore in Verse, vedi Tipi di contenitore.
Opzione
Il tipo option può contenere un valore o può essere vuoto.
Nell'esempio seguente, MaybeANumber è un numero intero opzionale int che non contiene alcun valore. Un nuovo valore per MaybeANumber viene quindi impostato su 42.
var MaybeANumber : ?int = false # unset optional value
set MaybeANumber := option{42} # assigned the value 42
Fai clic sull'immagine per ingrandirla.
Verse | Creazione di un'opzione: puoi inizializzare un'opzione con quanto segue:
Specifica il tipo aggiungendo |
Verse | Accesso a un elemento di un'opzione: utilizza l'operatore query |
Di seguito è riportato un esempio di utilizzo di un tipo di opzione per salvare un riferimento a un giocatore generato e, quando viene generato un giocatore, far reagire il dispositivo di attivazione:
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 =
Intervallo
L'espressione intervallo contiene tutti i numeri in un intervallo specificato e può essere utilizzata solo in espressioni specifiche, ad esempio l'espressione for. I valori dell'intervallo possono essere solo numeri interi.
Fai clic sull'immagine per ingrandirla.
Verse | Creazione di un intervallo: l'inizio dell'intervallo è il primo valore nell'espressione e la fine è il valore che segue |
Verse | Iterare un intervallo: è possibile utilizzare l'espressione |
Per maggiori informazioni, vedi Intervallo.
Array
Un array è un contenitore in cui è possibile memorizzare elementi dello stesso tipo e accedervi in base alla loro posizione nell'array, detta indice. Il primo indice dell'array è 0 e l'ultimo indice è uno in meno rispetto al numero di elementi dell'array.
Players : []player = array{Player1, Player2}
Fai clic sull'immagine per ingrandirla.
Verse | Creazione di un array: utilizza la parola chiave |
Verse | Accesso a un elemento in un array: utilizza |
Verse | Modifica di un elemento in un array: è possibile modificare il valore memorizzato in una variabile di array a livello di indice utilizzando la parola chiave |
Verse | Iterare un array: è possibile accedere a ogni elemento di un array, in ordine, dal primo all'ultimo, utilizzando l'espressione for. Con l'espressione |
Verse | Ottenere il numero di elementi di un array: utilizza |
Verse | Concatenazione degli array: è possibile unire gli array utilizzando l'operatore |
Per maggiori informazioni, vedi Array.
tupla
Una tupla è un raggruppamento di due o più espressioni trattate come una singola espressione. L'ordine degli elementi nell'espressione è importante. La stessa espressione può essere presente in più posizioni di una tupla. Le espressioni di tupla possono essere di qualsiasi tipo e possono avere tipi misti (a differenza degli array che possono avere elementi di un solo tipo).
Le tuple sono particolarmente utili per:
Restituire più valori da una funzione.
Passare più valori a una funzione.
Il raggruppamento sul posto è più conciso rispetto all'overhead di creare una struttura di dati riutilizzabile (come struct o class).
VerseExampleTuple : tuple(int, float, string) = (1, 2.0, "three")
Fai clic sull'immagine per ingrandirla.
Verse | Creazione di una tupla: utilizza |
Accesso a un elemento in una tupla: utilizza | |
Espansione di tupla: quando utilizzi una tupla come argomento di una funzione, invece di specificare ogni singolo argomento, la funzione sarà chiamata con ogni elemento della tupla utilizzato come argomento nell'ordine in cui è specificato nella tupla. | |
Coercizione array di tupla: le tuple si possono passare ovunque sia previsto un array, purché i tipi degli elementi della tupla siano dello stesso tipo dell'array. Non si possono passare array dove è prevista una tupla. |
Per maggiori informazioni, vedi Tupla.
Mappa
Una mappa è un contenitore in cui puoi memorizzare valori associati a un altro valore, chiamati coppie chiave-valore, e accedere agli elementi tramite le loro chiavi univoche.
WordCount : [string]int = map {"apple" => 11, "pear" => 7}
Fai clic sull'immagine per ingrandirla.
Creazione di una mappa: utilizza la parola chiave | |
Accesso a un elemento in una mappa: utilizza | |
Iterare una mappa: è possibile accedere a ogni elemento di una mappa, in ordine, dal primo inserito all'ultimo, utilizzando l'espressione for. Con l'espressione | |
Ottenere il numero di coppie chiave-valore in una mappa: utilizza |
Per maggiori informazioni, vedi Mappa.
Tipi compositi
Un tipo composito è un tipo che può essere composto da campi ed elementi (di solito denominati) di tipi primitivi o di altri tipi. I tipi compositi di solito hanno un numero fisso di campi o elementi per la loro durata. Per maggiori informazioni, vedi Tipi compositi.
Enum
Enum è l'abbreviazione di enumerazione, che significa nominare o elencare una serie di elementi, denominati enumeratori. Questo è un tipo di Verse che si può utilizzare, ad esempio, per i giorni della settimana o per le direzioni della bussola.
Fai clic sull'immagine per ingrandirla.
Creazione di un enum: utilizza la parola chiave | |
Accesso a un enumeratore: utilizza |
Struct
Struct è l'abbreviazione di struttura ed è un modo per raggruppare diverse variabili correlate. Le variabili possono essere di qualsiasi tipo.
Fai clic sull'immagine per ingrandirla.
Fai clic sull'immagine per ingrandirla.
Creazione di una struct: utilizza la parola chiave | |
*Creazione di un'istanza di una struct: è possibile costruire un'istanza di una struct da un archetipo. Un archetipo definisce i valori dei campi di una struct. | |
Accesso ai campi di una struct: puoi accedere ai campi di una struct per ottenerne il valore aggiungendo |
Classe
Una classe è un modello per la creazione di oggetti con comportamenti e proprietà simili (variabili e metodi) e ne deve essere creata un'istanza per creare un oggetto con valori reali. Le classi sono gerarchiche, il che significa che una classe può ereditare le informazioni dal padre (superclasse) e condividere le sue informazioni con i figli (sottoclassi). Una classe può essere un tipo personalizzato definito dall'utente.
Fai clic sull'immagine per ingrandirla.
Fai clic sull'immagine per ingrandirla.
Creazione di una classe: le definizioni annidate all'interno di una classe ne definiscono i campi. Le funzioni definite come campi di una classe sono chiamate anche metodi. | |
Creazione di un'istanza di una classe: è possibile costruire un'istanza della classe utilizzando il nome della classe seguito da | |
Accesso ai campi di una classe: è possibile accedere ai campi di una classe per ottenerne il valore aggiungendo | |
Accesso ai metodi di una classe: è possibile accedere ai metodi di una classe per chiamarli aggiungendo | |
Self: è possibile utilizzare |
Esistono anche specificatori che sono univoci per le classi e che modificano il loro comportamento. Per maggiori dettagli, vedi Specificatori di classe.
Per saperne di più sulle classi, vedi Classe.
Sottoclasse
Una sottoclasse è una classe che estende la definizione di un'altra classe aggiungendo o modificando i campi e i metodi dell'altra classe (chiamata superclasse).
Fai clic sull'immagine per ingrandirla.
Creazione di una sottoclasse: specifica una classe come sottoclasse di un'altra, aggiungendo l'altra classe tra | |
Override dei campi in una superclasse: è possibile modificare i valori di un campo definito nella superclasse per la sottoclasse soltanto aggiungendo lo specificatore | |
Metodi di esecuzione dell'override sulla superclasse: [INCLUDE:#overriding_methods_on_superclass_description] | |
Super: come per |
Per maggiori informazioni, vedi Sottoclasse.
Costruttore
Un costruttore è una funzione speciale che crea un'istanza della classe a cui è associato. Può essere utilizzato per impostare i valori iniziali del nuovo oggetto.
Definizione di un costruttore di una classe: è possibile aggiungere un costruttore di una classe aggiungendo lo specificatore | |
Aggiunta di variabili ed esecuzione del codice nel costruttore: è possibile eseguire le espressioni all'interno di un costruttore con l'espressione block e introdurre nuove variabili con la parola chiave | |
Chiamata di altri costruttori in un costruttore: è possibile chiamare altri costruttori da un costruttore. Inoltre, puoi chiamare costruttori per la superclasse della classe da un costruttore della classe, purché tutti i campi siano inizializzati. Quando un costruttore chiama un altro costruttore ed entrambi i costruttori inizializzano i campi, per i campi vengono utilizzati soltanto i valori forniti al primo costruttore. L'ordine di valutazione di espressioni tra i due costruttori sarà nell'ordine in cui le espressioni sono scritte (per quanto riguarda gli effetti collaterali), ma vengono utilizzati soltanto i valori forniti al primo costruttore. |
Interfaccia
Il tipo interfaccia fornisce un contratto per l'interazione con qualsiasi classe che implementa l'interfaccia. Non è possibile creare un'istanza di un'interfaccia, ma una classe può ereditare dall'interfaccia e implementarne i metodi. L'interfaccia è simile a una classe astratta, tranne per il fatto che non consente implementazioni parziali o campi nell'ambito della definizione.
Fai clic sull'immagine per ingrandirla.
Fai clic sull'immagine per ingrandirla.
Creazione di un'interfaccia: è possibile definire l'interfaccia in modo simile a una classe, ma utilizzando la parola chiave | |
Estensione di un'interfaccia: un'interfaccia può estendere la definizione di un'altra interfaccia specificando quale estendere tra le | |
Implementazione di un'interfaccia: è possibile implementare l'interfaccia con una classe, semplicemente specificando l'interfaccia tra le | |
Implementazione di più interfacce: una classe può implementare più interfacce. Le varie interfacce sono separate da | |
Ereditare da un'interfaccia e da un'altra classe: una classe può implementare un'interfaccia ed essere una sottoclasse di un'altra classe. L'interfaccia e la superclasse sono separate da |
Per maggiori informazioni, vedi Interfaccia.
Utilizzo dei tipi
Verse offre alcuni modi per facilitare l'utilizzo dei tipi.
Aliasing tipo: è possibile assegnare a un tipo un nome diverso nel codice e farvi riferimento con quel nuovo nome. La sintassi è simile a quella dell'inizializzazione delle costanti. Puoi anche assegnare un alias tipo a un tipo di funzione. Per ulteriori informazioni, vedi Aliasing tipo. | |
Tipo parametrico come argomento di tipo esplicito: Verse supporta i tipi parametrici (tipi previsti come argomenti). Ciò funziona solo con le classi e le funzioni. Per ulteriori informazioni, vedi Tipi parametrici. | |
Tipo parametrico come argomento implicito di tipo alle funzioni: il motivo per utilizzare i tipi parametrici impliciti con le funzioni è che consente di scrivere una volta sola il codice invariante per un particolare tipo, anziché doverlo scrivere per ogni tipo con cui la funzione viene utilizzata. Per ulteriori informazioni, vedi Tipi parametrici. | |
Macro di tipo: Verse ha un costrutto speciale che può essere utilizzato per ottenere il tipo di un'espressione arbitraria. Si può utilizzare ovunque sia possibile utilizzare un tipo. Per ulteriori informazioni, vedi Macro di tipo. | |
sottotipo: è possibile utilizzare |
Per ulteriori informazioni, vedi Utilizzo dei tipi di Verse.
Operatori
Gli operatori sono funzioni speciali definite nel linguaggio di programmazione Verse che eseguono azioni, come le operazioni matematiche, sui loro operandi.
Quando nella stessa espressione vengono utilizzati più operatori, questi vengono valutati nell'ordine di precedenza dal più alto al più basso. Se nella stessa espressione ci sono operatori con la stessa precedenza, essi vengono valutati da sinistra a destra.
La tabella seguente elenca tutti gli operatori incorporati in Verse in ordine di precedenza dal più alto al più basso.
| Operatore | Descrizione | Formato operatore | precedenza degli operatori | Esempio |
|---|---|---|---|---|
Query | L'operatore | operatore postfisso | 9 |
|
Not | L'operatore | Prefisso | 8 |
|
Positivo | È possibile utilizzare l'operatore | Prefisso | 8 |
|
Negativo | È possibile utilizzare l'operatore | Prefisso | 8 |
|
Moltiplicazione | L'operatore | operatore infisso | 7 |
|
Divisione | L'operatore | operatore infisso | 7 |
|
Somma | L'operatore + somma due valori numerici. Quando viene utilizzato con stringhe e array, i due valori vengono concatenati. Per maggiori dettagli, vedi Matematica. | operatore infisso | 6 |
|
Sottrazione | L'operatore | operatore infisso | 6 |
|
Assegnazione di addizione | Con questo operatore è possibile combinare l'addizione e l'assegnazione nella stessa operazione per aggiornare il valore di una variabile. Per maggiori dettagli, vedi Matematica. | operatore infisso | 5 |
|
Assegnazione di sottrazione | Con questo operatore è possibile combinare la sottrazione e l'assegnazione nella stessa operazione per aggiornare il valore di una variabile. Per maggiori dettagli, vedi Matematica. | operatore infisso | 5 |
|
Assegnazione di moltiplicazione | Con questo operatore è possibile combinare la moltiplicazione e l'assegnazione nella stessa operazione per aggiornare il valore di una variabile. Per maggiori dettagli, vedi Matematica. | operatore infisso | 5 |
|
Assegnazione di divisione | Con questo operatore è possibile combinare la divisione e l'assegnazione nella stessa operazione per aggiornare il valore di una variabile, a meno che la variabile non sia un numero intero. Per maggiori dettagli, vedi Matematica. | operatore infisso | 5 |
|
Uguale a | L'operatore | operatore infisso | 4 |
|
Diverso da | L'operatore | operatore infisso | 4 |
|
Minore di | L'operatore | operatore infisso | 4 |
|
Minore o uguale a | L'operatore | operatore infisso | 4 |
|
Maggiore di | L'operatore | operatore infisso | 4 |
|
Maggiore o uguale a | L'operatore | operatore infisso | 4 |
|
E | L'operatore | operatore infisso | 3 |
|
O | L'operatore | operatore infisso | 2 |
|
Inizializzazione variabile e costante | Con questo operatore è possibile memorizzare i valori in una costante o in una variabile. Per maggiori dettagli, vedi Costante e variabili. | operatore infisso | 1 |
|
Assegnazione variabile | Con questo operatore è possibile aggiornare i valori memorizzati in una variabile. Per maggiori dettagli, vedi Costante e variabili. | operatore infisso | 1 |
|
Per saperne di più, vedi Operatori.
Raggruppamento
È possibile modificare l'ordine di valutazione degli operatori raggruppando le espressioni con (). Ad esempio, (1+2)*3 e 1+(2*3) non vengono valutate con lo stesso valore perché le espressioni raggruppate in () verrebbero valutate per prime.
L'esempio seguente mostra come utilizzare il raggruppamento per calcolare un'esplosione in gioco che ridimensiona il danno in base alla distanza dal giocatore, ma in cui l'armatura del giocatore può ridurre il danno totale:
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)
Per maggiori dettagli, vedi Raggruppamento.
Blocchi di codice
Un blocco di codice è un gruppo di zero o più espressioni e introduce un nuovo corpo con ambito. Un blocco di codice deve seguire un identificatore. Può essere un identificatore di funzione o un identificatore di flusso di controllo, come if e for, per esempio.
Distanziato: inizia con | |
Multiriga tra parentesi graffe: racchiuso da | |
Riga singola tra parentesi graffe: racchiuso da |
È anche possibile utilizzare ; per inserire più di un'espressione su una riga. In un formato in cui ogni espressione si trova su una nuova riga, i caratteri {} non devono trovarsi sulla loro riga.
L'ultima espressione di un blocco di codice è il risultato del blocco stesso. Nell'esempio seguente, il blocco di codice dell'espressione if dà come risultato false, se IsLightOn? riesce, o true, se IsLightOn? non riesce. Il risultato logic viene quindi memorizzato in NewLightState.
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
false
else:
Light.TurnOn()
true
Per maggiori informazioni, vedi Blocchi di codice.
Ambito
Ambito si riferisce al codice all'interno di un programma Verse in cui è valida l'associazione di un identificatore (nome) a un valore e in cui tale identificatore può essere utilizzato per riferirsi al valore.
Ad esempio, le costanti o le variabili create all'interno di un blocco di codice esistono solo nel contesto di quel blocco di codice. Ciò significa che la durata degli oggetti è limitata all'ambito in cui sono stati creati e non possono essere utilizzati al di fuori di quel blocco di codice.
L'esempio seguente mostra come calcolare il numero massimo di frecce acquistabili utilizzando il numero di monete in possesso del giocatore. La costante MaxArrowsYouCanBuy viene creata all'interno del blocco if, quindi l'ambito è limitato al blocco if. Quando la costante MaxArrowsYouCanBuy viene utilizzata nella stringa di stampa, genera un errore perché il nome MaxArrowsYouCanBuy non esiste nell'ambito al di fuori dell'espressione 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
Fai clic sull'immagine per ingrandirla.
Verse non supporta il riutilizzo di un identificatore anche se è dichiarato in un ambito diverso, a meno che non si qualifichi l'identificatore aggiungendo (qualifying_scope:) prima di esso, dove qualifying_scope è il nome del modulo, della classe o dell'interfaccia di un identificatore. Ogni volta che definisci e utilizzi un identificatore, devi aggiungere anche il qualificatore all'identificatore.
Funzioni
Una funzione è una sequenza denominata di espressioni che si possono riutilizzare. Una funzione fornisce istruzioni per eseguire un'azione o creare un output basato sull'input.
Definizioni di funzione
Per definire la tua funzione, devi fornire tre parti fondamentali: un nome univoco (identificatore), il tipo di informazioni che sono previste come risultato e ciò che la funzione farà quando viene chiamata. Di seguito è riportata la sintassi di base di una funzione:
name() : type =
codeblock
I valori name() e type separati da due punti: questa è la firma della funzione, cioè il modo in cui si deve chiamare e utilizzare la funzione e il valore che deve essere restituito dalla funzione è del tipo da te fornito. Questo formato è simile al modo in cui si creano le costanti, tranne che per le () dopo il nome che imita il modo in cui si chiama la funzione nel codice.
Il blocco di codice della funzione: definisci cosa fa la funzione quando viene chiamata, fornendo
=codeblock, dovecodeblockè una qualsiasi sequenza di una o più espressioni. Ogni volta che chiami la funzione, le espressioni del blocco di codice vengono eseguite.
Fai clic sull'immagine per ingrandirla.
Risultati della funzione
Quando la funzione ha un tipo restituito specificato, il corpo della funzione deve generare un risultato di quel tipo, in caso contrario il codice non verrà compilato.
Ultima espressione restituita con un valore: per impostazione predefinita, l'ultima espressione nel blocco di codice della funzione è il risultato della funzione e il suo valore deve corrispondere al tipo restituito della funzione. | |
Ritorno esplicito con un valore: è possibile anche definire esplicitamente cosa restituirà la funzione utilizzando |
Quando crei una funzione che non deve generare un risultato, puoi impostare il tipo restituito della funzione a void, il che significa che non è previsto che la funzione generi un risultato utile e quindi l'ultima espressione nel blocco di codice della funzione può essere di qualsiasi tipo.
È possibile uscire da una funzione il cui tipo restituito è void utilizzando l'espressione return da sola. Questa espressione esce immediatamente dalla funzione, anche se ci sono altre espressioni dopo di essa nel blocco di codice.
Parametri della funzione
L'input di una funzione è definito tramite parametri. Un parametro è una costante dichiarata nella firma della funzione tra le parentesi che si può utilizzare nel corpo della funzione.
Segue la sintassi di una funzione con due parametri:
name(parameter1name : type, parameter2name : type) : type =
codeblock
Fai clic sull'immagine per ingrandirla.
Nell'esempio seguente, la funzione IncreaseScore() ha un parametro intero chiamato Points, che la funzione utilizza per aumentare il valore di MyScore:
var MyScore : int = 100
IncreaseScore(Points : int) : void =
# Increase MyScore by Points.
set MyScore = MyScore + Points
Chiamate di funzione
Quando vuoi utilizzare la sequenza di espressioni denominata (la funzione) nel codice, chiama la funzione per nome, come GetRandomInt(1, 10), che restituisce un numero intero casuale compreso tra 1 e 10, inclusi.
Esistono due modi per chiamare una funzione, a seconda che la chiamata di funzione sia fallibile:
Chiamata di funzione non fallibile: una chiamata di funzione che non può essere fallibile ha la forma | |
Chiamata di funzione fallibile: una chiamata di funzione fallibile ha la forma |
Argomenti della funzione
Quando si chiama una funzione che prevede parametri, è necessario assegnare valori ai parametri, proprio come è necessario assegnare valori alle costanti per poterle utilizzare. I valori assegnati sono chiamati argomenti della funzione.
Segue la sintassi per chiamare una funzione con due argomenti:
name(parameter1name := value, parameter2name := value)
Nell'esempio seguente, la funzione IncreaseScore() viene chiamata tre volte, ogni volta con argomenti diversi (10, 5 e 20), per aumentare il valore di 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)
Metodi di estensione
I metodi di estensione sono un tipo di funzione che si comporta come un membro di una classe o di un tipo esistente, ma non richiede la creazione di un nuovo tipo o sottoclasse.
Di seguito viene mostrato come creare un metodo di estensione per gli array di tipo int. Il metodo aggiunge tutti i numeri dell'array e restituisce il totale.
# Sum extension method for type []int
(Arr : []int).Sum<public>() : int =
var Total : int = 0
for (Number : Arr):
set Total = Total + Number
return TotalIl metodo può quindi essere chiamato su qualsiasi array di tipo int.
SumTotal := array{4, 3, 7}.Sum()
Print("The SumTotal is { SumTotal }")
# "The SumTotal is 14"
Non riuscito
A differenza di altri linguaggi di programmazione che utilizzano i valori booleani true e false per modificare il flusso di un programma, Verse utilizza espressioni che possono riuscire o non riuscire. Queste espressioni sono chiamate espressioni fallibili e si possono eseguire solo in un contesto di fallimento.
Espressioni fallibili
Un'espressione fallibile è un'espressione che può riuscire e generare un valore, oppure non riuscire e non generare alcun valore.
L'elenco seguente comprende tutte le espressioni fallibili in Verse:
Chiamate di funzione: solo quando la chiamata di funzione ha il formato | |
Confronto: un'espressione di confronto confronta due elementi utilizzando uno degli operatori di confronto. Per maggiori informazioni, vedi operatori. | |
Divisione di interi: per gli interi, l'operatore di divisione | |
Decisione: un'espressione decisionale utilizza gli operatori | |
Query: un'espressione di query utilizza l'operatore | |
Accesso a un elemento in un array: l'accesso al valore memorizzato in un array è un espressione fallibile perché potrebbe non esserci un elemento a quell'indice, quindi deve essere utilizzata in un contesto di fallimento. Per maggiori dettagli, vedi array. |
Contesti di fallimento
Un contesto di errore è un contesto in cui è consentita l'esecuzione di espressioni fallibili. definendo cosa succede se l'espressione [fallisce](#fail). Qualsiasi errore all'interno di un contesto di errore causa l'errore dell'intero contesto.
Un contesto di fallimento consente alle espressioni annidate di essere espressioni di errore, come ad esempio gli argomenti delle funzioni o le espressioni in un'espressione block.
L'elenco seguente comprende tutti i contesti di errore in Verse:
La condizione nelle espressioni | |
Le espressioni di iterazione e le espressioni di filtro nelle espressioni | |
Il corpo di una funzione che include lo specificatore di effetto | |
L'operando per l'operatore | |
L'operando di sinistra per l'operatore | |
Inizializza una variabile con il tipo |
esecuzione speculativa
Un aspetto utile dei contesti di fallimento in Verse è che sono una forma di esecuzione speculativa, il che significa che si possono provare le azioni senza eseguirne il commit. Quando un'espressione riesce, viene eseguito il commit degli effetti dell'espressione, come ad esempio la modifica del valore di una variabile. Se l'espressione non riesce, viene eseguito il rollback degli effetti dell'espressione, come se l'espressione non si fosse mai verificata.
In questo modo, è possibile eseguire una serie di azioni che accumulano modifiche, ma tali azioni saranno annullate se non riescono in qualsiasi punto del contesto di errore.
Affinché questo funzioni, tutte le funzioni chiamate nel contesto di fallimento devono avere lo specificatore di effetto transacts.
Specificatori
Gli specificatori in Verse descrivono un comportamento relativo alla semantica e si possono aggiungere agli identificatori e ad alcune parole chiave. La sintassi degli specificatori utilizza < e >, con la parola chiave in mezzo, come ad esempio IsPuzzleSolved()<decides><transacts> : void.
Le sezioni seguenti descrivono tutti gli specificatori di Verse e quando è possibile utilizzarli.
Specificatori di effetto
Gli effetti in Verse indicano le categorie di comportamento che una funzione è consentita ad assumere. Puoi aggiungere specificatori di effetto a:
() dopo il nome in una definizione di funzione:
name()<specifier> : type = codeblock.La parola chiave
class:name := class<specifier>():.
Gli specificatori di effetto si dividono in due categorie:
Esclusivo: puoi avere solo uno o nessuno degli specificatori di effetto esclusivo aggiunti a una funzione o alla parola chiave
class. Se non viene aggiunto alcun specificatore di effetto esclusivo, l'effetto predefinito èno_rollback.Additivo: puoi aggiungere tutti, alcuni o nessuno degli specificatori di effetto additivo a una funzione o alla parola chiave
class.
| Esempio | Effetto |
|---|---|
no_rollback: è l'effetto predefinito quando non viene specificato un effetto esclusivo. L'effetto | |
Effetti esclusivi | |
transacts: questo effetto indica che può essere eseguito il rollback di qualsiasi azione eseguita dalla funzione. L'effetto transacts è richiesto ogni volta che viene scritta una variabile modificabile ( | |
varies: questo effetto indica che lo stesso input della funzione potrebbe non generare sempre lo stesso output. L'effetto | |
computes: questo effetto richiede che la funzione non abbia effetti collaterali e non è garantito che venga completata. Esiste un requisito non controllato che la funzione, se fornita con gli stessi argomenti, generi lo stesso risultato. Qualsiasi funzione che non disponga dello specificatore native e che altrimenti avrebbe l'effetto | |
converges: questo effetto garantisce non solo che non ci siano effetti collaterali dall'esecuzione della funzione correlata, ma anche che la funzione si completi definitivamente (non sia ricorsiva all'infinito). Questo effetto può essere visualizzato solo nelle funzioni che hanno lo specificatore native, ma ciò non viene controllato dal compilatore. Il codice utilizzato per fornire valori predefiniti di classi o valori per variabili globali deve avere questo effetto. Le funzioni | |
Effetti additivi | |
decides: indica che la funzione può non riuscire e che la chiamata a questa funzione è un'espressione fallibile. Le definizioni di funzioni con l'effetto | |
suspends: indica che la funzione è asincrona. Crea un contesto asincrono per il corpo della funzione. |
In tutti i casi, la chiamata di una funzione che ha un effetto specifico richiede che anche il chiamante abbia quell'effetto.
Specificatori di accesso
Gli specificatori di accesso definiscono cosa può interagire con un membro e in che modo. Gli specificatori di accesso possono essere applicati a quanto segue:
L'identificatore di un membro:
name<specifier> : type = valueLa parola chiave
varper un membro:var<specifier> name : type = value
Specificatori di classe
Gli specificatori di classe definiscono alcune caratteristiche delle classi o dei loro membri, come la possibilità di creare una sottoclasse di una classe.
abstract: quando una classe o un metodo di classe ha lo specificatore abstract, non è possibile creare un'istanza della classe. Le classi astratte sono destinate a essere utilizzate come superclassi con implementazione parziale o come interfaccia comune. È utile quando non ha senso avere istanze di una superclasse, ma non si vogliono duplicare proprietà e comportamenti di classi simili. | |
concrete: quando una classe ha lo specificatore concrete, deve essere possibile costruirla con un archetipo vuoto, il che significa che ogni campo della classe deve avere un valore predefinito. Ogni sottoclasse di una classe concreta è implicitamente concreta. Una classe concreta può ereditare direttamente da una classe astratta, solo se entrambe le classi sono definite nello stesso modulo. | |
unique: lo specificatore unique può essere applicato a una classe per renderla univoca. Per costruire un'istanza di una classe univoca, Verse alloca un'identità univoca per l'istanza risultante. Ciò consente di verificare l'uguaglianza tra istanze di classi univoche confrontando le loro identità con gli operatori =, < e >, il che le rende sottotipi del tipo confrontable. Le classi prive dello specificatore univoco non hanno tale identità e quindi si possono confrontare solo per l'uguaglianza in base ai valori dei loro campi. Ciò significa che le classi univoche si possono confrontare con gli operatori = e <> e sono sottotipi del tipo confrontabile. |
Specificatori di implementazione
Non puoi utilizzare gli specificatori di implementazione quando scrivi il codice, ma saranno presenti nelle API UEFN.
native_callable: indica che un metodo di istanza è nativo (implementato in C++) e può essere richiamato da altro codice C++. Puoi vedere questo specificatore utilizzato su un metodo di istanza. Questo specificatore non si propaga alle sottoclassi e quindi non è necessario aggiungerlo a una definizione, quando esegui l'override di un metodo che include lo specificatore stesso. |
Specificatore di localizzazione
È necessario utilizzare lo specificatore localizes quando si definisce un nuovo messaggio. In particolare, questo accade quando la variabile ha il tipo message e si inizializza la variabile con un valore 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"))Non è necessario utilizzare lo specificatore localizes quando si inizializza un valore membro con un messaggio già creato, perché lo specificatore localizes serve solo per definire nuovi messaggi.
PlayerOne<localizes> : message = "Player One"
# The winning player's name:
PlayerName : message = PlayerOneAttributi
Gli attributi in Verse descrivono un comportamento che viene utilizzato al di fuori del linguaggio Verse (a differenza degli specificatori, che descrivono la semantica di Verse). Gli attributi possono essere aggiunti nella riga di codice prima delle definizioni.
La sintassi degli attributi utilizza @, seguito dalla parola chiave.
flusso di controllo
Il flusso di controllo è l'ordine in cui un computer esegue le istruzioni. Verse ha diverse espressioni che si possono utilizzare per controllare il flusso del programma.
Blocco
Poiché Verse richiede un identificatore prima di un blocco di codice, le espressioni block sono il modo per annidare i blocchi di codice. Un blocco di codice annidato si comporta in modo simile a un blocco di codice. Come per i blocchi di codice, un'espressione block introduce un nuovo corpo di ambito annidato.
| block |
|---|
Risultato: ultima espressione nel blocco di codice |
Per maggiori informazioni, vedi Blocco.
If
Con l'espressione if, puoi prendere decisioni che cambiano il flusso del programma. Come in altri linguaggi di programmazione, l'espressione if di Verse supporta l'esecuzione condizionale, ma in Verse le condizioni utilizzano la riuscita e l'errore per guidare la decisione.
| Se | if ... then |
|---|---|
Risultato: ultima espressione nel blocco di codice | Risultato: ultima espressione nel blocco di codice |
| if ... else | if ... else if ... else |
|---|
Nell'esempio seguente, il blocco di codice dell'espressione if dà come risultato false, se IsLightOn? riesce, o true, se IsLightOn? non riesce. Il risultato logic viene quindi memorizzato in NewLightState.
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
false
else:
Light.TurnOn()
true
Un caso utile per l'espressione if in Verse è che si possono provare le espressioni fallibili e se non riescono, viene eseguito il rollback delle azioni come se non fossero mai avvenute. Per maggiori dettagli su questa funzionalità, vedi Esecuzione speculativa.
Per maggiori informazioni, vedi If.
Case
Con le espressioni case, puoi controllare il flusso di un programma da un elenco di scelte. L'istruzione case in Verse consente di testare un valore rispetto a più valori possibili (come se si utilizzasse =) e di eseguire il codice in base a quello che corrisponde.
| astuccio |
|---|
Per maggiori informazioni, vedi Caso.
Loop
Con l'espressione loop, le espressioni del blocco del loop vengono ripetute per ogni iterazione del loop.
| loop |
|---|
Risultato: il risultato di un'espressione |
Nell'esempio seguente, una piattaforma appare e scompare ogni ToggleDelay secondi per tutta la durata del gioco.
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.
Per maggiori informazioni, vedi Loop.
Arresto dei loop
Un blocco del loop si ripete all'infinito, quindi per interromperlo si può uscire dal loop con break o con l'espressione restituita di una funzione.
| Loop e interruzione | loop e return |
|---|---|
Risultato: il risultato di un'espressione | Risultato: il risultato di un'espressione |
| loop annidato e break |
|---|
Risultato: il risultato di un'espressione |
Per maggiori informazioni, vedi Loop e break.
For
Un'espressione for, a volte chiamata for loop, è uguale a un'espressione loop, tranne per il fatto che le espressioni for utilizzano un generatore per produrre una sequenza di valori uno alla volta e assegnano un nome a ogni valore.
Ad esempio, l'espressione for(Value : 1..3) genera la sequenza 1, 2, 3, e a ogni numero della sequenza viene dato il nome Value per ogni iterazione, quindi il loop for viene eseguito tre volte.
L'espressione for contiene due parti:
Specifica dell'iterazione: le espressioni all'interno delle parentesi. La prima espressione deve essere un generatore, ma le altre possono essere un'inizializzazione costante o un filtro.
Corpo: le espressioni nel blocco di codice dopo le parentesi.
| per | for con un filtro |
|---|---|
Risultato: il risultato di un'espressione | Risultato: il risultato di un'espressione |
| for con più generatori | blocchi annidati di for |
|---|---|
Risultato: il risultato di un'espressione | Risultato: il risultato di un'espressione |
Per maggiori informazioni, vedi For.
defer
Un'espressione defer viene eseguita appena prima di trasferire il controllo del programma al di fuori dell'ambito in cui appare l'espressione defer, compresa qualsiasi espressione di risultato, come ad esempio in un return. Non importa come viene trasferito il controllo del programma.
| defer | defer prima di un'uscita |
|---|---|
Risultato: l'espressione | Risultato: l'espressione |
Un'espressione defer non viene eseguita se si verifica un'uscita anticipata prima che venga incontrato defer.
| defer con return anticipato | defer con un'attività simultanea annullata |
|---|---|
Risultato: l'espressione | Risultato: l'espressione |
Più espressioni defer che appaiono nello stesso ambito si accumulano. L'ordine in cui vengono eseguite è inverso a quello in cui vengono incontrate: l'ordine first-in-last-out (FILO). Poiché l'ultimo defer incontrato in un determinato ambito viene eseguito per primo, le espressioni all'interno di quell'ultimo defer possono fare riferimento a un contesto che sarà ripulito da altre espressioni defer incontrate in precedenza ed eseguite successivamente.
| Espressioni defer multiple in un blocco di codice | Espressioni defer multiple in blocchi di codice diversi |
|---|---|
Risultato: l'espressione | Risultato: l'espressione |
Flusso temporale e concorrenza
Il controllo del flusso temporale è il cuore del linguaggio di programmazione Verse e si ottiene con le espressioni simultanee. Per saperne di più sulla concorrenza, vedi Panoramica sulla concorrenza.
concorrenza strutturata
Le espressioni di concorrenza strutturata sono utilizzate per specificare il flusso temporale asincrono e per modificare la natura bloccante delle espressioni asincrone con una durata vincolata a un ambito del contesto asincrono specifico, come ad esempio il corpo di una funzione asincrona.
Questo è simile al flusso di controllo strutturato, come `block, if, for e loop, che vincola agli ambiti associati.
| Sincronizzazione | Ramo |
|---|---|
Esegue contemporaneamente tutte le espressioni nel suo blocco di codice e attende che finiscano tutte prima di eseguire l'espressione successiva dopo la | Il corpo dell'espressione |
Risultato: il risultato di un'espressione | Risultato: un'espressione |
| race | rush |
|---|---|
Simile a | Simile a |
Risultato: il risultato di un'espressione | Risultato: il risultato di un'espressione |
concorrenza non strutturata
Le espressioni di concorrenza non strutturate, di cui la generazione è l'unica, hanno una durata di vita non vincolata a uno specifico ambito del contesto asincrono che può estendersi oltre l'ambito in cui è stata eseguita.
| Genera |
|---|
Il corpo di |
Risultato: |
Task
Un'attività è un oggetto utilizzato per rappresentare lo stato di una funzione asincrona in esecuzione. Gli oggetti attività sono utilizzati per identificare il punto di sospensione di una funzione asincrona e i valori delle variabili locali in quel punto di sospensione.
# Get task to query / give commands to
# starts and continues independently
Task2 := spawn{Player.MoveTo(Target1)}
Task2.Await() # wait until MoveTo() completed
Moduli e percorsi
Un modulo di Verse è un'unità atomica di codice che può essere ridistribuita e da cui si dipende e può evolvere nel tempo senza interrompere le dipendenze. È possibile importare un modulo om Verse per utilizzare codice definito in altri file.
utilizzo: per utilizzare il contenuto di un modulo Verse, devi specificare il modulo con il relativo percorso. | |
modulo: oltre ai moduli introdotti dalle cartelle in un progetto, i moduli si possono introdurre all'interno di un file .verse utilizzando la parola chiave |
Per altre informazioni, vedi Moduli e percorsi.