这个程序就是做来玩和练习的,代码是玩具级别的,用的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 #!/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模块就够用了。
代码如下:
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。
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)