重新實現PHP中的範圍運算符
SitePoint精彩文章推薦:改進後的PHP範圍運算符實現
本文經作者授權轉載於SitePoint。以下內容由Thomas Punt撰寫,介紹了PHP範圍運算符的改進實現方法。如果您對PHP內部機制和為喜愛的編程語言添加功能感興趣,那麼現在正是學習的好時機!
本文假設讀者能夠從源代碼構建PHP。如果不是這樣,請先閱讀PHP內部機製書籍的“構建PHP”章節。
在本文的前篇(提示:請確保您已閱讀),我展示了一種在PHP中實現範圍運算符的方法。然而,最初的實現很少是最好的,因此本文旨在探討如何改進之前的實現。
再次感謝Nikita Popov校對本文!
關鍵要點
- Thomas Punt重新實現了PHP中的範圍運算符,將計算邏輯從Zend虛擬機中移出,從而允許在常量表達式上下文中使用範圍運算符。
- 此重新實現能夠在編譯時(對於字面量操作數)或運行時(對於動態操作數)進行計算。這不僅為Opcache用戶帶來了一點好處,而且允許將常量表達式功能與範圍運算符一起使用。
- 重新實現過程涉及更新詞法分析器、解析器、編譯階段和Zend虛擬機。詞法分析器實現保持不變,而解析器實現與之前部分相同。編譯階段不需要更新Zend/zend_compile.c文件,因為它已經包含處理二元運算的必要邏輯。 Zend虛擬機已更新為在運行時處理ZEND_RANGE操作碼的執行。
- 在本系列的第三部分中,Punt計劃通過介紹如何重載此運算符來構建此實現。這將使對象能夠用作操作數,並為字符串添加適當的支持。
先前實現的缺點
最初的實現將範圍運算符的所有邏輯都放在Zend虛擬機中,這迫使計算在執行ZEND_RANGE操作碼時純粹在運行時進行。這不僅意味著對於字面量操作數,計算不能轉移到編譯時,而且還意味著某些功能根本無法工作。
在此實現中,我們將範圍運算符邏輯從Zend虛擬機中移出,以便能夠在編譯時(對於字面量操作數)或運行時(對於動態操作數)進行計算。這不僅為Opcache用戶帶來了一點好處,更重要的是允許將常量表達式功能與範圍運算符一起使用。
例如:
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
因此,事不宜遲,讓我們重新實現範圍運算符。
更新詞法分析器
詞法分析器實現保持完全不變。令牌首先在Zend/zend_language_scanner.l(約1200行)中註冊:
<st_in_scripting>"|>" { </st_in_scripting> RETURN_TOKEN(T_RANGE); }
然後在Zend/zend_language_parser.y(約220行)中聲明:
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
必須再次通過進入ext/tokenizer目錄並執行tokenizer_data_gen.sh文件來重新生成標記器擴展。
更新解析器
解析器實現與之前部分相同。我們再次通過將T_RANGE令牌添加到以下行的末尾來聲明運算符的優先級和結合性(約70行):
<st_in_scripting>"|>" { </st_in_scripting> RETURN_TOKEN(T_RANGE); }
然後,我們再次更新expr_without_variable產生式規則,但這次語義動作(花括號內的代碼)將略有不同。使用以下代碼更新它(我將其放在T_SPACESHIP規則下方,約930行):
%token T_RANGE "|> (T_RANGE)"
這次,我們使用了zend_ast_create_binary_op函數(而不是zend_ast_create函數),它為我們創建了一個ZEND_AST_BINARY_OP節點。 zend_ast_create_binary_op採用一個操作碼名稱,該名稱將在編譯階段用於區分二元運算。
由於我們現在正在重用ZEND_AST_BINARY_OP節點類型,因此無需像之前在Zend/zend_ast.h文件中那樣定義新的ZEND_AST_RANGE節點類型。
更新編譯階段
這次,無需更新Zend/zend_compile.c文件,因為它已經包含處理二元運算的必要邏輯。因此,我們只需通過將我們的運算符設為ZEND_AST_BINARY_OP節點來重用此邏輯。
以下是zend_compile_binary_op函數的簡化版本:
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE
正如我們所看到的,它與我們上次創建的zend_compile_range函數非常相似。兩個重要的區別在於如何獲取操作碼類型以及當兩個操作數都是字面量時會發生什麼。
操作碼類型這次是從AST節點獲取的(而不是像上次那樣硬編碼),因為ZEND_AST_BINARY_OP節點存儲此值(如新的產生式規則的語義動作所示)以區分二元運算。當兩個操作數都是字面量時,將調用zend_try_ct_eval_binary_op函數。此函數如下所示:
| expr T_RANGE expr { $$ = zend_ast_create_binary_op(ZEND_RANGE, , ); }
該函數根據操作碼類型從Zend/zend_opcode.c中的get_binary_op函數(源代碼)獲取回調。這意味著我們需要接下來更新此函數以適應ZEND_RANGE操作碼。將以下case語句添加到get_binary_op函數(約750行):
void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *left_ast = ast->child[0]; zend_ast *right_ast = ast->child[1]; uint32_t opcode = ast->attr; znode left_node, right_node; zend_compile_expr(&left_node, left_ast); zend_compile_expr(&right_node, right_ast); if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) { if (zend_try_ct_eval_binary_op(&result->u.constant, opcode, &left_node.u.constant, &right_node.u.constant) ) { result->op_type = IS_CONST; zval_ptr_dtor(&left_node.u.constant); zval_ptr_dtor(&right_node.u.constant); return; } } do { // redacted code zend_emit_op_tmp(result, opcode, &left_node, &right_node); } while (0); } /* }}} */
現在我們必須定義range_function函數。這將在Zend/zend_operators.c文件中與所有其他運算符一起完成:
static inline zend_bool zend_try_ct_eval_binary_op(zval *result, uint32_t opcode, zval *op1, zval *op2) /* {{{ */ { binary_op_type fn = get_binary_op(opcode); /* don't evaluate division by zero at compile-time */ if ((opcode == ZEND_DIV || opcode == ZEND_MOD) && zval_get_long(op2) == 0) { return 0; } else if ((opcode == ZEND_SL || opcode == ZEND_SR) && zval_get_long(op2) return 0; } fn(result, op1, op2); return 1; } /* }}} */
函數原型包含兩個新的宏:ZEND_API和ZEND_FASTCALL。 ZEND_API用於通過使函數可用於編譯為共享對象的擴展來控制函數的可見性。 ZEND_FASTCALL用於確保使用更高效的調用約定,其中前兩個參數將使用寄存器而不是堆棧傳遞(對於x86上的64位構建比32位構建更相關)。
函數體與我們在上一篇文章中的Zend/zend_vm_def.h文件中所擁有的非常相似。 VM特定的內容不再存在,包括HANDLE_EXCEPTION宏調用(已替換為return FAILURE;),並且ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION宏調用已被完全刪除(此檢查和操作需要保留在VM中,因此宏將稍後從VM代碼中調用)。此外,如前所述,我們避免使用GET_OPn_ZVAL_PTR偽宏(而不是GET_OPn_ZVAL_PTR_DEREF)在VM中處理引用。
另一個值得注意的區別是我們正在對兩個操作數應用ZVAL_DEFEF以確保正確處理引用。這以前是在VM內部使用偽宏GET_OPn_ZVAL_PTR_DEREF完成的,但現在已轉移到此函數中。這樣做不是因為它在編譯時需要(因為對於編譯時處理,兩個操作數都必須是字面量,並且它們不能被引用),而是因為它使代碼庫中的其他位置能夠安全地調用range_function,而無需擔心引用處理。因此,大多數運算符函數(除了性能至關重要的地方)都執行引用處理,而不是在其VM操作碼定義中執行。
最後,我們必須將range_function原型添加到Zend/zend_operators.h文件:
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
更新Zend虛擬機
現在我們必須再次更新Zend虛擬機以在運行時處理ZEND_RANGE操作碼的執行。將以下代碼放在Zend/zend_vm_def.h(底部):
<st_in_scripting>"|>" { </st_in_scripting> RETURN_TOKEN(T_RANGE); }
(同樣,操作碼編號必須比當前最高操作碼編號大一,這可以在Zend/zend_vm_opcodes.h文件的底部看到。)
這次的定義要短得多,因為所有工作都在range_function中處理。我們只需調用此函數,傳入當前opline的結果操作數即可保存計算值。從range_function中刪除的異常檢查和跳到下一個操作碼仍在VM中由對ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION的調用處理。此外,如前所述,我們避免使用GET_OPn_ZVAL_PTR偽宏(而不是GET_OPn_ZVAL_PTR_DEREF)在VM中處理引用。
現在通過執行Zend/zend_vm_gen.php文件重新生成VM。
最後,漂亮打印機需要再次更新Zend/zend_ast.c文件。通過指定新運算符的優先級為170來更新優先級表註釋(約520行):
%token T_RANGE "|> (T_RANGE)"
然後,在zend_ast_export_ex函數中插入一個case語句,以在ZEND_AST_BINARY_OP case語句中處理ZEND_RANGE操作碼(約1300行):
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE
結論
本文展示了一種實現範圍運算符的替代方法,其中計算邏輯已從VM中移出。這具有能夠在常量表達式上下文中使用範圍運算符的優點。
本系列文章的第三部分將在此實現的基礎上構建,介紹如何重載此運算符。這將允許對像用作操作數(例如來自GMP庫的對像或實現__toString方法的對象)。它還將展示如何為字符串添加適當的支持(不像PHP當前範圍函數中看到的支持)。但就目前而言,我希望這能很好地演示ZE在將運算符實現到PHP中時的一些更深層次的方面。
以上是重新實現PHP中的範圍運算符的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undress AI Tool
免費脫衣圖片

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

用戶語音輸入通過前端JavaScript的MediaRecorderAPI捕獲並發送至PHP後端;2.PHP將音頻保存為臨時文件後調用STTAPI(如Google或百度語音識別)轉換為文本;3.PHP將文本發送至AI服務(如OpenAIGPT)獲取智能回复;4.PHP再調用TTSAPI(如百度或Google語音合成)將回復轉為語音文件;5.PHP將語音文件流式返回前端播放,完成交互。整個流程由PHP主導數據流轉與錯誤處理,確保各環節無縫銜接。

在PHP中搭建社交分享功能的核心方法是通過動態生成符合各平台要求的分享鏈接。 1.首先獲取當前頁面或指定的URL及文章信息;2.使用urlencode對參數進行編碼;3.根據各平台協議拼接生成分享鏈接;4.在前端展示鏈接供用戶點擊分享;5.動態生成頁面OG標籤優化分享內容展示;6.務必對用戶輸入進行轉義以防止XSS攻擊。該方法無需複雜認證,維護成本低,適用於大多數內容分享需求。

要實現PHP結合AI進行文本糾錯與語法優化,需按以下步驟操作:1.選擇適合的AI模型或API,如百度、騰訊API或開源NLP庫;2.通過PHP的curl或Guzzle調用API並處理返回結果;3.在應用中展示糾錯信息並允許用戶選擇是否採納;4.使用php-l和PHP_CodeSniffer進行語法檢測與代碼優化;5.持續收集反饋並更新模型或規則以提升效果。選擇AIAPI時應重點評估準確率、響應速度、價格及對PHP的支持。代碼優化應遵循PSR規範、合理使用緩存、避免循環查詢、定期審查代碼,並藉助X

1.評論系統商業價值最大化需結合原生廣告精準投放、用戶付費增值服務(如上傳圖片、評論置頂)、基於評論質量的影響力激勵機制及合規匿名數據洞察變現;2.審核策略應採用前置審核 動態關鍵詞過濾 用戶舉報機制組合,輔以評論質量評分實現內容分級曝光;3.防刷需構建多層防禦:reCAPTCHAv3無感驗證、Honeypot蜜罐字段識別機器人、IP與時間戳頻率限制阻止灌水、內容模式識別標記可疑評論,持續迭代應對攻擊。

PHP通過數據庫事務與FORUPDATE行鎖確保庫存扣減原子性,防止高並發超賣;2.多平台庫存一致性需依賴中心化管理與事件驅動同步,結合API/Webhook通知及消息隊列保障數據可靠傳遞;3.報警機制應分場景設置低庫存、零/負庫存、滯銷、補貨週期和異常波動策略,並按緊急程度選擇釘釘、短信或郵件通知責任人,且報警信息需完整明確,以實現業務適配與快速響應。

PHP不直接進行AI圖像處理,而是通過API集成,因為它擅長Web開發而非計算密集型任務,API集成能實現專業分工、降低成本、提升效率;2.整合關鍵技術包括使用Guzzle或cURL發送HTTP請求、JSON數據編解碼、API密鑰安全認證、異步隊列處理耗時任務、健壯錯誤處理與重試機制、圖像存儲與展示;3.常見挑戰有API成本失控、生成結果不可控、用戶體驗差、安全風險和數據管理難,應對策略分別為設置用戶配額與緩存、提供prompt指導與多圖選擇、異步通知與進度提示、密鑰環境變量存儲與內容審核、雲存

1.PHP開發問答社區首選Laravel MySQL Vue/React組合,因生態成熟、開發效率高;2.高性能需依賴緩存(Redis)、數據庫優化、CDN和異步隊列;3.安全性必須做好輸入過濾、CSRF防護、HTTPS、密碼加密及權限控制;4.變現可選廣告、會員訂閱、打賞、佣金、知識付費等模式,核心是匹配社區調性和用戶需求。

PHPisstillrelevantinmodernenterpriseenvironments.1.ModernPHP(7.xand8.x)offersperformancegains,stricttyping,JITcompilation,andmodernsyntax,makingitsuitableforlarge-scaleapplications.2.PHPintegrateseffectivelyinhybridarchitectures,servingasanAPIgateway
