blob: 059c3631056385cd58081716930d516c610074c5 [file] [log] [blame]
Rich Lanea06d0c32013-03-25 08:52:03 -07001# Copyright 2013, Big Switch Networks, Inc.
2#
3# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
4# the following special exception:
5#
6# LOXI Exception
7#
8# As a special exception to the terms of the EPL, you may distribute libraries
9# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
10# that copyright and licensing notices generated by LoxiGen are not altered or removed
11# from the LoxiGen Libraries and the notice provided below is (i) included in
12# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
13# documentation for the LoxiGen Libraries, if distributed in binary form.
14#
15# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
16#
17# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
18# a copy of the EPL at:
19#
20# http://www.eclipse.org/legal/epl-v10.html
21#
22# Unless required by applicable law or agreed to in writing, software
23# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
24# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
25# EPL for the specific language governing permissions and limitations
26# under the EPL.
27
28"""
29@brief Utilities involving LOXI naming conventions
30
Andreas Wundsam53256162013-05-02 14:05:53 -070031Utility functions for OpenFlow class generation
Rich Lanea06d0c32013-03-25 08:52:03 -070032
33These may need to be sorted out into language specific functions
34"""
35
Rich Lanec3405172013-02-20 18:18:57 -080036import sys
Rich Laneda5446f2013-11-10 17:21:48 -080037import os
Rich Lanea06d0c32013-03-25 08:52:03 -070038import of_g
39import tenjin
Andreas Wundsam37c7bc22013-09-17 13:48:35 -070040from generic_utils import find, memoize
Rich Lanea06d0c32013-03-25 08:52:03 -070041
42def class_signature(members):
43 """
44 Generate a signature string for a class in canonical form
45
46 @param cls The class whose signature is to be generated
47 """
48 return ";".join([",".join([x["m_type"], x["name"], str(x["offset"])])
49 for x in members])
50
51def type_dec_to_count_base(m_type):
52 """
53 Resolve a type declaration like uint8_t[4] to a count (4) and base_type
54 (uint8_t)
55
56 @param m_type The string type declaration to process
57 """
58 count = 1
59 chk_ar = m_type.split('[')
60 if len(chk_ar) > 1:
61 count_str = chk_ar[1].split(']')[0]
62 if count_str in of_g.ofp_constants:
63 count = of_g.ofp_constants[count_str]
64 else:
65 count = int(count_str)
66 base_type = chk_ar[0]
67 else:
68 base_type = m_type
69 return count, base_type
70
71##
72# Class types:
73#
74# Virtual
75# A virtual class is one which does not have an explicit wire
76# representation. For example, an inheritance super class
77# or a list type.
78#
79# List
80# A list of objects of some other type
81#
82# TLV16
83# The wire represenation starts with 16-bit type and length fields
84#
85# OXM
86# An extensible match object
87#
88# Message
89# A top level OpenFlow message
90#
91#
92
93def class_is_message(cls):
94 """
95 Return True if cls is a message object based on info in unified
96 """
97 return "xid" in of_g.unified[cls]["union"] and cls != "of_header"
98
99def class_is_tlv16(cls):
100 """
101 Return True if cls_name is an object which uses uint16 for type and length
102 """
103 if cls.find("of_action") == 0: # Includes of_action_id classes
104 return True
105 if cls.find("of_instruction") == 0:
106 return True
107 if cls.find("of_queue_prop") == 0:
108 return True
109 if cls.find("of_table_feature_prop") == 0:
110 return True
111 # *sigh*
112 if cls.find("of_meter_band_stats") == 0: # NOT A TLV
113 return False
114 if cls.find("of_meter_band") == 0:
115 return True
116 if cls.find("of_hello_elem") == 0:
117 return True
118 if cls == "of_match_v3":
119 return True
120 if cls == "of_match_v4":
121 return True
122 return False
123
124def class_is_u16_len(cls):
125 """
126 Return True if cls_name is an object which uses initial uint16 length
127 """
128 return cls in ["of_group_desc_stats_entry", "of_group_stats_entry",
129 "of_flow_stats_entry", "of_bucket", "of_table_features"]
130
131def class_is_oxm(cls):
132 """
133 Return True if cls_name is an OXM object
134 """
135 if cls.find("of_oxm") == 0:
136 return True
137 return False
138
139def class_is_action(cls):
140 """
141 Return True if cls_name is an action object
142
143 Note that action_id is not an action object, though it has
144 the same header. It looks like an action header, but the type
145 is used to identify a kind of action, it does not indicate the
146 type of the object following.
147 """
148 if cls.find("of_action_id") == 0:
149 return False
150 if cls.find("of_action") == 0:
151 return True
152
153 # For each vendor, check for vendor specific action
154 for exp in of_g.experimenter_name_to_id:
155 if cls.find("of_action" + exp) == 0:
156 return True
157
158 return False
159
160def class_is_action_id(cls):
161 """
162 Return True if cls_name is an action object
163
164 Note that action_id is not an action object, though it has
165 the same header. It looks like an action header, but the type
166 is used to identify a kind of action, it does not indicate the
167 type of the object following.
168 """
169 if cls.find("of_action_id") == 0:
170 return True
171
172 # For each vendor, check for vendor specific action
173 for exp in of_g.experimenter_name_to_id:
174 if cls.find("of_action_id_" + exp) == 0:
175 return True
176
177 return False
178
179def class_is_instruction(cls):
180 """
181 Return True if cls_name is an instruction object
182 """
183 if cls.find("of_instruction") == 0:
184 return True
185
186 # For each vendor, check for vendor specific action
187 for exp in of_g.experimenter_name_to_id:
188 if cls.find("of_instruction_" + exp) == 0:
189 return True
190
191 return False
192
193def class_is_meter_band(cls):
194 """
195 Return True if cls_name is an instruction object
196 """
197 # meter_band_stats is not a member of meter_band class hierarchy
198 if cls.find("of_meter_band_stats") == 0:
199 return False
200 if cls.find("of_meter_band") == 0:
201 return True
202 return False
203
204def class_is_hello_elem(cls):
205 """
206 Return True if cls_name is an instruction object
207 """
208 if cls.find("of_hello_elem") == 0:
209 return True
210 return False
211
212def class_is_queue_prop(cls):
213 """
214 Return True if cls_name is a queue_prop object
215 """
216 if cls.find("of_queue_prop") == 0:
217 return True
218
219 # For each vendor, check for vendor specific action
220 for exp in of_g.experimenter_name_to_id:
221 if cls.find("of_queue_prop_" + exp) == 0:
222 return True
223
224 return False
225
226def class_is_table_feature_prop(cls):
227 """
228 Return True if cls_name is a queue_prop object
229 """
230 if cls.find("of_table_feature_prop") == 0:
231 return True
232 return False
233
234def class_is_stats_message(cls):
235 """
236 Return True if cls_name is a message object based on info in unified
237 """
238
239 return "stats_type" in of_g.unified[cls]["union"]
240
241def class_is_list(cls):
242 """
243 Return True if cls_name is a list object
244 """
245 return (cls.find("of_list_") == 0)
246
247def type_is_of_object(m_type):
248 """
249 Return True if m_type is an OF object type
250 """
251 # Remove _t from the type id and see if key for unified class
252 if m_type[-2:] == "_t":
253 m_type = m_type[:-2]
254 return m_type in of_g.unified
255
256def list_to_entry_type(cls):
257 """
258 Return the entry type for a list
259 """
260 slen = len("of_list_")
Andreas Wundsam53256162013-05-02 14:05:53 -0700261 return "of_" + cls[slen:]
Rich Lanea06d0c32013-03-25 08:52:03 -0700262
263def type_to_short_name(m_type):
264 if m_type in of_g.of_base_types:
265 tname = of_g.of_base_types[m_type]["short_name"]
266 elif m_type in of_g.of_mixed_types:
267 tname = of_g.of_mixed_types[m_type]["short_name"]
268 else:
269 tname = "unknown"
270 return tname
271
272def type_to_name_type(cls, member_name):
273 """
274 Generate the root name of a member for accessor functions, etc
275 @param cls The class name
276 @param member_name The member name
277 """
278 members = of_g.unified[cls]["union"]
279 if not member_name in members:
280 debug("Error: %s is not in class %s for acc_name defn" %
281 (member_name, cls))
282 os.exit()
283
284 mem = members[member_name]
285 m_type = mem["m_type"]
286 id = mem["memid"]
287 tname = type_to_short_name(m_type)
288
289 return "o%d_m%d_%s" % (of_g.unified[cls]["object_id"], id, tname)
290
291
292def member_to_index(m_name, members):
293 """
294 Given a member name, return the index in the members dict
295 @param m_name The name of the data member to search for
296 @param members The dict of members
297 @return Index if found, -1 not found
298
299 Note we could generate an index when processing the original input
300 """
301 count = 0
302 for d in members:
303 if d["name"] == m_name:
304 return count
305 count += 1
306 return -1
307
308def member_base_type(cls, m_name):
309 """
310 Map a member to its of_ type
311 @param cls The class name
312 @param m_name The name of the member being gotten
313 @return The of_ type of the member
314 """
315 rv = of_g.unified[cls]["union"][m_name]["m_type"]
316 if rv[-2:] == "_t":
317 return rv
318 return rv + "_t"
319
320def member_type_is_octets(cls, m_name):
321 return member_base_type(cls, m_name) == "of_octets_t"
322
323def member_returns_val(cls, m_name):
324 """
325 Should get accessor return a value rather than void
326 @param cls The class name
327 @param m_name The member name
Andreas Wundsam53256162013-05-02 14:05:53 -0700328 @return True if of_g config and the specific member allow a
Rich Lanea06d0c32013-03-25 08:52:03 -0700329 return value. Otherwise False
330 """
331 m_type = of_g.unified[cls]["union"][m_name]["m_type"]
Andreas Wundsam53256162013-05-02 14:05:53 -0700332 return (config_check("get_returns") =="value" and
Rich Lanea06d0c32013-03-25 08:52:03 -0700333 m_type in of_g.of_scalar_types)
334
335def config_check(str, dictionary = of_g.code_gen_config):
336 """
337 Return config value if in dictionary; else return False.
338 @param str The lookup index
339 @param dictionary The dict to check; use code_gen_config if None
340 """
341
342 if str in dictionary:
343 return dictionary[str]
344
345 return False
346
347def h_file_to_define(name):
348 """
349 Convert a .h file name to the define used for the header
350 """
351 h_name = name[:-2].upper()
352 h_name = "_" + h_name + "_H_"
353 return h_name
354
355def type_to_cof_type(m_type):
356 if m_type in of_g.of_base_types:
357 if "cof_type" in of_g.of_base_types[m_type]:
358 return of_g.of_base_types[m_type]["cof_type"]
359 return m_type
360
Andreas Wundsam53256162013-05-02 14:05:53 -0700361
Rich Lanea06d0c32013-03-25 08:52:03 -0700362def member_is_scalar(cls, m_name):
363 return of_g.unified[cls]["union"][m_name]["m_type"] in of_g.of_scalar_types
364
365def type_is_scalar(m_type):
366 return m_type in of_g.of_scalar_types
367
368def skip_member_name(name):
369 return name.find("pad") == 0 or name in of_g.skip_members
370
371def enum_name(cls):
372 """
373 Return the name used for an enum identifier for the given class
374 @param cls The class name
375 """
376 return cls.upper()
377
378def class_in_version(cls, ver):
379 """
380 Return boolean indicating if cls is defined for wire version ver
381 """
382
383 return (cls, ver) in of_g.base_length
384
385def instance_to_class(instance, parent):
386 """
387 Return the name of the class for an instance of inheritance type parent
388 """
389 return parent + "_" + instance
390
391def sub_class_to_var_name(cls):
392 """
393 Given a subclass name like of_action_output, generate the
394 name of a variable like 'output'
395 @param cls The class name
396 """
397 pass
398
399def class_is_var_len(cls, version):
400 # Match is special case. Only version 1.2 (wire version 3) is var
401 if cls == "of_match":
402 return version == 3
403
404 return not (cls, version) in of_g.is_fixed_length
405
406def base_type_to_length(base_type, version):
407 if base_type + "_t" in of_g.of_base_types:
408 inst_len = of_g.of_base_types[base_type + "_t"]["bytes"]
409 else:
410 inst_len = of_g.base_length[(base_type, version)]
411
412def version_to_name(version):
413 """
414 Convert an integer version to the C macro name
415 """
416 return "OF_" + of_g.version_names[version]
417
418##
419# Is class a flow modify of some sort?
420
421def cls_is_flow_mod(cls):
Rich Lane488b3c52013-06-21 18:11:02 -0700422 return cls in ["of_flow_mod", "of_flow_modify", "of_flow_add", "of_flow_delete",
Rich Lanea06d0c32013-03-25 08:52:03 -0700423 "of_flow_modify_strict", "of_flow_delete_strict"]
424
425
426def all_member_types_get(cls, version):
427 """
428 Get the members and list of types for members of a given class
429 @param cls The class name to process
430 @param version The version for the class
431 """
432 member_types = []
433
434 if not version in of_g.unified[cls]:
435 return ([], [])
436
437 if "use_version" in of_g.unified[cls][version]:
438 v = of_g.unified[cls][version]["use_version"]
439 members = of_g.unified[cls][v]["members"]
440 else:
441 members = of_g.unified[cls][version]["members"]
442 # Accumulate variables that are supported
443 for member in members:
444 m_type = member["m_type"]
445 m_name = member["name"]
446 if skip_member_name(m_name):
447 continue
448 if not m_type in member_types:
449 member_types.append(m_type)
450
451 return (members, member_types)
452
453def list_name_extract(list_type):
454 """
455 Return the base name for a list object of the given type
456 @param list_type The type of the list as appears in the input,
457 for example list(of_port_desc_t).
458 @return A pair, (list-name, base-type) where list-name is the
459 base name for the list, for example of_list_port_desc, and base-type
460 is the type of list elements like of_port_desc_t
461 """
462 base_type = list_type[5:-1]
463 list_name = base_type
464 if list_name.find("of_") == 0:
465 list_name = list_name[3:]
466 if list_name[-2:] == "_t":
467 list_name = list_name[:-2]
468 list_name = "of_list_" + list_name
469 return (list_name, base_type)
470
471def version_to_name(version):
472 """
473 Convert an integer version to the C macro name
474 """
475 return "OF_" + of_g.version_names[version]
476
477def gen_c_copy_license(out):
478 """
479 Generate the top comments for copyright and license
480 """
Rich Laned983aa52013-06-13 11:48:37 -0700481 import c_gen.util
482 c_gen.util.render_template(out, '_copyright.c')
Rich Lanea06d0c32013-03-25 08:52:03 -0700483
484def accessor_returns_error(a_type, m_type):
485 is_var_len = (not type_is_scalar(m_type)) and \
486 [x for x in of_g.of_version_range if class_is_var_len(m_type[:-2], x)] != []
487 if a_type == "set" and is_var_len:
488 return True
489 elif m_type == "of_match_t":
490 return True
491 else:
492 return False
493
Andreas Wundsam8778d5e2013-05-06 14:12:46 -0700494def render_template(out, name, path, context, prefix = None):
Rich Lanea06d0c32013-03-25 08:52:03 -0700495 """
496 Render a template using tenjin.
497 out: a file-like object
498 name: name of the template
499 path: array of directories to search for the template
500 context: dictionary of variables to pass to the template
Andreas Wundsam8778d5e2013-05-06 14:12:46 -0700501 prefix: optional prefix to use for embedding (for other languages than python)
Rich Lanea06d0c32013-03-25 08:52:03 -0700502 """
Andreas Wundsam8778d5e2013-05-06 14:12:46 -0700503 pp = [ tenjin.PrefixedLinePreprocessor(prefix=prefix) if prefix else tenjin.PrefixedLinePreprocessor() ] # support "::" syntax
Rich Lanea06d0c32013-03-25 08:52:03 -0700504 template_globals = { "to_str": str, "escape": str } # disable HTML escaping
Rich Lanec3405172013-02-20 18:18:57 -0800505 engine = TemplateEngine(path=path, pp=pp)
Rich Lanea06d0c32013-03-25 08:52:03 -0700506 out.write(engine.render(name, context, template_globals))
507
508def render_static(out, name, path):
509 """
510 Write out a static template.
511 out: a file-like object
512 name: name of the template
513 path: array of directories to search for the template
514 """
515 # Reuse the tenjin logic for finding the template
516 template_filename = tenjin.FileSystemLoader().find(name, path)
517 if not template_filename:
518 raise ValueError("template %s not found" % name)
519 with open(template_filename) as infile:
520 out.write(infile.read())
Rich Lanec3405172013-02-20 18:18:57 -0800521
Andreas Wundsam37c7bc22013-09-17 13:48:35 -0700522@memoize
523def lookup_ir_wiretype(oftype, version):
524 """ if of is a reference to an enum in ir, resolve it to the wiretype
525 declared in that enum. Else return oftype """
526 enums = of_g.ir[version].enums
527 enum = find(lambda e: e.name == oftype, enums)
528 if enum and 'wire_type' in enum.params:
529 return enum.params['wire_type']
530 else:
531 return oftype
532
Rich Lanec3405172013-02-20 18:18:57 -0800533class TemplateEngine(tenjin.Engine):
534 def include(self, template_name, **kwargs):
535 """
536 Tenjin has an issue with nested includes that use the same local variable
537 names, because it uses the same context dict for each level of nesting.
538 The fix is to copy the context.
539 """
540 frame = sys._getframe(1)
541 locals = frame.f_locals
542 globals = frame.f_globals
543 context = locals["_context"].copy()
544 context.update(kwargs)
545 template = self.get_template(template_name, context, globals)
546 return template.render(context, globals, _buf=locals["_buf"])
Rich Laneda5446f2013-11-10 17:21:48 -0800547
548def open_output(name):
549 """
550 Open an output file for writing
551
552 'name' may include slashes. Subdirectories will be automatically created.
553 """
554 print "Writing %s" % name
555 path = os.path.join(of_g.options.install_dir, name)
556 dirpath = os.path.dirname(path)
557 if not os.path.exists(dirpath):
558 os.makedirs(dirpath)
559 return open(path, "w")