用Python撸DSL(领域特定语言)

何为DSL:

DSL就是一种特定解决领域问题的的迷你语言。如SQL语言,正则表达式

内部与外部

指的是实现DSL的方式是否与宿主语言隔离。

内部DSL(Internal DSL)
所谓内部DSL,就是用一种提供语言扩展功能的宿主语言来扩充的。
如Django中的Model

外部DSL(External DSL)
外部DSL则与源语言无关,重头写词法分析器、语法分析器以及解释执行器。

为何要编写DSL

举个例子,一个HTML表格

1
2
3
4
5
<form>
<label>Name:</label><input type=”text” name=”name”/>
<label>Email:</label><input type=”text” name=”email”/>
<label>Password:</label><input type=”password” name=”name”/>
</form>

编写这个HTML文档需要编写者具有HTML的知识
我们尝试着编写自己的DSL,将语句简化为

1
2
3
4
UserForm
username:CharField -> label:Username size:25;
email:EmailField -> size:32;
password:PasswordField;

下面我们使用pyparsing库,来制作一款属于自己的DSL语言


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from pyparsing import Word, OneOrMore, Group, Suppress, alphanums, oneOf, alphas, Optional, lineEnd
def convert_prop_to_dict(tokens):
prop_dict = {}
for token in tokens:
prop_dict[token[0]] = token[1]
return prop_dict
# Suppress 表示不捕获
newline = Suppress(lineEnd)
colon = Suppress(":")
arrow = Suppress("->")
semicolon = Suppress(";")
word = Word(alphas)
key = word
value = Word(alphanums)
field_type = oneOf("CharField EmailField PasswordField")
field_name = word
form_name = word.setResultsName("form_name")
field_property = Group(key + colon + value)
field = Group(field_name + colon + field_type + Optional(arrow + OneOrMore(field_property) + semicolon).setParseAction(
convert_prop_to_dict))
form = form_name + newline + OneOrMore(field).setResultsName("form_field")
input_form = '''
UserForm
username:CharField -> label:Username size:25;
email:EmailField -> size:32;
password:PasswordField;
'''
'''
External DSL
'''
def get_field_html(field):
properties = field[2]
label = properties["label"] if "label" in properties else field[0]
label_html = "<label>" + label + "</label>"
attributes = {"name": field[0]}
attributes.update(properties)
if field[1] == "CharField" or field[1] == "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.form_field])
return "<form id='" + form.form_name.lower() + "'>" + fields_html + "</form>"
print(render(form.parseString(input_form)))
'''
Internal DSL
'''
class HtmlElement(object):
default_attributes = {}
tag = "unknown_tag"
def __init__(self, *args, **kwargs):
self.attributes = kwargs
self.attributes.update(self.default_attributes)
self.children = args
def __str__(self):
attribute_html = " ".join(["{}='{}'".format(name, value)
for name, value in self.attributes.items()])
if not self.children:
return "<{} {}/>".format(self.tag, attribute_html)
else:
children_html = "".join([str(child) for child in self.children])
return "<{} {}>{}</{}>".format(self.tag, attribute_html, children_html, self.tag)
# print(HtmlElement(id="test"))
# <unknown_tag id='test'/>
# print (HtmlElement(HtmlElement(name="test"), id="id"))
# <unknown_tag id='id'><unknown_tag name='test'/></unknown_tag>
class InputElement(HtmlElement):
tag = "input"
def __init__(self, *args, **kwargs):
HtmlElement.__init__(self, *args, **kwargs)
self.label = self.attributes["label"] if "label" in self.attributes else self.attributes["name"]
if "label" in self.attributes:
del self.attributes["label"]
def __str__(self):
label_html = "<label>{}</label>".format(self.label)
return label_html + HtmlElement.__str__(self) + "<br/>"
# print(InputElement(name="username"))
# <label>username</label><input name='username'/><br/>
# print(InputElement(name="username", label="User ID"))
# <label>User ID</label><input name='username'/><br/>
class Form(HtmlElement):
tag = "form"
class CharField(InputElement):
default_attributes = {"type": "text"}
class EmailField(CharField):
pass
class PasswordField(InputElement):
default_attributes = {"type": "password"}
def render(form):
field_dict = {"CharField": CharField, "EmailField":
EmailField, "PasswordField": PasswordField}
fields = [field_dict[field[1]](name=field[0], **field[2]) for field in form.form_field]
return Form(*fields, id=form.form_name.lower())
print(render(form.parseString(input_form)))
文章目录
  1. 1. 何为DSL:
  2. 2. 内部与外部
  3. 3. 为何要编写DSL
|