1#!/usr/bin/env python
2
3from __future__ import print_function
4import sys,os,re
5import functools
6
7class Error(Exception):
8    pass
9
10
11class ParseError(Error):
12    def __init__(self, errline):
13        Error.__init__(self, errline)
14
15
16class Struct:
17    pass
18
19
20def createCNameMap():
21    t = ''
22    for i in range(256):
23        if ((ord('A') <= i) and (i <= ord('Z'))) or \
24               ((ord('a') <= i) and (i <= ord('z'))) or \
25               ((ord('0') <= i) and (i <= ord('9'))):
26            t += chr(i)
27        else:
28            t += '_'
29    return t
30
31
32def seekBegin(f):
33    while True:
34        line = f.readline()
35        if not line:
36            return False
37        if line.startswith('BEGIN SANE_Option_Descriptor'):
38            return True
39
40
41def parseVerbatim(o, line):
42    words = line.split(None, 1)
43    if (len(words) < 2) or (words[1][0] != '@'):
44        return False
45    o[words[0]] = words[1]
46    return True
47
48
49def parseLine_type(o, line):
50    words = line.split(None, 2)
51    otype = words[1]
52    o['type'] = 'SANE_TYPE_' + otype.upper()
53    if otype == 'group':
54        g.ngroups += 1
55        oname = '_group_%d' % g.ngroups
56        o['size'] = 0
57    else:
58        temp = words[2]
59        idx = temp.find('[')
60        if idx == -1:
61            oname = temp
62            o['size'] = 1
63        else:
64            oname = temp[0:idx]
65            o['size'] = int(temp[idx+1:-1])
66    o['name'] = oname
67
68
69def parseLine_title(o, line):
70    o['title'] = line.split(None, 1)[1]
71
72
73def parseLine_desc(o, line):
74    o['desc'] = line.split(None, 1)[1]
75
76
77def parseLine_unit(o, line):
78    o['unit'] = 'SANE_UNIT_' + line.split(None, 1)[1].upper()
79
80
81def parseLine_default(o, line):
82    o['default'] = line.split(None, 1)[1]
83
84
85def parseLine_cap(o, line):
86    words = line.split()
87    o['cap'] = ['SANE_CAP_' + s.upper() for s in words[1:]]
88
89
90def parseLine_constraint(o, line):
91    c = line.split(None,1)[1]
92    if c[0] == '{':
93        o['constraint'] = c[1:-1].split('|')
94    elif c[0] == '(':
95        o['constraint'] = tuple(c[1:-1].split(','))
96    else:
97        sys.stderr.write('Ignored: %s\n' % line)
98
99
100def parseLine_info(o, line):
101    words = line.split()
102    o['info'] = ['SANE_INFO_' + s.upper() for s in words[1:]]
103
104def parseLine_rem(o, line):
105    pass
106
107def normalize(o):
108    if 'cname' not in o:
109        cname = o['name'].translate(cnameMap)
110        o['cname'] = cname
111    else:
112        cname = o['cname']
113    o['cname_opt'] = 'opt_' + cname
114    o['cname_con'] = 'constraint_' + cname
115    if 'title' not in o:
116        o['title'] = 'NO TITLE'
117    if 'desc' not in o:
118        o['desc'] = '@sod->title' % o
119    if 'unit' not in o:
120        o['unit'] = 'SANE_UNIT_NONE'
121    if 'constraint_type' not in o:
122        if 'constraint' not in o:
123            ct = 'SANE_CONSTRAINT_NONE'
124        elif isinstance(o['constraint'], list):
125            if o['type'] == 'SANE_TYPE_STRING':
126                ct = 'SANE_CONSTRAINT_STRING_LIST'
127            else:
128                ct = 'SANE_CONSTRAINT_WORD_LIST'
129        elif isinstance(o['constraint'], tuple):
130            ct = 'SANE_CONSTRAINT_RANGE'
131        elif isinstance(o['constraint'], str):
132            oc = o['constraint']
133            if oc.startswith('@range'):
134                ct = 'SANE_CONSTRAINT_RANGE'
135            elif oc.startswith('@word_list'):
136                ct = 'SANE_CONSTRAINT_WORD_LIST'
137            elif oc.startswith('@string_list'):
138                ct = 'SANE_CONSTRAINT_STRING_LIST'
139        o['constraint_type'] = ct
140    return o
141
142
143def parseFile(f):
144    if not seekBegin(f):
145        return None
146    options = [ {
147        'name' : '',
148        'cname' : 'opt_num_opts',
149        'title' : '@SANE_TITLE_NUM_OPTIONS',
150        'desc' : '@SANE_DESC_NUM_OPTIONS',
151        'type' : 'SANE_TYPE_INT',
152        'unit' : 'SANE_UNIT_NONE',
153        'size' : 1,
154        'cap' : ['SANE_CAP_SOFT_DETECT'],
155        'constraint_type' : 'SANE_CONSTRAINT_NONE',
156        'default' : '@w = ' + opt_prefix + 'last'
157        } ]
158    o = {}
159    while True:
160        line = f.readline()
161        if not line:
162            break
163        line = line.strip()
164        if not line:
165            continue
166        token = line.split(None, 1)[0].lower()
167        if token == 'end':
168            break
169        if token == 'type':
170            if 'name' in o:
171                options.append(o)
172            o = {}
173        funcName = 'parseLine_' + token
174        if funcName in globals():
175            if not parseVerbatim(o, line):
176                func = globals()[funcName]
177                func(o, line)
178        else:
179            sys.stderr.write('Skip: %s\n' % line)
180    if 'name' in o:
181        options.append(o)
182    return [normalize(o) for o in options]
183
184
185def genHeader(options):
186    print ("\ntypedef union {")
187    print ("  SANE_Word w;")
188    print ("  SANE_Int  i;")
189    print ("  SANE_Bool b;")
190    print ("  SANE_Fixed f;")
191    print ("  SANE_String s;")
192    print ("  void *ptr;")
193    print ("} option_value_t;")
194    print ("\ntypedef enum {")
195    for o in options:
196        print ("  %(cname_opt)s," % o)
197    print ("  " + opt_prefix + "last")
198    print ("} option_t;")
199
200    print ("\ntypedef struct {")
201    print ("  SANE_Option_Descriptor sod;")
202    print ("  option_value_t val,def;")
203    print ("  SANE_Word info;")
204    print ("} option_descriptor_t;")
205
206    print ("\nstruct pixma_sane_t;")
207    print ("static int build_option_descriptors(struct pixma_sane_t *ss);\n")
208
209
210def genMinMaxRange(n, t, r):
211    if t == 'SANE_TYPE_FIXED':
212        r = ['SANE_FIX(%s)' % x for x in r]
213    print ("static const SANE_Range " + n + " =")
214    print ("  { " + r[0] + "," + r[1] + "," + r[2] + " };")
215
216
217def genList(n, t, l):
218    if t == 'SANE_TYPE_INT':
219        etype = 'SANE_Word'
220        l = [str(len(l))] + l
221    elif t == 'SANE_TYPE_FIXED':
222        etype = 'SANE_Word'
223        l = [str(len(l))] + ['SANE_FIX(%s)' % x for x in l]
224    elif t == 'SANE_TYPE_STRING':
225        etype = 'SANE_String_Const'
226        l = ['SANE_I18N("%s")' % x for x in l] + ['NULL']
227    print ("static const %s %s[%d] = {" % (etype, n, len(l)))
228    for x in l[0:-1]:
229        print ("\t" + x + ",")
230    print ("\t" + l[-1] + " };")
231
232
233def genConstraints(options):
234    print ("")
235    for o in options:
236        if 'constraint' not in o: continue
237        c = o['constraint']
238        oname = o['cname_con']
239        otype = o['type']
240        if isinstance(c, tuple):
241            genMinMaxRange(oname, otype, c)
242        elif isinstance(c, list):
243            genList(oname, otype, c)
244
245def buildCodeVerbatim(o):
246    for f in ('name', 'title', 'desc', 'type', 'unit', 'size', 'cap',
247              'constraint_type', 'constraint', 'default'):
248        if (f not in o): continue
249        temp = o[f]
250        if (not isinstance(temp,str)) or \
251           (len(temp) < 1) or (temp[0] != '@'):
252            continue
253        o['code_' + f] = temp[1:]
254
255def ccode(o):
256    buildCodeVerbatim(o)
257    if 'code_name' not in o:
258        o['code_name'] = '"' + o['name'] + '"'
259    for f in ('title', 'desc'):
260        cf = 'code_' + f
261        if cf in o: continue
262        o[cf] = 'SANE_I18N("' + o[f] + '")'
263
264    for f in ('type', 'unit', 'constraint_type'):
265        cf = 'code_' + f
266        if cf in o: continue
267        o[cf] = o[f]
268
269    if 'code_size' not in o:
270        otype = o['type']
271        osize = o['size']
272        if otype == 'SANE_TYPE_STRING':
273            code = str(osize + 1)
274        elif otype == 'SANE_TYPE_INT' or otype == 'SANE_TYPE_FIXED':
275            code = str(osize) + ' * sizeof(SANE_Word)'
276        elif otype == 'SANE_TYPE_BUTTON':
277            code = '0'
278        else:
279            code = 'sizeof(SANE_Word)'
280        o['code_size'] = code
281
282    if ('code_cap' not in o) and ('cap' in o):
283        o['code_cap'] = functools.reduce(lambda a,b: a+'|'+b, o['cap'])
284    else:
285        o['code_cap'] = '0'
286
287    if ('code_info' not in o) and ('info' in o):
288        o['code_info'] = functools.reduce(lambda a,b: a+'|'+b, o['info'])
289    else:
290        o['code_info'] = '0'
291
292    if ('code_default' not in o) and ('default' in o):
293        odefault = o['default']
294        otype = o['type']
295        if odefault == '_MIN':
296            rhs = 'w = sod->constraint.range->min'
297        elif odefault == '_MAX':
298            rhs = 'w = sod->constraint.range->max'
299        elif otype in ('SANE_TYPE_INT', 'SANE_TYPE_BOOL'):
300            rhs = 'w = %(default)s'
301        elif otype == 'SANE_TYPE_FIXED':
302            rhs = 'w = SANE_FIX(%(default)s)'
303        elif otype == 'SANE_TYPE_STRING':
304            rhs = 's = SANE_I18N("%(default)s")'
305        o['code_default'] = rhs % o
306    if 'code_default' in o:
307        code = '  opt->def.%(code_default)s;\n'
308        if o['constraint_type'] != 'SANE_CONSTRAINT_STRING_LIST':
309            code += '  opt->val.%(code_default)s;\n'
310        else:
311            code += '  opt->val.w = find_string_in_list' \
312                    '(opt->def.s, sod->constraint.string_list);\n'
313        o['full_code_default'] = code % o
314    else:
315        o['full_code_default'] = ''
316
317    if ('code_constraint' not in o) and ('constraint' in o):
318        ct = o['constraint_type']
319        idx = len('SANE_CONSTRAINT_')
320        ctype = ct[idx:].lower()
321        if ctype == 'range':
322            rhs = '&%(cname_con)s' % o
323        else:
324            rhs = '%(cname_con)s' % o
325        o['code_constraint'] = ctype + ' = ' + rhs
326    if 'code_constraint' in o:
327        code = '  sod->constraint.%(code_constraint)s;\n'
328        o['full_code_constraint'] = code % o
329    else:
330        o['full_code_constraint'] = ''
331
332    return o
333
334def genBuildOptions(options):
335  print ("\nstatic")
336  print ("int find_string_in_list(SANE_String_Const str, const SANE_String_Const *list)")
337  print ("{")
338  print ("  int i;")
339  print ("  for (i = 0; list[i] && strcmp(str, list[i]) != 0; i++) {}")
340  print ("  return i;")
341  print ("}")
342  print ("")
343  print ("static")
344  print ("int build_option_descriptors(struct pixma_sane_t *ss)")
345  print ("{")
346  print ("  SANE_Option_Descriptor *sod;")
347  print ("  option_descriptor_t *opt;")
348  print ("")
349  print ("  memset(OPT_IN_CTX, 0, sizeof(OPT_IN_CTX));")
350
351  for o in options:
352      o = ccode(o)
353      otype = o['type']
354      code = '\n  opt = &(OPT_IN_CTX[%(cname_opt)s]);\n' \
355             '  sod = &opt->sod;\n' \
356             '  sod->type = %(code_type)s;\n' \
357             '  sod->title = %(code_title)s;\n' \
358             '  sod->desc = %(code_desc)s;\n'
359      if otype != 'SANE_TYPE_GROUP':
360          code += '  sod->name = %(code_name)s;\n' \
361                  '  sod->unit = %(code_unit)s;\n' \
362                  '  sod->size = %(code_size)s;\n' \
363                  '  sod->cap  = %(code_cap)s;\n' \
364                  '  sod->constraint_type = %(code_constraint_type)s;\n' \
365                  '%(full_code_constraint)s' \
366                  '  OPT_IN_CTX[%(cname_opt)s].info = %(code_info)s;\n' \
367                  '%(full_code_default)s'
368      sys.stdout.write(code % o)
369  print ("")
370  print ("  return 0;")
371  print ("}\n")
372
373g = Struct()
374g.ngroups = 0
375opt_prefix = 'opt_'
376con_prefix = 'constraint_'
377cnameMap = createCNameMap()
378options = parseFile(sys.stdin)
379print ("/* DO NOT EDIT THIS FILE! */")
380print ("/* Automatically generated from pixma.c */")
381if (len(sys.argv) == 2) and (sys.argv[1] == 'h'):
382    genHeader(options)
383else:
384    genConstraints(options)
385    genBuildOptions(options)
386