ECL Format: differenze tra le versioni

Da Zulu Hotel Italia - Time Warp.
(istruzione 0F: return)
(Usages (Module) - codice 01 00)
 
(62 versioni intermedie di uno stesso utente non sono mostrate)
Riga 1: Riga 1:
 +
Nella seguente documentazione si farà a volte riferimento ai sorgenti del POL. I sorgenti in quetsione sono i sorgenti del POL099 disponibili al seguente indirizzo: https://github.com/polserver/polserver. Nello specifico, il commit di riferimento alla data di redazione di questo documento è: ''820790a6feedaa0462bfc3aa96faa739e08f0ecd''.
 +
 +
I file ''pol-core/bscript/tokens.h'' e ''pol-core/bscript/executor.cpp'' contengono la maggior parte delle informazioni necessarie.
 +
L'apertura e la lettura del file iniziano in ''pol-core/bscript/eprog_read.cpp''.
 +
 
=Codifica dei tipi=
 
=Codifica dei tipi=
 
All'interno del file, sono condificati dati di più tipi primitivi a cui si farà riferimento successivamente. Le codifiche finora individuate sono:
 
All'interno del file, sono condificati dati di più tipi primitivi a cui si farà riferimento successivamente. Le codifiche finora individuate sono:
Riga 9: Riga 14:
 
Ogni file inizia con un header di 6 bytes così definito:
 
Ogni file inizia con un header di 6 bytes così definito:
 
* byte 1-2: rappresentano il magic number del formato ("CE")
 
* byte 1-2: rappresentano il magic number del formato ("CE")
* byte 3: rappresenta il numero di versione (02 per POL093)
+
* byte 3-4: rappresentano il numero di versione (02 00 per POL093)
* byte 4: sempre 00
+
* byte 5-6: contengono il numero di variabili globali utilizzate
* byte 5: contiene un valore da decodificare (tra 00 e 07)
+
* byte 6: sempre 00
+
  
 
=Struttura=
 
=Struttura=
Successivamente all'header, il file ECL sembra avere una struttura a blocchi. Di seguito sono elencati quelli noti, riportati nello stesso ordine in cui compariranno all'interno del file.
+
Successivamente all'header, il file ECL sembra avere una struttura a sezioni. Di seguito sono elencati quelli noti, riportati nello stesso ordine in cui compariranno all'interno del file.
  
Ciascun blocco inizia con 6 (2+4) bytes. Di questi 6 bytes, i primi 2 rappresentano il '''codice''' del blocco, mentre i successivi 4 rappresentano, sotto forma di intero, la lunghezza del blocco, in bytes, non conteggiando i 6 bytes dell'intestazione stessa.
+
Ciascuna sezione inizia con 6 (2+4) bytes. Di questi 6 bytes, i primi 2 rappresentano il '''codice''' dela sezione, mentre i successivi 4 rappresentano, sotto forma di intero, la lunghezza della sezione, in bytes, non conteggiando i 6 bytes dell'intestazione stessa.
  
 
Fa '''eccezione''' il codice 01 00 per cui la lunghezza viene indicata sempre come 0.
 
Fa '''eccezione''' il codice 01 00 per cui la lunghezza viene indicata sempre come 0.
  
 
==Program - codice 04 00==
 
==Program - codice 04 00==
Questo blocco definisce la direttiva program.
+
Questa sezione definisce la direttiva program.
  
 
Non è sempre presente: ad esempio, '''non''' è usato nello script generale di startup ''start.ecl''.
 
Non è sempre presente: ad esempio, '''non''' è usato nello script generale di startup ''start.ecl''.
  
I primi 6 bytes sono 04 00 - 10 00 00 00, seguono 16 bytes di cui solo il primo sembra essere usato ed indica il numero di argomenti accettati dal blocco program (01, 02, etc...).
+
I primi 6 bytes sono 04 00 - 10 00 00 00, seguono 16 bytes di cui solo il primo sembra essere usato ed indica il numero di argomenti accettati dalla sezione program (01, 02, etc...).
  
 
Gli altri bytes sono sempre lasciati a NULL.
 
Gli altri bytes sono sempre lasciati a NULL.
  
==Usages - codice 01 00==
+
==Usages (Module) - codice 01 00==
 
I file ''.em'' contenenti le funzioni di sistema importati con la direttiva ''use'' sono specificate immediatamente dopo l'header. Ogni blocco ''use'' ha una dimensione variabile.
 
I file ''.em'' contenenti le funzioni di sistema importati con la direttiva ''use'' sono specificate immediatamente dopo l'header. Ogni blocco ''use'' ha una dimensione variabile.
  
I primi 6 bytes sono 01 00 00 00 00 00, segue un blocco di 13 bytes così suddiviso:
+
I primi 6 bytes sono 01 00 00 00 00 00
 +
 
 +
