Devuan fork of gpsd
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

658 lines
24 KiB

  1. #!/usr/bin/env python
  2. #
  3. # This tool is intended to automate away the drudgery in bring up support
  4. # for a new AIS message type. It parses the tabular description of a message
  5. # and generates various useful code snippets from that. It can also be used to
  6. # correct offsets in the tables themselves.
  7. #
  8. # Requires the AIVDM.txt file on standard input. Takes a single argument,
  9. # which must match a string in a //: Type comment. Things you can generate:
  10. #
  11. # * -t: A corrected version of the table. It will redo all the offsets to be
  12. # in conformance with the bit widths. (The other options rely only on the
  13. # bit widths). If the old and new tables are different, an error message
  14. # describing the corrections will be emitted to standard error.
  15. #
  16. # * -s: A structure definition capturing the message info, with member
  17. # names extracted from the table and types computed from it.
  18. #
  19. # * -c: Bit-extraction code for the AIVDM driver. Grind out the right sequence
  20. # of UBITS, SBITS, and UCHARS macros, and assignments to structure members,
  21. # guaranteed correct if the table offsets and widths are.
  22. #
  23. # * -d: Code to dump the contents of the unpacked message structure as JSON. If
  24. # the structure has float members, you'll get an if/then/else guarded by
  25. # the scaled flag.
  26. #
  27. # * -r: A Python initializer stanza for jsongen.py, which is in turn used to
  28. # generate the specification structure for a JSON parse that reads JSON
  29. # into an instance of the message structure.
  30. #
  31. # * -a: Generate all of -s, -d, -c, and -r, and -t, not to stdout but to
  32. # files named with 'tablegen' as a distinguishing part of the stem.
  33. # The stem name can be overridden with the -o option.
  34. #
  35. # This generates almost all the code required to support a new message type.
  36. # It's not quite "Look, ma, no handhacking!" You'll need to add default
  37. # values to the Python stanza. If the structure definition contains character
  38. # arrays, you'll have to fill in the dimensions by hand. You'll need to add
  39. # a bit of glue to ais_json.c so that json_ais_read() actually calls the parser
  40. # handing it the specification structure as a control argument.
  41. #
  42. # The -a, -c, -s, -d, and -r modes all take an argument, which should be a
  43. # structure reference prefix to be prepended (before a dot) to each fieldname.
  44. # Usually you'll need this to look something like "ais->typeN", but it could be
  45. # "ais->typeN.FOO" if the generated code has to operate on a union member
  46. # inside a type 6 or 8, or something similar.
  47. #
  48. # The -S and -E options allow you to generate code only for a specified span
  49. # of fields in the table. This may be useful for dealing with groups of
  50. # messages that have a common head section.
  51. #
  52. # This code interprets magic comments in the input
  53. #
  54. # //: Type
  55. # The token following "Type" is the name of the table
  56. # //: xxxx vocabulary
  57. # A subtable describing a controlled vocabulary for field xxxx in the
  58. # preceding table.
  59. #
  60. # TO-DO: generate code for ais.py.
  61. #
  62. # This code runs compatibly under Python 2 and 3.x for x >= 2.
  63. # Preserve this property!
  64. from __future__ import absolute_import, print_function, division
  65. import getopt
  66. import sys
  67. def correct_table(wfp):
  68. # Writes the corrected table.
  69. print("Total bits:", base, file=sys.stderr)
  70. for (i, t) in enumerate(table):
  71. if offsets[i].strip():
  72. print("|" + offsets[i] + t[owidth+1:].rstrip(), file=wfp)
  73. else:
  74. print(t.rstrip(), file=wfp)
  75. def make_driver_code(wfp):
  76. # Writes calls to bit-extraction macros.
  77. # Requires UBITS, SBITS, UCHARS to act as they do in the AIVDM driver.
  78. # Also relies on bitlen to be the message bit length, and i to be
  79. # available as abn index variable.
  80. record = after is None
  81. arrayname = None
  82. base = '\t'
  83. step = " " * 4
  84. indent = base
  85. for (i, t) in enumerate(table):
  86. if '|' in t:
  87. fields = [s.strip() for s in t.split('|')]
  88. width = fields[2]
  89. name = fields[4]
  90. ftype = fields[5]
  91. if after == name:
  92. record = True
  93. continue
  94. if before == name:
  95. record = False
  96. continue
  97. if not record:
  98. continue
  99. if ftype == 'x':
  100. print("\t/* skip %s bit%s */" % (width,
  101. ["", "s"][width > '1']), file=wfp)
  102. continue
  103. if ftype[0] == 'a':
  104. arrayname = name
  105. explicit = ftype[1] == '^'
  106. print('#define ARRAY_BASE %s' % offsets[i].strip(), file=wfp)
  107. print('#define ELEMENT_SIZE %s' % trailing, file=wfp)
  108. if explicit:
  109. lengthfield = last
  110. print(indent + "for (i = 0; i < %s; i++) {" % lengthfield,
  111. file=wfp)
  112. else:
  113. lengthfield = "n" + arrayname
  114. print(indent + "for (i = 0; ARRAY_BASE + "
  115. "(ELEMENT_SIZE*i) < bitlen; i++) {", file=wfp)
  116. indent += step
  117. print(indent + "int a = ARRAY_BASE + (ELEMENT_SIZE*i);",
  118. file=wfp)
  119. continue
  120. offset = offsets[i].split('-')[0]
  121. if arrayname:
  122. target = "%s.%s[i].%s" % (structnme, arrayname, name)
  123. offset = "a + " + offset
  124. else:
  125. target = "%s.%s" % (structname, name)
  126. if ftype[0].lower() in ('u', 'i', 'e'):
  127. print(indent + "%s\t= %sBITS(%s, %s);" %
  128. (target,
  129. {'u': 'U', 'e': 'U', 'i': 'S'}[ftype[0].lower()],
  130. offset, width), file=wfp)
  131. elif ftype == 't':
  132. print(indent + "UCHARS(%s, %s);" % (offset, target), file=wfp)
  133. elif ftype == 'b':
  134. print(indent + "%s\t= (bool)UBITS(%s, 1);" % (target, offset),
  135. file=wfp)
  136. else:
  137. print(indent + "/* %s bits of type %s */" %
  138. (width, ftype), file=wfp)
  139. last = name
  140. if arrayname:
  141. indent = base
  142. print(indent + "}", file=wfp)
  143. if not explicit:
  144. print(indent + "%s.%s = ind;" %
  145. (structname, lengthfield), file=wfp)
  146. print("#undef ARRAY_BASE", file=wfp)
  147. print("#undef ELEMENT_SIZE", file=wfp)
  148. def make_structure(wfp):
  149. # Write a structure definition correponding to the table.
  150. global structname
  151. record = after is None
  152. baseindent = 8
  153. step = 4
  154. inwards = step
  155. arrayname = None
  156. def tabify(n):
  157. return ('\t' * (n // 8)) + (" " * (n % 8))
  158. print(tabify(baseindent) + "struct {", file=wfp)
  159. for (i, t) in enumerate(table):
  160. if '|' in t:
  161. fields = [s.strip() for s in t.split('|')]
  162. width = fields[2]
  163. description = fields[3].strip()
  164. name = fields[4]
  165. ftype = fields[5]
  166. if after == name:
  167. record = True
  168. continue
  169. if before == name:
  170. record = False
  171. continue
  172. if ftype == 'x' or not record:
  173. continue
  174. if ftype[0] == 'a':
  175. arrayname = name
  176. if ftype[1] == '^':
  177. lengthfield = last
  178. ftype = ftype[1:]
  179. else:
  180. lengthfield = "n%s" % arrayname
  181. print(tabify(baseindent + inwards) +
  182. "signed int %s;" % lengthfield, file=wfp)
  183. if arrayname.endswith("s"):
  184. typename = arrayname[:-1]
  185. else:
  186. typename = arrayname
  187. print(tabify(baseindent + inwards) + "struct %s_t {" %
  188. typename, file=wfp)
  189. inwards += step
  190. arraydim = ftype[1:]
  191. continue
  192. elif ftype == 'u' or ftype == 'e' or ftype[0] == 'U':
  193. decl = "unsigned int %s;\t/* %s */" % (name, description)
  194. elif ftype == 'i' or ftype[0] == 'I':
  195. decl = "signed int %s;\t/* %s */" % (name, description)
  196. elif ftype == 'b':
  197. decl = "bool %s;\t/* %s */" % (name, description)
  198. elif ftype == 't':
  199. stl = int(width) // 6
  200. decl = "char %s[%d+1];\t/* %s */" % (name, stl, description)
  201. else:
  202. decl = "/* %s bits of type %s */" % (width, ftype)
  203. print(tabify(baseindent + inwards) + decl, file=wfp)
  204. last = name
  205. if arrayname:
  206. inwards -= step
  207. print(tabify(baseindent + inwards) + "} %s[%s];"
  208. % (arrayname, arraydim), file=wfp)
  209. if "->" in structname:
  210. typename = structname.split("->")[1]
  211. if "." in typename:
  212. structname = structname.split(".")[1]
  213. print(tabify(baseindent) + "} %s;" % typename, file=wfp)
  214. def make_json_dumper(wfp):
  215. # Write the skeleton of a JSON dump corresponding to the table.
  216. # Also, if there are subtables, some initializers
  217. if subtables:
  218. for (name, lines) in subtables:
  219. wfp.write(" const char *%s_vocabulary[] = {\n" % name)
  220. for line in lines:
  221. value = line[1]
  222. if value.endswith(" (default)"):
  223. value = value[:-10]
  224. wfp.write(' "%s",\n' % value)
  225. wfp.write(" };\n")
  226. wfp.write('#define DISPLAY_%s(n) (((n) < '
  227. '(unsigned int)NITEMS(%s_vocabulary)) ? '
  228. '%s_vocabulary[n] : "INVALID %s")\n' %
  229. (name.upper(), name, name, name.upper()))
  230. wfp.write("\n")
  231. record = after is None
  232. # Elements of each tuple type except 'a':
  233. # 1. variable name,
  234. # 2. unscaled printf format
  235. # 3. wrapper for unscaled variable reference
  236. # 4. scaled printf format
  237. # 5. wrapper for scaled variable reference
  238. # Elements of 'a' tuple:
  239. # 1. Name of array field
  240. # 2. None
  241. # 3. None
  242. # 4. None
  243. # 5. Name of length field
  244. tuples = []
  245. vocabularies = [x[0] for x in subtables]
  246. for (i, t) in enumerate(table):
  247. if '|' in t:
  248. fields = [s.strip() for s in t.split('|')]
  249. name = fields[4]
  250. ftype = fields[5]
  251. if after == name:
  252. record = True
  253. continue
  254. if before == name:
  255. record = False
  256. continue
  257. if ftype == 'x' or not record:
  258. continue
  259. fmt = r'\"%s\":' % name
  260. fmt_text = r'\"%s_text\":' % name
  261. if ftype == 'u':
  262. tuples.append((name,
  263. fmt+"%u", "%s",
  264. None, None))
  265. elif ftype == 'e':
  266. tuples.append((name,
  267. fmt+"%u", "%s",
  268. None, None))
  269. if vocabularies:
  270. this = vocabularies.pop(0)
  271. ref = "DISPLAY_%s(%%s)" % (this.upper())
  272. else:
  273. ref = 'FOO[%s]'
  274. tuples.append((name,
  275. fmt_text+r"\"%s\"", ref,
  276. None, None))
  277. elif ftype == 'i':
  278. tuples.append((name,
  279. fmt+"%d", "%s",
  280. None, None))
  281. elif ftype == 't':
  282. tuples.append((name,
  283. fmt+r'\"%s\"', "%s",
  284. None, None))
  285. elif ftype == 'b':
  286. tuples.append((name,
  287. fmt+r'\"%s\"', "JSON_BOOL(%s)",
  288. None, None))
  289. elif ftype[0] == 'd':
  290. print("Cannot generate code for data members", file=sys.stderr)
  291. sys.exit(1)
  292. elif ftype[0] == 'U':
  293. tuples.append((name,
  294. fmt+"%u", "%s",
  295. fmt+"%%.%sf" % ftype[1], '%s / SCALE'))
  296. elif ftype[0] == 'I':
  297. tuples.append((name,
  298. fmt+"%d", "%s",
  299. fmt+"%%.%sf" % ftype[1], '%s / SCALE'))
  300. elif ftype[0] == 'a':
  301. ftype = ftype[1:]
  302. if ftype[0] == '^':
  303. lengthfield = last
  304. else:
  305. lengthfield = "n" + name
  306. tuples.append((name, None, None, None, lengthfield))
  307. else:
  308. print("Unknown type code", ftype, file=sys.stderr)
  309. sys.exit(1)
  310. last = name
  311. startspan = 0
  312. def scaled(i):
  313. return tuples[i][3] is not None
  314. def tslice(e, i):
  315. return [x[i] for x in tuples[startspan:e+1]]
  316. base = " " * 8
  317. step = " " * 4
  318. inarray = None
  319. header = "(void)snprintf(buf + strlen(buf), buflen - strlen(buf),"
  320. for (i, (var, uf, uv, sf, sv)) in enumerate(tuples):
  321. if uf is not None:
  322. print(base + "for (i = 0; i < %s.%s; i++) {" % (structname, sv),
  323. file=wfp)
  324. inarray = var
  325. base = " " * 12
  326. startspan = i+1
  327. continue
  328. # At end of tuples, or if scaled flag changes, or if next op is array,
  329. # flush out dump code for a span of fields.
  330. if i+1 == len(tuples):
  331. endit = '}",'
  332. elif tuples[i+1][1] is not None:
  333. endit = r',\"%s\":[",' % tuples[i+1][0]
  334. elif scaled(i) != scaled(i + 1):
  335. endit = ',",'
  336. else:
  337. endit = None
  338. if endit:
  339. if not scaled(i):
  340. print(base + header, file=wfp)
  341. if inarray:
  342. prefix = '{"'
  343. else:
  344. prefix = '"'
  345. print(base + step + prefix + ','.join(tslice(i, 1)) + endit,
  346. file=wfp)
  347. for (j, t) in enumerate(tuples[startspan:i+1]):
  348. if inarray:
  349. ref = structname + "." + inarray + "[i]." + t[0]
  350. else:
  351. ref = structname + "." + t[0]
  352. wfp.write(base + step + t[2] % ref)
  353. if j == i - startspan:
  354. wfp.write(");\n")
  355. else:
  356. wfp.write(",\n")
  357. else:
  358. print(base + "if (scaled)", file=wfp)
  359. print(base + step + header, file=wfp)
  360. print(base + step * 2 + '"' + ','.join(tslice(i, 3)) + endit,
  361. file=wfp)
  362. for (j, t) in enumerate(tuples[startspan:i+1]):
  363. if inarray:
  364. ref = structname + "." + inarray + "[i]." + t[0]
  365. else:
  366. ref = structname + "." + t[0]
  367. wfp.write(base + step*2 + t[4] % ref)
  368. if j == i - startspan:
  369. wfp.write(");\n")
  370. else:
  371. wfp.write(",\n")
  372. print(base + "else", file=wfp)
  373. print(base + step + header, file=wfp)
  374. print(base + step * 2 + '"' + ','.join(tslice(i, 1)) + endit,
  375. file=wfp)
  376. for (j, t) in enumerate(tuples[startspan:i+1]):
  377. if inarray:
  378. ref = structname + "." + inarray + "[i]." + t[0]
  379. else:
  380. ref = structname + "." + t[0]
  381. wfp.write(base + step*2 + t[2] % ref)
  382. if j == i - startspan:
  383. wfp.write(");\n")
  384. else:
  385. wfp.write(",\n")
  386. startspan = i+1
  387. # If we were looking at a trailing array, close scope
  388. if inarray:
  389. base = " " * 8
  390. print(base + "}", file=wfp)
  391. print(base + "if (buf[strlen(buf)-1] == ',')", file=wfp)
  392. print(base + step + r"buf[strlen(buf)-1] = '\0';", file=wfp)
  393. print(base + "(void)strlcat(buf, \"]}\", buflen - strlen(buf));",
  394. file=wfp)
  395. def make_json_generator(wfp):
  396. # Write a stanza for jsongen.py.in describing how to generate a
  397. # JSON parser initializer from this table. You need to fill in
  398. # __INITIALIZER__ and default values after this is generated.
  399. extra = ""
  400. arrayname = None
  401. record = after is None
  402. print('''\
  403. {
  404. "initname" : "__INITIALIZER__",
  405. "headers": ("AIS_HEADER",),
  406. "structname": "%s",
  407. "fieldmap":(
  408. # fieldname type default''' % (structname,), file=wfp)
  409. for (i, t) in enumerate(table):
  410. if '|' in t:
  411. fields = [s.strip() for s in t.split('|')]
  412. name = fields[4]
  413. ftype = fields[5]
  414. if after == name:
  415. record = True
  416. continue
  417. if before == name:
  418. record = False
  419. continue
  420. if ftype == 'x' or not record:
  421. continue
  422. if ftype[0] == 'a':
  423. arrayname = name
  424. if arrayname.endswith("s"):
  425. typename = arrayname[:-1]
  426. else:
  427. typename = arrayname
  428. readtype = 'array'
  429. dimension = ftype[1:]
  430. if dimension[0] == '^':
  431. lengthfield = last
  432. dimension = dimension[1:]
  433. else:
  434. lengthfield = "n" + arrayname
  435. extra = " " * 8
  436. print(" ('%s',%s 'array', (" %
  437. (arrayname, " "*(10-len(arrayname))), file=wfp)
  438. print(" ('%s_t', '%s', (" % (typename, lengthfield),
  439. file=wfp)
  440. else:
  441. # Depends on the assumption that the read code
  442. # always sees unscaled JSON.
  443. readtype = {
  444. 'u': "uinteger",
  445. 'U': "uinteger",
  446. 'e': "uinteger",
  447. 'i': "integer",
  448. 'I': "integer",
  449. 'b': "boolean",
  450. 't': "string",
  451. 'd': "string",
  452. }[ftype[0]]
  453. typedefault = {
  454. 'u': "'PUT_DEFAULT_HERE'",
  455. 'U': "'PUT_DEFAULT_HERE'",
  456. 'e': "'PUT DEFAULT HERE'",
  457. 'i': "'PUT_DEFAULT_HERE'",
  458. 'I': "'PUT_DEFAULT_HERE'",
  459. 'b': "\'false\'",
  460. 't': "None",
  461. }[ftype[0]]
  462. namedefaults = {
  463. "month": "'0'",
  464. "day": "'0'",
  465. "hour": "'24'",
  466. "minute": "'60'",
  467. "second": "'60'",
  468. }
  469. default = namedefaults.get(name) or typedefault
  470. print(extra + " ('%s',%s '%s',%s %s)," %
  471. (name, " "*(10-len(name)), readtype,
  472. " "*(8-len(readtype)), default), file=wfp)
  473. if ftype[0] == 'e':
  474. print(extra + " ('%s_text',%s'ignore', None)," %
  475. (name, " "*(6-len(name))), file=wfp)
  476. last = name
  477. if arrayname:
  478. print(" )))),", file=wfp)
  479. print(" ),", file=wfp)
  480. print(" },", file=wfp)
  481. if __name__ == '__main__':
  482. try:
  483. (options, arguments) = getopt.getopt(sys.argv[1:], "a:tc:s:d:S:E:r:o:")
  484. except getopt.GetoptError as msg:
  485. print("tablecheck.py: " + str(msg))
  486. raise SystemExit(1)
  487. generate = maketable = makestruct = makedump = readgen = all = False
  488. after = before = None
  489. filestem = "tablegen"
  490. for (switch, val) in options:
  491. if switch == '-a':
  492. all = True
  493. structname = val
  494. elif switch == '-c':
  495. generate = True
  496. structname = val
  497. elif switch == '-s':
  498. makestruct = True
  499. structname = val
  500. elif switch == '-t':
  501. maketable = True
  502. elif switch == '-d':
  503. makedump = True
  504. structname = val
  505. elif switch == '-r':
  506. readgen = True
  507. structname = val
  508. elif switch == '-S':
  509. after = val
  510. elif switch == '-E':
  511. before = val
  512. elif switch == '-o':
  513. filestem = val
  514. if ((not generate and not maketable and not makestruct and
  515. not makedump and not readgen and not all)):
  516. print("tablecheck.py: no mode selected", file=sys.stderr)
  517. sys.exit(1)
  518. # First, read in the table.
  519. # Sets the following:
  520. # table - the table lines
  521. # widths - array of table widths
  522. # ranges - array of table offsets
  523. # trailing - bit length of the table or trailing array element
  524. # subtables - list of following vocabulary tables.
  525. tablename = arguments[0]
  526. table = []
  527. ranges = []
  528. subtables = []
  529. state = 0
  530. for line in sys.stdin:
  531. if state == 0 and line.startswith("//: Type") and tablename in line:
  532. state = 1
  533. continue
  534. elif state == 1: # Found table tag
  535. if line.startswith("|="):
  536. state = 2
  537. continue
  538. elif state == 2: # Found table header
  539. if line.startswith("|="):
  540. state = 3
  541. continue
  542. elif line[0] == '|':
  543. fields = line.split("|")
  544. trailing = fields[1]
  545. ranges.append(fields[1].strip())
  546. fields[1] = " " * len(fields[1])
  547. line = "|".join(fields)
  548. else:
  549. ranges.append('')
  550. table.append(line)
  551. continue
  552. elif state == 3: # Found table end
  553. state = 4
  554. continue
  555. elif state == 4: # Skipping until subsidiary table
  556. if line.startswith("//:") and "vocabulary" in line:
  557. subtable_name = line.split()[1]
  558. subtable_content = []
  559. state = 5
  560. elif state == 5: # Seen subtable header
  561. if line.startswith("|="):
  562. state = 6
  563. continue
  564. elif state == 6: # Parsing subtable content
  565. if line.startswith("|="):
  566. subtables.append((subtable_name, subtable_content))
  567. state = 4
  568. continue
  569. elif line[0] == '|':
  570. subtable_content.append(
  571. [f.strip() for f in line[1:].strip().split("|")])
  572. continue
  573. if state == 0:
  574. print("Can't find named table.", file=sys.stderr)
  575. sys.exit(1)
  576. elif state < 3:
  577. print("Ill-formed table (in state %d)." % state, file=sys.stderr)
  578. sys.exit(1)
  579. table = table[1:]
  580. ranges = ranges[1:]
  581. widths = []
  582. for line in table:
  583. fields = line.split('|')
  584. if '|' not in line: # Continuation line
  585. widths.append('')
  586. elif fields[5][0] == 'a': # Array boundary indicator
  587. widths.append(None)
  588. else:
  589. widths.append(fields[2].strip())
  590. if '-' in trailing:
  591. trailing = trailing.split('-')[1]
  592. trailing = str(int(trailing)+1)
  593. # Compute offsets for an AIVDM message breakdown, given the bit widths.
  594. offsets = []
  595. base = 0
  596. corrections = False
  597. for w in widths:
  598. if w is None:
  599. offsets.append(repr(base))
  600. base = 0
  601. elif w == '':
  602. offsets.append('')
  603. else:
  604. w = int(w)
  605. offsets.append("%d-%d" % (base, base + w - 1))
  606. base += w
  607. if [p for p in zip(ranges, offsets) if p[0] != p[1]]:
  608. corrections = True
  609. print("Offset corrections:")
  610. for (old, new) in zip(ranges, offsets):
  611. if old != new:
  612. print(old, "->", new, file=sys.stderr)
  613. owidth = max(*list(map(len, offsets)))
  614. for (i, off) in enumerate(offsets):
  615. offsets[i] += " " * (owidth - len(offsets[i]))
  616. # Here's where we generate useful output.
  617. if all:
  618. if corrections:
  619. correct_table(open(filestem + ".txt", "w"))
  620. make_driver_code(open(filestem + ".c", "w"))
  621. make_structure(open(filestem + ".h", "w"))
  622. make_json_dumper(open(filestem + "_json.c", "w"))
  623. make_json_generator(open(filestem + ".py", "w"))
  624. elif maketable:
  625. correct_table(sys.stdout)
  626. elif generate:
  627. make_driver_code(sys.stdout)
  628. elif makestruct:
  629. make_structure(sys.stdout)
  630. elif makedump:
  631. make_json_dumper(sys.stdout)
  632. elif readgen:
  633. make_json_generator(sys.stdout)
  634. # end