天天看点

一个基于约束传播的微型计算语言的设计和实现

一个基于约束传播的,玩具级微型计算语言的设计和简单实现。

这个程序就是做来玩和练习的,代码是玩具级别的,用的python,基本可以正常工作了。

先介绍应用背景:

在流体机械设计中,通常根据性能参数进行设计,算出其它变量,但问题是,在设计过程中,需要进行变量的手工调整,例如圆整,修正到某一范围,校核等等。

其计算模式举例如下:

1.定义变量,如输入压力Pin=0.98,输入温度Tin=27,输入流量Qvin=400,kv2,φ2r,b2,D2,u2,qin等等。。。

2.根据某些物理公式,算出几个新的量,如转速 n=33.9*sqrt(kv2*φ2r*b2/D2*(u2^3)/qin)

3.把n从8296.93圆整为整数8300,

4.重新计算b2/D2=0.06455,校核可知0.02<0.06455<0.065,符合要求

5.根据n计算出其它新的变量,修正,校核。。。

。。。

观察可以发现,这种计算模式,和《计算机程序的构造与解释》中提到的约束传播系统很像,如果把一个变量看作一个对象,那么,当它位于一个公式的左侧,例如n,也就意味着,右侧变量例如kv2更新时,应该给它发送一个消息,让它重新计算自己的值,当n更新后,如果它发现有别的变量依赖于自己,它应该发消息通知它们更新自己的值。

还可以看出,这种依赖关系形成了一个图,例如应该有一条从kv2到n的边,把n称为kv2的订阅者。

所以这种计算模式可以用约束传播系统建模,但是此处和书里的约束传播系统有差异:此处的约束传播系统是有向图,而书里是无向图,设计成有向图主要是为了简单,无向图的消息发送顺序是难以控制的,而且构造的时候公式中的每个变量都要持有其它对象的引用,太麻烦,有向图只需要在公式左侧的那个变量哪里保存公式右侧的每个变量的引用。

形成有向图后,每个变量的状态设为invaild,这种状态下,不会向它的会订阅者发送更新消息,收到获取值的消息时报错。

有向图中,还有一些源点,是最初给定值的变量。

当某个变量被赋值时,它把自己设为新值,同时向它的订阅者发送更新消息。订阅者计算自己的新值,如果和旧值相同,就沉默;否则,更新自己,通知订阅者更新。

so,想象一下,在你的面前,虚空之中漂浮着一张有向图, 由kv2—>n这样的一条条边练成,当一个点被赋予值,从这点荡出一圈圈涟漪,传到它的下一级,再从更新过的点荡出新的波纹,传开,传开。。。直到所有的点都收敛,水面恢复宁静。

好了,说代码,每一个变量都要保存它的订阅者,它的表达式,注意到,数字1.1是一种变量,变量a是一种表达式,所以代码如下:

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

100

101

102

103

104

105

106

107

108

109

#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""

变量is-a表达式

数值is-a表达式

故有如下继承关系

通过env的符号表可以查到Expr的实例

"""

__all__ = ['Expr','Var','Number']

class Expr(object):

op="" #a function

parameters=[] #Expr list

desc=""

def __init__(self,op,paras,desc=""):

self.op=op

self.parameters=paras

self.desc=desc

def get_value(self):

pl=[p.get_value() for p in self.parameters]

return self.op(*pl)

def set_desc(self,d):

self.desc=d

def dump(self):

pas=[]

if len(self.parameters):

pas=[s.dump() for s in self.parameters]

pas.insert(0, '('+self.op.__name__)

return ' '.join(pas) + ')'

class Number(Expr):

value=0.0

def __init__(self,v):

self.value=v

def get_value(self):

return self.value

def dump(self):

return str(self.value)

def update(self):

pass

class Var(Expr):

name=""

desc=""

expr=None

value=0.0

subscribers=[]

state="invaild"

def __init__(self,name,desc=""):

self.name=name

self.desc=desc

self.state="invaild"

def broadcast(self):

for s in self.subscribers:

s.update()

def update(self):

self.state="normal"

new_value=self.expr.get_value()

if new_value == self.value:

return

self.value=new_value

self.broadcast()

def set_expr(self,exp):

self.expr=exp

if isinstance(exp,Number):

self.update()

def set_value(self,v):

self.value=v

self.state="normal"

self.broadcast()

def get_value(self):

if self.state=="invaild":

self.update()

assert self.state=="normal"

return self.value

def subscribe(self,subs):

for sub in subs:

self.subscribers.append(sub)

def dump(self):

expr_d=""

if self.expr:

expr_d=' '+self.expr.dump()

return str(self.name) +"="+str(self.value)+expr_d#+" "+self.desc

def test():

a=Var("a","变量a")

b=Var("b","变量b")

if __name__=="__main__":

test()

所有的变量当然是要保存到一个符号表(或称环境)里的,同时,这个环境里还要有加减乘除,sin,sqrt这样的基本运算的定义,pi,e这样的常数的定义,python的operator和math模块就够用了。

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

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

#!/usr/bin/env python

# -*- coding: utf-8 -*-

import math

import operator

from expr import Var,Number,Expr

from parser import Parser

class CmdFilter(object):

def __init__(self,env,input_file):

self.env=env

self.input_file=input_file

def readline(self):

while True:

s=self.input_file.readline()

if not s:

return s

if self.env.filter_cmd(s):

return s

class Env(object):

"""

