scanner.py now stores a cached of File objects in .audiomangler/cache - approximately...
[audiomangler.git] / audiomangler / expression.py
blob9956e989d93e744bc3bd578b7775d66e70cedb78
1 ###########################################################################
2 # Copyright (C) 2008 by Andrew Mahone
3 # <andrew.mahone@gmail.com>
5 # Copyright: See COPYING file that comes with this distribution
7 ###########################################################################
8 from pyparsing import *
9 from audiomangler import Config
10 import re
11 ParserElement.enablePackrat()
13 class Value(object):
14 def __new__(cls,*args,**kw):
15 ret = object.__new__(cls,*args,**kw)
16 return ret
17 pass
19 def evaluate(item,cdict):
20 if isinstance(item,Value):
21 return item.evaluate(cdict)
22 else:
23 return item
25 #this is necessary because Formats need to know if items are substitutions,
26 #even if the items are literals.
27 class SubsValue(Value):
28 def __new__(cls,data):
29 if isinstance(data,Value):
30 return data
31 else:
32 return Value.__new__(cls,data)
33 def __init__(self,data):
34 self.data = data
36 def evaluate(self,cdict):
37 return self.data
39 class LookupValue(Value):
40 def __init__(self,key):
41 self.key = key
42 def evaluate(self, cdict):
43 return cdict.get(self.key,u'')
45 class FuncValue(Value):
46 def __init__(self,args):
47 self.funcname = args[0]
48 self.args = tuple(args[1:])
50 def evaluate(self, cdict):
51 return getattr(self,self.funcname)(cdict,*self.args)
53 def firstof(self,cdict,*args):
54 for arg in args:
55 arg = evaluate(arg,cdict)
56 if arg:
57 return arg
58 return u''
60 def format(self,cdict,*args):
61 return evaluate(args[0],cdict) % (evaluate(arg,cdict) for arg in args[1:])
63 def iftrue(self,cdict,*args):
64 cond = evaluate(args[0],cdict)
65 if len(args) == 1:
66 if cond: return cond
67 elif len(args) == 2:
68 if cond: return evaluate(args[1],cdict)
69 else:
70 if cond:
71 return evaluate(args[1],cdict)
72 else:
73 return evaluate(args[2],cdict)
74 return ''
75 locals()['if'] = iftrue
77 def joinpath(self, cdict, *args):
78 return os.path.join(evaluate(arg,cdict) for arg in args)
80 class TupleValue(Value):
81 def __new__(cls,items):
82 if not [item for item in items if isinstance(item,Value)]:
83 return tuple(items)
84 else:
85 return Value.__new__(cls,items)
86 def __init__(self,items):
87 self.items = items
88 def evaluate(self,cdict):
89 return tuple(evaluate(item,cdict) for item in self.items)
91 class ReductionValue(Value):
92 opstbl = {
93 '%': lambda x,y: x % y,
94 '/': lambda x,y: x / y,
95 '*': lambda x,y: x * y,
96 '+': lambda x,y: x + y,
97 '-': lambda x,y: x - y,
98 '<': lambda x,y: x < y,
99 '>': lambda x,y: x > y,
100 '<=': lambda x,y: x <= y,
101 '>=': lambda x,y: x >= y,
102 '!=': lambda x,y: x != y,
103 '==': lambda x,y: x == y,
105 def __new__(cls,args):
106 if len(args) == 1:
107 return args[0]
108 else:
109 if [args[i] for i in range(0,len(args),2) if isinstance(args[i],Value)]:
110 return Value.__new__(cls,args)
111 else:
112 first = args[0]
113 rest = tuple(tuple(args[n:n+2]) for n in range(1,len(args)-1,2))
114 return reduce(lambda x,y: cls.opstbl[y[0]](x,y[1]), rest, first)
116 def __init__(self,args):
117 #because of single-term reduction bubbling up through parse levels,
118 #we might get init'ed again.
119 if hasattr(self,'first') and hasattr(self,'rest'):
120 return
121 self.first = args[0]
122 if len(args) % 2 != 1:
123 raise ValueError('wrong number of arguments')
124 self.rest = tuple((self.opstbl[args[n]],args[n+1]) for n in range(1,len(args)-1,2))
126 def evaluate(self,cdict):
127 return reduce(lambda x,y: y[0](x,evaluate(y[1],cdict)), self.rest, evaluate(self.first,cdict))
129 class BooleanReductionValue(ReductionValue):
130 #values need to be wrapped in a lambda, then called, to prevent their
131 #evaluation in the short-circuit case
132 opstbl = {
133 'and': lambda x,y: x and y(),
134 'or': lambda x,y: x or y(),
137 def evaluate(self,cdict):
138 return reduce(lambda x,y: y[0](x,lambda:evaluate(y[1],cdict)), self.rest, evaluate(self.first,cdict))
140 #this means we also need to change the formula for constant reduction
141 def __new__(cls,args):
142 if len(args) == 1:
143 return args[0]
144 else:
145 if [args[i] for i in range(0,len(args),2) if isinstance(args[i],Value)]:
146 return Value.__new__(cls,args)
147 else:
148 first = args[0]
149 rest = tuple(tuple(args[n:n+2]) for n in range(1,len(args)-1,2))
150 return reduce(lambda x,y: cls.opstbl[y[0]](x,lambda:y[1]), rest, first)
152 class UnaryOperatorValue(Value):
153 opstbl = {
154 '-': lambda x: -x,
155 '+': lambda x: +x,
156 'not': lambda x: not x,
158 def __new__(cls, args):
159 if len(args) == 1:
160 return args[0]
161 else:
162 if isinstance(args[1],Value):
163 return Value.__new__(value,args)
164 else:
165 return cls.opstbl[args[0]](args[1])
166 def __init__(self,args):
167 self.op = self.opstbl(args[0])
168 self.value = args[1]
169 def evaluate(self,cdict):
170 return self.op(evaluate(self.value,cdict))
172 class AsIs(Value):
173 def __init__(self,items):
174 self.subvalue = items[0]
176 def evaluate(self,cdict):
177 return evaluate(self.subvalue,cdict)
179 class Format(Value):
180 _cache = {}
181 def __new__(cls,items):
182 if isinstance(items, basestring):
183 if items not in cls._cache:
184 ret = Value.__new__(cls)
185 ret.parsedformat = formatexpr.parseString(items)
186 cls._cache[items] = ret
187 return cls._cache[items]
188 elif isinstance(items,cls):
189 return items
190 def __init__(self,items):
191 pass
192 def sanitize(self,instring):
193 return re.sub(r'[]?[/\\=+<>:;",*|]','_',instring)
194 def evaluate(self, cdict):
195 reslist = []
196 for item in self.parsedformat:
197 if isinstance(item,Value):
198 if isinstance(item,AsIs):
199 item = re.sub(r'[]?[\\=+<>:;",*|]','_',item.evaluate(cdict).encode(Config['fs_encoding'],Config['fs_encoding_err'] or 'replace'))
200 else:
201 item = re.sub(r'[]?[/\\=+<>:;",*|]','_',item.evaluate(cdict).encode(Config['fs_encoding'],Config['fs_encoding_err'] or 'replace'))
202 reslist.append(item)
203 return ''.join(reslist)
205 class Expr(Value):
206 _cache = {}
207 def __new__(cls,items):
208 if isinstance(items, basestring):
209 if items not in cls._cache:
210 ret = Value.__new__(cls)
211 ret.parsedformat = expr.parseString(items)
212 cls._cache[items] = ret
213 return cls._cache[items]
214 elif isinstance(items,cls):
215 return items
216 def __init__(self,items):
217 pass
218 def evaluate(self, cdict):
219 return evaluate(self.parsedformat[0],cdict)
221 def NumericValue(string):
222 if '.' in string:
223 return float(string)
224 else:
225 return int(string)
227 doubquot = QuotedString('"','\\','\\')
228 singquot = QuotedString("'",'\\','\\')
229 quot = doubquot | singquot
230 quot.setParseAction(lambda s,loc,toks: unicode(u''.join(toks)))
231 spaces = Optional(Word(' \t\n').suppress())
232 expr = Forward()
233 number = Regex('([0-9]+(\.[0-9]*)?|(\.[0-9]+))')
234 number.setParseAction(lambda s,loc,toks: NumericValue(toks[0]))
235 truth = Keyword('True') | Keyword('False')
236 truth.setParseAction(lambda s,loc,toks: toks[0] == 'True')
237 validname = Word(alphas,alphanums+'_')
238 lookup = validname.copy()
239 lookup.setParseAction(lambda s,loc,toks: LookupValue(u''.join(toks)))
240 lparen = Literal('(').suppress()
241 rparen = Literal(')').suppress()
242 arglist = delimitedList(expr)
243 funccall = validname + lparen + spaces + arglist + spaces + rparen
244 funccall.setParseAction(lambda s,loc,toks: FuncValue(toks))
245 parenexpr = lparen + spaces + expr + spaces + rparen
246 tupleelem = expr + Literal(',').suppress()
247 tupleexpr = lparen + tupleelem + ZeroOrMore(tupleelem) + Optional(expr) + rparen
248 tupleexpr.setParseAction(lambda s,loc,toks: TupleValue(toks))
249 sumop = Literal('+') | Literal('-')
250 atom = Optional(sumop + spaces) + (truth | funccall | quot | lookup | number | parenexpr | tupleexpr) + spaces
251 atom.setParseAction(lambda s,loc,toks:UnaryOperatorValue(toks))
252 productop = Literal('*') | Literal('/') | Literal('%')
253 product = atom + ZeroOrMore(spaces + productop + spaces + atom)
254 product.setParseAction(lambda s,loc,toks: ReductionValue(toks))
255 sumop = Literal('+') | Literal('-')
256 sum = product + ZeroOrMore(spaces + sumop + spaces + product)
257 sum.setParseAction(lambda s,loc,toks: ReductionValue(toks))
258 compareop = Literal('<=') | Literal('>=') | Literal('<') | Literal('>') | Literal('==') | Literal('!=')
259 comparison = sum + ZeroOrMore(spaces + compareop + spaces + sum)
260 comparison.setParseAction(lambda s,loc,toks: ReductionValue(toks))
261 notexpr = Optional(Keyword('not') + spaces) + comparison
262 notexpr.setParseAction(lambda s,loc,toks: UnaryOperatorValue(toks))
263 andexpr = notexpr + ZeroOrMore(spaces + Keyword('and') + spaces + notexpr)
264 andexpr.setParseAction(lambda s,loc,toks: BooleanReductionValue(toks))
265 orexpr = andexpr + ZeroOrMore(spaces + Keyword('or') + spaces + andexpr)
266 orexpr.setParseAction(lambda s,loc,toks: BooleanReductionValue(toks))
267 expr << orexpr
268 expr.leaveWhitespace()
269 subsint = Literal('$').suppress()
270 funcsubs = subsint + funccall
271 asissubs = subsint + Literal('/').suppress() + lparen + spaces + expr + spaces + rparen
272 asissubs.setParseAction(lambda s,loc,toks: AsIs(toks))
273 looksubs = subsint + lookup + WordEnd(alphanums + '_')
274 exprsubs = subsint + parenexpr
275 subs = exprsubs | asissubs | funcsubs | looksubs
276 subs.setParseAction(lambda s,loc,toks: SubsValue(toks[0]))
277 literal = Combine(OneOrMore(CharsNotIn('$')|(subsint+Literal('$'))))
278 formatexpr = OneOrMore(subs|literal)
279 formatexpr.leaveWhitespace()
280 __all__ = ['Format','Expr','evaluate']