Skip to content

Commit 8f019ef

Browse files
authored
Add lexer and parser generator script (#28)
This enables generating lexer and parser files with a command-line script: cmake -P cmake/scripts/GenerateLexersParsers.cmake Instead of doing the entire CMake configuration phase and then executing some target to generate files this simplifies step to generate these files when needed. * Two new utility modules: PHP/Bison.cmake and PHP/Re2c.cmake. They provide using Bison and re2c in CMake normal workflow and command-line. They rely on their find modules. Find modules are not meant to provide functions. This is a bad practice learned from some existing modules out there for convenience but it is more proper to have such functions as separate modules. Find modules should ideally only deal with finding packages and providing the imported targets. Ideally, how the package is used should be done in some wrapper module, which provides these functions. Here, the php_bison() and php_re2c(). Also, downloading is moved to these modules for now. * ExternalProject is for now used to download and build Bison and re2c if they are not found on the system. * They also download these two tools if they are not found on the system. Bison download and custom build is for now only supported through its Autotools-based build system. * More common naming for parser and lexer files generation functionality used in filenames: "grammar" * Long Bison and re2c options used to find them in the docs more easily * The --conditions option is available since re2c 1.1. * Bison removed from CI
1 parent c96d7ed commit 8f019ef

22 files changed

+1726
-557
lines changed

.github/workflows/ci.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ jobs:
6363
run: |
6464
sudo apt-get -y install \
6565
build-essential \
66-
bison \
6766
libssl-dev \
6867
libpcre2-dev \
6968
libsqlite3-dev \

bin/check-cmake.php

+29-10
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ function getProjectModules(): array
191191
'FindPackageMessage' => ['find_package_message'],
192192
'ProcessorCount' => ['processorcount'],
193193
'PHP/AddCustomCommand' => ['php_add_custom_command'],
194+
'PHP/Bison' => ['php_bison', '/find_package\([\n ]*BISON/'],
194195
'PHP/CheckAttribute' => [
195196
'php_check_function_attribute',
196197
'php_check_variable_attribute',
@@ -199,6 +200,7 @@ function getProjectModules(): array
199200
'PHP/ConfigureFile' => ['php_configure_file'],
200201
'PHP/Install' => ['php_install'],
201202
'PHP/PkgConfigGenerator' => ['pkgconfig_generate_pc'],
203+
'PHP/Re2c' => ['php_re2c', '/find_package\([\n ]*RE2C/'],
202204
'PHP/SearchLibraries' => ['php_search_libraries'],
203205
'PHP/Set' => ['php_set'],
204206
'PHP/SystemExtensions' => ['PHP::SystemExtensions'],
@@ -273,24 +275,29 @@ function checkCMakeInclude(Iterator $files, array $modules): int
273275
$content = getCMakeCode($file);
274276

275277
// Check for redundant includes.
276-
foreach ($modules as $module => $commands) {
278+
foreach ($modules as $module => $patterns) {
277279
$hasModule = false;
278280
$moduleEscaped = str_replace('/', '\/', $module);
279281

280282
if (1 === preg_match('/^[ \t]*include[ \t]*\(' . $moduleEscaped . '[ \t]*\)/m', $content)) {
281283
$hasModule = true;
282284
}
283285

284-
$hasCommand = false;
285-
foreach ($commands as $command) {
286-
if (
286+
$hasPattern = false;
287+
foreach ($patterns as $pattern) {
288+
if (isRegularExpression($pattern)) {
289+
if (1 === preg_match($pattern, $content)) {
290+
$hasPattern = true;
291+
break;
292+
}
293+
} elseif (
287294
(
288-
1 === preg_match('/::/', $command)
289-
&& 1 === preg_match('/[^A-Za-z0-9_]' . $command . '[^A-Za-z0-9_]/m', $content)
295+
1 === preg_match('/::/', $pattern)
296+
&& 1 === preg_match('/[^A-Za-z0-9_]' . $pattern . '[^A-Za-z0-9_]/m', $content)
290297
)
291-
|| 1 === preg_match('/^[ \t]*' . $command . '[ \t]*\(/m', $content)
298+
|| 1 === preg_match('/^[ \t]*' . $pattern . '[ \t]*\(/m', $content)
292299
) {
293-
$hasCommand = true;
300+
$hasPattern = true;
294301
break;
295302
}
296303
}
@@ -304,12 +311,12 @@ function checkCMakeInclude(Iterator $files, array $modules): int
304311
continue;
305312
}
306313

307-
if ($hasModule && !$hasCommand) {
314+
if ($hasModule && !$hasPattern) {
308315
$status = 1;
309316
output("E: redundant include($module) in $file");
310317
}
311318

312-
if (!$hasModule && $hasCommand) {
319+
if (!$hasModule && $hasPattern) {
313320
$status = 1;
314321
output("E: missing include($module) in $file");
315322
}
@@ -319,6 +326,18 @@ function checkCMakeInclude(Iterator $files, array $modules): int
319326
return $status;
320327
};
321328

329+
/**
330+
* Check if given string is regular expression.
331+
*/
332+
function isRegularExpression(string $string): bool
333+
{
334+
set_error_handler(static function () {}, E_WARNING);
335+
$isRegularExpression = false !== preg_match($string, '');
336+
restore_error_handler();
337+
338+
return $isRegularExpression;
339+
}
340+
322341
/**
323342
* Check for set(<variable>) usages with only one argument. These should be
324343
* replaced with set(<variable> "").

cmake/Zend/CMakeLists.txt

+2-100
Original file line numberDiff line numberDiff line change
@@ -523,108 +523,10 @@ if(TARGET Zend::MaxExecutionTimers)
523523
endif()
524524

525525
################################################################################
526-
# Generate lexers and parsers.
526+
# Generate parser and lexer files.
527527
################################################################################
528528

529-
if(BISON_FOUND)
530-
bison_target(
531-
zend_ini_parser
532-
zend_ini_parser.y
533-
${CMAKE_CURRENT_SOURCE_DIR}/zend_ini_parser.c
534-
COMPILE_FLAGS "${PHP_DEFAULT_BISON_FLAGS}"
535-
VERBOSE REPORT_FILE zend_ini_parser.output
536-
DEFINES_FILE ${CMAKE_CURRENT_SOURCE_DIR}/zend_ini_parser.h
537-
)
538-
539-
add_custom_target(zend_ini_parser DEPENDS ${BISON_TARGET_outputs})
540-
add_dependencies(php_generate_files zend_ini_parser)
541-
542-
bison_target(
543-
zend_language_parser
544-
zend_language_parser.y
545-
${CMAKE_CURRENT_SOURCE_DIR}/zend_language_parser.c
546-
COMPILE_FLAGS "${PHP_DEFAULT_BISON_FLAGS}"
547-
VERBOSE REPORT_FILE zend_language_parser.output
548-
DEFINES_FILE ${CMAKE_CURRENT_SOURCE_DIR}/zend_language_parser.h
549-
)
550-
551-
# Tweak zendparse to be exported through ZEND_API. This has to be revisited
552-
# once bison supports foreign skeletons and that bison version is used. Read
553-
# https://git.savannah.gnu.org/cgit/bison.git/tree/data/README.md for more.
554-
file(
555-
GENERATE
556-
OUTPUT CMakeFiles/PatchLanguageParser.cmake
557-
CONTENT [[
558-
file(READ "${SOURCE_DIR}/zend_language_parser.h" content)
559-
string(
560-
REPLACE
561-
"int zendparse"
562-
"ZEND_API int zendparse"
563-
content_2
564-
"${content}"
565-
)
566-
if(
567-
NOT content MATCHES "ZEND_API int zendparse"
568-
AND NOT content STREQUAL "${content_2}"
569-
)
570-
execute_process(
571-
COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --blue --bold
572-
" [Zend] Patching Zend/zend_language_parser.h"
573-
)
574-
file(WRITE "${SOURCE_DIR}/zend_language_parser.h" "${content_2}")
575-
endif()
576-
577-
file(READ "${SOURCE_DIR}/zend_language_parser.c" content)
578-
string(
579-
REPLACE
580-
"int zendparse"
581-
"ZEND_API int zendparse"
582-
content_2
583-
"${content}"
584-
)
585-
if(
586-
NOT content MATCHES "ZEND_API int zendparse"
587-
AND NOT content STREQUAL "${content_2}"
588-
)
589-
execute_process(
590-
COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --blue --bold
591-
" [Zend] Patching Zend/zend_language_parser.c"
592-
)
593-
file(WRITE "${SOURCE_DIR}/zend_language_parser.c" "${content_2}")
594-
endif()
595-
]]
596-
)
597-
598-
add_custom_target(
599-
zend_patch_language_parser
600-
COMMAND ${CMAKE_COMMAND}
601-
-D SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}
602-
-P "CMakeFiles/PatchLanguageParser.cmake"
603-
DEPENDS ${BISON_zend_language_parser_OUTPUTS}
604-
VERBATIM
605-
)
606-
607-
add_dependencies(zend zend_patch_language_parser)
608-
add_dependencies(php_generate_files zend_patch_language_parser)
609-
endif()
610-
611-
if(RE2C_FOUND)
612-
re2c_target(
613-
zend_language_scanner
614-
zend_language_scanner.l
615-
${CMAKE_CURRENT_SOURCE_DIR}/zend_language_scanner.c
616-
HEADER ${CMAKE_CURRENT_SOURCE_DIR}/zend_language_scanner_defs.h
617-
OPTIONS --case-inverted -cbdF
618-
)
619-
620-
re2c_target(
621-
zend_ini_scanner
622-
zend_ini_scanner.l
623-
${CMAKE_CURRENT_SOURCE_DIR}/zend_ini_scanner.c
624-
HEADER ${CMAKE_CURRENT_SOURCE_DIR}/zend_ini_scanner_defs.h
625-
OPTIONS --case-inverted -cbdF
626-
)
627-
endif()
529+
include(cmake/GenerateGrammar.cmake)
628530

629531
################################################################################
630532
# Configure fibers.
+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Generate parser and lexer files.
2+
3+
if(CMAKE_SCRIPT_MODE_FILE STREQUAL CMAKE_CURRENT_LIST_FILE)
4+
message(FATAL_ERROR "This file should be used with include().")
5+
endif()
6+
7+
include(PHP/Bison)
8+
9+
if(CMAKE_SCRIPT_MODE_FILE)
10+
set(verbose "")
11+
else()
12+
set(verbose VERBOSE)
13+
endif()
14+
15+
php_bison(
16+
zend_ini_parser
17+
zend_ini_parser.y
18+
${CMAKE_CURRENT_SOURCE_DIR}/zend_ini_parser.c
19+
HEADER
20+
ADD_DEFAULT_OPTIONS
21+
${verbose}
22+
CODEGEN
23+
)
24+
25+
php_bison(
26+
zend_language_parser
27+
zend_language_parser.y
28+
${CMAKE_CURRENT_SOURCE_DIR}/zend_language_parser.c
29+
HEADER
30+
ADD_DEFAULT_OPTIONS
31+
${verbose}
32+
CODEGEN
33+
)
34+
35+
# Tweak zendparse to be exported through ZEND_API. This has to be revisited if
36+
# Bison will support foreign skeletons.
37+
# See: https://git.savannah.gnu.org/cgit/bison.git/tree/data/README.md
38+
block()
39+
string(
40+
CONCAT patch
41+
"cmake_path(SET SOURCE_DIR NORMALIZE ${CMAKE_CURRENT_SOURCE_DIR})\n"
42+
[[
43+
cmake_path(
44+
RELATIVE_PATH
45+
SOURCE_DIR
46+
BASE_DIRECTORY ${CMAKE_SOURCE_DIR}
47+
OUTPUT_VARIABLE relativeDir
48+
)
49+
file(READ "${SOURCE_DIR}/zend_language_parser.h" content)
50+
string(
51+
REPLACE
52+
"int zendparse"
53+
"ZEND_API int zendparse"
54+
content_2
55+
"${content}"
56+
)
57+
if(
58+
NOT content MATCHES "ZEND_API int zendparse"
59+
AND NOT content STREQUAL "${content_2}"
60+
)
61+
message(STATUS "Patching ${relativeDir}/zend_language_parser.h")
62+
file(WRITE "${SOURCE_DIR}/zend_language_parser.h" "${content_2}")
63+
endif()
64+
65+
file(READ "${SOURCE_DIR}/zend_language_parser.c" content)
66+
string(
67+
REPLACE
68+
"int zendparse"
69+
"ZEND_API int zendparse"
70+
content_2
71+
"${content}"
72+
)
73+
if(
74+
NOT content MATCHES "ZEND_API int zendparse"
75+
AND NOT content STREQUAL "${content_2}"
76+
)
77+
message(STATUS "Patching ${relativeDir}/zend_language_parser.c")
78+
file(WRITE "${SOURCE_DIR}/zend_language_parser.c" "${content_2}")
79+
endif()
80+
]])
81+
82+
# Run patch based on whether building or running inside a script.
83+
if(CMAKE_SCRIPT_MODE_FILE)
84+
cmake_language(EVAL CODE "${patch}")
85+
else()
86+
file(
87+
GENERATE
88+
OUTPUT CMakeFiles/Zend/PatchLanguageParser.cmake
89+
CONTENT "${patch}"
90+
)
91+
add_custom_target(
92+
zend_language_parser_patch
93+
COMMAND ${CMAKE_COMMAND} -P CMakeFiles/Zend/PatchLanguageParser.cmake
94+
DEPENDS zend_language_parser
95+
VERBATIM
96+
)
97+
add_dependencies(zend zend_language_parser_patch)
98+
endif()
99+
endblock()
100+
101+
include(PHP/Re2c)
102+
103+
php_re2c(
104+
zend_ini_scanner
105+
zend_ini_scanner.l
106+
${CMAKE_CURRENT_SOURCE_DIR}/zend_ini_scanner.c
107+
HEADER ${CMAKE_CURRENT_SOURCE_DIR}/zend_ini_scanner_defs.h
108+
ADD_DEFAULT_OPTIONS
109+
OPTIONS
110+
--bit-vectors
111+
--case-inverted
112+
--conditions
113+
--debug-output
114+
--flex-syntax
115+
CODEGEN
116+
)
117+
118+
php_re2c(
119+
zend_language_scanner
120+
zend_language_scanner.l
121+
${CMAKE_CURRENT_SOURCE_DIR}/zend_language_scanner.c
122+
HEADER ${CMAKE_CURRENT_SOURCE_DIR}/zend_language_scanner_defs.h
123+
ADD_DEFAULT_OPTIONS
124+
OPTIONS
125+
--bit-vectors
126+
--case-inverted
127+
--conditions
128+
--debug-output
129+
--flex-syntax
130+
CODEGEN
131+
)

cmake/cmake/Bootstrap.cmake

-4
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ add_library(php_sapi INTERFACE)
4848
add_library(PHP::sapi ALIAS php_sapi)
4949
target_link_libraries(php_sapi INTERFACE PHP::config)
5050

51-
# Create a custom target for generating files (parsers, lexers, etc.) manually:
52-
# cmake --build <dir> -t php_generate_files
53-
add_custom_target(php_generate_files)
54-
5551
# Configure build types.
5652
include(cmake/BuildTypes.cmake)
5753

cmake/cmake/Configuration.cmake

+5-10
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ mark_as_advanced(PHP_PROGRAM_PREFIX)
9292
set(PHP_PROGRAM_SUFFIX "" CACHE STRING "Append suffix to the program names")
9393
mark_as_advanced(PHP_PROGRAM_SUFFIX)
9494

95-
option(PHP_RE2C_CGOTO "Enable computed goto GCC extension with re2c")
96-
mark_as_advanced(PHP_RE2C_CGOTO)
97-
9895
option(PHP_THREAD_SAFETY "Enable thread safety (ZTS)")
9996

10097
cmake_dependent_option(
@@ -213,13 +210,6 @@ set(PHP_ZLIB_MIN_VERSION 1.2.0.4)
213210
set(PHP_BZIP2_MIN_VERSION 1.0.0)
214211

215212
# Additional metadata for external packages to avoid duplication.
216-
set_package_properties(
217-
BISON
218-
PROPERTIES
219-
URL "https://www.gnu.org/software/bison/"
220-
DESCRIPTION "General-purpose parser generator"
221-
)
222-
223213
set_package_properties(
224214
BZip2
225215
PROPERTIES
@@ -268,3 +258,8 @@ set_package_properties(
268258
URL "https://zlib.net/"
269259
DESCRIPTION "Compression library"
270260
)
261+
262+
# Set base directory for ExternalProject CMake module.
263+
set_directory_properties(
264+
PROPERTIES EP_BASE ${PHP_BINARY_DIR}/CMakeFiles/PHP/ExternalProject
265+
)

0 commit comments

Comments
 (0)