Path: blob/develop/src/sage_setup/autogen/flint/reader.py
4086 views
r"""1Extraction of function, macros, types from flint documentation.2"""34#*****************************************************************************5# Copyright (C) 2023 Vincent Delecroix <[email protected]>6#7# This program is free software: you can redistribute it and/or modify8# it under the terms of the GNU General Public License as published by9# the Free Software Foundation, either version 2 of the License, or10# (at your option) any later version.11# http://www.gnu.org/licenses/12#*****************************************************************************1314import os15from .env import FLINT_INCLUDE_DIR, FLINT_DOC_DIR161718class Extractor:19r"""20Tool to extract function declarations from a flint .rst file21"""22NONE = 023DOC = 124FUNCTION_DECLARATION = 225MACRO_DECLARATION = 426TYPE_DECLARATION = 82728def __init__(self, filename):29self.filename = filename30if not filename.endswith('.rst'):31raise ValueError3233# Attributes that are modified throughout the document parsing34self.state = self.NONE # position in the documentation35self.section = None # current section36self.content = {} # section -> list of pairs (function signatures, func documentation)37self.functions = [] # current list of pairs (function signatures, func documentation)38self.signatures = [] # current list of function/macro/type signatures39self.doc = [] # current function documentation4041with open(filename) as f:42text = f.read()43self.lines = text.splitlines()44self.i = 04546def run(self):47while self.process_line():48pass49if self.state & self.FUNCTION_DECLARATION:50self.add_function()51if self.state & self.MACRO_DECLARATION:52self.add_macro()53if self.functions:54self.update_section()55self.state = self.NONE5657def update_section(self):58if self.section not in self.content:59self.content[self.section] = []60self.content[self.section] += tuple(self.functions)61self.functions.clear()6263def clean_doc(self):64# Remove empty lines at the end of documentation65while self.doc and not self.doc[-1]:66self.doc.pop()6768for i, line in enumerate(self.doc):69# To make sage linter happier70line = line.replace('\\choose ', 'choose ')71self.doc[i] = line7273@staticmethod74def has_boolean_return_type(func_signature):75r"""76Determine whether the function func_signature has a boolean return type.7778If so, it will be declared in Cython as `bint` rather than `int`.79"""80if func_signature.count('(') != 1 or func_signature.count(')') != 1:81return False8283j = func_signature.index('(')84func_name = func_signature[:j].strip().split()85if len(func_name) != 2:86return False8788return_type = func_name[0]89if return_type != 'int':90return False9192func_name = func_name[1]9394return func_name.startswith('is_') or \95'_is_' in func_name or \96func_name.endswith('_eq') or \97func_name.endswith('_ne') or \98func_name.endswith('_lt') or \99func_name.endswith('_le') or \100func_name.endswith('_gt') or \101func_name.endswith('_ge') or \102'_contains_' in func_name or \103func_name.endswith('_contains') or \104'_equal_' in func_name or \105func_name.endswith('_equal') or \106func_name.endswith('_overlaps')107108def clean_signatures(self):109if (self.state & self.FUNCTION_DECLARATION) or (self.state & self.MACRO_DECLARATION):110for i, func_signature in enumerate(self.signatures):111replacement = [('(void)', '()'), (' enum ', ' ')]112for bad_type, good_type in replacement:113func_signature = func_signature.replace(bad_type, good_type)114115bad_arg_names = [('in', 'input'), ('lambda', 'lmbda'), ('iter', 'it'), ('is', 'iis')]116replacements = [(pattern.format(bad), pattern.format(good)) for pattern in [' {},', ' {})', '*{},', '*{})'] for bad, good in bad_arg_names]117for bad_form, good_form in replacements:118func_signature = func_signature.replace(bad_form, good_form)119120if self.has_boolean_return_type(func_signature):121func_signature = func_signature.strip()122if not func_signature.startswith('int '):123raise RuntimeError124func_signature = 'b' + func_signature125126self.signatures[i] = func_signature127128def add_declaration(self):129if self.state & self.FUNCTION_DECLARATION:130self.add_function()131elif self.state & self.MACRO_DECLARATION:132self.add_macro()133elif self.state & self.TYPE_DECLARATION:134self.add_type()135136self.signatures.clear()137self.doc.clear()138self.state = self.NONE139140def add_function(self):141self.clean_doc()142143# Drop va_list argument144signatures = []145for func_signature in self.signatures:146if '(' not in func_signature or ')' not in func_signature:147raise RuntimeError(func_signature)148elif 'va_list ' in func_signature:149print('Warning: va_list unsupported {}'.format(func_signature))150else:151signatures.append(func_signature)152self.signatures = signatures153self.clean_signatures()154155self.functions.append((tuple(self.signatures), tuple(self.doc)))156157def add_macro(self):158# TODO: we might want to support auto-generation of macros159return160161def add_type(self):162# TODO: we might want to support auto-generation of types163return164165def process_line(self):166r"""167Process one line of documentation.168"""169if self.i >= len(self.lines):170return 0171172if bool(self.state & self.FUNCTION_DECLARATION) + bool(self.state & self.MACRO_DECLARATION) + bool(self.state & self.TYPE_DECLARATION) > 1:173raise RuntimeError('self.state = {} and i = {}'.format(self.state, self.i))174175line = self.lines[self.i]176if line.startswith('.. function::'):177self.add_declaration()178line_rest = line.removeprefix('.. function::')179if not line_rest.startswith(' '):180print('Warning: no space {}'.format(line))181self.state = self.FUNCTION_DECLARATION182self.i += 1183signature = line_rest.strip()184while signature.endswith('\\'):185signature = signature.removesuffix('\\').strip() + ' ' + self.lines[self.i].strip()186self.i += 1187self.signatures.append(signature)188elif line.startswith('.. macro::'):189self.add_declaration()190if line[10] != ' ':191print('Warning no space{}'.format(line))192self.signatures.append(line[10:].strip())193self.state = self.MACRO_DECLARATION194self.i += 1195elif line.startswith('.. type::'):196# type197# NOTE: we do nothing as the documentation duplicates type declaration198# and lacks the actual list of attributes199self.add_declaration()200self.state = self.TYPE_DECLARATION201self.i += 1202elif self.state == self.FUNCTION_DECLARATION:203if len(line) > 14 and line.startswith(' ' * 14):204# function with similar declaration205line = line[14:].strip()206if line:207self.signatures.append(line)208self.i += 1209elif not line.strip():210# leaving function declaration211self.state |= self.DOC212self.i += 1213else:214raise ValueError(line)215elif self.state == self.MACRO_DECLARATION:216if len(line) > 10 and line.startswith(' ' * 10):217# macro with similar declaration218line = line[10:].strip()219if line:220self.signatures.append(line)221self.i += 1222elif not line.strip():223# leaving macro declaration224self.state |= self.DOC225self.i += 1226else:227raise ValueError(line)228elif (self.state & self.DOC) and line.startswith(' '):229# function doc230line = line.strip()231if line:232self.doc.append(line)233self.i += 1234elif self.i + 1 < len(self.lines) and self.lines[self.i + 1].startswith('----'):235# new section236self.add_declaration()237if self.functions:238self.update_section()239section = line240self.i += 2241elif not line:242self.i += 1243else:244self.add_declaration()245self.i += 1246247return 1248249250def extract_functions(filename):251r"""252OUTPUT:253254dictionary: section -> list of pairs (func_sig, doc)255"""256e = Extractor(filename)257e.run()258return e.content259260261