2
0
mirror of https://github.com/boostorg/json.git synced 2026-01-19 04:12:14 +00:00

pretty printer module

This commit is contained in:
Dmitry Arkhipov
2022-07-31 21:59:03 +03:00
committed by Dmitry Arkhipov
parent 824cc59b9f
commit 9faecfc7ce
5 changed files with 570 additions and 0 deletions

4
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/bin
/bin.v2
/bin64
/_build*
temp
@@ -19,3 +20,6 @@ temp
# local copy of bench
/bench/bench
/bench/bench.exe
# Python
__pycache__

View File

@@ -0,0 +1,118 @@
#
# Copyright (c) 2024 Dmitry Arkhipov (grisumbras@yandex.ru)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/json
#
find_package(Python3 QUIET COMPONENTS Interpreter)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(BoostPrettyPrinters
REQUIRED_VARS Python3_Interpreter_FOUND)
find_program(BoostPrettyPrinters_GDB gdb DOC "GDB executable tos use")
set(BoostPrettyPrinters_HAS_GDB "${BoostPrettyPrinters_GDB}")
set(BoostPrettyPrinters_GDB_HEADER_SCRIPT
"${CMAKE_CURRENT_LIST_DIR}/generate-gdb-header.py")
set(BoostPrettyPrinters_GDB_TEST_SCRIPT
"${CMAKE_CURRENT_LIST_DIR}/generate-gdb-test-runner.py")
set(BoostPrettyPrinters_INCLUDES "${CMAKE_CURRENT_LIST_DIR}/include")
function(boost_pretty_printers_gdb_python_header)
set(options EXCLUDE_FROM_ALL)
set(oneValueArgs TARGET INPUT OUTPUT HEADER_GUARD DISABLE_MACRO)
set(multiValueArgs)
cmake_parse_arguments(BOOST_PPRINT_GDB_GEN
"${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
foreach(kw TARGET INPUT OUTPUT)
if(NOT DEFINED "BOOST_PPRINT_GDB_GEN_${kw}")
message(FATAL_ERROR "Argument ${kw} is required for function \
boost_pretty_printers_gdb_python_header.")
endif()
endforeach()
if(DEFINED BOOST_PPRINT_GDB_GEN_HEADER_GUARD)
set(BOOST_PPRINT_GDB_GEN_HEADER_GUARD
"--header-guard=${BOOST_PPRINT_GDB_GEN_HEADER_GUARD}")
endif()
if(DEFINED BOOST_PPRINT_GDB_GEN_DISABLE_MACRO)
set(BOOST_PPRINT_GDB_GEN_DISABLE_MACRO
"--disable-macro=${BOOST_PPRINT_GDB_GEN_DISABLE_MACRO}")
endif()
add_custom_command(
OUTPUT "${BOOST_PPRINT_GDB_GEN_OUTPUT}"
MAIN_DEPENDENCY "${BOOST_PPRINT_GDB_GEN_INPUT}"
DEPENDS "${BoostPrettyPrinters_GDB_HEADER_SCRIPT}"
COMMAND
"${Python3_EXECUTABLE}"
"${BoostPrettyPrinters_GDB_HEADER_SCRIPT}"
"${CMAKE_CURRENT_SOURCE_DIR}/${BOOST_PPRINT_GDB_GEN_INPUT}"
"${CMAKE_CURRENT_SOURCE_DIR}/${BOOST_PPRINT_GDB_GEN_OUTPUT}"
${BOOST_PPRINT_GDB_GEN_HEADER_GUARD}
${BOOST_PPRINT_GDB_GEN_DISABLE_MACRO}
COMMENT "Regenerating ${BOOST_PPRINT_GDB_GEN_OUTPUT}")
if(NOT BOOST_PPRINT_GDB_GEN_EXCLUDE_FROM_ALL)
set(isInAll ALL)
endif()
add_custom_target(${BOOST_PPRINT_GDB_GEN_TARGET}
${isInAll}
DEPENDS "${BOOST_PPRINT_GDB_GEN_OUTPUT}")
endfunction()
function(boost_pretty_printers_test_gdb_printers)
set(options EXCLUDE_FROM_ALL)
set(oneValueArgs TEST PROGRAM)
set(multiValueArgs SOURCES)
cmake_parse_arguments(BOOST_PPRINT_TEST_GDB
"${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
foreach(kw TEST SOURCES)
if(NOT DEFINED "BOOST_PPRINT_TEST_GDB_${kw}")
message(FATAL_ERROR "Argument ${kw} is required for function \
boost_pretty_printers_test_gdb_printers.")
endif()
endforeach()
if(NOT DEFINED BOOST_PPRINT_TEST_GDB_PROGRAM)
set(BOOST_PPRINT_TEST_GDB_PROGRAM ${BOOST_PPRINT_TEST_GDB_TEST})
endif()
if(BOOST_PPRINT_TEST_GDB_EXCLUDE_FROM_ALL)
set(excludeFromAll EXCLUDE_FROM_ALL)
else()
set(includeInAll ALL)
endif()
LIST(GET BOOST_PPRINT_TEST_GDB_SOURCES 0 source0)
add_custom_command(
OUTPUT "${BOOST_PPRINT_TEST_GDB_TEST}.py"
DEPENDS "${source0}"
COMMAND
"${Python3_EXECUTABLE}"
"${BoostPrettyPrinters_GDB_TEST_SCRIPT}"
"${CMAKE_CURRENT_SOURCE_DIR}/${source0}"
"${BOOST_PPRINT_TEST_GDB_TEST}.py"
COMMENT "Generating ${source0}")
add_custom_target(${BOOST_PPRINT_TEST_GDB_TEST}_runner
${includeInAll}
DEPENDS "${BOOST_PPRINT_TEST_GDB_TEST}.py")
add_executable(${BOOST_PPRINT_TEST_GDB_PROGRAM}
${excludeFromAll}
${BOOST_PPRINT_TEST_GDB_SOURCES})
add_dependencies(
${BOOST_PPRINT_TEST_GDB_PROGRAM}
${BOOST_PPRINT_TEST_GDB_TEST}_runner)
add_test(
NAME ${BOOST_PPRINT_TEST_GDB_TEST}
COMMAND "${BoostPrettyPrinters_GDB}"
--batch-silent
-x "${BOOST_PPRINT_TEST_GDB_TEST}.py"
$<TARGET_FILE:${BOOST_PPRINT_TEST_GDB_PROGRAM}>)
endfunction()

View File

@@ -0,0 +1,159 @@
#
# Copyright (c) 2024 Dmitry Arkhipov (grisumbras@yandex.ru)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/json
#
import common ;
import make ;
import modules ;
import param ;
import path ;
import project ;
import property ;
import python ;
import testing ;
import toolset ;
rule init ( command * )
{
if ! $(.initialized)
{
.initialized = true ;
}
else
{
if ! $(command)
{
return ;
}
}
command ?= gdb ;
GDB_COMMAND = ;
.has-gdb = ;
for local part in $(command)
{
local found = [ common.find-tool $(part) ] ;
if $(found)
{
.has-gdb = true ;
}
else
{
found = $(part) ;
}
GDB_COMMAND += $(found) ;
}
}
rule has-gdb
{
return $(.has-gdb) ;
}
rule gdb-python-header ( target : sources + : requirements *
: usage-requirements * )
{
param.handle-named-params sources requirements usage-requirements ;
make $(target)
: $(sources)
: @boost-pretty-printers.generate-gdb-header
: $(requirements)
<dependency>$(.gdb-header-script)
: $(usage-requirements)
;
}
rule test-gdb-printers ( target : sources + : requirements * : default-build *
: usage-requirements * )
{
param.handle-named-params
sources requirements default-build usage-requirements ;
local project = [ project.current ] ;
local test-runner = _$(target:S=.py) ;
make $(test-runner)
: $(sources[1])
: @boost-pretty-printers.generate-gdb-test-runner
: <dependency>$(.gdb-test-generator-script)
;
$(project).mark-target-as-explicit $(test-runner) ;
local test-program = _$(target) ;
run $(sources)
: target-name $(test-program)
: requirements
<testing.launcher>$(GDB_COMMAND)
<testing.arg>--batch-silent
<testing.arg>-x
<testing.input-file>$(test-runner)
<debug-symbols>on
<runtime-debugging>on
<optimization>debug
$(requirements)
: default-build
$(default-build)
;
$(project).mark-target-as-explicit $(test-program) ;
alias $(target)
: $(test-program)
: $(requirements)
: $(default-build)
: $(usage-requirements)
;
}
.here = [ path.make [ modules.binding $(__name__) ] ] ;
.here = $(.here:D) ;
.gdb-header-script = $(.here)/generate-gdb-header.py ;
.gdb-test-generator-script = $(.here)/generate-gdb-test-runner.py ;
rule generate-gdb-header ( target : sources + : properties * )
{
warn-if-not-configuered ;
RUNNER on $(target) = [ path.native $(.gdb-header-script) ] ;
}
actions generate-gdb-header
{
"$(PYTHON:E=python)" "$(RUNNER)" $(>[1]) $(<) $(FLAGS)
}
toolset.flags boost-pretty-printers.generate-gdb-header FLAGS <flags> ;
toolset.flags boost-pretty-printers.generate-gdb-header PYTHON <python.interpreter> ;
rule generate-gdb-test-runner ( target : sources + : properties * )
{
warn-if-not-configuered ;
RUNNER on $(target) = [ path.native $(.gdb-test-generator-script) ] ;
}
actions generate-gdb-test-runner
{
"$(PYTHON:E=python)" "$(RUNNER)" $(>[1]) $(<)
}
toolset.flags boost-pretty-printers.generate-gdb-test-runner PYTHON <python.interpreter> ;
rule warn-if-not-configuered ( )
{
if $(.checked) { return ; }
if ! $(.initialized)
{
echo "warning: module boost-pretty-printers was not initialized!" ;
echo " add \"using boost-pretty-printers ;\" to your build scripts." ;
}
if ! [ python.configured ]
{
echo "warning: module python was not initialized!" ;
echo " add \"using python ;\" to your build scripts." ;
}
.checked = true ;
}

View File

@@ -0,0 +1,150 @@
#!/usr/bin/env python
#
# Copyright (c) 2024 Dmitry Arkhipov (grisumbras@yandex.ru)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/json
#
import argparse
import os
import random
import re
import sys
_top = '''\
#if defined(__ELF__)
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Woverlength-strings"
#elif defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Woverlength-strings"
#endif
__asm__(
".pushsection \\\".debug_gdb_scripts\\\", \\\"MS\\\",@progbits,1\\n"
".ascii \\\"\\\\4gdb.inlined-script.{script_id}\\\\n\\\"\\n"
'''
_bottom = '''\
".byte 0\\n"
".popsection\\n");
#if defined(__clang__)
# pragma clang diagnostic pop
#elif defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
#endif // defined(__ELF__)
'''
class Nullcontext():
def __init__(self):
pass
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
pass
def parse_args(args):
parser = argparse.ArgumentParser(
prog=args[0],
description=(
'Converts a Python script into a C header '
'that pushes that script into .debug_gdb_scripts ELF section'))
parser.add_argument(
'input',
help='Input file')
parser.add_argument(
'output',
nargs='?',
help='Output file; STDOUT by default')
parser.add_argument(
'--header-guard',
help=(
'Header guard macro to use; '
'by default deduced from the output file name'))
parser.add_argument(
'--disable-macro',
default='BOOST_ALL_NO_EMBEDDED_GDB_SCRIPTS',
help=(
'Macro to disable pretty printer embedding; '
'by default BOOST_ALL_NO_EMBEDDED_GDB_SCRIPTS'))
return parser.parse_args(args[1:])
def write_front_matter(header, header_guard, disable_macro, source):
header.write(
'// Autogenerated from %s by boost-pretty-printers\n\n' % os.path.basename(source))
if header_guard:
header.write('#ifndef %s\n' % header_guard)
header.write('#define %s\n\n' % header_guard)
if disable_macro:
header.write('#ifndef %s\n\n' % disable_macro)
header.write(
_top.format(script_id=header_guard or str(random.random()) ))
def write_back_matter(header, header_guard, disable_macro):
header.write(_bottom)
if disable_macro:
header.write('\n#endif // %s\n' % disable_macro)
if header_guard:
header.write('\n#endif // %s\n' % header_guard)
def main(args, stdin, stdout):
args = parse_args(args)
header_guard = args.header_guard
if header_guard is None:
if args.output:
header_guard = os.path.basename(args.output).upper()
if args.output:
header = open(args.output, 'w', encoding='utf-8')
header_ctx = header
else:
header = stdout
header_ctx = Nullcontext()
not_whitespace = re.compile('\\S', re.U)
with open(args.input, 'r', encoding='utf-8') as script:
with header_ctx:
check_for_attribution = True
for line in script:
if not not_whitespace.search(line):
header.write('\n')
continue
if check_for_attribution:
if line.startswith('#!'): # shebang
continue
elif line.startswith('#'):
header.write('// ')
header.write(line[1:])
continue
else:
write_front_matter(
header,
header_guard,
args.disable_macro,
args.input)
check_for_attribution = False
header.write(' ".ascii \\"')
header.write(line[:-1])
header.write('\\\\n\\"\\n"\n')
write_back_matter(header, header_guard, args.disable_macro)
if __name__ == '__main__':
main(sys.argv, sys.stdin, sys.stdout)

View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python
#
# Copyright (c) 2024 Dmitry Arkhipov (grisumbras@yandex.ru)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/json
#
import argparse
import re
import sys
_top = '''\
import gdb
import re
import sys
import traceback
def gdb_print(expr):
output = gdb.execute('print %s' % expr, to_string=True)
parts = output[:-1].split(' = ', 1)
if len(parts) > 1:
output = parts[1]
else:
output = parts[0]
return output
def TEST_EXPR(expr, pattern, *args, **kwargs):
def test():
if args or kwargs:
actual_args = [gdb_print(arg) for arg in args]
actual_kwargs = dict([
(k, gdb_print(v)) for (k,v) in kwargs.items() ])
actual_pattern = pattern.format(*actual_args, **actual_kwargs)
else:
actual_pattern = pattern
output = gdb_print(expr)
try:
if actual_pattern != output:
print((
'{0}: error: expression "{1}" evaluates to\\n'
'{2}\\n'
'expected\\n'
'{3}\\n').format(
bp.location, expr, output, actual_pattern),
file=sys.stderr)
gdb.execute('quit 1')
except:
raise
return test
_return_code = 0
_tests_to_run = []
try:
assert gdb.objfiles()
'''
_breakpoint = '''\
_tests_to_run.append(
(gdb.Breakpoint('{input}:{line}', internal=True), {text}))
'''
_bottom = '''\
gdb.execute('start', to_string=True)
for bp, test in _tests_to_run:
gdb.execute('continue', to_string=True)
test()
gdb.execute('continue', to_string=True)
except BaseException:
traceback.print_exc()
gdb.execute('disable breakpoints')
try:
gdb.execute('continue')
except:
pass
_return_code = 1
gdb.execute('quit %s' % _return_code)
'''
class Nullcontext():
def __init__(self):
pass
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
pass
def parse_args(args):
parser = argparse.ArgumentParser(
prog=args[0],
description=(
'Creates a Python script from C++ source file to control a GDB '
'test of that source file'))
parser.add_argument(
'input',
help='Input file')
parser.add_argument(
'output',
nargs='?',
help='Output file; STDOUT by default')
return parser.parse_args(args[1:])
def main(args, stdin, stdout):
args = parse_args(args)
if args.output:
output = open(args.output, 'w', encoding='utf-8')
output_ctx = output
else:
output = stdout
output_ctx = Nullcontext()
test_line = re.compile(r'^\s*//\s*TEST_', re.U)
with open(args.input, 'r', encoding='utf-8') as input:
with output_ctx:
output.write(_top)
for n, line in enumerate(input, start=1):
match = test_line.search(line)
if not match:
continue
line = line.strip()[2:].lstrip()
output.write(
_breakpoint.format(input=args.input, line=n, text=line))
output.write(_bottom)
if __name__ == '__main__':
main(sys.argv, sys.stdin, sys.stdout)