mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-21 14:24:09 +01:00
bolts: new parsing script and templates for new bolt format
the RFC's extract-format.py is switching to a new format. this script can correctly parse them. mostly moves logic over from generate-wire.py, uses a Python formatting libarary called mako, which needs to be installed prior to running this script. you can add it to your system with sudo apt-get install python3-mako
This commit is contained in:
parent
01e292a3ff
commit
6c240ab589
5 changed files with 867 additions and 0 deletions
71
tools/gen/header_template
Normal file
71
tools/gen/header_template
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* This file was generated by generate-bolts.py */
|
||||
/* Do not modify this file! Modify the _csv file it was generated from. */
|
||||
/* Original template can be found at tools/gen/header_template */
|
||||
|
||||
#ifndef LIGHTNING_${idem}
|
||||
#define LIGHTNING_${idem}
|
||||
#include <ccan/tal/tal.h>
|
||||
#include <wire/wire.h>
|
||||
% for i in includes:
|
||||
${i}
|
||||
% endfor
|
||||
|
||||
## Enum sets for wire messages & tlvs
|
||||
% for enum_set in enum_sets:
|
||||
enum ${enum_set['name']} {
|
||||
% for msg in enum_set['set']:
|
||||
## TODO: add back comments for a message
|
||||
##% for comment in msg.comments:
|
||||
##/* ${comment} */
|
||||
##% endfor
|
||||
${msg.enum_name()} = ${msg.number},
|
||||
% endfor
|
||||
};
|
||||
|
||||
%endfor
|
||||
## The 'name' functions for the enums
|
||||
% for enum_set in enum_sets:
|
||||
const char *${enum_set['name']}_name(int e);
|
||||
% endfor
|
||||
|
||||
## Structs for subtypes + tlv messages
|
||||
% for struct in structs:
|
||||
struct ${struct.struct_name()} {
|
||||
% for f in struct.fields.values():
|
||||
% if f.is_len_field:
|
||||
<% continue %>
|
||||
% endif
|
||||
% if f.has_len_field():
|
||||
${f.type_obj.type_name()} *${f.name};
|
||||
% elif f.is_array():
|
||||
${f.type_obj.type_name()} ${f.name}[${f.count}];
|
||||
% else:
|
||||
${f.type_obj.type_name()} ${f.name};
|
||||
% endif
|
||||
% endfor
|
||||
};
|
||||
% endfor
|
||||
## Structs for TLV types!
|
||||
% for tlv in tlvs:
|
||||
struct ${tlv.name} {
|
||||
% for msg_name in tlv.messages.keys():
|
||||
struct ${tlv.name}_${msg_name} *${msg_name};
|
||||
% endfor
|
||||
};
|
||||
% endfor
|
||||
|
||||
% if options.expose_subtypes and bool(subtypes):
|
||||
% for subtype in subtypes:
|
||||
/* SUBTYPE: ${subtype.name.upper()} */
|
||||
void towire_${subtype.name}(u8 **p, const struct ${subtype.name} *${subtype.name});
|
||||
bool fromwire_${subtype.name}(${'const tal_t *ctx, ' if subtype.has_len_fields() else '' }const u8 **cursor, size_t *plen, struct ${subtype.name} *${subtype.name});
|
||||
|
||||
% endfor
|
||||
% endif
|
||||
% for msg in messages:
|
||||
/* WIRE: ${msg.name.upper()} */
|
||||
u8 *towire_${msg.name}(const tal_t *ctx${''.join([f.arg_desc_to() for f in msg.fields.values() if not f.is_optional])});
|
||||
bool fromwire_${msg.name}(${'const tal_t *ctx, ' if msg.has_len_fields() else ''}const void *p${''.join([f.arg_desc_from() for f in msg.fields.values() if not f.is_optional])});
|
||||
|
||||
% endfor
|
||||
#endif /* LIGHTNING_${idem} */
|
212
tools/gen/impl_template
Normal file
212
tools/gen/impl_template
Normal file
|
@ -0,0 +1,212 @@
|
|||
/* This file was generated by generate-bolts.py */
|
||||
/* Do not modify this file! Modify the _csv file it was generated from. */
|
||||
/* Original template can be found at tools/gen/impl_template */
|
||||
|
||||
#include <${header_filename}>
|
||||
#include <ccan/mem/mem.h>
|
||||
#include <ccan/tal/str/str.h>
|
||||
#include <stdio.h>
|
||||
|
||||
% for enum_set in enum_sets:
|
||||
const char *${enum_set['name']}_name(int e)
|
||||
{
|
||||
static char invalidbuf[sizeof("INVALID ") + STR_MAX_CHARS(e)];
|
||||
|
||||
switch ((enum ${enum_set['name']})e) {
|
||||
% for msg in enum_set['set']:
|
||||
case ${msg.enum_name()}: return "${msg.enum_name()}";
|
||||
% endfor
|
||||
}
|
||||
|
||||
snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e);
|
||||
return invalidbuf;
|
||||
}
|
||||
|
||||
% endfor
|
||||
|
||||
## FIXME: extract out partials for the method declarations
|
||||
## (shared between here and header_template)
|
||||
% for subtype in subtypes:
|
||||
/* SUBTYPE: ${subtype.name.upper()} */
|
||||
<% static = '' if options.expose_subtypes else 'static ' %>
|
||||
${static}void towire_${subtype.name}(u8 **p, const struct ${subtype.name} *${subtype.name})
|
||||
{
|
||||
% for f in subtype.get_len_fields():
|
||||
${f.type_obj.type_name()} ${f.name} = tal_count(${subtype.name}->${f.len_field_of});
|
||||
% endfor
|
||||
|
||||
## FIXME: abstract this out? (semi-shared with towire_msg, minus the optional bits)
|
||||
% for f in subtype.fields.values():
|
||||
## FIXME: add field level comments
|
||||
<%
|
||||
fieldname = '{}->{}'.format(subtype.name,f.name)
|
||||
%> \
|
||||
% if f.is_array() or f.has_len_field(): ## multiples?
|
||||
% if f.type_obj.has_array_helper():
|
||||
towire_${f.type_obj.name}_array(&p, ${fieldname}, ${f.size()});
|
||||
% else:
|
||||
for (size_t i = 0; i < ${f.size()}; i++)
|
||||
% if f.type_obj.is_assignable() or f.type_obj.has_len_fields():
|
||||
towire_${f.type_obj.name}(p, ${fieldname}[i]);
|
||||
% else:
|
||||
towire_${f.type_obj.name}(p, ${fieldname} + i);
|
||||
% endif
|
||||
% endif
|
||||
% elif f.len_field_of:
|
||||
towire_${f.type_obj.name}(p, ${f.name});
|
||||
% else:
|
||||
towire_${f.type_obj.name}(&p, ${'' if f.type_obj.is_assignable() else '*'}${fieldname});
|
||||
% endif
|
||||
% endfor
|
||||
}
|
||||
${static}bool fromwire_${subtype.name}(${'const tal_t *ctx, ' if subtype.has_len_fields() else '' }const u8 **cursor, size_t *plen, struct ${subtype.name} *${subtype.name})
|
||||
{
|
||||
## Length field declarations
|
||||
% for f in subtype.get_len_fields():
|
||||
${f.type_obj.type_name()} ${f.name};
|
||||
% endfor
|
||||
|
||||
## FIXME: field level comments
|
||||
% for f in subtype.fields.values():
|
||||
<%
|
||||
fieldname = '{}->{}'.format(subtype.name,f.name)
|
||||
typename = f.type_obj.type_name()
|
||||
type_ = f.type_obj.name
|
||||
%> \
|
||||
% if f.has_len_field():
|
||||
${fieldname} = ${f.len_field} ? tal_arr(ctx, ${typename}${' *' if f.type_obj.has_len_fields() else ''}, ${f.len_field}) : NULL;
|
||||
% endif
|
||||
<%
|
||||
if f.is_array():
|
||||
fieldname = '*' + fieldname
|
||||
ctx = 'ctx'
|
||||
else:
|
||||
ctx = fieldname
|
||||
%> \
|
||||
% if f.is_array() or f.has_len_field():
|
||||
% if f.type_obj.has_array_helper():
|
||||
fromwire_${type_}_array(cursor, plen, ${fieldname}, ${f.size()});
|
||||
% else:
|
||||
for (size_t i = 0; i < ${f.size()}; i++)
|
||||
% if f.type_obj.is_assignable():
|
||||
(${fieldname})[i] = fromwire_${type_}(cursor, plen);
|
||||
% elif f.has_len_field():
|
||||
(${fieldname})[i] = fromwire_${type_}(${ctx}, cursor, plen);
|
||||
% else:
|
||||
fromwire_${type_}(${ctx}, cursor, plen, ${fieldname} + i);
|
||||
% endif
|
||||
% endif
|
||||
% else:
|
||||
% if f.type_obj.is_assignable():
|
||||
${ f.name if f.len_field_of else fieldname} = fromwire_${type_}(cursor, plen);
|
||||
% else:
|
||||
fromwire_${type_}(cursor, plen, &${fieldname});
|
||||
% endif
|
||||
%endif
|
||||
% endfor
|
||||
|
||||
return cursor != NULL;
|
||||
}
|
||||
|
||||
% endfor
|
||||
% for msg in messages:
|
||||
/* WIRE: ${msg.name.upper()} */
|
||||
u8 *towire_${msg.name}(const tal_t *ctx${''.join([f.arg_desc_to() for f in msg.fields.values() if not f.is_optional])})
|
||||
{
|
||||
## FIXME: we're ignoring TLV's rn
|
||||
% for f in msg.get_len_fields():
|
||||
${f.type_obj.type_name()} ${f.name} = tal_count(${f.len_field_of});
|
||||
% endfor
|
||||
u8 *p = tal_arr(ctx, u8, 0);
|
||||
|
||||
towire_u16(&p, ${msg.enum_name()});
|
||||
% for f in msg.fields.values():
|
||||
## FIXME: add field level comments
|
||||
% if f.is_array() or f.has_len_field(): ## multiples?
|
||||
% if f.type_obj.has_array_helper():
|
||||
towire_${f.type_obj.name}_array(&p, ${f.name}, ${f.size()});
|
||||
% else:
|
||||
for (size_t i = 0; i < ${f.size()}; i++)
|
||||
% if f.type_obj.is_assignable() or f.type_obj.has_len_fields():
|
||||
towire_${f.type_obj.name}(&p, ${f.name}[i]);
|
||||
% else:
|
||||
towire_${f.type_obj.name}(&p, ${f.name} + i);
|
||||
% endif
|
||||
% endif
|
||||
% elif f.is_optional: ## is optional?
|
||||
if (!${f.name})
|
||||
towire_bool(&p, false);
|
||||
else {
|
||||
towire_bool(&p, true);
|
||||
towire_${f.type_obj.name}(&p, ${'*' if f.type_obj.is_assignable() else ''}${f.name});
|
||||
}
|
||||
% else: ## all other cases
|
||||
towire_${f.type_obj.name}(&p, ${f.name});
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
return memcheck(p, tal_count(p));
|
||||
}
|
||||
bool fromwire_${msg.name}(${'const tal_t *ctx, ' if msg.has_len_fields() else ''}const void *p${''.join([f.arg_desc_from() for f in msg.fields.values() if not f.is_optional])})
|
||||
{
|
||||
% if msg.get_len_fields():
|
||||
% for f in msg.get_len_fields():
|
||||
${f.type_obj.type_name()} ${f.name};
|
||||
% endfor
|
||||
|
||||
% endif
|
||||
const u8 *cursor = p;
|
||||
size_t plen = tal_count(p);
|
||||
|
||||
if (fromwire_u16(&cursor, &plen) != ${msg.enum_name()})
|
||||
return false;
|
||||
## FIXME: TLV has been omitted
|
||||
% for f in msg.fields.values():
|
||||
<%
|
||||
typename = f.type_obj.type_name()
|
||||
if f.type_obj.has_len_fields():
|
||||
typename = typename + ' *'
|
||||
type_ = f.type_obj.name
|
||||
%> \
|
||||
% if f.has_len_field():
|
||||
// 2nd case ${f.name}
|
||||
*${f.name} = ${f.len_field} ? tal_arr(ctx, ${typename}, ${f.len_field}) : NULL;
|
||||
% endif
|
||||
% if f.len_field_of:
|
||||
${f.name} = fromwire_${type_}(&cursor, &plen);
|
||||
% elif f.is_array() or f.has_len_field():
|
||||
<%
|
||||
if f.has_len_field():
|
||||
fieldname = '*' + f.name
|
||||
ctx = fieldname
|
||||
else:
|
||||
fieldname = f.name
|
||||
ctx = 'ctx'
|
||||
%> \
|
||||
% if f.type_obj.has_array_helper():
|
||||
fromwire_${type_}_array(&cursor, &plen, ${fieldname}, ${f.size()});
|
||||
% else:
|
||||
for (size_t i = 0; i < ${f.size()}; i++)
|
||||
% if f.type_obj.is_assignable():
|
||||
(${fieldname})[i] = fromwire_${type_}(&cursor, &plen);
|
||||
## FIXME: case for 'varlen' structs
|
||||
## (${fieldname})[i] = fromwire_${type_}(${ctx}, &cursor, &plen);
|
||||
% else:
|
||||
fromwire_${type_}(${ctx + ', ' if f.type_obj.is_subtype() else ''}&cursor, &plen, ${fieldname} + i);
|
||||
% endif
|
||||
% endif
|
||||
% else:
|
||||
## FIXME: leaves out optional fields + 'varlen' structs
|
||||
%if f.type_obj.is_assignable():
|
||||
*${f.name} = fromwire_${type_}(&cursor, &plen);
|
||||
% else:
|
||||
fromwire_${type_}(&cursor, &plen, ${f.name});
|
||||
## assignment
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
return cursor != NULL;
|
||||
}
|
||||
|
||||
% endfor
|
||||
##${func_decls}
|
20
tools/gen/print_header_template
Normal file
20
tools/gen/print_header_template
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* This file was generated by generate-bolts.py */
|
||||
/* Do not modify this file! Modify the _csv file it was generated from. */
|
||||
/* Template located at tools/gen/print_header_template */
|
||||
#ifndef LIGHTNING_${idem}
|
||||
#define LIGHTNING_${idem}
|
||||
#include <ccan/tal/tal.h>
|
||||
#include <devtools/print_wire.h>
|
||||
% for i in includes:
|
||||
${i}
|
||||
% endfor
|
||||
|
||||
void print${options.enum_name}_message(const u8 *msg);
|
||||
|
||||
void print${options.enum_name}_tlv_message(const char *tlv_name, const u8 *msg);
|
||||
|
||||
% for msg in messages:
|
||||
void printwire_${msg.name}(const char *fieldname, const u8 *cursor);
|
||||
|
||||
% endfor
|
||||
#endif /* LIGHTNING_${idem} */
|
109
tools/gen/print_impl_template
Normal file
109
tools/gen/print_impl_template
Normal file
|
@ -0,0 +1,109 @@
|
|||
/* This file was generated by generate-bolts.py */
|
||||
/* Do not modify this file! Modify the _csv file it was generated from. */
|
||||
|
||||
#include "${options.header_filename}"
|
||||
#include <ccan/mem/mem.h>
|
||||
#include <ccan/tal/str/str.h>
|
||||
#include <common/utils.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void print${options.enum_name}_message(const u8 *msg)
|
||||
{
|
||||
switch ((enum ${options.enum_name})fromwire_peektype(msg)) {
|
||||
% for msg in enum_sets[0]['set']:
|
||||
case ${msg.enum_name()}:
|
||||
printf("${msg.enum_name()}:\n");
|
||||
printwire_${msg.name}("${msg.name}", msg);
|
||||
return;
|
||||
% endfor
|
||||
}
|
||||
|
||||
printf("UNKNOWN: %s\\n", tal_hex(msg, msg));
|
||||
}
|
||||
|
||||
void print${options.enum_name}_tlv_message(const char *tlv_name, const u8 *msg)
|
||||
{
|
||||
% if not bool(tlvs):
|
||||
printf("~~ No TLV definition found for %s ~~\\n", tlv_name);
|
||||
% else:
|
||||
% for tlv in tlvs:
|
||||
if (strcmp(tlv_name, "${tlv.name}") == 0) {
|
||||
printwire_${tlv.name}("${tlv.name}", msg);
|
||||
return;
|
||||
}
|
||||
% endfor
|
||||
printf("ERR: Unknown TLV message type: %s\n", tlv_name);
|
||||
% endif
|
||||
}
|
||||
|
||||
## 'component' for 'truncate check
|
||||
<%def name="truncate_check(nested=False)">
|
||||
if (!${ '*' if nested else '' }cursor) {
|
||||
printf("**TRUNCATED**\n");
|
||||
return;
|
||||
}
|
||||
</%def> \
|
||||
## definition for printing field sets
|
||||
<%def name="print_fieldset(fields, nested, cursor, plen)">
|
||||
## FIXME: optional field handling omitted since we only generate these for bolts rn
|
||||
% for f in fields:
|
||||
% if f.len_field_of:
|
||||
${f.type_obj.type_name()} ${f.name} = fromwire_${f.type_obj.name}(${cursor}, ${plen});${truncate_check(nested)} <% continue %> \
|
||||
% endif
|
||||
printf("${f.name}=");
|
||||
% if f.is_array() or f.has_len_field():
|
||||
% if f.type_obj.has_array_helper():
|
||||
printwire_${f.type_obj.name}_array(tal_fmt(NULL, "%s.${f.name}", fieldname), ${cursor}, ${plen}, ${f.size()});
|
||||
% else:
|
||||
printf("[");
|
||||
for (size_t i = 0; i < ${f.size()}; i++) {
|
||||
${f.type_obj.type_name()} v;
|
||||
% if f.type_obj.is_assignable():
|
||||
v = fromwire_${f.type_obj.name}(${cursor}, ${plen});
|
||||
% else:
|
||||
fromwire_${f.type_obj.name}(${cursor}, ${plen}, &v);
|
||||
% endif
|
||||
${truncate_check(nested)} \
|
||||
printwire_${f.type_obj.name}(tal_fmt(NULL, "%s.${f.name}", fieldname), &v);
|
||||
}
|
||||
printf("]");
|
||||
% endif
|
||||
${truncate_check(nested)} \
|
||||
% else:
|
||||
% if f.type_obj.is_assignable():
|
||||
${f.type_obj.type_name()} ${f.name} = fromwire_${f.type_obj.name}(${cursor}, ${plen});
|
||||
% else:
|
||||
${f.type_obj.type_name()} ${f.name};
|
||||
fromwire_${f.type_obj.name}(${cursor}, ${plen}, &${f.name});
|
||||
% endif
|
||||
printwire_${f.type_obj.name}(tal_fmt(NULL, "%s.${f.name}", fieldname), &${f.name}); ${truncate_check(nested)} \
|
||||
% endif
|
||||
% endfor
|
||||
</%def> \
|
||||
|
||||
## Definitions for 'subtypes'
|
||||
% for subtype in subtypes:
|
||||
static void printwire_${subtype.name}(const char *fieldname, const u9 **cursor, size_t *plen)
|
||||
{
|
||||
${print_fieldset(subtype.fields.values(), True, 'cursor', 'plen')}
|
||||
}
|
||||
% endfor
|
||||
|
||||
## FIXME: handling for tlv's :/
|
||||
% for msg in messages:
|
||||
void printwire_${msg.name}(const char *fieldname, const u8 *cursor)
|
||||
{
|
||||
|
||||
size_t plen = tal_count(cursor);
|
||||
if (fromwire_u16(&cursor, &plen) != ${msg.enum_name()}) {
|
||||
printf("WRONG TYPE?!\n");
|
||||
return;
|
||||
}
|
||||
${print_fieldset(msg.fields.values(), False, '&cursor', '&plen')}
|
||||
|
||||
## Length check
|
||||
if (plen != 0)
|
||||
printf("EXTRA: %s\n", tal_hexstr(NULL, cursor, plen));
|
||||
}
|
||||
% endfor
|
455
tools/generate-bolts.py
Executable file
455
tools/generate-bolts.py
Executable file
|
@ -0,0 +1,455 @@
|
|||
#! /usr/bin/env python3
|
||||
# Script to parse spec output CSVs and produce C files.
|
||||
# Released by lisa neigut under CC0:
|
||||
# https://creativecommons.org/publicdomain/zero/1.0/
|
||||
#
|
||||
# Reads from stdin, outputs C header or body file.
|
||||
#
|
||||
# Standard message types:
|
||||
# msgtype,<msgname>,<value>[,<option>]
|
||||
# msgdata,<msgname>,<fieldname>,<typename>,[<count>][,<option>]
|
||||
#
|
||||
# TLV types:
|
||||
# tlvtype,<tlvstreamname>,<tlvname>,<value>[,<option>]
|
||||
# tlvdata,<tlvstreamname>,<tlvname>,<fieldname>,<typename>,[<count>][,<option>]
|
||||
#
|
||||
# Subtypes:
|
||||
# subtype,<subtypename>
|
||||
# subtypedata,<subtypename>,<fieldname>,<typename>,[<count>]
|
||||
|
||||
from argparse import ArgumentParser, REMAINDER
|
||||
import copy
|
||||
import fileinput
|
||||
from mako.template import Template
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
# Generator to give us one line at a time.
|
||||
def next_line(args, lines):
|
||||
if lines is None:
|
||||
lines = fileinput.input(args)
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
yield i, line.strip()
|
||||
|
||||
|
||||
# Class definitions, to keep things classy
|
||||
class Field(object):
|
||||
def __init__(self, name, type_obj, optional=False):
|
||||
self.name = name
|
||||
self.type_obj = type_obj
|
||||
self.count = 1
|
||||
self.is_optional = optional
|
||||
self.len_field_of = None
|
||||
self.len_field = None
|
||||
|
||||
def add_count(self, count):
|
||||
self.count = int(count)
|
||||
|
||||
def add_len_field(self, len_field):
|
||||
self.count = False
|
||||
# we cache our len-field's name
|
||||
self.len_field = len_field.name
|
||||
# the len-field caches our name
|
||||
len_field.len_field_of = self.name
|
||||
|
||||
def is_array(self):
|
||||
return self.count > 1
|
||||
|
||||
def has_len_field(self):
|
||||
return not self.count
|
||||
|
||||
def is_optional(self):
|
||||
return self.is_optional
|
||||
|
||||
def size(self):
|
||||
if self.count:
|
||||
return self.count
|
||||
return self.len_field
|
||||
|
||||
def arg_desc_to(self):
|
||||
if self.len_field_of:
|
||||
return ''
|
||||
type_name = self.type_obj.type_name()
|
||||
if self.is_array():
|
||||
return ', const {} {}[{}]'.format(type_name, self.name, self.count)
|
||||
if self.type_obj.is_assignable() and not self.has_len_field():
|
||||
return ', {} {}'.format(type_name, self.name)
|
||||
# Are we a variable number of objects with a variable number of things?
|
||||
if self.has_len_field() and self.type_obj.has_len_fields():
|
||||
return ', {} **{}'.format(type_name, self.name)
|
||||
return ', const {} *{}'.format(type_name, self.name)
|
||||
|
||||
def arg_desc_from(self):
|
||||
if self.len_field_of:
|
||||
return ''
|
||||
type_name = self.type_obj.type_name()
|
||||
if self.is_array():
|
||||
return ', {} {}[{}]'.format(type_name, self.name, self.count)
|
||||
ptrs = '*'
|
||||
if self.has_len_field():
|
||||
ptrs += '*'
|
||||
if self.is_optional or self.type_obj.has_len_fields():
|
||||
ptrs += '*'
|
||||
return ', {} {}{}'.format(type_name, ptrs, self.name)
|
||||
|
||||
|
||||
class FieldSet(object):
|
||||
def __init__(self):
|
||||
self.fields = {}
|
||||
self.optional_fields = False
|
||||
self.len_fields = {}
|
||||
|
||||
def add_data_field(self, field_name, type_obj, count=1, is_optional=[]):
|
||||
# FIXME: use this somewhere?
|
||||
if is_optional:
|
||||
self.optional_fields = True
|
||||
|
||||
field = Field(field_name, type_obj, bool(is_optional))
|
||||
if bool(count):
|
||||
try:
|
||||
field.add_count(int(count))
|
||||
except ValueError:
|
||||
len_field = self.find_data_field(count)
|
||||
if not len_field:
|
||||
raise ValueError("No length field found with name {} for {}:{}"
|
||||
.format(count, self.name, field_name))
|
||||
field.add_len_field(len_field)
|
||||
self.len_fields[len_field.name] = len_field
|
||||
|
||||
self.fields[field_name] = field
|
||||
|
||||
def find_data_field(self, field_name):
|
||||
return self.fields[field_name]
|
||||
|
||||
def get_len_fields(self):
|
||||
return list(self.len_fields.values())
|
||||
|
||||
def has_len_fields(self):
|
||||
return bool(self.len_fields)
|
||||
|
||||
|
||||
class Type(FieldSet):
|
||||
assignables = [
|
||||
'u8',
|
||||
'u16',
|
||||
'u32',
|
||||
'u64',
|
||||
'bool',
|
||||
'amount_sat',
|
||||
'amount_msat',
|
||||
# FIXME: omits var_int
|
||||
]
|
||||
|
||||
typedefs = [
|
||||
'u8',
|
||||
'u16',
|
||||
'u32',
|
||||
'u64',
|
||||
'bool',
|
||||
'secp256k1_ecdsa_signature',
|
||||
]
|
||||
|
||||
# Some BOLT types are re-typed based on their field name
|
||||
# ('fieldname partial', 'original type'): ('true type', 'collapse array?')
|
||||
name_field_map = {
|
||||
('txid', 'sha256'): ('bitcoin_txid', False),
|
||||
('amt', 'u64'): ('amount_msat', False),
|
||||
('msat', 'u64'): ('amount_msat', False),
|
||||
('satoshis', 'u64'): ('amount_sat', False),
|
||||
('node_id', 'pubkey'): ('node_id', False),
|
||||
('temporary_channel_id', 'u8'): ('channel_id', True),
|
||||
}
|
||||
|
||||
# For BOLT specified types, a few type names need to be simply 'remapped'
|
||||
# 'original type': 'true type'
|
||||
name_remap = {
|
||||
'byte': 'u8',
|
||||
'signature': 'secp256k1_ecdsa_signature',
|
||||
'chain_hash': 'bitcoin_blkid',
|
||||
'point': 'pubkey',
|
||||
# FIXME: omits 'pad'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def true_type(type_name, field_name=None):
|
||||
""" Returns 'true' type of a given type and a flag if
|
||||
we've remapped a variable size/array type to a single struct
|
||||
(an example of this is 'temporary_channel_id' which is specified
|
||||
as a 32*byte, but we re-map it to a channel_id
|
||||
"""
|
||||
if type_name in Type.name_remap:
|
||||
type_name = Type.name_remap[type_name]
|
||||
|
||||
if field_name:
|
||||
for (partial, t), true_type in Type.name_field_map.items():
|
||||
if partial in field_name and t == type_name:
|
||||
return true_type
|
||||
return (type_name, False)
|
||||
|
||||
def __init__(self, name):
|
||||
FieldSet.__init__(self)
|
||||
self.name = name
|
||||
self.depends_on = {}
|
||||
# FIXME: internal msgs can be enums
|
||||
self.is_enum = False
|
||||
|
||||
def add_data_field(self, field_name, type_obj, count=1, is_optional=[]):
|
||||
FieldSet.add_data_field(self, field_name, type_obj, count, is_optional)
|
||||
if type_obj.name not in self.depends_on:
|
||||
self.depends_on[type_obj.name] = type_obj
|
||||
|
||||
def type_name(self):
|
||||
if self.name in self.typedefs:
|
||||
return self.name
|
||||
prefix = 'enum ' if self.is_enum else 'struct '
|
||||
return prefix + self.name
|
||||
|
||||
# We only accelerate the u8 case: it's common and trivial.
|
||||
def has_array_helper(self):
|
||||
return self.name in ['u8']
|
||||
|
||||
def struct_name(self):
|
||||
return self.name
|
||||
|
||||
def subtype_deps(self):
|
||||
return [dep for dep in self.depends_on.values() if dep.is_subtype()]
|
||||
|
||||
def is_subtype(self):
|
||||
return bool(self.fields)
|
||||
|
||||
def is_assignable(self):
|
||||
return self.name in self.assignables or self.is_enum
|
||||
|
||||
|
||||
class Message(FieldSet):
|
||||
def __init__(self, name, number, option=[], enum_prefix='wire', struct_prefix=None):
|
||||
FieldSet.__init__(self)
|
||||
self.name = name
|
||||
self.number = number
|
||||
self.enum_prefix = enum_prefix
|
||||
self.option = option[0] if len(option) else None
|
||||
self.struct_prefix = struct_prefix
|
||||
self.enumname = None
|
||||
|
||||
def has_option(self):
|
||||
return self.option is not None
|
||||
|
||||
def enum_name(self):
|
||||
name = self.enumname if self.enumname else self.name
|
||||
return "{}_{}".format(self.enum_prefix, name).upper()
|
||||
|
||||
def struct_name(self):
|
||||
if self.struct_prefix:
|
||||
return self.struct_prefix + "_" + self.name
|
||||
return self.name
|
||||
|
||||
|
||||
class Tlv(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.messages = {}
|
||||
|
||||
def add_message(self, tokens):
|
||||
""" tokens -> (name, value[, option]) """
|
||||
self.messages[tokens[0]] = Message(tokens[0], tokens[1], tokens[2:],
|
||||
self.name, self.name)
|
||||
|
||||
def find_message(self, name):
|
||||
return self.messages[name]
|
||||
|
||||
|
||||
class Master(object):
|
||||
types = {}
|
||||
tlvs = {}
|
||||
messages = {}
|
||||
extension_msgs = {}
|
||||
inclusions = []
|
||||
|
||||
def add_include(self, inclusion):
|
||||
self.inclusions.append(inclusion)
|
||||
|
||||
def add_tlv(self, tlv_name):
|
||||
if tlv_name not in self.tlvs:
|
||||
self.tlvs[tlv_name] = Tlv(tlv_name)
|
||||
return self.tlvs[tlv_name]
|
||||
|
||||
def add_message(self, tokens):
|
||||
""" tokens -> (name, value[, option])"""
|
||||
self.messages[tokens[0]] = Message(tokens[0], tokens[1], tokens[2:])
|
||||
|
||||
def add_extension_msg(self, name, msg):
|
||||
self.extension_msgs[name] = msg
|
||||
|
||||
def add_type(self, type_name, field_name=None):
|
||||
# Check for special type name re-mapping
|
||||
type_name, collapse_original = Type.true_type(type_name, field_name)
|
||||
|
||||
if type_name not in self.types:
|
||||
self.types[type_name] = Type(type_name)
|
||||
return self.types[type_name], collapse_original
|
||||
|
||||
def find_type(self, type_name):
|
||||
return self.types[type_name]
|
||||
|
||||
def find_message(self, msg_name):
|
||||
if msg_name in self.messages:
|
||||
return self.messages[msg_name]
|
||||
if msg_name in self.extension_msgs:
|
||||
return self.extension_msgs[msg_name]
|
||||
return None
|
||||
|
||||
def find_tlv(self, tlv_name):
|
||||
return self.tlvs[tlv_name]
|
||||
|
||||
def get_ordered_subtypes(self):
|
||||
""" We want to order subtypes such that the 'no dependency'
|
||||
types are printed first """
|
||||
subtypes = [s for s in self.types.values() if s.is_subtype()]
|
||||
|
||||
# Start with subtypes without subtype dependencies
|
||||
sorted_types = [s for s in subtypes if not len(s.subtype_deps())]
|
||||
unsorted = [s for s in subtypes if len(s.subtype_deps())]
|
||||
while len(unsorted):
|
||||
names = [s.name for s in sorted_types]
|
||||
for s in list(unsorted):
|
||||
if all([dependency.name in names for dependency in s.subtype_deps()]):
|
||||
sorted_types.append(s)
|
||||
unsorted.remove(s)
|
||||
return sorted_types
|
||||
|
||||
def tlv_messages(self):
|
||||
return [m for tlv in self.tlvs.values() for m in tlv.messages.values()]
|
||||
|
||||
def find_template(self, options):
|
||||
dirpath = os.path.dirname(os.path.abspath(__file__))
|
||||
filename = dirpath + '/gen/{}{}_template'.format(
|
||||
'print_' if options.print_wire else '', options.page)
|
||||
|
||||
return Template(filename=filename)
|
||||
|
||||
def write(self, options, output):
|
||||
template = self.find_template(options)
|
||||
enum_sets = []
|
||||
enum_sets.append({
|
||||
'name': options.enum_name,
|
||||
'set': self.messages.values(),
|
||||
})
|
||||
for tlv in self.tlvs.values():
|
||||
enum_sets.append({
|
||||
'name': tlv.name,
|
||||
'set': tlv.messages.values(),
|
||||
})
|
||||
stuff = {}
|
||||
stuff['options'] = options
|
||||
stuff['idem'] = re.sub(r'[^A-Z]+', '_', options.header_filename.upper())
|
||||
stuff['header_filename'] = options.header_filename
|
||||
stuff['includes'] = self.inclusions
|
||||
stuff['enum_sets'] = enum_sets
|
||||
subtypes = self.get_ordered_subtypes()
|
||||
stuff['structs'] = subtypes + self.tlv_messages()
|
||||
stuff['tlvs'] = self.tlvs.values()
|
||||
stuff['messages'] = list(self.messages.values()) + list(self.extension_msgs.values())
|
||||
stuff['subtypes'] = subtypes
|
||||
|
||||
print(template.render(**stuff), file=output)
|
||||
|
||||
|
||||
def main(options, args=None, output=sys.stdout, lines=None):
|
||||
genline = next_line(args, lines)
|
||||
|
||||
# Create a new 'master' that serves as the coordinator for the file generation
|
||||
master = Master()
|
||||
try:
|
||||
while True:
|
||||
ln, line = next(genline)
|
||||
tokens = line.split(',')
|
||||
token_type = tokens[0]
|
||||
if token_type == 'subtype':
|
||||
master.add_type(tokens[1])
|
||||
elif token_type == 'subtypedata':
|
||||
subtype = master.find_type(tokens[1])
|
||||
if not subtype:
|
||||
raise ValueError('Unknown subtype {} for data.\nat {}:{}'
|
||||
.format(tokens[1], ln, line))
|
||||
type_obj, collapse = master.add_type(tokens[3], tokens[2])
|
||||
if collapse:
|
||||
count = 1
|
||||
else:
|
||||
count = tokens[4]
|
||||
subtype.add_data_field(tokens[2], type_obj, count)
|
||||
elif token_type == 'tlvtype':
|
||||
tlv = master.add_tlv(tokens[1])
|
||||
tlv.add_message(tokens[2:])
|
||||
elif token_type == 'tlvdata':
|
||||
type_obj, collapse = master.add_type(tokens[4], tokens[3])
|
||||
tlv = master.find_tlv(tokens[1])
|
||||
if not tlv:
|
||||
raise ValueError('tlvdata for unknown tlv {}.\nat {}:{}'
|
||||
.format(tokens[1], ln, line))
|
||||
msg = tlv.find_message(tokens[2])
|
||||
if not msg:
|
||||
raise ValueError('tlvdata for unknown tlv-message {}.\nat {}:{}'
|
||||
.format(tokens[2], ln, line))
|
||||
if collapse:
|
||||
count = 1
|
||||
else:
|
||||
count = tokens[5]
|
||||
msg.add_data_field(tokens[3], type_obj, count)
|
||||
elif token_type == 'msgtype':
|
||||
master.add_message(tokens[1:])
|
||||
elif token_type == 'msgdata':
|
||||
msg = master.find_message(tokens[1])
|
||||
if not msg:
|
||||
raise ValueError('Unknown message type {}. {}:{}'.format(tokens[1], ln, line))
|
||||
type_obj, collapse = master.add_type(tokens[3], tokens[2])
|
||||
|
||||
# if this is an 'extension' field*, we want to add a new 'message' type
|
||||
# in the future, extensions will be handled as TLV's
|
||||
#
|
||||
# *(in the spec they're called 'optional', but that term is overloaded
|
||||
# in that internal wire messages have 'optional' fields that are treated
|
||||
# differently. for the sake of clarity here, for bolt-wire messages,
|
||||
# we'll refer to 'optional' message fields as 'extensions')
|
||||
#
|
||||
if bool(tokens[5:]): # is an extension field
|
||||
extension_name = "{}_{}".format(tokens[1], tokens[5])
|
||||
orig_msg = msg
|
||||
msg = master.find_message(extension_name)
|
||||
if not msg:
|
||||
msg = copy.deepcopy(orig_msg)
|
||||
msg.enumname = msg.name
|
||||
msg.name = extension_name
|
||||
master.add_extension_msg(msg.name, msg)
|
||||
|
||||
if collapse:
|
||||
count = 1
|
||||
else:
|
||||
count = tokens[4]
|
||||
msg.add_data_field(tokens[2], type_obj, count)
|
||||
elif token_type.startswith('#include'):
|
||||
master.add_include(token_type)
|
||||
else:
|
||||
raise ValueError('Unknown token type {} on line {}:{}'.format(token_type, ln, line))
|
||||
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
master.write(options, output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-s", "--expose-subtypes", help="print subtypes in header",
|
||||
action="store_true", default=False)
|
||||
parser.add_argument("-P", "--print_wire", help="generate wire printing source files",
|
||||
action="store_true", default=False)
|
||||
parser.add_argument("--page", choices=['header', 'impl'], help="page to print")
|
||||
parser.add_argument('header_filename', help='The filename of the header')
|
||||
parser.add_argument('enum_name', help='The name of the enum to produce')
|
||||
parser.add_argument("files", help='Files to read in (or stdin)', nargs=REMAINDER)
|
||||
parsed_args = parser.parse_args()
|
||||
|
||||
main(parsed_args, parsed_args.files)
|
Loading…
Add table
Reference in a new issue