求值环境,提供变量符号表和函数符号表

"""

symbol_table={} #存放变量

expr_table=[] #存放自由表达式

function_table={}#存放函数,对外只读

cmds=['dump','run'] #env先于parser处理掉一些命令,如dump

parser=None

def __init__(self):

self.fill_function_table()

self.fill_symbol_table()

self.parser=Parser(self)

def dump(self):

print "-"*70,"\ndump all variables and expressions:"

for k,v in self.symbol_table.items():

print k+":",v.get_value()

print "\nall checkings:"

for e in self.expr_table:

print e.get_value(),"=",e.dump()

print "-"*70

def run(self):

for k,v in self.symbol_table.items():

v.update()

def fill_function_table(self):

#算术运算符

#1.+,-,*,/,^,=,(,) 算术运算符

self.function_table['+']=operator.add

self.function_table['-']=operator.sub

self.function_table['*']=operator.mul

self.function_table['/']=operator.div

self.function_table['^']=operator.pow

#逻辑运算符

#2.==,>=,>,&lt;=,&lt;,!= 逻辑运算符

self.function_table['==']=operator.eq

self.function_table['>=']=operator.ge

self.function_table['>']=operator.gt

self.function_table['&lt;=']=operator.le

self.function_table['&lt;']=operator.lt

self.function_table['!=']=operator.ne

self.function_table['sqrt']=math.sqrt

self.function_table['sin']=math.sin

self.function_table['cos']=math.cos

self.function_table['tan']=math.tan

self.function_table['asin']=math.asin

self.function_table['acos']=math.acos

self.function_table['atan']=math.atan

self.function_table['exp']=math.exp

self.function_table['pow']=math.pow

self.function_table['factorial']=math.factorial

self.function_table['fabs']=math.fabs

self.function_table['ln']=math.log

self.function_table['log10']=math.log10

def fill_symbol_table(self):

self.symbol_table['pi']=Number(math.pi)

self.symbol_table['e'] =Number(math.e)

def add_expr(self,e):

if e:

self.expr_table.append(e)

def get_function(self,name):

if self.function_table.has_key(name):

return self.function_table[name]

else:

return None

def get_variable(self,name):

if self.symbol_table.has_key(name):

return self.symbol_table[name]

else:

return None

def set_variable(self,name,var):

self.symbol_table[name]=var

def filter_cmd(self,s):

s=s.strip()

if s in self.cmds:

fun=getattr(self,s)

fun()

return None

return s

def exec_stream(self,in_file):

input_file=CmdFilter(self,in_file)

self.parser.parse(input_file)

import sys

def test():

env=Env()

env.exec_stream(sys.stdin)

if __name__=="__main__":

test()

接下来,词法分析和语法分析,词法分析没有手写,也没有用flex那样的工具,直接用了一排正则表达式,挨个往下匹配,匹配上了就返回。

严格来说,这个是不太好的,此处的词法分析原理上是不能和flex比的,flex里的多个正则表达式是合并到一个NFA里,再转化成一个DFA的,所以它的规则首先是最长优先,其次是长度相同时写在前面的优先,此处只有写在前面的优先,不太好。

语法分析是递归下降文法分析。一行一行地分析,一行是一个token的list。

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

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from math import *

from operator import *

import re

from expr import Var,Number,Expr

""" 把

a=1+b+c^2

c=2

b=c+2

这样的一行一行,分成