Nella versione 2 (POL093), segue un blocco di 13 bytes così suddiviso:
 
* bytes 1-9: contengono il nome dell'usage sotto forma di stringa a lunghezza fissa
 
* bytes 1-9: contengono il nome dell'usage sotto forma di stringa a lunghezza fissa
* byte 10: indica il numero di funzioni utilizzate da questo file
+
* byte 10-13: indica il numero di funzioni utilizzate da questo file
* bytes 11-13: sempre NULL
+
 
 +
Nella versione 12 (POL099), segue un blocco di 18 bytes così suddiviso:
 +
* bytes 1-14: contengono il nome dell'usage sotto forma di stringa a lunghezza fissa
 +
* bytes 15-18: indica il numero di funzioni utilizzate da questo file
  
Segue la lista delle funzioni utilizzate da questo file, sotto forma 34 bytes ripetuti per il numero di volte indicato dal byte 10. Di questi 34 bytes, i primi 33 rappresentano una stringa a lunghezza fissa contenente il nome della funzione, mentre l'ultimo byte indica il numero di parametri accettati dalla funzione stessa.
+
Segue la lista delle funzioni utilizzate da questo file, sotto forma 34 bytes ripetuti per il numero di volte indicato dai byte 10-13 o 15-18. Di questi 34 bytes, i primi 33 rappresentano una stringa a lunghezza fissa contenente il nome della funzione, mentre l'ultimo byte indica il numero di parametri accettati dalla funzione stessa.
  
 
"basic" e "basicio" sembrano essere sempre i primi due usages e sono inclusi implicitamente, e ribaditi quindi nel file ECL, anche se non dichiarati nello script sorgente.
 
"basic" e "basicio" sembrano essere sempre i primi due usages e sono inclusi implicitamente, e ribaditi quindi nel file ECL, anche se non dichiarati nello script sorgente.
Riga 44: Riga 52:
 
==Istruzioni - codice 02 00==
 
==Istruzioni - codice 02 00==
 
  ATTENZIONE!!! È probabile che questa sezione contenga ancora molti errori o imprecisioni
 
  ATTENZIONE!!! È probabile che questa sezione contenga ancora molti errori o imprecisioni
Il blocco segue la struttura sopra citata: nell'intestazione è indicata la lunghezza del blocco in byte. Segue un intero che indica nuovamente la lunghezza del blocco meno i 4 byte che compongono l'intero stesso, per un totale di 10 byte di intestazione.
+
La sezione segue la struttura sopra citata: nell'intestazione è indicata la lunghezza della sezione in byte. Segue un intero che indica nuovamente la lunghezza della sezione meno i 4 byte che compongono l'intero stesso, per un totale di 10 byte di intestazione.
  
 
I successivi byte rappresentano le istruzioni stesse e sono sempre un numero di byte divisibile per 5.
 
I successivi byte rappresentano le istruzioni stesse e sono sempre un numero di byte divisibile per 5.
  
