OOB: Objektumok bash parancsértelmezőben

Konstruktor

A konstruktor néhány parancs összessége, amelyek célszerű használatával új prototípust és osztályt alakíthatunk ki. A parancsoknak meg kell valósítaniuk az öröklődést, a tagok kezelését és az osztály lezárását, azaz megvédését a további módosításoktól.

Az első lépés a leendő osztály üres leíróinak elkészítése. A konstruktor parancsai ezeket töltik fel az adattagok és a metódusok létrehozásához szükséges információkkal. Amint létrehozunk akár csak egyetlen objektumot is ezekből a leírókból, a leíróknak le kell záródniuk.

Új leírók készítése

Megírjuk az új leírók elkészítését végző segédfüggvényt a basic.oob (verzió: 0.1.1) fájlba:

oob_newDescriptors() {  # result: new descriptors and pointers to descriptors
 dclass="${EPOCHREALTIME/,/_}"; declare -gA "dc_${dclass}_d=()" "dc_${dclass}_m=()"  # create new class number and descriptors
 declare -gn dc_new_d=dc_${dclass}_d dc_new_m=dc_${dclass}_m  # create pointers of descriptors
}  # oob_newDescriptors()
oob_newDescriptors  # create new empty class descriptors just in start

A segédfüggvény létrehozza az új osztály azonosítóját, amely a létrehozás időpontját tartalmazza nanomásodperc pontossággal. Így az osztály azonosítója már nem egy sorszám, hanem az időbélyeg lesz, amely nagyon nagy valószínűséggel egyedi osztályazonosítót eredményez a Földön. A basic.oob fájl betöltésekor azonnal létrehozzuk az üres oszályleírókat, hogy ezzel ne kelljen a felhasználónak foglalkoznia.

Példányosítás

Ha kialakítottuk az osztályt, létrehozhatjuk belőle az új objektumainkat. A parancsot a basic.oob (verzió: 0.1.2) fájlba írjuk:

oob_new() {  # input: name of new objects in same class  # create new objects and new empty proto descriptors
 local i ref
 for i in ${!dc_new_d[@]}; do ref+=" self_$i=\${self}_$i"; done  # collect reference
 [[ -n $ref ]] && ref="local -n$ref;"; ref="local self=\${FUNCNAME%_*}; $ref"  # set reference commands
 for i in ${!dc_new_m[@]}; do dc_new_m[$i]="${dc_new_m[$i]/\{/\{ $ref :; }"; done  # prepare methods
 for i; do oob_cpOneObject $i class; done  # create new objects
 oob_newDescriptors  # create new empty dclass descriptors after close the previous
}  # oob_new()

A parancs először beolvassa az adattagok neveit és összeállítja belőlük a referenciákat tartalmazó parancsokat a ref helyi változóba. Majd a változó tartalmát az elválasztó üres utasítással együtt befűzi a metódusok elejére. Ezután az argumentumokat az új objektumok neveiként értelmezve darabonként meghívja az oob_cpOneObject utasítást, hogy az osztályból, mint mintából létrehozza az új objektumokat. Mivel az oob_cpOneObject nem ellenőrzi, hogy a minta objektum-e, így ugyanúgy megszerzi az osztály azonosítóját, mint egy objektumból. A parancs végül meghívja az új osztályt létrehozó utasítást, ezzel le is zárja az osztályt. A továbbiakban az osztály azonosítóját már csak az osztályhoz tartozó objektumok tárolják. Így, ha az osztályhoz tartozó összes objektumot töröljük, (egyszerű módszerekkel) már nem férünk hozzá az osztályhoz.

Öröklődés

Az OOB megengedi a többszörös öröklődést, sőt, a minták használatával teljes struktúrákat másolhatunk egyetlen osztályba. Az esetleges névütközésket nem kezeli, tehát az újabb öröklődés felülírhatja a már meglévő adatokat. Az örökléődés technikája egyszerű: be kell másolni az ős osztály leíróit az új osztály leíróiba. A metódusok esetén a referenciákat tartalmazó utasításokat el kell távolítani. Az objektumok minden metódusába beleírt :; utasítás, mint szakaszhatár megvédi a metódus tényleges utasításait a törléstől. Az öröklődés parancsát a basic.oob (verzió: 0.1.3) fájlba írjuk:

