Per creare istruzioni che siano allo stesso tempo complesse, ma veloci da esecuzione bisogna progettare un codice simile all'assembler (RISC o CISC che sia).
A mio avviso la soluzione migliore è la seguente:
CODICE FUNZIONE | VARIABILE DESTINAZIONE | PARAMETRI |
Ogni funzione deve sapere la lungezza dei parametri ad essa associata (quantità
parametri implicita). Questo valore potrebbe però essere esplicito.
In questo modo se la funzione non è conosciuta verrà saltata
visto che si conosce la sua dimensione (nei rari casi di linguaggio che possa
essere interpretato da diverse versioni della VM).
Al momento della compilazione a ogni funzione definita dall'utente sarà assegnato un ID che non dovrà sovrapporsi agli ID implementati dall VM. Allo stesso modo le stringhe verra assegnato un ID per riferirsi come variabile.
Le variabili possono essere senza tipo o con tipo. L'unica cosa che cambia in quelle con tipo e la generazione di un messaggio di errore se non esiste una conversione di tipo.
Le variabili possono essere oggetti. Usare script a oggetti permette di ampliare in modo esponenziale le potenzialità dello script elevando all'infinito la complessità di interpretazione, compilazione ed esecuzione (e di errore?).
Orientare comunque a oggetti la variabili è sempre una buona cosa invece, non permettendo ne variabili ne classi definite dal'utente. Se il linguaggio di script è sufficientemente ampio questo è possibile.
; I commenti servono. | ; è comodo perchè indica di saltare interamente la linea |
@A=CreateRectangle(100,100,200,200); | Una sintassi colorita aiuta molto la scrittura del codice, mentre complica l'interpretazione. uso il simbolo @ per indicare le variabili per esempio. |
@A CreateRectangle 100 100 200 200 | E' molto più semplice da compilare, ma meno bello da vedere... |
@A.Draw | Vederlo come oggetto |
Draw @A | o no. E' identico. E' solo una questione di estetica. La procedura non richiede un return. Implementare una variabile void (0) |
DrawText 5 5 "Script di prova" | La stringa andrà salvata a parte e codificato solo un indicatore alla stringa. uso il costrutto "..." per indicare il testo, visto che come ho scelto io gli spazi separano le variabili |
Release @A | Una visione abbastanza Object Oriented. ma non troppo. |
Proc ShadowText @X @Y @T ... EndProc |
Le procedure sono una parte fondamentale per uno script. Vanno cercate inizialmente per assegnargli gli ID e una semantica, poi compilate con il resto. |
ShadowText(int @X, int @Y, string @T) ... ShadowText End |
Altro esempio di possibile implementazione... un po' meno interpreter oriented e un po' più user oriented. |
Ci sarebbe da implementare anche lo stack se se ne ha voglia...
Faccio poi notare che normalmente in un linguaggio di script si fa senza dei
puntatori (Basic, Perl, JavaScript etc etc...). Una strada da seguire per
non impazzire e non combinare eccessivi danni. L'implementazione di array
(ovviamente dinamici) risolve questo problema in una maniere elegante. Questo
obbliga perciò a aggiungere la terza forma di indirzzamento: quella
con scostamento (BASE + OFFSET). Ovviamente per generalizzare si potrebbe
pensare ogni variabile come un array e fare riferimento normalmente all'entry
0.
Esempio:
Script | OpCode Espanso: |
DrawText @X @Y "Prova" | DRAWTEXT VOID mem[@X+0] mem[@Y+0] mem[@String0+0] |
DrawText @X[1] @Y[1] @Text[1] | DRAWTEXT VOID mem[@X+1] mem[@Y+1] mem[@Text+1] |
DrawText 5 5 "Prova" | DRAWTEXT VOID immediato[5] immediato[5] mem[@String0+0] |
Script | |
@C = (@A+@B)*2 | ADD @temp1 @A @B MUL @C @temp1 immediate(2) |
Diciamo che in generale istruzioni Assembler Like sono molto più semplici da implementare che funzioni più Complesse, ma a scapito di una crescita di complessità del compilatore.
A volte comunque non sono necessarie azioni complesse (come incapsulamento
di oggetti e roba simile...) e si possono definire script molto semplici.
Vogliamo gestire gli eventi di una semplice avventura grafica. Per esempio...
per aprire una porta è necessario inserire una chiave nella serratura.
Quando clicchiamo la chiave sulla porta o quando cerchiamo successivamente
di aprirla, viene chiamata la procedura porta con due variabili: tipo di azione
e oggetto.
Queste variabili saranno globali (per semplicità). Chiamate per esempio
@Action e @Object e un parametro Object Oriented @This
Un linguaggio evoluto sarebbe una cosa simile
Codice | Spiegazione | Decompiled OpCode |
proc Porta(@Action, @Object,@This) | Solo per motivi di semplicità di lettura | Nessuna istruzione |
if @Action==Use | Use è una costante definita nel compilatore | IFEQUIV [VAR/COST1] [VAR/COST2] |
if @Object==Key | Key è una costante definita nel compilatore | |
@KeyFlag=true | Assegnamento: true è definita nel compiulatore | MOV @KeyFlag 1 |
endif | Fine dell'IF... se la condizione è sbagliata da qui in poi ricomincia a controllare. Attenzione all'annidamento degli IF | ENDIF |
if @Object==None | None è una costate definita nel compilatore | IFEQUIV @Object 0 |
if @KeyFlag==true | IFEQUIV @KeyFlag 1 | |
DoAction OpenDoor @This | DoAction è un qualsiasi procedura esposta dal programma principale e implementata direttamente. OpenDoor è una costante | DOACTION <codice di OpenDoor> @This |
else | Se if non è vero può trovare un else | ELSE |
Say "La porta è chiusa a chiave" | Say è un metodo esposto dal programma... | SAY @String0001 |
endif | Chisura dell'ultimo if. l'annidamento scende di un livello | ENDIF |
endif | ENDIF | |
endif | ENDIF | |
endproc | Restituisce il controllo al programma | RET |
Un problema è ovviamento l'annidamento degli IF/ELSE/ENDIF. Usare l'annidamento di funzioni potrebbe complicare il codice ma è una soluzione opinabile. Altrimenti si può creare uno stack locale gestito dall'utente che si ricorda se l'espressione è vera o falsa e esegue di conseguenza il codice
Codice | Stack |
if @Object==None | La condizione è VERA (per esempio) AGGIUNGIAMO VERO Allo stack |
if @KeyFlag==true | L'ultimo oggetto dello stack corrente è vero. Eseguiamo l'istruzione. L'istruzione è falsa per esempio. Inseriamo FALSO nello stack |
... | L'ultimo oggetto dello stack è FALSO, l'istruzione non verrà eseguita (ulteriori if verranno marcati con un ulteriore stato... DUMMY) |
else | Else: Si Invere la flag dello stack. adesso da FALSE diventa TRUE. |
... | Lo stack mostra TRUE... eseguiamo l'istruzione |
endif | Sia che sia True che sia False (o dummy) si elimina l'ultimo oggetto dallo stack.Ora è True |
endif | Si elimina l'oggetto dallo stack |
Perchè è stato necessario aggiungere lo stato Dummy? Per evitare che endif annidati in stati FALSE forzino l'uscita dallo stato.
#main
$A=Create 130 90
for $I 0 90
wsprintf $N "flag%04u" $I
$X=calc "($I%10)*13"
$Y=calc "($I/10)*9"
&Flagged
end
Save $A "JPG:90" "flag.jpg"
Release $A
end
#Flagged
$B=Load $N
Resize $B 13 9
Blit $A $X $Y $B
Release $B
end
Paolo Medici, che ultimamente aveva del tempo da perdere in retorica. Dalla Serie delle Guide Veloci per fare Software Miliardari | Visto che queste sono considerazioni personali, gradirei anche qualche altro punto di vista.... la mia email è: software@pmx.it |