Qué es un DSL
● Lenguaje EspecÍfico de Dominio (Domain
Specific Language):
– Dominio: El problema que queremos resolver
– Específico: Solo para ese dominio
– Lenguaje: Para ser escrito y leído por personas
● Los problemas se pueden resolver con un
lenguaje de propósito general, pero...
Especificar soluciones
● A veces, puede ser una buena opción crear un
lenguaje específico para ese problema (o
sea, un DSL)
● Ese lenguaje permite expresar la solución de
una forma más sencilla
Ejemplo 1: SQL
Dominio: Extraer información de un sistema de
base de datos relacional
SELECT *
FROM VENTAS
WHERE precio <= 0.99;
Ejemplo 2: Expresiones regulares
Dominio: Buscar y extraer patrones de texto
dentro de otros textos.
[A-Za-z_][A-Za-z0-9_]*
Ejemplo 3: CSS
Dominio: Especificar la apariencia gráfica de
elementos HTML (No es un lenguaje de
programación, y sus usuarios no son
necesariamente desarrolladores)
div.titulo {
color: #BB2266;
font-size: 24pt;
}
Más ejemplos
● Html
● Django templates,
Jinja2
● MarkDown, RST
● Ficheros de
configuración
● Graphviz
● Make, Rake
● Gherkin Syntax
● AWK
● XAQL - Xapian Query
Language Parser
● Less, Sass
● YAML, JSON
● SIML
● ...
Ejemplos de lenguajes que no son
DSL (IMHO)
● R (Estadísticas)
– Restringido a un dominio (estadísticas), pero en
realidad es un lenguaje turing-completo
● XSLT
– Restringido a un dominio (Transformar contenidos
XML), pero no pensado para ser leído o escrito por
humanos (cuerdos)
Características que definen un DSL
● No están pensado para ser de uso general
(rara vez un DSL es turing-completo)
● Hacen únicamente lo que tienen que hacer,
nunca se salen del dominio
● Pensados para ser leídos y escritos por
personas (aunque se puedan generar
automáticamente)
Ventajas
● El lenguaje es más legible y más corto
● No necesita ser Turing-completo
● Puede ser usado por personal no técnico
● Flexible y adaptable
● Es código: Podemos transformarlo, almacenarlo,
distribuirlo, representarlo, etc...
● Fomenta la colaboración entre personas de
distintas especialidades
● Control de versiones
Tipos de DSL
DSL Interno
● Expande el interprete de un lenguaje de propósito
general. Python puede hacer cosas así con
Macros, metaprogramación, el módulo ast, etc...
– Fácil, pero te puede condicionar mucho
– Muy usado en lenguajes como scala, groovy, lisp...
– Parser ya incluido
– Ventajas: toda la potencia del lenguaje base
– Desventajas: toda la potencia del lenguaje base
Usar AST en Python
● Nos permite modificar el árbol AST que
implementa el propio Python para interpretarse.
● Muy potente, muy loco, muy peligroso, muy
divertido. Elige una.
Usar Metaclases
● El ejemplo más famoso es la definición de
modelos en Django.
● Muy potente, algo loco, no tan peligroso como
el AST
DSL Externos
● El lenguaje es totalmente independiente.
– Tenemos que escribir nuestro propio lenguaje
– Mola
– Pero puede dar miedito
– No es tan terrible como parece
Características DSL externo
● Tienes que crear tu propio parser y tu propia
Gramática (más sobre estas palabrejas más
adelante)
● Libertad absoluta
● La sintaxis del lenguaje solo está condicionada
por el dominio (y tu talento)
Estructura de un lenguaje
● Distintos autores usan diferentes nombres,
pero básicamente consta de 4 fases:
– Scanner
– Analizador Léxico, Tokenizer o Lexer
– Analizador Sintáctico o Parser*
– Intérprete o generación de código
● Cada capa o fase usa los servicios de la
anterior y pasa sus resultados a la siguiente
* El turrón está aquí / The Nougat is here
Scanner
● La primera capa y la más sencilla.
Simplemente lee la entrada carácter a carácter.
● A veces puede ser un poco más complicada.
por ejemplo, puede que necesite leer
caracteres por delante del actual, para poder
tomar decisiones
– Por ejemplo, para discriminar entre a++ y a+1
Lexer/Tokenizer
● También llamado analizador léxico
● Usando los servicios del scanner, lee una secuencia de
bytes y devuelve una serie de unidades léxicas o
Tokens (Si fuera un lenguaje humano, palabras y
signos)
● Es razonablemente sencillo de implementar. Una
técnica muy usada es usar expresiones regulares.
● Los tokens devueltos están etiquetados para saber de
que tipo son (Si fuera un lenguaje humano, la palabra y
su función, p.e. verbo).
Parser
● Organiza los tokens entregados por el lexer de
forma que sean procesables. Esto es,
normalmente, en forma de árbol.
● Para ello utiliza una Gramática del lenguaje.
● Existen varios algoritmos, cada uno con sus
ventajas e inconvenientes.
Generación/Ejecución de código
● La última fase. En el caso de un lenguaje de
programación general, se genera o ejecuta
código, bytecodes o código ejecutable.
● Normalmente en un DSL se crea o controla un
módelo creado por nosotros.
● En cualquier caso, es la fase final. Lo que
hagamos con el árbol es cosa nuestra.
¿Para qué sirve la gramática?
¿Para qué sirve la gramática?
● El parser usa la gramática para:
– Detectar si la entrada es correcta, es decir, si
pertenece al lenguaje.
– Organizar los tokens de entrada en forma de Árbol
de sintáxis o Árbol de Sintáxis Abstracta o AST.
– Se le clasifica de abstracto porque no representa
fielmente todos los detalles de la sintaxis.
– Por ejemplo, los caracteres ; que diferencia los
líneas en lenguajes como C, se suelen eliminar del
AST
Ejemplo AST
Los paréntesis no se incorporaron al AST, no son necesarios
Definición formal de Gramática
● Una gramática es la definición matemática de
un lenguaje
● La gramática nos dice si un texto pertenece al
lenguaje o no
● Para ello, establece unas reglas de producción;
si usando esas reglas se puede llegar a
obtener un texto, entonces ese texto pertenece
al lenguaje
Gramáticas
● Desde un punto de visto formal, una gramática
es un conjunto de reglas llamadas
producciones
● De esas reglas, una debe ser la inicial
● Las reglas tienen la forma:
<parte derecha> → <parte izquierda>
● la → se suele representar de muchas maneras,
pero viene a significar “se define como”
Mejor con un ejemplo
Supongamos un lenguaje muy, muy sencillo:
Begin
Bob 12
Alice 26
End
Entre Begin y End, una lista de varios nombres,
junto con un número.
Unas aclaraciones previas
Los nombres están formados por una sola
palabra; “Bob” es válido, “Robert Drake” no.
La primera letra del nombre tiene que ser
alfabética, pero luego se aceptan números:
“labo3” es válido pero “3ell” no.
Los números son enteros, no se aceptan
decimales, “123” es válido, “3.14” no.
Al menos tiene que venir una línea de datos
Una gramática
Program --> Begin line+ End
line --> id num
id --> [A-Za-z][A-Za-z0-9]*
num --> [0-9]+
Otra gramática
Program --> Begin lines End
lines → line+
line --> id num
id --> [A-Za-z][A-Za-z0-9]*
num --> [0-9]+
Syntax Chart
Nomenclatura y otras yerbas
● Cualquier símbolo que aparezca a la izquierda
de una flecha es un símbolo No Terminal.
● Cualquier símbolo que no aparezca en la parte
izquierda de ninguna regla es un Terminal o
Literal
● En la parte de la derecha de la regla pueden
aparecer terminales, no terminales y símbolos
especiales
Símbolos especiales
● | Representa alternativas, a|b significa que
se puede generar a o b
● * Significa repetición de cero o más
● + Significa repetición de uno o más
● ? Opcional (uno o cero)
● [a-z] Rango de caracteres
[0-9] se podría representar como 0|1|2|3|4|5|6|8|9
Parser Recursivo Descendente
● Hay varios tipos de parsers
● Uno de los más simples es el Parser Recursivo
Descendente
● Es tan simple que se puede escribir a mano
● Vamos a hacerlo
Parser LL(1)
● Necesitamos definir una función para cada
símbolo no terminal de la gramática
● El parser consiste en una serie de llamadas
recursivas
● En nuestro caso, debemos escribir una función
para cada uno de los símbolos:
– program
– line
– id
– num
Además, necesitamos:
● Que el tokenizer haya hecho su trabajo, y nos
haya entregado una lista o cola de tokens (Lo
simularemos)
● Una función que nos permita examinar el
siguiente token a procesar
● Una función auxiliar, consume, que retira un
elemento de la lista de tokens si es el que
esperamos. Si no lo fuera, da un error.
Lista de Tokens
Token = namedtuple('Token', ['kind', 'value'])
buffer = [
Token('BEGIN', 'Begin'),
Token('ID', 'Bob'),
Token('NUM', 12),
Token('ID', 'Alice'),
Token('NUM', 26),
Token('END', 'End'),
]
pos = 0
Función auxiliar reduce
def consume(token):
global buffer, pos
tk = buffer[pos]
if tk.kind != token:
raise ParserError(...)
pos += 1
return True
Función program
def program():
begin()
r = line()
while r:
r = line()
end()
if not eof():
raise ParserError(...)
return True
Función begin
def begin():
return consume('BEGIN')
Función line
def line():
global buffer, pos
tk = buffer[pos]
if tk.kind == 'ID':
pos += 1
tk = buffer[pos]
if tk.kind == 'NUM':
pos += 1
else:
raise ParserError(...)
return True
return False
Función end
def end():
return consume('END')
Ventajas
● Muy fácil de escribir: se siguen unas reglas
para cada tipo de símbolo y regla de la
gramática.
● Fácil de entender
● Si cambia la gramática, se cambia el programa
● La primera vez es divertido
Inconvenientes
● Los detalles de la gramática están “perdidos”
en el código; no contamos con una
representación explícita de la gramática
● Si la gramática es complicada, es tedioso
● Algunas gramáticas no se puede parsear así.
Las reglas recursivas, de tipo
– expr → expr (+|-|*|+) expr
hacen que el parser entre un un bucle sin fin
● La décima vez ya no es tan divertido...
Soluciones
● Afortunadamente, hay formas de generar un parser
automáticamente a partir de la gramática.
● El parser que hemos hecho es de tipo LL. La segunda L
indica que hacemos un análisis descendente, es decir,
empezamos en la regla inicial y bajamos
● Otro tipo de parsers son los LR o ascendentes, en este
caso, comenzamos desde la parte derecha de las reglas y
vamos subiendo hasta la regla inicial.
Características de las parsers LR
● El análisis se hace de forma ascendente, por lo
que pueden usarse reglas gramaticales recursivas
● La gramática pueda ser más sencilla (Pero aun
hay ciertos problemas, por ejemplo con
gramáticas ambiguas)
● En general, más poderosos que los LL
● Existe formas automáticas de generarlos, igual
que los LR
Técnicas de generación de parser
● No tenemos tiempo de ver más de las técnicas
de creación de parsers, pero como se pueden
imaginar, es un campo amplio y complejo.
● Lo bueno es que no nos hace falta. Podemos
usar un generador
Algunos generadores de parser
● Lex y Yaccç; Lex se encarga del tokenizer y
Yacc del parser. Generan código C y el parser
resultante es LR
● Flex y Bison son similares, pero generan C++
● ANTL-R es una herramienta escrita en Java
pero que puede general código en Python,
entre otros lenguajes. Genera parsers LL(K).
● Hay muchísimos más...
Centrándonos en Python
● También hay muchos, algunos de los más
destacados son:
– Plex
– Grako
– PLY
– Pyparsing
– Muchos más ...
http://nedbatchelder.com/text/python-parsers.html
Pyparsing
● Vamos a ver como resuelve Pyparsing el
lenguaje anterior
● La idea es componer, a base de elementos
más pequeños, el parser
● La estructura refleja la gramática (pero al
revés, la regla inicial es la última, para evitar
referencias futuras)
Pyparsing
from pyparsing import (Literal, Word, nums, alphas, Optional,
OneOrMore, Group)
num = Word(nums)
id = Word(alphas)
line = id + num
lines = OneOrMore(line)
begin = Literal('Begin')
end = Literal('End')
program = begin + lines + end
Cambios en la gramática
Begin
Bob 12
Alice 26
Charles 33+22
Daniel 12-133
End
Fácil de reflejar en el código
num = Word(nums)
op = Literal('+') | Literal('-')
id = Word(alphas)
line = Group(id + num + Optional(op + num))
lines = OneOrMore(line)
begin = Literal('Begin')
end = Literal('End')
program = begin + lines + end
Ejecutemos el parser
['Begin', 'Bob', '12', 'Alice', '26', 'End']
¡Pero eso es un split!
¡Qué engaño! ¡A la hoguera con él!
¡Tranquilidad!
● Nosotros podemos incluir las acciones que
queramos, asociadas a los elementos que
constituyen las reglas.
● Como no hay acciones, el resultado es una
simple lista de tokens.
['Begin', 'Bob', '12', 'Alice', '26', 'End']
Añadamos acciones
begin = Suppress(Literal('Begin'))
end = Suppress(Literal('End'))
num = Word(nums)
op = Literal('+') | Literal('-')
id = Word(alphas)
line = Group(id + num + Optional(op + num))
lines = OneOrMore(line)
program = begin + lines + end
num.setParseAction(lambda p: int(p[0]))
Esto está mejor
Ejecutemos ejemplo_pyparsing_2.py:
['Begin', ['Bob', 12], ['Alice', 26], 'End']
Las acciones nos permiten construir
nuestro modelo (o un AST)
def parse_line(s, loc, toks):
items = toks[0]
if len(items) == 4:
op = items[2]
delta = items[3]
if op == '+':
v = items[1] + delta
elif op == '-':
v = items[1] - delta
return (items[0], v)
else:
return tuple(items)
line.setParseAction(parse_line)
Mucho mejor
Ejecutemos ejemplo_pyparsing_3.py
ejemplo3.txt:
[('Bob', 12), ('Alice', 26), ('Charles', 55), ('Daniel', 29)]
Begin
Bob 12
Alice 26
Charles 33+22
Daniel 72-43
End
Ventajas de Pyparsing
● Sencillo (pero potente, como Python)
● 100% pure python
● La gramática está reflejada en el código, pero
es legible
● Se puede testear por partes
● Es relativamente lento comparado con otras
opciones, pero no suele ser problema para un
DSL
Un ejemplo mayor
● Grafel: Un lenguaje para hacer animaciones
sencillas, pensado para no programadores
● Desarrollado en una semana
● Las animaciones de esta presentación están
hechas en él
● Usa pyparsing para el lenguaje, PyGame para
realizar las animaciones.
Hola, mundo grafel
Cast:
a = Box pos 110x160
Actions:
25-75 a Swing 1090x600
Ejemplo de uso de pyparsing
attrs = ZeroOrMore(attr)
Identifier = Word(alphas, alphanums)
role = oneOf(
'Square Rect RoundRect Star Dice Label'
' Text Circle Box Triangle Bitmap Path'
)
castline = (
Identifier('name')
+ Suppress('=')
+ role('role')
+ Group(attrs)('params')
)
Actores
● Square
● Box
● Rect
● Circle
● Path
● Polygon
● Triangle
● Star
● RoundRect
● Dice
● Text
● Label
● Bitmap
Acciones
● Blink
● Colorize
● Enter
● FadeIn
● FadeOut
● Exit
● Background
● Move
● Fall
● Land
● Swing
● EaseIn
● EaseOut
● Timer
Libros
● Esta es la biblia de como construir tu propio
intérprete o compilador
No lo recomiendo para un DSL
● Es demasiado complejo. Es EL LIBRO si vas a
implementar un lenguaje completo, pero un DSL es mucho
más sencillo.
Recomendaciones
Domain-Specific Languages
Martin Fowler, Rebbeca Parsons
Addison Wesley
Language Implementation Patterns
Terence Parr
Pragmatic Bookshelf
Gracias por su
tiempo
Repositorio de Grafel:
http://bitbucket.org/euribates/grafel
Juan Ignacio Rodríguez de León
@jileon en twitter

