4 puntos por GN⁺ 2023-10-09 | 1 comentarios | Compartir por WhatsApp
  • Berry es un lenguaje de scripting de tipado dinámico para dispositivos embebidos de bajo rendimiento; tomando como referencia ARM Cortex M4, el núcleo del intérprete ocupa menos de 40 KiB y puede ejecutarse con un heap de menos de 4 KiB
  • El runtime está compuesto por un compilador de una sola pasada y una VM basada en registros; todo el código está escrito en ANSI C99, por lo que es fácil integrarlo en código embebido existente
  • Para mejorar el rendimiento, int, real, boolean y string no se implementan como objetos de clase, y solo list, map y range se manejan como objetos de clase
  • Las funciones del lenguaje soportan programación imperativa, orientada a objetos y funcional, e incluyen GC, FFI, manejo de excepciones, archivos de bytecode y módulos de extensión
  • La construcción de objetos en tiempo de compilación y la fijación en flash permiten reducir el uso de RAM de código, clases y módulos, por lo que se adapta bien a entornos con fuertes restricciones de memoria

Un runtime orientado a entornos embebidos

  • Berry es un lenguaje de scripting de tipado dinámico pensado para usarse en dispositivos embebidos de bajo rendimiento, como microprocesadores
    • Con CPU ARM Cortex M4, ISA Thumb y compilador ARMCC, el tamaño del código del núcleo del intérprete es inferior a 40 KiB
    • En las mismas condiciones, puede ejecutarse con un heap de menos de 4 KiB
  • El intérprete está escrito en ANSI C99 e incluye un compilador de bytecode de una sola pasada y una máquina virtual basada en registros
  • El diseño de tipos prioriza el rendimiento en ejecución y distingue entre tipos por valor y tipos objeto
    • Tipos que no son objetos de clase: int, real, boolean, string
    • Tipos que sí son objetos de clase: list, map, range
  • La construcción de objetos en tiempo de compilación almacena la mayoría de los objetos constantes en el segmento de datos de código de solo lectura, reduciendo el uso de RAM al iniciar el intérprete

Funciones del lenguaje y modelo de ejecución

  • Los tipos básicos incluyen nil, boolean, integer, real, string, class, instance, module, list, map y range
  • Las expresiones y operadores admiten asignación, relacionales, lógicos, aritméticos, de bits, de campo, indexación, concatenación de cadenas, operador condicional, paréntesis y bytes buffer
  • Las estructuras de control ofrecen if elif else end, while, for, break y continue
  • Las funciones incluyen variables locales y alcance de bloque, sentencia de retorno, definición de funciones anidadas, closures basados en Upvalue, funciones anónimas y expresiones lambda
  • Las clases solo soportan herencia pública simple y ofrecen métodos, sobrecarga de operadores, método constructor y método destructor
  • El GC usa Mark-Sweep GC
  • En el manejo de excepciones, la sentencia raise puede lanzar cualquier valor de excepción y se soportan múltiples modos de catch
  • La función de archivos de bytecode permite exportar funciones a archivos de bytecode para luego volver a cargarlos y ejecutarlos

Módulos, integración con C y ejemplos

  • La gestión de módulos soporta tanto módulos integrados como módulos de extensión
    • Los módulos de extensión incluyen módulos de script, módulos de archivos de bytecode y módulos de bibliotecas compartidas como *.so y *.dll
    • Es posible fijar código, clases y módulos en flash para reducir el uso de RAM
    • Ofrece soporte opcional de Regex y LVGL mapping opcional
  • Gracias a la interfaz nativa de C, puede embeberse como una biblioteca dentro de código existente, con ejemplos como Tasmota
    • También ofrece easy mapping opcional para llamar código C desde Berry
  • Los ejemplos de sintaxis muestran recorrido de listas, creación de map y recorrido de claves, declaración de class y herencia simple mediante el operador :
  • Se pueden consultar más ejemplos de código en GitHub

