Autor Tema: Generación automática de código C con Python  (Leído 102 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado Picuino

  • Moderador Local
  • DsPIC30
  • *****
  • Mensajes: 3744
    • Picuino
Generación automática de código C con Python
« en: 16 de Marzo de 2017, 13:13:42 »
Entre las muchas aplicaciones que tiene Python una que me gusta mucho es su capacidad para generar código de forma automática con gran facilidad.

Para conseguirlo no es necesario utilizar una librería de plantillas (templates), pero yo siempre utilizo una, Jinja2, porque facilita mucho la tarea:
Página oficial de Jinja2
Descarga de Jinja2
El único inconveniente de esta poderosa herramienta es que necesita un pequeño aprendizaje, pero es relativamente sencillo.

Aquí dejo un pequeño ejemplo de cómo generar código automáticamente con plantillas:

Código: Python
  1. # -*- coding: cp1252 -*-
  2. import jinja2
  3.  
  4. # Datos del código c
  5. data = [ 'a', 'e', 'i', 'o', 'u']
  6.  
  7. # Preparar la plantilla de Jinja
  8. template_string = """
  9. void test(char chr) {
  10.   {% for d in data %}
  11.   else if (chr == '{{d}}') {
  12.       printf("{{d}}\\n");
  13.   }
  14.   {% endfor %}
  15. }
  16. """
  17. template = jinja2.Template(template_string, lstrip_blocks=True, trim_blocks=True)
  18.  
  19. # Genera el código c con la plantilla y los datos
  20. sourcecode = template.render(data=data)
  21.  
  22. # Escribe el código en un archivo de disco
  23. print sourcecode
  24. open('main.c', 'wt').write (sourcecode)
  25.  

El código generado sólo necesita eliminar el primer 'else' y ya está preparado:

Código: C
  1. void test(char chr) {
  2.    else if (chr == 'a') {
  3.        printf("a\n");
  4.    }
  5.    else if (chr == 'e') {
  6.        printf("e\n");
  7.    }
  8.    else if (chr == 'i') {
  9.        printf("i\n");
  10.    }
  11.    else if (chr == 'o') {
  12.        printf("o\n");
  13.    }
  14.    else if (chr == 'u') {
  15.        printf("u\n");
  16.    }
  17. }
  18.  

El primer 'else' se podría eliminar de forma automática, pero complicaría el ejemplo más de la cuenta.


Desconectado Picuino

  • Moderador Local
  • DsPIC30
  • *****
  • Mensajes: 3744
    • Picuino
Re:Generación automática de código C con Python
« Respuesta #1 en: 16 de Marzo de 2017, 13:20:29 »
En el programa anterior la sentencia {% for d in data %} es un bucle for que repite el código c para todos los datos de la lista 'data'.
la sentencia {{d}} se sustituye por el valor de la variable 'd' dentro del código c.

Las plantillas se pueden extender y anidar en estructuras muy complejas. Hay sitios web estáticos cuyos ficheros html están diseñados por completo con plantillas jinja.
El ejemplo más inmediato es la propia página web de documentación de Python:
https://docs.python.org/2/

Un saludo.
« Última modificación: 16 de Marzo de 2017, 13:23:14 por Picuino »

Desconectado Picuino

  • Moderador Local
  • DsPIC30
  • *****
  • Mensajes: 3744
    • Picuino
Re:Generación automática de código C con Python
« Respuesta #2 en: 16 de Marzo de 2017, 14:08:20 »
El tipo de código que vienen bien generar con plantillas es el código relativamente sencillo y repetitivo
En estos casos la ventaja de generar código c con plantillas es que se evitan errores al realizar una tarea repetitiva, se puede llegar a generar el código más rápido y es más sencillo realizar modificaciones de todo el código.

Otra ventaja consiste en que se pueden agrupar en una sola estructura de datos sencilla muchos datos que luego aparecen dispersos por varios archivos de código y de cabecera. Estos datos dispersos en varios archivos y relacionados entre sí tienden a generar errores en las sucesivas modificaciones o ampliaciones del programa. Con las plantillas se evitan en gran medida estos errores.
A su vez, las plantillas tienden a guiar la generación de código para que sea más homogeneo y esto ayuda a evitar errores y hacer el debugging más sencillo.

No todo son ventajas, pero hasta ahora no me he arrepentido en ninguna ocasión de haber generado el código mediante base de datos y plantillas. Siempre me ha obligado a replantearme el código y, aunque al principio puede dar más trabajo, el resultado final es más satisfactorio.

Un saludo.


Desconectado Picuino

  • Moderador Local
  • DsPIC30
  • *****
  • Mensajes: 3744
    • Picuino
Re:Generación automática de código C con Python
« Respuesta #3 en: 16 de Marzo de 2017, 14:12:52 »
He conocido proyectos en los que la generación automática de código se ha hecho con la hoja de cálculo Excel.
Excel tiene la ventaja de mostrar datos de manera más visual, pero es muchísimo menos flexible y la generación de código es mucho más laboriosa.

La flexibilidad, sencillez y potencia de Python + Jinja es difícilmente igualable.

Desconectado Picuino

  • Moderador Local
  • DsPIC30
  • *****
  • Mensajes: 3744
    • Picuino
Re:Generación automática de código C con Python
« Respuesta #4 en: 17 de Marzo de 2017, 09:07:21 »
Últimamente estoy actualizando Ardublock con este método.
Ardublock es una herramienta que transforma bloques gráficos en código para Arduino. Es muy sencillo de utilizar y permite a los principiantes empezar a programar sin necesidad de aprender la sintaxis un poco compleja de c.

Esta es una imagen de ejemplo de programa en Ardublock:

ardublock-2017R03-01.png
*ardublock-2017R03-01.png
(60.95 kB . 763x420 - visto 21 veces)


Cada bloque gráfico se transforma en una o más instrucciones en c para Arduino.

Ardublock está programado en java y tiene una serie de archivos de configuración para guardar todos los datos.

Cada vez que se añade un bloque para una nueva instrucción hay que modificar el código en varios puntos:
  • Añadir en la sección de bloques la definición del bloque con sus entradas y su salida de datos (si es una función), el tipo de los datos y los comentarios que aparecen representados en el bloque.
  • Añadir en la sección family si ese bloque pertenece a una familia de bloques emparentados. Por ejemplo los datos HIGH y LOW están emparentados entre sí y se puede transformar uno en otro pinchando en el bloque la flecha de opción.
  • Añadir el nombre del bloque en el menú correspondiente para que aparezca al pinchar en el menú.
  • Definir las variables con el texto que les corresponda. Por ejemplo la variable bc.integer se asociará al texto "Integer" y esa variable se utilizará en todos los bloques que utilicen números enteros.
  • Código en java que genera las instrucciones en c correspondientes al bloque representado en Ardublock. Es el código "Traductor"

Cada vez que quiero añadir una nueva instrucción tengo que modificar estos 5 puntos diferentes, bastante repetitivos y muy sensibles a pequeños errores.
Después de modificar a mano el programa en varias ocasiones me he decidido a generarlo con plantillas. Esto me ha permitido 'limpiar' el programa, redefinir los nombres con más coherencia, eliminar algún error y espero que me sirva para reducir los errores de programación.

El programa de las plantillas es demasiado largo, pero pondré aquí algún ejemplo de los resultados.

Los datos están guardados en formato YAML. Adjunto una pequeña muestra.
Datos definidos en archivo YAML:
Código: [Seleccionar]
# Code Blocks
Blocks:
- name: pc_ledWrite
  parameter:
  - {type: number,  label: bc.pc_led,   default: [number, '1']}
  - {type: boolean, label: bc.pc_state, default: [digital-on, 'ON']}
  return:
  translator:
    name:  PcLedWrite
    headers: ['PC42.h', 'Wire.h']
    setup: ['pc.begin();']
    code:
    - 'return "pc.ledWrite(" + arg1 + ", " + arg2 + ");";'
  properties:
    bg.pc_ledWrite: Led Write

# Common properties
Properties:
  bc.pc_led: Led
  bc.pc_state: State

A partir de la pequeña muestra anterior se generan todos los siguientes bloques de código:

Bloque en archivo ardublock.xml:
Código: [Seleccionar]
<BlockGenus name="pc_ledWrite" kind="command" color="160 0 0" initlabel="bg.pc_ledWrite">
<BlockConnectors>
<BlockConnector connector-type="number" connector-kind="socket" label="bc.pc_led">
<DefaultArg genus-name="number" label="1" />
</BlockConnector>
<BlockConnector connector-type="boolean" connector-kind="socket" label="bc.pc_state">
<DefaultArg genus-name="digital-on" label="ON" />
</BlockConnector>
</BlockConnectors>
</BlockGenus>

Familia: En este caso no se genera código, porque se trata de un solo bloque aislado.

Menú en archivo ardublock.xml:
Código: [Seleccionar]
<BlockDrawerSets>
<BlockDrawerSet name="factory" type="stack" location="southwest" window-per-drawer="no" drawer-draggable="no">
<BlockDrawer button-color="160 0 0" name="bd.Picuino" type="factory">
<BlockGenusMember>pc_ledWrite</BlockGenusMember>
</BlockDrawer>
</BlockDrawerSet>
</BlockDrawerSets>

Variables en archivo ardublock.properties
Código: [Seleccionar]
bg.pc_ledWrite=Led Write
bc.pc_led=Led
bc.pc_state=State

Código java en archivo PcLedWrite.java:
Código: [Seleccionar]
package com.ardublock.translator.block;

import com.ardublock.translator.Translator;
import com.ardublock.translator.block.NumberBlock;
import com.ardublock.translator.block.TranslatorBlock;
import com.ardublock.translator.block.exception.BlockException;
import com.ardublock.translator.block.exception.SocketNullException;
import com.ardublock.translator.block.exception.SubroutineNotDeclaredException;

public class PcLedWrite extends TranslatorBlock {

public PcLedWrite(Long blockId, Translator translator, String codePrefix, String codeSuffix, String label) {
super(blockId, translator, codePrefix, codeSuffix, label);
}

@Override
public String toCode() throws SocketNullException, SubroutineNotDeclaredException {

translator.addHeaderFile("PC42.h");
translator.addHeaderFile("Wire.h");

translator.addSetupCommand("pc.begin();");

TranslatorBlock translatorBlock;
translatorBlock = this.getRequiredTranslatorBlockAtSocket(0);
String arg1 = translatorBlock.toCode();
translatorBlock = this.getRequiredTranslatorBlockAtSocket(1);
String arg2 = translatorBlock.toCode();

return "pc.ledWrite(" + arg1 + ", " + arg2 + ");";
}
}

Cada uno de los bloques se inserta automáticamente en su archivo correspondiente, de manera que todo el proceso es automático y solo resta compilar con maven y archivar el resultado.

Como puede verse, un pequeño fragmento de datos agrupa todo lo necesario para generar muchos bloques de código dispersos, que de otra forma sería más complejo generar. Cuando se tiene en cuenta que Ardublock dispone de cientos de bloques semejantes puede verse la ventaja de trabajar con plantillas.

Otro tipo de bloques mucho más parecidos entre sí se pueden generar con menos datos todavía.
Los datos siguientes definen de una sola vez 5 bloques distintos:

Código: [Seleccionar]
Definitions:
- pc3: &PICUINO_BEGIN    [pc_begin, pc_dispBegin, pc_buzzBegin, pc_ledBegin, pc_keyBegin]

Blocks:
- name: *PICUINO_BEGIN
  parameter:
  return:
  translator:
    name:  PcBegin
    headers: ['PC42.h', 'Wire.h']
    setup: ['pc.begin();']
    code:
    - 'String functionName = this.getTranslator().getBlock(blockId).getGenusName();'
    - 'if (functionName == "pc_keyBegin")'
    - ' return "pc.keyBegin();";'
    - 'else if (functionName == "pc_ledBegin")'
    - ' return "pc.ledBegin();";'
    - 'else if (functionName == "pc_buzzBegin")'
    - ' return "pc.buzzBegin();";'
    - 'else if (functionName == "pc_dispBegin")'
    - ' return "pc.dispBegin();";'
    - 'else'
    - ' return "pc.begin();";'
  properties:
    bg.pc_begin: Reset Panel
    bg.pc_keyBegin: Reset Keys
    bg.pc_ledBegin: Reset Leds
    bg.pc_buzzBegin: Reset Buzzer
    bg.pc_dispBegin: Reset Display

Un saludo.
« Última modificación: 17 de Marzo de 2017, 09:42:04 por Picuino »

Desconectado Picuino

  • Moderador Local
  • DsPIC30
  • *****
  • Mensajes: 3744
    • Picuino
Re:Generación automática de código C con Python
« Respuesta #5 en: 17 de Marzo de 2017, 09:29:00 »
En total el programa de plantillas tiene 265 líneas de código, de las cuales una tercera parte son plantillas jinja con el código XML o JAVA necesario para generar luego los datos y los programas y el resto son líneas de separación en blanco y líneas de código Python (120 líneas)

El archivo de datos en YAML contiene 350 líneas y 9700 bytes.

Al final las plantillas generan automáticamente como resultado 19 ficheros de código y datos con un tamaño de 1636 líneas y 68280 bytes


Programar el código Python y generar los datos YAML me ha llevado un par de tardes, aproximadamente lo mismo que generar unas 20 o 30 instrucciones de un menú de Ardublock, de manera que el método comienza a ser ventajoso a partir de la modificación de una cantidad importante de código.
Para modificar menos bloques (una pequeña modificación) no merece la pena tanto trabajo.

Por ahora solo he definido con plantillas unos 150 bloques (de instrucciones y de datos) de dos nuevos menús, pero mi intención es traducir todos los menús con este método para que todo el código esté definido con plantillas.
« Última modificación: 17 de Marzo de 2017, 09:43:26 por Picuino, Razón: Añadidos datos de tamaño de código generado »


 

anything