Introducción a DSL (Lenguajes Específicos de Dominios) con Python

  • 2.
    Qué es unDSL ● Lenguaje EspecÍfico de Dominio (Domain Specific Language): – Dominio: El problema que queremos resolver – Específico: Solo para ese dominio – Lenguaje: Para ser escrito y leído por personas ● Los problemas se pueden resolver con un lenguaje de propósito general, pero...
  • 3.
    Especificar soluciones ● Aveces, puede ser una buena opción crear un lenguaje específico para ese problema (o sea, un DSL) ● Ese lenguaje permite expresar la solución de una forma más sencilla
  • 4.
    Ejemplo 1: SQL Dominio:Extraer información de un sistema de base de datos relacional SELECT * FROM VENTAS WHERE precio <= 0.99;
  • 5.
    Ejemplo 2: Expresionesregulares Dominio: Buscar y extraer patrones de texto dentro de otros textos. [A-Za-z_][A-Za-z0-9_]*
  • 6.
    Ejemplo 3: CSS Dominio:Especificar la apariencia gráfica de elementos HTML (No es un lenguaje de programación, y sus usuarios no son necesariamente desarrolladores) div.titulo { color: #BB2266; font-size: 24pt; }
  • 7.
    Más ejemplos ● Html ●Django templates, Jinja2 ● MarkDown, RST ● Ficheros de configuración ● Graphviz ● Make, Rake ● Gherkin Syntax ● AWK ● XAQL - Xapian Query Language Parser ● Less, Sass ● YAML, JSON ● SIML ● ...
  • 8.
    Ejemplos de lenguajesque no son DSL (IMHO) ● R (Estadísticas) – Restringido a un dominio (estadísticas), pero en realidad es un lenguaje turing-completo ● XSLT – Restringido a un dominio (Transformar contenidos XML), pero no pensado para ser leído o escrito por humanos (cuerdos)
  • 9.
    Características que definenun DSL ● No están pensado para ser de uso general (rara vez un DSL es turing-completo) ● Hacen únicamente lo que tienen que hacer, nunca se salen del dominio ● Pensados para ser leídos y escritos por personas (aunque se puedan generar automáticamente)
  • 10.
    Ventajas ● El lenguajees más legible y más corto ● No necesita ser Turing-completo ● Puede ser usado por personal no técnico ● Flexible y adaptable ● Es código: Podemos transformarlo, almacenarlo, distribuirlo, representarlo, etc... ● Fomenta la colaboración entre personas de distintas especialidades ● Control de versiones
  • 11.
  • 12.
    DSL Interno ● Expandeel interprete de un lenguaje de propósito general. Python puede hacer cosas así con Macros, metaprogramación, el módulo ast, etc... – Fácil, pero te puede condicionar mucho – Muy usado en lenguajes como scala, groovy, lisp... – Parser ya incluido – Ventajas: toda la potencia del lenguaje base – Desventajas: toda la potencia del lenguaje base
  • 13.
    Usar AST enPython ● Nos permite modificar el árbol AST que implementa el propio Python para interpretarse. ● Muy potente, muy loco, muy peligroso, muy divertido. Elige una.
  • 14.
    Usar Metaclases ● Elejemplo más famoso es la definición de modelos en Django. ● Muy potente, algo loco, no tan peligroso como el AST
  • 15.
    DSL Externos ● Ellenguaje es totalmente independiente. – Tenemos que escribir nuestro propio lenguaje – Mola – Pero puede dar miedito – No es tan terrible como parece
  • 16.
    Características DSL externo ●Tienes que crear tu propio parser y tu propia Gramática (más sobre estas palabrejas más adelante) ● Libertad absoluta ● La sintaxis del lenguaje solo está condicionada por el dominio (y tu talento)
  • 17.
    Estructura de unlenguaje ● Distintos autores usan diferentes nombres, pero básicamente consta de 4 fases: – Scanner – Analizador Léxico, Tokenizer o Lexer – Analizador Sintáctico o Parser* – Intérprete o generación de código ● Cada capa o fase usa los servicios de la anterior y pasa sus resultados a la siguiente * El turrón está aquí / The Nougat is here
  • 18.
    Scanner ● La primeracapa y la más sencilla. Simplemente lee la entrada carácter a carácter. ● A veces puede ser un poco más complicada. por ejemplo, puede que necesite leer caracteres por delante del actual, para poder tomar decisiones – Por ejemplo, para discriminar entre a++ y a+1
  • 19.
    Lexer/Tokenizer ● También llamadoanalizador léxico ● Usando los servicios del scanner, lee una secuencia de bytes y devuelve una serie de unidades léxicas o Tokens (Si fuera un lenguaje humano, palabras y signos) ● Es razonablemente sencillo de implementar. Una técnica muy usada es usar expresiones regulares. ● Los tokens devueltos están etiquetados para saber de que tipo son (Si fuera un lenguaje humano, la palabra y su función, p.e. verbo).
  • 20.
    Parser ● Organiza lostokens entregados por el lexer de forma que sean procesables. Esto es, normalmente, en forma de árbol. ● Para ello utiliza una Gramática del lenguaje. ● Existen varios algoritmos, cada uno con sus ventajas e inconvenientes.
  • 21.
    Generación/Ejecución de código ●La última fase. En el caso de un lenguaje de programación general, se genera o ejecuta código, bytecodes o código ejecutable. ● Normalmente en un DSL se crea o controla un módelo creado por nosotros. ● En cualquier caso, es la fase final. Lo que hagamos con el árbol es cosa nuestra.
  • 22.
    ¿Para qué sirvela gramática?
  • 23.
    ¿Para qué sirvela gramática? ● El parser usa la gramática para: – Detectar si la entrada es correcta, es decir, si pertenece al lenguaje. – Organizar los tokens de entrada en forma de Árbol de sintáxis o Árbol de Sintáxis Abstracta o AST. – Se le clasifica de abstracto porque no representa fielmente todos los detalles de la sintaxis. – Por ejemplo, los caracteres ; que diferencia los líneas en lenguajes como C, se suelen eliminar del AST
  • 24.
    Ejemplo AST Los paréntesisno se incorporaron al AST, no son necesarios
  • 25.
    Definición formal deGramática ● Una gramática es la definición matemática de un lenguaje ● La gramática nos dice si un texto pertenece al lenguaje o no ● Para ello, establece unas reglas de producción; si usando esas reglas se puede llegar a obtener un texto, entonces ese texto pertenece al lenguaje
  • 26.
    Gramáticas ● Desde unpunto de visto formal, una gramática es un conjunto de reglas llamadas producciones ● De esas reglas, una debe ser la inicial ● Las reglas tienen la forma: <parte derecha> → <parte izquierda> ● la → se suele representar de muchas maneras, pero viene a significar “se define como”
  • 27.
    Mejor con unejemplo Supongamos un lenguaje muy, muy sencillo: Begin Bob 12 Alice 26 End Entre Begin y End, una lista de varios nombres, junto con un número.
  • 28.
    Unas aclaraciones previas Losnombres están formados por una sola palabra; “Bob” es válido, “Robert Drake” no. La primera letra del nombre tiene que ser alfabética, pero luego se aceptan números: “labo3” es válido pero “3ell” no. Los números son enteros, no se aceptan decimales, “123” es válido, “3.14” no. Al menos tiene que venir una línea de datos
  • 29.
    Una gramática Program -->Begin line+ End line --> id num id --> [A-Za-z][A-Za-z0-9]* num --> [0-9]+
  • 30.
    Otra gramática Program -->Begin lines End lines → line+ line --> id num id --> [A-Za-z][A-Za-z0-9]* num --> [0-9]+
  • 31.
  • 32.
    Nomenclatura y otrasyerbas ● Cualquier símbolo que aparezca a la izquierda de una flecha es un símbolo No Terminal. ● Cualquier símbolo que no aparezca en la parte izquierda de ninguna regla es un Terminal o Literal ● En la parte de la derecha de la regla pueden aparecer terminales, no terminales y símbolos especiales
  • 33.
    Símbolos especiales ● |Representa alternativas, a|b significa que se puede generar a o b ● * Significa repetición de cero o más ● + Significa repetición de uno o más ● ? Opcional (uno o cero) ● [a-z] Rango de caracteres [0-9] se podría representar como 0|1|2|3|4|5|6|8|9
  • 34.
    Parser Recursivo Descendente ●Hay varios tipos de parsers ● Uno de los más simples es el Parser Recursivo Descendente ● Es tan simple que se puede escribir a mano ● Vamos a hacerlo
  • 35.
    Parser LL(1) ● Necesitamosdefinir una función para cada símbolo no terminal de la gramática ● El parser consiste en una serie de llamadas recursivas ● En nuestro caso, debemos escribir una función para cada uno de los símbolos: – program – line – id – num
  • 36.
    Además, necesitamos: ● Queel tokenizer haya hecho su trabajo, y nos haya entregado una lista o cola de tokens (Lo simularemos) ● Una función que nos permita examinar el siguiente token a procesar ● Una función auxiliar, consume, que retira un elemento de la lista de tokens si es el que esperamos. Si no lo fuera, da un error.
  • 37.
    Lista de Tokens Token= namedtuple('Token', ['kind', 'value']) buffer = [ Token('BEGIN', 'Begin'), Token('ID', 'Bob'), Token('NUM', 12), Token('ID', 'Alice'), Token('NUM', 26), Token('END', 'End'), ] pos = 0
  • 38.
    Función auxiliar reduce defconsume(token): global buffer, pos tk = buffer[pos] if tk.kind != token: raise ParserError(...) pos += 1 return True
  • 39.
    Función program def program(): begin() r= line() while r: r = line() end() if not eof(): raise ParserError(...) return True
  • 40.
  • 41.
    Función line def line(): globalbuffer, pos tk = buffer[pos] if tk.kind == 'ID': pos += 1 tk = buffer[pos] if tk.kind == 'NUM': pos += 1 else: raise ParserError(...) return True return False
  • 42.
  • 43.
    Ventajas ● Muy fácilde escribir: se siguen unas reglas para cada tipo de símbolo y regla de la gramática. ● Fácil de entender ● Si cambia la gramática, se cambia el programa ● La primera vez es divertido
  • 44.
    Inconvenientes ● Los detallesde la gramática están “perdidos” en el código; no contamos con una representación explícita de la gramática ● Si la gramática es complicada, es tedioso ● Algunas gramáticas no se puede parsear así. Las reglas recursivas, de tipo – expr → expr (+|-|*|+) expr hacen que el parser entre un un bucle sin fin ● La décima vez ya no es tan divertido...
  • 45.
    Soluciones ● Afortunadamente, hayformas de generar un parser automáticamente a partir de la gramática. ● El parser que hemos hecho es de tipo LL. La segunda L indica que hacemos un análisis descendente, es decir, empezamos en la regla inicial y bajamos ● Otro tipo de parsers son los LR o ascendentes, en este caso, comenzamos desde la parte derecha de las reglas y vamos subiendo hasta la regla inicial.
  • 46.
    Características de lasparsers LR ● El análisis se hace de forma ascendente, por lo que pueden usarse reglas gramaticales recursivas ● La gramática pueda ser más sencilla (Pero aun hay ciertos problemas, por ejemplo con gramáticas ambiguas) ● En general, más poderosos que los LL ● Existe formas automáticas de generarlos, igual que los LR
  • 47.
    Técnicas de generaciónde parser ● No tenemos tiempo de ver más de las técnicas de creación de parsers, pero como se pueden imaginar, es un campo amplio y complejo. ● Lo bueno es que no nos hace falta. Podemos usar un generador
  • 48.
    Algunos generadores deparser ● Lex y Yaccç; Lex se encarga del tokenizer y Yacc del parser. Generan código C y el parser resultante es LR ● Flex y Bison son similares, pero generan C++ ● ANTL-R es una herramienta escrita en Java pero que puede general código en Python, entre otros lenguajes. Genera parsers LL(K). ● Hay muchísimos más...
  • 49.
    Centrándonos en Python ●También hay muchos, algunos de los más destacados son: – Plex – Grako – PLY – Pyparsing – Muchos más ... http://nedbatchelder.com/text/python-parsers.html
  • 50.
    Pyparsing ● Vamos aver como resuelve Pyparsing el lenguaje anterior ● La idea es componer, a base de elementos más pequeños, el parser ● La estructura refleja la gramática (pero al revés, la regla inicial es la última, para evitar referencias futuras)
  • 51.
    Pyparsing from pyparsing import(Literal, Word, nums, alphas, Optional, OneOrMore, Group) num = Word(nums) id = Word(alphas) line = id + num lines = OneOrMore(line) begin = Literal('Begin') end = Literal('End') program = begin + lines + end
  • 52.
    Cambios en lagramática Begin Bob 12 Alice 26 Charles 33+22 Daniel 12-133 End
  • 53.
    Fácil de reflejaren el código num = Word(nums) op = Literal('+') | Literal('-') id = Word(alphas) line = Group(id + num + Optional(op + num)) lines = OneOrMore(line) begin = Literal('Begin') end = Literal('End') program = begin + lines + end
  • 54.
    Ejecutemos el parser ['Begin','Bob', '12', 'Alice', '26', 'End']
  • 55.
    ¡Pero eso esun split! ¡Qué engaño! ¡A la hoguera con él!
  • 56.
    ¡Tranquilidad! ● Nosotros podemosincluir las acciones que queramos, asociadas a los elementos que constituyen las reglas. ● Como no hay acciones, el resultado es una simple lista de tokens. ['Begin', 'Bob', '12', 'Alice', '26', 'End']
  • 57.
    Añadamos acciones begin =Suppress(Literal('Begin')) end = Suppress(Literal('End')) num = Word(nums) op = Literal('+') | Literal('-') id = Word(alphas) line = Group(id + num + Optional(op + num)) lines = OneOrMore(line) program = begin + lines + end num.setParseAction(lambda p: int(p[0]))
  • 58.
    Esto está mejor Ejecutemosejemplo_pyparsing_2.py: ['Begin', ['Bob', 12], ['Alice', 26], 'End']
  • 59.
    Las acciones nospermiten construir nuestro modelo (o un AST) def parse_line(s, loc, toks): items = toks[0] if len(items) == 4: op = items[2] delta = items[3] if op == '+': v = items[1] + delta elif op == '-': v = items[1] - delta return (items[0], v) else: return tuple(items) line.setParseAction(parse_line)
  • 60.
    Mucho mejor Ejecutemos ejemplo_pyparsing_3.py ejemplo3.txt: [('Bob',12), ('Alice', 26), ('Charles', 55), ('Daniel', 29)] Begin Bob 12 Alice 26 Charles 33+22 Daniel 72-43 End
  • 61.
    Ventajas de Pyparsing ●Sencillo (pero potente, como Python) ● 100% pure python ● La gramática está reflejada en el código, pero es legible ● Se puede testear por partes ● Es relativamente lento comparado con otras opciones, pero no suele ser problema para un DSL
  • 62.
    Un ejemplo mayor ●Grafel: Un lenguaje para hacer animaciones sencillas, pensado para no programadores ● Desarrollado en una semana ● Las animaciones de esta presentación están hechas en él ● Usa pyparsing para el lenguaje, PyGame para realizar las animaciones.
  • 63.
    Hola, mundo grafel Cast: a= Box pos 110x160 Actions: 25-75 a Swing 1090x600
  • 64.
    Ejemplo de usode pyparsing attrs = ZeroOrMore(attr) Identifier = Word(alphas, alphanums) role = oneOf( 'Square Rect RoundRect Star Dice Label' ' Text Circle Box Triangle Bitmap Path' ) castline = ( Identifier('name') + Suppress('=') + role('role') + Group(attrs)('params') )
  • 65.
    Actores ● Square ● Box ●Rect ● Circle ● Path ● Polygon ● Triangle ● Star ● RoundRect ● Dice ● Text ● Label ● Bitmap
  • 66.
    Acciones ● Blink ● Colorize ●Enter ● FadeIn ● FadeOut ● Exit ● Background ● Move ● Fall ● Land ● Swing ● EaseIn ● EaseOut ● Timer
  • 67.
    Libros ● Esta esla biblia de como construir tu propio intérprete o compilador
  • 68.
    No lo recomiendopara un DSL ● Es demasiado complejo. Es EL LIBRO si vas a implementar un lenguaje completo, pero un DSL es mucho más sencillo.
  • 69.
    Recomendaciones Domain-Specific Languages Martin Fowler,Rebbeca Parsons Addison Wesley Language Implementation Patterns Terence Parr Pragmatic Bookshelf
  • 70.
    Gracias por su tiempo Repositoriode Grafel: http://bitbucket.org/euribates/grafel Juan Ignacio Rodríguez de León @jileon en twitter