Commit cd36209c authored by Per Cederqvist's avatar Per Cederqvist
Browse files

(defined_request_names): New variable.

(defined_request_names): New variable.
(defined_async_names): New variable.
(set_values): New variable.
(tt): New variable.
(tr): New variable.
(rt): New variable.
(rr): New variable.
(at): New variable.
(ar): New variable.
(has_suffix): New function.
(remove_suffix): New function.
(prot_a_type.__init__): Initialize __protover.
(prot_a_type.line_no): Renamed from line().
(prot_a_type.use): New argument: protover.  All callers
	and derived methods updated.
(prot_a_type.protover): New method.
(prot_a_builtin.use_recurse): Ditto.
(prot_a_simple.base_type): New method.
(prot_a_simple.array): New method.
(prot_a_alternate.type_a): New method.
(prot_a_alternate.type_b): New method.
(prot_a_struct.fields): New method.
(prot_a_bitstring.add_field): Store the bits in the order they are
	defined.
(prot_a_bitstring.bits): New method.
(prot_a_selection.fields): New method.
(prot_a_enumeration_of.base_type): New method.
(prot_a_msg): New class.
(prot_a_request): New class.
(prot_a_async): New class.
(menu): New class.
(reader.menu_re): New constant.
(reader.getc_eofok): If a menu line for a request or async message
	is found, store the information.
(reader.menu): New method.
(lexer.section_re): New method.
(lexer.__init__): __implemented_conftypes and
	__implemented_privbits both maps to None, not a random number.
(lexer.run): If __toplevel_at() returns a non-None value, return it.
(lexer.__toplevel_at): Return the result of the called "toplevel_"
	method instead of always returning None.
(lexer.toplevel_set): New method.
(lexer.toplevel_section): New method.
(lexer.toplevel_findex): Store information about the request in a
	prot_a_request instance.  Check the corresponding @section
	heading and @menu item.
(lexer.toplevel_amindex): Ditto for asyncs.
(lexer.__parse_async): New API.
(lexer.toplevel_bye): Generate stable names.  Return the error
	flag instead of calling sys.exit().