oob_parents() {  # input: pattern of objects  # copy objects into class descriptors
 local i dobj t; local -a oob_list
 for i; do  # one pattern
  oob_filter $i || continue  # get objects
  for dobj in ${oob_list[@]}; do  # one object
   local -n dc_parent_d=dc_${!dobj#d}_d dc_parent_m=dc_${!dobj#d}_m  # get descriptors the parent object
   for t in ${!dc_parent_d[@]}; do dc_new_d[$t]="${dc_parent_d[$t]}"; done  # inherit data
   for t in ${!dc_parent_m[@]}; do dc_new_m[$t]="{ ${dc_parent_m[$t]#*:;}"; done  # inherit methods
  done  # one object
 done  # one pattern
}  # oob_parents()

A parancs az argumentumokat az oob_filter számára átadandó mintaként értelmezi. Az oob_list tömb elemeihez tartozó leírókat beolvassa és hozzáadja az új osztályleírókhoz.

A develop.sh fájlt ismét kicseréljük:

#!/usr/bin/env oob

## objects
 do_starX=1 # do_star_planet4=1 do_star_planet4_moon1=1 do_star_planet4_moon2=1 

# attributes
  declare -gA o_starX_info=([name]=Sun )

# methods
  o_starX_getName() { local self=${FUNCNAME%_*}; local -n self_info=${self}_info; :; echo "${self_info[name]}"; }
  o_starX_sayHello() { local self=${FUNCNAME%_*}; local -n self_info=${self}_info; :; echo "Hello $(${self}_getName)!"; }

## classes
 declare -gA dc_1_d=( [info]="-gA" )
 declare -gA dc_1_m=(
  [sayHello]='{ local self=${FUNCNAME%_*}; local -n self_info=${self}_info; :; echo "Hello $(${self}_getName)"; }'
  [getName]='{ local self=${FUNCNAME%_*}; local -n self_info=${self}_info; :; echo "${self_info[name]}"; }'
 )

## run
 main() {
  oob_parents o_starX
  oob_new o_star o_star_planet4 o_star_planet4_moon1 o_star_planet4_moon2
  o_star_planet4_moon1_info=( [name]=Phobos )
  oob_code > $HOME/user-objects
 }
 : set -x; main "$@"; set +x

Tagok kezelése

A létező objektumok osztályainak összemásolásával tudunk kombinált osztályokat és azokból új objektumokat létrehozni. Még meg kell valósítsuk a felesleges tagok törlését és az újak létrehozását. A következő parancsok egy-egy műveletet végeznek az adattagok vagy a metódusok megadott sorozatán. A parancsok első argumentuma kötelezően -d vagy -m, attól függően, hogy adattagokkal vagy metódusokkal végzünk műveletet.

Tagok törlése

Szükség lehet az öröklődéssel feleslegesen bekerült tagok törlésére. A parancs a basic.oob (verzió: 0.1.4) fájlban:

oob_rmTags() {  # input: -d|-m name of tags  # remove data or method tags from class descriptors
 local i mode="$1"; shift
 case $mode in  # select data or methods
  -d) for i; do unset dc_new_d[$i]; done;;  # remove data tags into descriptor
  -m) for i; do unset dc_new_m[$i]; done;;  # remove method tags into descriptor
  *) oob_trap "Bad option:" "$mode"; return 1;;
 esac  # select data or methods
}  # oob_rmTags()

A teszteléshez átírjuk a develop.sh fájl ## run szakaszát:

## run
 main() {
  oob_parents o_starX; oob_rmTags $@
  oob_new o_star o_star_planet4 o_star_planet4_moon1 o_star_planet4_moon2
  o_star_planet4_moon1_info=( [name]=Phobos )
  oob_code > $HOME/user-objects
 }
 : set -x; main "$@"; set +x

Tagok másolása