ID ASSIGN ADD ID ADD EQ POW 2 EOL

ID ASSIGN 2 EOL

ID ASSIGN ID ADD 2 EOL

这样的一行一行token,

输出是一行一个token list的形式

token分为如下几类:

1.+,-,*,/,^,=,(,),, 算术运算符

2.==,>=,>,<=,<,!= 逻辑运算符

3.[0-9]+\.[0-9]*[Ee][+-]?[0-9]+ [0-9]+ 字面浮点数和整数

4.[a-zA-Z_]+ 变量或函数名称标识符

5.[ \\t\\n] 忽略,或结束

由于使用正则表达式直接匹配,所以和flex不同的是:

无法确保当有多个匹配项时,最长优先,因此,只能利用先后顺序解决冲突,

因而必须把==放在=前面。

"""

logic_op_re=re.compile(r'==|>=|>|<=|<|!=')

number_re =re.compile(r'[+-]?[0-9]+\.?[0-9]*[Ee]?[+-]?[0-9]?')

arith_op_re=re.compile(r'\+|-|\*|/|\^|=|\(|\)|,')

int_re =re.compile(r'[0-9]+')

id_re =re.compile(r'[a-zA-Z_]+')

blank_re =re.compile(r'[\ |\t|\r]+')

comment_re =re.compile(r'"([^"]*)"')

other_re =re.compile(r'.+')

def scanner(line):

result=[]

while True:

line=line.strip()

if not line:

return result

m=re.match(logic_op_re,line)

if m:

result.append(('logic_op',m.group()))

line=line[m.end():]

continue

m=re.match(number_re ,line)

if m:

result.append(('number',float(m.group())))

line=line[m.end():]

continue

m=re.match(arith_op_re,line)

if m:

result.append(('arith_op',m.group()))

line=line[m.end():]

continue

m=re.match(int_re ,line)

if m:

result.append(('number',float(m.group())))

line=line[m.end():]

continue

m=re.match(id_re ,line)

if m:

result.append(('id',m.group()))

line=line[m.end():]

continue

m=re.match(comment_re ,line)

if m:

result.append(('comment',m.group()))

line=line[m.end():]

continue

m=re.match(blank_re ,line)

if m:

line=line[m.end():]

continue

m=re.match(other_re,line)

if m:

print "亲,看看你都塞了一堆什么进来呀?\""+m.group()+"\" 人家好伤心的呀!"

line=line[m.end():]

return result

class Parser(object):

""" 文法分析: """

input_file=None

env=None

def __init__(self,env):

self.env=env

def parse(self,input_file):

"""

如入可以是sys.stdin,a file,a string

要求实现readline()方法

"""

self.input_file=input_file

self.run()

def run(self):

while True:

line=self.input_file.readline()

if not line:

return

tokens=scanner(line)

#把字母名称的函数标示出来

r=[]

for t in tokens:

if t[0]=='id' and self.env.get_function(t[1]):

r.append(('function',t[1]))

else:

r.append(t)

tokens=r

#把comment提取出来

comments=map(lambda x:x[1],

filter(lambda x:x[0]=="comment",tokens))

comments=' '.join(comments)

tokens=filter(lambda x:x[0]!="comment",tokens)

#含有=的表达式是约束方程,其它的都是expr

c=tokens.count( ('arith_op', '='))

if c==0:

#没有约束到变量的表达式

e=self.parse_expr(tokens)

#e.set_desc(comments)

self.env.add_expr(e)

continue

if c>1:

print "亲,赋值一次就够了,你肿么赋值了"+str(c)+"次涅?"

continue

#c=1

if len(tokens)<3 or tokens[0][0]!='id' or \

tokens[1]!=('arith_op','='):

print "亲,你给我的表达式格式有问题偶~:"+line

continue

var_name=tokens[0][1]

var=self.env.get_variable(var_name)

if var is None:

var=Var(var_name,comments)

self.env.set_variable(var_name,var)

e=self.parse_expr(tokens[2:])

var.set_expr(e)

def parse_expr(self,tokens):

"""

token分为如下几类:

1.+,-,*,/,^,=,(,),, 算术运算符

2.==,>=,>,<=,<,!= 逻辑运算符

3.[0-9]+\.[0-9]*[Ee][+-]?[0-9]+ [0-9]+ 字面浮点数和整数

4.[a-zA-Z_]+ 变量或函数名称标识符

