This document discusses domain specific languages (DSLs) and provides examples of creating DSLs in Python. It explains that DSLs allow users to work in specialized mini-languages tailored to specific problem domains. As examples, it discusses SQL as a DSL for databases and regular expressions as a DSL for patterns. It then demonstrates how to create an external DSL for defining forms using PyParsing and an internal DSL for the same using Python features like metaclasses. The document concludes that DSLs make code easier to read, write and maintain for non-programmers.
Life Without RegularExpressions
def is_ip_address(ip_address):
components = ip_address_string.split(".")
if len(components) != 4: return False
try:
int_components = [int(component) for component in
components]
except ValueError:
return False
for component in int_components:
if component < 0 or component > 255:
return False
return True
6.
Life With RegularExpressions
def is_ip(ip_address_string):
match = re.match(r"^(d{1,3}).(d{1,3}).(d{1,3}).
(d{1,3})$", ip_address_string)
if not match: return False
for component in match.groups():
if int(component) < 0 or int(component) > 255:
return False
return True
7.
The DSL thatsimplifies our life
^(d{1,3}).(d{1,3}).(d{1,3}).(d{1,3})$
8.
Why DSL -Answered
When working in a particular domain, write your
code in a syntax that fits the domain.
When working with patterns, use RegEx
When working with RDBMS, use SQL
When working in your domain – create your own DSL
9.
The two typesof DSLs
External DSL – The code is written in an external
file or as a string, which is read and parsed by the
application
10.
The two typesof DSLs
Internal DSL – Use features of the language (like
metaclasses) to enable people to write code in
python that resembles the domain syntax
Creating Forms –No DSL
– Requires HTML knowledge to maintain
– Therefore it is not possible for the end user to
change the structure of the form by themselves
13.
Creating Forms –External DSL
UserForm
name->CharField label:Username
email->EmailField label:Email Address
password->PasswordField
This text file is parsed and rendered by the app
14.
Creating Forms –External DSL
+ Easy to understand form structure
+ Can be easily edited by end users
– Requires you to read and parse the file
15.
Creating Forms –Internal DSL
class UserForm(forms.Form):
username = forms.RegexField(regex=r'^w+$',
max_length=30)
email = forms.EmailField(maxlength=75)
password =
forms.CharField(widget=forms.PasswordInput())
Django uses metaclass magic to convert this
syntax to an easily manipulated python class
16.
Creating Forms –Internal DSL
+ Easy to understand form structure
+ Easy to work with the form as it is regular python
+ No need to read and parse the file
– Cannot be used by non-programmers
– Can sometimes be complicated to implement
– Behind the scenes magic → debugging hell
17.
Creating an ExternalDSL
UserForm
name:CharField -> label:Username size:25
email:EmailField -> size:32
password:PasswordField
Lets write code to parse and render this form
18.
Options for Parsing
Usingstring functions → You have to be crazy
Using regular expressions →
Some people, when confronted with a problem, think "I know, I'll use
regular expressions." Now they have two problems. - Jamie Zawinski
Writing a parser → ✓ (we will use PyParsing)
Step 3: Implementthe Grammar
newline = "n"
colon = ":"
arrow = "->"
word = Word(alphas)
key = word
value = Word(alphanums)
field_type = oneOf("CharField EmailField PasswordField")
field_name = word
form_name = word
field_property = key + colon + value
field = field_name + colon + field_type +
Optional(arrow + OneOrMore(field_property)) + newline
form = form_name + newline + OneOrMore(field)
23.
Quick Note
PyParsing itselfimplements a neat little internal
DSL for you to describe the parser grammer
Notice how the PyParsing code almost perfectly
reflects the BNF grammer
24.
Output
> print form.parseString(input_form)
['UserForm','n', 'name', ':', 'CharField', '->',
'label', ':', 'Username', 'size', ':', '25', 'n',
'email', ':', 'EmailField', '->', 'size', ':', '25', 'n',
'password', ':', 'PasswordField', 'n']
PyParsing has neatly parsed our form input into
tokens. Thats nice, but we can do more.
Output
> print form.parseString(input_form)
['UserForm','name', 'CharField', 'label', 'Username',
'size', '25', 'email', 'EmailField', 'size', '25',
'password', 'PasswordField']
All the noise tokens are now removed from the
parsed output
Output
> print form.parseString(input_form)
['UserForm',
['name', 'CharField',
[['label', 'Username'], ['size', '25']]],
['email', 'EmailField',
[['size', '25']]],
['password', 'PasswordField',[]]]
Related tokens are now grouped together in a list
Output
> parsed_form =form.parseString(input_form)
> print parsed_form.form_name
UserForm
> print parsed_form.fields[1].field_type
EmailField
Now we can refer to parsed tokens by name
31.
Step 7: ConvertProperties to Dict
def convert_prop_to_dict(tokens):
prop_dict = {}
for token in tokens:
prop_dict[token.property_key] =
token.property_value
return prop_dict
field = Group(field_name + colon + field_type +
Optional(arrow + OneOrMore(field_property))
.setParseAction(convert_prop_to_dict) +
newline).setResultsName("form_field")
32.
Output
> print form.parseString(input_form)
['UserForm',
['name', 'CharField',
{'size': '25', 'label': 'Username'}],
['email', 'EmailField',
{'size': '32'}],
['password', 'PasswordField', {}]
]
Sweet! The field properties are parsed into a dict
33.
Step 7: GenerateHTML Output
We need to walk through the parsed form and
generate a html string out of it
34.
def get_field_html(field):
properties = field[2]
label = properties["label"] if "label" in properties else field.field_name
label_html = "<label>" + label + "</label>"
attributes = {"name":field.field_name}
attributes.update(properties)
if field.field_type == "CharField" or field.field_type == "EmailField":
attributes["type"] = "text"
else:
attributes["type"] = "password"
if "label" in attributes:
del attributes["label"]
attributes_html = " ".join([name+"='"+value+"'" for name,value in attributes.items()])
field_html = "<input " + attributes_html + "/>"
return label_html + field_html + "<br/>"
def render(form):
fields_html = "".join([get_field_html(field) for field in form.fields])
return "<form id='" + form.form_name.lower() +"'>" + fields_html + "</form>"
Wish we coulddo this...
> print Form(CharField(name=”user”,size=”25”,label=”ID”),
id=”myform”)
<form id='myform'>
<label>ID</label>
<input type='text' name='name' size='25'/><br/>
</form>
Neat, clean syntax that matches the output domain
well. But how do we create this kind of syntax?
class Form(HtmlElement):
tag = "form"
class CharField(Input):
default_attributes = {"type":"text"}
class EmailField(CharField):
pass
class PasswordField(Input):
default_attributes = {"type":"password"}
Summary
+ DSLs makeyour code easier to read
+ DSLs make your code easier to write
+ DSLs make it easy to for non-programmers to
maintain code
+ PyParsing makes is easy to write External DSLs
+ Python makes it easy to write Internal DSLs