(lexer.__parse_request): New API.
(lexer.__parse_type): New argument: protover.
(lexer.__parse_request_arg): Ditto.  Return the parsed info.
(lexer.__bad_arg): Fixed minor typo.
(lexer.generate_stable_names): New method.
(generate_stable_output): New function.
parent 344dd202
......@@ -24,14 +24,46 @@
# Check @aarg{} and @rarg{} usage in Protocol-A.texi.
# ...and a lot of other stuff...
# ...and generate a stable machine-readable protocol specification.
import sys
import types
import string
import re
import os
# A mapping from type names (as strings) to prot_a_* objects.
defined_types = {}
# A mapping from request names (as strings) to prot_a_request objects.
defined_request_names = {}
# A mapping from request numbers (as ints) to prot_a_request objects.
defined_request_numbers = {}
# A mapping from async names (as strings) to prot_a_async objects.
defined_async_names = {}
# A mapping containing values set with @set. (@ifinfo et c
# are not understood; the latest @set found is used.)
set_values = {}
# Translation tables between the stable names and the names used by
# Protocol-A.texi. The first letter is the domain:
# t : types
# r : requests
# a : asyncs
#
# The second letter is the direction:
# t : table (from Protocol-A.texi to stable name)
# r : reverse (from stable name to current Protocol-A.texi alias)
tt = {}
tr = {}
rt = {}
rr = {}
at = {}
ar = {}
# All fields that have been seen so far. Initialized with some
# built-in fields.
defined_fields = {
......@@ -44,6 +76,8 @@ defined_fields = {
undefined_fields = {}
def number_suffixed(s, base):
"""Return true if S is BASE followed by a decimal number.
"""
if s[:len(base)] != base:
return 0
s = s[len(base):]
......@@ -55,6 +89,25 @@ def number_suffixed(s, base):
except:
return 0
def has_suffix(s, suffix):
"""Return true if S has the specified SUFFIX.
"""
if len(s) < len(suffix):
return 0
return s[len(s) - len(suffix):] == suffix
def remove_suffix(s, suffix):
"""Return S, but with SUFFIX removed.
Return S unchanged if it doesn't end with SUFFIX.
"""
if has_suffix(s, suffix):
return s[:len(s) - len(suffix)]
else:
return s
# We define our own isalpha et c to avoid dependencies on the current
# locale, and to be portable across old Python versions.
def isalpha(s):
for c in s:
if c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
......@@ -90,52 +143,104 @@ class reader_eof(Exception):
class prot_a_type:
"""Base class used for all Protocol A types.
"""
__usage_count = 0
def __init__(self, line):
"""Create a type.
LINE should be the line number in Protocol-A.texi where this
type was defined. It is used to create error messages.
"""
self.__line = line
self.__protover = None
def line(self):
def line_no(self):
"""Return the line number that defined this type.
"""
return self.__line
def use(self):
def use(self, protover):
"""Mark this type as used by something else.
This will increase the usage count (returned by usage())
and call use_recurse(), which in turn will call use() on all
types referenced by this type.
PROTOVER is the version of the request or async message that
uses this type. The type will remember the lowest PROTOVER;
this is the protocol version where the type was introduced.
"""
self.__usage_count = self.__usage_count + 1
if self.__protover == None or protover < self.__protover:
self.__protover = protover
if self.__usage_count == 1:
self.use_recurse()
self.use_recurse(protover)
def usage(self):
"""Return the usage count.
"""
return self.__usage_count
def protover(self):
"""Return the lowest protocol version that used this type.
"""
return self.__protover
class prot_a_builtin(prot_a_type):
def use_recurse(self):
"""Basic types defined by the protocol, such as INT32.
"""
def use_recurse(self, protover):
pass
class prot_a_simple(prot_a_type):
"""Simple alias types, such as Conf-No or ARRAY Text-Stat.
"""
def __init__(self, line, typ, is_array):
self.__type = typ
self.__array = is_array
prot_a_type.__init__(self, line)
def use_recurse(self):
defined_types[self.__type].use()
def use_recurse(self, protover):
defined_types[self.__type].use(protover)
def base_type(self):
return self.__type
def array(self):
return self.__array
class prot_a_alternate(prot_a_type):
"""A type that is an alternative of two other types.
The only example of this is currently Any-Conf-Stat.
"""
def __init__(self, line, type_a, type_b):
self.__type_a = type_a
self.__type_b = type_b
prot_a_type.__init__(self, line)
def use_recurse(self):
defined_types[self.__type_a].use()
defined_types[self.__type_b].use()
def use_recurse(self, protover):
defined_types[self.__type_a].use(protover)
defined_types[self.__type_b].use(protover)
def type_a(self):
return self.__type_a
def type_b(self):
return self.__type_b
class prot_a_struct(prot_a_type):
"""A structure, such as Conference.
"""
def __init__(self, line):
prot_a_type.__init__(self, line)
self.__fields = []
self.__used_names = {}
def add_field(self, field_name, type_name, is_array):
"""Add a field to the structure.
"""
if self.__used_names.has_key(field_name):
return "field name ``%s'' used twice" % field_name
......@@ -147,14 +252,21 @@ class prot_a_struct(prot_a_type):
del undefined_fields[field_name]
return None
def use_recurse(self):
def use_recurse(self, protover):
for (fn, tn, ar) in self.__fields:
defined_types[tn].use()
defined_types[tn].use(protover)
def fields(self):
return self.__fields
class prot_a_bitstring(prot_a_type):
"""A bitstring, such Aux-Item-Flags.
"""
def __init__(self, line):
prot_a_type.__init__(self, line)
self.__bits = {}
self.__ordered_bits = []
def add_field(self, field_name):
if self.__bits.has_key(field_name):
......@@ -163,6 +275,7 @@ class prot_a_bitstring(prot_a_type):
defined_fields[field_name] = None
if undefined_fields.has_key(field_name):
del undefined_fields[field_name]
self.__ordered_bits.append(field_name)
def check_implemented(self, name, implemented_fields):
res = []
......@@ -176,10 +289,15 @@ class prot_a_bitstring(prot_a_type):
res.append("%s: bit ``%s'' not implemented" % (name, f))
return res
def use_recurse(self):
def use_recurse(self, protover):
pass
def bits(self):
return self.__ordered_bits
class prot_a_selection(prot_a_type):
"""A selection, such as Misc-Info or Local-To-Global-Block.
"""
def __init__(self, line):
prot_a_type.__init__(self, line)
self.__fields = []
......@@ -211,19 +329,149 @@ class prot_a_selection(prot_a_type):
def all_names(self):
return self.__used_names.keys()
def use_recurse(self):
def use_recurse(self, protover):
for (number, name, tailname, tail_type, is_array) in self.__fields:
defined_types[tail_type].use()
defined_types[tail_type].use(protover)
def fields(self):
return self.__fields
class prot_a_enumration_of(prot_a_type):
class prot_a_enumeration_of(prot_a_type):
"""An ENUMERATION-OF, such as Info-Type.
"""
def __init__(self, line, base):
prot_a_type.__init__(self, line)
self.__base = base
def use_recurse(self):
defined_types[self.__base].use()
def use_recurse(self, protover):
defined_types[self.__base].use(protover)
def base_type(self):
return self.__base
class prot_a_msg:
"""Base class for requests and asynchronous messages.
"""
def __init__(self, line, reqnr, reqname, rec, exp, obs, protover, obsver):
"""Construct a request.
LINE: Line number that defines the message.
REQNR: The number of this request.
REQNAME: The name, as it appears in Protocol-A.texi.
REC: True if recommended.
EXP: True if experimental.
OBS: True if obsolete.
PROTOVER: Protocol version when it was introduced.
OBSVER: Protocol version when it became obsolete, or None.
"""
self.__line_no = line
self.__reqnr = reqnr
self.__reqname = reqname
self.__rec = rec
self.__exp = exp
self.__obs = obs
self.__protover = protover
self.__obsver = obsver
self.__args = []
self.__retval = None
def line_no(self):
return self.__line_no
def request_nr(self):
return self.__reqnr
def request_name(self):
return self.__reqname
def recommended(self):
return self.__rec
def experimental(self):
return self.__exp
def obsolete(self):
return self.__obs
def protover(self):
return self.__protover
def obsver(self):
return self.__obsver
def append_arg(self, request_arg):
self.__args.append(request_arg)
def arguments(self):
return self.__args
class prot_a_request(prot_a_msg):
"""Hold all information about a request.
"""
__retval = None
def set_return_type(self, ret_type, is_array):
# FIXME: cleanup possible? Use anonymous prot_a_simple?
self.__retval = (ret_type, is_array)
def return_type(self):
if self.__retval == None:
return None
return self.__retval[0]
def array(self):
return self.__retval[1]
class prot_a_async(prot_a_msg):
"""Hold all information about an async message.
"""
pass
class menu:
def __init__(self, line_no, name, nr, state):
self.__line_no = line_no
self.__name = name
self.__nr = int(nr)
self.__state = state
def line_no(self):
return self.__line_no
def name(self):
return self.__name
def nr(self):
return self.__nr
def state(self):
return self.__state
def same_state(self, req):
if req.recommended() and self.__state == "r":
return 1
if req.experimental() and self.__state == "e":
return 1
if req.obsolete() and self.__state == "O":
return 1
return 0
class reader:
"""Read a character at a time.
The reader checks that unqoted occurances of (), [] and {} are
properly matched, and gives an error message otherwise.
The reader also keeps track of the line number.
The reader recognizes the info menu lines for the request and
async menues, and saves them.
"""
parens = {'(': ')',
'[': ']',
'{': '}'}
......@@ -233,6 +481,10 @@ class reader:
del v
del k
menu_re = re.compile('\\* (?P<name>[-a-z0-9]*):: '
'*(?P<state>[Oer]) .*'
'\\((?P<nr>[0-9][0-9]*)\\)\n')
def __init__(self, file):
self.__filename = file
self.__file = open(file, "r")
......@@ -242,6 +494,7 @@ class reader:
self.__parenstack = []
self.__errfound = 0
self.__quoted = 0
self.__menu = {}
def filename(self):
return self.__filename
......@@ -274,6 +527,15 @@ class reader:
return None
self.__line = list(line)
self.__line_no = self.__line_no + 1
if len(line) == 80:
m = self.menu_re.match(line)
if m != None:
self.__menu[m.group("name")] = menu(
self.__line_no,
m.group("name"), m.group("nr"),
m.group("state"))
ret = self.__line[0]
del self.__line[0]
if not self.__quoted:
......@@ -309,7 +571,25 @@ class reader:
self.error(line, "unclosed ``%s''" % par)
self.__parenstack = []
def menu(self, name):
"""Return the menu line for the request or async named NAME.
"""
if self.__menu.has_key(name):
return self.__menu[name]
else:
return None
class lexer:
section_re = re.compile(
'(?P<req>[a-z][-a-z0-9]*) '
'\\[(?P<reqnr>[0-9][0-9]*)\\] '
'\\((?P<protover>[1-9][0-9]*)\\) '
'(?:(?P<recommended>Recommended)'
'|(?P<experimental>Experimental)'
'|(?P<obsolete>Obsolete(?: \\((?P<obsver>[0-9]*)\\))?))'
'$')
def __init__(self, file):
self.__reader = reader(file)
self.__findex = None
......@@ -326,10 +606,15 @@ class lexer:
self.__tindex_seen = {}
for t in self.__builtin_types:
defined_types[t] = prot_a_builtin('*builtin*')
# A mapping from request name to line number where it is defined.
self.__defined_requests = {}
# A mapping from async name to line number where it is defined.
self.__defined_asyncs = {}
# A mapping from request name to request number, extracted
# from the lyskomd source code.
self.__implemented_reqs = {}
f = open("requests-numbered.tmp", "r")
for line in f.readlines():
......@@ -337,6 +622,8 @@ class lexer:
self.__implemented_reqs[string.strip(name)] = nr
f.close()
# A mapping from async name to async number, extracted
# from the lyskomd source code.
self.__implemented_asyncs = {}
n = {}
f = open("asyncs-numbered.tmp", "r")
......@@ -352,6 +639,8 @@ class lexer:
self.__implemented_asyncs[name] = nr
f.close()
# A mapping from misc-item name to number, extracted
# from the lyskomd source code.
self.__implemented_miscs = {}
n = {}
f = open("miscs-numbered.tmp", "r")
......@@ -368,25 +657,31 @@ class lexer:
self.__implemented_miscs[name] = nr
f.close()
# A mapping from conftype bit name to None, extracted
# from the lyskomd source code.
self.__implemented_conftypes = {}
f = open("conftypes.tmp", "r")
for line in f.readlines():
name = string.strip(line)
self.__implemented_conftypes[name] = nr
self.__implemented_conftypes[name] = None
f.close()
# A mapping from privilege bit name to None, extracted
# from the lyskomd source code.
self.__implemented_privbits = {}
f = open("privbits.tmp", "r")
for line in f.readlines():
name = string.strip(line)
self.__implemented_privbits[name] = nr
self.__implemented_privbits[name] = None
f.close()
def run(self):
while 1:
c = self.__reader.getc()
if c == '@':
self.__toplevel_at()
ret = self.__toplevel_at()
if ret != None:
return ret
def __toplevel_at(self):
line_no = self.__reader.line_no()
......@@ -410,10 +705,10 @@ class lexer:
self.__reader.ungetc(c)
if hasattr(self, 'toplevel_' + cmd):
getattr(self, 'toplevel_' + cmd)(arg, line_no)
return getattr(self, 'toplevel_' + cmd)(arg, line_no)
else:
self.error(line_no, "unknown command @%s{}" % cmd)
return
return
else:
self.error(line_no, "bad command ``@%s%s''" % (cmd, c))
return
......@@ -432,7 +727,6 @@ class lexer:
toplevel_setfilename = ignore
toplevel_settitle = ignore
toplevel_setchapternewpage = ignore
toplevel_set = ignore
toplevel_macro = ignore
toplevel_end = ignore
toplevel_ifinfo = ignore
......@@ -462,7 +756,6 @@ class lexer:
toplevel_menu = ignore
toplevel_chapter = ignore
toplevel_penalty = ignore
toplevel_section = ignore
toplevel_table = ignore
toplevel_item = ignore
toplevel_req = ignore
......@@ -533,6 +826,14 @@ class lexer:
toplevel_file = pushback
toplevel_cindex = pushback
toplevel_cite = pushback
def toplevel_set(self, arg, line_no):
[k, v] = string.split(arg, None, 1)
set_values[k] = v
def toplevel_section(self, arg, line_no):
self.__section_name = arg
self.__section_start = line_no
def toplevel_node(self, arg, line_no):
if self.__findex != None:
......@@ -564,7 +865,31 @@ class lexer:
self.error(line_no, "multiple @findex in single @node")
return
self.__findex = arg
m = self.section_re.match(self.__section_name)
if m == None:
self.error(self.__section_start, "bad section heading for request")
return
if m.group("req") != arg:
self.error(line_no, "@section/@findex mismatch: %s..." % arg)
self.error(self.__section_start, "...inside section %s" %
m.group("req"))
return
reqnr = int(m.group("reqnr"))
protover = int(m.group("protover"))
if m.group("obsolete") != None and m.group("obsver") == None:
self.error(self.__section_start, "when was this obsoleted?")
rec = m.group("recommended") != None
exp = m.group("experimental") != None
obs = m.group("obsolete") != None
# self.__args holds a mapping from the argument name
# to a list of two elements:
# 0: the line number where it is defined
# 1: a usage count
self.__args = {}
if self.__defined_requests.has_key(arg):
self.error(line_no, "request ``%s'' redefined" % arg)
self.error(self.__defined_requests[arg], "previous definition")
......@@ -572,7 +897,21 @@ class lexer:
if self.__get_token() != '@example':
self.error(self.__reader.line_no(), "missing @example")
return
self.__parse_request()
req = prot_a_request(line_no, reqnr, arg, rec, exp, obs, protover,
m.group("obsver"))
self.__parse_request(req)
defined_request_names[arg] = req
defined_request_numbers[reqnr] = req
menu = self.__reader.menu(req.request_name())
if menu == None:
self.error(line_no, "no @menu item found")
else:
if not menu.same_state(req):
self.error(menu.line_no(), "state clash for %s" % menu.name())
self.error(line_no, "...clashing")
if menu.nr() != req.request_nr():
self.error(menu.line_no(), "number clash for %s" % menu.name())
self.error(line_no, "...clashing")
def toplevel_rarg(self, arg, line_no):
if self.__findex == None:
......@@ -614,9 +953,43 @@ class lexer:
if token != '@example':
self.error(self.__reader.line_no(), "missing @example")
return
self.__parse_async(obsolete)
def __parse_async(self, obsolete):
m = self.section_re.match(self.__section_name)
if m == None:
self.error(self.__section_start, "bad section heading for message")
return
if m.group("req") != arg:
self.error(line_no, "@section/@amindex mismatch: %s..." % arg)