5.[ \\t\\n] 忽略,或结束

BNF:

expr=expr[==|>=|>|<=|<|!=]expr|expr

expr=expr+expr | expr-expr

expr=expr*expr | expr/expr

expr=expr^expr

expr=function(expr[,expr])

expr=(expr)

expr=<float>|<var>

"""

if len(tokens):

expr,rest=self.parse_logic_op(tokens)

return expr

#能处理就处理,不能处理原样返回。

def parse_logic_op(self,tokens):

left,rest=self.parse_add_sub_op(tokens)

if not len(rest):

return left,rest

logic_op_list=["==",">=",">","<=","<","!="]

if rest[0][1] not in logic_op_list:

return left,rest

op=self.env.get_function(rest[0][1])

right,rest=self.parse_add_sub_op(rest[1:])

return Expr(op,[left,right]),rest

def parse_add_sub_op(self,tokens):

left,rest=self.parse_mul_div_op(tokens)

add_sub_op_list=["+","-"]

while len(rest) and rest[0][1] in add_sub_op_list:

op=self.env.get_function(rest[0][1])

right,rest=self.parse_mul_div_op(rest[1:])

left=Expr(op,[left,right])

return left,rest

def parse_mul_div_op(self,tokens):

left,rest=self.parse_pow_op(tokens)

mul_div_op_list=["*","/"]

while len(rest) and rest[0][1] in mul_div_op_list:

op=self.env.get_function(rest[0][1])

right,rest=self.parse_pow_op(rest[1:])

left=Expr(op,[left,right])

return left,rest

def parse_pow_op(self,tokens):

left,rest=self.parse_function_op(tokens)

pow_op_list=["^"]

while len(rest) and (rest[0][1] in pow_op_list):

op=self.env.get_function(rest[0][1])

right,rest=self.parse_pow_op(rest[1:])

left=Expr(op,[left,right])

return left,rest

def parse_function_op(self,tokens):

if tokens[0][0] in ['number','id']:

return self.parse_float_var_op(tokens)

if tokens[0][1]=='(':

return self.parse_parentheses_op(tokens)

if tokens[0][0]!='function':

return None,tokens

op=self.env.get_function(tokens[0][1])

if op and tokens[1][1]=='(':

paras=[]

tokens=tokens[2:]

left,tokens=self.parse_add_sub_op(tokens)

paras.append(left)

while tokens[0][1]==',':

left,tokens=self.parse_add_sub_op(tokens[1:])

paras.append(left)

if tokens[0][1]==')':

tokens=tokens[1:]

else:

print "bad syntax. tokens found ->",tokens

expr=Expr(op,paras)

return expr,tokens

else:

print "error bad syntax ->",tokens

return None,tokens

def parse_parentheses_op(self,tokens):

if tokens[0][1]=='(':

left,tokens=self.parse_logic_op(tokens[1:])

if tokens[0][1]==')':

return left,tokens[1:]

return left,tokens

return None,tokens

def parse_float_var_op(self,tokens):

if tokens[0][0] == 'number':

n=Number(tokens[0][1])

return n,tokens[1:]

if tokens[0][0] == 'id':

var=self.env.get_variable(tokens[0][1])

if not var:

var_name=tokens[0][1]

var=Var(var_name,'')

self.env.set_variable(var_name,var)

var=self.env.get_variable(tokens[0][1])

return var,tokens[1:]

return None,tokens

import StringIO

from env import *

def test():

s=""" a=1+(b+c)^2/23.1e-10 "变量a"

c=2 "变量c" "c是个好变量"

b=c+2 "变量b" "b也是个好变量" "这是为它写的第三条注释"

a>c "检查a是否大于c"

a>=c "检查a是否大于等于c"

run

dump

c=3 "change c again."

"注释也可以在前面" c^2>=sin(pow(a,b)+b)

run

dump

"""

#for l in s.split('\n'):

# scanner(l)

print "+"*80

print '*'*70

print s

print '*'*70

e=Env()

i=StringIO.StringIO(s)

e.exec_stream(i)

print "+"*80

if __name__=="__main__":

test()

最后是个main.py

1

2

3

4

5

6

7

8

9

10

11

12

#!/usr/bin/env python

# -*- coding: utf-8 -*-

import sys

from env import Env

import StringIO

e=Env()

e.exec_stream(sys.stdin)

s=""" x=-1"""

i=StringIO.StringIO(s)

e.exec_stream(i)