Un'istruzione è composta da 5 byte. Se il 2°byte è 2F, questo indica il codice istruzione, altrimenti il byte, indica il codice istruzione.
+
Un'istruzione è composta da '''5 byte''', così definiti (''pol-core/bscript/symcont.h:30''):
 +
* byte 1 - type: sebbene sia dichiarato come instruction type ed abbia una serie di valori predefiniti (''pol-core/bscript/tokens.h:14''), si ritiene che il suo significato originale sia andato perso. La maggior parte delle volte il suo valore può essere ignorato, mentre in alcuni casi speciali si limita a contenere degli argomenti
 +
* byte 2 - id: identifica il tipo di istruzione e la sua funzione (''pol-core/bscript/tokens.h:47'')
 +
* byte 3-4 - offset: contengono il primo parametro per l'istruzione, solitamente un puntatore a un indirizzo all'interno del blocco costanti, l'ID di una variabile, o una posizione dall'interno del programma (goto)
 +
* byte 5 - module: contengono il secondo parametro per l'istruzione. Non viene usato spesso.
 +
 
 +
===registri===
 +
Sono presenti un numero indefinito di registri ''W'', utilizzati come argomenti per le chiamate alle funzioni builtin. Tali registri sono rappresentati come un'array o lista di registri.
 +
Nell'implementazione POL è usata una deque (http://it.cppreference.com/w/cpp/container/deque).
  
 
===elementi===
 
===elementi===
Riga 54: Riga 70:
 
* intero3 (3 byte): Si tratta di un intero che ha lo stesso formato dell'intero definito precedentemente, ma manca del byte più significativo, che si assume essere sempre 00.
 
* intero3 (3 byte): Si tratta di un intero che ha lo stesso formato dell'intero definito precedentemente, ma manca del byte più significativo, che si assume essere sempre 00.
 
* puntatore (4 byte):
 
* puntatore (4 byte):
** byte 1: Il tipo di argomento: 00 = intero, 01 = float, 02 = stringa, 33 = variabile
+
** byte 1: Il tipo di argomento: 00 = intero, 01 = float, 02 = stringa, 33 = variabile locale, 34 = variabile globale
** byte 2-4: Se il byte precedente indica una costante, è l'offset all'interno del blocco delle costanti (intestazione esclusa): indica la posizione in byte dell'argomento da leggere. È da notare che le stringhe possono non essere lette dall'inizio: ad esempio, una stringa "abc" può essere usata come stringa "bc" semplicemente puntando al secondo carattere anziché all'inizio della stringa. Nel caso di una variabile, è il suo ID univoco. Rappresentato come intero3.
+
** byte 2-4: Se il byte precedente indica una costante, è l'offset all'interno della sezione delle costanti (intestazione esclusa): indica la posizione in byte dell'argomento da leggere. È da notare che le stringhe possono non essere lette dall'inizio: ad esempio, una stringa "abc" può essere usata come stringa "bc" semplicemente puntando al secondo carattere anziché all'inizio della stringa. Nel caso di una variabile, è il suo ID univoco. Rappresentato come intero3.
  
===istruzione xx 2F: run===
+
===istruzione 2F: run===
Esegue una funzione builtin (use). Si tratta di un'istruzione anomala, in quanto identificata dal secondo byte. Gli argomenti passati sono quelli precedentemente caricati nei registri '''Wx'''.
+
Esegue una funzione builtin (use).Il numero progressivo (partendo da 0) della funzione da eseguire, nell'ordine in cui è citata all'interno della sezione usage è indicato dal byte "type". Il byte "module" indica a quale blocco usage fa riferimento l'istruzione (00 solitamente è ''basic'', 01 ''basicio'' e 02 quello indicato dalla prima direttiva "use" del sorgente).
* byte 1: indica il numero progressivo (partendo da 0) della funzione da eseguire, nell'ordine in cui è citata all'interno del blocco usage
+
* byte 3-4: sempre 00 00
+
* byte 5: indica a quale blocco usage fa riferimento l'istruzione (00 solitamente è ''basic'', 01 ''basicio'' e 02 quello indicato dalla prima direttiva "use" del sorgente)
+
  
===istruzione 01: accesso a una variabile o costante (load)===
+
Per prima cosa recupera il numero di parametri indicato nella relativa dichiarazione dall'interno della sezione usage, consumandoli da W. Quindi aggiunge il risultato della funzione alla fine di W.
Sposta una variabile o costante in un registro temporaneo che chiameremo '''W1''' per essere successivamente utilizzata da una funzione. Se viene chiamata più volte, il registro utilizzato è il successivo (W2, W3, etc...): utile per funzioni che accettano più argomenti. I byte 2-4 rappresentano il puntatore alla variabile o costante.
+
 
 +
===istruzione 3B: invocazione di un metodo (method)===
 +
Per prima cosa, recupera il numero di parametri (Pn) indicato da "type", rimuovendoli uno ad uno dalla fine di W.
 +
Quindi recupera un ulteriore parametro R, consumandolo.
 +
Esegue il metodo R.const dove const è la costante stringa indicata da "offset".
 +
Aggiunge il risultato alla fine di W.
 +
 
 +
===istruzioni 23,41: recupero di un argomento per una user function (poparg)===
 +
"offset" indica il nome dell'argomento come puntatore stringa al blocco delle costanti.
 +
* 23: recupera l'argomento
 +
* 41: recupera l'argomento byRef
 +
 
 +
===istruzioni 00,01,02, 31, 33,34: accesso a una variabile o costante (load)===
 +
Copia una costante o una variabile aggiungendo un nuovo elemento alla fine di W.
 +
Se si tratta di una costante, "offset" è la posizione del valore all'interno del blocco costanti. Se invece si tratta di una variabile, "offset" indica l'ID della variabile. Il token è così differenziato:
 +
* 00: legge una costante interpretando il valore come intero
 +
* 01: legge una costante interpretando il valore come float
 +
* 02: legge una costante interpretando il valore come stringa
 +
* 31: legge la costante speciale ''error''
 +
* 33: legge una variabile locale
 +
* 34: legge una variabile globale
 +
 
 +
===istruzioni 04,05,06,07,08, 0d,0e,0f,10, 13,14, 1A,1B,1C 1E, 32, 42, 43, 44,45,46: assegnazione (assign)===
 +
Questa categoria di istruzioni prende due operatori dai registri '''W''' e li sostituisce con uno. Il tipo di operazione eseguito, l'operatore, cambia in base al token. I due operatori sono '''R''' (right), ovvero l'''ultimo'' elemento di W e ''L'' (left), ovvero il ''penultimo'' elemento di W. R viene rimosso mentre L viene sostituito con il ''risultato'' dell'operazione.
  
===istruzione 02: assegnazione (assign)===
 
 
L'istruzione cambia significato in base al valore del byte 2:
 
