8. Características avanzadas¶
8.1 Modo estricto
¶
Berry permite total libertad del desarrollador. Pero después de un poco
de experiencia en la codificación con Berry, encontrará que hay errores
comunes que son difíciles de encontrar y que el compilador podría
ayudarlo a detectar. El modo estricto
realiza verificaciones
adicionales en tiempo de compilación sobre algunos errores comunes.
Este modo está habilitado con import strict
o cuando se ejecuta
Berry con la opción -s
: berry -s
var
obligatorio para variables locales¶
Este es el error más común, una variable asignada sin var
es global
si ya existe una variable global o local en caso contrario. El modo
estricto rechaza la asignación si no hay un global con el mismo nombre.
No más permitido:
def f()
i = 0 # this is a local variable
var j = 0
end
Pero todavía funciona para globales:
g_i = 0
def f()
g_i = 1
end
Sin anulación de elementos integrados¶
Berry permite anular una función incorporada. Sin embargo, esto generalmente no es deseable y es una fuente de errores difíciles de encontrar.
map = 1
syntax_error: stdin:1: estricto: redefinición de 'map' incorporado
Múltiples var
con el mismo nombre no permitidos en el mismo ámbito¶
Berry toleraba la declaración múltiple de una variable local con el mismo nombre. Esto ahora se considera como un error (incluso sin modo estricto).
def f()
var a
var a # redefinición de a
end
syntax_error: stdin:3: redefinición de 'a'
No ocultar la variable local del alcance externo¶
En Berry puedes declarar variables locales con el mismo nombre en el ámbito interno. La variable en el ámbito interno oculta la variable del ámbito externo durante la duración del ámbito.
La única excepción son las variables que comienzan con el punto ‘.’ que
se pueden enmascarar desde el alcance externo. Este es el caso de la
variable local oculta .it
cuando se incrustan múltiples for
.
def f()
var a # variable en el ámbito externo
if a
var a # redefinición de a en ámbito interno
end
end
syntax_error: stdin:4: estricto: redefinición de 'a' desde el ámbito externo
8.2 Miembros virtuales¶
Los miembros virtuales le permiten agregar de forma dinámica y programática miembros y métodos a clases y módulos. Ya no está limitado a los miembros declarados en el momento de la creación.
Esta función está inspirada en __getattr__()
/ __setattr__()
de
Python. La motivación proviene de la integración de LVGL a Berry en
Tasmota. La integración necesita cientos de constantes en un módulo y
miles de métodos asignados a funciones C. La creación estática de
atributos y métodos funciona, pero consume una cantidad significativa de
espacio de código.
Esta característica permite crear dos métodos:
Mét odo Be rry |
Descripción |
---|---|
` me mbe r` |
|
` se tme mbe r` |
|
Módulo undefined
¶
La función member()
debe ser capaz de distinguir entre un miembro
con un valor nil
y el miembro que no existe. Para evitar cualquier
ambigüedad, la función member()
puede indicar que el miembro no
existe de dos maneras:
generar una excepción - o
import undefined
y devolver el móduloundefined
. Esto se usa como un marcador para que la VM sepa que el atributo no existe, mientras se beneficia de excepciones consistentes.
Ejemplo de un objeto dinámico al que puede agregar miembros, pero devolvería un error si el miembro no se agregó previamente.
class dyn
var _attr
def init()
self._attr = {}
end
def setmember(nombre, valor)
self._attr[nombre] = valor
end
def member(nombre)
if self._attr.contains(nombre)
return self._attr[nombre]
else
import undefined
return undefined
end
end
end
Ejemplo de uso:
a = dyn()
a.a
attribute_error: el objeto ‘dyn’ no tiene el atributo ‘a’ stack traceback: stdin:1: en función main
a.a = 1 a.a
1
a.a = nil a.a
Llamada implícita de member()
¶
Cuando se ejecuta el siguiente código a.b
, Berry VM hace lo
siguiente:
Obtiene el objeto llamado
a
(local o global), genera una excepción si no existeComprueba si el objeto
a
es de tipomódulo
,instancia
oclase
. Genera una excepción de lo contrarioComprueba si el objeto
a
tiene un miembro llamadob
. En caso afirmativo, devuelve su valor, en caso negativo, procede a continuaciónSi el objeto
a
es del tipoclase
, genera una excepción porque los miembros virtuales no funcionan para métodos estáticos (clase)Comprueba si el objeto
a
tiene un miembro llamadomember
y es unafunción
. En caso afirmativo, lo llama con el parámetro"b"
como cadena. Si no, genera una excepciónComprueba el valor de retorno. Si es el módulo
undefined
genera una excepción que indica que el miembro no existe
Llamada implícita de setmember()
¶
Cuando se ejecuta el siguiente código ab = 0
(mutador), Berry VM
hace lo siguiente:
Obtiene el objeto llamado
a
(local o global), genera una excepción si no existeComprueba si el objeto
a
es de tipomódulo
,instancia
oclase
. Genera una excepción de lo contrarioSi
a
es del tipoclase
, comprueba si existe el miembrob
. En caso afirmativo, cambia su valor. Si no, genera una excepción. (los miembros virtuales no funcionan para clases o métodos estáticos)Si
a
es del tipoinstancia
, comprueba si existe el miembrob
. En caso afirmativo, cambia su valor. Si no, procede a continuaciónComprueba si
a
tiene un miembro llamadosetmember
. Si es así, lo llama, si no, genera una excepción.
Si
a
es de tipomódulo
. Si el módulo no es de solo lectura, crea o cambia el valor (setmember
nunca se llama para un módulo de escritura). Si el módulo es de solo lectura, entonces se llama asetmember
si existe.
Manejo de excepciones¶
Para indicar que un miembro no existe, member()
devolverá
undefined
después de import undefined
. También puede generar una
excepción en member()
, pero tenga en cuenta que Berry podría
intentar llamar a métodos como tostring()
que aterrizarán en su
método member()
si no existen como métodos estáticos. Para indicar
que un miembro no es válido, setmember()
debe generar una excepción
o devolver undefined
. Devolver cualquier otra cosa como nil
indica que la asignación fue exitosa. Tenga en cuenta que puede recibir
nombres de miembros que no sean identificadores válidos de Berry. La
sintaxis a.("<->")
llamará a a.member("<->")
con un nombre de
miembro virtual que no es léxicamente válido, es decir, no se puede
llamar en código normal, excepto mediante el uso indirecto formas como
introspect
o member()
.
Especificidades para las clases¶
El acceso a los miembros del objeto de clase no desencadena miembros virtuales. Por lo tanto, no es posible tener métodos estáticos virtuales.
Especificidades de los módulos¶
Los módulos admiten la lectura de miembros estáticos con member()
.
Al escribir en un miembro, el comportamiento depende de si el módulo es
de escritura (en la memoria) o de solo lectura (en el firmware). Si se
puede escribir en el módulo, los nuevos miembros se agregan directamente
al módulo y nunca se llama a setmember()
. Si el módulo es de solo
lectura, se llama a setmember()
cada vez que intenta cambiar o crear
un miembro. Entonces es su responsabilidad almacenar los valores en un
objeto separado como un global.
Ejemplo¶
class T
var a
def init()
self.a = 'a'
end
def member(nombre)
return "miembro "+nombre
end
def setmember(nombre, valor)
print("Almacenar '"+nombre+"': "+str(valor))
end
end
t=T()
Ahora intentémoslo:
t.a
‘a’
t.b
‘miembro b’
t.foo
‘miembro foo’
t.bar = 2
Almacenar ‘bar’: 2
Esto también funciona para los módulos:
m = module()
m.a = 1
m.member = def (nombre)
return "miembro "+nombre
end
m.setmember(nombre, valor)
print("Almacenar '"+nombre+"': "+str(valor))
end
Intentemoslo:
m.a
1
m.b
‘miembro b’
m.c = 3 # la asignación es válida por lo que no se llama a `setmember()
m.c
3
Ejemplo más avanzado:
class A
var i
def member(n)
if n == 'ii' return self.i end
return nil # lo hacemos explícito aquí, pero esta línea es opcional
end
def setmember(n, v)
if n == 'ii' self.i = v end
end
end
a=A()
a.i # devuelve nil
a.ii # i llama implícitamente `a.member("ii")`
# devuelve un excepción ya que el miembro es nulo (considerado inexistente)
a.ii = 42 # llama implícitamente `a.setmember("ii", 42)`
a.ii # llama implícitamente `a.member("ii")` and returns `42`
42
a.i # la variable concreta también fue cambiada
42
8.3 Cómo empaquetar un módulo¶
Esta guía lo lleva a través de las diferentes opciones de empaquetado de código para su reutilización utilizando la directiva de “import” de Berry.
Comportamiento de import
¶
Cuando se utiliza import <modulo> [as <nombre> ]
, suceden los
siguientes pasos:
Hay una caché global de todos los módulos ya importados. Si
<modulo>
ya fue importado,import
devuelve el valor en caché ya devuelto por la primera llamada aimport
. No se realizan otras acciones.import
busca un módulo de nombre<modulo>
en el siguiente orden:
en módulos nativos incrustados en el firmware en tiempo de compilación
en el sistema de archivos, comenzando con el directorio actual, luego iterando en todos los directorios desde
sys.path
: busque el archivo<nombre>
, entonces<nombre>.bec
(código de bytes compilado), luego<nombre>.be
. SiBE_USE_SHARED_LIB
está habilitado, también busca bibliotecas compartidas como<nombre>.so que
o<nombre>.dll
aunque esta opción generalmente no está disponible en MCU.
Se ejecuta el código cargado. El código debe terminar con una declaración
return
. El objeto devuelto se almacena en la memoria caché global y se pone a disposición de la persona que llama (en el ámbito local o global).Si el objeto devuelto es un
módulo
y si el módulo posee un miembroinit
, entonces se toma un paso adicional. La función<modulo>.init(m)
se llama pasando como argumento el propio objeto del módulo. El valor devuelto porinit()
reemplaza el valor en el caché global. Tenga en cuenta queinit()
se llama como máximo una vez durante la primeraimportación
.
Nota: una función init(m)
implícita siempre está presente en todos
los módulos, incluso si no se declaró ninguno. Esta función implícita no
tiene ningún efecto.
Empaquetado de un módulo¶
Aquí hay un ejemplo simple de un módulo:
Archivo demo_modulo.be
:
# modulo simple
# use `import demo_modulo`
demo_module = module("demo_module")
demo_modulo.foo = "bar"
demo_modulo.decir_hola = def ()
print("Hola Berry!")
end
return demo_modulo # devuelve el módulo como salida de import
Ejemplo de uso:
import demo_modulo
demo_modulo
<module: demo_modulo>
demo_module.decir_hola()
Hola Berry!
demo_modulo.foo
‘bar’
demo_modulo.foo = "baz" # el módulo se puede escribir, aunque esto es muy desaconsejado
demo_modulo.foo
‘baz’
Empaquetar un singleton (mónada)¶
El problema de usar módulos es que no tienen variables de instancia para realizar un seguimiento de los datos. Están diseñados esencialmente para bibliotecas sin estado.
A continuación, encontrará una forma elegante de empaquetar una clase única devuelta como una “declaración de importación”.
Para ello, utilizamos diferentes trucos. Primero, declaramos la clase para el singleton como una clase interna de una función, esto evita que se contamine el espacio de nombres global con esta clase. Es decir, la clase no será accesible por otro código.
En segundo lugar, declaramos una función init()
del módulo que crea
la clase, crea la instancia y la devuelve.
Según este esquema, import <modulo>
en realidad devuelve una
instancia de una clase oculta.
Ejemplo de demo_monad.be
:
# monada simple
# use `import demo_monad`
demo_monad = module("demo_monad")
# el módulo tiene un solo miembro `init()` y delega todo a la clase interna
demo_monad.init = def (m)
# inncer class
class my_monad
var i
def init()
self.i = 0
end
def say_hello()
print("Hola Berry!")
end
end
# rdevolver una sola instancia para esta clase
return my_monad()
end
return demo_monad # evuelve el módulo como la salida de importación, que eventualmente se reemplaza por el valor de retorno de 'init()'
Ejemplo:
import demo_monad
demo_monad
<instance: my_monad()> # es una instancia no un modulo
demo_monad.say_hello()
Hola Berry!
demo_monad.i = 42 # puedes usarlo como cualquier instancia
demo_monad.i
42
demo_monad.j = 0 # hay una fuerte verificación de miembros en comparación con los módulos
Attribute_error: la clase ‘my_monad’ no puede asignarse al atributo ‘j’ stack traceback: stdin:1: en función main
8.4 Solidificación¶
La solidificación es el proceso de capturar estructuras y códigos Berry compilados (clases, módulos, mapas, listas…) y almacenarlos en el firmware. Reduce drásticamente el uso de la memoria, pero tiene algunas limitaciones.
Módulo solidify
¶
La solidificación es manejada por el módulo solidify
. Este módulo no
está compilado por defecto debido a su tamaño (~10kB). Debe compilar con
la directiva #define BE_USE_SOLIDIFY_MODULE 1
.
El módulo tiene un solo miembro dump(x)
que toma un solo argumento
(el objeto a solidificar) y envía a stdout
el código solidificado.
De forma predeterminada, solidify agrega todas las constantes de cadena al grupo global. En su lugar, puede generar cadenas débiles (elegibles para la poda por parte del enlazador) estableciendo el segundo argumento en “verdadero”.
Por defecto, solidify.dump
genera el código solidificado en la
salida estándar. Puede especificar un archivo como tercer argumento. El
archivo debe estar abierto en modo de escritura y no está cerrado para
que pueda concatenar varios objetos.
solidify.dump(object:any, [, strings_weak:bool, file_out:file]) -> nil
Solidificación de funciones¶
Puede solidificar una sola función.
Ejemplo:
> def f() return "hello" end
> import solidify
> solidify.dump(f)
/********************************************************************
** Solidified function: f
********************************************************************/
be_local_closure(f, /* name */
be_nested_proto(
0, /* nstack */
0, /* argc */
0, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 1]) { /* constants */
/* K0 */ be_nested_str(hello),
}),
&be_const_str_f,
&be_const_str_solidified,
( &(const binstruction[ 1]) { /* code */
0x80060000, // 0000 RET 1 K0
})
)
);
/*******************************************************************/
Para compilar utilizando cadenas débiles (es decir, cadenas que el
enlazador puede eliminar si el objeto no está incluido en el ejecutable
de destino), use solidify.dump(f, true)
:
/********************************************************************
** Solidified function: f
********************************************************************/
be_local_closure(f, /* name */
be_nested_proto(
0, /* nstack */
0, /* argc */
0, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 1]) { /* constants */
/* K0 */ be_nested_str_weak(hello),
}),
be_str_weak(f),
&be_const_str_solidified,
( &(const binstruction[ 1]) { /* code */
0x80060000, // 0000 RET 1 K0
})
)
);
/*******************************************************************/
Solidificación de clases¶
Cuando solidifica una clase, incrusta todos los subelementos. También se
agrega un código auxiliar C
para crear la clase y agregarla al
ámbito global.
> class demo
var i
static foo = "bar"
def init()
self.i = 0
end
def say_hello()
print("Hello Berry!")
end
end
> import solidify
> solidify.dump(demo)
/********************************************************************
** Solidified function: init
********************************************************************/
be_local_closure(demo_init, /* name */
be_nested_proto(
1, /* nstack */
1, /* argc */
2, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 2]) { /* constants */
/* K0 */ be_nested_str(i),
/* K1 */ be_const_int(0),
}),
&be_const_str_init,
&be_const_str_solidified,
( &(const binstruction[ 2]) { /* code */
0x90020101, // 0000 SETMBR R0 K0 K1
0x80000000, // 0001 RET 0
})
)
);
/*******************************************************************/
/********************************************************************
** Solidified function: say_hello
********************************************************************/
be_local_closure(demo_say_hello, /* name */
be_nested_proto(
3, /* nstack */
1, /* argc */
2, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 1]) { /* constants */
/* K0 */ be_nested_str(Hello_X20Berry_X21),
}),
&be_const_str_say_hello,
&be_const_str_solidified,
( &(const binstruction[ 4]) { /* code */
0x60040001, // 0000 GETGBL R1 G1
0x58080000, // 0001 LDCONST R2 K0
0x7C040200, // 0002 CALL R1 1
0x80000000, // 0003 RET 0
})
)
);
/*******************************************************************/
/********************************************************************
** Solidified class: demo
********************************************************************/
be_local_class(demo,
1,
NULL,
be_nested_map(4,
( (struct bmapnode*) &(const bmapnode[]) {
{ be_const_key(i, -1), be_const_var(0) },
{ be_const_key(say_hello, 2), be_const_closure(demo_say_hello_closure) },
{ be_const_key(init, -1), be_const_closure(demo_init_closure) },
{ be_const_key(foo, 1), be_nested_str(bar) },
})),
(bstring*) &be_const_str_demo
);
/*******************************************************************/
void be_load_demo_class(bvm *vm) {
be_pushntvclass(vm, &be_class_demo);
be_setglobal(vm, "demo");
be_pop(vm, 1);
}
Las subclases también son compatibles.
> class demo_sub : demo
var j
def init()
super(self).init()
self.j = 1
end
end
> solidify.dump(demo_sub)
/********************************************************************
** Solidified function: init
********************************************************************/
be_local_closure(demo_sub_init, /* name */
be_nested_proto(
3, /* nstack */
1, /* argc */
0, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 3]) { /* constants */
/* K0 */ be_nested_str(init),
/* K1 */ be_nested_str(j),
/* K2 */ be_const_int(1),
}),
&be_const_str_init,
&be_const_str_solidified,
( &(const binstruction[ 7]) { /* code */
0x60040003, // 0000 GETGBL R1 G3
0x5C080000, // 0001 MOVE R2 R0
0x7C040200, // 0002 CALL R1 1
0x8C040300, // 0003 GETMET R1 R1 K0
0x7C040200, // 0004 CALL R1 1
0x90020302, // 0005 SETMBR R0 K1 K2
0x80000000, // 0006 RET 0
})
)
);
/*******************************************************************/
/********************************************************************
** Solidified class: demo_sub
********************************************************************/
extern const bclass be_class_demo;
be_local_class(demo_sub,
1,
&be_class_demo,
be_nested_map(2,
( (struct bmapnode*) &(const bmapnode[]) {
{ be_const_key(init, -1), be_const_closure(demo_sub_init_closure) },
{ be_const_key(j, 0), be_const_var(0) },
})),
be_str_literal("demo_sub")
);
/*******************************************************************/
void be_load_demo_sub_class(bvm *vm) {
be_pushntvclass(vm, &be_class_demo_sub);
be_setglobal(vm, "demo_sub");
be_pop(vm, 1);
}
Solidificación de módulos¶
Cuando solidifica un módulo, incrusta todos los subelementos. También funciona con listas o mapas incrustados.
> def say_hello() print("Hello Berry!") end
> m = module("demo_module")
> m.i = 0
> m.s = "foo"
> m.f = say_hello
> m.l = [0,1,"a"]
> m.m = {"a":"b", "2":3}
> import solidify
> solidify.dump(m)
/********************************************************************
** Solidified function: say_hello
********************************************************************/
be_local_closure(demo_module_say_hello, /* name */
be_nested_proto(
2, /* nstack */
0, /* argc */
0, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 1]) { /* constants */
/* K0 */ be_nested_str(Hello_X20Berry_X21),
}),
&be_const_str_say_hello,
&be_const_str_solidified,
( &(const binstruction[ 4]) { /* code */
0x60000001, // 0000 GETGBL R0 G1
0x58040000, // 0001 LDCONST R1 K0
0x7C000200, // 0002 CALL R0 1
0x80000000, // 0003 RET 0
})
)
);
/*******************************************************************/
/********************************************************************
** Solidified module: demo_module
********************************************************************/
be_local_module(demo_module,
"demo_module",
be_nested_map(5,
( (struct bmapnode*) &(const bmapnode[]) {
{ be_const_key(l, -1), be_const_simple_instance(be_nested_simple_instance(&be_class_list, {
be_const_list( * be_nested_list(3,
( (struct bvalue*) &(const bvalue[]) {
be_const_int(0),
be_const_int(1),
be_nested_str(a),
})) ) } )) },
{ be_const_key(m, 3), be_const_simple_instance(be_nested_simple_instance(&be_class_map, {
be_const_map( * be_nested_map(2,
( (struct bmapnode*) &(const bmapnode[]) {
{ be_const_key(a, -1), be_nested_str(b) },
{ be_const_key(2, -1), be_const_int(3) },
})) ) } )) },
{ be_const_key(i, 4), be_const_int(0) },
{ be_const_key(f, -1), be_const_closure(demo_module_say_hello_closure) },
{ be_const_key(s, -1), be_nested_str(foo) },
}))
);
BE_EXPORT_VARIABLE be_define_const_native_module(demo_module);
/********************************************************************/
limitaciones de la solidificación¶
La solidificación funciona para muchos objetos: clase
, módulo
,
funciones
y constantes incrustadas u objetos como int
, real
,
string
, list
y map
.
Limitaciones:
Los upvals no son compatibles. No puede solidificar un cierre que captura upvals del alcance externo
La captura de variables globales requiere compilar con la opción
-g
“globales con nombre” (habilitada de forma predeterminada en Tasmota)Las constantes de cadena están limitadas a 255 bytes, cadenas largas (más de 255 caracteres no son compatibles, porque nadie nunca los necesitó)
Los objetos solidificados son de solo lectura, esto tiene algunas consecuencias en las clases. Puede solidificar una clase con sus miembros estáticos cuando se crea, pero no puede solidificar una función que crea una clase derivada de otra clase o con miembros estáticos. La razón principal es que la configuración de la superclase o la asignación de miembros estáticos se implementa mediante el código mutante en la nueva clase, que no puede funcionar en una clase no mutante de solo lectura.
El código solidificado puede depender del tamaño de “int” y “real” y es posible que no se transfiera a través de MCU con tipos de diferentes tamaños. Es posible que deba volver a solidificar para cada objetivo.