《手写PHP编译器》之准备工作
准备工作
在写代码之前,我们很有必要先把编译C++代码的工作做好。主要涉及到以下几个方面:
- 编写CMakeLists
- 编写一个编译的脚本
编写CMakeLists
因为CMakeLists.txt的内容比较简单,所以我直接贴出我们的CMakeLists.txt文件的内容:
cmake_minimum_required(VERSION 3.4)
project(yaphp)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_COMPILER clang++)
find_package(FLEX REQUIRED)
set(FlexOutput ${CMAKE_SOURCE_DIR}/src/Zend/zend_language_scanner.cc)
if(FLEX_FOUND)
add_custom_command(
OUTPUT ${FlexOutput}
COMMAND ${FLEX_EXECUTABLE}
--outfile=${FlexOutput}
${CMAKE_SOURCE_DIR}/src/Zend/zend_language_scanner.l
COMMENT "Generating zend_language_scanner.cc"
)
endif()
find_package(BISON REQUIRED)
set(BisonOutput ${CMAKE_SOURCE_DIR}/src/Zend/zend_language_parser.cc)
if(BISON_FOUND)
add_custom_command(
OUTPUT ${BisonOutput}
COMMAND ${BISON_EXECUTABLE}
--defines=${CMAKE_SOURCE_DIR}/src/Zend/zend_language_parser.h
--output=${BisonOutput}
${CMAKE_SOURCE_DIR}/src/Zend/zend_language_parser.y
COMMENT "Generating zend_language_parser.cc"
)
endif()
add_executable(yaphp
${FlexOutput}
${BisonOutput}
)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(yaphp PUBLIC ${CMAKE_CXX_FLAGS} -Wall -Wno-deprecated-register -O0 -g)
message(STATUS "summary of build options:
Install prefix: ${CMAKE_INSTALL_PREFIX}
Target system: ${CMAKE_SYSTEM_NAME}
Compiler:
CXX compiler: ${CMAKE_CXX_COMPILER}
")
我们来讲一下核心的东西,其他不清楚的地方,可以网上搜一下。
首先来看这段代码:
project(yaphp)
我们把我们的这个项目叫做yaphp,即表示Yet another php。
然后是这段代码:
find_package(FLEX REQUIRED)
set(FlexOutput ${CMAKE_SOURCE_DIR}/src/Zend/zend_language_scanner.cc)
if(FLEX_FOUND)
add_custom_command(
OUTPUT ${FlexOutput}
COMMAND ${FLEX_EXECUTABLE}
--outfile=${FlexOutput}
${CMAKE_SOURCE_DIR}/src/Zend/zend_language_scanner.l
COMMENT "Generating zend_language_scanner.cc"
)
endif()
这段代码做的事情是,通过flex,让zend_language_scanner.l文件生成zend_language_scanner.cc文件(如果不清楚zend_language_scanner.l的小伙伴不用着急,我们后面会讲)。并且,我们可以看到,我们把zend_language_scanner.l文件放在了Zend目录下,这实际上是和php-src(即php解释器)的一致的。
然后是这段代码:
find_package(BISON REQUIRED)
set(BisonOutput ${CMAKE_SOURCE_DIR}/src/Zend/zend_language_parser.cc)
if(BISON_FOUND)
add_custom_command(
OUTPUT ${BisonOutput}
COMMAND ${BISON_EXECUTABLE}
--defines=${CMAKE_SOURCE_DIR}/src/Zend/zend_language_parser.h
--output=${BisonOutput}
${CMAKE_SOURCE_DIR}/src/Zend/zend_language_parser.y
COMMENT "Generating zend_language_parser.cc"
)
endif()
这段代码做的事情是,通过bison,让zend_language_parser.y文件生成zend_language_parser.cc文件和zend_language_parser.h文件(如果不清楚zend_language_parser.y的小伙伴不用着急,我们后面会讲)。
然后是这段代码:
add_executable(yaphp
${FlexOutput}
${BisonOutput}
)
表示,我们要把${FlexOutput}和${BisonOutput}编译成yaphp可执行文件。
OK,按照这个CMakeLists.txt的意思,我们来创建对应的文件。
首先是编写src/Zend/zend_language_scanner.l文件:
%option noyywrap
%option nounput
%option noinput
%{
#include "zend_language_parser.h"
%}
%%
echo {
return T_ECHO;
}
[;(),+*/-] return *yytext;
[0-9]+ {
yylval = atoi(yytext);
return T_NUMBER;
}
[\t\n ]+ /* ignore \t, \n, whitespace */
%%
然后是编写src/Zend/zend_language_parser.y文件:
%{
#include <stdio.h>
#include <string.h>
extern int yylex(void);
extern int yyparse(void);
extern FILE *yyin;
extern int yylineno;
int yywrap()
{
return 1;
}
void yyerror(const char *s)
{
printf("[error] %s, in line %d\n", s, yylineno);
}
int main(int argc, char const *argv[])
{
const char *file = argv[1];
FILE *fp = fopen(file, "r");
if(fp == nullptr)
{
printf("cannot open %s\n", file);
return -1;
}
yyin = fp;
yyparse();
return 0;
}
%}
%token T_ECHO T_NUMBER
%%
statement: %empty
| T_ECHO expr { printf("%d\n", $2); }
;
expr: %empty
| T_NUMBER {$$ = $1;}
;
%%
然后,我们创建文件tests/test1.php:
echo 1
编写编译的脚本
我们创建文件rebuild.sh:
#!/bin/bash
__DIR__=$(cd "$(dirname "$0")" || exit 1; pwd); [ -z "${__DIR__}" ] && exit 1
cd "${__DIR__}" && ./clean.sh && mkdir -p build && cd build && cmake .. && make
这段代码很简单,就是先调用clean.sh脚本做一些清理工作,然后调用cmake来生成Makefile,然后调用make来编译代码,生成yaphp。
然后创建文件tools/cleaner.sh:
#!/bin/bash
__DIR__=$(cd "$(dirname "$0")" || exit 1; pwd); [ -z "${__DIR__}" ] && exit 1
error(){ echo "[ERROR] $1"; exit 1; }
success(){ echo "[SUCCESS] $1"; exit 0; }
info(){ echo "[INFO] $1";}
workdir="$1"
if ! cd "${workdir}"; then
error "Cd to ${workdir} failed"
fi
info "Scanning dir \"${workdir}\" ..."
if [ ! -f "./Makefile" ] && [ ! -f "./CMakeLists.txt" ]; then
error "Non-project dir ${workdir}"
fi
info "CMake build dir will be removed:"
rm -rf -v ./build
info "Following files will be removed:"
find ${workdir}/src/Zend -name zend_language_scanner.cc -print0 | xargs -0 rm -f -v
find ${workdir}/src/Zend -name zend_language_parser.h -print0 | xargs -0 rm -f -v
find ${workdir}/src/Zend -name zend_language_parser.cc -print0 | xargs -0 rm -f -v
success "Clean '${workdir}' done"
这个脚本会清理掉cmake生成的一系列文件。
然后创建文件clean.sh:
#!/bin/bash
__DIR__=$(cd "$(dirname "$0")" || exit 1; pwd); [ -z "${__DIR__}" ] && exit 1
"${__DIR__}"/tools/cleaner.sh "${__DIR__}"
OK,现在,所以的事情都做好了,我们只需要执行脚本rebuild.sh:
./rebuild.sh
# 省略其他的输出
[100%] Linking CXX executable yaphp
[100%] Built target yaphp
现在,你将会在目录build下面看到编译好的yaphp。并且,细心的话,你会发现,在目录src/Zend下面,生成了文件zend_language_scanner.cc、zend_language_parser.h、zend_language_parser.cc。
现在,让我们执行这个yaphp:
./build/yaphp tests/test1.php
1
我们将会看到,1被打印了出来。

浙公网安备 33010602011771号