blob: a2be3ee03447ba80a7b74ffa9be3a8d6a8996304 [file] [log] [blame]
Jack Jansen5ccd8261995-07-17 11:43:20 +00001"""
2gensuitemodule - Generate an AE suite module from an aete/aeut resource
3
4Based on aete.py
5"""
6
7import MacOS
8import os
9import string
10import sys
11import types
12import StringIO
13import macfs
14
15from Res import *
16
17def main():
18 fss, ok = macfs.StandardGetFile()
19 if not ok:
20 sys.exit(0)
21 process(fss.as_pathname())
22
23def process(fullname):
24 """Process all resources in a single file"""
25 cur = CurResFile()
26 print fullname
27 rf = OpenRFPerm(fullname, 0, 1)
28 try:
29 UseResFile(rf)
30 resources = []
31 for i in range(Count1Resources('aete')):
32 res = Get1IndResource('aete', 1+i)
33 resources.append(res)
34 for i in range(Count1Resources('aeut')):
35 res = Get1IndResource('aeut', 1+i)
36 resources.append(res)
37 print "\nLISTING aete+aeut RESOURCES IN", `fullname`
38 for res in resources:
39 print "decoding", res.GetResInfo(), "..."
40 data = res.data
41 aete = decode(data)
42 compileaete(aete, fullname)
43 finally:
44 if rf <> cur:
45 CloseResFile(rf)
46 UseResFile(cur)
47
48def decode(data):
49 """Decode a resource into a python data structure"""
50 f = StringIO.StringIO(data)
51 aete = generic(getaete, f)
52 aete = simplify(aete)
53 processed = f.tell()
54 unprocessed = len(f.read())
55 total = f.tell()
56 if unprocessed:
57 sys.stderr.write("%d processed + %d unprocessed = %d total\n" %
58 (processed, unprocessed, total))
59 return aete
60
61def simplify(item):
62 """Recursively replace singleton tuples by their constituent item"""
63 if type(item) is types.ListType:
64 return map(simplify, item)
65 elif type(item) == types.TupleType and len(item) == 2:
66 return simplify(item[1])
67 else:
68 return item
69
70
71# Here follows the aete resource decoder.
72# It is presented bottom-up instead of top-down because there are direct
73# references to the lower-level part-decoders from the high-level part-decoders.
74
75def getbyte(f, *args):
76 c = f.read(1)
77 if not c:
78 raise EOFError, 'in getbyte' + str(args)
79 return ord(c)
80
81def getword(f, *args):
82 getalign(f)
83 s = f.read(2)
84 if len(s) < 2:
85 raise EOFError, 'in getword' + str(args)
86 return (ord(s[0])<<8) | ord(s[1])
87
88def getlong(f, *args):
89 getalign(f)
90 s = f.read(4)
91 if len(s) < 4:
92 raise EOFError, 'in getlong' + str(args)
93 return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3])
94
95def getostype(f, *args):
96 getalign(f)
97 s = f.read(4)
98 if len(s) < 4:
99 raise EOFError, 'in getostype' + str(args)
100 return s
101
102def getpstr(f, *args):
103 c = f.read(1)
104 if len(c) < 1:
105 raise EOFError, 'in getpstr[1]' + str(args)
106 nbytes = ord(c)
107 if nbytes == 0: return ''
108 s = f.read(nbytes)
109 if len(s) < nbytes:
110 raise EOFError, 'in getpstr[2]' + str(args)
111 return s
112
113def getalign(f):
114 if f.tell() & 1:
115 c = f.read(1)
116 ##if c <> '\0':
117 ## print 'align:', `c`
118
119def getlist(f, description, getitem):
120 count = getword(f)
121 list = []
122 for i in range(count):
123 list.append(generic(getitem, f))
124 getalign(f)
125 return list
126
127def alt_generic(what, f, *args):
128 print "generic", `what`, args
129 res = vageneric(what, f, args)
130 print '->', `res`
131 return res
132
133def generic(what, f, *args):
134 if type(what) == types.FunctionType:
135 return apply(what, (f,) + args)
136 if type(what) == types.ListType:
137 record = []
138 for thing in what:
139 item = apply(generic, thing[:1] + (f,) + thing[1:])
140 record.append((thing[1], item))
141 return record
142 return "BAD GENERIC ARGS: %s" % `what`
143
144getdata = [
145 (getostype, "type"),
146 (getpstr, "description"),
147 (getword, "flags")
148 ]
149getargument = [
150 (getpstr, "name"),
151 (getostype, "keyword"),
152 (getdata, "what")
153 ]
154getevent = [
155 (getpstr, "name"),
156 (getpstr, "description"),
157 (getostype, "suite code"),
158 (getostype, "event code"),
159 (getdata, "returns"),
160 (getdata, "accepts"),
161 (getlist, "optional arguments", getargument)
162 ]
163getproperty = [
164 (getpstr, "name"),
165 (getostype, "code"),
166 (getdata, "what")
167 ]
168getelement = [
169 (getostype, "type"),
170 (getlist, "keyform", getostype)
171 ]
172getclass = [
173 (getpstr, "name"),
174 (getostype, "class code"),
175 (getpstr, "description"),
176 (getlist, "properties", getproperty),
177 (getlist, "elements", getelement)
178 ]
179getcomparison = [
180 (getpstr, "operator name"),
181 (getostype, "operator ID"),
182 (getpstr, "operator comment"),
183 ]
184getenumerator = [
185 (getpstr, "enumerator name"),
186 (getostype, "enumerator ID"),
187 (getpstr, "enumerator comment")
188 ]
189getenumeration = [
190 (getostype, "enumeration ID"),
191 (getlist, "enumerator", getenumerator)
192 ]
193getsuite = [
194 (getpstr, "suite name"),
195 (getpstr, "suite description"),
196 (getostype, "suite ID"),
197 (getword, "suite level"),
198 (getword, "suite version"),
199 (getlist, "events", getevent),
200 (getlist, "classes", getclass),
201 (getlist, "comparisons", getcomparison),
202 (getlist, "enumerations", getenumeration)
203 ]
204getaete = [
205 (getword, "major/minor version in BCD"),
206 (getword, "language code"),
207 (getword, "script code"),
208 (getlist, "suites", getsuite)
209 ]
210
211def compileaete(aete, fname):
212 """Generate code for a full aete resource. fname passed for doc purposes"""
213 [version, language, script, suites] = aete
214 major, minor = divmod(version, 256)
215 for suite in suites:
216 compilesuite(suite, major, minor, language, script, fname)
217
218def compilesuite(suite, major, minor, language, script, fname):
219 """Generate code for a single suite"""
220 [name, desc, code, level, version, events, classes, comps, enums] = suite
221
222 modname = identify(name)
223 fss, ok = macfs.StandardPutFile('Python output file', modname+'.py')
224 if not ok:
225 return
226 fp = open(fss.as_pathname(), 'w')
227 fss.SetCreatorType('PYTH', 'TEXT')
228
229 fp.write('"""Suite %s: %s\n' % (name, desc))
230 fp.write("Level %d, version %d\n\n" % (level, version))
231 fp.write("Generated from %s\n"%fname)
232 fp.write("AETE/AEUT resource version %d/%d, language %d, script %d\n" % \
233 (major, minor, language, script))
234 fp.write('"""\n\n')
235 # XXXX Temp?
236 fp.write("import addpack\n")
237 fp.write("addpack.addpack('Tools')\n")
238 fp.write("addpack.addpack('bgen')\n")
239 fp.write("addpack.addpack('ae')\n\n")
240
241 fp.write('import aetools\n')
242 fp.write('import MacOS\n\n')
243 fp.write("_code = %s\n\n"% `code`)
244
245 enum_names = []
246 for enum in enums:
247 name = compileenumeration(fp, enum)
248 enum_names.append(enum)
249
250 compileclassheader(fp, modname)
251 if events:
252 for event in events:
253 compileevent(fp, event)
254 else:
255 fp.write("\tpass\n\n")
256 for cls in classes:
257 compileclass(fp, cls)
258 for comp in comps:
259 compilecomparison(fp, comp)
260
261def compileclassheader(fp, name):
262 """Generate class boilerplate"""
263 fp.write("class %s:\n\n"%name)
264
265def compileevent(fp, event):
266 """Generate code for a single event"""
267 [name, desc, code, subcode, returns, accepts, arguments] = event
268 funcname = identify(name)
269 #
270 # generate name->keyword map
271 #
272 if arguments:
273 fp.write("\t_argmap_%s = {\n"%funcname)
274 for a in arguments:
275 fp.write("\t\t%s : %s,\n"%(`identify(a[0])`, `a[1]`))
276 fp.write("\t}\n\n")
277
278 #
279 # Generate function header
280 #
281 has_arg = (not is_null(accepts))
282 opt_arg = (has_arg and is_optional(accepts))
283
284 if has_arg:
285 fp.write("\tdef %s(self, object, *arguments):\n"%funcname)
286 else:
287 fp.write("\tdef %s(self, *arguments):\n"%funcname)
288 #
289 # Generate doc string (important, since it may be the only
290 # available documentation, due to our name-remaping)
291 #
292 fp.write('\t\t"""%s: %s\n'%(name, desc))
293 if has_arg:
294 fp.write("\t\tRequired argument: %s\n"%getdatadoc(accepts))
295 elif opt_arg:
296 fp.write("\t\tOptional argument: %s\n"%getdatadoc(accepts))
297 for arg in arguments:
298 fp.write("\t\tKeyword argument %s: %s\n"%(identify(arg[0]),
299 getdatadoc(arg[2])))
300 fp.write("\t\tKeyword argument _attributes: AppleEvent attribute dictionary\n")
301 if not is_null(returns):
302 fp.write("\t\tReturns: %s\n"%getdatadoc(returns))
303 fp.write('\t\t"""\n')
304 #
305 # Fiddle the args so everything ends up in 'arguments' dictionary
306 #
307 fp.write("\t\t_code = %s\n"% `code`)
308 fp.write("\t\t_subcode = %s\n\n"% `subcode`)
309 if opt_arg:
310 fp.write("\t\tif len(arguments):\n")
311 fp.write("\t\t\tobject = arguments[0]\n")
312 fp.write("\t\t\targuments = arguments[1:]\n")
313 fp.write("\t\telse:\n")
314 fp.write("\t\t\tobject = None\n")
315 fp.write("\t\tif len(arguments) > 1:\n")
316 fp.write("\t\t\traise TypeError, 'Too many arguments'\n")
317 fp.write("\t\tif arguments:\n")
318 fp.write("\t\t\targuments = arguments[0]\n")
319 fp.write("\t\t\tif type(arguments) <> type({}):\n")
320 fp.write("\t\t\t\traise TypeError, 'Must be a mapping'\n")
321 fp.write("\t\telse:\n")
322 fp.write("\t\t\targuments = {}\n")
323 if has_arg:
324 fp.write("\t\targuments['----'] = object\n")
325 elif opt_arg:
326 fp.write("\t\tif object:\n")
327 fp.write("\t\t\targuments['----'] = object\n")
328 fp.write("\n")
329 #
330 # Extract attributes
331 #
332 fp.write("\t\tif arguments.has_key('_attributes'):\n")
333 fp.write("\t\t\tattributes = arguments['_attributes']\n")
334 fp.write("\t\t\tdel arguments['_attributes']\n")
335 fp.write("\t\telse:\n");
336 fp.write("\t\t\tattributes = {}\n")
337 fp.write("\n")
338 #
339 # Do key substitution
340 #
341 if arguments:
342 fp.write("\t\taetools.keysubst(arguments, self._argmap_%s)\n"%funcname)
343 for a in arguments:
344 if is_enum(a[2]):
345 kname = a[1]
346 ename = a[2][0]
347 fp.write("\t\taetools.enumsubst(arguments, %s, _Enum_%s)\n" %
348 (`kname`, ename))
349 fp.write("\n")
350 #
351 # Do the transaction
352 #
353 fp.write("\t\treply, arguments, attributes = self.send(_code, _subcode,\n")
354 fp.write("\t\t\t\targuments, attributes)\n")
355 #
356 # Error handling
357 #
358 fp.write("\t\tif arguments.has_key('errn'):\n")
359 fp.write("\t\t\traise MacOS.Error, aetools.decodeerror(arguments)\n")
360 fp.write("\t\t# XXXX Optionally decode result\n")
361 #
362 # Decode result
363 #
364 fp.write("\t\tif arguments.has_key('----'):\n")
365 if is_enum(returns):
366 fp.write("\t\t\t# XXXX Should do enum remapping here...\n")
367 fp.write("\t\t\treturn arguments['----']\n")
368 fp.write("\n")
369
370# print "\n# Command %s -- %s (%s, %s)" % (`name`, `desc`, `code`, `subcode`)
371# print "# returns", compiledata(returns)
372# print "# accepts", compiledata(accepts)
373# for arg in arguments:
374# compileargument(arg)
375
376def compileargument(arg):
377 [name, keyword, what] = arg
378 print "# %s (%s)" % (name, `keyword`), compiledata(what)
379
380def compileclass(fp, cls):
381 [name, code, desc, properties, elements] = cls
382 fp.write("\n# Class %s (%s) -- %s\n" % (`name`, `code`, `desc`))
383 for prop in properties:
384 compileproperty(fp, prop)
385 for elem in elements:
386 compileelement(fp, elem)
387
388def compileproperty(fp, prop):
389 [name, code, what] = prop
390 fp.write("# property %s (%s) %s\n" % (`name`, `code`, compiledata(what)))
391
392def compileelement(fp, elem):
393 [code, keyform] = elem
394 fp.write("# element %s as %s\n" % (`code`, keyform))
395
396def compilecomparison(fp, comp):
397 [name, code, comment] = comp
398 fp.write("# comparison %s (%s) -- %s\n" % (`name`, `code`, comment))
399
400def compileenumeration(fp, enum):
401 [code, items] = enum
402 fp.write("_Enum_%s = {\n" % identify(code))
403 for item in items:
404 compileenumerator(fp, item)
405 fp.write("}\n\n")
406 return code
407
408def compileenumerator(fp, item):
409 [name, code, desc] = item
410 fp.write("\t%s : %s,\t# %s\n" % (`identify(name)`, `code`, desc))
411
412def compiledata(data):
413 [type, description, flags] = data
414 return "%s -- %s %s" % (`type`, `description`, compiledataflags(flags))
415
416def is_null(data):
417 return data[0] == 'null'
418
419def is_optional(data):
420 return (data[2] & 0x8000)
421
422def is_enum(data):
423 return (data[2] & 0x2000)
424
425def getdatadoc(data):
426 [type, descr, flags] = data
427 if descr:
428 return descr
429 if type == '****':
430 return 'anything'
431 if type == 'obj ':
432 return 'an AE object reference'
433 return "undocumented, typecode %s"%`type`
434
435dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
436def compiledataflags(flags):
437 bits = []
438 for i in range(16):
439 if flags & (1<<i):
440 if i in dataflagdict.keys():
441 bits.append(dataflagdict[i])
442 else:
443 bits.append(`i`)
444 return '[%s]' % string.join(bits)
445
446# XXXX Do we have a set of python keywords somewhere?
447illegal_ids = [ "for", "in", "from", "and", "or", "not", "print" ]
448
449def identify(str):
450 """Turn any string into an identifier:
451 - replace space by _
452 - replace other illegal chars by _xx_ (hex code)
453 - prepend _ if the result is a python keyword
454 """
455 rv = ''
456 ok = string.letters + '_'
457 ok2 = ok + string.digits
458 for c in str:
459 if c in ok:
460 rv = rv + c
461 elif c == ' ':
462 rv = rv + '_'
463 else:
464 rv = rv + '_%02.2x_'%ord(c)
465 ok = ok2
466 if rv in illegal_ids:
467 rv = '_' + rv
468 return rv
469
470# Call the main program
471
472if __name__ == '__main__':
473 main()
474 sys.exit(1)