diff --git a/doc/checkargs.py b/doc/checkargs.py new file mode 100644 index 0000000000000000000000000000000000000000..a2d701806ee8a1be1daaff8862392b0f9e0f1b1b --- /dev/null +++ b/doc/checkargs.py @@ -0,0 +1,445 @@ +# Check @aarg{} and @rarg{} usage in Protocol-A.texi. + +import sys +import types + +class reader_eof(Exception): + pass + +class reader: + def __init__(self, file): + self.__filename = file + self.__file = open(file, "r") + self.__line_no = 0 + self.__line = [] + self.__eof = 0 + + def filename(self): + return self.__filename + + def ungetc(self, c): + self.__line.insert(0, c) + + def getc_eofok(self): + if self.__line == []: + if self.__eof: + return None + line = self.__file.readline() + if line == '': + self.__eof = 1 + return None + self.__line = list(line) + self.__line_no += 1 + ret = self.__line[0] + del self.__line[0] + return ret + + def getc(self): + c = self.getc_eofok() + if c == None: + raise reader_eof + return c + + def line_no(self): + return self.__line_no + +class lexer: + def __init__(self, file): + self.__reader = reader(file) + self.__errfound = 0 + self.__findex = None + + def run(self): + while 1: + c = self.__reader.getc_eofok() + if c == None: + return self.__errfound + if c == '@': + self.__toplevel_at() + + def __toplevel_at(self): + line_no = self.__reader.line_no() + c = self.__reader.getc() + if c in '{}@-"*': + return + cmd = "" + while 1: + if c.isalpha() or c in '_"': + cmd = cmd + c + c = self.__reader.getc() + elif c in ' \t\n{@=-': + assert cmd != '' + if c == '{': + arg = self.__read_arg() + elif c == ' ' and cmd not in ['tab']: + arg = self.__read_line() + else: + arg = None + if c == '@': + self.__reader.ungetc(c) + + if hasattr(self, 'toplevel_' + cmd): + getattr(self, 'toplevel_' + cmd)(arg, line_no) + else: + self.error(line_no, "unknown command @%s{}" % cmd) + return + else: + self.error(line_no, "bad command ``@%s%s''" % (cmd, c)) + return + + def __read_line(self): + line = "" + while 1: + c = self.__reader.getc() + if c == '\n': + return line + line = line + c + + def ignore(self, arg, line_no): + pass + + toplevel_setfilename = ignore + toplevel_settitle = ignore + toplevel_setchapternewpage = ignore + toplevel_set = ignore + toplevel_macro = ignore + toplevel_code = ignore + toplevel_end = ignore + toplevel_ifinfo = ignore + toplevel_value = ignore + toplevel_copyright = ignore + toplevel_iftex = ignore + toplevel_parindent = ignore + toplevel_ifinfo = ignore + toplevel_begin = ignore + toplevel_titlepage = ignore + toplevel_title = ignore + toplevel_subtitle = ignore + toplevel_author = ignore + toplevel_font = ignore + toplevel_ignore = ignore + toplevel_tensltt = ignore + toplevel_page = ignore + toplevel_vskip = ignore + toplevel_ifnothtml = ignore + toplevel_contents = ignore + toplevel_dircategory = ignore + toplevel_direntry = ignore + toplevel_ifhtml = ignore + toplevel_html = ignore + toplevel_ifnottex = ignore + toplevel_top = ignore + toplevel_uref = ignore + toplevel_menu = ignore + toplevel_chapter = ignore + toplevel_uref = ignore + toplevel_footnote = ignore + toplevel_email = ignore + toplevel_penalty = ignore + toplevel_section = ignore + toplevel_table = ignore + toplevel_asis = ignore + toplevel_item = ignore + toplevel_req = ignore + toplevel_subsection = ignore + toplevel_itemize = ignore + toplevel_bullet = ignore + toplevel_multitable = ignore + toplevel_tab = ignore + toplevel_aux = ignore + toplevel_samp = ignore + toplevel_pxref = ignore + toplevel_errorcode = ignore + toplevel_misc = ignore + toplevel_type = ignore + toplevel_field = ignore + toplevel_dfn = ignore + toplevel_conftype = ignore + toplevel_command = ignore + toplevel_ref = ignore + toplevel_var = ignore + toplevel_subsubsection = ignore + toplevel_priv = ignore + toplevel_emph = ignore + toplevel_enumerate = ignore + toplevel_tindex = ignore # FIXME: check 'em? + toplevel_xref = ignore + toplevel_itemx = ignore + toplevel_ae = ignore + toplevel_i = ignore + toplevel_subheading = ignore + toplevel_c = ignore + toplevel_t = ignore + toplevel_aa = ignore + toplevel_async = ignore + toplevel_unnumbered = ignore + toplevel_printindex = ignore + toplevel_example = ignore + + def toplevel_node(self, arg, line_no): + if self.__findex != None: + self.__findex = None + for (argname, [lineno, usage]) in self.__args.items(): + if usage == 0: + self.error(lineno, + "Undocumented argument ``%s''" % (argname, )) + self.__findex = None + self.__node_name = arg + self.__node_start = line_no + + def toplevel_findex(self, arg, line_no): + if self.__node_name != arg: + self.error(line_no, "@node/@findex mismatch: %s..." % arg) + self.error(line_no, "...inside node %s" % self.__node_name) + return + if self.__findex != None: + self.error(line_no, "multiple @findex in single @node") + return + self.__findex = arg + self.__args = {} + self.__tokens = [] + if self.__get_token() != '@example': + self.error(self.__reader.line_no(), "missing @example") + return + self.__parse_request() + + def toplevel_rarg(self, arg, line_no): + if self.__findex == None: + self.error(line_no, "@rarg outside @findex node") + return + if not self.__args.has_key(arg): + self.error(line_no, "undefined argument ``%s''" % (arg, )) + return + self.__args[arg][1] += 1 + + def toplevel_aarg(self, arg, line_no): + pass + # FIXME + + def toplevel_bye(self, arg, line_no): + if self.__findex != None: + self.error(self.__reader.line_no(), "unterminated @findex node") + + def __parse_request(self): + self.__tokens = [] + req = self.__get_token() + if req != self.__findex: + self.error(self.__reader.line_no(), + "wrong request name ``%s''" % req) + return + + if self.__get_token() != '[': + self.error(self.__reader.line_no(), "missing ``[''") + return + + nr = self.__get_token() + if type(nr) != types.IntType: + self.error(self.__reader.line_no(), "bad request number") + + if self.__get_token() != ']': + self.error(self.__reader.line_no(), "missing ``]''") + return + + paren = self.__get_token() + if paren == '(': + next = self.__get_token() + if next != ')': + self.__unget_token(next) + self.__parse_request_arg() + next = self.__get_token() + if next != ')': + self.error(self.__reader.line_no(), + "missing close parenthesis after arguments") + return + elif paren == '((': + self.__parse_request_arg() + next = self.__get_token() + while next == ';': + self.__parse_request_arg() + next = self.__get_token() + if next != '))': + self.error(self.__reader.line_no(), + "missing double close parenthesis after arguments") + return + else: + self.error(self.__reader.line_no(), + "missing argument list") + return + + if self.__get_token() != '->': + self.error(self.__reader.line_no(), "missing ``->''") + return + + if self.__get_token() != '(': + self.error(self.__reader.line_no(), "missing ``('' for result") + return + + next = self.__get_token() + if next != ')': + self.__unget_token(next) + ret_type = self.__parse_type() + next = self.__get_token() + if next != ')': + self.error(self.__reader.line_no(), "missing ``)'' for result") + return + + if self.__get_token() != ';': + self.error(self.__reader.line_no(), "missing final ``;''") + return + + if self.__get_token() != '@end': + self.error(self.__reader.line_no(), "extra garbage found") + return + + return + + def __parse_type(self): + token = self.__get_token() + if token == 'ARRAY': + tp = self.__get_token() + if self.__bad_type(tp): + return None + return token + " " + tp + else: + if self.__bad_type(token): + return None + return token + + def __bad_type(self, tp): + if tp in ['INT8', 'INT16', 'INT32']: + return 0 + ok = 1 + if len(tp) < 0: + ok = 0 + if ok and not tp[0].isupper(): + ok = 0 + for c in tp[1:]: + if not c.isalpha() and c != '-': + ok = 0 + if ok and tp[-1] == '-': + ok = 0 + if not ok: + self.error(self.__reader.line_no(), "bad type name ``%s''" % (tp,)) + return not ok + + def __bad_arg(self, arg): + ok = 1 + if len(arg) < 0: + ok = 0 + if ok and not arg[0].islower(): + ok = 0 + for c in arg[1:]: + if not c.islower() and c != '-': + ok = 0 + if ok and arg[-1] == '-': + ok = 0 + if not ok: + if arg == '))': + self.error(self.__reader.line_no(), + "extra semicolon after last arg") + else: + self.error(self.__reader.line_no(), + "bad argument ``%s''" % (arg,)) + return not ok + + def __parse_request_arg(self): + argname = self.__get_token() + if self.__bad_arg(argname): + return + if self.__get_token() != ':': + self.error(self.__reader.line_no(), "missing ``:'' after argument") + return + tp = self.__parse_type() + if tp == None: + return + if self.__args.has_key(argname): + self.error(self.__reader.line_no(), + "argument name ``%s'' used twice" % (argname, )) + return + self.__args[argname] = [self.__reader.line_no(), 0] + + def __unget_token(self, token): + self.__tokens.insert(0, token) + + def __get_token(self): + if len(self.__tokens) > 0: + res = self.__tokens[0] + del self.__tokens[0] + return res + c = self.__reader.getc() + while c.isspace(): + c = self.__reader.getc() + if c.isalpha(): + res = c + while 1: + c = self.__reader.getc() + if not c.isalpha() and c not in "-" and not c.isdigit(): + if c.isdigit(): + self.error(self.__reader.line_no(), + "bad token ``%s''" % (res + c)) + self.__reader.ungetc(c) + return res + res = res + c + elif c in '[];:': + return c + elif c in '()': + d = self.__reader.getc() + if c != d: + self.__reader.ungetc(d) + return c + else: + return c + d + elif c.isdigit(): + res = c + while 1: + c = self.__reader.getc() + if not c.isdigit(): + if c.isalpha(): + self.error(self.__reader.line_no(), + "bad token ``%s''" % (res + c)) + self.__reader.ungetc(c) + if res[0] == '0' and len(res) > 1: + self.error(self.__reader.line_no(), + "bad number ``%s''" % (res,)) + return int(res) + res = res + c + elif c == '-': + d = self.__reader.getc() + if d == '>': + return '->' + else: + self.error(self.__reader.line_no(), + "bad token ``%s%s''" % (c, d)) + self.__reader.ungetc(d) + return c + elif c == '@': + res = c + while 1: + c = self.__reader.getc() + if not c.isalpha(): + self.__reader.ungetc(c) + return res + res = res + c + else: + self.error(self.__reader.line_no(), + "bad character ``%s''" % (c,)) + return c + + + def __read_arg(self): + arg = "" + while 1: + c = self.__reader.getc() + if c == '}': + return arg + else: + arg = arg + c + + def error(self, line_no, errmsg): + sys.stderr.write("%s:%d:%s\n" % (self.__reader.filename(), + line_no, errmsg)) + self.__errfound = 1 + +if __name__ == '__main__': + l = lexer(sys.argv[1]) + sys.exit(l.run())