1 comentarios

 
GN⁺ 2023-10-09
Opiniones en Hacker News
  • Para tener un runtime de 40 KB, es sorprendentemente rico en funciones: corre un lenguaje similar a Python/Ruby en una VM con recolección de basura y soporta estilos procedurales, orientados a objetos y funcionales.
    A simple vista parece bastante cómodo de escribir. En particular, llama la atención la función de crear objetos constantes por adelantado y poner la mayor parte en ROM, usando RAM solo para los datos que realmente pueden cambiar. Hasta donde sé, MicroPython o Lua no tienen ese enfoque, y en MCU donde sobra ROM/flash pero falta RAM, eso marca una gran diferencia.

    • MicroPython también puede congelar módulos para guardarlos en flash, y en ese caso en la flash solo queda bytecode precompilado.
      Si un módulo tiene datos almacenados como objetos inmutables, como cadenas o bytes(), se leen directamente desde la flash sin copiarlos antes a RAM. Eso sí, para hacer el freeze hay que ejecutar el código una vez en una computadora de escritorio.
      https://docs.micropython.org/en/latest/reference/constrained...
    • Después de leer la explicación volví al sitio, y me pareció mucho más completo y atractivo que la página de inicio.
    • Como explicó @snops, MicroPython puede ejecutar código desde la flash en forma de código congelado. Como resultado, hay que compilarlo dentro del binario del firmware, lo que vuelve más lento el ciclo habitual de desarrollo rápido de MicroPython.
      Por eso, el flujo cómodo es distribuir el código en tiempo de ejecución hasta que la base de código esté estable o se acerque a la etapa de producto, y en ese momento congelarlo dentro del firmware. Como modelo más conveniente, existe una propuesta de soporte para mapfs, que permite asignar una parte de la flash para almacenar código, compilar Python a bytecode (mpy_cross), subirlo a esa zona y ejecutarlo en tiempo de ejecución. Ya hay una prueba de concepto, pero todavía quedan detalles por pulir antes de que entre en mainline.
      https://github.com/micropython/micropython/pull/8381
    • Se ve bastante interesante y en varios aspectos similar a Lua, aunque también hay diferencias notables. Como referencia, en eLua también hubo trabajo para ubicar algunos datos de forma transparente en ROM.
      Ver las rotables en https://eluaproject.net/doc/v0.9/en_arch_ltr.html
  • Berry se usa en Tasmota: https://tasmota.github.io/docs/Berry/

    • Tasmota se ve interesante. Si lo has usado o lo has comparado con toit/jaguar, me gustaría conocer tu opinión.
  • Se ve realmente bueno. Personalmente, creo que su documentación está cerca de ser de primer nivel entre las que he visto.
    Me gusta especialmente el “short manual”, que permite a un desarrollador experimentado captar rápido la sensación del lenguaje. No conocía Tasmota antes, pero creo que voy a buscar una excusa para probarlo en algún proyecto.

  • Lo que más me intriga es cómo se compara con Lua en rendimiento y uso de memoria, y qué tan posible es hacer sandboxing.
    También me pregunto si se puede ejecutar código no confiable.

    • La comparación con Lua es lo más interesante. La mayoría de las empresas donde trabajé usaban Python como una especie de proxy tosco o herramienta CLI, pero Lua era difícil de recomendar porque su orientación a objetos basada en metatablas resultaba confusa.
      Si este lenguaje es una alternativa válida con estructuras simples y constructores básicos, creo que podría recomendarlo para eliminar scripts de Python y sus dependencias.
    • Por defecto, el código puede abrir y escribir en el sistema de archivos, así que no se puede ejecutar tal cual código no confiable.
      El sandboxing queda bajo responsabilidad de la aplicación. Para sandboxing, WebAssembly es una buena alternativa.
    • Si incluyera manejo nativo de hilos con la palabra clave async, creo que se diferenciaría claramente de Lua para usos en motores de juegos.
  • Algo casi igual a esto, pero con menos paradigmas y tipado estático, sería realmente bueno como lenguaje de configuración.
    En varios proyectos que requerían configuraciones complejas, en vez de obligarte a escribir cientos de líneas de YAML te dejaban usar Lua o Starlark/Python, y se sentía mucho mejor. Pero siempre se extrañan el autocompletado y la reflexión. No se ve un candidato ideal para este uso, y casi todos los lenguajes de scripting pequeños e integrables son de tipado dinámico.

    • He estado pensando en una dirección parecida, aunque más que tipado estático, lo veo como configuración fuertemente validada. Sería mejor poder cargar toda la configuración y luego generar una lista de problemas, y si se hace bien también debería permitir buen soporte en editores.
      Dicho eso, https://dhall-lang.org/ muestra que muchas configuraciones pueden manejarse con tipado estático y que eso trae grandes beneficios. Parece que se puede integrar programáticamente en varios lenguajes.
      https://docs.dhall-lang.org/howtos/How-to-integrate-Dhall.ht...
  • Se ve bueno. Aun así, todavía me inclino bastante por uLisp (http://www.ulisp.com), pero me alegra que aparezcan cosas como esta.

  • Si sueles mirar primero ejemplos de código para captar la sensación de un lenguaje, conviene ver el directorio de ejemplos: https://github.com/berry-lang/berry/tree/master/examples

  • Sería muy bueno que ofrecieran bindings para otros lenguajes. Nosotros usamos quickjs desde Rust y funciona bastante bien.
    Como se puede proporcionar a la VM solo lo que uno quiera, también permite ejecutar código no confiable.

  • Parece un lenguaje optimizado para uso embebido. El diseño y la documentación están bien hechos, y no parece tener comportamientos raros o inesperados.
    La sintaxis también es agradablemente minimalista y va con mis gustos. Sin duda lo tendré en cuenta para mi próximo proyecto con ESP32.

    • Voy a probarlo de inmediato. Estaba trasteando con un kit de desarrollo ESP-32 para hacer adornos luminosos de Halloween, y me entusiasma la idea de probar esto.
      Parece encajar muy bien en este nicho, y me gusta especialmente que esté haciendo cosas para las que no se me ocurre un equivalente claro en otros lenguajes.
  • Me pregunto si existe un enfoque estándar para mostrar el nombre de la función de script en los frames de pila dentro de un stack trace nativo.
    Cuando ocurre un crash o se usa un profiler de CPU, quisiera saber si se pueden mostrar mezclados el stack trace nativo y el stack trace del script.