L'istruzione cambia significato in base al valore del byte 2:
* 08/42: Assegna il valore indicato dal registro W2 al registro W1 (W1 := W2). I byte 3-5 sono sempre 00. Sempre che sia usato 08 se l'assegnazione avviene contestualmente alla dichiarazione, altrimenti 42.
+
* 04: L = L + R
* 1E: Assegna al registro W1 la priorità dell'oggetto identificata da W1.W2
+
* 05: L = L - R
* 38: Assegna il nome della variabile al blocco program. I byte 3-5 sono un puntatore alla costante. L'ID della variabile usato è automaticamente il primo libero.
+
* 06: L = L * R
 +
* 07: L = L / R
 +
* 08: L = R
 +
* 0D: L = L < R
 +
* 0E: L = L <= R
 +
* 0F: L = L > R
 +
* 10: L = L >= R
 +
* 13: L = L == R
 +
* 14: L = L != R
 +
* 1A: L = L[R] (l'offset indica la posizione di accesso es per L[,R] sarà indicato un offset pari a 1)
 +
* 1B: L = L.+R
 +
* 1c: L = L.-R
 +
* 1E: L = L.R (legge la proprietà di un oggetto)
 +
* 32: L = L in R
 +
* 42: L = & R (assegnazione byRef)
 +
* 43: L = L % R
 +
* 44: L = L & R
 +
* 45: L = L | R
 +
* 46: L = L ^ R
  
===istruzione 03: pulizia dei registri (clear)===
+
===istruzioni 15,16,17: operatore unario (unary)===
Pulisce tutti i registri temporanei '''Wn'''. Si presenta sempre nella forma 03 19 00 00 00.
+
Questa categoria di istruzioni prende l'ultimo operatore dai registri '''W''' e lo sostituisce con un altro.
  
===istruzione 08 2A/2B: dichiarazione di una variabile (var)===
+
L'istruzione cambia significato in base al valore del byte 2:
Dichiara una variabile
+
* 15: R = + R
* byte 2: probabilmente identifica lo scope. 2B = globale, 2A = locale
+
* 16: R = - R
* byte 3-5: l'ID univoco della variabile
+
* 17: R = ! R
 +
 
 +
===istruzioni 35,36: loop foreach (foreach)===
 +
* 35: inizializza un loop foreach sull'ultimo valore di W, aggiungendo in coda a W l'iterator, l'indice e il riferimento all'ultimo valore (start)
 +
* 36: esegue il prossimo loop dell'iterator (step)
 +
 
 +
===istruzioni 3E,3F: loop for su di un range (for)===
 +
* 35: inizializza un loop for L to R, dove L ed R sono il penultimo e l'ultimo valore di W (start)
 +
* 36: esegue il prossimo loop dell'iterator (step)
 +
 
 +
===istruzione 37: switch statement (case)===
 +
Inizia un blocco "switch". Il bit "offset" punta al blocco delle costanti, dove è contenuta la vera definizione dell'istruzione.
 +
Vengono letti 3 bytes (2+1) che rappresentano la prima istruzione "case", così codificata:
 +
* bytes 1-2: contengono un unsigned short con il numero dell'istruzione a cui saltare all'interno del programma
 +
* byte 3: indica il tipo di parametro:
 +
** se < FE si tratta di un parametro di tipo stringa, questo byte ne indica anche la lunghezza
 +
** se FE si tratta di un case di tipo "default"
 +
** se FF si tratta di un parametro di tipo intero, la lunghezza è sempre 4
 +
i bytes successivi (4 per l'interno, N per la stringa) contengono il parametro dell'istruzione "case". Il caso "FE" (default) è sempre presente e chiude il blocco.
 +
 
 +
===istruzione 38: recupera argomento (getarg)===
 +
Recupera un argomento il blocco corrente. "offset" è un puntatore alla sezione costanti, dove è memorizzato il nome della variabile. L'ID della variabile usato è automaticamente il primo libero nel contesto locale.
 +
 
 +
===istruzioni 39,3a: definizione di un'array (array)===
 +
Queste istruzioni sono usate per la creazione di una nuova array
 +
* 3a: Crea una nuova array, aggiungendola in coda a W
 +
* 39: Rimuove l'ultimo valore di W e lo aggiunge all'array che si trova nel penultimo valore di W
 +
 
 +
===istruzione 47: definizione di una struttura (struct)===
 +
Crea una nuova struttura, aggiungendola in coda a W
 +
 
 +
===istruzione 3C: definizione di un dizionario (dict)===
 +
Crea un nuovo dizionario, aggiungendolo in coda a W
 +
 
 +
===istruzione 3D: definizione di uno stack (stack)===
 +
Crea un nuovo stack, aggiungendolo in coda a W
 +
 
 +
===istruzione 19: pulizia dell'ultimo registro (consume)===
 +
Rimuove l'ultimo elemento dall'array W senza utilizzarlo.
 +
 
 +
===istruzioni 2A/2B: dichiarazione di una variabile (var)===
 +
Dichiara una variabile e la carica nel prossimo registro (load). L'istruzione 2A dichiara una variabile locale mentre la 2B una globale. I bit "offset" indicano l'ID univoco della variabile.
 +
 
 +
===istruzione 2E: dichiarazione di un'array (vararr)===
 +
Segue sempre un'istruzione di tipo var e inizializza la variabile appena dichiarata come array
 +
 
 +
===istruzioni 25,26,27: salto (goto)===
 +
Salta alla posizione indicata da "offset". Se il salto è condizionale, salta solo dopo aver valutato l'ultimo valore nel registro W, consumandolo.
 +
* 25: salta se FALSE
 +
* 26: salta se TRUE
 +
* 27: salto incondizionato (non modifica i registri)
 +
 
 +
===istruzione 20: fine programma (progend)===
 +
Arresta l'esecuzione. È automaticamente inclusa dal compilatore alla fine della sezione istruzioni.
  
===istruzione 08 25/26: salto (goto)===
+
===istruzione 29: uscita (exit)===
Salta alla posizione indicata. Se il salto è condizionale, salta solo dopo aver valutato il valore nel registro registro W1
+
Arresta l'esecuzione.
* byte 2: 25 = salta se W1 è TRUE, 26 = salta se W1 è false, 27 = salto incondizionato
+
* byte 3-5: posizione a cui effettuare il salto
+
  
===istruzione 0F: return===
+
===istruzione 24: fine blocco (blockend)===
* I 5 byte 0F 20 00 00 00 rappresentano un return generico. Uno di questi è automaticamente incluso alla fine del blocco istruzioni
+
Rimuove il numero di variabili indicato da "offset" dalla fine dei registri W.
* I 5 byte 0F 24 02 00 00 rappresentano end_program
+
  
 
==Costanti - codice 03 00==
 
==Costanti - codice 03 00==
In questo blocco, l'ultimo del file, sono contenute le costanti usate all'interno del file stesso.
+
In questa sezione, solitamente l'ultima del file, sono contenute le costanti usate all'interno del file stesso.
  
L'inizio del blocco è indicato dai 6 bytes 03 00 XX XX XX XX: dove XX rappresenta la lunghezza del blocco, in formato intero: sembra sempre uguale all'intero indicato dai successivi 4 bytes + 4.
+
L'inizio della sezione è indicata dai 6 bytes 03 00 XX XX XX XX: dove XX rappresenta la lunghezza della sezione, in formato intero: sembra sempre uguale all'intero indicato dai successivi 4 bytes + 4.
  
 
I successivi 4 bytes rappresentano un intero che indica il numero di bytes da cui è composta la sezione delle costanti + 1 (ovvero indica 1 per 0 bytes, 2 per 1 byte, etc...).
 
I successivi 4 bytes rappresentano un intero che indica il numero di bytes da cui è composta la sezione delle costanti + 1 (ovvero indica 1 per 0 bytes, 2 per 1 byte, etc...).
  
Ad esempio, un blocco costanti contenente 4 bytes di dati puri inizierà con: 03 00 - 09 00 00 00 - 05 00 00 00.
+
Ad esempio, una sezione costanti contenente 4 bytes di dati puri inizierà con: 03 00 - 09 00 00 00 - 05 00 00 00.
  
 
Possono essere presenti i seguenti tipi di dati:
 
Possono essere presenti i seguenti tipi di dati:
Riga 102: Riga 205:
 
* interi
 
* interi
 
* float
 
* float
 +
 +
==Exports - codice 06 00==
 +
In questa sezione opzionale, raramente utilizzata e posta dopo al blocco delle costanti, è contenuta la lista delle funzioni esportate.
 +
 +
Per ciascuna funzione esportata, sono presenti 41 bytes, così suddivisi:
 +
* 1-33: il nome della funzione, come stringa (name)
 +
* 34-37: il numero di argomenti accettato dalla funzione, come interno (nargs)
 +
* 38-41: il numero di istruzione di inizio della funzione (pc)

Versione attuale delle 04:11, 26 gen 2016

Nella seguente documentazione si farà a volte riferimento ai sorgenti del POL. I sorgenti in quetsione sono i sorgenti del POL099 disponibili al seguente indirizzo: https://github.com/polserver/polserver. Nello specifico, il commit di riferimento alla data di redazione di questo documento è: 820790a6feedaa0462bfc3aa96faa739e08f0ecd.

I file pol-core/bscript/tokens.h e pol-core/bscript/executor.cpp contengono la maggior parte delle informazioni necessarie. L'apertura e la lettura del file iniziano in pol-core/bscript/eprog_read.cpp.

Codifica dei tipi

All'interno del file, sono condificati dati di più tipi primitivi a cui si farà riferimento successivamente. Le codifiche finora individuate sono:

  • stringa a lunghezza fissa: La lunghezza è indicata preventivamente all'interno del blocco che ne fa uso o altrove. È rappresentata da un numero fisso di bytes, dove quelli non utilizzati sono impostati a NULL.
  • stringa a lunghezza variabile: Si tratta di un numero variabile di bytes stringa di cui l'ultimo è sempre NULL e rappresenta la fine della stringa stessa
  • numero intero: gli interi sono codificati sempre come 4 bytes, ordinati dal meno al più significativo (quindi invertiti, da un punto di vista algebrico). Sono sempre signed: il valore massimo per un intero è 0x7FFFFFFF, rappresentato come FF FF FF 7F. I valori negativi sono rappresentati partendo da FF FF FF FF che significa -1 e poi sottraendo la rappresentazione del valore come se fosse (intero - 1); ad esempio -255 è rappresentato come 01 FF FF FF.
  • numeri a virgola mobile (float): i numeri a virgola mobile sono codificati sempre come 8 bytes (da chiarire in che formato, probabilmente 4 bytes base + 4 bytes esponente)

Header

Ogni file inizia con un header di 6 bytes così definito:

  • byte 1-2: rappresentano il magic number del formato ("CE")
  • byte 3-4: rappresentano il numero di versione (02 00 per POL093)
  • byte 5-6: contengono il numero di variabili globali utilizzate

Struttura

Successivamente all'header, il file ECL sembra avere una struttura a sezioni. Di seguito sono elencati quelli noti, riportati nello stesso ordine in cui compariranno all'interno del file.

Ciascuna sezione inizia con 6 (2+4) bytes. Di questi 6 bytes, i primi 2 rappresentano il codice dela sezione, mentre i successivi 4 rappresentano, sotto forma di intero, la lunghezza della sezione, in bytes, non conteggiando i 6 bytes dell'intestazione stessa.

Fa eccezione il codice 01 00 per cui la lunghezza viene indicata sempre come 0.

Program - codice 04 00

Questa sezione definisce la direttiva program.

Non è sempre presente: ad esempio, non è usato nello script generale di startup start.ecl.

I primi 6 bytes sono 04 00 - 10 00 00 00, seguono 16 bytes di cui solo il primo sembra essere usato ed indica il numero di argomenti accettati dalla sezione program (01, 02, etc...).

Gli altri bytes sono sempre lasciati a NULL.

Usages (Module) - codice 01 00

I file .em contenenti le funzioni di sistema importati con la direttiva use sono specificate immediatamente dopo l'header. Ogni blocco use ha una dimensione variabile.

I primi 6 bytes sono 01 00 00 00 00 00

Nella versione 2 (POL093), segue un blocco di 13 bytes così suddiviso:

  • bytes 1-9: contengono il nome dell'usage sotto forma di stringa a lunghezza fissa
  • byte 10-13: indica il numero di funzioni utilizzate da questo file

Nella versione 12 (POL099), segue un blocco di 18 bytes così suddiviso:

  • bytes 1-14: contengono il nome dell'usage sotto forma di stringa a lunghezza fissa
  • bytes 15-18: indica il numero di funzioni utilizzate da questo file

Segue la lista delle funzioni utilizzate da questo file, sotto forma 34 bytes ripetuti per il numero di volte indicato dai byte 10-13 o 15-18. Di questi 34 bytes, i primi 33 rappresentano una stringa a lunghezza fissa contenente il nome della funzione, mentre l'ultimo byte indica il numero di parametri accettati dalla funzione stessa.

"basic" e "basicio" sembrano essere sempre i primi due usages e sono inclusi implicitamente, e ribaditi quindi nel file ECL, anche se non dichiarati nello script sorgente.

Istruzioni - codice 02 00

ATTENZIONE!!! È probabile che questa sezione contenga ancora molti errori o imprecisioni

La sezione segue la struttura sopra citata: nell'intestazione è indicata la lunghezza della sezione in byte. Segue un intero che indica nuovamente la lunghezza della sezione meno i 4 byte che compongono l'intero stesso, per un totale di 10 byte di intestazione.

I successivi byte rappresentano le istruzioni stesse e sono sempre un numero di byte divisibile per 5.

Un'istruzione è composta da 5 byte, così definiti (pol-core/bscript/symcont.h:30):

  • byte 1 - type: sebbene sia dichiarato come instruction type ed abbia una serie di valori predefiniti (pol-core/bscript/tokens.h:14), si ritiene che il suo significato originale sia andato perso. La maggior parte delle volte il suo valore può essere ignorato, mentre in alcuni casi speciali si limita a contenere degli argomenti
  • byte 2 - id: identifica il tipo di istruzione e la sua funzione (pol-core/bscript/tokens.h:47)
  • byte 3-4 - offset: contengono il primo parametro per l'istruzione, solitamente un puntatore a un indirizzo all'interno del blocco costanti, l'ID di una variabile, o una posizione dall'interno del programma (goto)
  • byte 5 - module: contengono il secondo parametro per l'istruzione. Non viene usato spesso.

registri

Sono presenti un numero indefinito di registri W, utilizzati come argomenti per le chiamate alle funzioni builtin. Tali registri sono rappresentati come un'array o lista di registri. Nell'implementazione POL è usata una deque (http://it.cppreference.com/w/cpp/container/deque).

elementi

All'interno di una istruzione, si possono trovare uno o più dei seguenti elementi che si ripetono, tutti a lunghezza fissa:

  • intero3 (3 byte): Si tratta di un intero che ha lo stesso formato dell'intero definito precedentemente, ma manca del byte più significativo, che si assume essere sempre 00.
  • puntatore (4 byte):
    • byte 1: Il tipo di argomento: 00 = intero, 01 = float, 02 = stringa, 33 = variabile locale, 34 = variabile globale
    • byte 2-4: Se il byte precedente indica una costante, è l'offset all'interno della sezione delle costanti (intestazione esclusa): indica la posizione in byte dell'argomento da leggere. È da notare che le stringhe possono non essere lette dall'inizio: ad esempio, una stringa "abc" può essere usata come stringa "bc" semplicemente puntando al secondo carattere anziché all'inizio della stringa. Nel caso di una variabile, è il suo ID univoco. Rappresentato come intero3.

istruzione 2F: run

Esegue una funzione builtin (use).Il numero progressivo (partendo da 0) della funzione da eseguire, nell'ordine in cui è citata all'interno della sezione usage è indicato dal byte "type". Il byte "module" indica a quale blocco usage fa riferimento l'istruzione (00 solitamente è basic, 01 basicio e 02 quello indicato dalla prima direttiva "use" del sorgente).

Per prima cosa recupera il numero di parametri indicato nella relativa dichiarazione dall'interno della sezione usage, consumandoli da W. Quindi aggiunge il risultato della funzione alla fine di W.

istruzione 3B: invocazione di un metodo (method)

Per prima cosa, recupera il numero di parametri (Pn) indicato da "type", rimuovendoli uno ad uno dalla fine di W. Quindi recupera un ulteriore parametro R, consumandolo. Esegue il metodo R.const dove const è la costante stringa indicata da "offset". Aggiunge il risultato alla fine di W.

istruzioni 23,41: recupero di un argomento per una user function (poparg)

"offset" indica il nome dell'argomento come puntatore stringa al blocco delle costanti.

  • 23: recupera l'argomento
  • 41: recupera l'argomento byRef

istruzioni 00,01,02, 31, 33,34: accesso a una variabile o costante (load)

Copia una costante o una variabile aggiungendo un nuovo elemento alla fine di W. Se si tratta di una costante, "offset" è la posizione del valore all'interno del blocco costanti. Se invece si tratta di una variabile, "offset" indica l'ID della variabile. Il token è così differenziato:

  • 00: legge una costante interpretando il valore come intero
  • 01: legge una costante interpretando il valore come float
  • 02: legge una costante interpretando il valore come stringa
  • 31: legge la costante speciale error
  • 33: legge una variabile locale
  • 34: legge una variabile globale

istruzioni 04,05,06,07,08, 0d,0e,0f,10, 13,14, 1A,1B,1C 1E, 32, 42, 43, 44,45,46: assegnazione (assign)

Questa categoria di istruzioni prende due operatori dai registri W e li sostituisce con uno. Il tipo di operazione eseguito, l'operatore, cambia in base al token. I due operatori sono R (right), ovvero l'ultimo elemento di W e L (left), ovvero il penultimo elemento di W. R viene rimosso mentre L viene sostituito con il risultato dell'operazione.

L'istruzione cambia significato in base al valore del byte 2:

  • 04: L = L + R
  • 05: L = L - R
  • 06: L = L * R
  • 07: L = L / R
  • 08: L = R
  • 0D: L = L < R
  • 0E: L = L <= R
  • 0F: L = L > R
  • 10: L = L >= R
  • 13: L = L == R
  • 14: L = L != R
  • 1A: L = L[R] (l'offset indica la posizione di accesso es per L[,R] sarà indicato un offset pari a 1)
  • 1B: L = L.+R
  • 1c: L = L.-R
  • 1E: L = L.R (legge la proprietà di un oggetto)
  • 32: L = L in R
  • 42: L = & R (assegnazione byRef)
  • 43: L = L % R
  • 44: L = L & R
  • 45: L = L | R
  • 46: L = L ^ R

istruzioni 15,16,17: operatore unario (unary)

Questa categoria di istruzioni prende l'ultimo operatore dai registri W e lo sostituisce con un altro.

L'istruzione cambia significato in base al valore del byte 2:

  • 15: R = + R
  • 16: R = - R
  • 17: R = ! R

istruzioni 35,36: loop foreach (foreach)

  • 35: inizializza un loop foreach sull'ultimo valore di W, aggiungendo in coda a W l'iterator, l'indice e il riferimento all'ultimo valore (start)
  • 36: esegue il prossimo loop dell'iterator (step)

istruzioni 3E,3F: loop for su di un range (for)

  • 35: inizializza un loop for L to R, dove L ed R sono il penultimo e l'ultimo valore di W (start)
  • 36: esegue il prossimo loop dell'iterator (step)

istruzione 37: switch statement (case)

Inizia un blocco "switch". Il bit "offset" punta al blocco delle costanti, dove è contenuta la vera definizione dell'istruzione. Vengono letti 3 bytes (2+1) che rappresentano la prima istruzione "case", così codificata:

  • bytes 1-2: contengono un unsigned short con il numero dell'istruzione a cui saltare all'interno del programma
  • byte 3: indica il tipo di parametro:
    • se < FE si tratta di un parametro di tipo stringa, questo byte ne indica anche la lunghezza
    • se FE si tratta di un case di tipo "default"
    • se FF si tratta di un parametro di tipo intero, la lunghezza è sempre 4

i bytes successivi (4 per l'interno, N per la stringa) contengono il parametro dell'istruzione "case". Il caso "FE" (default) è sempre presente e chiude il blocco.

istruzione 38: recupera argomento (getarg)

Recupera un argomento il blocco corrente. "offset" è un puntatore alla sezione costanti, dove è memorizzato il nome della variabile. L'ID della variabile usato è automaticamente il primo libero nel contesto locale.

istruzioni 39,3a: definizione di un'array (array)

Queste istruzioni sono usate per la creazione di una nuova array

  • 3a: Crea una nuova array, aggiungendola in coda a W
  • 39: Rimuove l'ultimo valore di W e lo aggiunge all'array che si trova nel penultimo valore di W

istruzione 47: definizione di una struttura (struct)

Crea una nuova struttura, aggiungendola in coda a W

istruzione 3C: definizione di un dizionario (dict)

Crea un nuovo dizionario, aggiungendolo in coda a W

istruzione 3D: definizione di uno stack (stack)

Crea un nuovo stack, aggiungendolo in coda a W

istruzione 19: pulizia dell'ultimo registro (consume)

Rimuove l'ultimo elemento dall'array W senza utilizzarlo.

istruzioni 2A/2B: dichiarazione di una variabile (var)

Dichiara una variabile e la carica nel prossimo registro (load). L'istruzione 2A dichiara una variabile locale mentre la 2B una globale. I bit "offset" indicano l'ID univoco della variabile.

istruzione 2E: dichiarazione di un'array (vararr)

Segue sempre un'istruzione di tipo var e inizializza la variabile appena dichiarata come array

istruzioni 25,26,27: salto (goto)

Salta alla posizione indicata da "offset". Se il salto è condizionale, salta solo dopo aver valutato l'ultimo valore nel registro W, consumandolo.

  • 25: salta se FALSE
  • 26: salta se TRUE
  • 27: salto incondizionato (non modifica i registri)

istruzione 20: fine programma (progend)

Arresta l'esecuzione. È automaticamente inclusa dal compilatore alla fine della sezione istruzioni.

istruzione 29: uscita (exit)

Arresta l'esecuzione.

istruzione 24: fine blocco (blockend)

Rimuove il numero di variabili indicato da "offset" dalla fine dei registri W.

Costanti - codice 03 00

In questa sezione, solitamente l'ultima del file, sono contenute le costanti usate all'interno del file stesso.

L'inizio della sezione è indicata dai 6 bytes 03 00 XX XX XX XX: dove XX rappresenta la lunghezza della sezione, in formato intero: sembra sempre uguale all'intero indicato dai successivi 4 bytes + 4.

I successivi 4 bytes rappresentano un intero che indica il numero di bytes da cui è composta la sezione delle costanti + 1 (ovvero indica 1 per 0 bytes, 2 per 1 byte, etc...).

Ad esempio, una sezione costanti contenente 4 bytes di dati puri inizierà con: 03 00 - 09 00 00 00 - 05 00 00 00.

Possono essere presenti i seguenti tipi di dati:

  • stringhe a lunghezza variabile
  • interi
  • float

Exports - codice 06 00

In questa sezione opzionale, raramente utilizzata e posta dopo al blocco delle costanti, è contenuta la lista delle funzioni esportate.

Per ciascuna funzione esportata, sono presenti 41 bytes, così suddivisi:

  • 1-33: il nome della funzione, come stringa (name)
  • 34-37: il numero di argomenti accettato dalla funzione, come interno (nargs)
  • 38-41: il numero di istruzione di inizio della funzione (pc)