Célszerű néha egy-egy örökölt tagot átnevezni. Mivel tagot törölni már tudunk, csak a tagok másolását kell megírnunk. Az ezt végző parancs argumentuma (az első kivételével) <új tag>=<tag> alakú. A parancs a basic.oob (verzió: 0.1.5) fájlban:

oob_cpTags() {  # input: -d|-m new-tag=tag...  # copy data or method tags in class descriptors
 local i mode="$1"; shift
 case $mode in  # select data or methods
  -d) for i; do dc_new_d[${i%%=*}]="${dc_new_d[${i#*=}]:-"-g"}"; done;;  # copy data tags into descriptor
  -m) for i; do dc_new_m[${i%%=*}]="${dc_new_m[${i#*=}]:-"{ :; }"}"; done;;  # copy method tags into descriptor
  *) oob_trap "Bad option:" "$mode"; return 1;;
 esac  # select data or methods
}  # oob_cpTags()

Ha a másolandó tag nem létezik, akkor is létrejön az új tag. Ha adattag, akkor mint egyszerű változó, ha metódus, akkor egy semmit sem végző függvényként. Ehhez a bash :- operátorát használjuk, amely a változó értéket helyettesíti a mögötte álló kifejezéssel, ha a változó nem létezik, vagy üres.

A teszteléshez átírjuk a develop.sh fájl ## run szakaszát:

## run
 main() {
  oob_parents o_starX; oob_cpTags $1 temp=$2; oob_rmTags $@; oob_cpTags $@=temp
  oob_new o_star o_star_planet4 o_star_planet4_moon1 o_star_planet4_moon2
  o_star_planet4_moon1_info=( [name]=Phobos )
  oob_code > $HOME/user-objects
 }
 : set -x; main "$@"; set +x

Tagok létrehozása

Szükségünk van teljesen új tagok létrehozására is. Ennek argumentumai (az első kivételével) <név>='<érték>' alakúak. Adattag esetén az érték az adattag típusa. Ügyeljünk rá, hogy ez a -g karakterekkel kezdődjön! Ez biztosítja, hogy az adattag globális legyen. Metódus esetén az érték a metódus végrehajtható kódja a kapcsos zázójelekkel együtt. Itt különösen fontos, hogy a kód aposztrófok között legyen, így védjük a véletlen behelyettesítéstől. Ha a kód belseje aposztrófot tartalmaz, akkor helyette a \x27 karaktersorozatot kell használni. Írjuk meg a parancsot a basic.oob (verzió: 0.1.6) fájlba:

oob_setTags() {  # input: -d|-m name of tags & value  # create new data or method tags in class descriptors
 local i mode="$1"; shift
 case $mode in  # select data or methods
  -d) for i; do dc_new_d+=( [${i%%=*}]="${i#*=}" ); done;;  # create data tags into descriptor
  -m) for i; do dc_new_m+=( [${i%%=*}]="${i#*=}" ); done;;  # create method tags into descriptor
  *) oob_trap "Bad option:" "$mode"; return 1;;
 esac  # select data or methods
}  # oob_setTags()

A develop.sh fájl most már nem tartalmaz előre megírt objektumokat és osztályokat, minden objektumot és szerepet az OOB parancsaival hozunk létre:

#!/usr/bin/env oob

## run
 main() {
  oob_setTags -d info="-gA"
  oob_setTags -m sayHello='{ echo "Hello $(${self}_getName)!"; }' getName='{ echo "${self_info[name]}"; }'
  oob_new o_star o_star_planet4 o_star_planet4_moon1 o_star_planet4_moon2
  o_star_planet4_moon1_info=( [name]=Phobos )
  oob_code > $HOME/user-objects
 }
 : set -x; main "$@"; set +x

Létrehoztuk a használható OOB rendszert. Itt az ideje tehát, hogy a basic.oob verziószámát felemeljük a fejlesztési szintről a használható szintre. Így a basic.oob fejléce a következő lesz:

#
# OOB internal
# basic.oob, version 1.0.0
# https://oob.xport.hu
# basic commands for Object Oriented Bash
1 2 3 4 5 6 7 8 9