-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdes.py
185 lines (169 loc) · 7.13 KB
/
des.py
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
""" Module dealing with all kind of des files - like maps, rooms, bacgrounds etc"""
import os
from pyparsing import *
import string
import random
from collections import Iterable
#from critters import *
#from features import *
#from items import *
#chars allowed for right-hand-value
viable_chars = '!"#$%&\'()*+,-./:;<?@[\]^_`{|}~'
equals = Literal('=').suppress()
#comment = Regex(r'^#[' + alphanums + ']$')
comma = ZeroOrMore(',').suppress()
#right-hand-value(rhv) definition
rhv = Combine(Optional('$') + Word(alphanums + viable_chars + '\ '))
#assign to dictionary
dict_assign = Literal('=>').suppress()
#multiline syntax
multi_line = QuotedString(quoteChar='"""', escChar='\\', multiline=True)
line_end = ZeroOrMore(lineEnd.suppress())
#right hand value for dictionary. parses => value
dict_rhv = Group( OneOrMore(Word(alphanums + viable_chars )) + dict_assign + rhv) + comma
any_keyword = Combine(Word(alphas, min=1) + ZeroOrMore(Word(alphanums + '_-')))
#parses KEYWORD="""
multiline_keyword = any_keyword + equals + multi_line
#end of block keyword
end_keyword = Keyword('END').suppress() + line_end
assignment = any_keyword + equals + (OneOrMore(dict_rhv) | rhv)
ml_assignment = multiline_keyword
assignment = ml_assignment | assignment
parser = OneOrMore(OneOrMore(assignment) + end_keyword)
param_number = Combine(Optional('-') + Word(string.digits)).setParseAction(lambda x: int(x[0]))
param_lhv = Word(alphanums) + Literal(':').suppress()
param_rhv = (param_number | Word(alphanums + "- " +"'" + '"' + '- ')) + Optional(Literal(',')).suppress()
params_parser = dictOf(param_lhv, param_rhv)
def _parseFile(fname, ttype, lookup_dicts):
result = [None]#None indicates that a new item should be created
assignment.setParseAction(lambda x: process(result,ttype, x, lookup_dicts))
end_keyword.setParseAction(lambda x : end(result))
cont = open(fname).read()
parser.parseString(cont)
if result[-1] is None:
result.pop()
return result
def lookup_val(val, lookup_dicts):
for lookup in lookup_dicts:
if lookup.has_key(val):
res = lookup[val]
if res is None:
raise RuntimeError('None contained in lookup dicts under the key %s' % val)
return res
return None
def extend_list(l, what):
""" Extends list l with item/items from what
If what is Iterable - calls l.extend
if it's not - calls append(what)
"""
if isinstance(what, Iterable):
l.extend(what)
else:
l.append(what)
return l
def parse_val(_val, where, lookup_dicts):
"""Adjusts the value from des file. There are several ways to do so
1) string.Template substitution when ${ is replaced with attribute of object
2) $-values. Values that starts with $-sign will be parsed using eval
3) %-blocks. Values that starts with %-sign will be parsed using compile/exec. Then
we expect that such a block defines local named 'out'
lookup_dicts stores dicts that holds available classes for that type of parsing
"""
if _val.find('${') != -1:
val = string.Template(_val).safe_substitute(where.__dict__)
else:
val = _val
if val.startswith('$') and not val.startswith('${'):
val = val[1:]
if val.find('&&') > -1:
args = val.partition('&&')
item = parse_val('$' + args[0].strip(), where, lookup_dicts)
if not item:
raise RuntimeError('No callable under the name [%s]' % args[0])
val2 = args[2].lstrip()
if not val2.startswith('$'):
val2 = '$' + val2
return extend_list([item], parse_val(val2, where, lookup_dicts))
#here we address simple value evaluation via eval
try:
if val.find('(') > -1:#we have a record in form $func_name(param:value)
func = val[:val.find('(')].strip() #get function name
args = val[val.find('(')+1:val.find(')')] #get args between '(' and ')'
args = params_parser.parseString(args).asDict() #parse params
target = lookup_val(func, lookup_dicts)
if not target:
raise RuntimeError('No callable under the name [%s]' % func)
#now we check two options: if this is a type - then we create new parametrized type
#if it's function - we just call it with provided params
if isinstance(target, type): #if this is a type - create subtype
return type(target.__name__, (target,), args)
if callable(target):
return target(**args)
else:
raise RuntimeError('Invalid target in parsing %s' % target)
else:
return lookup_val(val.strip(), lookup_dicts)
except Exception ,e:
print 'Error parsing code :\n' + val + '\n' + str(e)
import traceback
import sys
t,v, tb = sys.exc_info()
traceback.print_tb(tb)
sys.exit(-1)
elif val.startswith('%'): #more complex - via compile and exec
val = val[1:]
try:
code = compile(val, '', 'exec')
exec(code)
#actualy any executed code should set 'out' variable. we take it and set to target class
return out
except Exception ,e:
print 'Error compiling code :\n' + val + '\n' + str(e)
import sys
sys.exit(-1)
else:
return val
def process(items, ttype, toks, lookup_dicts):
"""Invoked on each rhv """
where = items[-1]
#just take previous item from results. if it's empty - it's either start of file or
#END keyword was prior to current line
if where is None:
items.pop()
where = ttype() #create new item of requested type
items.append(where)
key = toks[0]
key = str(key).lower()
value = toks[1]
if isinstance(value, str): #if the result is plain string - parse and set
out = parse_val(value, where, lookup_dicts)
setattr(where,key,out)
else: #if the result is map - parse each value
if not hasattr(where, key):
setattr(where, key, {})
if hasattr(where, 'get_' + key):
dict = getattr(where, 'get_' + key)()
else:
dict = getattr(where, key)
for k, v in zip(value[::2], value[1::2]):
dict[k] = parse_val(v, where, lookup_dicts)
def end(result):
result.append(None)
parsed_des_files = { }
def parseFile(file_name, type, lookup_dicts):
"""parses file"""
if parsed_des_files.has_key(file_name):
return parsed_des_files[file_name]
_des = _parseFile(file_name, type, lookup_dicts)
parsed_des_files[file_name] = _des
return _des
def parseDes(file_name, type, sub_type='des'):
"""parses des file from data/des/ folder.
parameters:
file_name => is the name if the file without .des
type => class of the resource
sub_type => 'des' subfolder inside data folder
returns collection of type() items from parsed file
"""
file_name = os.path.join(os.path.dirname(__file__), 'data', sub_type, file_name + '.des')
return parseFile(file_name, type, [globals(), locals()])