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

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

先介绍应用背景:

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

其计算模式举例如下:

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

2.根据某些物理公式,算出几个新的量,如转速 n=33.9*sqrt(kv22r*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是一种表达式,所以代码如下:

View Code
  1 #!/usr/bin/env python
  2 # -*- coding: utf-8 -*-
  3 
  4 """
  5 变量is-a表达式
  6 数值is-a表达式
  7 故有如下继承关系
  8 
  9 通过env的符号表可以查到Expr的实例
 10 """
 11 __all__ = ['Expr','Var','Number']
 12 
 13 class Expr(object):
 14     op=""               #a function
 15     parameters=[]       #Expr list
 16     desc=""
 17 
 18     def __init__(self,op,paras,desc=""):
 19         self.op=op
 20         self.parameters=paras
 21         self.desc=desc
 22 
 23     def get_value(self):
 24         pl=[p.get_value() for p in self.parameters]
 25         return self.op(*pl)
 26 
 27     def set_desc(self,d):
 28         self.desc=d
 29 
 30     def dump(self):
 31         pas=[]
 32         if len(self.parameters):
 33             pas=[s.dump() for s in self.parameters]
 34         pas.insert(0, '('+self.op.__name__)
 35         return ' '.join(pas) + ')'
 36 
 37 
 38 class Number(Expr):
 39     value=0.0
 40     def __init__(self,v):
 41         self.value=v
 42 
 43     def get_value(self):
 44         return self.value
 45 
 46     def dump(self):
 47         return str(self.value)
 48 
 49     def update(self):
 50         pass
 51 
 52 class Var(Expr):
 53     name=""
 54     desc=""
 55     expr=None
 56     value=0.0
 57     subscribers=[]
 58     state="invaild"
 59 
 60     def __init__(self,name,desc=""):
 61         self.name=name
 62         self.desc=desc
 63         self.state="invaild"
 64 
 65     def broadcast(self):
 66         for s in self.subscribers:
 67             s.update()
 68 
 69     def update(self):
 70         self.state="normal"
 71         new_value=self.expr.get_value()
 72         if new_value == self.value:
 73             return
 74         self.value=new_value
 75         self.broadcast()
 76 
 77     def set_expr(self,exp):
 78         self.expr=exp
 79         if isinstance(exp,Number):
 80             self.update()
 81 
 82     def set_value(self,v):
 83         self.value=v
 84         self.state="normal"
 85         self.broadcast()
 86 
 87     def get_value(self):
 88         if self.state=="invaild":
 89             self.update()
 90         assert self.state=="normal"
 91         return self.value
 92 
 93     def subscribe(self,subs):
 94         for sub in subs:
 95             self.subscribers.append(sub)
 96 
 97     def dump(self):
 98         expr_d=""
 99         if self.expr:
100             expr_d=' '+self.expr.dump()
101         return str(self.name) +"="+str(self.value)+expr_d#+" "+self.desc
102 
103 
104 def test():
105     a=Var("a","变量a")
106     b=Var("b","变量b")
107 
108 if __name__=="__main__":
109     test()

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

代码如下:

View Code
  1 #!/usr/bin/env python
  2 # -*- coding: utf-8 -*-
  3 import math
  4 import operator
  5 from expr import Var,Number,Expr
  6 from parser import Parser
  7 
  8 class CmdFilter(object):
  9     def __init__(self,env,input_file):
 10         self.env=env
 11         self.input_file=input_file
 12 
 13     def readline(self):
 14         while True:
 15             s=self.input_file.readline()
 16             if not s:
 17                 return s
 18             if self.env.filter_cmd(s):
 19                 return s
 20 
 21 class Env(object):
 22     """
 23     求值环境,提供变量符号表和函数符号表
 24     """
 25     symbol_table={} #存放变量
 26     expr_table=[]   #存放自由表达式
 27     function_table={}#存放函数,对外只读
 28     cmds=['dump','run']    #env先于parser处理掉一些命令,如dump
 29     parser=None
 30 
 31     def __init__(self):
 32         self.fill_function_table()
 33         self.fill_symbol_table()
 34         self.parser=Parser(self)
 35 
 36     def dump(self):
 37         print "-"*70,"\ndump all variables and expressions:"
 38         for k,v in self.symbol_table.items():
 39             print k+":",v.get_value()
 40         print "\nall checkings:"
 41         for e in self.expr_table:
 42             print e.get_value(),"=",e.dump()
 43         print "-"*70
 44 
 45     def run(self):
 46         for k,v in self.symbol_table.items():
 47             v.update()
 48 
 49     def fill_function_table(self):
 50         #算术运算符
 51         #1.+,-,*,/,^,=,(,)   算术运算符
 52         self.function_table['+']=operator.add
 53         self.function_table['-']=operator.sub
 54         self.function_table['*']=operator.mul
 55         self.function_table['/']=operator.div
 56         self.function_table['^']=operator.pow
 57         #逻辑运算符
 58         #2.==,>=,>,<=,<,!=   逻辑运算符
 59         self.function_table['==']=operator.eq
 60         self.function_table['>=']=operator.ge
 61         self.function_table['>']=operator.gt
 62         self.function_table['<=']=operator.le
 63         self.function_table['<']=operator.lt
 64         self.function_table['!=']=operator.ne
 65         self.function_table['sqrt']=math.sqrt
 66         self.function_table['sin']=math.sin
 67         self.function_table['cos']=math.cos
 68         self.function_table['tan']=math.tan
 69         self.function_table['asin']=math.asin
 70         self.function_table['acos']=math.acos
 71         self.function_table['atan']=math.atan
 72         self.function_table['exp']=math.exp
 73         self.function_table['pow']=math.pow
 74         self.function_table['factorial']=math.factorial
 75         self.function_table['fabs']=math.fabs
 76         self.function_table['ln']=math.log
 77         self.function_table['log10']=math.log10
 78 
 79 
 80     def fill_symbol_table(self):
 81         self.symbol_table['pi']=Number(math.pi)
 82         self.symbol_table['e'] =Number(math.e)
 83 
 84     def add_expr(self,e):
 85         if e:
 86             self.expr_table.append(e)
 87 
 88     def get_function(self,name):
 89         if self.function_table.has_key(name):
 90             return self.function_table[name]
 91         else:
 92             return None
 93 
 94     def get_variable(self,name):
 95         if self.symbol_table.has_key(name):
 96             return self.symbol_table[name]
 97         else:
 98             return None
 99 
100     def set_variable(self,name,var):
101         self.symbol_table[name]=var
102 
103     def filter_cmd(self,s):
104         s=s.strip()
105         if s in self.cmds:
106             fun=getattr(self,s)
107             fun()
108             return None
109         return s
110 
111     def exec_stream(self,in_file):
112         input_file=CmdFilter(self,in_file)
113         self.parser.parse(input_file)
114 
115 import sys
116 def test():
117     env=Env()
118     env.exec_stream(sys.stdin)
119 
120 if __name__=="__main__":
121     test()


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

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

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

View Code
  1 #!/usr/bin/env python
  2 # -*- coding: utf-8 -*-
  3 from math import *
  4 from operator import *
  5 import re
  6 from expr import Var,Number,Expr
  7 
  8 """  9 a=1+b+c^2
 10 c=2
 11 b=c+2
 12 这样的一行一行,分成
 13 ID ASSIGN ADD ID ADD EQ POW 2 EOL
 14 ID ASSIGN 2 EOL
 15 ID ASSIGN ID ADD 2 EOL
 16 这样的一行一行token,
 17 输出是一行一个token list的形式
 18 
 19 token分为如下几类:
 20 1.+,-,*,/,^,=,(,),,   算术运算符
 21 2.==,>=,>,<=,<,!=   逻辑运算符
 22 3.[0-9]+\.[0-9]*[Ee][+-]?[0-9]+  [0-9]+ 字面浮点数和整数
 23 4.[a-zA-Z_]+        变量或函数名称标识符
 24 5.[ \\t\\n]           忽略,或结束
 25 
 26 由于使用正则表达式直接匹配,所以和flex不同的是:
 27 无法确保当有多个匹配项时,最长优先,因此,只能利用先后顺序解决冲突,
 28 因而必须把==放在=前面。
 29 """
 30 logic_op_re=re.compile(r'==|>=|>|<=|<|!=')
 31 number_re  =re.compile(r'[+-]?[0-9]+\.?[0-9]*[Ee]?[+-]?[0-9]?') 
 32 arith_op_re=re.compile(r'\+|-|\*|/|\^|=|\(|\)|,')
 33 int_re     =re.compile(r'[0-9]+')
 34 id_re      =re.compile(r'[a-zA-Z_]+')
 35 blank_re   =re.compile(r'[\ |\t|\r]+')
 36 comment_re =re.compile(r'"([^"]*)"')
 37 other_re   =re.compile(r'.+')
 38 
 39 def scanner(line):
 40     result=[]
 41     while True:
 42         line=line.strip()
 43         if not line:
 44             return result
 45 
 46         m=re.match(logic_op_re,line)
 47         if m:
 48             result.append(('logic_op',m.group()))
 49             line=line[m.end():]
 50             continue
 51 
 52         m=re.match(number_re  ,line)
 53         if m:
 54             result.append(('number',float(m.group())))
 55             line=line[m.end():]
 56             continue
 57 
 58         m=re.match(arith_op_re,line)
 59         if m:
 60             result.append(('arith_op',m.group()))
 61             line=line[m.end():]
 62             continue
 63 
 64         m=re.match(int_re     ,line)
 65         if m:
 66             result.append(('number',float(m.group())))
 67             line=line[m.end():]
 68             continue
 69 
 70         m=re.match(id_re      ,line)
 71         if m:
 72             result.append(('id',m.group()))
 73             line=line[m.end():]
 74             continue
 75 
 76         m=re.match(comment_re ,line)
 77         if m:
 78             result.append(('comment',m.group()))
 79             line=line[m.end():]
 80             continue
 81 
 82         m=re.match(blank_re   ,line)
 83         if m:
 84             line=line[m.end():]
 85             continue
 86 
 87         m=re.match(other_re,line)
 88         if m:
 89             print "亲,看看你都塞了一堆什么进来呀?\""+m.group()+"\" 人家好伤心的呀!" 
 90             line=line[m.end():]
 91             return result
 92 
 93 class Parser(object):
 94     """ 文法分析: """
 95     input_file=None
 96     env=None
 97 
 98     def __init__(self,env):
 99         self.env=env
100 
101     def parse(self,input_file):
102         """
103         如入可以是sys.stdin,a file,a string
104         要求实现readline()方法
105         """
106         self.input_file=input_file
107         self.run()
108 
109     def run(self):
110         while True:
111             line=self.input_file.readline()
112             if not line:
113                 return
114             tokens=scanner(line)
115 
116             #把字母名称的函数标示出来
117             r=[]
118             for t in tokens:
119                 if t[0]=='id' and self.env.get_function(t[1]):
120                     r.append(('function',t[1]))
121                 else:
122                     r.append(t)
123 
124             tokens=r
125 
126             #把comment提取出来
127             comments=map(lambda x:x[1],
128                          filter(lambda x:x[0]=="comment",tokens))
129             comments=' '.join(comments)
130             tokens=filter(lambda x:x[0]!="comment",tokens)
131 
132             #含有=的表达式是约束方程,其它的都是expr
133             c=tokens.count( ('arith_op', '='))
134             if c==0:
135                 #没有约束到变量的表达式
136                 e=self.parse_expr(tokens)
137                 #e.set_desc(comments)
138                 self.env.add_expr(e)
139                 continue
140 
141             if c>1:
142                 print "亲,赋值一次就够了,你肿么赋值了"+str(c)+"次涅?"
143                 continue
144 
145             #c=1
146             if len(tokens)<3 or tokens[0][0]!='id' or \
147                tokens[1]!=('arith_op','='):
148                 print "亲,你给我的表达式格式有问题偶~:"+line
149                 continue
150 
151             var_name=tokens[0][1]
152             var=self.env.get_variable(var_name) 
153             if var is None:
154                 var=Var(var_name,comments)
155                 self.env.set_variable(var_name,var)
156 
157             e=self.parse_expr(tokens[2:])
158             var.set_expr(e)
159 
160     def parse_expr(self,tokens):
161         """
162         token分为如下几类:
163         1.+,-,*,/,^,=,(,),,   算术运算符
164         2.==,>=,>,<=,<,!=   逻辑运算符
165         3.[0-9]+\.[0-9]*[Ee][+-]?[0-9]+  [0-9]+ 字面浮点数和整数
166         4.[a-zA-Z_]+        变量或函数名称标识符
167         5.[ \\t\\n]           忽略,或结束
168 
169         BNF:
170         expr=expr[==|>=|>|<=|<|!=]expr|expr
171         expr=expr+expr | expr-expr
172         expr=expr*expr | expr/expr
173         expr=expr^expr
174         expr=function(expr[,expr])
175         expr=(expr)
176         expr=<float>|<var>
177         """
178         if len(tokens):
179             expr,rest=self.parse_logic_op(tokens)
180             return expr
181 
182     #能处理就处理,不能处理原样返回。
183     def parse_logic_op(self,tokens):
184         left,rest=self.parse_add_sub_op(tokens)
185         if not len(rest):
186             return left,rest
187 
188         logic_op_list=["==",">=",">","<=","<","!="]
189 
190         if rest[0][1] not in logic_op_list:
191             return left,rest
192 
193         op=self.env.get_function(rest[0][1])
194         right,rest=self.parse_add_sub_op(rest[1:])
195         return Expr(op,[left,right]),rest
196 
197     def parse_add_sub_op(self,tokens):
198         left,rest=self.parse_mul_div_op(tokens)
199         add_sub_op_list=["+","-"]
200 
201         while len(rest) and rest[0][1] in add_sub_op_list:
202             op=self.env.get_function(rest[0][1])
203             right,rest=self.parse_mul_div_op(rest[1:])
204             left=Expr(op,[left,right])
205 
206         return left,rest
207 
208     def parse_mul_div_op(self,tokens):
209         left,rest=self.parse_pow_op(tokens)
210         mul_div_op_list=["*","/"]
211 
212         while len(rest) and rest[0][1] in mul_div_op_list:
213             op=self.env.get_function(rest[0][1])
214             right,rest=self.parse_pow_op(rest[1:])
215             left=Expr(op,[left,right])
216 
217         return left,rest
218 
219     def parse_pow_op(self,tokens):
220         left,rest=self.parse_function_op(tokens)
221         pow_op_list=["^"]
222 
223         while len(rest) and (rest[0][1] in pow_op_list):
224             op=self.env.get_function(rest[0][1])
225             right,rest=self.parse_pow_op(rest[1:])
226             left=Expr(op,[left,right])
227         return left,rest
228 
229     def parse_function_op(self,tokens):
230         if tokens[0][0] in ['number','id']:
231             return self.parse_float_var_op(tokens)
232         if tokens[0][1]=='(':
233             return self.parse_parentheses_op(tokens)
234 
235         if tokens[0][0]!='function':
236             return None,tokens
237 
238         op=self.env.get_function(tokens[0][1])
239         if op and tokens[1][1]=='(':
240                 paras=[]
241                 tokens=tokens[2:]
242                 left,tokens=self.parse_add_sub_op(tokens)
243                 paras.append(left)
244                 while tokens[0][1]==',':
245                     left,tokens=self.parse_add_sub_op(tokens[1:])
246                     paras.append(left)
247                 if tokens[0][1]==')':
248                     tokens=tokens[1:]
249                 else:
250                     print "bad syntax. tokens found ->",tokens
251 
252                 expr=Expr(op,paras)
253                 return expr,tokens
254         else:
255             print "error bad syntax ->",tokens
256         return None,tokens
257 
258     def parse_parentheses_op(self,tokens):
259         if tokens[0][1]=='(':
260             left,tokens=self.parse_logic_op(tokens[1:])
261             if tokens[0][1]==')':
262                 return left,tokens[1:]
263             return left,tokens
264         return None,tokens
265 
266     def parse_float_var_op(self,tokens):
267         if tokens[0][0] == 'number':
268             n=Number(tokens[0][1])
269             return n,tokens[1:]
270         if tokens[0][0] == 'id':
271             var=self.env.get_variable(tokens[0][1]) 
272             if not var:
273                 var_name=tokens[0][1]
274                 var=Var(var_name,'')
275                 self.env.set_variable(var_name,var)
276                 var=self.env.get_variable(tokens[0][1]) 
277             return var,tokens[1:]
278         return None,tokens
279 
280 
281 import StringIO
282 from env import *
283 def test():
284     s=""" a=1+(b+c)^2/23.1e-10 "变量a"
285     c=2  "变量c" "c是个好变量"
286     b=c+2 "变量b" "b也是个好变量" "这是为它写的第三条注释"
287     a>c  "检查a是否大于c"
288     a>=c  "检查a是否大于等于c"
289     run
290     dump
291     c=3   "change c again."
292     "注释也可以在前面" c^2>=sin(pow(a,b)+b)
293     run
294     dump
295     """
296     #for l in s.split('\n'):
297     #    scanner(l)
298     print "+"*80
299     print '*'*70
300     print s
301     print '*'*70
302     e=Env()
303     i=StringIO.StringIO(s)
304     e.exec_stream(i)
305     print "+"*80
306 
307 if __name__=="__main__":
308     test()

最后是个main.py

 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 import sys
 4 from env import Env
 5 import StringIO
 6 
 7 e=Env()
 8 e.exec_stream(sys.stdin)
 9 
10 s=""" x=-1"""
11 i=StringIO.StringIO(s)
12 e.exec_stream(i)



原文地址:https://www.cnblogs.com/windydays/p/2439829.html