2023-11-18 02:45:28 +01:00
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import hashlib
|
|
|
|
|
import json
|
|
|
|
|
|
2023-11-18 08:15:13 +01:00
|
|
|
import colors as C
|
2024-08-11 03:45:00 +02:00
|
|
|
import toolchain_info
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
class Block(object):
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_blocks_from_rst(rst_file, input_text):
|
2025-03-22 01:14:06 +01:00
|
|
|
lang_re = re.compile(r"\s*.. code::\s*(\w+)?\s*")
|
|
|
|
|
project_re = re.compile(r"\s*.. code::.*project=(\S+)?")
|
|
|
|
|
main_re = re.compile(r"\s*.. code::.*main=(\S+)?")
|
|
|
|
|
manual_chop_re = re.compile(r"\s*.. code::.*manual_chop?")
|
|
|
|
|
button_re = re.compile(r"\s+(\S+)_button")
|
|
|
|
|
code_config_re = re.compile(r":code-config:`(.*)?`")
|
|
|
|
|
classes_re = re.compile(r"\s*:class:\s*(.+)")
|
|
|
|
|
switches_re = re.compile(r"\s*.. code::.*switches=(\S+)?")
|
|
|
|
|
compiler_switches_re = re.compile(r"Compiler[(](\S+)?[)]")
|
|
|
|
|
gnat_version_re=re.compile(r"\s*.. code::.*gnat=(\S+)?")
|
|
|
|
|
gnatprove_version_re=re.compile(r"\s*.. code::.*gnatprove=(\S+)?")
|
|
|
|
|
gprbuild_version_re=re.compile(r"\s*.. code::.*gprbuild=(\S+)?")
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
blocks = []
|
|
|
|
|
lines = input_text.splitlines()
|
|
|
|
|
|
|
|
|
|
def first_nonws(line):
|
|
|
|
|
for i, c in enumerate(line):
|
|
|
|
|
if not c.isspace():
|
|
|
|
|
return i
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
indents = map(first_nonws, lines)
|
|
|
|
|
|
|
|
|
|
classes = []
|
|
|
|
|
compiler_switches = []
|
|
|
|
|
buttons = []
|
|
|
|
|
cb_start = -1
|
|
|
|
|
cb_indent = -1
|
|
|
|
|
lang = ""
|
|
|
|
|
project = None
|
|
|
|
|
main_file = None
|
|
|
|
|
manual_chop = None
|
2024-08-11 14:53:51 +02:00
|
|
|
gnat_version = None
|
|
|
|
|
gnatprove_version = None
|
|
|
|
|
gprbuild_version = None
|
2023-11-18 02:45:28 +01:00
|
|
|
last_line_number = -1
|
|
|
|
|
|
|
|
|
|
def is_empty(line):
|
|
|
|
|
return (not line) or line.isspace()
|
|
|
|
|
|
2024-08-11 14:54:44 +02:00
|
|
|
def reset_block_info():
|
|
|
|
|
# Reset information for next block
|
|
|
|
|
nonlocal classes, cb_start, cb_indent, lang
|
|
|
|
|
|
|
|
|
|
classes, cb_start, cb_indent, lang = [], -1, -1, ""
|
|
|
|
|
|
2023-11-18 02:45:28 +01:00
|
|
|
def process_block(i, line, indent):
|
|
|
|
|
nonlocal classes, cb_start, cb_indent, lang
|
|
|
|
|
|
|
|
|
|
if cb_indent == -1 and not is_empty(line):
|
|
|
|
|
cb_indent = indent
|
|
|
|
|
|
|
|
|
|
if indent < cb_indent and not is_empty(line):
|
|
|
|
|
text = ("\n".join(l[cb_indent:] for l in lines[cb_start:i]))
|
|
|
|
|
text = text[1:] # Remove first newline
|
|
|
|
|
|
|
|
|
|
blocks.append(CodeBlock(
|
|
|
|
|
rst_file,
|
|
|
|
|
cb_start,
|
|
|
|
|
i,
|
|
|
|
|
text,
|
|
|
|
|
lang,
|
|
|
|
|
project,
|
|
|
|
|
main_file,
|
2024-08-04 23:24:28 +02:00
|
|
|
gnat_version,
|
|
|
|
|
gnatprove_version,
|
|
|
|
|
gprbuild_version,
|
2023-11-18 02:45:28 +01:00
|
|
|
compiler_switches,
|
|
|
|
|
classes,
|
|
|
|
|
manual_chop,
|
|
|
|
|
buttons
|
|
|
|
|
))
|
|
|
|
|
|
2024-08-11 14:54:44 +02:00
|
|
|
reset_block_info()
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
m = classes_re.match(line)
|
|
|
|
|
|
|
|
|
|
if m:
|
|
|
|
|
classes = [str.strip(l) for l in m.groups()[0].split(",")]
|
|
|
|
|
cb_start = i + 1
|
|
|
|
|
|
|
|
|
|
def start_code_block(i, line, indent):
|
|
|
|
|
nonlocal cb_start, lang, project, main_file, manual_chop, \
|
2024-08-04 23:24:28 +02:00
|
|
|
buttons, compiler_switches, \
|
|
|
|
|
gnat_version, gnatprove_version, gprbuild_version
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
cb_start, lang = (
|
|
|
|
|
i + 1,
|
|
|
|
|
lang_re.match(line).groups()[0]
|
|
|
|
|
)
|
|
|
|
|
project = project_re.match(line)
|
|
|
|
|
if project is not None:
|
|
|
|
|
project = project.groups()[0]
|
|
|
|
|
|
|
|
|
|
main_file = main_re.match(line)
|
|
|
|
|
if main_file is not None:
|
|
|
|
|
# Retrieve actual main filename
|
|
|
|
|
main_file = main_file.groups()[0]
|
|
|
|
|
if lang == "c":
|
|
|
|
|
manual_chop = True
|
|
|
|
|
else:
|
|
|
|
|
manual_chop = (manual_chop_re.match(line) is not None)
|
|
|
|
|
buttons = button_re.findall(line)
|
|
|
|
|
|
2024-08-04 23:24:28 +02:00
|
|
|
project_gnat_version = gnat_version_re.match(line)
|
|
|
|
|
project_gnatprove_version = gnatprove_version_re.match(line)
|
|
|
|
|
project_gprbuild_version = gprbuild_version_re.match(line)
|
|
|
|
|
|
|
|
|
|
if project_gnat_version is not None:
|
2024-08-11 03:45:00 +02:00
|
|
|
gnat_version = ["selected", project_gnat_version.groups()[0]]
|
2024-08-11 14:53:51 +02:00
|
|
|
else:
|
|
|
|
|
gnat_version = ["default", toolchain_info.get_toolchain_default_version('gnat')]
|
2024-08-04 23:24:28 +02:00
|
|
|
if project_gnatprove_version is not None:
|
2024-08-11 03:45:00 +02:00
|
|
|
gnatprove_version = ["selected", project_gnatprove_version.groups()[0]]
|
2024-08-11 14:53:51 +02:00
|
|
|
else:
|
|
|
|
|
gnatprove_version = ["default", toolchain_info.get_toolchain_default_version('gnatprove')]
|
2024-08-04 23:24:28 +02:00
|
|
|
if project_gprbuild_version is not None:
|
2024-08-11 03:45:00 +02:00
|
|
|
gprbuild_version = ["selected", project_gprbuild_version.groups()[0]]
|
2024-08-11 14:53:51 +02:00
|
|
|
else:
|
|
|
|
|
gprbuild_version = ["default", toolchain_info.get_toolchain_default_version('gprbuild')]
|
2024-08-04 23:24:28 +02:00
|
|
|
|
2023-11-18 02:45:28 +01:00
|
|
|
all_switches = switches_re.match(line)
|
|
|
|
|
|
|
|
|
|
compiler_switches = []
|
|
|
|
|
if all_switches is not None:
|
|
|
|
|
all_switches = all_switches.groups()[0]
|
|
|
|
|
compiler_switches = compiler_switches_re.match(all_switches)
|
|
|
|
|
if compiler_switches is not None:
|
|
|
|
|
compiler_switches = [str.strip(l)
|
|
|
|
|
for l in compiler_switches.groups()[0].split(",")]
|
|
|
|
|
|
|
|
|
|
# Add default switches
|
|
|
|
|
default_switches = {
|
|
|
|
|
"Builder": [],
|
|
|
|
|
"Compiler": [
|
|
|
|
|
"-gnata",
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for category in default_switches:
|
|
|
|
|
for sw in default_switches[category]:
|
|
|
|
|
if sw not in compiler_switches:
|
|
|
|
|
compiler_switches.append(sw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_config_block(i, line, indent):
|
|
|
|
|
blocks.append(ConfigBlock(rst_file,
|
|
|
|
|
**dict(
|
|
|
|
|
kv.split('=')
|
|
|
|
|
for kv in code_config_re.findall(line)[0].split(";"))
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
2024-08-11 14:54:44 +02:00
|
|
|
reset_block_info()
|
|
|
|
|
|
2023-11-18 02:45:28 +01:00
|
|
|
for i, (line, indent) in enumerate(zip(lines, indents)):
|
|
|
|
|
last_line_number = i
|
|
|
|
|
|
|
|
|
|
if cb_start != -1:
|
|
|
|
|
process_block(i, line, indent)
|
|
|
|
|
else:
|
|
|
|
|
if line[indent:].startswith(".. code::"):
|
|
|
|
|
start_code_block(i, line, indent)
|
|
|
|
|
elif line[indent:].startswith(":code-config:"):
|
|
|
|
|
start_config_block(i, line, indent)
|
|
|
|
|
|
|
|
|
|
if cb_start != -1:
|
|
|
|
|
print("{}: code block (start: {}, project: {}) doesn't have explanatory section!".format(
|
|
|
|
|
C.col("WARNING", C.Colors.YELLOW), cb_start, project))
|
|
|
|
|
process_block(last_line_number + 1, "END", 0)
|
|
|
|
|
|
|
|
|
|
# Error: unable to process last code block
|
|
|
|
|
if cb_start != -1:
|
|
|
|
|
print("{}: code block (start: {}, project: {}) hasn't been successfully processed!".format(
|
|
|
|
|
C.col("ERROR", C.Colors.RED), cb_start, project))
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
return blocks
|
|
|
|
|
|
|
|
|
|
def to_json_file(self, json_filename=None):
|
|
|
|
|
block_info = vars(self)
|
|
|
|
|
|
|
|
|
|
if json_filename is None:
|
|
|
|
|
json_filename = "block_info.json"
|
|
|
|
|
with open(json_filename, u'w') as f:
|
|
|
|
|
json.dump(block_info, f, indent=4)
|
|
|
|
|
|
|
|
|
|
class CodeBlock(Block):
|
|
|
|
|
@staticmethod
|
|
|
|
|
def from_json_file(json_filename=None):
|
|
|
|
|
|
|
|
|
|
if json_filename is None:
|
|
|
|
|
json_filename = "block_info.json"
|
|
|
|
|
|
|
|
|
|
if os.path.isfile(json_filename):
|
|
|
|
|
with open(json_filename, u'r') as f:
|
|
|
|
|
block_info_json = json.load(f)
|
|
|
|
|
return CodeBlock(**block_info_json)
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def __init__(self, rst_file, line_start, line_end, text, language, project,
|
2024-08-04 23:24:28 +02:00
|
|
|
main_file,
|
|
|
|
|
gnat_version,gnatprove_version,gprbuild_version,
|
|
|
|
|
compiler_switches, classes, manual_chop, buttons,
|
2023-11-24 20:14:52 +01:00
|
|
|
active=None,no_check=None,syntax_only=None,run_it=None,
|
2023-11-18 02:45:28 +01:00
|
|
|
compile_it=None,prove_it=None,
|
|
|
|
|
source_files=None,
|
|
|
|
|
project_filename=None,spark_project_filename=None,
|
|
|
|
|
project_main_file=None,
|
|
|
|
|
text_hash=None,text_hash_short=None):
|
|
|
|
|
self.rst_file = rst_file
|
|
|
|
|
self.line_start = line_start
|
|
|
|
|
self.line_end = line_end
|
|
|
|
|
self.text = text
|
|
|
|
|
self.language = language
|
|
|
|
|
self.project = project
|
|
|
|
|
self.main_file = main_file
|
2024-08-04 23:24:28 +02:00
|
|
|
self.gnat_version = gnat_version
|
|
|
|
|
self.gnatprove_version = gnatprove_version
|
|
|
|
|
self.gprbuild_version = gprbuild_version
|
2023-11-18 02:45:28 +01:00
|
|
|
self.compiler_switches = compiler_switches
|
|
|
|
|
self.classes = classes
|
|
|
|
|
self.manual_chop = manual_chop
|
|
|
|
|
self.buttons = buttons
|
2023-11-24 20:14:52 +01:00
|
|
|
self.active = active if active is not None else True
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
self.no_check = no_check if no_check is not None else \
|
|
|
|
|
any(sphinx_class in ["ada-nocheck", "c-nocheck"]
|
|
|
|
|
for sphinx_class in self.classes)
|
|
|
|
|
|
|
|
|
|
self.syntax_only = syntax_only if syntax_only is not None else \
|
2023-11-24 20:14:52 +01:00
|
|
|
'ada-syntax-only' in self.classes
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
self.run_it = run_it if run_it is not None else \
|
|
|
|
|
(('ada-run' in self.classes
|
|
|
|
|
or 'ada-run-expect-failure' in self.classes
|
|
|
|
|
or 'run' in self.buttons)
|
|
|
|
|
and not 'ada-norun' in self.classes)
|
|
|
|
|
self.compile_it = compile_it if compile_it is not None else \
|
2024-08-09 17:49:29 +02:00
|
|
|
self.run_it or \
|
2024-08-17 20:29:23 +02:00
|
|
|
(('ada-compile' in self.classes and self.language == 'ada')
|
|
|
|
|
or ('c-compile' in self.classes and self.language == 'c')
|
2024-08-09 17:49:29 +02:00
|
|
|
or 'compile' in self.buttons)
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
prove_buttons = ["prove", "prove_flow", "prove_flow_report_all",
|
|
|
|
|
"prove_report_all"]
|
2024-08-09 17:49:29 +02:00
|
|
|
prove_classes = ["ada-prove", "ada-prove-flow", "ada-prove-flow-report-all",
|
|
|
|
|
"ada-prove-report-all"]
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
self.prove_it = prove_it if prove_it is not None else \
|
2024-08-09 17:49:29 +02:00
|
|
|
(any(b in prove_classes for b in self.classes)
|
|
|
|
|
or any(b in prove_buttons for b in self.buttons))
|
2023-11-18 02:45:28 +01:00
|
|
|
|
|
|
|
|
self.source_files = source_files if source_files is not None else \
|
|
|
|
|
list()
|
|
|
|
|
self.project_filename = project_filename
|
|
|
|
|
self.spark_project_filename = spark_project_filename
|
|
|
|
|
self.project_main_file = project_main_file
|
|
|
|
|
|
|
|
|
|
# Hash of source-code
|
|
|
|
|
str_text = str(self.text).encode("utf-8")
|
|
|
|
|
self.text_hash: str = text_hash if text_hash is not None else \
|
|
|
|
|
hashlib.sha512(str_text).hexdigest()
|
|
|
|
|
self.text_hash_short: str = text_hash_short if text_hash_short is not None else \
|
|
|
|
|
hashlib.md5(str_text).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigBlock(Block):
|
|
|
|
|
def __init__(self, **opts):
|
|
|
|
|
self._opts = opts
|
|
|
|
|
for k, v in opts.items():
|
|
|
|
|
setattr(self, k, False if v == "False" else True)
|
|
|
|
|
|
|
|
|
|
def update(self, other_config):
|
|
|
|
|
self.__init__(**other_config._opts)
|