mercoledì, marzo 26, 2008

OOP in VimScript

Vim è uno dei (due) editor di testo più potenti che esistano, e come il suo concorrente (Emacs) può essere programmato in (almeno) un linguaggio di scripting apposito: il VimScript (in Emacs si usa l'elisp). Il VimScript è un linguaggio molto potente, articolato, con strutture dati flessibili (simili alle strututre dati di Python), tra le quali i dizioniari. I dizioniari, utilizzati in maniera particolare, consentono la costruzione di funzioni in cui sia possibile accedere al loro contentuo, una sorta di self pointer. Buttiamo lì un'identità di cui discuterò con voi volentieri:
strutture+function_handler+self=object orientation
Vediamo come questa cosa possa essere vera in VimScript: per prima cosa vi esorto a scaricare questo archivio con all'interno del codice VimScript, fra cui quello che ho scritto per l'OOP. In secundis aprite ed osservate il file "OO.vim" presente nella cartella "VimPresentation\Automation\Scripting". In esso:
  • BaseClass: salva in una variabile globale la classe corrente, costruita come dizionario con l'entry speciale "__name" contenente i nomi delle classi da cui l'oggetto eredita (per ora solo la corrente) e l'array (ora vuoto) delle interfacce implementate. Successivamente lo restituisce.
  • Interface: nefinisce una struttura simile a quella di una classe ma senza array delle interfacce.
  • Hinerit: l'ereditatrietà è gestita all'interno del costruttore chiamando questa funzione, qui viene creata l'istanza della superclasse, viene poi aggiunto ai nomi delle classi da cui si eredita il nome della sottoclasse, sarà poi compito del costruttore terminare la propria opera aggingendo nuovi attributi e svolgendo altri compiti di inizializzazione.
  • Virtual: i metodi virtuali non sono altro che metodi che vengono (nella sottoclasse, non nella superclasse come in C++) salvati all'interno della strututra d'istanza sotto nuovo nome tipo "superclasse_metodo" e che quindi vengono sostituiti nella sottoclasse.
  • PureVirtual: un metodo virtuale puro invece è un metodo che non è ancora stato implementato, se ne genera quindi una versione dummy ( che genera solo un'eccezione) e si utilizza la stessa tecnica dei metodi virtuali.
  • Implements: l'implementazione delle interfacce significa due cose:
    1. l'interfaccia (il suo nome) deve figurare tra le interfacce implementate,
    2. tutti i metodi virtuali puri presenti nell'interfaccia devono essere in quelche modo "importati" all'interno della classe corrente (starà poi al programmatore implementarli).
  • Isa: usando i nomi da dizionario si può sapere se una istanza è (da qualche parte nell'albero d'ereditarietà) istanza di una determinata classe.
  • IsImplemented: in modo analogo si può controllare se un istanza implementa una determinata interfaccia.
  • Import: un po' di comodità non guasta! ;)
Detto questo, sperando che abbiate contemporaneamente visionato il (semplicissimo) codice in VimScript che definisce queste funzioni, vediamo come utilizzarle per definire una classe, il classico contatore. Riporto e commento il file "Counter.vim":
let s:_default = 42
All'interno di uno script le variabili nello scope s: sono locali allo script stesso, per una classe questo significa avere a disposizione uno strumento per gli attributi statici.
function! Counter(...)
...
endfunction
Al solito definiamo una classe come funzione con funzioni innestate, la funzione di suo farà da costruttore. In fondo si può trovare il codice:
function! Counter_compare(a,b)
" Contronto actual:
return a:a._actual>a:b._actual
endfunction
che definisce una funzione friend. Il costruttore svolge le seguenti azioni:
if a:0<1
let initial = s:_default
else
let initial = a:1
end
let counter = BaseClass('Counter') | Implements ICounter
call extend(counter,{'_actual': initial})
che rappresentano:
  1. inizializzazione dell'attributo "initial" come variabile (per ora) locale,
  2. creazione dell'istanza con implementazione dell'interfaccia ICounter,
  3. agginta dell'attributo privato "initial".
Vedremo dopo come è stata definita l'interfaccia, ovviamente la sintassi deriva dal fatto che è stato creato un comando apposito per la definizione delle interfacce implementate, che richiama la funzione omonima.
A questo punto vengono aggiunti i metodi (eventualmente dichiarati virtuali se devono sovrascrivere i metodi di una superclasse o di un'interfaccia):
Virtual actual
function! counter.actual() dict
return self._actual
endfunction
E la classe è pronta! ;) Si osservi che i metodi vengono dichiarati con la speciale sintassi:
funciton! struttura.nome(...) dict
...
endfunction
dove la parola chiave "dict" assicura che all'interno del metodo si possa fare riferimento al dizionario "counter" con la parola chiave self. Si noti anche che il nome della funzione inizia con il nome dell'istanza che si sta costruento e che verrà restituita al termine del costruttore, infatti si sta semplicemente dichiarando un funciotn_handler di nome (in questo esempio) "actual" all'interno del dizionario "counter" richiedendo di generare il riferimento locale "self" con la parola chiave "dict".
Nel file "StepCounter.vim" si può osservare che l'ereditarietà è ottenuta semplicemente sostituendo la chiamata a "BaseClass" con la chiamata a "Hinerit" passando anche il nome della superclasse e la lista degli argomenti.
Con la definizione dei comandi si è resa semplicissima la dichiarazione delle interfacce:
fun! ICounter()
let counter = Interface('ICounter')
Abstract actual next prev set
return counter
endfun

Infatti:
  1. Si crea l'interfaccia in una fuzione "costruttore",
  2. si crea "l'istanza" con "Interface",
  3. si creano i metodi virtuali puri col comando "Abstract",
  4. si restituisce l'interfaccia.

Nessun commento: