From 53bff2f22bfe373cf8b04694be5790c4007bb184 Mon Sep 17 00:00:00 2001 From: King-sj Date: Wed, 6 Nov 2024 18:09:26 +0000 Subject: [PATCH] deploy: King-sj/KBlog@d4e7e5986fa6d73ebda4abc0c8d318abce877562 --- 404.html | 8 +- article/index.html | 23 +-- ...{0.html-BMihHxnS.js => 0.html-CC8GZdvL.js} | 2 +- ...{1.html-B2woGSgL.js => 1.html-C8e1pvTo.js} | 2 +- ...{2.html-DuLx7xSk.js => 2.html-CfN7D4gf.js} | 2 +- ...{3.html-CZB5joNY.js => 3.html-D9pPtuiP.js} | 2 +- ...{4.html-C8TuLMaF.js => 4.html-DavKkBaH.js} | 2 +- ....html-BC0rHQLu.js => 404.html-CGMXaJuv.js} | 2 +- ...{5.html-qLlLbRlw.js => 5.html-DpkUOZDe.js} | 2 +- ...{6.html-BPGbpgjm.js => 6.html-4dTDTOb4.js} | 2 +- ...{7.html-DopH3hey.js => 7.html-BzOHeKw-.js} | 2 +- ...{8.html-D1TnfZgI.js => 8.html-C-sA1tiy.js} | 2 +- ...{9.html-CGdd1xtP.js => 9.html-DOIgY3Qt.js} | 2 +- ...8.js => DesignPrinciples.html-BAjp9viD.js} | 2 +- ...5\256\211\350\243\205Qt5.html-DdU1Gd4z.js" | 2 +- assets/SearchResult-DAas8KH8.js | 1 + assets/SearchResult-DUP77o_D.js | 1 - ...qp.js => abstractFactory.html-D_oh5f2L.js} | 2 +- ...l-B2UlYWFL.js => adapter.html-x6kHt0VN.js} | 2 +- ...\346\215\242\346\272\220.html-DkRIM6mj.js" | 2 +- assets/{app-B69Gl_S-.js => app-B4LGNJZ0.js} | 142 +++++++++--------- assets/{arc-BCmLGSVu.js => arc-DWlpCdAw.js} | 2 +- ...2.js => blockDiagram-a8a0820c-DvOSxzpN.js} | 2 +- ...ml-LTDknqB7.js => bridge.html-DzkNbynT.js} | 2 +- ...l-Ba33pTyP.js => builder.html-B7MALN7e.js} | 2 +- ...sQW3.js => c4Diagram-dc8f04df-DWstOjwk.js} | 2 +- assets/carrobot.html-B7set7IG.js | 1 + ...=> chainOfResponsibility.html-CpbeYqOk.js} | 2 +- assets/channel-DJsAcM5I.js | 1 - assets/channel-yU22lAlC.js | 1 + ...html-CedraPRV.js => cicd.html-BxLHD3yn.js} | 2 +- ...u.js => classDiagram-8298e144-DKojaSxZ.js} | 2 +- ...s => classDiagram-v2-296fb1cd-DTrZkQpq.js} | 2 +- assets/clone-C3EvpqoY.js | 1 + assets/clone-CI3kFeqi.js | 1 - ...l-d4IhdjVF.js => command.html-Bzqk5uGA.js} | 2 +- ...DOBxYen3.js => composite.html-D89dw1yo.js} | 2 +- ...XUz.js => createText-4a4f35c9-CISzj231.js} | 2 +- ...FfT8RlFX.js => decorator.html-0jwtowLH.js} | 2 +- ...ml-B-hYGcsZ.js => docker.html-DtdutZB7.js} | 2 +- ...\351\241\271\347\233\256.html-CZONKQdf.js" | 2 +- ...B19hLEst.js => edges-5962ec63-ApWhby2b.js} | 2 +- ....html-De4ifiEs.js => end.html-CkqhlgwJ.js} | 2 +- ...-GEp.js => erDiagram-51d2b61e-D5StoYmd.js} | 2 +- ...ml-Cj3lyBcS.js => facade.html-BODpRH45.js} | 2 +- ...xz9.js => factory_method.html-NuMbQ7is.js} | 2 +- ...ME-hebA.js => figure_bed.html-vyc89ngd.js} | 2 +- ...3mEtQas.js => flowDb-0da60e67-XaXEQp9e.js} | 2 +- ...aS.js => flowDiagram-622f9fc1-BpT3eoaF.js} | 2 +- assets/flowDiagram-v2-a33b4996-BQrZXIe9.js | 1 + assets/flowDiagram-v2-a33b4996-BVubizbB.js | 1 - ...chart-elk-definition-930acc39-D2d6YT5I.js} | 2 +- ...CWx8TgTS.js => flyweight.html-DUTi0_Qx.js} | 2 +- ...C.js => ganttDiagram-a649f789-B7dFucT2.js} | 2 +- ...s => gitGraphDiagram-23af1bdb-Dd7cNN9F.js} | 2 +- ...\344\275\234\346\265\201.html-DJORqAx-.js" | 2 +- .../{graph-T5JS4vfK.js => graph-B1296pfd.js} | 2 +- assets/image-Dc9IOqtq.png | Bin 0 -> 196365 bytes ...B8uh0R2i.js => index-9620d214-BlfbO5wW.js} | 2 +- ...tml-CZDJXbd3.js => index.html-B1eL3fl0.js} | 2 +- ...tml-B7IKPS0E.js => index.html-BCqj2PVi.js} | 2 +- ...tml-0LHdE1Bw.js => index.html-BFj2MrdQ.js} | 2 +- ...tml-BlxatHgv.js => index.html-BJX9B5Ij.js} | 2 +- ...tml-Bb8RnxYT.js => index.html-BOaWfYA8.js} | 2 +- ...tml-DRlrhC7d.js => index.html-BgifGsNR.js} | 2 +- assets/index.html-BjAw7f-F.js | 1 - assets/index.html-BjDh3PHW.js | 1 - ...tml-C8X5E5_U.js => index.html-BjeRhPA4.js} | 2 +- ...tml-CXhg3e64.js => index.html-BlsqD0oa.js} | 2 +- assets/index.html-Bq7J9cN0.js | 1 + ...tml-CLOegCHC.js => index.html-BwXv95cV.js} | 2 +- ...tml-BtJnl_OK.js => index.html-C9AeqWIc.js} | 2 +- ...tml-BK-iNYKv.js => index.html-CE6ffKsf.js} | 2 +- ...tml-ZxSgoRoh.js => index.html-CL479cux.js} | 2 +- ...tml-B3D_5fpm.js => index.html-COyA9Sec.js} | 2 +- ...tml-DDeLtmuh.js => index.html-CVNN5Fwq.js} | 2 +- ...tml-DzsdPiFk.js => index.html-CYOJa5u_.js} | 2 +- assets/index.html-CdwZ1P48.js | 1 + ...tml-Dakgazin.js => index.html-CqYdKjYM.js} | 2 +- ...tml-B2T3kUK3.js => index.html-CryaZFG_.js} | 2 +- ...tml-BZLD09T1.js => index.html-Cw9y1ZOg.js} | 2 +- ...tml-DDn6tClc.js => index.html-CzSwk-A5.js} | 2 +- ...tml-BKjCcFT0.js => index.html-D8p9fsJ8.js} | 2 +- ...tml-VMxXqaQ8.js => index.html-DCZKDe-x.js} | 2 +- ...tml-DeH2SCB3.js => index.html-DERlmHfl.js} | 2 +- ...tml-BXtqeyHq.js => index.html-DOXbY5aY.js} | 2 +- ...tml-B71I-yvI.js => index.html-DOZrz5tX.js} | 2 +- ...tml-DR-PJoSN.js => index.html-DW1c3ODz.js} | 2 +- ...tml-CcgdDCiF.js => index.html-DYx5t_sy.js} | 2 +- ...tml-D2pTqZFy.js => index.html-DeNCk-ye.js} | 2 +- ...tml-BSJr7qwh.js => index.html-DhCS_0vf.js} | 2 +- ...tml-B5GIg4Ij.js => index.html-Dj_dWWjR.js} | 2 +- ...tml-BHNGjpbb.js => index.html-DklcXGYl.js} | 2 +- ...tml-Dh_a7h_3.js => index.html-DottIbHL.js} | 2 +- ...tml-CGsa9ffM.js => index.html-Dpy8imS-.js} | 2 +- ...tml-Dv6rE_pN.js => index.html-DtPJaw_H.js} | 2 +- ...tml-BLuB_Eoc.js => index.html-DwPUACaS.js} | 2 +- assets/index.html-DzJoMr4P.js | 1 - ...tml-ayClYbZ3.js => index.html-F61LMFJS.js} | 2 +- ...tml-CRzs15I0.js => index.html-JQfnAIea.js} | 2 +- ...tml-kOhwCTdr.js => index.html-NUfXNLwU.js} | 2 +- ...tml-BvZSJ2r-.js => index.html-SMm58lXr.js} | 2 +- ...tml-Ak5uwHrg.js => index.html-SwHQd6yQ.js} | 2 +- ...tml-CO8-8uSt.js => index.html-TRJ6qSMT.js} | 2 +- ...tml-UrkKwESw.js => index.html-ZzlVKqB8.js} | 2 +- ...tml-BNAyU6dK.js => index.html-auYNF0Cp.js} | 2 +- ...tml-DZYdDCPP.js => index.html-hPg6lxSo.js} | 2 +- ...tml-QNuKjuoZ.js => index.html-i5A_pLCb.js} | 2 +- assets/index.html-jwKIpsPi.js | 1 + ...tml-bipylPNF.js => index.html-mplfcIL_.js} | 2 +- ...tml-CSRSsNOG.js => index.html-rivgPJ9m.js} | 2 +- ...tml-BpjUVtkj.js => index.html-y8ELyp0v.js} | 2 +- ...tml-9q-r5JHT.js => index.html-yUxPRBBH.js} | 2 +- assets/index.html-ybZqFA4D.js | 1 + ...He.js => infoDiagram-f86e0131-BvySRD9D.js} | 2 +- ...nsycaD.js => interpreter.html-DfDQyPSV.js} | 2 +- ...tml-CEHYbhC9.js => intro.html-DcelwrZW.js} | 2 +- ...-USsHv5Pr.js => iterator.html-9EjNXcsT.js} | 2 +- ...js => journeyDiagram-de0d04b7-bCIGoTun.js} | 2 +- ...{layout-DUYLy6Ue.js => layout-qMafh1ee.js} | 2 +- assets/{line-D0jG_HZ-.js => line-BqHmcuby.js} | 2 +- ...{linear-CRQH6hcv.js => linear-Cb1OAgs3.js} | 2 +- ...ITZCknd.js => mailserver.html-DO5Xbwgi.js} | 2 +- ...-RBFBa63Z.js => mediator.html-C5vte2nl.js} | 2 +- ...l-CbbBldep.js => memento.html-Dw4vFzUb.js} | 2 +- ...e-ChLYKTZm.js => mermaid.core-DNo_7Q2B.js} | 8 +- ...> mindmap-definition-b08d3ac4-CtqZ8Vgk.js} | 2 +- ...ml-M2GwLZ2U.js => modint.html-DNrISIPe.js} | 2 +- ...-BvrPoU9w.js => observer.html-DCoBW7Y-.js} | 2 +- ...fQ6.js => pieDiagram-31d824a1-BIcjeA1S.js} | 2 +- ...Boe-0El2.js => prototype.html-Be3THUal.js} | 2 +- ...tml-CSHBH0Xk.js => proxy.html-DydmfTHY.js} | 2 +- ...js => python_requrements.html-D_iboyN4.js} | 2 +- ...s => quadrantDiagram-a712a7d9-CNklQ4II.js} | 2 +- ...> requirementDiagram-eb878fee-OFUpKWxv.js} | 2 +- ....js => sankeyDiagram-c599f8ec-Ss3oz1oB.js} | 2 +- ...s => sequenceDiagram-66748113-sH_v1_cD.js} | 2 +- ...Pzh1ZzrO.js => singleton.html-CJylCsqx.js} | 2 +- ...tml-DsXXtCU9.js => state.html-D0BJywlY.js} | 2 +- ...X.js => stateDiagram-35d39939-D7FEhrYs.js} | 2 +- ...s => stateDiagram-v2-6c43c13a-C7zU9Dfv.js} | 2 +- ...\345\274\200\345\217\221.html-BnsNbJlM.js" | 2 +- ...-Cprd53d9.js => strategy.html-BeQd57Ga.js} | 2 +- ...tml-CXxr9xzn.js => style.html-4qtN5eOW.js} | 2 +- ...XxGUMW8.js => styles-422bd1c9-CFsDtXUv.js} | 2 +- ...jk-EQ8n.js => styles-de3becd0-D7QN2-IT.js} | 2 +- ...YWOaoPC.js => styles-e2b9d258-CoUUuHq5.js} | 2 +- ....js => svgDrawCommon-9d162435-DBc9TLjw.js} | 2 +- ...Sl.js => template_method.html-B7oRZ-kS.js} | 2 +- ...\350\270\251\345\235\221.html-CtBHw7dX.js" | 2 +- ... timeline-definition-6eaa3543-CYqJxmAu.js} | 2 +- ...\346\277\200\346\264\273.html-DUOLfN6Y.js" | 2 +- ...l-C6V612X1.js => visitor.html-DaIekGHb.js} | 2 +- ...\345\260\217\350\256\260.html-BytDpa8J.js" | 2 +- ...js => xychartDiagram-4a8b8217-DS1INfpk.js} | 2 +- ...\347\204\266\346\225\260.html-NHamcIiY.js" | 2 +- ...10\260mobile\347\253\257.html-CKHKQibh.js" | 2 +- ...\345\212\240\351\224\201.html-gfcHinKw.js" | 2 +- ...\345\215\217\347\250\213.html-BovqfV_l.js" | 2 +- ...\351\241\273\346\234\211.html-B46pNP68.js" | 2 +- ...5\221\230\347\232\204PPT.html-D4bbFarZ.js" | 2 +- ...\347\216\260\347\261\273.html-CeWxsqOK.js" | 2 +- category/build-tools/index.html | 8 +- category/cicd/index.html | 8 +- category/docker/index.html | 8 +- category/index.html | 8 +- category/nginx/index.html | 8 +- category/oi/index.html | 8 +- category/python/index.html | 8 +- .../index.html" | 8 +- .../\346\225\260\345\255\246/index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../\351\243\216\346\240\274/index.html" | 8 +- essays/index.html | 8 +- ...\347\273\210\351\241\273\346\234\211.html" | 6 +- index.html | 23 +-- intro.html | 6 +- projects/carrobot.html | 40 +++++ projects/index.html | 8 +- resources/compile/index.html | 40 +++++ resources/html2md/index.html | 8 +- resources/index.html | 8 +- search-pro.worker.js | 2 +- sitemap.xml | 2 +- star/index.html | 8 +- tag/c/index.html | 8 +- tag/cicd/index.html | 8 +- tag/docker/index.html | 8 +- tag/index.html | 8 +- tag/mobile/index.html | 8 +- tag/modint/index.html | 8 +- tag/typescript/index.html | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- "tag/\346\277\200\346\264\273/index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- "tag/\351\203\250\347\275\262/index.html" | 8 +- "tag/\351\243\216\346\240\274/index.html" | 8 +- tech/DesignPatterns/DesignPrinciples.html | 6 +- tech/DesignPatterns/abstractFactory.html | 6 +- tech/DesignPatterns/adapter.html | 6 +- tech/DesignPatterns/bridge.html | 6 +- tech/DesignPatterns/builder.html | 6 +- .../DesignPatterns/chainOfResponsibility.html | 6 +- tech/DesignPatterns/command.html | 6 +- tech/DesignPatterns/composite.html | 6 +- tech/DesignPatterns/decorator.html | 6 +- tech/DesignPatterns/end.html | 6 +- tech/DesignPatterns/facade.html | 6 +- tech/DesignPatterns/factory_method.html | 6 +- tech/DesignPatterns/flyweight.html | 6 +- tech/DesignPatterns/index.html | 6 +- tech/DesignPatterns/interpreter.html | 6 +- tech/DesignPatterns/iterator.html | 6 +- tech/DesignPatterns/mediator.html | 6 +- tech/DesignPatterns/memento.html | 6 +- tech/DesignPatterns/observer.html | 6 +- tech/DesignPatterns/prototype.html | 6 +- tech/DesignPatterns/proxy.html | 6 +- tech/DesignPatterns/singleton.html | 6 +- tech/DesignPatterns/state.html | 6 +- tech/DesignPatterns/strategy.html | 6 +- tech/DesignPatterns/template_method.html | 6 +- tech/DesignPatterns/visitor.html | 6 +- ...0\203\275\345\256\211\350\243\205Qt5.html" | 6 +- ...droid studio\346\215\242\346\272\220.html" | 6 +- tech/cicd.html | 6 +- tech/designASimpileCCompiler/0.html | 6 +- tech/designASimpileCCompiler/1.html | 6 +- tech/designASimpileCCompiler/2.html | 6 +- tech/designASimpileCCompiler/3.html | 6 +- tech/designASimpileCCompiler/4.html | 6 +- tech/designASimpileCCompiler/5.html | 6 +- tech/designASimpileCCompiler/6.html | 6 +- tech/designASimpileCCompiler/7.html | 6 +- tech/designASimpileCCompiler/8.html | 6 +- tech/designASimpileCCompiler/9.html | 6 +- tech/designASimpileCCompiler/index.html | 6 +- tech/docker.html | 6 +- ...7\275\262web\351\241\271\347\233\256.html" | 6 +- tech/figure_bed.html | 6 +- ...\345\267\245\344\275\234\346\265\201.html" | 6 +- tech/index.html | 6 +- tech/mailserver.html | 6 +- tech/modint.html | 6 +- tech/python_requrements.html | 6 +- ...\347\273\204\345\274\200\345\217\221.html" | 6 +- tech/style.html | 6 +- .../tensorflow\350\270\251\345\235\221.html" | 6 +- ...\345\217\221\345\260\217\350\256\260.html" | 6 +- ...\350\207\252\347\204\266\346\225\260.html" | 6 +- ...47\273\345\210\260mobile\347\253\257.html" | 6 +- ...\350\246\201\345\212\240\351\224\201.html" | 6 +- "tech/\345\215\217\347\250\213.html" | 6 +- ...5\272\217\345\221\230\347\232\204PPT.html" | 6 +- ...\345\256\236\347\216\260\347\261\273.html" | 6 +- timeline/index.html | 8 +- tutorials/index.html | 6 +- .../typora\346\277\200\346\264\273.html" | 6 +- 274 files changed, 707 insertions(+), 611 deletions(-) rename assets/{0.html-BMihHxnS.js => 0.html-CC8GZdvL.js} (99%) rename assets/{1.html-B2woGSgL.js => 1.html-C8e1pvTo.js} (99%) rename assets/{2.html-DuLx7xSk.js => 2.html-CfN7D4gf.js} (99%) rename assets/{3.html-CZB5joNY.js => 3.html-D9pPtuiP.js} (99%) rename assets/{4.html-C8TuLMaF.js => 4.html-DavKkBaH.js} (99%) rename assets/{404.html-BC0rHQLu.js => 404.html-CGMXaJuv.js} (93%) rename assets/{5.html-qLlLbRlw.js => 5.html-DpkUOZDe.js} (99%) rename assets/{6.html-BPGbpgjm.js => 6.html-4dTDTOb4.js} (99%) rename assets/{7.html-DopH3hey.js => 7.html-BzOHeKw-.js} (99%) rename assets/{8.html-D1TnfZgI.js => 8.html-C-sA1tiy.js} (99%) rename assets/{9.html-CGdd1xtP.js => 9.html-DOIgY3Qt.js} (99%) rename assets/{DesignPrinciples.html-BLnXHGN8.js => DesignPrinciples.html-BAjp9viD.js} (99%) rename "assets/Qt6 Maintence tools\344\270\215\350\203\275\345\256\211\350\243\205Qt5.html-CTk8exni.js" => "assets/Qt6 Maintence tools\344\270\215\350\203\275\345\256\211\350\243\205Qt5.html-DdU1Gd4z.js" (97%) create mode 100644 assets/SearchResult-DAas8KH8.js delete mode 100644 assets/SearchResult-DUP77o_D.js rename assets/{abstractFactory.html-DS3CgVqp.js => abstractFactory.html-D_oh5f2L.js} (99%) rename assets/{adapter.html-B2UlYWFL.js => adapter.html-x6kHt0VN.js} (99%) rename "assets/android studio\346\215\242\346\272\220.html-CxjDu5df.js" => "assets/android studio\346\215\242\346\272\220.html-DkRIM6mj.js" (98%) rename assets/{app-B69Gl_S-.js => app-B4LGNJZ0.js} (63%) rename assets/{arc-BCmLGSVu.js => arc-DWlpCdAw.js} (96%) rename assets/{blockDiagram-a8a0820c-1CGnBiz2.js => blockDiagram-a8a0820c-DvOSxzpN.js} (98%) rename assets/{bridge.html-LTDknqB7.js => bridge.html-DzkNbynT.js} (99%) rename assets/{builder.html-Ba33pTyP.js => builder.html-B7MALN7e.js} (99%) rename assets/{c4Diagram-dc8f04df-CiA8sQW3.js => c4Diagram-dc8f04df-DWstOjwk.js} (99%) create mode 100644 assets/carrobot.html-B7set7IG.js rename assets/{chainOfResponsibility.html-DhJtx4gW.js => chainOfResponsibility.html-CpbeYqOk.js} (99%) delete mode 100644 assets/channel-DJsAcM5I.js create mode 100644 assets/channel-yU22lAlC.js rename assets/{cicd.html-CedraPRV.js => cicd.html-BxLHD3yn.js} (98%) rename assets/{classDiagram-8298e144-w121JOWu.js => classDiagram-8298e144-DKojaSxZ.js} (97%) rename assets/{classDiagram-v2-296fb1cd-BmyNy9b3.js => classDiagram-v2-296fb1cd-DTrZkQpq.js} (92%) create mode 100644 assets/clone-C3EvpqoY.js delete mode 100644 assets/clone-CI3kFeqi.js rename assets/{command.html-d4IhdjVF.js => command.html-Bzqk5uGA.js} (99%) rename assets/{composite.html-DOBxYen3.js => composite.html-D89dw1yo.js} (99%) rename assets/{createText-4a4f35c9-BuT3nXUz.js => createText-4a4f35c9-CISzj231.js} (99%) rename assets/{decorator.html-FfT8RlFX.js => decorator.html-0jwtowLH.js} (99%) rename assets/{docker.html-B-hYGcsZ.js => docker.html-DtdutZB7.js} (99%) rename "assets/docker_nginx\351\203\250\347\275\262web\351\241\271\347\233\256.html-4IxhKq9E.js" => "assets/docker_nginx\351\203\250\347\275\262web\351\241\271\347\233\256.html-CZONKQdf.js" (99%) rename assets/{edges-5962ec63-B19hLEst.js => edges-5962ec63-ApWhby2b.js} (99%) rename assets/{end.html-De4ifiEs.js => end.html-CkqhlgwJ.js} (97%) rename assets/{erDiagram-51d2b61e-BxYe-GEp.js => erDiagram-51d2b61e-D5StoYmd.js} (99%) rename assets/{facade.html-Cj3lyBcS.js => facade.html-BODpRH45.js} (99%) rename assets/{factory_method.html-B20phxz9.js => factory_method.html-NuMbQ7is.js} (99%) rename assets/{figure_bed.html-BME-hebA.js => figure_bed.html-vyc89ngd.js} (96%) rename assets/{flowDb-0da60e67-C3mEtQas.js => flowDb-0da60e67-XaXEQp9e.js} (99%) rename assets/{flowDiagram-622f9fc1-C8iBq7aS.js => flowDiagram-622f9fc1-BpT3eoaF.js} (97%) create mode 100644 assets/flowDiagram-v2-a33b4996-BQrZXIe9.js delete mode 100644 assets/flowDiagram-v2-a33b4996-BVubizbB.js rename assets/{flowchart-elk-definition-930acc39-Q-ihdrlR.js => flowchart-elk-definition-930acc39-D2d6YT5I.js} (99%) rename assets/{flyweight.html-CWx8TgTS.js => flyweight.html-DUTi0_Qx.js} (99%) rename assets/{ganttDiagram-a649f789-B8bnTgqC.js => ganttDiagram-a649f789-B7dFucT2.js} (99%) rename assets/{gitGraphDiagram-23af1bdb-BuC3CTB9.js => gitGraphDiagram-23af1bdb-Dd7cNN9F.js} (99%) rename "assets/github \345\267\245\344\275\234\346\265\201.html-CFTCrRk5.js" => "assets/github \345\267\245\344\275\234\346\265\201.html-DJORqAx-.js" (97%) rename assets/{graph-T5JS4vfK.js => graph-B1296pfd.js} (99%) create mode 100644 assets/image-Dc9IOqtq.png rename assets/{index-9620d214-B8uh0R2i.js => index-9620d214-BlfbO5wW.js} (96%) rename assets/{index.html-CZDJXbd3.js => index.html-B1eL3fl0.js} (94%) rename assets/{index.html-B7IKPS0E.js => index.html-BCqj2PVi.js} (94%) rename assets/{index.html-0LHdE1Bw.js => index.html-BFj2MrdQ.js} (94%) rename assets/{index.html-BlxatHgv.js => index.html-BJX9B5Ij.js} (94%) rename assets/{index.html-Bb8RnxYT.js => index.html-BOaWfYA8.js} (93%) rename assets/{index.html-DRlrhC7d.js => index.html-BgifGsNR.js} (94%) delete mode 100644 assets/index.html-BjAw7f-F.js delete mode 100644 assets/index.html-BjDh3PHW.js rename assets/{index.html-C8X5E5_U.js => index.html-BjeRhPA4.js} (94%) rename assets/{index.html-CXhg3e64.js => index.html-BlsqD0oa.js} (96%) create mode 100644 assets/index.html-Bq7J9cN0.js rename assets/{index.html-CLOegCHC.js => index.html-BwXv95cV.js} (94%) rename assets/{index.html-BtJnl_OK.js => index.html-C9AeqWIc.js} (96%) rename assets/{index.html-BK-iNYKv.js => index.html-CE6ffKsf.js} (93%) rename assets/{index.html-ZxSgoRoh.js => index.html-CL479cux.js} (94%) rename assets/{index.html-B3D_5fpm.js => index.html-COyA9Sec.js} (94%) rename assets/{index.html-DDeLtmuh.js => index.html-CVNN5Fwq.js} (93%) rename assets/{index.html-DzsdPiFk.js => index.html-CYOJa5u_.js} (94%) create mode 100644 assets/index.html-CdwZ1P48.js rename assets/{index.html-Dakgazin.js => index.html-CqYdKjYM.js} (93%) rename assets/{index.html-B2T3kUK3.js => index.html-CryaZFG_.js} (95%) rename assets/{index.html-BZLD09T1.js => index.html-Cw9y1ZOg.js} (94%) rename assets/{index.html-DDn6tClc.js => index.html-CzSwk-A5.js} (94%) rename assets/{index.html-BKjCcFT0.js => index.html-D8p9fsJ8.js} (93%) rename assets/{index.html-VMxXqaQ8.js => index.html-DCZKDe-x.js} (94%) rename assets/{index.html-DeH2SCB3.js => index.html-DERlmHfl.js} (93%) rename assets/{index.html-BXtqeyHq.js => index.html-DOXbY5aY.js} (93%) rename assets/{index.html-B71I-yvI.js => index.html-DOZrz5tX.js} (94%) rename assets/{index.html-DR-PJoSN.js => index.html-DW1c3ODz.js} (93%) rename assets/{index.html-CcgdDCiF.js => index.html-DYx5t_sy.js} (94%) rename assets/{index.html-D2pTqZFy.js => index.html-DeNCk-ye.js} (93%) rename assets/{index.html-BSJr7qwh.js => index.html-DhCS_0vf.js} (94%) rename assets/{index.html-B5GIg4Ij.js => index.html-Dj_dWWjR.js} (95%) rename assets/{index.html-BHNGjpbb.js => index.html-DklcXGYl.js} (95%) rename assets/{index.html-Dh_a7h_3.js => index.html-DottIbHL.js} (93%) rename assets/{index.html-CGsa9ffM.js => index.html-Dpy8imS-.js} (94%) rename assets/{index.html-Dv6rE_pN.js => index.html-DtPJaw_H.js} (98%) rename assets/{index.html-BLuB_Eoc.js => index.html-DwPUACaS.js} (94%) delete mode 100644 assets/index.html-DzJoMr4P.js rename assets/{index.html-ayClYbZ3.js => index.html-F61LMFJS.js} (94%) rename assets/{index.html-CRzs15I0.js => index.html-JQfnAIea.js} (94%) rename assets/{index.html-kOhwCTdr.js => index.html-NUfXNLwU.js} (94%) rename assets/{index.html-BvZSJ2r-.js => index.html-SMm58lXr.js} (93%) rename assets/{index.html-Ak5uwHrg.js => index.html-SwHQd6yQ.js} (94%) rename assets/{index.html-CO8-8uSt.js => index.html-TRJ6qSMT.js} (93%) rename assets/{index.html-UrkKwESw.js => index.html-ZzlVKqB8.js} (94%) rename assets/{index.html-BNAyU6dK.js => index.html-auYNF0Cp.js} (94%) rename assets/{index.html-DZYdDCPP.js => index.html-hPg6lxSo.js} (94%) rename assets/{index.html-QNuKjuoZ.js => index.html-i5A_pLCb.js} (94%) create mode 100644 assets/index.html-jwKIpsPi.js rename assets/{index.html-bipylPNF.js => index.html-mplfcIL_.js} (93%) rename assets/{index.html-CSRSsNOG.js => index.html-rivgPJ9m.js} (94%) rename assets/{index.html-BpjUVtkj.js => index.html-y8ELyp0v.js} (93%) rename assets/{index.html-9q-r5JHT.js => index.html-yUxPRBBH.js} (94%) create mode 100644 assets/index.html-ybZqFA4D.js rename assets/{infoDiagram-f86e0131-BJ_tgdHe.js => infoDiagram-f86e0131-BvySRD9D.js} (98%) rename assets/{interpreter.html-CansycaD.js => interpreter.html-DfDQyPSV.js} (99%) rename assets/{intro.html-CEHYbhC9.js => intro.html-DcelwrZW.js} (97%) rename assets/{iterator.html-USsHv5Pr.js => iterator.html-9EjNXcsT.js} (99%) rename assets/{journeyDiagram-de0d04b7-DXj0d_vq.js => journeyDiagram-de0d04b7-bCIGoTun.js} (98%) rename assets/{layout-DUYLy6Ue.js => layout-qMafh1ee.js} (99%) rename assets/{line-D0jG_HZ-.js => line-BqHmcuby.js} (93%) rename assets/{linear-CRQH6hcv.js => linear-Cb1OAgs3.js} (99%) rename assets/{mailserver.html-DITZCknd.js => mailserver.html-DO5Xbwgi.js} (99%) rename assets/{mediator.html-RBFBa63Z.js => mediator.html-C5vte2nl.js} (99%) rename assets/{memento.html-CbbBldep.js => memento.html-Dw4vFzUb.js} (99%) rename assets/{mermaid.core-ChLYKTZm.js => mermaid.core-DNo_7Q2B.js} (98%) rename assets/{mindmap-definition-b08d3ac4-DYSC8IDG.js => mindmap-definition-b08d3ac4-CtqZ8Vgk.js} (99%) rename assets/{modint.html-M2GwLZ2U.js => modint.html-DNrISIPe.js} (99%) rename assets/{observer.html-BvrPoU9w.js => observer.html-DCoBW7Y-.js} (99%) rename assets/{pieDiagram-31d824a1-CV6f6fQ6.js => pieDiagram-31d824a1-BIcjeA1S.js} (98%) rename assets/{prototype.html-Boe-0El2.js => prototype.html-Be3THUal.js} (99%) rename assets/{proxy.html-CSHBH0Xk.js => proxy.html-DydmfTHY.js} (99%) rename assets/{python_requrements.html-Va55tBYB.js => python_requrements.html-D_iboyN4.js} (97%) rename assets/{quadrantDiagram-a712a7d9-Bu83u7Zz.js => quadrantDiagram-a712a7d9-CNklQ4II.js} (99%) rename assets/{requirementDiagram-eb878fee-DPiNb9b5.js => requirementDiagram-eb878fee-OFUpKWxv.js} (98%) rename assets/{sankeyDiagram-c599f8ec-BGnWqC27.js => sankeyDiagram-c599f8ec-Ss3oz1oB.js} (99%) rename assets/{sequenceDiagram-66748113-MIUFy2Oj.js => sequenceDiagram-66748113-sH_v1_cD.js} (99%) rename assets/{singleton.html-Pzh1ZzrO.js => singleton.html-CJylCsqx.js} (99%) rename assets/{state.html-DsXXtCU9.js => state.html-D0BJywlY.js} (99%) rename assets/{stateDiagram-35d39939-BlCBJSJX.js => stateDiagram-35d39939-D7FEhrYs.js} (97%) rename assets/{stateDiagram-v2-6c43c13a-CtOdsXqX.js => stateDiagram-v2-6c43c13a-C7zU9Dfv.js} (90%) rename "assets/steam\346\250\241\347\273\204\345\274\200\345\217\221.html-DQKsmuFu.js" => "assets/steam\346\250\241\347\273\204\345\274\200\345\217\221.html-BnsNbJlM.js" (98%) rename assets/{strategy.html-Cprd53d9.js => strategy.html-BeQd57Ga.js} (99%) rename assets/{style.html-CXxr9xzn.js => style.html-4qtN5eOW.js} (98%) rename assets/{styles-422bd1c9-CXxGUMW8.js => styles-422bd1c9-CFsDtXUv.js} (98%) rename assets/{styles-de3becd0-Bjk-EQ8n.js => styles-de3becd0-D7QN2-IT.js} (99%) rename assets/{styles-e2b9d258-TYWOaoPC.js => styles-e2b9d258-CoUUuHq5.js} (99%) rename assets/{svgDrawCommon-9d162435-DXbPg0E8.js => svgDrawCommon-9d162435-DBc9TLjw.js} (95%) rename assets/{template_method.html-Do5aF6Sl.js => template_method.html-B7oRZ-kS.js} (99%) rename "assets/tensorflow\350\270\251\345\235\221.html-BAaphJ4H.js" => "assets/tensorflow\350\270\251\345\235\221.html-CtBHw7dX.js" (97%) rename assets/{timeline-definition-6eaa3543-BApSUGyX.js => timeline-definition-6eaa3543-CYqJxmAu.js} (99%) rename "assets/typora\346\277\200\346\264\273.html-BuwKOD8g.js" => "assets/typora\346\277\200\346\264\273.html-DUOLfN6Y.js" (98%) rename assets/{visitor.html-C6V612X1.js => visitor.html-DaIekGHb.js} (99%) rename "assets/vsocde\346\217\222\344\273\266\345\274\200\345\217\221\345\260\217\350\256\260.html-DtdCKw5q.js" => "assets/vsocde\346\217\222\344\273\266\345\274\200\345\217\221\345\260\217\350\256\260.html-BytDpa8J.js" (97%) rename assets/{xychartDiagram-4a8b8217-CYu3f7i9.js => xychartDiagram-4a8b8217-DS1INfpk.js} (99%) rename "assets/\343\200\214\347\256\227\346\234\257\345\205\254\347\220\206\347\263\273\347\273\237 1\343\200\215\350\207\252\347\204\266\346\225\260.html-CtzEDcR8.js" => "assets/\343\200\214\347\256\227\346\234\257\345\205\254\347\220\206\347\263\273\347\273\237 1\343\200\215\350\207\252\347\204\266\346\225\260.html-NHamcIiY.js" (99%) rename "assets/\344\275\277\347\224\250capacitor\345\222\214ionic\345\260\206vue\351\241\271\347\233\256\350\277\201\347\247\273\345\210\260mobile\347\253\257.html-CjY4pFX9.js" => "assets/\344\275\277\347\224\250capacitor\345\222\214ionic\345\260\206vue\351\241\271\347\233\256\350\277\201\347\247\273\345\210\260mobile\347\253\257.html-CKHKQibh.js" (98%) rename "assets/\345\215\217\347\250\213(asyncio)\345\210\260\345\272\225\351\234\200\344\270\215\351\234\200\350\246\201\345\212\240\351\224\201.html-CTO2grll.js" => "assets/\345\215\217\347\250\213(asyncio)\345\210\260\345\272\225\351\234\200\344\270\215\351\234\200\350\246\201\345\212\240\351\224\201.html-gfcHinKw.js" (99%) rename "assets/\345\215\217\347\250\213.html-LdIMvjSl.js" => "assets/\345\215\217\347\250\213.html-BovqfV_l.js" (93%) rename "assets/\345\221\275\351\207\214\346\234\211\346\227\266\347\273\210\351\241\273\346\234\211.html-DOvT7-xg.js" => "assets/\345\221\275\351\207\214\346\234\211\346\227\266\347\273\210\351\241\273\346\234\211.html-B46pNP68.js" (97%) rename "assets/\347\250\213\345\272\217\345\221\230\347\232\204PPT.html-CmGk8DAJ.js" => "assets/\347\250\213\345\272\217\345\221\230\347\232\204PPT.html-D4bbFarZ.js" (98%) rename "assets/\351\227\255\345\214\205\345\256\236\347\216\260\347\261\273.html-DjTAmLiV.js" => "assets/\351\227\255\345\214\205\345\256\236\347\216\260\347\261\273.html-CeWxsqOK.js" (99%) create mode 100644 projects/carrobot.html create mode 100644 resources/compile/index.html diff --git a/404.html b/404.html index 53300cf..7b45a30 100644 --- a/404.html +++ b/404.html @@ -30,11 +30,11 @@ blog - - + + -
跳至主要內容

404

页面不存在

我们是怎么来到这儿的?

- +
跳至主要內容

404

页面不存在

这 是 四 零 四 !

+ diff --git a/article/index.html b/article/index.html index 7665a35..4314d62 100644 --- a/article/index.html +++ b/article/index.html @@ -30,11 +30,19 @@ 文章 | blog - - + + -
跳至主要內容
github 图床

参考

+
跳至主要內容
github 图床

参考

  • https://zhuanlan.zhihu.com/p/347342082
@@ -64,12 +72,7 @@

示例代码

  • 简化语法解析:通过定义文法规则,可以轻松解析和执行特定的语言或指令集。
  • 可扩展性:可以轻松添加新的语法规则或指令,而无需修改现有代码。
  • 代码复用:通过将不同的语法规则封装在不同的类中,可以提高代码的复用性和可维护性。
  • -

    KSJ大约 3 分钟设计模式设计模式TypeScript用类实现
    Proxy 模式

    为什么使用代理模式

    -

    在面向对象编程中,“本人”和“代理人”都是对象。如果“本人”对象太忙了.有些工作无法自己亲自完成,就将其交给“代理人"对象负责。

    -

    KSJ大约 5 分钟设计模式设计模式TypeScript避免浪费
    Flyweight 模式

    为什么使用 Flyweight 模式

    -

    Flyweight 模式是一种结构型设计模式,它通过共享尽可能多的相同对象来减少内存使用,从而提高性能。在需要生成大量细粒度对象的场景中,Flyweight 模式非常有用。通过共享对象,Flyweight 模式可以显著减少内存消耗,并提高应用程序的效率。

    -

    实例代码

    -

    KSJ大约 8 分钟设计模式设计模式TypeScript避免浪费
    - +

    KSJ大约 3 分钟设计模式设计模式TypeScript用类实现
    + diff --git a/assets/0.html-BMihHxnS.js b/assets/0.html-CC8GZdvL.js similarity index 99% rename from assets/0.html-BMihHxnS.js rename to assets/0.html-CC8GZdvL.js index 94991b6..13be91d 100644 --- a/assets/0.html-BMihHxnS.js +++ b/assets/0.html-CC8GZdvL.js @@ -1 +1 @@ -import{_ as l,r as a,o as i,c as p,b as e,e as t,d as n,f as o}from"./app-B69Gl_S-.js";const s={},h=e("h1",{id:"转载声明",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#转载声明"},[e("span",null,"转载声明")])],-1),c=e("strong",null,"手把手教你构建 C 语言编译器(0)- 前言",-1),d=e("strong",null,"三点水",-1),_={href:"https://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},u={href:"https://devtool.tech/html-md",target:"_blank",rel:"noopener noreferrer"},f={href:"https://www.helloworld.net/html2md",target:"_blank",rel:"noopener noreferrer"},m=e("h1",{id:"原文内容",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#原文内容"},[e("span",null,"原文内容")])],-1),g=e("p",null,"“手把手教你构建 C 语言编译器” 这一系列教程将带你从头编写一个 C 语言的编译器。希望通过这个系列,我们能对编译器的构建有一定的了解,同时,我们也将构建出一个能用的 C 语言编译器,尽管有许多语法并不支持。",-1),C=e("p",null,"手把手教你构建 C 语言编译器系列共有10个部分:",-1),b={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},w={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},B={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},x={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},v={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},L={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},D=o('

    在开始进入正题之前,本篇是一些闲聊,谈谈这个系列的初衷。如果你急切地想进入正篇,请跳过本章。

    为什么要学编译原理

    如果要我说计算机专业最重要的三门课,我会说是《数据结构》、《算法》和《编译原理》。在我看来,能不能理解“递归”像是程序员的第一道门槛,而会不会写编译器则是第二道。

    (当然,并不是说是没写过编译器就不是好程序员,只能说它是一个相当大的挑战吧)

    以前人们会说,学习了编译原理,你就能写出更加高效的代码,但随着计算机性能的提升,代码是否高效显得就不那么重要了。那么为什么要学习编译原理呢?

    原因只有一个:装B。

    好吧,也许现在还想学习编译原理的人只可能是因为兴趣了。一方面想了解它的工作原理;另一方面希望挑战一下自己,看看自己能走多远。

    理论很复杂,实现也很复杂?

    我对编译器一直心存敬佩。所以当学校开《编译原理》的课程后,我是抱着满腔热情去上课的,但是两节课后我就放弃了。原因是太复杂了,听不懂。

    一般编译原理的课程会说一些:

    1. 如何表示语法(BNF什么的)
    2. 词法分析,用什么有穷自动机和无穷自动机
    3. 语法分析,递归下降法,什么 LL(k),LALR 分析。
    4. 中间代码的表示
    5. 代码的生成
    6. 代码优化

    我相信绝大多数(98%)的学生顶多学到语法分析就结束了。并且最重要的是,学了这么多也没用!依旧帮助不了我们学习编译器!这其中最主要的原因是《编译原理》试图教会我们的是如何构造“编译器生成器”,即构造一个工具,根据文法来生成编译器(如 lex/yacc)等等。

    这些理论试图教会我们如何用通用的方法来自动解决问题,它们有很强的实际意义,只是对于一般的学生或程序员来说,它们过于强大,内容过于复杂。如果你尝试阅读 lex/yacc (或 flex/bison)的代码,就会发现太可怕了。

    然而如果你能跟我一样,真正来实现一个简单的编译器,那么你会发现,比起可怕的《编译原理》,这点复杂度还是不算什么的(因为好多理论根本用不上)。

    项目的初衷

    ',15),F={href:"https://github.com/rswier/c4",target:"_blank",rel:"noopener noreferrer"},N=e("p",null,"一般的编译器相关的教程要么就十分简单(如实现四则运算),要么就是借助了自动生成的工具(如 flex/bison)。而 c4 的代码完全是手工实现的,不用外部工具。可惜的是它的代码初衷是代码最小化,所以写得很乱,很难懂。所以本项目的主要目的:",-1),S=e("ol",null,[e("li",null,"实现一个功能完善的 C 语言编译器"),e("li",null,"通过教程来说明这个过程。")],-1),j={href:"https://github.com/lotabout/write-a-C-interpreter",target:"_blank",rel:"noopener noreferrer"},I=o('

    声明:本项目中的代码逻辑绝大多数取自 c4 ,但确为自己重写。

    做好心理准备

    在写编译器的时候会遇到两个主要问题:

    1. 繁琐,会有许多相似的代码,写起来很无聊。
    2. 难以调试,一方面没有很好的测试用例,另一方面需要对照生成的代码来调试(遇到的时候就知道了)。

    所以我希望你有足够的耐心和时间来学习,相信当你真正完成的时候会像我一样,十分有成就感。

    PS. 第一篇完全没有正题相关的内容也是希望你能有所心理准备再开始学习。

    参考资料

    最后想介绍几个资料:

    ',8),K={href:"http://compilers.iecc.com/crenshaw/",target:"_blank",rel:"noopener noreferrer"},P={href:"http://www.hwaci.com/sw/lemon/",target:"_blank",rel:"noopener noreferrer"},V=e("p",null,"由于本人水平一般,文章、代码难免会有错误,敬请批评指正!",-1),J=e("p",null,"最后祝你学得愉快。",-1),O=e("h1",{id:"转载者注",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#转载者注"},[e("span",null,"转载者注")])],-1),T=e("p",null,"这篇博客并没有使用诸如 flex、bison 等工具来编写 token 和文法的定义,因而灵活性较差。相反,它通过定义自己的指令集并支持该指令集的虚拟机来模拟编译的部分过程,具有一定的参考价值。不过,我仍然认为通过定义 token 和文法并编写解析算法(如 regex、LL(k) 等)可能会更具普适性。",-1),z=e("div",{class:"hint-container info"},[e("p",{class:"hint-container-title"},"相关信息"),e("p",null,"不过据说c、cpp、java这些成熟的语言都是手动实现的?还没仔细了解过。 TODO")],-1);function G(R,M){const r=a("ExternalLinkIcon");return i(),p("div",null,[h,e("p",null,[t("本文转载自 "),c,t(",原作者 "),d,t(",原文链接:"),e("a",_,[t("原文链接"),n(r)]),t(",如有侵权,请联系删除。")]),e("p",null,[t("转载工具:"),e("a",u,[t("devtool"),n(r)]),t("、"),e("a",f,[t("helloworld"),n(r)])]),m,g,C,e("ol",null,[e("li",null,[e("a",b,[t("手把手教你构建 C 语言编译器(0)——前言"),n(r)])]),e("li",null,[e("a",E,[t("手把手教你构建 C 语言编译器(1)——设计"),n(r)])]),e("li",null,[e("a",k,[t("手把手教你构建 C 语言编译器(2)——虚拟机"),n(r)])]),e("li",null,[e("a",w,[t("手把手教你构建 C 语言编译器(3)——词法分析器"),n(r)])]),e("li",null,[e("a",B,[t("手把手教你构建 C 语言编译器(4)——递归下降"),n(r)])]),e("li",null,[e("a",x,[t("手把手教你构建 C 语言编译器(5)——变量定义"),n(r)])]),e("li",null,[e("a",A,[t("手把手教你构建 C 语言编译器(6)——函数定义"),n(r)])]),e("li",null,[e("a",v,[t("手把手教你构建 C 语言编译器(7)——语句"),n(r)])]),e("li",null,[e("a",y,[t("手把手教你构建 C 语言编译器(8)——表达式"),n(r)])]),e("li",null,[e("a",L,[t("手把手教你构建 C 语言编译器(9)——总结"),n(r)])])]),D,e("p",null,[t("有一次在 Github 上看到了一个项目(当时很火的),名叫 "),e("a",F,[t("c4"),n(r)]),t(",号称用 4 个函数来实现了一个小的 C 语言编译器。它最让我震惊的是能够自举,即能自己编译自己。并且它用很少的代码就完成了一个功能相当完善的 C 语言编译器。")]),N,S,e("p",null,[t("c4 大致500+行。重写的代码历时一周,总共代码加注释1400行。项目地址: "),e("a",j,[t("Write a C Interpreter"),n(r)]),t("。")]),I,e("ol",null,[e("li",null,[e("a",K,[t("Let’s Build a Compiler"),n(r)]),t(" 很好的初学者教程,英文的。")]),e("li",null,[e("a",P,[t("Lemon Parser Generator"),n(r)]),t(",一个语法分析器生成器,对照《编译原理》观看效果更佳。")])]),V,J,O,T,z])}const q=l(s,[["render",G],["__file","0.html.vue"]]),H=JSON.parse('{"path":"/tech/designASimpileCCompiler/0.html","title":"手把手教你构建 C 语言编译器(0)- 前言","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(0)- 前言","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转载自 手把手教你构建 C 语言编译器(0)- 前言,原作者 三点水,原文链接:原文链接,如有侵权,请联系删除。 转载工具:devtool、helloworld 原文内容 “手把手教你构建 C 语言编译器” 这一系列教程将带你从头编写一个 C 语言的编译器。希望通过这个系列,我们能对编译器的构建有一定的了解,同时,我们也将构建出一个能用的...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/0.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(0)- 前言"}],["meta",{"property":"og:description","content":"转载声明 本文转载自 手把手教你构建 C 语言编译器(0)- 前言,原作者 三点水,原文链接:原文链接,如有侵权,请联系删除。 转载工具:devtool、helloworld 原文内容 “手把手教你构建 C 语言编译器” 这一系列教程将带你从头编写一个 C 语言的编译器。希望通过这个系列,我们能对编译器的构建有一定的了解,同时,我们也将构建出一个能用的..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(0)- 前言\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"为什么要学编译原理","slug":"为什么要学编译原理","link":"#为什么要学编译原理","children":[]},{"level":2,"title":"理论很复杂,实现也很复杂?","slug":"理论很复杂-实现也很复杂","link":"#理论很复杂-实现也很复杂","children":[]},{"level":2,"title":"项目的初衷","slug":"项目的初衷","link":"#项目的初衷","children":[]},{"level":2,"title":"做好心理准备","slug":"做好心理准备","link":"#做好心理准备","children":[]},{"level":2,"title":"参考资料","slug":"参考资料","link":"#参考资料","children":[]}],"readingTime":{"minutes":6.12,"words":1835},"filePathRelative":"tech/designASimpileCCompiler/0.md","excerpt":"\\n

    本文转载自 手把手教你构建 C 语言编译器(0)- 前言,原作者 三点水,原文链接:原文链接,如有侵权,请联系删除。

    \\n

    转载工具:devtoolhelloworld

    ","autoDesc":true}');export{q as comp,H as data}; +import{_ as l,r as a,o as i,c as p,b as e,e as t,d as n,f as o}from"./app-B4LGNJZ0.js";const s={},h=e("h1",{id:"转载声明",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#转载声明"},[e("span",null,"转载声明")])],-1),c=e("strong",null,"手把手教你构建 C 语言编译器(0)- 前言",-1),d=e("strong",null,"三点水",-1),_={href:"https://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},u={href:"https://devtool.tech/html-md",target:"_blank",rel:"noopener noreferrer"},f={href:"https://www.helloworld.net/html2md",target:"_blank",rel:"noopener noreferrer"},m=e("h1",{id:"原文内容",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#原文内容"},[e("span",null,"原文内容")])],-1),g=e("p",null,"“手把手教你构建 C 语言编译器” 这一系列教程将带你从头编写一个 C 语言的编译器。希望通过这个系列,我们能对编译器的构建有一定的了解,同时,我们也将构建出一个能用的 C 语言编译器,尽管有许多语法并不支持。",-1),C=e("p",null,"手把手教你构建 C 语言编译器系列共有10个部分:",-1),b={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},w={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},B={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},x={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},v={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},L={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},D=o('

    在开始进入正题之前,本篇是一些闲聊,谈谈这个系列的初衷。如果你急切地想进入正篇,请跳过本章。

    为什么要学编译原理

    如果要我说计算机专业最重要的三门课,我会说是《数据结构》、《算法》和《编译原理》。在我看来,能不能理解“递归”像是程序员的第一道门槛,而会不会写编译器则是第二道。

    (当然,并不是说是没写过编译器就不是好程序员,只能说它是一个相当大的挑战吧)

    以前人们会说,学习了编译原理,你就能写出更加高效的代码,但随着计算机性能的提升,代码是否高效显得就不那么重要了。那么为什么要学习编译原理呢?

    原因只有一个:装B。

    好吧,也许现在还想学习编译原理的人只可能是因为兴趣了。一方面想了解它的工作原理;另一方面希望挑战一下自己,看看自己能走多远。

    理论很复杂,实现也很复杂?

    我对编译器一直心存敬佩。所以当学校开《编译原理》的课程后,我是抱着满腔热情去上课的,但是两节课后我就放弃了。原因是太复杂了,听不懂。

    一般编译原理的课程会说一些:

    1. 如何表示语法(BNF什么的)
    2. 词法分析,用什么有穷自动机和无穷自动机
    3. 语法分析,递归下降法,什么 LL(k),LALR 分析。
    4. 中间代码的表示
    5. 代码的生成
    6. 代码优化

    我相信绝大多数(98%)的学生顶多学到语法分析就结束了。并且最重要的是,学了这么多也没用!依旧帮助不了我们学习编译器!这其中最主要的原因是《编译原理》试图教会我们的是如何构造“编译器生成器”,即构造一个工具,根据文法来生成编译器(如 lex/yacc)等等。

    这些理论试图教会我们如何用通用的方法来自动解决问题,它们有很强的实际意义,只是对于一般的学生或程序员来说,它们过于强大,内容过于复杂。如果你尝试阅读 lex/yacc (或 flex/bison)的代码,就会发现太可怕了。

    然而如果你能跟我一样,真正来实现一个简单的编译器,那么你会发现,比起可怕的《编译原理》,这点复杂度还是不算什么的(因为好多理论根本用不上)。

    项目的初衷

    ',15),F={href:"https://github.com/rswier/c4",target:"_blank",rel:"noopener noreferrer"},N=e("p",null,"一般的编译器相关的教程要么就十分简单(如实现四则运算),要么就是借助了自动生成的工具(如 flex/bison)。而 c4 的代码完全是手工实现的,不用外部工具。可惜的是它的代码初衷是代码最小化,所以写得很乱,很难懂。所以本项目的主要目的:",-1),S=e("ol",null,[e("li",null,"实现一个功能完善的 C 语言编译器"),e("li",null,"通过教程来说明这个过程。")],-1),j={href:"https://github.com/lotabout/write-a-C-interpreter",target:"_blank",rel:"noopener noreferrer"},I=o('

    声明:本项目中的代码逻辑绝大多数取自 c4 ,但确为自己重写。

    做好心理准备

    在写编译器的时候会遇到两个主要问题:

    1. 繁琐,会有许多相似的代码,写起来很无聊。
    2. 难以调试,一方面没有很好的测试用例,另一方面需要对照生成的代码来调试(遇到的时候就知道了)。

    所以我希望你有足够的耐心和时间来学习,相信当你真正完成的时候会像我一样,十分有成就感。

    PS. 第一篇完全没有正题相关的内容也是希望你能有所心理准备再开始学习。

    参考资料

    最后想介绍几个资料:

    ',8),K={href:"http://compilers.iecc.com/crenshaw/",target:"_blank",rel:"noopener noreferrer"},P={href:"http://www.hwaci.com/sw/lemon/",target:"_blank",rel:"noopener noreferrer"},V=e("p",null,"由于本人水平一般,文章、代码难免会有错误,敬请批评指正!",-1),J=e("p",null,"最后祝你学得愉快。",-1),O=e("h1",{id:"转载者注",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#转载者注"},[e("span",null,"转载者注")])],-1),T=e("p",null,"这篇博客并没有使用诸如 flex、bison 等工具来编写 token 和文法的定义,因而灵活性较差。相反,它通过定义自己的指令集并支持该指令集的虚拟机来模拟编译的部分过程,具有一定的参考价值。不过,我仍然认为通过定义 token 和文法并编写解析算法(如 regex、LL(k) 等)可能会更具普适性。",-1),z=e("div",{class:"hint-container info"},[e("p",{class:"hint-container-title"},"相关信息"),e("p",null,"不过据说c、cpp、java这些成熟的语言都是手动实现的?还没仔细了解过。 TODO")],-1);function G(R,M){const r=a("ExternalLinkIcon");return i(),p("div",null,[h,e("p",null,[t("本文转载自 "),c,t(",原作者 "),d,t(",原文链接:"),e("a",_,[t("原文链接"),n(r)]),t(",如有侵权,请联系删除。")]),e("p",null,[t("转载工具:"),e("a",u,[t("devtool"),n(r)]),t("、"),e("a",f,[t("helloworld"),n(r)])]),m,g,C,e("ol",null,[e("li",null,[e("a",b,[t("手把手教你构建 C 语言编译器(0)——前言"),n(r)])]),e("li",null,[e("a",E,[t("手把手教你构建 C 语言编译器(1)——设计"),n(r)])]),e("li",null,[e("a",k,[t("手把手教你构建 C 语言编译器(2)——虚拟机"),n(r)])]),e("li",null,[e("a",w,[t("手把手教你构建 C 语言编译器(3)——词法分析器"),n(r)])]),e("li",null,[e("a",B,[t("手把手教你构建 C 语言编译器(4)——递归下降"),n(r)])]),e("li",null,[e("a",x,[t("手把手教你构建 C 语言编译器(5)——变量定义"),n(r)])]),e("li",null,[e("a",A,[t("手把手教你构建 C 语言编译器(6)——函数定义"),n(r)])]),e("li",null,[e("a",v,[t("手把手教你构建 C 语言编译器(7)——语句"),n(r)])]),e("li",null,[e("a",y,[t("手把手教你构建 C 语言编译器(8)——表达式"),n(r)])]),e("li",null,[e("a",L,[t("手把手教你构建 C 语言编译器(9)——总结"),n(r)])])]),D,e("p",null,[t("有一次在 Github 上看到了一个项目(当时很火的),名叫 "),e("a",F,[t("c4"),n(r)]),t(",号称用 4 个函数来实现了一个小的 C 语言编译器。它最让我震惊的是能够自举,即能自己编译自己。并且它用很少的代码就完成了一个功能相当完善的 C 语言编译器。")]),N,S,e("p",null,[t("c4 大致500+行。重写的代码历时一周,总共代码加注释1400行。项目地址: "),e("a",j,[t("Write a C Interpreter"),n(r)]),t("。")]),I,e("ol",null,[e("li",null,[e("a",K,[t("Let’s Build a Compiler"),n(r)]),t(" 很好的初学者教程,英文的。")]),e("li",null,[e("a",P,[t("Lemon Parser Generator"),n(r)]),t(",一个语法分析器生成器,对照《编译原理》观看效果更佳。")])]),V,J,O,T,z])}const q=l(s,[["render",G],["__file","0.html.vue"]]),H=JSON.parse('{"path":"/tech/designASimpileCCompiler/0.html","title":"手把手教你构建 C 语言编译器(0)- 前言","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(0)- 前言","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转载自 手把手教你构建 C 语言编译器(0)- 前言,原作者 三点水,原文链接:原文链接,如有侵权,请联系删除。 转载工具:devtool、helloworld 原文内容 “手把手教你构建 C 语言编译器” 这一系列教程将带你从头编写一个 C 语言的编译器。希望通过这个系列,我们能对编译器的构建有一定的了解,同时,我们也将构建出一个能用的...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/0.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(0)- 前言"}],["meta",{"property":"og:description","content":"转载声明 本文转载自 手把手教你构建 C 语言编译器(0)- 前言,原作者 三点水,原文链接:原文链接,如有侵权,请联系删除。 转载工具:devtool、helloworld 原文内容 “手把手教你构建 C 语言编译器” 这一系列教程将带你从头编写一个 C 语言的编译器。希望通过这个系列,我们能对编译器的构建有一定的了解,同时,我们也将构建出一个能用的..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(0)- 前言\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"为什么要学编译原理","slug":"为什么要学编译原理","link":"#为什么要学编译原理","children":[]},{"level":2,"title":"理论很复杂,实现也很复杂?","slug":"理论很复杂-实现也很复杂","link":"#理论很复杂-实现也很复杂","children":[]},{"level":2,"title":"项目的初衷","slug":"项目的初衷","link":"#项目的初衷","children":[]},{"level":2,"title":"做好心理准备","slug":"做好心理准备","link":"#做好心理准备","children":[]},{"level":2,"title":"参考资料","slug":"参考资料","link":"#参考资料","children":[]}],"readingTime":{"minutes":6.12,"words":1835},"filePathRelative":"tech/designASimpileCCompiler/0.md","excerpt":"\\n

    本文转载自 手把手教你构建 C 语言编译器(0)- 前言,原作者 三点水,原文链接:原文链接,如有侵权,请联系删除。

    \\n

    转载工具:devtoolhelloworld

    ","autoDesc":true}');export{q as comp,H as data}; diff --git a/assets/1.html-B2woGSgL.js b/assets/1.html-C8e1pvTo.js similarity index 99% rename from assets/1.html-B2woGSgL.js rename to assets/1.html-C8e1pvTo.js index 277485a..bbd3632 100644 --- a/assets/1.html-B2woGSgL.js +++ b/assets/1.html-C8e1pvTo.js @@ -1 +1 @@ -import{_ as t,r as p,o as r,c as o,b as s,e as a,d as e,f as l}from"./app-B69Gl_S-.js";const i={},c=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},d=l('

    原文内容

    手把手教你构建 C 语言编译器(1)- 设计

    Table of Contents

    1. 1. 编译器的构建流程
    2. 2. 编译器框架

    这是“手把手教你构建 C 语言编译器”系列的第二篇,我们要从整体上讲解如何设计我们的 C 语言编译器。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),h={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},w=l('

    首先要说明的是,虽然标题是编译器,但实际上我们构建的是 C 语言的解释器,这意味着我们可以像运行脚本一样去运行 C 语言的源代码文件。这么做的理由有两点:

    1. 解释器与编译器仅在代码生成阶段有区别,而其它方面如词法分析、语法分析是一样的。
    2. 解释器需要我们实现自己的虚拟机与指令集,而这部分能帮助我们了解计算机的工作原理。

    编译器的构建流程

    一般而言,编译器的编写分为 3 个步骤:

    1. 词法分析器,用于将字符串转化成内部的表示结构。
    2. 语法分析器,将词法分析得到的标记流(token)生成一棵语法树。
    3. 目标代码的生成,将语法树转化成目标代码。

    已经有许多工具能帮助我们处理阶段1和2,如 flex 用于词法分析,bison 用于语法分析。只是它们的功能都过于强大,屏蔽了许多实现上的细节,对于学习构建编译器帮助不大。所以我们要完全手写这些功能。

    所以我们会依照以下步骤来构建我们的编译器:

    1. 构建我们自己的虚拟机以及指令集。这后生成的目标代码便是我们的指令集。
    2. 构建我们的词法分析器
    3. 构建语法分析器

    编译器框架

    我们的编译器主要包括 4 个函数:

    1. next() 用于词法分析,获取下一个标记,它将自动忽略空白字符。
    2. program() 语法分析的入口,分析整个 C 语言程序。
    3. expression(level) 用于解析一个表达式。
    4. eval() 虚拟机的入口,用于解释目标代码。

    这里有一个单独用于解析“表达式”的函数 expression 是因为表达式在语法分析中相对独立并且比较复杂,所以我们将它单独作为一个模块(函数)。下面是相应的源代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <string.h>

    int token; // current token
    char *src, *old_src; // pointer to source code string;
    int poolsize; // default size of text/data/stack
    int line; // line number

    void next() {
    token = *src++;
    return;
    }

    void expression(int level) {
    // do nothing
    }

    void program() {
    next(); // get next token
    while (token > 0) {
    printf("token is: %c\\n", token);
    next();
    }
    }


    int eval() { // do nothing yet
    return 0;
    }

    int main(int argc, char **argv)
    {
    int i, fd;

    argc--;
    argv++;

    poolsize = 256 * 1024; // arbitrary size
    line = 1;

    if ((fd = open(*argv, 0)) < 0) {
    printf("could not open(%s)\\n", *argv);
    return -1;
    }

    if (!(src = old_src = malloc(poolsize))) {
    printf("could not malloc(%d) for source area\\n", poolsize);
    return -1;
    }

    // read the source file
    if ((i = read(fd, src, poolsize-1)) <= 0) {
    printf("read() returned %d\\n", i);
    return -1;
    }
    src[i] = 0; // add EOF character
    close(fd);

    program();
    return eval();
    }

    上面的代码看上去挺复杂,但其实内容不多。它的流程为:读取一个文件(内容为 C 语言代码),逐个读取文件中的字符,并输出。这里需要的是注意每个函数的作用,后面的文章中,我们将逐个填充每个函数的功能,最终构建起我们的编译器。

    ',14),A={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-0",target:"_blank",rel:"noopener noreferrer"},B=s("table",null,[s("tbody",null,[s("tr",null,[s("td",{class:"code"},[s("pre",null,[s("span",{class:"line"},"git clone -b step-0 https://github.com/lotabout/write-a-C-interpreter"),s("br")])])])])],-1),x=s("p",null,[a("这样我们就有了一个最简单的编译器:什么都不干的编译器,下一章中,我们将实现其中的"),s("code",null,"eval"),a("函数,即我们自己的虚拟机。")],-1);function v(z,q){const n=p("ExternalLinkIcon");return r(),o("div",null,[c,s("p",null,[a("本文转自 "),s("a",b,[a("https://lotabout.me/2015/write-a-C-interpreter-1/"),e(n)]),a(",如有侵权,请联系删除。")]),d,s("ol",null,[s("li",null,[s("a",h,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",_,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",C,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),w,s("p",null,[a("本节的代码可以在 "),s("a",A,[a("Github"),e(n)]),a(" 上下载,也可以直接 clone")]),B,x])}const N=t(i,[["render",v],["__file","1.html.vue"]]),S=JSON.parse('{"path":"/tech/designASimpileCCompiler/1.html","title":"手把手教你构建 C 语言编译器(1)- 设计","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(1)- 设计","description":"转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-1/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(1)- 设计 Table of Contents 1. 编译器的构建流程 2. 编译器框架 这是“手把手教你构建 C 语言编译器”系列的第二篇,我们要从整体上讲解如何设...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/1.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(1)- 设计"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-1/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(1)- 设计 Table of Contents 1. 编译器的构建流程 2. 编译器框架 这是“手把手教你构建 C 语言编译器”系列的第二篇,我们要从整体上讲解如何设..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(1)- 设计\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"编译器的构建流程","slug":"编译器的构建流程","link":"#编译器的构建流程","children":[]},{"level":2,"title":"编译器框架","slug":"编译器框架","link":"#编译器框架","children":[]}],"readingTime":{"minutes":6.74,"words":2023},"filePathRelative":"tech/designASimpileCCompiler/1.md","excerpt":"\\n

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-1/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(1)- 设计

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 编译器的构建流程
    2. \\n
    3. 2. 编译器框架
    4. \\n
    ","autoDesc":true}');export{N as comp,S as data}; +import{_ as t,r as p,o as r,c as o,b as s,e as a,d as e,f as l}from"./app-B4LGNJZ0.js";const i={},c=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},d=l('

    原文内容

    手把手教你构建 C 语言编译器(1)- 设计

    Table of Contents

    1. 1. 编译器的构建流程
    2. 2. 编译器框架

    这是“手把手教你构建 C 语言编译器”系列的第二篇,我们要从整体上讲解如何设计我们的 C 语言编译器。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),h={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},w=l('

    首先要说明的是,虽然标题是编译器,但实际上我们构建的是 C 语言的解释器,这意味着我们可以像运行脚本一样去运行 C 语言的源代码文件。这么做的理由有两点:

    1. 解释器与编译器仅在代码生成阶段有区别,而其它方面如词法分析、语法分析是一样的。
    2. 解释器需要我们实现自己的虚拟机与指令集,而这部分能帮助我们了解计算机的工作原理。

    编译器的构建流程

    一般而言,编译器的编写分为 3 个步骤:

    1. 词法分析器,用于将字符串转化成内部的表示结构。
    2. 语法分析器,将词法分析得到的标记流(token)生成一棵语法树。
    3. 目标代码的生成,将语法树转化成目标代码。

    已经有许多工具能帮助我们处理阶段1和2,如 flex 用于词法分析,bison 用于语法分析。只是它们的功能都过于强大,屏蔽了许多实现上的细节,对于学习构建编译器帮助不大。所以我们要完全手写这些功能。

    所以我们会依照以下步骤来构建我们的编译器:

    1. 构建我们自己的虚拟机以及指令集。这后生成的目标代码便是我们的指令集。
    2. 构建我们的词法分析器
    3. 构建语法分析器

    编译器框架

    我们的编译器主要包括 4 个函数:

    1. next() 用于词法分析,获取下一个标记,它将自动忽略空白字符。
    2. program() 语法分析的入口,分析整个 C 语言程序。
    3. expression(level) 用于解析一个表达式。
    4. eval() 虚拟机的入口,用于解释目标代码。

    这里有一个单独用于解析“表达式”的函数 expression 是因为表达式在语法分析中相对独立并且比较复杂,所以我们将它单独作为一个模块(函数)。下面是相应的源代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <string.h>

    int token; // current token
    char *src, *old_src; // pointer to source code string;
    int poolsize; // default size of text/data/stack
    int line; // line number

    void next() {
    token = *src++;
    return;
    }

    void expression(int level) {
    // do nothing
    }

    void program() {
    next(); // get next token
    while (token > 0) {
    printf("token is: %c\\n", token);
    next();
    }
    }


    int eval() { // do nothing yet
    return 0;
    }

    int main(int argc, char **argv)
    {
    int i, fd;

    argc--;
    argv++;

    poolsize = 256 * 1024; // arbitrary size
    line = 1;

    if ((fd = open(*argv, 0)) < 0) {
    printf("could not open(%s)\\n", *argv);
    return -1;
    }

    if (!(src = old_src = malloc(poolsize))) {
    printf("could not malloc(%d) for source area\\n", poolsize);
    return -1;
    }

    // read the source file
    if ((i = read(fd, src, poolsize-1)) <= 0) {
    printf("read() returned %d\\n", i);
    return -1;
    }
    src[i] = 0; // add EOF character
    close(fd);

    program();
    return eval();
    }

    上面的代码看上去挺复杂,但其实内容不多。它的流程为:读取一个文件(内容为 C 语言代码),逐个读取文件中的字符,并输出。这里需要的是注意每个函数的作用,后面的文章中,我们将逐个填充每个函数的功能,最终构建起我们的编译器。

    ',14),A={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-0",target:"_blank",rel:"noopener noreferrer"},B=s("table",null,[s("tbody",null,[s("tr",null,[s("td",{class:"code"},[s("pre",null,[s("span",{class:"line"},"git clone -b step-0 https://github.com/lotabout/write-a-C-interpreter"),s("br")])])])])],-1),x=s("p",null,[a("这样我们就有了一个最简单的编译器:什么都不干的编译器,下一章中,我们将实现其中的"),s("code",null,"eval"),a("函数,即我们自己的虚拟机。")],-1);function v(z,q){const n=p("ExternalLinkIcon");return r(),o("div",null,[c,s("p",null,[a("本文转自 "),s("a",b,[a("https://lotabout.me/2015/write-a-C-interpreter-1/"),e(n)]),a(",如有侵权,请联系删除。")]),d,s("ol",null,[s("li",null,[s("a",h,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",_,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",C,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),w,s("p",null,[a("本节的代码可以在 "),s("a",A,[a("Github"),e(n)]),a(" 上下载,也可以直接 clone")]),B,x])}const N=t(i,[["render",v],["__file","1.html.vue"]]),S=JSON.parse('{"path":"/tech/designASimpileCCompiler/1.html","title":"手把手教你构建 C 语言编译器(1)- 设计","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(1)- 设计","description":"转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-1/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(1)- 设计 Table of Contents 1. 编译器的构建流程 2. 编译器框架 这是“手把手教你构建 C 语言编译器”系列的第二篇,我们要从整体上讲解如何设...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/1.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(1)- 设计"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-1/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(1)- 设计 Table of Contents 1. 编译器的构建流程 2. 编译器框架 这是“手把手教你构建 C 语言编译器”系列的第二篇,我们要从整体上讲解如何设..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(1)- 设计\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"编译器的构建流程","slug":"编译器的构建流程","link":"#编译器的构建流程","children":[]},{"level":2,"title":"编译器框架","slug":"编译器框架","link":"#编译器框架","children":[]}],"readingTime":{"minutes":6.74,"words":2023},"filePathRelative":"tech/designASimpileCCompiler/1.md","excerpt":"\\n

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-1/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(1)- 设计

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 编译器的构建流程
    2. \\n
    3. 2. 编译器框架
    4. \\n
    ","autoDesc":true}');export{N as comp,S as data}; diff --git a/assets/2.html-DuLx7xSk.js b/assets/2.html-CfN7D4gf.js similarity index 99% rename from assets/2.html-DuLx7xSk.js rename to assets/2.html-CfN7D4gf.js index 5d04dae..7a5900e 100644 --- a/assets/2.html-DuLx7xSk.js +++ b/assets/2.html-CfN7D4gf.js @@ -1 +1 @@ -import{_ as l,r as t,o as c,c as r,b as s,e as a,d as n,f as p}from"./app-B69Gl_S-.js";const o={},i=s("hr",null,null,-1),d=s("p",null,'title: "手把手教你构建 C 语言编译器(2)——虚拟机" category:',-1),b=s("ul",null,[s("li",null,"编译原理 tag:"),s("li",null,"c"),s("li",null,"编译器"),s("li",null,"解释器")],-1),h=s("hr",null,null,-1),u=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),m={href:"https://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},y=p('

    原文内容

    手把手教你构建 C 语言编译器(2)- 虚拟机

    Table of Contents

    1. 1. 计算机的内部工作原理
      1. 1.1. 内存
      2. 1.2. 寄存器
    2. 2. 指令集
      1. 2.1. MOV
      2. 2.2. PUSH
      3. 2.3. JMP
      4. 2.4. JZ/JNZ
      5. 2.5. 子函数调用
      6. 2.6. ENT
      7. 2.7. ADJ
      8. 2.8. LEV
      9. 2.9. LEA
      10. 2.10. 运算符指令
      11. 2.11. 内置函数
    3. 3. 测试
    4. 4. 小结

    这是“手把手教你构建 C 语言编译器”系列的第三篇,本章我们要构建一台虚拟的电脑,设计我们自己的指令集,运行我们的指令集,说得通俗一点就是自己实现一套汇编语言。它们将作为我们的编译器最终输出的目标代码。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),f={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},x={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},w={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},v={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},L=p('

    计算机的内部工作原理

    计算机中有三个基本部件需要我们关注:CPU、寄存器及内存。代码(汇编指令)以二进制的形式保存在内存中;CPU 从中一条条地加载指令执行;程序运行的状态保存在寄存器中。

    内存

    内存用于存储数据,这里的数据可以是代码,也可以是其它的数据。现代操作系统在操作内存时,并不是直接处理”物理内存“,而是操作”虚拟内存“。虚拟内存可以理解为一种映射,它的作用是屏蔽了物理的细节。例如 32 位的机器中,我们可以使用的内存地址为 2^32 = 4G,而电脑上的实际内存可能只有 256 M。操作系统将我们使用的虚拟地址映射到了到实际的内存上。

    当然,我们这里并不需要了解太多,但需要了解的是:进程的内存会被分成几个段:

    1. 代码段(text)用于存放代码(指令)。
    2. 数据段(data)用于存放初始化了的数据,如int i = 10;,就需要存放到数据段中。
    3. 未初始化数据段(bss)用于存放未初始化的数据,如 int i[1000];,因为不关心其中的真正数值,所以单独存放可以节省空间,减少程序的体积。
    4. 栈(stack)用于处理函数调用相关的数据,如调用帧(calling frame)或是函数的局部变量等。
    5. 堆(heap)用于为程序动态分配内存。

    它们在内存中的位置类似于下图:

    +------------------+
    | stack | | high address
    | ... v |
    | |
    | |
    | |
    | |
    | ... ^ |
    | heap | |
    +------------------+
    | bss segment |
    +------------------+
    | data segment |
    +------------------+
    | text segment | low address
    +------------------+

    我们的虚拟机并不打算模拟完整的计算机,因此简单起见,我们只关心三个内容:代码段、数据段以及栈。其中的数据段我们只用来存放字符串,因为我们的编译器并不支持初始化变量,因此我们也不需要未初始化数据段。

    当用户的程序需要分配内存时,理论上我们的虚拟机需要维护一个堆用于内存分配,但实际实现上较为复杂且与编译无关,故我们引入一个指令MSET,使我们能直接使用编译器(解释器)中的内存。

    综上,我们需要首先在全局添加如下代码:

    int *text,            // text segment
    *old_text, // for dump text segment
    *stack; // stack
    char *data; // data segment

    注意这里的类型,虽然是int型,但理解起来应该作为无符号的整型,因为我们会在代码段(text)中存放如指针/内存地址的数据,它们就是无符号的。其中数据段(data)由于只存放字符串,所以是 char * 型的。

    接着,在main函数中加入初始化代码,真正为其分配内存:

    int main() {
    close(fd);
    ...

    // allocate memory for virtual machine
    if (!(text = old_text = malloc(poolsize))) {
    printf("could not malloc(%d) for text area\\n", poolsize);
    return -1;
    }
    if (!(data = malloc(poolsize))) {
    printf("could not malloc(%d) for data area\\n", poolsize);
    return -1;
    }
    if (!(stack = malloc(poolsize))) {
    printf("could not malloc(%d) for stack area\\n", poolsize);
    return -1;
    }

    memset(text, 0, poolsize);
    memset(data, 0, poolsize);
    memset(stack, 0, poolsize);

    ...
    program();
    }

    寄存器

    计算机中的寄存器用于存放计算机的运行状态,真正的计算机中有许多不同种类的寄存器,但我们的虚拟机中只使用 4 个寄存器,分别如下:

    1. PC 程序计数器,它存放的是一个内存地址,该地址中存放着 下一条 要执行的计算机指令。
    2. SP 指针寄存器,永远指向当前的栈顶。注意的是由于栈是位于高地址并向低地址增长的,所以入栈时 SP 的值减小。
    3. BP 基址指针。也是用于指向栈的某些位置,在调用函数时会使用到它。
    4. AX 通用寄存器,我们的虚拟机中,它用于存放一条指令执行后的结果。

    要理解这些寄存器的作用,需要去理解程序运行中会有哪些状态。而这些寄存器只是用于保存这些状态的。

    在全局中加入如下定义:

    int *pc, *bp, *sp, ax, cycle; // virtual machine registers

    main 函数中加入初始化代码,注意的是PC在初始应指向目标代码中的main函数,但我们还没有写任何编译相关的代码,因此先不处理。代码如下:

    memset(stack, 0, poolsize);
    ...

    bp = sp = (int *)((int)stack + poolsize);
    ax = 0;

    ...
    program();

    与 CPU 相关的是指令集,我们将专门作为一个小节。

    指令集

    指令集是 CPU 能识别的命令的集合,也可以说是 CPU 能理解的语言。这里我们要为我们的虚拟机构建自己的指令集。它们基于 x86 的指令集,但更为简单。

    首先在全局变量中加入一个枚举类型,这是我们要支持的全部指令:

    // instructions
    enum { LEA ,IMM ,JMP ,CALL,JZ ,JNZ ,ENT ,ADJ ,LEV ,LI ,LC ,SI ,SC ,PUSH,
    OR ,XOR ,AND ,EQ ,NE ,LT ,GT ,LE ,GE ,SHL ,SHR ,ADD ,SUB ,MUL ,DIV ,MOD ,
    OPEN,READ,CLOS,PRTF,MALC,MSET,MCMP,EXIT };

    这些指令的顺序安排是有意的,稍后你会看到,带有参数的指令在前,没有参数的指令在后。这种顺序的唯一作用就是在打印调试信息时更加方便。但我们讲解的顺序并不依据它。

    MOV

    MOV 是所有指令中最基础的一个,它用于将数据放进寄存器或内存地址,有点类似于 C 语言中的赋值语句。x86 的 MOV 指令有两个参数,分别是源地址和目标地址:MOV dest, source (Intel 风格),表示将 source 的内容放在 dest 中,它们可以是一个数、寄存器或是一个内存地址。

    一方面,我们的虚拟机只有一个寄存器,另一方面,识别这些参数的类型(是数据还是地址)是比较困难的,因此我们将 MOV 指令拆分成 5 个指令,这些指令只接受一个参数,如下:

    1. IMM <num><num> 放入寄存器 ax 中。
    2. LC 将对应地址中的字符载入 ax 中,要求 ax 中存放地址。
    3. LI 将对应地址中的整数载入 ax 中,要求 ax 中存放地址。
    4. SCax 中的数据作为字符存放入地址中,要求栈顶存放地址。
    5. SIax 中的数据作为整数存放入地址中,要求栈顶存放地址。

    你可能会觉得将一个指令变成了许多指令,整个系统就变得复杂了,但实际情况并非如此。首先是 x86 的 MOV 指令其实有许多变种,根据类型的不同有 MOVB, MOVW 等指令,我们这里的 LC/SCLI/SI 就是对应字符型和整型的存取操作。

    但最为重要的是,通过将 MOV 指令拆分成这些指令,只有 IMM 需要有参数,且不需要判断类型,所以大大简化了实现的难度。

    eval() 函数中加入下列代码:

    void eval() {
    int op, *tmp;
    while (1) {
    if (op == IMM) {ax = *pc++;} // load immediate value to ax
    else if (op == LC) {ax = *(char *)ax;} // load character to ax, address in ax
    else if (op == LI) {ax = *(int *)ax;} // load integer to ax, address in ax
    else if (op == SC) {ax = *(char *)*sp++ = ax;} // save character to address, value in ax, address on stack
    else if (op == SI) {*(int *)*sp++ = ax;} // save integer to address, value in ax, address on stack
    }

    ...
    return 0;
    }

    其中的 *sp++ 的作用是退栈,相当于 POP 操作。

    这里要解释的一点是,为什么 SI/SC 指令中,地址存放在栈中,而 LI/LC 中,地址存放在 ax 中?原因是默认计算的结果是存放在 ax 中的,而地址通常是需要通过计算获得,所以执行 LI/LC 时直接从 ax 取值会更高效。另一点是我们的 PUSH 指令只能将 ax 的值放到栈上,而不能以值作为参数,详细见下文。

    PUSH

    在 x86 中,PUSH 的作用是将值或寄存器,而在我们的虚拟机中,它的作用是将 ax 的值放入栈中。这样做的主要原因是为了简化虚拟机的实现,并且我们也只有一个寄存器 ax 。代码如下:

    else if (op == PUSH) {*--sp = ax;}                                     // push the value of ax onto the stack

    JMP

    JMP <addr> 是跳转指令,无条件地将当前的 PC 寄存器设置为指定的 <addr>,实现如下:

    else if (op == JMP)  {pc = (int *)*pc;}                                // jump to the address

    需要注意的是,pc 寄存器指向的是 下一条 指令。所以此时它存放的是 JMP 指令的参数,即 <addr> 的值。

    JZ/JNZ

    为了实现 if 语句,我们需要条件判断相关的指令。这里我们只实现两个最简单的条件判断,即结果(ax)为零或不为零情况下的跳转。

    实现如下:

    else if (op == JZ)   {pc = ax ? pc + 1 : (int *)*pc;}                   // jump if ax is zero
    else if (op == JNZ) {pc = ax ? (int *)*pc : pc + 1;} // jump if ax is not zero

    子函数调用

    这是汇编中最难理解的部分,所以合在一起说,要引入的命令有 CALL, ENT, ADJLEV

    首先我们介绍 CALL <addr>RET 指令,CALL 的作用是跳转到地址为 <addr> 的子函数,RET 则用于从子函数中返回。

    为什么不能直接使用 JMP 指令呢?原因是当我们从子函数中返回时,程序需要回到跳转之前的地方继续运行,这就需要事先将这个位置信息存储起来。反过来,子函数要返回时,就需要获取并恢复这个信息。因此实际中我们将 PC 保存在栈中。如下:

    else if (op == CALL) {*--sp = (int)(pc+1); pc = (int *)*pc;}           // call subroutine
    //else if (op == RET) {pc = (int *)*sp++;} // return from subroutine;

    这里我们把 RET 相关的内容注释了,是因为之后我们将用 LEV 指令来代替它。

    在实际调用函数时,不仅要考虑函数的地址,还要考虑如何传递参数和如何返回结果。这里我们约定,如果子函数有返回结果,那么就在返回时保存在 ax 中,它可以是一个值,也可以是一个地址。那么参数的传递呢?

    各种编程语言关于如何调用子函数有不同的约定,例如 C 语言的调用标准是:

    1. 由调用者将参数入栈。
    2. 调用结束时,由调用者将参数出栈。
    3. 参数逆序入栈。
    ',59),B={href:"https://en.wikipedia.org/wiki/X86_calling_conventions",target:"_blank",rel:"noopener noreferrer"},M=p('
    int callee(int, int, int);

    int caller(void)
    {
    int i, ret;

    ret = callee(1, 2, 3);
    ret += 5;
    return ret;
    }

    会生成如下的 x86 汇编代码:

    caller:
    ; make new call frame
    push ebp
    mov ebp, esp
    sub 1, esp ; save stack for variable: i
    ; push call arguments
    push 3
    push 2
    push 1
    ; call subroutine 'callee'
    call callee
    ; remove arguments from frame
    add esp, 12
    ; use subroutine result
    add eax, 5
    ; restore old call frame
    mov esp, ebp
    pop ebp
    ; return
    ret

    上面这段代码在我们自己的虚拟机里会有几个问题:

    1. push ebp,但我们的 PUSH 指令并无法指定寄存器。
    2. mov ebp, esp,我们的 MOV 指令同样功能不足。
    3. add esp, 12,也是一样的问题(尽管我们还没定义)。

    也就是说由于我们的指令过于简单(如只能操作ax寄存器),所以用上面提到的指令,我们连函数调用都无法实现。而我们又不希望扩充现有指令的功能,因为这样实现起来就会变得复杂,因此我们采用的方法是增加指令集。毕竟我们不是真正的计算机,增加指令会消耗许多资源(钱)。

    ENT

    ENT <size> 指的是 enter,用于实现 ‘make new call frame’ 的功能,即保存当前的栈指针,同时在栈上保留一定的空间,用以存放局部变量。对应的汇编代码为:

    ; make new call frame
    push ebp
    mov ebp, esp
    sub 1, esp ; save stack for variable: i

    实现如下:

    else if (op == ENT)  {*--sp = (int)bp; bp = sp; sp = sp - *pc++;}      // make new stack frame

    ADJ

    ADJ <size> 用于实现 ‘remove arguments from frame’。在将调用子函数时压入栈中的数据清除,本质上是因为我们的 ADD 指令功能有限。对应的汇编代码为:

    ; remove arguments from frame
    add esp, 12

    实现如下:

    else if (op == ADJ)  {sp = sp + *pc++;}                                // add esp, <size>

    LEV

    本质上这个指令并不是必需的,只是我们的指令集中并没有 POP 指令。并且三条指令写来比较麻烦且浪费空间,所以用一个指令代替。对应的汇编指令为:

    ; restore old call frame
    mov esp, ebp
    pop ebp
    ; return
    ret

    具体的实现如下:

    else if (op == LEV)  {sp = bp; bp = (int *)*sp++; pc = (int *)*sp++;}  // restore call frame and PC

    注意的是,LEV 已经把 RET 的功能包含了,所以我们不再需要 RET 指令。

    LEA

    上面的一些指令解决了调用帧的问题,但还有一个问题是如何在子函数中获得传入的参数。这里我们首先要了解的是当参数调用时,栈中的调用帧是什么样的。我们依旧用上面的例子(只是现在用“顺序”调用参数):

    sub_function(arg1, arg2, arg3);

    | .... | high address
    +---------------+
    | arg: 1 | new_bp + 4
    +---------------+
    | arg: 2 | new_bp + 3
    +---------------+
    | arg: 3 | new_bp + 2
    +---------------+
    |return address | new_bp + 1
    +---------------+
    | old BP | <- new BP
    +---------------+
    | local var 1 | new_bp - 1
    +---------------+
    | local var 2 | new_bp - 2
    +---------------+
    | .... | low address

    所以为了获取第一个参数,我们需要得到 new_bp + 4,但就如上面的说,我们的 ADD 指令无法操作除 ax 外的寄存器,所以我们提供了一个新的指令:LEA <offset>

    实现如下:

    else if (op == LEA)  {ax = (int)(bp + *pc++);}                         // load address for arguments.

    以上就是我们为了实现函数调用需要的指令了。

    运算符指令

    我们为 C 语言中支持的运算符都提供对应汇编指令。每个运算符都是二元的,即有两个参数,第一个参数放在栈顶,第二个参数放在 ax 中。这个顺序要特别注意。因为像 -/ 之类的运算符是与参数顺序有关的。计算后会将栈顶的参数退栈,结果存放在寄存器 ax 中。因此计算结束后,两个参数都无法取得了(汇编的意义上,存在内存地址上就另当别论)。

    实现如下:

    else if (op == OR)  ax = *sp++ | ax;
    else if (op == XOR) ax = *sp++ ^ ax;
    else if (op == AND) ax = *sp++ & ax;
    else if (op == EQ) ax = *sp++ == ax;
    else if (op == NE) ax = *sp++ != ax;
    else if (op == LT) ax = *sp++ < ax;
    else if (op == LE) ax = *sp++ <= ax;
    else if (op == GT) ax = *sp++ > ax;
    else if (op == GE) ax = *sp++ >= ax;
    else if (op == SHL) ax = *sp++ << ax;
    else if (op == SHR) ax = *sp++ >> ax;
    else if (op == ADD) ax = *sp++ + ax;
    else if (op == SUB) ax = *sp++ - ax;
    else if (op == MUL) ax = *sp++ * ax;
    else if (op == DIV) ax = *sp++ / ax;
    else if (op == MOD) ax = *sp++ % ax;

    内置函数

    写的程序要”有用“,除了核心的逻辑外还需要输入输出,例如 C 语言中我们经常使用的 printf 函数就是用于输出。但是 printf 函数的实现本身就十分复杂,如果我们的编译器要达到自举,就势必要实现 printf 之类的函数,但它又与编译器没有太大的联系,因此我们继续实现新的指令,从虚拟机的角度予以支持。

    编译器中我们需要用到的函数有:exit, open, close, read, printf, malloc, memsetmemcmp。代码如下:

    else if (op == EXIT) { printf("exit(%d)", *sp); return *sp;}
    else if (op == OPEN) { ax = open((char *)sp[1], sp[0]); }
    else if (op == CLOS) { ax = close(*sp);}
    else if (op == READ) { ax = read(sp[2], (char *)sp[1], *sp); }
    else if (op == PRTF) { tmp = sp + pc[1]; ax = printf((char *)tmp[-1], tmp[-2], tmp[-3], tmp[-4], tmp[-5], tmp[-6]); }
    else if (op == MALC) { ax = (int)malloc(*sp);}
    else if (op == MSET) { ax = (int)memset((char *)sp[2], sp[1], *sp);}
    else if (op == MCMP) { ax = memcmp((char *)sp[2], (char *)sp[1], *sp);}

    这里的原理是,我们的电脑上已经有了这些函数的实现,因此编译编译器时,这些函数的二进制代码就被编译进了我们的编译器,因此在我们的编译器/虚拟机上运行我们提供的这些指令时,这些函数就是可用的。换句话说就是不需要我们自己去实现了。

    最后再加上一个错误判断:

    else {
    printf("unknown instruction:%d\\n", op);
    return -1;
    }

    测试

    下面我们用我们的汇编写一小段程序,来计算 10+20,在 main 函数中加入下列代码:

    int main(int argc, char *argv[])
    {
    ax = 0;
    ...

    i = 0;
    text[i++] = IMM;
    text[i++] = 10;
    text[i++] = PUSH;
    text[i++] = IMM;
    text[i++] = 20;
    text[i++] = ADD;
    text[i++] = PUSH;
    text[i++] = EXIT;
    pc = text;

    ...
    program();
    }

    编译程序 gcc xc-tutor.c,运行程序:./a.out hello.c。输出

    exit(30)

    另外,我们的代码里有一些指针的强制转换,默认是 32 位的,因此在 64 位机器下,会出现 segmentation fault,解决方法(二选一):

    1. 编译时加上 -m32 参数:gcc -m32 xc-tutor.c
    2. 在代码的开头,增加 #define int long longlong long 是 64 位的,不会出现强制转换后的问题。

    注意我们的之前的程序需要指令一个源文件,只是现在还用不着,但从结果可以看出,我们的虚拟机还是工作良好的。

    小结

    本章中我们回顾了计算机的内部运行原理,并仿照 x86 汇编指令设计并实现了我们自己的指令集。希望通过本章的学习,你能对计算机程序的原理有一定的了解,同时能对汇编语言有一定的概念,因为汇编语言就是 C 编译器的输出。

    ',50),P={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-1",target:"_blank",rel:"noopener noreferrer"},D=s("table",null,[s("tbody",null,[s("tr",null,[s("td",{class:"code"},[s("pre",null,[s("span",{class:"line"},"git clone -b step-1 https://github.com/lotabout/write-a-C-interpreter"),s("br")])])])])],-1),S=s("p",null,"实际计算机中,添加一个新的指令需要设计许多新的电路,会增加许多的成本,但我们的虚拟机中,新的指令几乎不消耗资源,因此我们可以利用这一点,用更多的指令来完成更多的功能,从而简化具体的实现。",-1);function j(z,T){const e=t("ExternalLinkIcon");return c(),r("div",null,[i,d,b,h,u,s("p",null,[a("本文转自 "),s("a",m,[a("https://lotabout.me/2015/write-a-C-interpreter-2/"),n(e)]),a(",如有侵权,请联系删除。")]),y,s("ol",null,[s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(0)——前言"),n(e)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(1)——设计"),n(e)])]),s("li",null,[s("a",x,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),n(e)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),n(e)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(4)——递归下降"),n(e)])]),s("li",null,[s("a",w,[a("手把手教你构建 C 语言编译器(5)——变量定义"),n(e)])]),s("li",null,[s("a",_,[a("手把手教你构建 C 语言编译器(6)——函数定义"),n(e)])]),s("li",null,[s("a",C,[a("手把手教你构建 C 语言编译器(7)——语句"),n(e)])]),s("li",null,[s("a",A,[a("手把手教你构建 C 语言编译器(8)——表达式"),n(e)])]),s("li",null,[s("a",v,[a("手把手教你构建 C 语言编译器(9)——总结"),n(e)])])]),L,s("p",null,[a("事先声明一下,我们的编译器参数是顺序入栈的,下面的例子(C 语言调用标准)取自 "),s("a",B,[a("维基百科"),n(e)]),a(":")]),M,s("p",null,[a("本章的代码可以在 "),s("a",P,[a("Github"),n(e)]),a(" 上下载,也可以直接 clone")]),D,S])}const I=l(o,[["render",j],["__file","2.html.vue"]]),V=JSON.parse('{"path":"/tech/designASimpileCCompiler/2.html","title":"转载声明","lang":"zh-CN","frontmatter":{"description":"title: \\"手把手教你构建 C 语言编译器(2)——虚拟机\\" category: 编译原理 tag: c 编译器 解释器 转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-2/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(2)- 虚拟机 Table of Cont...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/2.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"转载声明"}],["meta",{"property":"og:description","content":"title: \\"手把手教你构建 C 语言编译器(2)——虚拟机\\" category: 编译原理 tag: c 编译器 解释器 转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-2/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(2)- 虚拟机 Table of Cont..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"转载声明\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"计算机的内部工作原理","slug":"计算机的内部工作原理","link":"#计算机的内部工作原理","children":[{"level":3,"title":"内存","slug":"内存","link":"#内存","children":[]},{"level":3,"title":"寄存器","slug":"寄存器","link":"#寄存器","children":[]}]},{"level":2,"title":"指令集","slug":"指令集","link":"#指令集","children":[{"level":3,"title":"MOV","slug":"mov","link":"#mov","children":[]},{"level":3,"title":"PUSH","slug":"push","link":"#push","children":[]},{"level":3,"title":"JMP","slug":"jmp","link":"#jmp","children":[]},{"level":3,"title":"JZ/JNZ","slug":"jz-jnz","link":"#jz-jnz","children":[]},{"level":3,"title":"子函数调用","slug":"子函数调用","link":"#子函数调用","children":[]},{"level":3,"title":"ENT","slug":"ent","link":"#ent","children":[]},{"level":3,"title":"ADJ","slug":"adj","link":"#adj","children":[]},{"level":3,"title":"LEV","slug":"lev","link":"#lev","children":[]},{"level":3,"title":"LEA","slug":"lea","link":"#lea","children":[]},{"level":3,"title":"运算符指令","slug":"运算符指令","link":"#运算符指令","children":[]},{"level":3,"title":"内置函数","slug":"内置函数","link":"#内置函数","children":[]}]},{"level":2,"title":"测试","slug":"测试","link":"#测试","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":25.04,"words":7511},"filePathRelative":"tech/designASimpileCCompiler/2.md","excerpt":"
    \\n

    title: \\"手把手教你构建 C 语言编译器(2)——虚拟机\\"\\ncategory:

    \\n\\n
    \\n

    转载声明

    \\n

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-2/,如有侵权,请联系删除。

    ","autoDesc":true}');export{I as comp,V as data}; +import{_ as l,r as t,o as c,c as r,b as s,e as a,d as n,f as p}from"./app-B4LGNJZ0.js";const o={},i=s("hr",null,null,-1),d=s("p",null,'title: "手把手教你构建 C 语言编译器(2)——虚拟机" category:',-1),b=s("ul",null,[s("li",null,"编译原理 tag:"),s("li",null,"c"),s("li",null,"编译器"),s("li",null,"解释器")],-1),h=s("hr",null,null,-1),u=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),m={href:"https://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},y=p('

    原文内容

    手把手教你构建 C 语言编译器(2)- 虚拟机

    Table of Contents

    1. 1. 计算机的内部工作原理
      1. 1.1. 内存
      2. 1.2. 寄存器
    2. 2. 指令集
      1. 2.1. MOV
      2. 2.2. PUSH
      3. 2.3. JMP
      4. 2.4. JZ/JNZ
      5. 2.5. 子函数调用
      6. 2.6. ENT
      7. 2.7. ADJ
      8. 2.8. LEV
      9. 2.9. LEA
      10. 2.10. 运算符指令
      11. 2.11. 内置函数
    3. 3. 测试
    4. 4. 小结

    这是“手把手教你构建 C 语言编译器”系列的第三篇,本章我们要构建一台虚拟的电脑,设计我们自己的指令集,运行我们的指令集,说得通俗一点就是自己实现一套汇编语言。它们将作为我们的编译器最终输出的目标代码。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),f={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},x={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},w={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},v={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},L=p('

    计算机的内部工作原理

    计算机中有三个基本部件需要我们关注:CPU、寄存器及内存。代码(汇编指令)以二进制的形式保存在内存中;CPU 从中一条条地加载指令执行;程序运行的状态保存在寄存器中。

    内存

    内存用于存储数据,这里的数据可以是代码,也可以是其它的数据。现代操作系统在操作内存时,并不是直接处理”物理内存“,而是操作”虚拟内存“。虚拟内存可以理解为一种映射,它的作用是屏蔽了物理的细节。例如 32 位的机器中,我们可以使用的内存地址为 2^32 = 4G,而电脑上的实际内存可能只有 256 M。操作系统将我们使用的虚拟地址映射到了到实际的内存上。

    当然,我们这里并不需要了解太多,但需要了解的是:进程的内存会被分成几个段:

    1. 代码段(text)用于存放代码(指令)。
    2. 数据段(data)用于存放初始化了的数据,如int i = 10;,就需要存放到数据段中。
    3. 未初始化数据段(bss)用于存放未初始化的数据,如 int i[1000];,因为不关心其中的真正数值,所以单独存放可以节省空间,减少程序的体积。
    4. 栈(stack)用于处理函数调用相关的数据,如调用帧(calling frame)或是函数的局部变量等。
    5. 堆(heap)用于为程序动态分配内存。

    它们在内存中的位置类似于下图:

    +------------------+
    | stack | | high address
    | ... v |
    | |
    | |
    | |
    | |
    | ... ^ |
    | heap | |
    +------------------+
    | bss segment |
    +------------------+
    | data segment |
    +------------------+
    | text segment | low address
    +------------------+

    我们的虚拟机并不打算模拟完整的计算机,因此简单起见,我们只关心三个内容:代码段、数据段以及栈。其中的数据段我们只用来存放字符串,因为我们的编译器并不支持初始化变量,因此我们也不需要未初始化数据段。

    当用户的程序需要分配内存时,理论上我们的虚拟机需要维护一个堆用于内存分配,但实际实现上较为复杂且与编译无关,故我们引入一个指令MSET,使我们能直接使用编译器(解释器)中的内存。

    综上,我们需要首先在全局添加如下代码:

    int *text,            // text segment
    *old_text, // for dump text segment
    *stack; // stack
    char *data; // data segment

    注意这里的类型,虽然是int型,但理解起来应该作为无符号的整型,因为我们会在代码段(text)中存放如指针/内存地址的数据,它们就是无符号的。其中数据段(data)由于只存放字符串,所以是 char * 型的。

    接着,在main函数中加入初始化代码,真正为其分配内存:

    int main() {
    close(fd);
    ...

    // allocate memory for virtual machine
    if (!(text = old_text = malloc(poolsize))) {
    printf("could not malloc(%d) for text area\\n", poolsize);
    return -1;
    }
    if (!(data = malloc(poolsize))) {
    printf("could not malloc(%d) for data area\\n", poolsize);
    return -1;
    }
    if (!(stack = malloc(poolsize))) {
    printf("could not malloc(%d) for stack area\\n", poolsize);
    return -1;
    }

    memset(text, 0, poolsize);
    memset(data, 0, poolsize);
    memset(stack, 0, poolsize);

    ...
    program();
    }

    寄存器

    计算机中的寄存器用于存放计算机的运行状态,真正的计算机中有许多不同种类的寄存器,但我们的虚拟机中只使用 4 个寄存器,分别如下:

    1. PC 程序计数器,它存放的是一个内存地址,该地址中存放着 下一条 要执行的计算机指令。
    2. SP 指针寄存器,永远指向当前的栈顶。注意的是由于栈是位于高地址并向低地址增长的,所以入栈时 SP 的值减小。
    3. BP 基址指针。也是用于指向栈的某些位置,在调用函数时会使用到它。
    4. AX 通用寄存器,我们的虚拟机中,它用于存放一条指令执行后的结果。

    要理解这些寄存器的作用,需要去理解程序运行中会有哪些状态。而这些寄存器只是用于保存这些状态的。

    在全局中加入如下定义:

    int *pc, *bp, *sp, ax, cycle; // virtual machine registers

    main 函数中加入初始化代码,注意的是PC在初始应指向目标代码中的main函数,但我们还没有写任何编译相关的代码,因此先不处理。代码如下:

    memset(stack, 0, poolsize);
    ...

    bp = sp = (int *)((int)stack + poolsize);
    ax = 0;

    ...
    program();

    与 CPU 相关的是指令集,我们将专门作为一个小节。

    指令集

    指令集是 CPU 能识别的命令的集合,也可以说是 CPU 能理解的语言。这里我们要为我们的虚拟机构建自己的指令集。它们基于 x86 的指令集,但更为简单。

    首先在全局变量中加入一个枚举类型,这是我们要支持的全部指令:

    // instructions
    enum { LEA ,IMM ,JMP ,CALL,JZ ,JNZ ,ENT ,ADJ ,LEV ,LI ,LC ,SI ,SC ,PUSH,
    OR ,XOR ,AND ,EQ ,NE ,LT ,GT ,LE ,GE ,SHL ,SHR ,ADD ,SUB ,MUL ,DIV ,MOD ,
    OPEN,READ,CLOS,PRTF,MALC,MSET,MCMP,EXIT };

    这些指令的顺序安排是有意的,稍后你会看到,带有参数的指令在前,没有参数的指令在后。这种顺序的唯一作用就是在打印调试信息时更加方便。但我们讲解的顺序并不依据它。

    MOV

    MOV 是所有指令中最基础的一个,它用于将数据放进寄存器或内存地址,有点类似于 C 语言中的赋值语句。x86 的 MOV 指令有两个参数,分别是源地址和目标地址:MOV dest, source (Intel 风格),表示将 source 的内容放在 dest 中,它们可以是一个数、寄存器或是一个内存地址。

    一方面,我们的虚拟机只有一个寄存器,另一方面,识别这些参数的类型(是数据还是地址)是比较困难的,因此我们将 MOV 指令拆分成 5 个指令,这些指令只接受一个参数,如下:

    1. IMM <num><num> 放入寄存器 ax 中。
    2. LC 将对应地址中的字符载入 ax 中,要求 ax 中存放地址。
    3. LI 将对应地址中的整数载入 ax 中,要求 ax 中存放地址。
    4. SCax 中的数据作为字符存放入地址中,要求栈顶存放地址。
    5. SIax 中的数据作为整数存放入地址中,要求栈顶存放地址。

    你可能会觉得将一个指令变成了许多指令,整个系统就变得复杂了,但实际情况并非如此。首先是 x86 的 MOV 指令其实有许多变种,根据类型的不同有 MOVB, MOVW 等指令,我们这里的 LC/SCLI/SI 就是对应字符型和整型的存取操作。

    但最为重要的是,通过将 MOV 指令拆分成这些指令,只有 IMM 需要有参数,且不需要判断类型,所以大大简化了实现的难度。

    eval() 函数中加入下列代码:

    void eval() {
    int op, *tmp;
    while (1) {
    if (op == IMM) {ax = *pc++;} // load immediate value to ax
    else if (op == LC) {ax = *(char *)ax;} // load character to ax, address in ax
    else if (op == LI) {ax = *(int *)ax;} // load integer to ax, address in ax
    else if (op == SC) {ax = *(char *)*sp++ = ax;} // save character to address, value in ax, address on stack
    else if (op == SI) {*(int *)*sp++ = ax;} // save integer to address, value in ax, address on stack
    }

    ...
    return 0;
    }

    其中的 *sp++ 的作用是退栈,相当于 POP 操作。

    这里要解释的一点是,为什么 SI/SC 指令中,地址存放在栈中,而 LI/LC 中,地址存放在 ax 中?原因是默认计算的结果是存放在 ax 中的,而地址通常是需要通过计算获得,所以执行 LI/LC 时直接从 ax 取值会更高效。另一点是我们的 PUSH 指令只能将 ax 的值放到栈上,而不能以值作为参数,详细见下文。

    PUSH

    在 x86 中,PUSH 的作用是将值或寄存器,而在我们的虚拟机中,它的作用是将 ax 的值放入栈中。这样做的主要原因是为了简化虚拟机的实现,并且我们也只有一个寄存器 ax 。代码如下:

    else if (op == PUSH) {*--sp = ax;}                                     // push the value of ax onto the stack

    JMP

    JMP <addr> 是跳转指令,无条件地将当前的 PC 寄存器设置为指定的 <addr>,实现如下:

    else if (op == JMP)  {pc = (int *)*pc;}                                // jump to the address

    需要注意的是,pc 寄存器指向的是 下一条 指令。所以此时它存放的是 JMP 指令的参数,即 <addr> 的值。

    JZ/JNZ

    为了实现 if 语句,我们需要条件判断相关的指令。这里我们只实现两个最简单的条件判断,即结果(ax)为零或不为零情况下的跳转。

    实现如下:

    else if (op == JZ)   {pc = ax ? pc + 1 : (int *)*pc;}                   // jump if ax is zero
    else if (op == JNZ) {pc = ax ? (int *)*pc : pc + 1;} // jump if ax is not zero

    子函数调用

    这是汇编中最难理解的部分,所以合在一起说,要引入的命令有 CALL, ENT, ADJLEV

    首先我们介绍 CALL <addr>RET 指令,CALL 的作用是跳转到地址为 <addr> 的子函数,RET 则用于从子函数中返回。

    为什么不能直接使用 JMP 指令呢?原因是当我们从子函数中返回时,程序需要回到跳转之前的地方继续运行,这就需要事先将这个位置信息存储起来。反过来,子函数要返回时,就需要获取并恢复这个信息。因此实际中我们将 PC 保存在栈中。如下:

    else if (op == CALL) {*--sp = (int)(pc+1); pc = (int *)*pc;}           // call subroutine
    //else if (op == RET) {pc = (int *)*sp++;} // return from subroutine;

    这里我们把 RET 相关的内容注释了,是因为之后我们将用 LEV 指令来代替它。

    在实际调用函数时,不仅要考虑函数的地址,还要考虑如何传递参数和如何返回结果。这里我们约定,如果子函数有返回结果,那么就在返回时保存在 ax 中,它可以是一个值,也可以是一个地址。那么参数的传递呢?

    各种编程语言关于如何调用子函数有不同的约定,例如 C 语言的调用标准是:

    1. 由调用者将参数入栈。
    2. 调用结束时,由调用者将参数出栈。
    3. 参数逆序入栈。
    ',59),B={href:"https://en.wikipedia.org/wiki/X86_calling_conventions",target:"_blank",rel:"noopener noreferrer"},M=p('
    int callee(int, int, int);

    int caller(void)
    {
    int i, ret;

    ret = callee(1, 2, 3);
    ret += 5;
    return ret;
    }

    会生成如下的 x86 汇编代码:

    caller:
    ; make new call frame
    push ebp
    mov ebp, esp
    sub 1, esp ; save stack for variable: i
    ; push call arguments
    push 3
    push 2
    push 1
    ; call subroutine 'callee'
    call callee
    ; remove arguments from frame
    add esp, 12
    ; use subroutine result
    add eax, 5
    ; restore old call frame
    mov esp, ebp
    pop ebp
    ; return
    ret

    上面这段代码在我们自己的虚拟机里会有几个问题:

    1. push ebp,但我们的 PUSH 指令并无法指定寄存器。
    2. mov ebp, esp,我们的 MOV 指令同样功能不足。
    3. add esp, 12,也是一样的问题(尽管我们还没定义)。

    也就是说由于我们的指令过于简单(如只能操作ax寄存器),所以用上面提到的指令,我们连函数调用都无法实现。而我们又不希望扩充现有指令的功能,因为这样实现起来就会变得复杂,因此我们采用的方法是增加指令集。毕竟我们不是真正的计算机,增加指令会消耗许多资源(钱)。

    ENT

    ENT <size> 指的是 enter,用于实现 ‘make new call frame’ 的功能,即保存当前的栈指针,同时在栈上保留一定的空间,用以存放局部变量。对应的汇编代码为:

    ; make new call frame
    push ebp
    mov ebp, esp
    sub 1, esp ; save stack for variable: i

    实现如下:

    else if (op == ENT)  {*--sp = (int)bp; bp = sp; sp = sp - *pc++;}      // make new stack frame

    ADJ

    ADJ <size> 用于实现 ‘remove arguments from frame’。在将调用子函数时压入栈中的数据清除,本质上是因为我们的 ADD 指令功能有限。对应的汇编代码为:

    ; remove arguments from frame
    add esp, 12

    实现如下:

    else if (op == ADJ)  {sp = sp + *pc++;}                                // add esp, <size>

    LEV

    本质上这个指令并不是必需的,只是我们的指令集中并没有 POP 指令。并且三条指令写来比较麻烦且浪费空间,所以用一个指令代替。对应的汇编指令为:

    ; restore old call frame
    mov esp, ebp
    pop ebp
    ; return
    ret

    具体的实现如下:

    else if (op == LEV)  {sp = bp; bp = (int *)*sp++; pc = (int *)*sp++;}  // restore call frame and PC

    注意的是,LEV 已经把 RET 的功能包含了,所以我们不再需要 RET 指令。

    LEA

    上面的一些指令解决了调用帧的问题,但还有一个问题是如何在子函数中获得传入的参数。这里我们首先要了解的是当参数调用时,栈中的调用帧是什么样的。我们依旧用上面的例子(只是现在用“顺序”调用参数):

    sub_function(arg1, arg2, arg3);

    | .... | high address
    +---------------+
    | arg: 1 | new_bp + 4
    +---------------+
    | arg: 2 | new_bp + 3
    +---------------+
    | arg: 3 | new_bp + 2
    +---------------+
    |return address | new_bp + 1
    +---------------+
    | old BP | <- new BP
    +---------------+
    | local var 1 | new_bp - 1
    +---------------+
    | local var 2 | new_bp - 2
    +---------------+
    | .... | low address

    所以为了获取第一个参数,我们需要得到 new_bp + 4,但就如上面的说,我们的 ADD 指令无法操作除 ax 外的寄存器,所以我们提供了一个新的指令:LEA <offset>

    实现如下:

    else if (op == LEA)  {ax = (int)(bp + *pc++);}                         // load address for arguments.

    以上就是我们为了实现函数调用需要的指令了。

    运算符指令

    我们为 C 语言中支持的运算符都提供对应汇编指令。每个运算符都是二元的,即有两个参数,第一个参数放在栈顶,第二个参数放在 ax 中。这个顺序要特别注意。因为像 -/ 之类的运算符是与参数顺序有关的。计算后会将栈顶的参数退栈,结果存放在寄存器 ax 中。因此计算结束后,两个参数都无法取得了(汇编的意义上,存在内存地址上就另当别论)。

    实现如下:

    else if (op == OR)  ax = *sp++ | ax;
    else if (op == XOR) ax = *sp++ ^ ax;
    else if (op == AND) ax = *sp++ & ax;
    else if (op == EQ) ax = *sp++ == ax;
    else if (op == NE) ax = *sp++ != ax;
    else if (op == LT) ax = *sp++ < ax;
    else if (op == LE) ax = *sp++ <= ax;
    else if (op == GT) ax = *sp++ > ax;
    else if (op == GE) ax = *sp++ >= ax;
    else if (op == SHL) ax = *sp++ << ax;
    else if (op == SHR) ax = *sp++ >> ax;
    else if (op == ADD) ax = *sp++ + ax;
    else if (op == SUB) ax = *sp++ - ax;
    else if (op == MUL) ax = *sp++ * ax;
    else if (op == DIV) ax = *sp++ / ax;
    else if (op == MOD) ax = *sp++ % ax;

    内置函数

    写的程序要”有用“,除了核心的逻辑外还需要输入输出,例如 C 语言中我们经常使用的 printf 函数就是用于输出。但是 printf 函数的实现本身就十分复杂,如果我们的编译器要达到自举,就势必要实现 printf 之类的函数,但它又与编译器没有太大的联系,因此我们继续实现新的指令,从虚拟机的角度予以支持。

    编译器中我们需要用到的函数有:exit, open, close, read, printf, malloc, memsetmemcmp。代码如下:

    else if (op == EXIT) { printf("exit(%d)", *sp); return *sp;}
    else if (op == OPEN) { ax = open((char *)sp[1], sp[0]); }
    else if (op == CLOS) { ax = close(*sp);}
    else if (op == READ) { ax = read(sp[2], (char *)sp[1], *sp); }
    else if (op == PRTF) { tmp = sp + pc[1]; ax = printf((char *)tmp[-1], tmp[-2], tmp[-3], tmp[-4], tmp[-5], tmp[-6]); }
    else if (op == MALC) { ax = (int)malloc(*sp);}
    else if (op == MSET) { ax = (int)memset((char *)sp[2], sp[1], *sp);}
    else if (op == MCMP) { ax = memcmp((char *)sp[2], (char *)sp[1], *sp);}

    这里的原理是,我们的电脑上已经有了这些函数的实现,因此编译编译器时,这些函数的二进制代码就被编译进了我们的编译器,因此在我们的编译器/虚拟机上运行我们提供的这些指令时,这些函数就是可用的。换句话说就是不需要我们自己去实现了。

    最后再加上一个错误判断:

    else {
    printf("unknown instruction:%d\\n", op);
    return -1;
    }

    测试

    下面我们用我们的汇编写一小段程序,来计算 10+20,在 main 函数中加入下列代码:

    int main(int argc, char *argv[])
    {
    ax = 0;
    ...

    i = 0;
    text[i++] = IMM;
    text[i++] = 10;
    text[i++] = PUSH;
    text[i++] = IMM;
    text[i++] = 20;
    text[i++] = ADD;
    text[i++] = PUSH;
    text[i++] = EXIT;
    pc = text;

    ...
    program();
    }

    编译程序 gcc xc-tutor.c,运行程序:./a.out hello.c。输出

    exit(30)

    另外,我们的代码里有一些指针的强制转换,默认是 32 位的,因此在 64 位机器下,会出现 segmentation fault,解决方法(二选一):

    1. 编译时加上 -m32 参数:gcc -m32 xc-tutor.c
    2. 在代码的开头,增加 #define int long longlong long 是 64 位的,不会出现强制转换后的问题。

    注意我们的之前的程序需要指令一个源文件,只是现在还用不着,但从结果可以看出,我们的虚拟机还是工作良好的。

    小结

    本章中我们回顾了计算机的内部运行原理,并仿照 x86 汇编指令设计并实现了我们自己的指令集。希望通过本章的学习,你能对计算机程序的原理有一定的了解,同时能对汇编语言有一定的概念,因为汇编语言就是 C 编译器的输出。

    ',50),P={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-1",target:"_blank",rel:"noopener noreferrer"},D=s("table",null,[s("tbody",null,[s("tr",null,[s("td",{class:"code"},[s("pre",null,[s("span",{class:"line"},"git clone -b step-1 https://github.com/lotabout/write-a-C-interpreter"),s("br")])])])])],-1),S=s("p",null,"实际计算机中,添加一个新的指令需要设计许多新的电路,会增加许多的成本,但我们的虚拟机中,新的指令几乎不消耗资源,因此我们可以利用这一点,用更多的指令来完成更多的功能,从而简化具体的实现。",-1);function j(z,T){const e=t("ExternalLinkIcon");return c(),r("div",null,[i,d,b,h,u,s("p",null,[a("本文转自 "),s("a",m,[a("https://lotabout.me/2015/write-a-C-interpreter-2/"),n(e)]),a(",如有侵权,请联系删除。")]),y,s("ol",null,[s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(0)——前言"),n(e)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(1)——设计"),n(e)])]),s("li",null,[s("a",x,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),n(e)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),n(e)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(4)——递归下降"),n(e)])]),s("li",null,[s("a",w,[a("手把手教你构建 C 语言编译器(5)——变量定义"),n(e)])]),s("li",null,[s("a",_,[a("手把手教你构建 C 语言编译器(6)——函数定义"),n(e)])]),s("li",null,[s("a",C,[a("手把手教你构建 C 语言编译器(7)——语句"),n(e)])]),s("li",null,[s("a",A,[a("手把手教你构建 C 语言编译器(8)——表达式"),n(e)])]),s("li",null,[s("a",v,[a("手把手教你构建 C 语言编译器(9)——总结"),n(e)])])]),L,s("p",null,[a("事先声明一下,我们的编译器参数是顺序入栈的,下面的例子(C 语言调用标准)取自 "),s("a",B,[a("维基百科"),n(e)]),a(":")]),M,s("p",null,[a("本章的代码可以在 "),s("a",P,[a("Github"),n(e)]),a(" 上下载,也可以直接 clone")]),D,S])}const I=l(o,[["render",j],["__file","2.html.vue"]]),V=JSON.parse('{"path":"/tech/designASimpileCCompiler/2.html","title":"转载声明","lang":"zh-CN","frontmatter":{"description":"title: \\"手把手教你构建 C 语言编译器(2)——虚拟机\\" category: 编译原理 tag: c 编译器 解释器 转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-2/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(2)- 虚拟机 Table of Cont...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/2.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"转载声明"}],["meta",{"property":"og:description","content":"title: \\"手把手教你构建 C 语言编译器(2)——虚拟机\\" category: 编译原理 tag: c 编译器 解释器 转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-2/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(2)- 虚拟机 Table of Cont..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"转载声明\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"计算机的内部工作原理","slug":"计算机的内部工作原理","link":"#计算机的内部工作原理","children":[{"level":3,"title":"内存","slug":"内存","link":"#内存","children":[]},{"level":3,"title":"寄存器","slug":"寄存器","link":"#寄存器","children":[]}]},{"level":2,"title":"指令集","slug":"指令集","link":"#指令集","children":[{"level":3,"title":"MOV","slug":"mov","link":"#mov","children":[]},{"level":3,"title":"PUSH","slug":"push","link":"#push","children":[]},{"level":3,"title":"JMP","slug":"jmp","link":"#jmp","children":[]},{"level":3,"title":"JZ/JNZ","slug":"jz-jnz","link":"#jz-jnz","children":[]},{"level":3,"title":"子函数调用","slug":"子函数调用","link":"#子函数调用","children":[]},{"level":3,"title":"ENT","slug":"ent","link":"#ent","children":[]},{"level":3,"title":"ADJ","slug":"adj","link":"#adj","children":[]},{"level":3,"title":"LEV","slug":"lev","link":"#lev","children":[]},{"level":3,"title":"LEA","slug":"lea","link":"#lea","children":[]},{"level":3,"title":"运算符指令","slug":"运算符指令","link":"#运算符指令","children":[]},{"level":3,"title":"内置函数","slug":"内置函数","link":"#内置函数","children":[]}]},{"level":2,"title":"测试","slug":"测试","link":"#测试","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":25.04,"words":7511},"filePathRelative":"tech/designASimpileCCompiler/2.md","excerpt":"
    \\n

    title: \\"手把手教你构建 C 语言编译器(2)——虚拟机\\"\\ncategory:

    \\n\\n
    \\n

    转载声明

    \\n

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-2/,如有侵权,请联系删除。

    ","autoDesc":true}');export{I as comp,V as data}; diff --git a/assets/3.html-CZB5joNY.js b/assets/3.html-D9pPtuiP.js similarity index 99% rename from assets/3.html-CZB5joNY.js rename to assets/3.html-D9pPtuiP.js index 576c6fb..9d3ca87 100644 --- a/assets/3.html-CZB5joNY.js +++ b/assets/3.html-D9pPtuiP.js @@ -1 +1 @@ -import{_ as l,r,o as c,c as t,b as s,e as a,d as e,f as p}from"./app-B69Gl_S-.js";const i={},o=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},d=p('

    原文内容

    手把手教你构建 C 语言编译器(3)- 词法分析器

    Table of Contents

    1. 1. 什么是词法分析器
    2. 2. 词法分析器与编译器
    3. 3. 词法分析器的实现
      1. 3.1. 支持的标记
      2. 3.2. 词法分析器的框架
      3. 3.3. 换行符
      4. 3.4. 宏定义
      5. 3.5. 标识符与符号表
      6. 3.6. 数字
      7. 3.7. 字符串
      8. 3.8. 注释
      9. 3.9. 其它
      10. 3.10. 关键字与内置函数
    4. 4. 代码
    5. 5. 小结

    本章我们要讲解如何构建词法分析器。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),k={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},h={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},w={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},_=p('

    什么是词法分析器

    简而言之,词法分析器用于对源码字符串做预处理,以减少语法分析器的复杂程度。

    词法分析器以源码字符串为输入,输出为标记流(token stream),即一连串的标记,每个标记通常包括: (token, token value) 即标记本身和标记的值。例如,源码中若包含一个数字 '998' ,词法分析器将输出 (Number, 998),即(数字,998)。再例如:

    2 + 3 * (4 - 5)
    =>
    (Number, 2) Add (Number, 3) Multiply Left-Bracket (Number, 4) Subtract (Number, 5) Right-Bracket

    通过词法分析器的预处理,语法分析器的复杂度会大大降低,这点在后面的语法分析器我们就能体会。

    词法分析器与编译器

    要是深入词法分析器,你就会发现,它的本质上也是编译器。我们的编译器是以标记流为输入,输出汇编代码,而词法分析器则是以源码字符串为输入,输出标记流。

                       +-------+                      +--------+
    -- source code --> | lexer | --> token stream --> | parser | --> assembly
    +-------+ +--------+

    在这个前提下,我们可以这样认为:直接从源代码编译成汇编代码是很困难的,因为输入的字符串比较难处理。所以我们先编写一个较为简单的编译器(词法分析器)来将字符串转换成标记流,而标记流对于语法分析器而言就容易处理得多了。

    词法分析器的实现

    由于词法分析的工作很常见,但又枯燥且容易出错,所以人们已经开发出了许多工具来生成词法分析器,如 lex, flex。这些工具允许我们通过正则表达式来识别标记。

    这里注意的是,我们并不会一次性地将所有源码全部转换成标记流,原因有二:

    1. 字符串转换成标记流有时是有状态的,即与代码的上下文是有关系的。
    2. 保存所有的标记流没有意义且浪费空间。

    所以实际的处理方法是提供一个函数(即前几篇中提到的 next()),每次调用该函数则返回下一个标记。

    支持的标记

    在全局中添加如下定义:

    // tokens and classes (operators last and in precedence order)
    enum {
    Num = 128, Fun, Sys, Glo, Loc, Id,
    Char, Else, Enum, If, Int, Return, Sizeof, While,
    Assign, Cond, Lor, Lan, Or, Xor, And, Eq, Ne, Lt, Gt, Le, Ge, Shl, Shr, Add, Sub, Mul, Div, Mod, Inc, Dec, Brak
    };

    这些就是我们要支持的标记符。例如,我们会将 = 解析为 Assign;将 == 解析为 Eq;将 != 解析为 Ne 等等。

    所以这里我们会有这样的印象,一个标记(token)可能包含多个字符,且多数情况下如此。而词法分析器能减小语法分析复杂度的原因,正是因为它相当于通过一定的编码(更多的标记)来压缩了源码字符串。

    当然,上面这些标记是有顺序的,跟它们在 C 语言中的优先级有关,如 *(Mul) 的优先级就要高于 +(Add)。它们的具体使用在后面的语法分析中会提到。

    最后要注意的是还有一些字符,它们自己就构成了标记,如右方括号 ] 或波浪号 ~ 等。我们不另外处理它们的原因是:

    1. 它们是单字符的,即并不是多个字符共同构成标记(如 == 需要两个字符);
    2. 它们不涉及优先级关系。

    词法分析器的框架

    next() 函数的主体:

    void next() {
    char *last_pos;
    int hash;

    while (token = *src) {
    ++src;
    // parse token here
    }
    return;
    }

    这里的一个问题是,为什么要用 while 循环呢?这就涉及到编译器(记得我们说过词法分析器也是某种意义上的编译器)的一个问题:如何处理错误?

    对词法分析器而言,若碰到了一个我们不认识的字符该怎么处理?一般处理的方法有两种:

    1. 指出错误发生的位置,并退出整个程序
    2. 指出错误发生的位置,跳过当前错误并继续编译

    这个 while 循环的作用就是跳过这些我们不识别的字符,我们同时还用它来处理空白字符。我们知道,C 语言中空格是用来作为分隔用的,并不作为语法的一部分。因此在实现中我们将它作为“不识别”的字符,这个 while 循环可以用来跳过它。

    换行符

    换行符和空格类似,但有一点不同,每次遇到换行符,我们需要将当前的行号加一:

    // parse token here
    ...

    if (token == '\\n') {
    ++line;
    }
    ...

    宏定义

    C 语言的宏定义以字符 # 开头,如 # include <stdio.h>。我们的编译器并不支持宏定义,所以直接跳过它们。

    else if (token == '#') {
    // skip macro, because we will not support it
    while (*src != 0 && *src != '\\n') {
    src++;
    }
    }

    标识符与符号表

    标识符(identifier)可以理解为变量名。对于语法分析而言,我们并不关心一个变量具体叫什么名字,而只关心这个变量名代表的唯一标识。例如 int a; 定义了变量 a,而之后的语句 a = 10,我们需要知道这两个 a 指向的是同一个变量。

    基于这个理由,词法分析器会把扫描到的标识符全都保存到一张表中,遇到新的标识符就去查这张表,如果标识符已经存在,就返回它的唯一标识。

    那么我们怎么表示标识符呢?如下:

    struct identifier {
    int token;
    int hash;
    char * name;
    int class;
    int type;
    int value;
    int Bclass;
    int Btype;
    int Bvalue;
    }

    这里解释一下具体的含义:

    1. token:该标识符返回的标记,理论上所有的变量返回的标记都应该是 Id,但实际上由于我们还将在符号表中加入关键字如 if, while 等,它们都有对应的标记。
    2. hash:顾名思义,就是这个标识符的哈希值,用于标识符的快速比较。
    3. name:存放标识符本身的字符串。
    4. class:该标识符的类别,如数字,全局变量或局部变量等。
    5. type:标识符的类型,即如果它是个变量,变量是 int 型、char 型还是指针型。
    6. value:存放这个标识符的值,如标识符是函数,刚存放函数的地址。
    7. BXXXX:C 语言中标识符可以是全局的也可以是局部的,当局部标识符的名字与全局标识符相同时,用作保存全局标识符的信息。

    由上可以看出,我们实现的词法分析器与传统意义上的词法分析器不太相同。传统意义上的符号表只需要知道标识符的唯一标识即可,而我们还存放了一些只有语法分析器才会得到的信息,如 type

    由于我们的目标是能自举,而我们定义的语法不支持 struct,故而使用下列方式。

    Symbol table:
    ----+-----+----+----+----+-----+-----+-----+------+------+----
    .. |token|hash|name|type|class|value|btype|bclass|bvalue| ..
    ----+-----+----+----+----+-----+-----+-----+------+------+----
    |<--- one single identifier --->|

    即用一个整型数组来保存相关的ID信息。每个ID占用数组中的9个空间,分析标识符的相关代码如下:

    int token_val;                // value of current token (mainly for number)
    int *current_id, // current parsed ID
    *symbols; // symbol table

    // fields of identifier
    enum {Token, Hash, Name, Type, Class, Value, BType, BClass, BValue, IdSize};


    void next() {
    ...

    else if ((token >= 'a' && token <= 'z') || (token >= 'A' && token <= 'Z') || (token == '_')) {

    // parse identifier
    last_pos = src - 1;
    hash = token;

    while ((*src >= 'a' && *src <= 'z') || (*src >= 'A' && *src <= 'Z') || (*src >= '0' && *src <= '9') || (*src == '_')) {
    hash = hash * 147 + *src;
    src++;
    }

    // look for existing identifier, linear search
    current_id = symbols;
    while (current_id[Token]) {
    if (current_id[Hash] == hash && !memcmp((char *)current_id[Name], last_pos, src - last_pos)) {
    //found one, return
    token = current_id[Token];
    return;
    }
    current_id = current_id + IdSize;
    }


    // store new ID
    current_id[Name] = (int)last_pos;
    current_id[Hash] = hash;
    token = current_id[Token] = Id;
    return;
    }
    ...
    }

    查找已有标识符的方法是线性查找 symbols 表。

    数字

    数字中较为复杂的一点是需要支持十进制、十六进制及八进制。逻辑也较为直接,可能唯一不好理解的是获取十六进制的值相关的代码。

    token_val = token_val * 16 + (token & 15) + (token >= 'A' ? 9 : 0);

    这里要注意的是在ASCII码中,字符a对应的十六进制值是 61, A41,故通过 (token & 15) 可以得到个位数的值。其它就不多说了,这里这样写的目的是装B(其实是抄 c4 的源代码的)。

    void next() {
    ...


    else if (token >= '0' && token <= '9') {
    // parse number, three kinds: dec(123) hex(0x123) oct(017)
    token_val = token - '0';
    if (token_val > 0) {
    // dec, starts with [1-9]
    while (*src >= '0' && *src <= '9') {
    token_val = token_val*10 + *src++ - '0';
    }
    } else {
    // starts with number 0
    if (*src == 'x' || *src == 'X') {
    //hex
    token = *++src;
    while ((token >= '0' && token <= '9') || (token >= 'a' && token <= 'f') || (token >= 'A' && token <= 'F')) {
    token_val = token_val * 16 + (token & 15) + (token >= 'A' ? 9 : 0);
    token = *++src;
    }
    } else {
    // oct
    while (*src >= '0' && *src <= '7') {
    token_val = token_val*8 + *src++ - '0';
    }
    }
    }

    token = Num;
    return;
    }

    ...
    }

    字符串

    在分析时,如果分析到字符串,我们需要将它存放到前一篇文章中说的 data 段中。然后返回它在 data 段中的地址。另一个特殊的地方是我们需要支持转义符。例如用 \\n 表示换行符。由于本编译器的目的是达到自己编译自己,所以代码中并没有支持除 \\n 的转义符,如 \\t, \\r 等,但仍支持 \\a 表示字符 a 的语法,如 \\" 表示 "

    在分析时,我们将同时分析单个字符如 'a' 和字符串如 "a string"。若得到的是单个字符,我们以 Num 的形式返回。相关代码如下:

    void next() {
    ...

    else if (token == '"' || token == '\\'') {
    // parse string literal, currently, the only supported escape
    // character is '\\n', store the string literal into data.
    last_pos = data;
    while (*src != 0 && *src != token) {
    token_val = *src++;
    if (token_val == '\\\\') {
    // escape character
    token_val = *src++;
    if (token_val == 'n') {
    token_val = '\\n';
    }
    }

    if (token == '"') {
    *data++ = token_val;
    }
    }

    src++;
    // if it is a single character, return Num token
    if (token == '"') {
    token_val = (int)last_pos;
    } else {
    token = Num;
    }

    return;
    }
    }

    注释

    在我们的 C 语言中,只支持 // 类型的注释,不支持 /* comments */ 的注释。

    void next() {
    ...

    else if (token == '/') {
    if (*src == '/') {
    // skip comments
    while (*src != 0 && *src != '\\n') {
    ++src;
    }
    } else {
    // divide operator
    token = Div;
    return;
    }
    }

    ...
    }

    这里我们要额外介绍 lookahead 的概念,即提前看多个字符。上述代码中我们看到,除了跳过注释,我们还可能返回除号 /(Div) 标记。

    提前看字符的原理是:有一个或多个标记是以同样的字符开头的(如本小节中的注释与除号),因此只凭当前的字符我们并无法确定具体应该解释成哪一个标记,所以只能再向前查看字符,如本例需向前查看一个字符,若是 / 则说明是注释,反之则是除号。

    我们之前说过,词法分析器本质上也是编译器,其实提前看字符的概念也存在于编译器,只是这时就是提前看k个“标记”而不是“字符”了。平时听到的 LL(k) 中的 k 就是需要向前看的标记的个数了。

    另外,我们用词法分析器将源码转换成标记流,能减小语法分析复杂度,原因之一就是减少了语法分析器需要“向前看”的字符个数。

    其它

    其它的标记的解析就相对容易一些了,我们直接贴上代码:

    void next() {
    ...

    else if (token == '=') {
    // parse '==' and '='
    if (*src == '=') {
    src ++;
    token = Eq;
    } else {
    token = Assign;
    }
    return;
    }
    else if (token == '+') {
    // parse '+' and '++'
    if (*src == '+') {
    src ++;
    token = Inc;
    } else {
    token = Add;
    }
    return;
    }
    else if (token == '-') {
    // parse '-' and '--'
    if (*src == '-') {
    src ++;
    token = Dec;
    } else {
    token = Sub;
    }
    return;
    }
    else if (token == '!') {
    // parse '!='
    if (*src == '=') {
    src++;
    token = Ne;
    }
    return;
    }
    else if (token == '<') {
    // parse '<=', '<<' or '<'
    if (*src == '=') {
    src ++;
    token = Le;
    } else if (*src == '<') {
    src ++;
    token = Shl;
    } else {
    token = Lt;
    }
    return;
    }
    else if (token == '>') {
    // parse '>=', '>>' or '>'
    if (*src == '=') {
    src ++;
    token = Ge;
    } else if (*src == '>') {
    src ++;
    token = Shr;
    } else {
    token = Gt;
    }
    return;
    }
    else if (token == '|') {
    // parse '|' or '||'
    if (*src == '|') {
    src ++;
    token = Lor;
    } else {
    token = Or;
    }
    return;
    }
    else if (token == '&') {
    // parse '&' and '&&'
    if (*src == '&') {
    src ++;
    token = Lan;
    } else {
    token = And;
    }
    return;
    }
    else if (token == '^') {
    token = Xor;
    return;
    }
    else if (token == '%') {
    token = Mod;
    return;
    }
    else if (token == '*') {
    token = Mul;
    return;
    }
    else if (token == '[') {
    token = Brak;
    return;
    }
    else if (token == '?') {
    token = Cond;
    return;
    }
    else if (token == '~' || token == ';' || token == '{' || token == '}' || token == '(' || token == ')' || token == ']' || token == ',' || token == ':') {
    // directly return the character as token;
    return;
    }

    ...
    }

    代码较多,但主要逻辑就是向前看一个字符来确定真正的标记。

    关键字与内置函数

    虽然上面写完了词法分析器,但还有一个问题需要考虑,那就是“关键字”,例如 if, while, return 等。它们不能被作为普通的标识符,因为有特殊的含义。

    一般有两种处理方法:

    1. 词法分析器中直接解析这些关键字。
    2. 在语法分析前将关键字提前加入符号表。

    这里我们就采用第二种方法,将它们加入符号表,并提前为它们赋予必要的信息(还记得前面说的标识符 Token 字段吗?)。这样当源代码中出现关键字时,它们会被解析成标识符,但由于符号表中已经有了相关的信息,我们就能知道它们是特殊的关键字。

    内置函数的行为也和关键字类似,不同的只是赋值的信息,在main函数中进行初始化如下:

    // types of variable/function
    enum { CHAR, INT, PTR };
    int *idmain; // the `main` function

    void main() {
    ...

    src = "char else enum if int return sizeof while "
    "open read close printf malloc memset memcmp exit void main";

    // add keywords to symbol table
    i = Char;
    while (i <= While) {
    next();
    current_id[Token] = i++;
    }

    // add library to symbol table
    i = OPEN;
    while (i <= EXIT) {
    next();
    current_id[Class] = Sys;
    current_id[Type] = INT;
    current_id[Value] = i++;
    }

    next(); current_id[Token] = Char; // handle void type
    next(); idmain = current_id; // keep track of main

    ...
    program();
    }

    代码

    ',76),B={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-2",target:"_blank",rel:"noopener noreferrer"},C=p('
    git clone -b step-2 https://github.com/lotabout/write-a-C-interpreter

    上面的代码运行后会出现 ‘Segmentation Falt’,这是正常的,因为它会尝试运行我们上一章创建的虚拟机,但其中并没有任何汇编代码。

    小结

    本章我们为我们的编译器构建了词法分析器,通过本章的学习,我认为有几个要点需要强调:

    1. 词法分析器的作用是对源码字符串进行预处理,作用是减小语法分析器的复杂程度。
    2. 词法分析器本身可以认为是一个编译器,输入是源码,输出是标记流。
    3. lookahead(k) 的概念,即向前看 k 个字符或标记。
    4. 词法分析中如何处理标识符与符号表。

    下一章中,我们将介绍递归下降的语法分析器。我们下一章见。

    ',6);function v(x,D){const n=r("ExternalLinkIcon");return c(),t("div",null,[o,s("p",null,[a("本文转自 "),s("a",b,[a("https://lotabout.me/2015/write-a-C-interpreter-3/"),e(n)]),a(",如有侵权,请联系删除。")]),d,s("ol",null,[s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",h,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",A,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",w,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),_,s("p",null,[a("本章的代码可以在 "),s("a",B,[a("Github"),e(n)]),a(" 上下载,也可以直接 clone")]),C])}const N=l(i,[["render",v],["__file","3.html.vue"]]),S=JSON.parse('{"path":"/tech/designASimpileCCompiler/3.html","title":"手把手教你构建 C 语言编译器(3)——词法分析器","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(3)——词法分析器","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-3/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(3)- 词法分析器 Table of Contents 1. 什么是词法分析器 2. 词法分析器与编译器 3. 词法分析器的实现 3.1. 支持的标记 3.2. 词法分...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/3.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(3)——词法分析器"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-3/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(3)- 词法分析器 Table of Contents 1. 什么是词法分析器 2. 词法分析器与编译器 3. 词法分析器的实现 3.1. 支持的标记 3.2. 词法分..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(3)——词法分析器\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"什么是词法分析器","slug":"什么是词法分析器","link":"#什么是词法分析器","children":[]},{"level":2,"title":"词法分析器与编译器","slug":"词法分析器与编译器","link":"#词法分析器与编译器","children":[]},{"level":2,"title":"词法分析器的实现","slug":"词法分析器的实现","link":"#词法分析器的实现","children":[{"level":3,"title":"支持的标记","slug":"支持的标记","link":"#支持的标记","children":[]},{"level":3,"title":"词法分析器的框架","slug":"词法分析器的框架","link":"#词法分析器的框架","children":[]},{"level":3,"title":"换行符","slug":"换行符","link":"#换行符","children":[]},{"level":3,"title":"宏定义","slug":"宏定义","link":"#宏定义","children":[]},{"level":3,"title":"标识符与符号表","slug":"标识符与符号表","link":"#标识符与符号表","children":[]},{"level":3,"title":"数字","slug":"数字","link":"#数字","children":[]},{"level":3,"title":"字符串","slug":"字符串","link":"#字符串","children":[]},{"level":3,"title":"注释","slug":"注释","link":"#注释","children":[]},{"level":3,"title":"其它","slug":"其它","link":"#其它","children":[]},{"level":3,"title":"关键字与内置函数","slug":"关键字与内置函数","link":"#关键字与内置函数","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":25.21,"words":7563},"filePathRelative":"tech/designASimpileCCompiler/3.md","excerpt":"\\n

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-3/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(3)- 词法分析器

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 什么是词法分析器
    2. \\n
    3. 2. 词法分析器与编译器
    4. \\n
    5. 3. 词法分析器的实现\\n
        \\n
      1. 3.1. 支持的标记
      2. \\n
      3. 3.2. 词法分析器的框架
      4. \\n
      5. 3.3. 换行符
      6. \\n
      7. 3.4. 宏定义
      8. \\n
      9. 3.5. 标识符与符号表
      10. \\n
      11. 3.6. 数字
      12. \\n
      13. 3.7. 字符串
      14. \\n
      15. 3.8. 注释
      16. \\n
      17. 3.9. 其它
      18. \\n
      19. 3.10. 关键字与内置函数
      20. \\n
      \\n
    6. \\n
    7. 4. 代码
    8. \\n
    9. 5. 小结
    10. \\n
    ","autoDesc":true}');export{N as comp,S as data}; +import{_ as l,r,o as c,c as t,b as s,e as a,d as e,f as p}from"./app-B4LGNJZ0.js";const i={},o=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},d=p('

    原文内容

    手把手教你构建 C 语言编译器(3)- 词法分析器

    Table of Contents

    1. 1. 什么是词法分析器
    2. 2. 词法分析器与编译器
    3. 3. 词法分析器的实现
      1. 3.1. 支持的标记
      2. 3.2. 词法分析器的框架
      3. 3.3. 换行符
      4. 3.4. 宏定义
      5. 3.5. 标识符与符号表
      6. 3.6. 数字
      7. 3.7. 字符串
      8. 3.8. 注释
      9. 3.9. 其它
      10. 3.10. 关键字与内置函数
    4. 4. 代码
    5. 5. 小结

    本章我们要讲解如何构建词法分析器。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),k={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},h={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},w={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},_=p('

    什么是词法分析器

    简而言之,词法分析器用于对源码字符串做预处理,以减少语法分析器的复杂程度。

    词法分析器以源码字符串为输入,输出为标记流(token stream),即一连串的标记,每个标记通常包括: (token, token value) 即标记本身和标记的值。例如,源码中若包含一个数字 '998' ,词法分析器将输出 (Number, 998),即(数字,998)。再例如:

    2 + 3 * (4 - 5)
    =>
    (Number, 2) Add (Number, 3) Multiply Left-Bracket (Number, 4) Subtract (Number, 5) Right-Bracket

    通过词法分析器的预处理,语法分析器的复杂度会大大降低,这点在后面的语法分析器我们就能体会。

    词法分析器与编译器

    要是深入词法分析器,你就会发现,它的本质上也是编译器。我们的编译器是以标记流为输入,输出汇编代码,而词法分析器则是以源码字符串为输入,输出标记流。

                       +-------+                      +--------+
    -- source code --> | lexer | --> token stream --> | parser | --> assembly
    +-------+ +--------+

    在这个前提下,我们可以这样认为:直接从源代码编译成汇编代码是很困难的,因为输入的字符串比较难处理。所以我们先编写一个较为简单的编译器(词法分析器)来将字符串转换成标记流,而标记流对于语法分析器而言就容易处理得多了。

    词法分析器的实现

    由于词法分析的工作很常见,但又枯燥且容易出错,所以人们已经开发出了许多工具来生成词法分析器,如 lex, flex。这些工具允许我们通过正则表达式来识别标记。

    这里注意的是,我们并不会一次性地将所有源码全部转换成标记流,原因有二:

    1. 字符串转换成标记流有时是有状态的,即与代码的上下文是有关系的。
    2. 保存所有的标记流没有意义且浪费空间。

    所以实际的处理方法是提供一个函数(即前几篇中提到的 next()),每次调用该函数则返回下一个标记。

    支持的标记

    在全局中添加如下定义:

    // tokens and classes (operators last and in precedence order)
    enum {
    Num = 128, Fun, Sys, Glo, Loc, Id,
    Char, Else, Enum, If, Int, Return, Sizeof, While,
    Assign, Cond, Lor, Lan, Or, Xor, And, Eq, Ne, Lt, Gt, Le, Ge, Shl, Shr, Add, Sub, Mul, Div, Mod, Inc, Dec, Brak
    };

    这些就是我们要支持的标记符。例如,我们会将 = 解析为 Assign;将 == 解析为 Eq;将 != 解析为 Ne 等等。

    所以这里我们会有这样的印象,一个标记(token)可能包含多个字符,且多数情况下如此。而词法分析器能减小语法分析复杂度的原因,正是因为它相当于通过一定的编码(更多的标记)来压缩了源码字符串。

    当然,上面这些标记是有顺序的,跟它们在 C 语言中的优先级有关,如 *(Mul) 的优先级就要高于 +(Add)。它们的具体使用在后面的语法分析中会提到。

    最后要注意的是还有一些字符,它们自己就构成了标记,如右方括号 ] 或波浪号 ~ 等。我们不另外处理它们的原因是:

    1. 它们是单字符的,即并不是多个字符共同构成标记(如 == 需要两个字符);
    2. 它们不涉及优先级关系。

    词法分析器的框架

    next() 函数的主体:

    void next() {
    char *last_pos;
    int hash;

    while (token = *src) {
    ++src;
    // parse token here
    }
    return;
    }

    这里的一个问题是,为什么要用 while 循环呢?这就涉及到编译器(记得我们说过词法分析器也是某种意义上的编译器)的一个问题:如何处理错误?

    对词法分析器而言,若碰到了一个我们不认识的字符该怎么处理?一般处理的方法有两种:

    1. 指出错误发生的位置,并退出整个程序
    2. 指出错误发生的位置,跳过当前错误并继续编译

    这个 while 循环的作用就是跳过这些我们不识别的字符,我们同时还用它来处理空白字符。我们知道,C 语言中空格是用来作为分隔用的,并不作为语法的一部分。因此在实现中我们将它作为“不识别”的字符,这个 while 循环可以用来跳过它。

    换行符

    换行符和空格类似,但有一点不同,每次遇到换行符,我们需要将当前的行号加一:

    // parse token here
    ...

    if (token == '\\n') {
    ++line;
    }
    ...

    宏定义

    C 语言的宏定义以字符 # 开头,如 # include <stdio.h>。我们的编译器并不支持宏定义,所以直接跳过它们。

    else if (token == '#') {
    // skip macro, because we will not support it
    while (*src != 0 && *src != '\\n') {
    src++;
    }
    }

    标识符与符号表

    标识符(identifier)可以理解为变量名。对于语法分析而言,我们并不关心一个变量具体叫什么名字,而只关心这个变量名代表的唯一标识。例如 int a; 定义了变量 a,而之后的语句 a = 10,我们需要知道这两个 a 指向的是同一个变量。

    基于这个理由,词法分析器会把扫描到的标识符全都保存到一张表中,遇到新的标识符就去查这张表,如果标识符已经存在,就返回它的唯一标识。

    那么我们怎么表示标识符呢?如下:

    struct identifier {
    int token;
    int hash;
    char * name;
    int class;
    int type;
    int value;
    int Bclass;
    int Btype;
    int Bvalue;
    }

    这里解释一下具体的含义:

    1. token:该标识符返回的标记,理论上所有的变量返回的标记都应该是 Id,但实际上由于我们还将在符号表中加入关键字如 if, while 等,它们都有对应的标记。
    2. hash:顾名思义,就是这个标识符的哈希值,用于标识符的快速比较。
    3. name:存放标识符本身的字符串。
    4. class:该标识符的类别,如数字,全局变量或局部变量等。
    5. type:标识符的类型,即如果它是个变量,变量是 int 型、char 型还是指针型。
    6. value:存放这个标识符的值,如标识符是函数,刚存放函数的地址。
    7. BXXXX:C 语言中标识符可以是全局的也可以是局部的,当局部标识符的名字与全局标识符相同时,用作保存全局标识符的信息。

    由上可以看出,我们实现的词法分析器与传统意义上的词法分析器不太相同。传统意义上的符号表只需要知道标识符的唯一标识即可,而我们还存放了一些只有语法分析器才会得到的信息,如 type

    由于我们的目标是能自举,而我们定义的语法不支持 struct,故而使用下列方式。

    Symbol table:
    ----+-----+----+----+----+-----+-----+-----+------+------+----
    .. |token|hash|name|type|class|value|btype|bclass|bvalue| ..
    ----+-----+----+----+----+-----+-----+-----+------+------+----
    |<--- one single identifier --->|

    即用一个整型数组来保存相关的ID信息。每个ID占用数组中的9个空间,分析标识符的相关代码如下:

    int token_val;                // value of current token (mainly for number)
    int *current_id, // current parsed ID
    *symbols; // symbol table

    // fields of identifier
    enum {Token, Hash, Name, Type, Class, Value, BType, BClass, BValue, IdSize};


    void next() {
    ...

    else if ((token >= 'a' && token <= 'z') || (token >= 'A' && token <= 'Z') || (token == '_')) {

    // parse identifier
    last_pos = src - 1;
    hash = token;

    while ((*src >= 'a' && *src <= 'z') || (*src >= 'A' && *src <= 'Z') || (*src >= '0' && *src <= '9') || (*src == '_')) {
    hash = hash * 147 + *src;
    src++;
    }

    // look for existing identifier, linear search
    current_id = symbols;
    while (current_id[Token]) {
    if (current_id[Hash] == hash && !memcmp((char *)current_id[Name], last_pos, src - last_pos)) {
    //found one, return
    token = current_id[Token];
    return;
    }
    current_id = current_id + IdSize;
    }


    // store new ID
    current_id[Name] = (int)last_pos;
    current_id[Hash] = hash;
    token = current_id[Token] = Id;
    return;
    }
    ...
    }

    查找已有标识符的方法是线性查找 symbols 表。

    数字

    数字中较为复杂的一点是需要支持十进制、十六进制及八进制。逻辑也较为直接,可能唯一不好理解的是获取十六进制的值相关的代码。

    token_val = token_val * 16 + (token & 15) + (token >= 'A' ? 9 : 0);

    这里要注意的是在ASCII码中,字符a对应的十六进制值是 61, A41,故通过 (token & 15) 可以得到个位数的值。其它就不多说了,这里这样写的目的是装B(其实是抄 c4 的源代码的)。

    void next() {
    ...


    else if (token >= '0' && token <= '9') {
    // parse number, three kinds: dec(123) hex(0x123) oct(017)
    token_val = token - '0';
    if (token_val > 0) {
    // dec, starts with [1-9]
    while (*src >= '0' && *src <= '9') {
    token_val = token_val*10 + *src++ - '0';
    }
    } else {
    // starts with number 0
    if (*src == 'x' || *src == 'X') {
    //hex
    token = *++src;
    while ((token >= '0' && token <= '9') || (token >= 'a' && token <= 'f') || (token >= 'A' && token <= 'F')) {
    token_val = token_val * 16 + (token & 15) + (token >= 'A' ? 9 : 0);
    token = *++src;
    }
    } else {
    // oct
    while (*src >= '0' && *src <= '7') {
    token_val = token_val*8 + *src++ - '0';
    }
    }
    }

    token = Num;
    return;
    }

    ...
    }

    字符串

    在分析时,如果分析到字符串,我们需要将它存放到前一篇文章中说的 data 段中。然后返回它在 data 段中的地址。另一个特殊的地方是我们需要支持转义符。例如用 \\n 表示换行符。由于本编译器的目的是达到自己编译自己,所以代码中并没有支持除 \\n 的转义符,如 \\t, \\r 等,但仍支持 \\a 表示字符 a 的语法,如 \\" 表示 "

    在分析时,我们将同时分析单个字符如 'a' 和字符串如 "a string"。若得到的是单个字符,我们以 Num 的形式返回。相关代码如下:

    void next() {
    ...

    else if (token == '"' || token == '\\'') {
    // parse string literal, currently, the only supported escape
    // character is '\\n', store the string literal into data.
    last_pos = data;
    while (*src != 0 && *src != token) {
    token_val = *src++;
    if (token_val == '\\\\') {
    // escape character
    token_val = *src++;
    if (token_val == 'n') {
    token_val = '\\n';
    }
    }

    if (token == '"') {
    *data++ = token_val;
    }
    }

    src++;
    // if it is a single character, return Num token
    if (token == '"') {
    token_val = (int)last_pos;
    } else {
    token = Num;
    }

    return;
    }
    }

    注释

    在我们的 C 语言中,只支持 // 类型的注释,不支持 /* comments */ 的注释。

    void next() {
    ...

    else if (token == '/') {
    if (*src == '/') {
    // skip comments
    while (*src != 0 && *src != '\\n') {
    ++src;
    }
    } else {
    // divide operator
    token = Div;
    return;
    }
    }

    ...
    }

    这里我们要额外介绍 lookahead 的概念,即提前看多个字符。上述代码中我们看到,除了跳过注释,我们还可能返回除号 /(Div) 标记。

    提前看字符的原理是:有一个或多个标记是以同样的字符开头的(如本小节中的注释与除号),因此只凭当前的字符我们并无法确定具体应该解释成哪一个标记,所以只能再向前查看字符,如本例需向前查看一个字符,若是 / 则说明是注释,反之则是除号。

    我们之前说过,词法分析器本质上也是编译器,其实提前看字符的概念也存在于编译器,只是这时就是提前看k个“标记”而不是“字符”了。平时听到的 LL(k) 中的 k 就是需要向前看的标记的个数了。

    另外,我们用词法分析器将源码转换成标记流,能减小语法分析复杂度,原因之一就是减少了语法分析器需要“向前看”的字符个数。

    其它

    其它的标记的解析就相对容易一些了,我们直接贴上代码:

    void next() {
    ...

    else if (token == '=') {
    // parse '==' and '='
    if (*src == '=') {
    src ++;
    token = Eq;
    } else {
    token = Assign;
    }
    return;
    }
    else if (token == '+') {
    // parse '+' and '++'
    if (*src == '+') {
    src ++;
    token = Inc;
    } else {
    token = Add;
    }
    return;
    }
    else if (token == '-') {
    // parse '-' and '--'
    if (*src == '-') {
    src ++;
    token = Dec;
    } else {
    token = Sub;
    }
    return;
    }
    else if (token == '!') {
    // parse '!='
    if (*src == '=') {
    src++;
    token = Ne;
    }
    return;
    }
    else if (token == '<') {
    // parse '<=', '<<' or '<'
    if (*src == '=') {
    src ++;
    token = Le;
    } else if (*src == '<') {
    src ++;
    token = Shl;
    } else {
    token = Lt;
    }
    return;
    }
    else if (token == '>') {
    // parse '>=', '>>' or '>'
    if (*src == '=') {
    src ++;
    token = Ge;
    } else if (*src == '>') {
    src ++;
    token = Shr;
    } else {
    token = Gt;
    }
    return;
    }
    else if (token == '|') {
    // parse '|' or '||'
    if (*src == '|') {
    src ++;
    token = Lor;
    } else {
    token = Or;
    }
    return;
    }
    else if (token == '&') {
    // parse '&' and '&&'
    if (*src == '&') {
    src ++;
    token = Lan;
    } else {
    token = And;
    }
    return;
    }
    else if (token == '^') {
    token = Xor;
    return;
    }
    else if (token == '%') {
    token = Mod;
    return;
    }
    else if (token == '*') {
    token = Mul;
    return;
    }
    else if (token == '[') {
    token = Brak;
    return;
    }
    else if (token == '?') {
    token = Cond;
    return;
    }
    else if (token == '~' || token == ';' || token == '{' || token == '}' || token == '(' || token == ')' || token == ']' || token == ',' || token == ':') {
    // directly return the character as token;
    return;
    }

    ...
    }

    代码较多,但主要逻辑就是向前看一个字符来确定真正的标记。

    关键字与内置函数

    虽然上面写完了词法分析器,但还有一个问题需要考虑,那就是“关键字”,例如 if, while, return 等。它们不能被作为普通的标识符,因为有特殊的含义。

    一般有两种处理方法:

    1. 词法分析器中直接解析这些关键字。
    2. 在语法分析前将关键字提前加入符号表。

    这里我们就采用第二种方法,将它们加入符号表,并提前为它们赋予必要的信息(还记得前面说的标识符 Token 字段吗?)。这样当源代码中出现关键字时,它们会被解析成标识符,但由于符号表中已经有了相关的信息,我们就能知道它们是特殊的关键字。

    内置函数的行为也和关键字类似,不同的只是赋值的信息,在main函数中进行初始化如下:

    // types of variable/function
    enum { CHAR, INT, PTR };
    int *idmain; // the `main` function

    void main() {
    ...

    src = "char else enum if int return sizeof while "
    "open read close printf malloc memset memcmp exit void main";

    // add keywords to symbol table
    i = Char;
    while (i <= While) {
    next();
    current_id[Token] = i++;
    }

    // add library to symbol table
    i = OPEN;
    while (i <= EXIT) {
    next();
    current_id[Class] = Sys;
    current_id[Type] = INT;
    current_id[Value] = i++;
    }

    next(); current_id[Token] = Char; // handle void type
    next(); idmain = current_id; // keep track of main

    ...
    program();
    }

    代码

    ',76),B={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-2",target:"_blank",rel:"noopener noreferrer"},C=p('
    git clone -b step-2 https://github.com/lotabout/write-a-C-interpreter

    上面的代码运行后会出现 ‘Segmentation Falt’,这是正常的,因为它会尝试运行我们上一章创建的虚拟机,但其中并没有任何汇编代码。

    小结

    本章我们为我们的编译器构建了词法分析器,通过本章的学习,我认为有几个要点需要强调:

    1. 词法分析器的作用是对源码字符串进行预处理,作用是减小语法分析器的复杂程度。
    2. 词法分析器本身可以认为是一个编译器,输入是源码,输出是标记流。
    3. lookahead(k) 的概念,即向前看 k 个字符或标记。
    4. 词法分析中如何处理标识符与符号表。

    下一章中,我们将介绍递归下降的语法分析器。我们下一章见。

    ',6);function v(x,D){const n=r("ExternalLinkIcon");return c(),t("div",null,[o,s("p",null,[a("本文转自 "),s("a",b,[a("https://lotabout.me/2015/write-a-C-interpreter-3/"),e(n)]),a(",如有侵权,请联系删除。")]),d,s("ol",null,[s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",h,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",A,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",w,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),_,s("p",null,[a("本章的代码可以在 "),s("a",B,[a("Github"),e(n)]),a(" 上下载,也可以直接 clone")]),C])}const N=l(i,[["render",v],["__file","3.html.vue"]]),S=JSON.parse('{"path":"/tech/designASimpileCCompiler/3.html","title":"手把手教你构建 C 语言编译器(3)——词法分析器","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(3)——词法分析器","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-3/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(3)- 词法分析器 Table of Contents 1. 什么是词法分析器 2. 词法分析器与编译器 3. 词法分析器的实现 3.1. 支持的标记 3.2. 词法分...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/3.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(3)——词法分析器"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2015/write-a-C-interpreter-3/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(3)- 词法分析器 Table of Contents 1. 什么是词法分析器 2. 词法分析器与编译器 3. 词法分析器的实现 3.1. 支持的标记 3.2. 词法分..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(3)——词法分析器\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"什么是词法分析器","slug":"什么是词法分析器","link":"#什么是词法分析器","children":[]},{"level":2,"title":"词法分析器与编译器","slug":"词法分析器与编译器","link":"#词法分析器与编译器","children":[]},{"level":2,"title":"词法分析器的实现","slug":"词法分析器的实现","link":"#词法分析器的实现","children":[{"level":3,"title":"支持的标记","slug":"支持的标记","link":"#支持的标记","children":[]},{"level":3,"title":"词法分析器的框架","slug":"词法分析器的框架","link":"#词法分析器的框架","children":[]},{"level":3,"title":"换行符","slug":"换行符","link":"#换行符","children":[]},{"level":3,"title":"宏定义","slug":"宏定义","link":"#宏定义","children":[]},{"level":3,"title":"标识符与符号表","slug":"标识符与符号表","link":"#标识符与符号表","children":[]},{"level":3,"title":"数字","slug":"数字","link":"#数字","children":[]},{"level":3,"title":"字符串","slug":"字符串","link":"#字符串","children":[]},{"level":3,"title":"注释","slug":"注释","link":"#注释","children":[]},{"level":3,"title":"其它","slug":"其它","link":"#其它","children":[]},{"level":3,"title":"关键字与内置函数","slug":"关键字与内置函数","link":"#关键字与内置函数","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":25.21,"words":7563},"filePathRelative":"tech/designASimpileCCompiler/3.md","excerpt":"\\n

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-3/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(3)- 词法分析器

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 什么是词法分析器
    2. \\n
    3. 2. 词法分析器与编译器
    4. \\n
    5. 3. 词法分析器的实现\\n
        \\n
      1. 3.1. 支持的标记
      2. \\n
      3. 3.2. 词法分析器的框架
      4. \\n
      5. 3.3. 换行符
      6. \\n
      7. 3.4. 宏定义
      8. \\n
      9. 3.5. 标识符与符号表
      10. \\n
      11. 3.6. 数字
      12. \\n
      13. 3.7. 字符串
      14. \\n
      15. 3.8. 注释
      16. \\n
      17. 3.9. 其它
      18. \\n
      19. 3.10. 关键字与内置函数
      20. \\n
      \\n
    6. \\n
    7. 4. 代码
    8. \\n
    9. 5. 小结
    10. \\n
    ","autoDesc":true}');export{N as comp,S as data}; diff --git a/assets/4.html-C8TuLMaF.js b/assets/4.html-DavKkBaH.js similarity index 99% rename from assets/4.html-C8TuLMaF.js rename to assets/4.html-DavKkBaH.js index 55b0054..b52bced 100644 --- a/assets/4.html-C8TuLMaF.js +++ b/assets/4.html-DavKkBaH.js @@ -1 +1 @@ -import{_ as t,r as p,o as r,c,b as s,e as a,d as e,f as l}from"./app-B69Gl_S-.js";const i={},o=s("p",null,"[]",-1),b=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),d={href:"https://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},h=l('

    原文内容

    手把手教你构建 C 语言编译器(4)- 递归下降

    Table of Contents

    1. 1. 什么是递归下降
    2. 2. 终结符与非终结符
    3. 3. 四则运算的递归下降
    4. 4. 为什么选择递归下降
    5. 5. 左递归
    6. 6. 四则运算的实现
    7. 7. 小结

    本章我们将讲解递归下降的方法,并用它完成一个基本的四则运算的语法分析器。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),E={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},B={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},w=l('

    什么是递归下降

    传统上,编写语法分析器有两种方法,一种是自顶向下,一种是自底向上。自顶向下是从起始非终结符开始,不断地对非终结符进行分解,直到匹配输入的终结符;自底向上是不断地将终结符进行合并,直到合并成起始的非终结符。

    其中的自顶向下方法就是我们所说的递归下降。

    终结符与非终结符

    ',4),x={href:"https://zh.wikipedia.org/wiki/%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F",target:"_blank",rel:"noopener noreferrer"},A=l('
    <expr> ::= <expr> + <term>
    | <expr> - <term>
    | <term>

    <term> ::= <term> * <factor>
    | <term> / <factor>
    | <factor>

    <factor> ::= ( <expr> )
    | Num

    用尖括号 <> 括起来的就称作 非终结符 ,因为它们可以用 ::= 右侧的式子代替。| 表示选择,如 <expr> 可以是 <expr> + <term><expr> - <term><term> 中的一种。而没有出现在::=左边的就称作 终结符 ,一般终结符对应于词法分析器输出的标记。

    四则运算的递归下降

    例如,我们对 3 * (4 + 2) 进行语法分析。我们假设词法分析器已经正确地将其中的数字识别成了标记 Num

    递归下降是从起始的非终结符开始(顶),本例中是 <expr>,实际中可以自己指定,不指定的话一般认为是第一个出现的非终结符。

    1. <expr> => <expr>
    2. => <term> * <factor>
    3. => <factor> |
    4. => Num (3) |
    5. => ( <expr> )
    6. => <expr> + <term>
    7. => <term> |
    8. => <factor> |
    9. => Num (4) |
    10. => <factor>
    11. => Num (2)

    可以看到,整个解析的过程是在不断对非终结符进行替换(向下),直到遇见了终结符(底)。而我们可以从解析的过程中看出,一些非终结符如<expr>被递归地使用了。

    为什么选择递归下降

    从上小节对四则运算的递归下降解析可以看出,整个解析的过程和语法的 BNF 表示是十分接近的,更为重要的是,我们可以很容易地直接将 BNF 表示转换成实际的代码。方法是为每个产生式(即 非终结符 ::= ...)生成一个同名的函数。

    这里会有一个疑问,就是上例中,当一个终结符有多个选择时,如何确定具体选择哪一个?如为什么用 <expr> ::= <term> * <factor> 而不是 <expr> ::= <term> / <factor> ?这就用到了上一章中提到的“向前看 k 个标记”的概念了。我们向前看一个标记,发现是 *,而这个标记足够让我们确定用哪个表达式了。

    另外,递归下下降方法对 BNF 方法本身有一定的要求,否则会有一些问题,如经典的“左递归”问题。

    左递归

    原则上我们是不讲这么深入,但我们上面的四则运算的文法就是左递归的,而左递归的语法是没法直接使用递归下降的方法实现的。因此我们要消除左递归,消除后的文法如下:

    <expr> ::= <term> <expr_tail>
    <expr_tail> ::= + <term> <expr_tail>
    | - <term> <expr_tail>
    | <empty>

    <term> ::= <factor> <term_tail>
    <term_tail> ::= * <factor> <term_tail>
    | / <factor> <term_tail>
    | <empty>

    <factor> ::= ( <expr> )
    | Num

    消除左递归的相关方法,这里不再多说,请自行查阅相关的资料。

    四则运算的实现

    本节中我们专注语法分析器部分的实现,具体实现很容易,我们直接贴上代码,就是上述的消除左递归后的文法直接转换而来的:

    int expr();

    int factor() {
    int value = 0;
    if (token == '(') {
    match('(');
    value = expr();
    match(')');
    } else {
    value = token_val;
    match(Num);
    }
    return value;
    }

    int term_tail(int lvalue) {
    if (token == '*') {
    match('*');
    int value = lvalue * factor();
    return term_tail(value);
    } else if (token == '/') {
    match('/');
    int value = lvalue / factor();
    return term_tail(value);
    } else {
    return lvalue;
    }
    }

    int term() {
    int lvalue = factor();
    return term_tail(lvalue);
    }

    int expr_tail(int lvalue) {
    if (token == '+') {
    match('+');
    int value = lvalue + term();
    return expr_tail(value);
    } else if (token == '-') {
    match('-');
    int value = lvalue - term();
    return expr_tail(value);
    } else {
    return lvalue;
    }
    }

    int expr() {
    int lvalue = term();
    return expr_tail(lvalue);
    }

    可以看到,有了BNF方法后,采用递归向下的方法来实现编译器是很直观的。

    我们把词法分析器的代码一并贴上:

    ##include <stdio.h>
    ##include <stdlib.h>

    enum {Num};
    int token;
    int token_val;
    char *line = NULL;
    char *src = NULL;

    void next() {
    // skip white space
    while (*src == ' ' || *src == '\\t') {
    src ++;
    }

    token = *src++;

    if (token >= '0' && token <= '9' ) {
    token_val = token - '0';
    token = Num;

    while (*src >= '0' && *src <= '9') {
    token_val = token_val*10 + *src - '0';
    src ++;
    }
    return;
    }
    }

    void match(int tk) {
    if (token != tk) {
    printf("expected token: %d(%c), got: %d(%c)\\n", tk, tk, token, token);
    exit(-1);
    }
    next();
    }

    最后是main函数:

    int main(int argc, char *argv[])
    {
    size_t linecap = 0;
    ssize_t linelen;
    while ((linelen = getline(&line, &linecap, stdin)) > 0) {
    src = line;
    next();
    printf("%d\\n", expr());
    }
    return 0;
    }

    小结

    本章中我们介绍了递归下降的方法,并用它来实现了四则运算的语法分析器。

    花这么大精力讲解递归下降方法,是因为几乎所有手工编写的语法分析器都或多或少地有它的影子。换句话说,掌握了递归下降的方法,就可以应付大多数的语法分析器编写。

    同时我们也用实例看到了理论(BNF 语法,左递归的消除)是如何帮助我们的工程实现的。尽管理论不是必需的,但如果能掌握它,对于提高我们的水平还是很有帮助的。

    ',27);function v(D,N){const n=p("ExternalLinkIcon");return r(),c("div",null,[o,b,s("p",null,[a("本文转自 "),s("a",d,[a("https://lotabout.me/2016/write-a-C-interpreter-4/"),e(n)]),a(",如有侵权,请联系删除。")]),h,s("ol",null,[s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",B,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",_,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",C,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),w,s("p",null,[a("没有学过编译原理的话可能并不知道什么是“终结符”,“非终结符”。这里我简单介绍一下。首先是 "),s("a",x,[a("BNF"),e(n)]),a(" 范式,就是一种用来描述语法的语言,例如,四则运算的规则可以表示如下:")]),A])}const S=t(i,[["render",v],["__file","4.html.vue"]]),L=JSON.parse('{"path":"/tech/designASimpileCCompiler/4.html","title":"手把手教你构建 C 语言编译器(4)——递归下降","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(4)——递归下降","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"[] 转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-4/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(4)- 递归下降 Table of Contents 1. 什么是递归下降 2. 终结符与非终结符 3. 四则运算的递归下降 4. 为什么选择递归下降 5. 左递...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/4.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(4)——递归下降"}],["meta",{"property":"og:description","content":"[] 转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-4/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(4)- 递归下降 Table of Contents 1. 什么是递归下降 2. 终结符与非终结符 3. 四则运算的递归下降 4. 为什么选择递归下降 5. 左递..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(4)——递归下降\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"什么是递归下降","slug":"什么是递归下降","link":"#什么是递归下降","children":[]},{"level":2,"title":"终结符与非终结符","slug":"终结符与非终结符","link":"#终结符与非终结符","children":[]},{"level":2,"title":"四则运算的递归下降","slug":"四则运算的递归下降","link":"#四则运算的递归下降","children":[]},{"level":2,"title":"为什么选择递归下降","slug":"为什么选择递归下降","link":"#为什么选择递归下降","children":[]},{"level":2,"title":"左递归","slug":"左递归","link":"#左递归","children":[]},{"level":2,"title":"四则运算的实现","slug":"四则运算的实现","link":"#四则运算的实现","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":11.33,"words":3399},"filePathRelative":"tech/designASimpileCCompiler/4.md","excerpt":"

    []

    \\n

    转载声明

    \\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-4/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(4)- 递归下降

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 什么是递归下降
    2. \\n
    3. 2. 终结符与非终结符
    4. \\n
    5. 3. 四则运算的递归下降
    6. \\n
    7. 4. 为什么选择递归下降
    8. \\n
    9. 5. 左递归
    10. \\n
    11. 6. 四则运算的实现
    12. \\n
    13. 7. 小结
    14. \\n
    ","autoDesc":true}');export{S as comp,L as data}; +import{_ as t,r as p,o as r,c,b as s,e as a,d as e,f as l}from"./app-B4LGNJZ0.js";const i={},o=s("p",null,"[]",-1),b=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),d={href:"https://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},h=l('

    原文内容

    手把手教你构建 C 语言编译器(4)- 递归下降

    Table of Contents

    1. 1. 什么是递归下降
    2. 2. 终结符与非终结符
    3. 3. 四则运算的递归下降
    4. 4. 为什么选择递归下降
    5. 5. 左递归
    6. 6. 四则运算的实现
    7. 7. 小结

    本章我们将讲解递归下降的方法,并用它完成一个基本的四则运算的语法分析器。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),E={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},B={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},w=l('

    什么是递归下降

    传统上,编写语法分析器有两种方法,一种是自顶向下,一种是自底向上。自顶向下是从起始非终结符开始,不断地对非终结符进行分解,直到匹配输入的终结符;自底向上是不断地将终结符进行合并,直到合并成起始的非终结符。

    其中的自顶向下方法就是我们所说的递归下降。

    终结符与非终结符

    ',4),x={href:"https://zh.wikipedia.org/wiki/%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F",target:"_blank",rel:"noopener noreferrer"},A=l('
    <expr> ::= <expr> + <term>
    | <expr> - <term>
    | <term>

    <term> ::= <term> * <factor>
    | <term> / <factor>
    | <factor>

    <factor> ::= ( <expr> )
    | Num

    用尖括号 <> 括起来的就称作 非终结符 ,因为它们可以用 ::= 右侧的式子代替。| 表示选择,如 <expr> 可以是 <expr> + <term><expr> - <term><term> 中的一种。而没有出现在::=左边的就称作 终结符 ,一般终结符对应于词法分析器输出的标记。

    四则运算的递归下降

    例如,我们对 3 * (4 + 2) 进行语法分析。我们假设词法分析器已经正确地将其中的数字识别成了标记 Num

    递归下降是从起始的非终结符开始(顶),本例中是 <expr>,实际中可以自己指定,不指定的话一般认为是第一个出现的非终结符。

    1. <expr> => <expr>
    2. => <term> * <factor>
    3. => <factor> |
    4. => Num (3) |
    5. => ( <expr> )
    6. => <expr> + <term>
    7. => <term> |
    8. => <factor> |
    9. => Num (4) |
    10. => <factor>
    11. => Num (2)

    可以看到,整个解析的过程是在不断对非终结符进行替换(向下),直到遇见了终结符(底)。而我们可以从解析的过程中看出,一些非终结符如<expr>被递归地使用了。

    为什么选择递归下降

    从上小节对四则运算的递归下降解析可以看出,整个解析的过程和语法的 BNF 表示是十分接近的,更为重要的是,我们可以很容易地直接将 BNF 表示转换成实际的代码。方法是为每个产生式(即 非终结符 ::= ...)生成一个同名的函数。

    这里会有一个疑问,就是上例中,当一个终结符有多个选择时,如何确定具体选择哪一个?如为什么用 <expr> ::= <term> * <factor> 而不是 <expr> ::= <term> / <factor> ?这就用到了上一章中提到的“向前看 k 个标记”的概念了。我们向前看一个标记,发现是 *,而这个标记足够让我们确定用哪个表达式了。

    另外,递归下下降方法对 BNF 方法本身有一定的要求,否则会有一些问题,如经典的“左递归”问题。

    左递归

    原则上我们是不讲这么深入,但我们上面的四则运算的文法就是左递归的,而左递归的语法是没法直接使用递归下降的方法实现的。因此我们要消除左递归,消除后的文法如下:

    <expr> ::= <term> <expr_tail>
    <expr_tail> ::= + <term> <expr_tail>
    | - <term> <expr_tail>
    | <empty>

    <term> ::= <factor> <term_tail>
    <term_tail> ::= * <factor> <term_tail>
    | / <factor> <term_tail>
    | <empty>

    <factor> ::= ( <expr> )
    | Num

    消除左递归的相关方法,这里不再多说,请自行查阅相关的资料。

    四则运算的实现

    本节中我们专注语法分析器部分的实现,具体实现很容易,我们直接贴上代码,就是上述的消除左递归后的文法直接转换而来的:

    int expr();

    int factor() {
    int value = 0;
    if (token == '(') {
    match('(');
    value = expr();
    match(')');
    } else {
    value = token_val;
    match(Num);
    }
    return value;
    }

    int term_tail(int lvalue) {
    if (token == '*') {
    match('*');
    int value = lvalue * factor();
    return term_tail(value);
    } else if (token == '/') {
    match('/');
    int value = lvalue / factor();
    return term_tail(value);
    } else {
    return lvalue;
    }
    }

    int term() {
    int lvalue = factor();
    return term_tail(lvalue);
    }

    int expr_tail(int lvalue) {
    if (token == '+') {
    match('+');
    int value = lvalue + term();
    return expr_tail(value);
    } else if (token == '-') {
    match('-');
    int value = lvalue - term();
    return expr_tail(value);
    } else {
    return lvalue;
    }
    }

    int expr() {
    int lvalue = term();
    return expr_tail(lvalue);
    }

    可以看到,有了BNF方法后,采用递归向下的方法来实现编译器是很直观的。

    我们把词法分析器的代码一并贴上:

    ##include <stdio.h>
    ##include <stdlib.h>

    enum {Num};
    int token;
    int token_val;
    char *line = NULL;
    char *src = NULL;

    void next() {
    // skip white space
    while (*src == ' ' || *src == '\\t') {
    src ++;
    }

    token = *src++;

    if (token >= '0' && token <= '9' ) {
    token_val = token - '0';
    token = Num;

    while (*src >= '0' && *src <= '9') {
    token_val = token_val*10 + *src - '0';
    src ++;
    }
    return;
    }
    }

    void match(int tk) {
    if (token != tk) {
    printf("expected token: %d(%c), got: %d(%c)\\n", tk, tk, token, token);
    exit(-1);
    }
    next();
    }

    最后是main函数:

    int main(int argc, char *argv[])
    {
    size_t linecap = 0;
    ssize_t linelen;
    while ((linelen = getline(&line, &linecap, stdin)) > 0) {
    src = line;
    next();
    printf("%d\\n", expr());
    }
    return 0;
    }

    小结

    本章中我们介绍了递归下降的方法,并用它来实现了四则运算的语法分析器。

    花这么大精力讲解递归下降方法,是因为几乎所有手工编写的语法分析器都或多或少地有它的影子。换句话说,掌握了递归下降的方法,就可以应付大多数的语法分析器编写。

    同时我们也用实例看到了理论(BNF 语法,左递归的消除)是如何帮助我们的工程实现的。尽管理论不是必需的,但如果能掌握它,对于提高我们的水平还是很有帮助的。

    ',27);function v(D,N){const n=p("ExternalLinkIcon");return r(),c("div",null,[o,b,s("p",null,[a("本文转自 "),s("a",d,[a("https://lotabout.me/2016/write-a-C-interpreter-4/"),e(n)]),a(",如有侵权,请联系删除。")]),h,s("ol",null,[s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",B,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",_,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",C,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),w,s("p",null,[a("没有学过编译原理的话可能并不知道什么是“终结符”,“非终结符”。这里我简单介绍一下。首先是 "),s("a",x,[a("BNF"),e(n)]),a(" 范式,就是一种用来描述语法的语言,例如,四则运算的规则可以表示如下:")]),A])}const S=t(i,[["render",v],["__file","4.html.vue"]]),L=JSON.parse('{"path":"/tech/designASimpileCCompiler/4.html","title":"手把手教你构建 C 语言编译器(4)——递归下降","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(4)——递归下降","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"[] 转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-4/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(4)- 递归下降 Table of Contents 1. 什么是递归下降 2. 终结符与非终结符 3. 四则运算的递归下降 4. 为什么选择递归下降 5. 左递...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/4.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(4)——递归下降"}],["meta",{"property":"og:description","content":"[] 转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-4/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(4)- 递归下降 Table of Contents 1. 什么是递归下降 2. 终结符与非终结符 3. 四则运算的递归下降 4. 为什么选择递归下降 5. 左递..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(4)——递归下降\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"什么是递归下降","slug":"什么是递归下降","link":"#什么是递归下降","children":[]},{"level":2,"title":"终结符与非终结符","slug":"终结符与非终结符","link":"#终结符与非终结符","children":[]},{"level":2,"title":"四则运算的递归下降","slug":"四则运算的递归下降","link":"#四则运算的递归下降","children":[]},{"level":2,"title":"为什么选择递归下降","slug":"为什么选择递归下降","link":"#为什么选择递归下降","children":[]},{"level":2,"title":"左递归","slug":"左递归","link":"#左递归","children":[]},{"level":2,"title":"四则运算的实现","slug":"四则运算的实现","link":"#四则运算的实现","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":11.33,"words":3399},"filePathRelative":"tech/designASimpileCCompiler/4.md","excerpt":"

    []

    \\n

    转载声明

    \\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-4/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(4)- 递归下降

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 什么是递归下降
    2. \\n
    3. 2. 终结符与非终结符
    4. \\n
    5. 3. 四则运算的递归下降
    6. \\n
    7. 4. 为什么选择递归下降
    8. \\n
    9. 5. 左递归
    10. \\n
    11. 6. 四则运算的实现
    12. \\n
    13. 7. 小结
    14. \\n
    ","autoDesc":true}');export{S as comp,L as data}; diff --git a/assets/404.html-BC0rHQLu.js b/assets/404.html-CGMXaJuv.js similarity index 93% rename from assets/404.html-BC0rHQLu.js rename to assets/404.html-CGMXaJuv.js index 5e5e7ad..7fd665f 100644 --- a/assets/404.html-BC0rHQLu.js +++ b/assets/404.html-CGMXaJuv.js @@ -1 +1 @@ -import{_ as t,o as e,c as o,b as n}from"./app-B69Gl_S-.js";const r={},a=n("p",null,"404 Not Found",-1),c=[a];function p(s,i){return e(),o("div",null,c)}const d=t(r,[["render",p],["__file","404.html.vue"]]),m=JSON.parse('{"path":"/404.html","title":"","lang":"zh-CN","frontmatter":{"layout":"NotFound","description":"404 Not Found","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/404.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:description","content":"404 Not Found"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"\\",\\"description\\":\\"404 Not Found\\"}"]]},"headers":[],"readingTime":{"minutes":0.01,"words":3},"filePathRelative":null,"excerpt":"

    404 Not Found

    \\n","autoDesc":true}');export{d as comp,m as data}; +import{_ as t,o as e,c as o,b as n}from"./app-B4LGNJZ0.js";const r={},a=n("p",null,"404 Not Found",-1),c=[a];function p(s,i){return e(),o("div",null,c)}const d=t(r,[["render",p],["__file","404.html.vue"]]),m=JSON.parse('{"path":"/404.html","title":"","lang":"zh-CN","frontmatter":{"layout":"NotFound","description":"404 Not Found","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/404.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:description","content":"404 Not Found"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"\\",\\"description\\":\\"404 Not Found\\"}"]]},"headers":[],"readingTime":{"minutes":0.01,"words":3},"filePathRelative":null,"excerpt":"

    404 Not Found

    \\n","autoDesc":true}');export{d as comp,m as data}; diff --git a/assets/5.html-qLlLbRlw.js b/assets/5.html-DpkUOZDe.js similarity index 99% rename from assets/5.html-qLlLbRlw.js rename to assets/5.html-DpkUOZDe.js index 04ceab3..77dc916 100644 --- a/assets/5.html-qLlLbRlw.js +++ b/assets/5.html-DpkUOZDe.js @@ -1 +1 @@ -import{_ as p,r as t,o as r,c as i,b as a,e as s,d as e,f as l}from"./app-B69Gl_S-.js";const c={},o=a("h1",{id:"转载声明",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#转载声明"},[a("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},d=l('

    原文内容

    手把手教你构建 C 语言编译器(5)- 变量定义

    Table of Contents

    1. 1. EBNF 表示
    2. 2. 解析变量的定义
      1. 2.1. program()
      2. 2.2. global_declaration()
      3. 2.3. enum_declaration()
      4. 2.4. 其它
    3. 3. 代码
    4. 4. 小结

    本章中我们用 EBNF 来大致描述我们实现的 C 语言的文法,并实现其中解析变量定义部分。

    由于语法分析本身比较复杂,所以我们将它拆分成 3 个部分进行讲解,分别是:变量定义、函数定义、表达式。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',7),m={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},h={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},w=l('

    EBNF 表示

    EBNF 是对前一章提到的 BNF 的扩展,它的语法更容易理解,实现起来也更直观。但真正看起来还是很烦,如果不想看可以跳过。

    program ::= {global_declaration}+

    global_declaration ::= enum_decl | variable_decl | function_decl

    enum_decl ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'] '}'

    variable_decl ::= type {'*'} id { ',' {'*'} id } ';'

    function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'

    parameter_decl ::= type {'*'} id {',' type {'*'} id}

    body_decl ::= {variable_decl}, {statement}

    statement ::= non_empty_statement | empty_statement

    non_empty_statement ::= if_statement | while_statement | '{' statement '}'
    | 'return' expression | expression ';'

    if_statement ::= 'if' '(' expression ')' statement ['else' non_empty_statement]

    while_statement ::= 'while' '(' expression ')' non_empty_statement

    其中 expression 相关的内容我们放到后面解释,主要原因是我们的语言不支持跨函数递归,而为了实现自举,实际上我们也不能使用递归(亏我们说了一章的递归下降)。

    P.S. 我是先写程序再总结上面的文法,所以实际上它们间的对应关系并不是特别明显。

    解析变量的定义

    本章要讲解的就是上节文法中的 enum_declvariable_decl 部分。

    program()

    首先是之前定义过的 program 函数,将它改成:

    void program() {
    // get next token
    next();
    while (token > 0) {
    global_declaration();
    }
    }

    我知道 global_declaration 函数还没有出现过,但没有关系,采用自顶向下的编写方法就是要不断地实现我们需要的内容。下面是 global_declaration 函数的内容:

    global_declaration()

    即全局的定义语句,包括变量定义,类型定义(只支持枚举)及函数定义。代码如下:

    int basetype;    // the type of a declaration, make it global for convenience
    int expr_type; // the type of an expression

    void global_declaration() {
    // global_declaration ::= enum_decl | variable_decl | function_decl
    //
    // enum_decl ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'} '}'
    //
    // variable_decl ::= type {'*'} id { ',' {'*'} id } ';'
    //
    // function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'


    int type; // tmp, actual type for variable
    int i; // tmp

    basetype = INT;

    // parse enum, this should be treated alone.
    if (token == Enum) {
    // enum [id] { a = 10, b = 20, ... }
    match(Enum);
    if (token != '{') {
    match(Id); // skip the [id] part
    }
    if (token == '{') {
    // parse the assign part
    match('{');
    enum_declaration();
    match('}');
    }

    match(';');
    return;
    }

    // parse type information
    if (token == Int) {
    match(Int);
    }
    else if (token == Char) {
    match(Char);
    basetype = CHAR;
    }

    // parse the comma seperated variable declaration.
    while (token != ';' && token != '}') {
    type = basetype;
    // parse pointer type, note that there may exist `int ****x;`
    while (token == Mul) {
    match(Mul);
    type = type + PTR;
    }

    if (token != Id) {
    // invalid declaration
    printf("%d: bad global declaration\\n", line);
    exit(-1);
    }
    if (current_id[Class]) {
    // identifier exists
    printf("%d: duplicate global declaration\\n", line);
    exit(-1);
    }
    match(Id);
    current_id[Type] = type;

    if (token == '(') {
    current_id[Class] = Fun;
    current_id[Value] = (int)(text + 1); // the memory address of function
    function_declaration();
    } else {
    // variable declaration
    current_id[Class] = Glo; // global variable
    current_id[Value] = (int)data; // assign memory address
    data = data + sizeof(int);
    }

    if (token == ',') {
    match(',');
    }
    }
    next();
    }

    看了上面的代码,能大概理解吗?这里我们讲解其中的一些细节。

    向前看标记 :其中的 if (token == xxx) 语句就是用来向前查看标记以确定使用哪一个产生式,例如只要遇到 enum 我们就知道是需要解析枚举类型。而如果只解析到类型,如 int identifier 时我们并不能确定 identifier 是一个普通的变量还是一个函数,所以还需要继续查看后续的标记,如果遇到 ( 则可以断定是函数了,反之则是变量。

    变量类型的表示 :我们的编译器支持指针类型,那意味着也支持指针的指针,如 int **data;。那么我们如何表示指针类型呢?前文中我们定义了支持的类型:

    // types of variable/function
    enum { CHAR, INT, PTR };

    所以一个类型首先有基本类型,如 CHARINT,当它是一个指向基本类型的指针时,如 int *data,我们就将它的类型加上 PTR 即代码中的:type = type + PTR;。同理,如果是指针的指针,则再加上 PTR

    enum_declaration()

    用于解析枚举类型的定义。主要的逻辑用于解析用逗号(,)分隔的变量,值得注意的是在编译器中如何保存枚举变量的信息。

    即我们将该变量的类别设置成了 Num,这样它就成了全局的常量了,而注意到上节中,正常的全局变量的类别则是 Glo,类别信息在后面章节中解析 expression 会使用到。

    void enum_declaration() {
    // parse enum [id] { a = 1, b = 3, ...}
    int i;
    i = 0;
    while (token != '}') {
    if (token != Id) {
    printf("%d: bad enum identifier %d\\n", line, token);
    exit(-1);
    }
    next();
    if (token == Assign) {
    // like {a=10}
    next();
    if (token != Num) {
    printf("%d: bad enum initializer\\n", line);
    exit(-1);
    }
    i = token_val;
    next();
    }

    current_id[Class] = Num;
    current_id[Type] = INT;
    current_id[Value] = i++;

    if (token == ',') {
    next();
    }
    }
    }

    其它

    其中的 function_declaration 函数我们将放到下一章中讲解。match 函数是一个辅助函数:

    void match(int tk) {
    if (token == tk) {
    next();
    } else {
    printf("%d: expected token: %d\\n", line, tk);
    exit(-1);
    }
    }

    它将 next 函数包装起来,如果不是预期的标记则报错并退出。

    代码

    ',28),x={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-3",target:"_blank",rel:"noopener noreferrer"},A=l('
    git clone -b step-3 https://github.com/lotabout/write-a-C-interpreter

    本章的代码还无法正常运行,因为还有许多功能没有实现,但如果有兴趣的话,可以自己先试着去实现它。

    小结

    本章的内容应该不难,除了开头的 EBNF 表达式可能相对不好理解一些,但如果你查看了 EBNF 的具体表示方法后就不难理解了。

    剩下的内容就是按部就班地将 EBNF 的产生式转换成函数的过程,如果你理解了上一章中的内容,相信这部分也不难理解。

    下一章中我们将介绍如何解析函数的定义,敬请期待。

    ',6);function B(v,N){const n=t("ExternalLinkIcon");return r(),i("div",null,[o,a("p",null,[s("本文转自 "),a("a",b,[s("https://lotabout.me/2016/write-a-C-interpreter-5/"),e(n)]),s(",如有侵权,请联系删除。")]),d,a("ol",null,[a("li",null,[a("a",m,[s("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),a("li",null,[a("a",h,[s("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),a("li",null,[a("a",u,[s("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),a("li",null,[a("a",_,[s("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),a("li",null,[a("a",f,[s("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),a("li",null,[a("a",g,[s("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),a("li",null,[a("a",y,[s("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),a("li",null,[a("a",k,[s("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),a("li",null,[a("a",E,[s("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),a("li",null,[a("a",C,[s("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),w,a("p",null,[s("本章的代码可以在 "),a("a",x,[s("Github"),e(n)]),s(" 上下载,也可以直接 clone")]),A])}const T=p(c,[["render",B],["__file","5.html.vue"]]),I=JSON.parse('{"path":"/tech/designASimpileCCompiler/5.html","title":"手把手教你构建 C 语言编译器(5)——变量定义","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(5)——变量定义","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-5/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(5)- 变量定义 Table of Contents 1. EBNF 表示 2. 解析变量的定义 2.1. program() 2.2. global_declarat...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/5.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(5)——变量定义"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-5/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(5)- 变量定义 Table of Contents 1. EBNF 表示 2. 解析变量的定义 2.1. program() 2.2. global_declarat..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(5)——变量定义\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"EBNF 表示","slug":"ebnf-表示","link":"#ebnf-表示","children":[]},{"level":2,"title":"解析变量的定义","slug":"解析变量的定义","link":"#解析变量的定义","children":[{"level":3,"title":"program()","slug":"program","link":"#program","children":[]},{"level":3,"title":"global_declaration()","slug":"global-declaration","link":"#global-declaration","children":[]},{"level":3,"title":"enum_declaration()","slug":"enum-declaration","link":"#enum-declaration","children":[]},{"level":3,"title":"其它","slug":"其它","link":"#其它","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":10.71,"words":3212},"filePathRelative":"tech/designASimpileCCompiler/5.md","excerpt":"\\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-5/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(5)- 变量定义

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. EBNF 表示
    2. \\n
    3. 2. 解析变量的定义\\n
        \\n
      1. 2.1. program()
      2. \\n
      3. 2.2. global_declaration()
      4. \\n
      5. 2.3. enum_declaration()
      6. \\n
      7. 2.4. 其它
      8. \\n
      \\n
    4. \\n
    5. 3. 代码
    6. \\n
    7. 4. 小结
    8. \\n
    ","autoDesc":true}');export{T as comp,I as data}; +import{_ as p,r as t,o as r,c as i,b as a,e as s,d as e,f as l}from"./app-B4LGNJZ0.js";const c={},o=a("h1",{id:"转载声明",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#转载声明"},[a("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},d=l('

    原文内容

    手把手教你构建 C 语言编译器(5)- 变量定义

    Table of Contents

    1. 1. EBNF 表示
    2. 2. 解析变量的定义
      1. 2.1. program()
      2. 2.2. global_declaration()
      3. 2.3. enum_declaration()
      4. 2.4. 其它
    3. 3. 代码
    4. 4. 小结

    本章中我们用 EBNF 来大致描述我们实现的 C 语言的文法,并实现其中解析变量定义部分。

    由于语法分析本身比较复杂,所以我们将它拆分成 3 个部分进行讲解,分别是:变量定义、函数定义、表达式。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',7),m={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},h={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},w=l('

    EBNF 表示

    EBNF 是对前一章提到的 BNF 的扩展,它的语法更容易理解,实现起来也更直观。但真正看起来还是很烦,如果不想看可以跳过。

    program ::= {global_declaration}+

    global_declaration ::= enum_decl | variable_decl | function_decl

    enum_decl ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'] '}'

    variable_decl ::= type {'*'} id { ',' {'*'} id } ';'

    function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'

    parameter_decl ::= type {'*'} id {',' type {'*'} id}

    body_decl ::= {variable_decl}, {statement}

    statement ::= non_empty_statement | empty_statement

    non_empty_statement ::= if_statement | while_statement | '{' statement '}'
    | 'return' expression | expression ';'

    if_statement ::= 'if' '(' expression ')' statement ['else' non_empty_statement]

    while_statement ::= 'while' '(' expression ')' non_empty_statement

    其中 expression 相关的内容我们放到后面解释,主要原因是我们的语言不支持跨函数递归,而为了实现自举,实际上我们也不能使用递归(亏我们说了一章的递归下降)。

    P.S. 我是先写程序再总结上面的文法,所以实际上它们间的对应关系并不是特别明显。

    解析变量的定义

    本章要讲解的就是上节文法中的 enum_declvariable_decl 部分。

    program()

    首先是之前定义过的 program 函数,将它改成:

    void program() {
    // get next token
    next();
    while (token > 0) {
    global_declaration();
    }
    }

    我知道 global_declaration 函数还没有出现过,但没有关系,采用自顶向下的编写方法就是要不断地实现我们需要的内容。下面是 global_declaration 函数的内容:

    global_declaration()

    即全局的定义语句,包括变量定义,类型定义(只支持枚举)及函数定义。代码如下:

    int basetype;    // the type of a declaration, make it global for convenience
    int expr_type; // the type of an expression

    void global_declaration() {
    // global_declaration ::= enum_decl | variable_decl | function_decl
    //
    // enum_decl ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'} '}'
    //
    // variable_decl ::= type {'*'} id { ',' {'*'} id } ';'
    //
    // function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'


    int type; // tmp, actual type for variable
    int i; // tmp

    basetype = INT;

    // parse enum, this should be treated alone.
    if (token == Enum) {
    // enum [id] { a = 10, b = 20, ... }
    match(Enum);
    if (token != '{') {
    match(Id); // skip the [id] part
    }
    if (token == '{') {
    // parse the assign part
    match('{');
    enum_declaration();
    match('}');
    }

    match(';');
    return;
    }

    // parse type information
    if (token == Int) {
    match(Int);
    }
    else if (token == Char) {
    match(Char);
    basetype = CHAR;
    }

    // parse the comma seperated variable declaration.
    while (token != ';' && token != '}') {
    type = basetype;
    // parse pointer type, note that there may exist `int ****x;`
    while (token == Mul) {
    match(Mul);
    type = type + PTR;
    }

    if (token != Id) {
    // invalid declaration
    printf("%d: bad global declaration\\n", line);
    exit(-1);
    }
    if (current_id[Class]) {
    // identifier exists
    printf("%d: duplicate global declaration\\n", line);
    exit(-1);
    }
    match(Id);
    current_id[Type] = type;

    if (token == '(') {
    current_id[Class] = Fun;
    current_id[Value] = (int)(text + 1); // the memory address of function
    function_declaration();
    } else {
    // variable declaration
    current_id[Class] = Glo; // global variable
    current_id[Value] = (int)data; // assign memory address
    data = data + sizeof(int);
    }

    if (token == ',') {
    match(',');
    }
    }
    next();
    }

    看了上面的代码,能大概理解吗?这里我们讲解其中的一些细节。

    向前看标记 :其中的 if (token == xxx) 语句就是用来向前查看标记以确定使用哪一个产生式,例如只要遇到 enum 我们就知道是需要解析枚举类型。而如果只解析到类型,如 int identifier 时我们并不能确定 identifier 是一个普通的变量还是一个函数,所以还需要继续查看后续的标记,如果遇到 ( 则可以断定是函数了,反之则是变量。

    变量类型的表示 :我们的编译器支持指针类型,那意味着也支持指针的指针,如 int **data;。那么我们如何表示指针类型呢?前文中我们定义了支持的类型:

    // types of variable/function
    enum { CHAR, INT, PTR };

    所以一个类型首先有基本类型,如 CHARINT,当它是一个指向基本类型的指针时,如 int *data,我们就将它的类型加上 PTR 即代码中的:type = type + PTR;。同理,如果是指针的指针,则再加上 PTR

    enum_declaration()

    用于解析枚举类型的定义。主要的逻辑用于解析用逗号(,)分隔的变量,值得注意的是在编译器中如何保存枚举变量的信息。

    即我们将该变量的类别设置成了 Num,这样它就成了全局的常量了,而注意到上节中,正常的全局变量的类别则是 Glo,类别信息在后面章节中解析 expression 会使用到。

    void enum_declaration() {
    // parse enum [id] { a = 1, b = 3, ...}
    int i;
    i = 0;
    while (token != '}') {
    if (token != Id) {
    printf("%d: bad enum identifier %d\\n", line, token);
    exit(-1);
    }
    next();
    if (token == Assign) {
    // like {a=10}
    next();
    if (token != Num) {
    printf("%d: bad enum initializer\\n", line);
    exit(-1);
    }
    i = token_val;
    next();
    }

    current_id[Class] = Num;
    current_id[Type] = INT;
    current_id[Value] = i++;

    if (token == ',') {
    next();
    }
    }
    }

    其它

    其中的 function_declaration 函数我们将放到下一章中讲解。match 函数是一个辅助函数:

    void match(int tk) {
    if (token == tk) {
    next();
    } else {
    printf("%d: expected token: %d\\n", line, tk);
    exit(-1);
    }
    }

    它将 next 函数包装起来,如果不是预期的标记则报错并退出。

    代码

    ',28),x={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-3",target:"_blank",rel:"noopener noreferrer"},A=l('
    git clone -b step-3 https://github.com/lotabout/write-a-C-interpreter

    本章的代码还无法正常运行,因为还有许多功能没有实现,但如果有兴趣的话,可以自己先试着去实现它。

    小结

    本章的内容应该不难,除了开头的 EBNF 表达式可能相对不好理解一些,但如果你查看了 EBNF 的具体表示方法后就不难理解了。

    剩下的内容就是按部就班地将 EBNF 的产生式转换成函数的过程,如果你理解了上一章中的内容,相信这部分也不难理解。

    下一章中我们将介绍如何解析函数的定义,敬请期待。

    ',6);function B(v,N){const n=t("ExternalLinkIcon");return r(),i("div",null,[o,a("p",null,[s("本文转自 "),a("a",b,[s("https://lotabout.me/2016/write-a-C-interpreter-5/"),e(n)]),s(",如有侵权,请联系删除。")]),d,a("ol",null,[a("li",null,[a("a",m,[s("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),a("li",null,[a("a",h,[s("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),a("li",null,[a("a",u,[s("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),a("li",null,[a("a",_,[s("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),a("li",null,[a("a",f,[s("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),a("li",null,[a("a",g,[s("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),a("li",null,[a("a",y,[s("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),a("li",null,[a("a",k,[s("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),a("li",null,[a("a",E,[s("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),a("li",null,[a("a",C,[s("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),w,a("p",null,[s("本章的代码可以在 "),a("a",x,[s("Github"),e(n)]),s(" 上下载,也可以直接 clone")]),A])}const T=p(c,[["render",B],["__file","5.html.vue"]]),I=JSON.parse('{"path":"/tech/designASimpileCCompiler/5.html","title":"手把手教你构建 C 语言编译器(5)——变量定义","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(5)——变量定义","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-5/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(5)- 变量定义 Table of Contents 1. EBNF 表示 2. 解析变量的定义 2.1. program() 2.2. global_declarat...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/5.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(5)——变量定义"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-5/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(5)- 变量定义 Table of Contents 1. EBNF 表示 2. 解析变量的定义 2.1. program() 2.2. global_declarat..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(5)——变量定义\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"EBNF 表示","slug":"ebnf-表示","link":"#ebnf-表示","children":[]},{"level":2,"title":"解析变量的定义","slug":"解析变量的定义","link":"#解析变量的定义","children":[{"level":3,"title":"program()","slug":"program","link":"#program","children":[]},{"level":3,"title":"global_declaration()","slug":"global-declaration","link":"#global-declaration","children":[]},{"level":3,"title":"enum_declaration()","slug":"enum-declaration","link":"#enum-declaration","children":[]},{"level":3,"title":"其它","slug":"其它","link":"#其它","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":10.71,"words":3212},"filePathRelative":"tech/designASimpileCCompiler/5.md","excerpt":"\\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-5/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(5)- 变量定义

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. EBNF 表示
    2. \\n
    3. 2. 解析变量的定义\\n
        \\n
      1. 2.1. program()
      2. \\n
      3. 2.2. global_declaration()
      4. \\n
      5. 2.3. enum_declaration()
      6. \\n
      7. 2.4. 其它
      8. \\n
      \\n
    4. \\n
    5. 3. 代码
    6. \\n
    7. 4. 小结
    8. \\n
    ","autoDesc":true}');export{T as comp,I as data}; diff --git a/assets/6.html-BPGbpgjm.js b/assets/6.html-4dTDTOb4.js similarity index 99% rename from assets/6.html-BPGbpgjm.js rename to assets/6.html-4dTDTOb4.js index a0a3092..d10c062 100644 --- a/assets/6.html-BPGbpgjm.js +++ b/assets/6.html-4dTDTOb4.js @@ -1 +1 @@ -import{_ as p,r as t,o as r,c,b as s,e as a,d as e,f as l}from"./app-B69Gl_S-.js";const i={},o=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},d=l('

    原文内容

    手把手教你构建 C 语言编译器(6)- 函数定义

    Table of Contents

    1. 1. EBNF 表示
    2. 2. 解析函数的定义
      1. 2.1. 函数参数与汇编代码
      2. 2.2. 函数定义的解析
      3. 2.3. 解析参数
      4. 2.4. 函数体的解析
    3. 3. 代码
    4. 4. 小结

    由于语法分析本身比较复杂,所以我们将它拆分成 3 个部分进行讲解,分别是:变量定义、函数定义、表达式。本章讲解函数定义相关的内容。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),h={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},B={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},A=l('

    EBNF 表示

    这是上一章的 EBNF 方法中与函数定义相关的内容。

    variable_decl ::= type {'*'} id { ',' {'*'} id } ';'

    function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'

    parameter_decl ::= type {'*'} id {',' type {'*'} id}

    body_decl ::= {variable_decl}, {statement}

    statement ::= non_empty_statement | empty_statement

    non_empty_statement ::= if_statement | while_statement | '{' statement '}'
    | 'return' expression | expression ';'

    if_statement ::= 'if' '(' expression ')' statement ['else' non_empty_statement]

    while_statement ::= 'while' '(' expression ')' non_empty_statement

    解析函数的定义

    上一章的代码中,我们已经知道了什么时候开始解析函数的定义,相关的代码如下:

    ...
    if (token == '(') {
    current_id[Class] = Fun;
    current_id[Value] = (int)(text + 1); // the memory address of function
    function_declaration();
    } else {
    ...

    即在这断代码之前,我们已经为当前的标识符(identifier)设置了正确的类型,上面这断代码为当前的标识符设置了正确的类别(Fun),以及该函数在代码段(text segment)中的位置。接下来开始解析函数定义相关的内容:parameter_declbody_decl

    函数参数与汇编代码

    现在我们要回忆如何将“函数”转换成对应的汇编代码,因为这决定了在解析时我们需要哪些相关的信息。考虑下列函数:

    int demo(int param_a, int *param_b) {
    int local_1;
    char local_2;

    ...
    }

    那么它应该被转换成什么样的汇编代码呢?在思考这个问题之前,我们需要了解当 demo函数被调用时,计算机的栈的状态,如下(参照第三章讲解的虚拟机):

    |    ....       | high address
    +---------------+
    | arg: param_a | new_bp + 3
    +---------------+
    | arg: param_b | new_bp + 2
    +---------------+
    |return address | new_bp + 1
    +---------------+
    | old BP | <- new BP
    +---------------+
    | local_1 | new_bp - 1
    +---------------+
    | local_2 | new_bp - 2
    +---------------+
    | .... | low address

    这里最为重要的一点是,无论是函数的参数(如 param_a)还是函数的局部变量(如 local_1)都是存放在计算机的 上的。因此,与存放在 数据段 中的全局变量不同,在函数内访问它们是通过 new_bp 指针和对应的位移量进行的。因此,在解析的过程中,我们需要知道参数的个数,各个参数的位移量。

    函数定义的解析

    这相当于是整个函数定义的语法解析的框架,代码如下:

    void function_declaration() {
    // type func_name (...) {...}
    // | this part

    match('(');
    function_parameter();
    match(')');
    match('{');
    function_body();
    //match('}'); // ①

    // ②
    // unwind local variable declarations for all local variables.
    current_id = symbols;
    while (current_id[Token]) {
    if (current_id[Class] == Loc) {
    current_id[Class] = current_id[BClass];
    current_id[Type] = current_id[BType];
    current_id[Value] = current_id[BValue];
    }
    current_id = current_id + IdSize;
    }
    }

    其中①中我们没有消耗最后的}字符。这么做的原因是:variable_declfunction_decl 是放在一起解析的,而 variable_decl 是以字符 ; 结束的。而 function_decl 是以字符 } 结束的,若在此通过 match 消耗了 ‘;’ 字符,那么外层的 while 循环就没法准确地知道函数定义已经结束。所以我们将结束符的解析放在了外层的 while 循环中。

    而②中的代码是用于将符号表中的信息恢复成全局的信息。这是因为,局部变量是可以和全局变量同名的,一旦同名,在函数体内局部变量就会覆盖全局变量,出了函数体,全局变量就恢复了原先的作用。这段代码线性地遍历所有标识符,并将保存在 BXXX 中的信息还原。

    解析参数

    parameter_decl ::= type {'*'} id {',' type {'*'} id}

    解析函数的参数就是解析以逗号分隔的一个个标识符,同时记录它们的位置与类型。

    int index_of_bp; // index of bp pointer on stack

    void function_parameter() {
    int type;
    int params;
    params = 0;
    while (token != ')') {
    // ①

    // int name, ...
    type = INT;
    if (token == Int) {
    match(Int);
    } else if (token == Char) {
    type = CHAR;
    match(Char);
    }

    // pointer type
    while (token == Mul) {
    match(Mul);
    type = type + PTR;
    }

    // parameter name
    if (token != Id) {
    printf("%d: bad parameter declaration\\n", line);
    exit(-1);
    }
    if (current_id[Class] == Loc) {
    printf("%d: duplicate parameter declaration\\n", line);
    exit(-1);
    }

    match(Id);

    //②
    // store the local variable
    current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
    current_id[BType] = current_id[Type]; current_id[Type] = type;
    current_id[BValue] = current_id[Value]; current_id[Value] = params++; // index of current parameter

    if (token == ',') {
    match(',');
    }
    }

    // ③
    index_of_bp = params+1;
    }

    其中①与全局变量定义的解析十分一样,用于解析该参数的类型。

    而②则与上节中提到的“局部变量覆盖全局变量”相关,先将全局变量的信息保存(无论是是否真的在全局中用到了这个变量)在 BXXX 中,再赋上局部变量相关的信息,如 Value 中存放的是参数的位置(是第几个参数)。

    ③则与汇编代码的生成有关,index_of_bp 就是前文提到的 new_bp 的位置。

    函数体的解析

    我们实现的 C 语言与现代的 C 语言不太一致,我们需要所有的变量定义出现在所有的语句之前。函数体的代码如下:

    void function_body() {
    // type func_name (...) {...}
    // -->| |<--

    // ... {
    // 1. local declarations
    // 2. statements
    // }

    int pos_local; // position of local variables on the stack.
    int type;
    pos_local = index_of_bp;

    // ①
    while (token == Int || token == Char) {
    // local variable declaration, just like global ones.
    basetype = (token == Int) ? INT : CHAR;
    match(token);

    while (token != ';') {
    type = basetype;
    while (token == Mul) {
    match(Mul);
    type = type + PTR;
    }

    if (token != Id) {
    // invalid declaration
    printf("%d: bad local declaration\\n", line);
    exit(-1);
    }
    if (current_id[Class] == Loc) {
    // identifier exists
    printf("%d: duplicate local declaration\\n", line);
    exit(-1);
    }
    match(Id);

    // store the local variable
    current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
    current_id[BType] = current_id[Type]; current_id[Type] = type;
    current_id[BValue] = current_id[Value]; current_id[Value] = ++pos_local; // index of current parameter

    if (token == ',') {
    match(',');
    }
    }
    match(';');
    }

    // ②
    // save the stack size for local variables
    *++text = ENT;
    *++text = pos_local - index_of_bp;

    // statements
    while (token != '}') {
    statement();
    }

    // emit code for leaving the sub function
    *++text = LEV;
    }

    其中①用于解析函数体内的局部变量的定义,代码与全局的变量定义几乎一样。

    而②则用于生成汇编代码,我们在第三章的虚拟机中提到过,我们需要在栈上为局部变量预留空间,这两行代码起的就是这个作用。

    代码

    ',31),C={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-4",target:"_blank",rel:"noopener noreferrer"},w=l('
    git clone -b step-4 https://github.com/lotabout/write-a-C-interpreter

    本章的代码依旧无法运行,还有两个重要函数没有完成:statementexpression,感兴趣的话可以尝试自己实现它们。

    小结

    本章中我们用了不多的代码完成了函数定义的解析。大部分的代码依旧是用于解析变量:参数和局部变量,而它们的逻辑和全局变量的解析几乎一致,最大的区别就是保存的信息不同。

    当然,要理解函数定义的解析过程,最重要的是理解我们会为函数生成怎样的汇编代码,因为这决定了我们需要从解析中获取什么样的信息(例如参数的位置,个数等),而这些可能需要你重新回顾一下“虚拟机”这一章,或是重新学习学习汇编相关的知识。

    下一章中我们将讲解语句的解析,敬请期待。

    ',6);function x(v,T){const n=t("ExternalLinkIcon");return r(),c("div",null,[o,s("p",null,[a("本文转自 "),s("a",b,[a("https://lotabout.me/2016/write-a-C-interpreter-6/"),e(n)]),a(",如有侵权,请联系删除。")]),d,s("ol",null,[s("li",null,[s("a",h,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",_,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",B,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),A,s("p",null,[a("本章的代码可以在 "),s("a",C,[a("Github"),e(n)]),a(" 上下载,也可以直接 clone")]),w])}const N=p(i,[["render",x],["__file","6.html.vue"]]),D=JSON.parse('{"path":"/tech/designASimpileCCompiler/6.html","title":"手把手教你构建 C 语言编译器(6)——函数定义","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(6)——函数定义","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-6/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(6)- 函数定义 Table of Contents 1. EBNF 表示 2. 解析函数的定义 2.1. 函数参数与汇编代码 2.2. 函数定义的解析 2.3. 解析...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/6.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(6)——函数定义"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-6/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(6)- 函数定义 Table of Contents 1. EBNF 表示 2. 解析函数的定义 2.1. 函数参数与汇编代码 2.2. 函数定义的解析 2.3. 解析..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(6)——函数定义\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"EBNF 表示","slug":"ebnf-表示","link":"#ebnf-表示","children":[]},{"level":2,"title":"解析函数的定义","slug":"解析函数的定义","link":"#解析函数的定义","children":[{"level":3,"title":"函数参数与汇编代码","slug":"函数参数与汇编代码","link":"#函数参数与汇编代码","children":[]},{"level":3,"title":"函数定义的解析","slug":"函数定义的解析","link":"#函数定义的解析","children":[]},{"level":3,"title":"解析参数","slug":"解析参数","link":"#解析参数","children":[]},{"level":3,"title":"函数体的解析","slug":"函数体的解析","link":"#函数体的解析","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":12.61,"words":3783},"filePathRelative":"tech/designASimpileCCompiler/6.md","excerpt":"\\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-6/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(6)- 函数定义

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. EBNF 表示
    2. \\n
    3. 2. 解析函数的定义\\n
        \\n
      1. 2.1. 函数参数与汇编代码
      2. \\n
      3. 2.2. 函数定义的解析
      4. \\n
      5. 2.3. 解析参数
      6. \\n
      7. 2.4. 函数体的解析
      8. \\n
      \\n
    4. \\n
    5. 3. 代码
    6. \\n
    7. 4. 小结
    8. \\n
    ","autoDesc":true}');export{N as comp,D as data}; +import{_ as p,r as t,o as r,c,b as s,e as a,d as e,f as l}from"./app-B4LGNJZ0.js";const i={},o=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},d=l('

    原文内容

    手把手教你构建 C 语言编译器(6)- 函数定义

    Table of Contents

    1. 1. EBNF 表示
    2. 2. 解析函数的定义
      1. 2.1. 函数参数与汇编代码
      2. 2.2. 函数定义的解析
      3. 2.3. 解析参数
      4. 2.4. 函数体的解析
    3. 3. 代码
    4. 4. 小结

    由于语法分析本身比较复杂,所以我们将它拆分成 3 个部分进行讲解,分别是:变量定义、函数定义、表达式。本章讲解函数定义相关的内容。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),h={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},B={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},A=l('

    EBNF 表示

    这是上一章的 EBNF 方法中与函数定义相关的内容。

    variable_decl ::= type {'*'} id { ',' {'*'} id } ';'

    function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'

    parameter_decl ::= type {'*'} id {',' type {'*'} id}

    body_decl ::= {variable_decl}, {statement}

    statement ::= non_empty_statement | empty_statement

    non_empty_statement ::= if_statement | while_statement | '{' statement '}'
    | 'return' expression | expression ';'

    if_statement ::= 'if' '(' expression ')' statement ['else' non_empty_statement]

    while_statement ::= 'while' '(' expression ')' non_empty_statement

    解析函数的定义

    上一章的代码中,我们已经知道了什么时候开始解析函数的定义,相关的代码如下:

    ...
    if (token == '(') {
    current_id[Class] = Fun;
    current_id[Value] = (int)(text + 1); // the memory address of function
    function_declaration();
    } else {
    ...

    即在这断代码之前,我们已经为当前的标识符(identifier)设置了正确的类型,上面这断代码为当前的标识符设置了正确的类别(Fun),以及该函数在代码段(text segment)中的位置。接下来开始解析函数定义相关的内容:parameter_declbody_decl

    函数参数与汇编代码

    现在我们要回忆如何将“函数”转换成对应的汇编代码,因为这决定了在解析时我们需要哪些相关的信息。考虑下列函数:

    int demo(int param_a, int *param_b) {
    int local_1;
    char local_2;

    ...
    }

    那么它应该被转换成什么样的汇编代码呢?在思考这个问题之前,我们需要了解当 demo函数被调用时,计算机的栈的状态,如下(参照第三章讲解的虚拟机):

    |    ....       | high address
    +---------------+
    | arg: param_a | new_bp + 3
    +---------------+
    | arg: param_b | new_bp + 2
    +---------------+
    |return address | new_bp + 1
    +---------------+
    | old BP | <- new BP
    +---------------+
    | local_1 | new_bp - 1
    +---------------+
    | local_2 | new_bp - 2
    +---------------+
    | .... | low address

    这里最为重要的一点是,无论是函数的参数(如 param_a)还是函数的局部变量(如 local_1)都是存放在计算机的 上的。因此,与存放在 数据段 中的全局变量不同,在函数内访问它们是通过 new_bp 指针和对应的位移量进行的。因此,在解析的过程中,我们需要知道参数的个数,各个参数的位移量。

    函数定义的解析

    这相当于是整个函数定义的语法解析的框架,代码如下:

    void function_declaration() {
    // type func_name (...) {...}
    // | this part

    match('(');
    function_parameter();
    match(')');
    match('{');
    function_body();
    //match('}'); // ①

    // ②
    // unwind local variable declarations for all local variables.
    current_id = symbols;
    while (current_id[Token]) {
    if (current_id[Class] == Loc) {
    current_id[Class] = current_id[BClass];
    current_id[Type] = current_id[BType];
    current_id[Value] = current_id[BValue];
    }
    current_id = current_id + IdSize;
    }
    }

    其中①中我们没有消耗最后的}字符。这么做的原因是:variable_declfunction_decl 是放在一起解析的,而 variable_decl 是以字符 ; 结束的。而 function_decl 是以字符 } 结束的,若在此通过 match 消耗了 ‘;’ 字符,那么外层的 while 循环就没法准确地知道函数定义已经结束。所以我们将结束符的解析放在了外层的 while 循环中。

    而②中的代码是用于将符号表中的信息恢复成全局的信息。这是因为,局部变量是可以和全局变量同名的,一旦同名,在函数体内局部变量就会覆盖全局变量,出了函数体,全局变量就恢复了原先的作用。这段代码线性地遍历所有标识符,并将保存在 BXXX 中的信息还原。

    解析参数

    parameter_decl ::= type {'*'} id {',' type {'*'} id}

    解析函数的参数就是解析以逗号分隔的一个个标识符,同时记录它们的位置与类型。

    int index_of_bp; // index of bp pointer on stack

    void function_parameter() {
    int type;
    int params;
    params = 0;
    while (token != ')') {
    // ①

    // int name, ...
    type = INT;
    if (token == Int) {
    match(Int);
    } else if (token == Char) {
    type = CHAR;
    match(Char);
    }

    // pointer type
    while (token == Mul) {
    match(Mul);
    type = type + PTR;
    }

    // parameter name
    if (token != Id) {
    printf("%d: bad parameter declaration\\n", line);
    exit(-1);
    }
    if (current_id[Class] == Loc) {
    printf("%d: duplicate parameter declaration\\n", line);
    exit(-1);
    }

    match(Id);

    //②
    // store the local variable
    current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
    current_id[BType] = current_id[Type]; current_id[Type] = type;
    current_id[BValue] = current_id[Value]; current_id[Value] = params++; // index of current parameter

    if (token == ',') {
    match(',');
    }
    }

    // ③
    index_of_bp = params+1;
    }

    其中①与全局变量定义的解析十分一样,用于解析该参数的类型。

    而②则与上节中提到的“局部变量覆盖全局变量”相关,先将全局变量的信息保存(无论是是否真的在全局中用到了这个变量)在 BXXX 中,再赋上局部变量相关的信息,如 Value 中存放的是参数的位置(是第几个参数)。

    ③则与汇编代码的生成有关,index_of_bp 就是前文提到的 new_bp 的位置。

    函数体的解析

    我们实现的 C 语言与现代的 C 语言不太一致,我们需要所有的变量定义出现在所有的语句之前。函数体的代码如下:

    void function_body() {
    // type func_name (...) {...}
    // -->| |<--

    // ... {
    // 1. local declarations
    // 2. statements
    // }

    int pos_local; // position of local variables on the stack.
    int type;
    pos_local = index_of_bp;

    // ①
    while (token == Int || token == Char) {
    // local variable declaration, just like global ones.
    basetype = (token == Int) ? INT : CHAR;
    match(token);

    while (token != ';') {
    type = basetype;
    while (token == Mul) {
    match(Mul);
    type = type + PTR;
    }

    if (token != Id) {
    // invalid declaration
    printf("%d: bad local declaration\\n", line);
    exit(-1);
    }
    if (current_id[Class] == Loc) {
    // identifier exists
    printf("%d: duplicate local declaration\\n", line);
    exit(-1);
    }
    match(Id);

    // store the local variable
    current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
    current_id[BType] = current_id[Type]; current_id[Type] = type;
    current_id[BValue] = current_id[Value]; current_id[Value] = ++pos_local; // index of current parameter

    if (token == ',') {
    match(',');
    }
    }
    match(';');
    }

    // ②
    // save the stack size for local variables
    *++text = ENT;
    *++text = pos_local - index_of_bp;

    // statements
    while (token != '}') {
    statement();
    }

    // emit code for leaving the sub function
    *++text = LEV;
    }

    其中①用于解析函数体内的局部变量的定义,代码与全局的变量定义几乎一样。

    而②则用于生成汇编代码,我们在第三章的虚拟机中提到过,我们需要在栈上为局部变量预留空间,这两行代码起的就是这个作用。

    代码

    ',31),C={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-4",target:"_blank",rel:"noopener noreferrer"},w=l('
    git clone -b step-4 https://github.com/lotabout/write-a-C-interpreter

    本章的代码依旧无法运行,还有两个重要函数没有完成:statementexpression,感兴趣的话可以尝试自己实现它们。

    小结

    本章中我们用了不多的代码完成了函数定义的解析。大部分的代码依旧是用于解析变量:参数和局部变量,而它们的逻辑和全局变量的解析几乎一致,最大的区别就是保存的信息不同。

    当然,要理解函数定义的解析过程,最重要的是理解我们会为函数生成怎样的汇编代码,因为这决定了我们需要从解析中获取什么样的信息(例如参数的位置,个数等),而这些可能需要你重新回顾一下“虚拟机”这一章,或是重新学习学习汇编相关的知识。

    下一章中我们将讲解语句的解析,敬请期待。

    ',6);function x(v,T){const n=t("ExternalLinkIcon");return r(),c("div",null,[o,s("p",null,[a("本文转自 "),s("a",b,[a("https://lotabout.me/2016/write-a-C-interpreter-6/"),e(n)]),a(",如有侵权,请联系删除。")]),d,s("ol",null,[s("li",null,[s("a",h,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",_,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",B,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),A,s("p",null,[a("本章的代码可以在 "),s("a",C,[a("Github"),e(n)]),a(" 上下载,也可以直接 clone")]),w])}const N=p(i,[["render",x],["__file","6.html.vue"]]),D=JSON.parse('{"path":"/tech/designASimpileCCompiler/6.html","title":"手把手教你构建 C 语言编译器(6)——函数定义","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(6)——函数定义","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-6/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(6)- 函数定义 Table of Contents 1. EBNF 表示 2. 解析函数的定义 2.1. 函数参数与汇编代码 2.2. 函数定义的解析 2.3. 解析...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/6.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(6)——函数定义"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-6/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(6)- 函数定义 Table of Contents 1. EBNF 表示 2. 解析函数的定义 2.1. 函数参数与汇编代码 2.2. 函数定义的解析 2.3. 解析..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(6)——函数定义\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"EBNF 表示","slug":"ebnf-表示","link":"#ebnf-表示","children":[]},{"level":2,"title":"解析函数的定义","slug":"解析函数的定义","link":"#解析函数的定义","children":[{"level":3,"title":"函数参数与汇编代码","slug":"函数参数与汇编代码","link":"#函数参数与汇编代码","children":[]},{"level":3,"title":"函数定义的解析","slug":"函数定义的解析","link":"#函数定义的解析","children":[]},{"level":3,"title":"解析参数","slug":"解析参数","link":"#解析参数","children":[]},{"level":3,"title":"函数体的解析","slug":"函数体的解析","link":"#函数体的解析","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":12.61,"words":3783},"filePathRelative":"tech/designASimpileCCompiler/6.md","excerpt":"\\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-6/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(6)- 函数定义

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. EBNF 表示
    2. \\n
    3. 2. 解析函数的定义\\n
        \\n
      1. 2.1. 函数参数与汇编代码
      2. \\n
      3. 2.2. 函数定义的解析
      4. \\n
      5. 2.3. 解析参数
      6. \\n
      7. 2.4. 函数体的解析
      8. \\n
      \\n
    4. \\n
    5. 3. 代码
    6. \\n
    7. 4. 小结
    8. \\n
    ","autoDesc":true}');export{N as comp,D as data}; diff --git a/assets/7.html-DopH3hey.js b/assets/7.html-BzOHeKw-.js similarity index 99% rename from assets/7.html-DopH3hey.js rename to assets/7.html-BzOHeKw-.js index 7b6d505..cb78404 100644 --- a/assets/7.html-DopH3hey.js +++ b/assets/7.html-BzOHeKw-.js @@ -1 +1 @@ -import{_ as l,r,o as p,c as i,b as e,e as a,d as n,f as t}from"./app-B69Gl_S-.js";const o={},c=e("h1",{id:"转载声明",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#转载声明"},[e("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},h=t('

    原文内容

    手把手教你构建 C 语言编译器(7)- 语句

    Table of Contents

    1. 1. 语句
      1. 1.1. IF 语句
      2. 1.2. While 语句
      3. 1.3. Return 语句
      4. 1.4. 其它语句
    2. 2. 代码
    3. 3. 小结

    整个编译器还剩下最后两个部分:语句和表达式的解析。它们的内容比较多,主要涉及如何将语句和表达式编译成汇编代码。这章讲解语句的解析,相对于表达式来说它还是较为容易的。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),d={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},y=t('

    语句

    C 语言区分“语句”(statement)和“表达式”(expression)两个概念。简单地说,可以认为语句就是表达式加上末尾的分号。

    在我们的编译器中共识别 6 种语句:

    1. if (...) <statement> [else <statement>]
    2. while (...) <statement>
    3. { <statement> }
    4. return xxx;
    5. <empty statement>;
    6. expression; (expression end with semicolon)

    它们的语法分析都相对容易,重要的是去理解如何将这些语句编译成汇编代码,下面我们逐一解释。

    IF 语句

    IF 语句的作用是跳转,跟据条件表达式决定跳转的位置。我们看看下面的伪代码:

    if (...) <statement> [else <statement>]

    if (<cond>) <cond>
    JZ a
    <true_statement> ===> <true_statement>
    else: JMP b
    a: a:
    <false_statement> <false_statement>
    b: b:

    对应的汇编代码流程为:

    1. 执行条件表达式 <cond>
    2. 如果条件失败,则跳转到 a 的位置,执行 else 语句。这里 else 语句是可以省略的,此时 ab 都指向 IF 语句后方的代码。
    3. 因为汇编代码是顺序排列的,所以如果执行了 true_statement,为了防止因为顺序排列而执行了 false_statement,所以需要无条件跳转 JMP b

    对应的 C 代码如下:

    if (token == If) {
    match(If);
    match('(');
    expression(Assign); // parse condition
    match(')');

    *++text = JZ;
    b = ++text;

    statement(); // parse statement
    if (token == Else) { // parse else
    match(Else);

    // emit code for JMP B
    *b = (int)(text + 3);
    *++text = JMP;
    b = ++text;

    statement();
    }

    *b = (int)(text + 1);
    }

    While 语句

    While 语句比 If 语句简单,它对应的汇编代码如下:

    a:                     a:
    while (<cond>) <cond>
    JZ b
    <statement> <statement>
    JMP a
    b: b:

    没有什么值得说明的内容,它的 C 代码如下:

    else if (token == While) {
    match(While);

    a = text + 1;

    match('(');
    expression(Assign);
    match(')');

    *++text = JZ;
    b = ++text;

    statement();

    *++text = JMP;
    *++text = (int)a;
    *b = (int)(text + 1);
    }

    Return 语句

    Return 唯一特殊的地方是:一旦遇到了 Return 语句,则意味着函数要退出了,所以需要生成汇编代码 LEV 来表示退出。

    else if (token == Return) {
    // return [expression];
    match(Return);

    if (token != ';') {
    expression(Assign);
    }

    match(';');

    // emit code for return
    *++text = LEV;
    }

    其它语句

    其它语句并不直接生成汇编代码,所以不多做说明,代码如下:

    else if (token == '{') {
    // { <statement> ... }
    match('{');

    while (token != '}') {
    statement();
    }

    match('}');
    }
    else if (token == ';') {
    // empty statement
    match(';');
    }
    else {
    // a = b; or function_call();
    expression(Assign);
    match(';');
    }

    代码

    ',24),w={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-5",target:"_blank",rel:"noopener noreferrer"},x=t('
    git clone -b step-5 https://github.com/lotabout/write-a-C-interpreter

    本章的代码依旧无法运行,还剩最后一部分没有完成:expression

    小结

    本章讲解了如何将语句编译成汇编代码,内容相对容易一些,关键就是去理解汇编代码的执行原理。

    同时值得一提的是,编译器的语法分析部分其实是很简单的,而真正的难点是如何在语法分析时收集足够多的信息,最终把源代码转换成目标代码(汇编)。我认为这也是初学者实现编译器的一大难点,往往比词法分析/语法分析更困难。

    所以建议如果没有学过汇编,可以学习学习,它本身不难,但对理解计算机的原理有很大帮助。

    ',6);function F(B,D){const s=r("ExternalLinkIcon");return p(),i("div",null,[c,e("p",null,[a("本文转自 "),e("a",b,[a("https://lotabout.me/2016/write-a-C-interpreter-7/"),n(s)]),a(",如有侵权,请联系删除。")]),h,e("ol",null,[e("li",null,[e("a",d,[a("手把手教你构建 C 语言编译器(0)——前言"),n(s)])]),e("li",null,[e("a",m,[a("手把手教你构建 C 语言编译器(1)——设计"),n(s)])]),e("li",null,[e("a",u,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),n(s)])]),e("li",null,[e("a",f,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),n(s)])]),e("li",null,[e("a",g,[a("手把手教你构建 C 语言编译器(4)——递归下降"),n(s)])]),e("li",null,[e("a",_,[a("手把手教你构建 C 语言编译器(5)——变量定义"),n(s)])]),e("li",null,[e("a",k,[a("手把手教你构建 C 语言编译器(6)——函数定义"),n(s)])]),e("li",null,[e("a",A,[a("手把手教你构建 C 语言编译器(7)——语句"),n(s)])]),e("li",null,[e("a",E,[a("手把手教你构建 C 语言编译器(8)——表达式"),n(s)])]),e("li",null,[e("a",C,[a("手把手教你构建 C 语言编译器(9)——总结"),n(s)])])]),y,e("p",null,[a("本章的代码可以在 "),e("a",w,[a("Github"),n(s)]),a(" 上下载,也可以直接 clone")]),x])}const J=l(o,[["render",F],["__file","7.html.vue"]]),v=JSON.parse('{"path":"/tech/designASimpileCCompiler/7.html","title":"手把手教你构建 C 语言编译器(7)——语句","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(7)——语句","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-7/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(7)- 语句 Table of Contents 1. 语句 1.1. IF 语句 1.2. While 语句 1.3. Return 语句 1.4. 其它语句 2. ...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/7.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(7)——语句"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-7/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(7)- 语句 Table of Contents 1. 语句 1.1. IF 语句 1.2. While 语句 1.3. Return 语句 1.4. 其它语句 2. ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(7)——语句\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"语句","slug":"语句","link":"#语句","children":[{"level":3,"title":"IF 语句","slug":"if-语句","link":"#if-语句","children":[]},{"level":3,"title":"While 语句","slug":"while-语句","link":"#while-语句","children":[]},{"level":3,"title":"Return 语句","slug":"return-语句","link":"#return-语句","children":[]},{"level":3,"title":"其它语句","slug":"其它语句","link":"#其它语句","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":6.99,"words":2097},"filePathRelative":"tech/designASimpileCCompiler/7.md","excerpt":"\\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-7/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(7)- 语句

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 语句\\n
        \\n
      1. 1.1. IF 语句
      2. \\n
      3. 1.2. While 语句
      4. \\n
      5. 1.3. Return 语句
      6. \\n
      7. 1.4. 其它语句
      8. \\n
      \\n
    2. \\n
    3. 2. 代码
    4. \\n
    5. 3. 小结
    6. \\n
    ","autoDesc":true}');export{J as comp,v as data}; +import{_ as l,r,o as p,c as i,b as e,e as a,d as n,f as t}from"./app-B4LGNJZ0.js";const o={},c=e("h1",{id:"转载声明",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#转载声明"},[e("span",null,"转载声明")])],-1),b={href:"https://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},h=t('

    原文内容

    手把手教你构建 C 语言编译器(7)- 语句

    Table of Contents

    1. 1. 语句
      1. 1.1. IF 语句
      2. 1.2. While 语句
      3. 1.3. Return 语句
      4. 1.4. 其它语句
    2. 2. 代码
    3. 3. 小结

    整个编译器还剩下最后两个部分:语句和表达式的解析。它们的内容比较多,主要涉及如何将语句和表达式编译成汇编代码。这章讲解语句的解析,相对于表达式来说它还是较为容易的。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),d={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},y=t('

    语句

    C 语言区分“语句”(statement)和“表达式”(expression)两个概念。简单地说,可以认为语句就是表达式加上末尾的分号。

    在我们的编译器中共识别 6 种语句:

    1. if (...) <statement> [else <statement>]
    2. while (...) <statement>
    3. { <statement> }
    4. return xxx;
    5. <empty statement>;
    6. expression; (expression end with semicolon)

    它们的语法分析都相对容易,重要的是去理解如何将这些语句编译成汇编代码,下面我们逐一解释。

    IF 语句

    IF 语句的作用是跳转,跟据条件表达式决定跳转的位置。我们看看下面的伪代码:

    if (...) <statement> [else <statement>]

    if (<cond>) <cond>
    JZ a
    <true_statement> ===> <true_statement>
    else: JMP b
    a: a:
    <false_statement> <false_statement>
    b: b:

    对应的汇编代码流程为:

    1. 执行条件表达式 <cond>
    2. 如果条件失败,则跳转到 a 的位置,执行 else 语句。这里 else 语句是可以省略的,此时 ab 都指向 IF 语句后方的代码。
    3. 因为汇编代码是顺序排列的,所以如果执行了 true_statement,为了防止因为顺序排列而执行了 false_statement,所以需要无条件跳转 JMP b

    对应的 C 代码如下:

    if (token == If) {
    match(If);
    match('(');
    expression(Assign); // parse condition
    match(')');

    *++text = JZ;
    b = ++text;

    statement(); // parse statement
    if (token == Else) { // parse else
    match(Else);

    // emit code for JMP B
    *b = (int)(text + 3);
    *++text = JMP;
    b = ++text;

    statement();
    }

    *b = (int)(text + 1);
    }

    While 语句

    While 语句比 If 语句简单,它对应的汇编代码如下:

    a:                     a:
    while (<cond>) <cond>
    JZ b
    <statement> <statement>
    JMP a
    b: b:

    没有什么值得说明的内容,它的 C 代码如下:

    else if (token == While) {
    match(While);

    a = text + 1;

    match('(');
    expression(Assign);
    match(')');

    *++text = JZ;
    b = ++text;

    statement();

    *++text = JMP;
    *++text = (int)a;
    *b = (int)(text + 1);
    }

    Return 语句

    Return 唯一特殊的地方是:一旦遇到了 Return 语句,则意味着函数要退出了,所以需要生成汇编代码 LEV 来表示退出。

    else if (token == Return) {
    // return [expression];
    match(Return);

    if (token != ';') {
    expression(Assign);
    }

    match(';');

    // emit code for return
    *++text = LEV;
    }

    其它语句

    其它语句并不直接生成汇编代码,所以不多做说明,代码如下:

    else if (token == '{') {
    // { <statement> ... }
    match('{');

    while (token != '}') {
    statement();
    }

    match('}');
    }
    else if (token == ';') {
    // empty statement
    match(';');
    }
    else {
    // a = b; or function_call();
    expression(Assign);
    match(';');
    }

    代码

    ',24),w={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-5",target:"_blank",rel:"noopener noreferrer"},x=t('
    git clone -b step-5 https://github.com/lotabout/write-a-C-interpreter

    本章的代码依旧无法运行,还剩最后一部分没有完成:expression

    小结

    本章讲解了如何将语句编译成汇编代码,内容相对容易一些,关键就是去理解汇编代码的执行原理。

    同时值得一提的是,编译器的语法分析部分其实是很简单的,而真正的难点是如何在语法分析时收集足够多的信息,最终把源代码转换成目标代码(汇编)。我认为这也是初学者实现编译器的一大难点,往往比词法分析/语法分析更困难。

    所以建议如果没有学过汇编,可以学习学习,它本身不难,但对理解计算机的原理有很大帮助。

    ',6);function F(B,D){const s=r("ExternalLinkIcon");return p(),i("div",null,[c,e("p",null,[a("本文转自 "),e("a",b,[a("https://lotabout.me/2016/write-a-C-interpreter-7/"),n(s)]),a(",如有侵权,请联系删除。")]),h,e("ol",null,[e("li",null,[e("a",d,[a("手把手教你构建 C 语言编译器(0)——前言"),n(s)])]),e("li",null,[e("a",m,[a("手把手教你构建 C 语言编译器(1)——设计"),n(s)])]),e("li",null,[e("a",u,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),n(s)])]),e("li",null,[e("a",f,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),n(s)])]),e("li",null,[e("a",g,[a("手把手教你构建 C 语言编译器(4)——递归下降"),n(s)])]),e("li",null,[e("a",_,[a("手把手教你构建 C 语言编译器(5)——变量定义"),n(s)])]),e("li",null,[e("a",k,[a("手把手教你构建 C 语言编译器(6)——函数定义"),n(s)])]),e("li",null,[e("a",A,[a("手把手教你构建 C 语言编译器(7)——语句"),n(s)])]),e("li",null,[e("a",E,[a("手把手教你构建 C 语言编译器(8)——表达式"),n(s)])]),e("li",null,[e("a",C,[a("手把手教你构建 C 语言编译器(9)——总结"),n(s)])])]),y,e("p",null,[a("本章的代码可以在 "),e("a",w,[a("Github"),n(s)]),a(" 上下载,也可以直接 clone")]),x])}const J=l(o,[["render",F],["__file","7.html.vue"]]),v=JSON.parse('{"path":"/tech/designASimpileCCompiler/7.html","title":"手把手教你构建 C 语言编译器(7)——语句","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(7)——语句","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-7/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(7)- 语句 Table of Contents 1. 语句 1.1. IF 语句 1.2. While 语句 1.3. Return 语句 1.4. 其它语句 2. ...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/7.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(7)——语句"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-7/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(7)- 语句 Table of Contents 1. 语句 1.1. IF 语句 1.2. While 语句 1.3. Return 语句 1.4. 其它语句 2. ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(7)——语句\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"语句","slug":"语句","link":"#语句","children":[{"level":3,"title":"IF 语句","slug":"if-语句","link":"#if-语句","children":[]},{"level":3,"title":"While 语句","slug":"while-语句","link":"#while-语句","children":[]},{"level":3,"title":"Return 语句","slug":"return-语句","link":"#return-语句","children":[]},{"level":3,"title":"其它语句","slug":"其它语句","link":"#其它语句","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":6.99,"words":2097},"filePathRelative":"tech/designASimpileCCompiler/7.md","excerpt":"\\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-7/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(7)- 语句

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 语句\\n
        \\n
      1. 1.1. IF 语句
      2. \\n
      3. 1.2. While 语句
      4. \\n
      5. 1.3. Return 语句
      6. \\n
      7. 1.4. 其它语句
      8. \\n
      \\n
    2. \\n
    3. 2. 代码
    4. \\n
    5. 3. 小结
    6. \\n
    ","autoDesc":true}');export{J as comp,v as data}; diff --git a/assets/8.html-D1TnfZgI.js b/assets/8.html-C-sA1tiy.js similarity index 99% rename from assets/8.html-D1TnfZgI.js rename to assets/8.html-C-sA1tiy.js index e9ef834..3f906ea 100644 --- a/assets/8.html-D1TnfZgI.js +++ b/assets/8.html-C-sA1tiy.js @@ -1 +1 @@ -import{_ as l,r as t,o as c,c as r,b as s,e as a,d as e,f as p}from"./app-B69Gl_S-.js";const i={},o=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),b={href:"https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501",target:"_blank",rel:"noopener noreferrer"},d=p('

    原文内容

    手把手教你构建 C 语言编译器(8)- 表达式

    Table of Contents

    1. 1. 运算符的优先级
    2. 2. 一元运算符
      1. 2.1. 常量
      2. 2.2. sizeof
      3. 2.3. 变量与函数调用
      4. 2.4. 强制转换
      5. 2.5. 指针取值
      6. 2.6. 取址操作
      7. 2.7. 逻辑取反
      8. 2.8. 按位取反
      9. 2.9. 正负号
      10. 2.10. 自增自减
    3. 3. 二元运算符
      1. 3.1. 赋值操作
      2. 3.2. 三目运算符
      3. 3.3. 逻辑运算符
      4. 3.4. 数学运算符
      5. 3.5. 自增自减
      6. 3.6. 数组取值操作
    4. 4. 代码
    5. 5. 小结

    这是整个编译器的最后一部分,解析表达式。什么是表达式?表达式是将各种语言要素的一个组合,用来求值。例如:函数调用、变量赋值、运算符运算等等。

    表达式的解析难点有二:一是运算符的优先级问题,二是如何将表达式编译成目标代码。我们就来逐一说明。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',7),h={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},x={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},_=p('

    运算符的优先级

    运算符的优先级决定了表达式的运算顺序,如在普通的四则运算中,乘法 * 优先级高于加法 +,这就意味着表达式 2 + 3 * 4 的实际运行顺序是 2 + (3 * 4) 而不是 (2 + 3) * 4

    ',2),A={href:"http://en.cppreference.com/w/c/language/operator_precedence",target:"_blank",rel:"noopener noreferrer"},w=p('

    传统的编程书籍会用“逆波兰式”实现四则运算来讲解优先级问题。实际上,优先级关心的就是哪个运算符先计算,哪个运算符后计算(毕竟叫做“优先级”嘛)。而这就意味着我们需要决定先为哪个运算符生成目标代码(汇编),因为汇编代码是顺序排列的,我们必须先计算优先级高的运算符。

    那么如何确定运算符的优先级呢?答曰:栈(递归调用的实质也是栈的处理)。

    举一个例子:2 + 3 - 4 * 5,它的运算顺序是这样的:

    1. 2 入栈
    2. 遇到运算符 +,入栈,此时我们期待的是+的另一个参数
    3. 遇到数字 3,原则上我们需要立即计算 2+3的值,但我们不确定数字 3 是否属于优先级更高的运算符,所以先将它入栈。
    4. 遇到运算符 -,它的优先级和 + 相同,此时判断参数 3 属于这前的 +。将运算符 + 出栈,并将之前的 23 出栈,计算 2+3 的结果,得到 5 入栈。同时将运算符 - 入栈。
    5. 遇到数字4,同样不能确定是否能立即计算,入栈
    6. 遇到运算符 * 优先级大于 -,入栈
    7. 遇到数字5,依旧不能确定是否立即计算,入栈
    8. 表达式结束,运算符出栈,为 *,将参数出栈,计算 4*5 得到结果 20 入栈。
    9. 运算符出栈,为 -,将参数出栈,计算 5-20,得到 -15 入栈。
    10. 此时运算符栈为空,因此得到结果 -15
    // after step 1, 2
    | |
    +------+
    | 3 | | |
    +------+ +------+
    | 2 | | + |
    +------+ +------+

    // after step 4
    | | | |
    +------+ +------+
    | 5 | | - |
    +------+ +------+

    // after step 7
    | |
    +------+
    | 5 |
    +------+ +------+
    | 4 | | * |
    +------+ +------+
    | 5 | | - |
    +------+ +------+

    综上,在计算一个运算符‘x’之前,必须先查看它的右方,找出并计算所有优先级大于‘x’的运算符,之后再计算运算符‘x’。

    最后注意的是优先通常只与多元运算符相关,单元运算符往往没有这个问题(因为只有一个参数)。也可以认为“优先级”的实质就是两个运算符在抢参数。

    一元运算符

    上节中说到了运算符的优先级,也提到了优先级一般只与多元运算符有关,这也意味着一元运算符的优先级总是高于多元运算符。因为我们需要先对它们进行解析。

    当然,这部分也将同时解析参数本身(如变量、数字、字符串等等)。

    关于表达式的解析,与语法分析相关的部分就是上文所说的优先级问题了,而剩下的较难较烦的部分是与目标代码的生成有关的。因此对于需要讲解的运算符,我们主要从它的目标代码入手。

    常量

    首先是数字,用 IMM 指令将它加载到 AX 中即可:

    if (token == Num) {
    match(Num);

    // emit code
    *++text = IMM;
    *++text = token_val;
    expr_type = INT;
    }

    接着是字符串常量。它比较特殊的一点是 C 语言的字符串常量支持如下风格:

    char *p;
    p = "first line"
    "second line";

    即跨行的字符串拼接,它相当于:

    char *p;
    p = "first linesecond line";

    所以解析的时候要注意这一点:

    else if (token == '"') {
    // emit code
    *++text = IMM;
    *++text = token_val;

    match('"');
    // store the rest strings
    while (token == '"') {
    match('"');
    }

    // append the end of string character '\\0', all the data are default
    // to 0, so just move data one position forward.
    data = (char *)(((int)data + sizeof(int)) & (-sizeof(int)));
    expr_type = PTR;
    }

    sizeof

    sizeof 是一个一元运算符,我们需要知道后面参数的类型,类型的解析在前面的文章中我们已经很熟悉了。

    else if (token == Sizeof) {
    // sizeof is actually an unary operator
    // now only `sizeof(int)`, `sizeof(char)` and `sizeof(*...)` are
    // supported.
    match(Sizeof);
    match('(');
    expr_type = INT;

    if (token == Int) {
    match(Int);
    } else if (token == Char) {
    match(Char);
    expr_type = CHAR;
    }

    while (token == Mul) {
    match(Mul);
    expr_type = expr_type + PTR;
    }

    match(')');

    // emit code
    *++text = IMM;
    *++text = (expr_type == CHAR) ? sizeof(char) : sizeof(int);

    expr_type = INT;
    }

    注意的是只支持 sizeof(int)sizeof(char)sizeof(pointer type...)。并且它的结果是 int 型。

    变量与函数调用

    由于取变量的值与函数的调用都是以 Id 标记开头的,因此将它们放在一起处理。

    else if (token == Id) {
    // there are several type when occurs to Id
    // but this is unit, so it can only be
    // 1. function call
    // 2. Enum variable
    // 3. global/local variable
    match(Id);

    id = current_id;

    if (token == '(') {
    // function call
    match('(');

    // ①
    // pass in arguments
    tmp = 0; // number of arguments
    while (token != ')') {
    expression(Assign);
    *++text = PUSH;
    tmp ++;

    if (token == ',') {
    match(',');
    }
    }
    match(')');

    // ②
    // emit code
    if (id[Class] == Sys) {
    // system functions
    *++text = id[Value];
    }
    else if (id[Class] == Fun) {
    // function call
    *++text = CALL;
    *++text = id[Value];
    }
    else {
    printf("%d: bad function call\\n", line);
    exit(-1);
    }

    // ③
    // clean the stack for arguments
    if (tmp > 0) {
    *++text = ADJ;
    *++text = tmp;
    }
    expr_type = id[Type];
    }
    else if (id[Class] == Num) {
    // ④
    // enum variable
    *++text = IMM;
    *++text = id[Value];
    expr_type = INT;
    }
    else {
    // ⑤
    // variable
    if (id[Class] == Loc) {
    *++text = LEA;
    *++text = index_of_bp - id[Value];
    }
    else if (id[Class] == Glo) {
    *++text = IMM;
    *++text = id[Value];
    }
    else {
    printf("%d: undefined variable\\n", line);
    exit(-1);
    }

    //⑥
    // emit code, default behaviour is to load the value of the
    // address which is stored in `ax`
    expr_type = id[Type];
    *++text = (expr_type == Char) ? LC : LI;
    }
    }

    ①中注意我们是顺序将参数入栈,这和第三章:虚拟机中讲解的指令是对应的。与之不同,标准 C 是逆序将参数入栈的。

    ②中判断函数的类型,同样在第三章:“虚拟机”中我们介绍过内置函数的支持,如 printf, read, malloc 等等。内置函数有对应的汇编指令,而普通的函数则编译成 CALL <addr> 的形式。

    ③用于清除入栈的参数。因为我们不在乎出栈的值,所以直接修改栈指针的大小即可。

    ④:当该标识符是全局定义的枚举类型时,直接将对应的值用 IMM 指令存入 AX 即可。

    ⑤则是用于加载变量的值,如果是局部变量则采用与 bp 指针相对位置的形式(参见第 7章函数定义)。而如果是全局变量则用 IMM 加载变量的地址。

    ⑥:无论是全局还是局部变量,最终都根据它们的类型用 LCLI 指令加载对应的值。

    关于变量,你可能有疑问,如果遇到标识符就用 LC/LI 载入相应的值,那诸如 a[10] 之类的表达式要如何实现呢?后面我们会看到,根据标识符后的运算符,我们可能会修改或删除现有的 LC/LI 指令。

    强制转换

    虽然我们前面没有提到,但我们一直用 expr_type 来保存一个表达式的类型,强制转换的作用是获取转换的类型,并直接修改 expr_type 的值。

    else if (token == '(') {
    // cast or parenthesis
    match('(');
    if (token == Int || token == Char) {
    tmp = (token == Char) ? CHAR : INT; // cast type
    match(token);
    while (token == Mul) {
    match(Mul);
    tmp = tmp + PTR;
    }

    match(')');

    expression(Inc); // cast has precedence as Inc(++)

    expr_type = tmp;
    } else {
    // normal parenthesis
    expression(Assign);
    match(')');
    }
    }

    指针取值

    诸如 *a 的指针取值,关键是判断 a 的类型,而就像上节中提到的,当一个表达式解析结束时,它的类型保存在变量 expr_type 中。

    else if (token == Mul) {
    // dereference *<addr>
    match(Mul);
    expression(Inc); // dereference has the same precedence as Inc(++)

    if (expr_type >= PTR) {
    expr_type = expr_type - PTR;
    } else {
    printf("%d: bad dereference\\n", line);
    exit(-1);
    }

    *++text = (expr_type == CHAR) ? LC : LI;
    }

    取址操作

    这里我们就能看到“变量与函数调用”一节中所说的修改或删除 LC/LI 指令了。前文中我们说到,对于变量,我们会先加载它的地址,并根据它们类型使用 LC/LI 指令加载实际内容,例如对变量 a

    IMM <addr>
    LI

    那么对变量 a 取址,其实只要不执行 LC/LI 即可。因此我们删除相应的指令。

    else if (token == And) {
    // get the address of
    match(And);
    expression(Inc); // get the address of
    if (*text == LC || *text == LI) {
    text --;
    } else {
    printf("%d: bad address of\\n", line);
    exit(-1);
    }

    expr_type = expr_type + PTR;
    }

    逻辑取反

    我们没有直接的逻辑取反指令,因此我们判断它是否与数字 0 相等。而数字 0 代表了逻辑 “False”。

    else if (token == '!') {
    // not
    match('!');
    expression(Inc);

    // emit code, use <expr> == 0
    *++text = PUSH;
    *++text = IMM;
    *++text = 0;
    *++text = EQ;

    expr_type = INT;
    }

    按位取反

    同样我们没有相应的指令,所以我们用异或来实现,即 ~a = a ^ 0xFFFF

    else if (token == '~') {
    // bitwise not
    match('~');
    expression(Inc);

    // emit code, use <expr> XOR -1
    *++text = PUSH;
    *++text = IMM;
    *++text = -1;
    *++text = XOR;

    expr_type = INT;
    }

    正负号

    注意这里并不是四则运算中的加减法,而是单个数字的取正取负操作。同样,我们没有取负的操作,用 0 - x 来实现 -x

    else if (token == Add) {
    // +var, do nothing
    match(Add);
    expression(Inc);

    expr_type = INT;
    }
    else if (token == Sub) {
    // -var
    match(Sub);

    if (token == Num) {
    *++text = IMM;
    *++text = -token_val;
    match(Num);
    } else {

    *++text = IMM;
    *++text = -1;
    *++text = PUSH;
    expression(Inc);
    *++text = MUL;
    }

    expr_type = INT;
    }

    自增自减

    注意的是自增自减操作的优先级是和它的位置有关的。如 ++p 的优先级高于 p++,这里我们解析的就是类似 ++p 的操作。

    else if (token == Inc || token == Dec) {
    tmp = token;
    match(token);
    expression(Inc);
    // ①
    if (*text == LC) {
    *text = PUSH; // to duplicate the address
    *++text = LC;
    } else if (*text == LI) {
    *text = PUSH;
    *++text = LI;
    } else {
    printf("%d: bad lvalue of pre-increment\\n", line);
    exit(-1);
    }
    *++text = PUSH;
    *++text = IMM;
    // ②
    *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
    *++text = (tmp == Inc) ? ADD : SUB;
    *++text = (expr_type == CHAR) ? SC : SI;
    }

    对应的汇编代码也比较直观,只是在实现 ++p时,我们要使用变量 p 的地址两次,所以我们需要先 PUSH (①)。

    ②则是因为自增自减操作还需要处理是指针的情形。

    二元运算符

    这里,我们需要处理多运算符的优先级问题,就如前文的“优先级”一节提到的,我们需要不断地向右扫描,直到遇到优先级 小于 当前优先级的运算符。

    回想起我们之前定义过的各个标记,它们是以优先级从低到高排列的,即 Assign 的优先级最低,而 Brak[) 的优先级最高。

    enum {
    Num = 128, Fun, Sys, Glo, Loc, Id,
    Char, Else, Enum, If, Int, Return, Sizeof, While,
    Assign, Cond, Lor, Lan, Or, Xor, And, Eq, Ne, Lt, Gt, Le, Ge, Shl, Shr, Add, Sub, Mul, Div, Mod, Inc, Dec, Brak
    };

    所以,当我们调用 expression(level) 进行解析的时候,我们其实通过了参数 level 指定了当前的优先级。在前文的一元运算符处理中也用到了这一点。

    所以,此时的二元运算符的解析的框架为:

    while (token >= level) {
    // parse token for binary operator and postfix operator
    }

    解决了优先级的问题,让我们继续讲解如何把运算符编译成汇编代码吧。

    赋值操作

    赋值操作是优先级最低的运算符。考虑诸如 a = (expession) 的表达式,在解析 = 之前,我们已经为变量 a 生成了如下的汇编代码:

    IMM <addr>
    LC/LI

    当解析完=右边的表达式后,相应的值会存放在 ax 中,此时,为了实际将这个值保存起来,我们需要类似下面的汇编代码:

    IMM <addr>
    PUSH
    SC/SI

    明白了这点,也就能理解下面的源代码了:

    tmp = expr_type;
    if (token == Assign) {
    // var = expr;
    match(Assign);
    if (*text == LC || *text == LI) {
    *text = PUSH; // save the lvalue's pointer
    } else {
    printf("%d: bad lvalue in assignment\\n", line);
    exit(-1);
    }
    expression(Assign);

    expr_type = tmp;
    *++text = (expr_type == CHAR) ? SC : SI;
    }

    三目运算符

    这是 C 语言中唯一的一个三元运算符: ? :,它相当于一个小型的 If 语句,所以生成的代码也类似于 If 语句,这里就不多作解释。

    else if (token == Cond) {
    // expr ? a : b;
    match(Cond);
    *++text = JZ;
    addr = ++text;
    expression(Assign);
    if (token == ':') {
    match(':');
    } else {
    printf("%d: missing colon in conditional\\n", line);
    exit(-1);
    }
    *addr = (int)(text + 3);
    *++text = JMP;
    addr = ++text;
    expression(Cond);
    *addr = (int)(text + 1);
    }

    逻辑运算符

    这包括 ||&&。它们对应的汇编代码如下:

    <expr1> || <expr2>     <expr1> && <expr2>

    ...<expr1>... ...<expr1>...
    JNZ b JZ b
    ...<expr2>... ...<expr2>...
    b: b:

    所以源码如下:

    else if (token == Lor) {
    // logic or
    match(Lor);
    *++text = JNZ;
    addr = ++text;
    expression(Lan);
    *addr = (int)(text + 1);
    expr_type = INT;
    }
    else if (token == Lan) {
    // logic and
    match(Lan);
    *++text = JZ;
    addr = ++text;
    expression(Or);
    *addr = (int)(text + 1);
    expr_type = INT;
    }

    数学运算符

    它们包括 |, ^, &, ==, != <=, >=, <, >, <<, >>, +, -, *, /, %。它们的实现都很类似,我们以异或 ^ 为例:

    <expr1> ^ <expr2>

    ...<expr1>... <- now the result is on ax
    PUSH
    ...<expr2>... <- now the value of <expr2> is on ax
    XOR

    所以它对应的代码为:

    else if (token == Xor) {
    // bitwise xor
    match(Xor);
    *++text = PUSH;
    expression(And);
    *++text = XOR;
    expr_type = INT;
    }

    其它的我们便不再详述。但这当中还有一个问题,就是指针的加减。在 C 语言中,指针加上数值等于将指针移位,且根据不同的类型移动的位移不同。如 a + 1,如果 achar * 型,则移动一字节,而如果 aint * 型,则移动 4 个字节(32位系统)。

    另外,在作指针减法时,如果是两个指针相减(相同类型),则结果是两个指针间隔的元素个数。因此要有特殊的处理。

    下面以加法为例,对应的汇编代码为:

    <expr1> + <expr2>

    normal pointer

    <expr1> <expr1>
    PUSH PUSH
    <expr2> <expr2> |
    ADD PUSH | <expr2> * <unit>
    IMM <unit> |
    MUL |
    ADD

    即当 <expr1> 是指针时,要根据它的类型放大 <expr2> 的值,因此对应的源码如下:

    else if (token == Add) {
    // add
    match(Add);
    *++text = PUSH;
    expression(Mul);

    expr_type = tmp;
    if (expr_type > PTR) {
    // pointer type, and not `char *`
    *++text = PUSH;
    *++text = IMM;
    *++text = sizeof(int);
    *++text = MUL;
    }
    *++text = ADD;
    }

    相应的减法的代码就不贴了,可以自己实现看看,也可以看文末给出的链接。

    自增自减

    这次是后缀形式的,即 p++p--。与前缀形式不同的是,在执行自增自减后, ax上需要保留原来的值。所以我们首先执行类似前缀自增自减的操作,再将 ax 中的值执行减/增的操作。

    // 前缀形式 生成汇编代码
    *++text = PUSH;
    *++text = IMM;
    *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
    *++text = (tmp == Inc) ? ADD : SUB;
    *++text = (expr_type == CHAR) ? SC : SI;

    // 后缀形式 生成汇编代码
    *++text = PUSH;
    *++text = IMM;
    *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
    *++text = (token == Inc) ? ADD : SUB;
    *++text = (expr_type == CHAR) ? SC : SI;
    *++text = PUSH; //
    *++text = IMM; // 执行相反的增/减操作
    *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char); //
    *++text = (token == Inc) ? SUB : ADD; //

    数组取值操作

    在学习 C 语言的时候你可能已经知道了,诸如 a[10] 的操作等价于 *(a + 10)。因此我们要做的就是生成类似的汇编代码:

    else if (token == Brak) {
    // array access var[xx]
    match(Brak);
    *++text = PUSH;
    expression(Assign);
    match(']');

    if (tmp > PTR) {
    // pointer, `not char *`
    *++text = PUSH;
    *++text = IMM;
    *++text = sizeof(int);
    *++text = MUL;
    }
    else if (tmp < PTR) {
    printf("%d: pointer type expected\\n", line);
    exit(-1);
    }
    expr_type = tmp - PTR;
    *++text = ADD;
    *++text = (expr_type == CHAR) ? LC : LI;
    }

    代码

    除了上述对表达式的解析外,我们还需要初始化虚拟机的栈,我们可以正确调用 main 函数,且当 main 函数结束时退出进程。

    int *tmp;
    // setup stack
    sp = (int *)((int)stack + poolsize);
    *--sp = EXIT; // call exit if main returns
    *--sp = PUSH; tmp = sp;
    *--sp = argc;
    *--sp = (int)argv;
    *--sp = (int)tmp;

    当然,最后要注意的一点是:所有的变量定义必须放在语句之前。

    ',104),B={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-6",target:"_blank",rel:"noopener noreferrer"},I=p('
    git clone -b step-6 https://github.com/lotabout/write-a-C-interpreter

    通过 gcc -o xc-tutor xc-tutor.c 进行编译。并执行 ./xc-tutor hello.c 查看结果。

    正如我们保证的那样,我们的代码是自举的,能自己编译自己,所以你可以执行 ./xc-tutor xc-tutor.c hello.c。可以看到和之前有同样的输出。

    小结

    本章我们进行了最后的解析,解析表达式。本章有两个难点:

    1. 如何通过递归调用 expression 来实现运算符的优先级。
    2. 如何为每个运算符生成对应的汇编代码。

    尽管代码看起来比较简单(虽然多),但其中用到的原理还是需要仔细推敲的。

    最后,恭喜你!通过一步步的学习,自己实现了一个C语言的编译器(好吧,是解释器)。

    ',8);function M(S,v){const n=t("ExternalLinkIcon");return c(),r("div",null,[o,s("p",null,[a("本文转自 "),s("a",b,[a("https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501"),e(n)]),a(",如有侵权,请联系删除。")]),d,s("ol",null,[s("li",null,[s("a",h,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",x,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",C,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),_,s("p",null,[a("C 语言定义了各种表达式的优先级,可以参考 "),s("a",A,[a("C 语言运算符优先级"),e(n)]),a("。")]),w,s("p",null,[a("本章的代码可以在 "),s("a",B,[a("Github"),e(n)]),a(" 上下载,也可以直接 clone")]),I])}const L=l(i,[["render",M],["__file","8.html.vue"]]),F=JSON.parse('{"path":"/tech/designASimpileCCompiler/8.html","title":"手把手教你构建 C 语言编译器(8)——表达式","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(8)——表达式","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(8)- 表达式 Table of Contents 1. 运算符的优先级 2. 一元运算符 2.1. 常...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/8.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(8)——表达式"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(8)- 表达式 Table of Contents 1. 运算符的优先级 2. 一元运算符 2.1. 常..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(8)——表达式\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"运算符的优先级","slug":"运算符的优先级","link":"#运算符的优先级","children":[]},{"level":2,"title":"一元运算符","slug":"一元运算符","link":"#一元运算符","children":[{"level":3,"title":"常量","slug":"常量","link":"#常量","children":[]},{"level":3,"title":"sizeof","slug":"sizeof","link":"#sizeof","children":[]},{"level":3,"title":"变量与函数调用","slug":"变量与函数调用","link":"#变量与函数调用","children":[]},{"level":3,"title":"强制转换","slug":"强制转换","link":"#强制转换","children":[]},{"level":3,"title":"指针取值","slug":"指针取值","link":"#指针取值","children":[]},{"level":3,"title":"取址操作","slug":"取址操作","link":"#取址操作","children":[]},{"level":3,"title":"逻辑取反","slug":"逻辑取反","link":"#逻辑取反","children":[]},{"level":3,"title":"按位取反","slug":"按位取反","link":"#按位取反","children":[]},{"level":3,"title":"正负号","slug":"正负号","link":"#正负号","children":[]},{"level":3,"title":"自增自减","slug":"自增自减","link":"#自增自减","children":[]}]},{"level":2,"title":"二元运算符","slug":"二元运算符","link":"#二元运算符","children":[{"level":3,"title":"赋值操作","slug":"赋值操作","link":"#赋值操作","children":[]},{"level":3,"title":"三目运算符","slug":"三目运算符","link":"#三目运算符","children":[]},{"level":3,"title":"逻辑运算符","slug":"逻辑运算符","link":"#逻辑运算符","children":[]},{"level":3,"title":"数学运算符","slug":"数学运算符","link":"#数学运算符","children":[]},{"level":3,"title":"自增自减","slug":"自增自减-1","link":"#自增自减-1","children":[]},{"level":3,"title":"数组取值操作","slug":"数组取值操作","link":"#数组取值操作","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":29.67,"words":8900},"filePathRelative":"tech/designASimpileCCompiler/8.md","excerpt":"\\n

    本文转自 https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(8)- 表达式

    \\n

    Table of Contents

    ","autoDesc":true}');export{L as comp,F as data}; +import{_ as l,r as t,o as c,c as r,b as s,e as a,d as e,f as p}from"./app-B4LGNJZ0.js";const i={},o=s("h1",{id:"转载声明",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#转载声明"},[s("span",null,"转载声明")])],-1),b={href:"https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501",target:"_blank",rel:"noopener noreferrer"},d=p('

    原文内容

    手把手教你构建 C 语言编译器(8)- 表达式

    Table of Contents

    1. 1. 运算符的优先级
    2. 2. 一元运算符
      1. 2.1. 常量
      2. 2.2. sizeof
      3. 2.3. 变量与函数调用
      4. 2.4. 强制转换
      5. 2.5. 指针取值
      6. 2.6. 取址操作
      7. 2.7. 逻辑取反
      8. 2.8. 按位取反
      9. 2.9. 正负号
      10. 2.10. 自增自减
    3. 3. 二元运算符
      1. 3.1. 赋值操作
      2. 3.2. 三目运算符
      3. 3.3. 逻辑运算符
      4. 3.4. 数学运算符
      5. 3.5. 自增自减
      6. 3.6. 数组取值操作
    4. 4. 代码
    5. 5. 小结

    这是整个编译器的最后一部分,解析表达式。什么是表达式?表达式是将各种语言要素的一个组合,用来求值。例如:函数调用、变量赋值、运算符运算等等。

    表达式的解析难点有二:一是运算符的优先级问题,二是如何将表达式编译成目标代码。我们就来逐一说明。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',7),h={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},x={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},k={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},E={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},_=p('

    运算符的优先级

    运算符的优先级决定了表达式的运算顺序,如在普通的四则运算中,乘法 * 优先级高于加法 +,这就意味着表达式 2 + 3 * 4 的实际运行顺序是 2 + (3 * 4) 而不是 (2 + 3) * 4

    ',2),A={href:"http://en.cppreference.com/w/c/language/operator_precedence",target:"_blank",rel:"noopener noreferrer"},w=p('

    传统的编程书籍会用“逆波兰式”实现四则运算来讲解优先级问题。实际上,优先级关心的就是哪个运算符先计算,哪个运算符后计算(毕竟叫做“优先级”嘛)。而这就意味着我们需要决定先为哪个运算符生成目标代码(汇编),因为汇编代码是顺序排列的,我们必须先计算优先级高的运算符。

    那么如何确定运算符的优先级呢?答曰:栈(递归调用的实质也是栈的处理)。

    举一个例子:2 + 3 - 4 * 5,它的运算顺序是这样的:

    1. 2 入栈
    2. 遇到运算符 +,入栈,此时我们期待的是+的另一个参数
    3. 遇到数字 3,原则上我们需要立即计算 2+3的值,但我们不确定数字 3 是否属于优先级更高的运算符,所以先将它入栈。
    4. 遇到运算符 -,它的优先级和 + 相同,此时判断参数 3 属于这前的 +。将运算符 + 出栈,并将之前的 23 出栈,计算 2+3 的结果,得到 5 入栈。同时将运算符 - 入栈。
    5. 遇到数字4,同样不能确定是否能立即计算,入栈
    6. 遇到运算符 * 优先级大于 -,入栈
    7. 遇到数字5,依旧不能确定是否立即计算,入栈
    8. 表达式结束,运算符出栈,为 *,将参数出栈,计算 4*5 得到结果 20 入栈。
    9. 运算符出栈,为 -,将参数出栈,计算 5-20,得到 -15 入栈。
    10. 此时运算符栈为空,因此得到结果 -15
    // after step 1, 2
    | |
    +------+
    | 3 | | |
    +------+ +------+
    | 2 | | + |
    +------+ +------+

    // after step 4
    | | | |
    +------+ +------+
    | 5 | | - |
    +------+ +------+

    // after step 7
    | |
    +------+
    | 5 |
    +------+ +------+
    | 4 | | * |
    +------+ +------+
    | 5 | | - |
    +------+ +------+

    综上,在计算一个运算符‘x’之前,必须先查看它的右方,找出并计算所有优先级大于‘x’的运算符,之后再计算运算符‘x’。

    最后注意的是优先通常只与多元运算符相关,单元运算符往往没有这个问题(因为只有一个参数)。也可以认为“优先级”的实质就是两个运算符在抢参数。

    一元运算符

    上节中说到了运算符的优先级,也提到了优先级一般只与多元运算符有关,这也意味着一元运算符的优先级总是高于多元运算符。因为我们需要先对它们进行解析。

    当然,这部分也将同时解析参数本身(如变量、数字、字符串等等)。

    关于表达式的解析,与语法分析相关的部分就是上文所说的优先级问题了,而剩下的较难较烦的部分是与目标代码的生成有关的。因此对于需要讲解的运算符,我们主要从它的目标代码入手。

    常量

    首先是数字,用 IMM 指令将它加载到 AX 中即可:

    if (token == Num) {
    match(Num);

    // emit code
    *++text = IMM;
    *++text = token_val;
    expr_type = INT;
    }

    接着是字符串常量。它比较特殊的一点是 C 语言的字符串常量支持如下风格:

    char *p;
    p = "first line"
    "second line";

    即跨行的字符串拼接,它相当于:

    char *p;
    p = "first linesecond line";

    所以解析的时候要注意这一点:

    else if (token == '"') {
    // emit code
    *++text = IMM;
    *++text = token_val;

    match('"');
    // store the rest strings
    while (token == '"') {
    match('"');
    }

    // append the end of string character '\\0', all the data are default
    // to 0, so just move data one position forward.
    data = (char *)(((int)data + sizeof(int)) & (-sizeof(int)));
    expr_type = PTR;
    }

    sizeof

    sizeof 是一个一元运算符,我们需要知道后面参数的类型,类型的解析在前面的文章中我们已经很熟悉了。

    else if (token == Sizeof) {
    // sizeof is actually an unary operator
    // now only `sizeof(int)`, `sizeof(char)` and `sizeof(*...)` are
    // supported.
    match(Sizeof);
    match('(');
    expr_type = INT;

    if (token == Int) {
    match(Int);
    } else if (token == Char) {
    match(Char);
    expr_type = CHAR;
    }

    while (token == Mul) {
    match(Mul);
    expr_type = expr_type + PTR;
    }

    match(')');

    // emit code
    *++text = IMM;
    *++text = (expr_type == CHAR) ? sizeof(char) : sizeof(int);

    expr_type = INT;
    }

    注意的是只支持 sizeof(int)sizeof(char)sizeof(pointer type...)。并且它的结果是 int 型。

    变量与函数调用

    由于取变量的值与函数的调用都是以 Id 标记开头的,因此将它们放在一起处理。

    else if (token == Id) {
    // there are several type when occurs to Id
    // but this is unit, so it can only be
    // 1. function call
    // 2. Enum variable
    // 3. global/local variable
    match(Id);

    id = current_id;

    if (token == '(') {
    // function call
    match('(');

    // ①
    // pass in arguments
    tmp = 0; // number of arguments
    while (token != ')') {
    expression(Assign);
    *++text = PUSH;
    tmp ++;

    if (token == ',') {
    match(',');
    }
    }
    match(')');

    // ②
    // emit code
    if (id[Class] == Sys) {
    // system functions
    *++text = id[Value];
    }
    else if (id[Class] == Fun) {
    // function call
    *++text = CALL;
    *++text = id[Value];
    }
    else {
    printf("%d: bad function call\\n", line);
    exit(-1);
    }

    // ③
    // clean the stack for arguments
    if (tmp > 0) {
    *++text = ADJ;
    *++text = tmp;
    }
    expr_type = id[Type];
    }
    else if (id[Class] == Num) {
    // ④
    // enum variable
    *++text = IMM;
    *++text = id[Value];
    expr_type = INT;
    }
    else {
    // ⑤
    // variable
    if (id[Class] == Loc) {
    *++text = LEA;
    *++text = index_of_bp - id[Value];
    }
    else if (id[Class] == Glo) {
    *++text = IMM;
    *++text = id[Value];
    }
    else {
    printf("%d: undefined variable\\n", line);
    exit(-1);
    }

    //⑥
    // emit code, default behaviour is to load the value of the
    // address which is stored in `ax`
    expr_type = id[Type];
    *++text = (expr_type == Char) ? LC : LI;
    }
    }

    ①中注意我们是顺序将参数入栈,这和第三章:虚拟机中讲解的指令是对应的。与之不同,标准 C 是逆序将参数入栈的。

    ②中判断函数的类型,同样在第三章:“虚拟机”中我们介绍过内置函数的支持,如 printf, read, malloc 等等。内置函数有对应的汇编指令,而普通的函数则编译成 CALL <addr> 的形式。

    ③用于清除入栈的参数。因为我们不在乎出栈的值,所以直接修改栈指针的大小即可。

    ④:当该标识符是全局定义的枚举类型时,直接将对应的值用 IMM 指令存入 AX 即可。

    ⑤则是用于加载变量的值,如果是局部变量则采用与 bp 指针相对位置的形式(参见第 7章函数定义)。而如果是全局变量则用 IMM 加载变量的地址。

    ⑥:无论是全局还是局部变量,最终都根据它们的类型用 LCLI 指令加载对应的值。

    关于变量,你可能有疑问,如果遇到标识符就用 LC/LI 载入相应的值,那诸如 a[10] 之类的表达式要如何实现呢?后面我们会看到,根据标识符后的运算符,我们可能会修改或删除现有的 LC/LI 指令。

    强制转换

    虽然我们前面没有提到,但我们一直用 expr_type 来保存一个表达式的类型,强制转换的作用是获取转换的类型,并直接修改 expr_type 的值。

    else if (token == '(') {
    // cast or parenthesis
    match('(');
    if (token == Int || token == Char) {
    tmp = (token == Char) ? CHAR : INT; // cast type
    match(token);
    while (token == Mul) {
    match(Mul);
    tmp = tmp + PTR;
    }

    match(')');

    expression(Inc); // cast has precedence as Inc(++)

    expr_type = tmp;
    } else {
    // normal parenthesis
    expression(Assign);
    match(')');
    }
    }

    指针取值

    诸如 *a 的指针取值,关键是判断 a 的类型,而就像上节中提到的,当一个表达式解析结束时,它的类型保存在变量 expr_type 中。

    else if (token == Mul) {
    // dereference *<addr>
    match(Mul);
    expression(Inc); // dereference has the same precedence as Inc(++)

    if (expr_type >= PTR) {
    expr_type = expr_type - PTR;
    } else {
    printf("%d: bad dereference\\n", line);
    exit(-1);
    }

    *++text = (expr_type == CHAR) ? LC : LI;
    }

    取址操作

    这里我们就能看到“变量与函数调用”一节中所说的修改或删除 LC/LI 指令了。前文中我们说到,对于变量,我们会先加载它的地址,并根据它们类型使用 LC/LI 指令加载实际内容,例如对变量 a

    IMM <addr>
    LI

    那么对变量 a 取址,其实只要不执行 LC/LI 即可。因此我们删除相应的指令。

    else if (token == And) {
    // get the address of
    match(And);
    expression(Inc); // get the address of
    if (*text == LC || *text == LI) {
    text --;
    } else {
    printf("%d: bad address of\\n", line);
    exit(-1);
    }

    expr_type = expr_type + PTR;
    }

    逻辑取反

    我们没有直接的逻辑取反指令,因此我们判断它是否与数字 0 相等。而数字 0 代表了逻辑 “False”。

    else if (token == '!') {
    // not
    match('!');
    expression(Inc);

    // emit code, use <expr> == 0
    *++text = PUSH;
    *++text = IMM;
    *++text = 0;
    *++text = EQ;

    expr_type = INT;
    }

    按位取反

    同样我们没有相应的指令,所以我们用异或来实现,即 ~a = a ^ 0xFFFF

    else if (token == '~') {
    // bitwise not
    match('~');
    expression(Inc);

    // emit code, use <expr> XOR -1
    *++text = PUSH;
    *++text = IMM;
    *++text = -1;
    *++text = XOR;

    expr_type = INT;
    }

    正负号

    注意这里并不是四则运算中的加减法,而是单个数字的取正取负操作。同样,我们没有取负的操作,用 0 - x 来实现 -x

    else if (token == Add) {
    // +var, do nothing
    match(Add);
    expression(Inc);

    expr_type = INT;
    }
    else if (token == Sub) {
    // -var
    match(Sub);

    if (token == Num) {
    *++text = IMM;
    *++text = -token_val;
    match(Num);
    } else {

    *++text = IMM;
    *++text = -1;
    *++text = PUSH;
    expression(Inc);
    *++text = MUL;
    }

    expr_type = INT;
    }

    自增自减

    注意的是自增自减操作的优先级是和它的位置有关的。如 ++p 的优先级高于 p++,这里我们解析的就是类似 ++p 的操作。

    else if (token == Inc || token == Dec) {
    tmp = token;
    match(token);
    expression(Inc);
    // ①
    if (*text == LC) {
    *text = PUSH; // to duplicate the address
    *++text = LC;
    } else if (*text == LI) {
    *text = PUSH;
    *++text = LI;
    } else {
    printf("%d: bad lvalue of pre-increment\\n", line);
    exit(-1);
    }
    *++text = PUSH;
    *++text = IMM;
    // ②
    *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
    *++text = (tmp == Inc) ? ADD : SUB;
    *++text = (expr_type == CHAR) ? SC : SI;
    }

    对应的汇编代码也比较直观,只是在实现 ++p时,我们要使用变量 p 的地址两次,所以我们需要先 PUSH (①)。

    ②则是因为自增自减操作还需要处理是指针的情形。

    二元运算符

    这里,我们需要处理多运算符的优先级问题,就如前文的“优先级”一节提到的,我们需要不断地向右扫描,直到遇到优先级 小于 当前优先级的运算符。

    回想起我们之前定义过的各个标记,它们是以优先级从低到高排列的,即 Assign 的优先级最低,而 Brak[) 的优先级最高。

    enum {
    Num = 128, Fun, Sys, Glo, Loc, Id,
    Char, Else, Enum, If, Int, Return, Sizeof, While,
    Assign, Cond, Lor, Lan, Or, Xor, And, Eq, Ne, Lt, Gt, Le, Ge, Shl, Shr, Add, Sub, Mul, Div, Mod, Inc, Dec, Brak
    };

    所以,当我们调用 expression(level) 进行解析的时候,我们其实通过了参数 level 指定了当前的优先级。在前文的一元运算符处理中也用到了这一点。

    所以,此时的二元运算符的解析的框架为:

    while (token >= level) {
    // parse token for binary operator and postfix operator
    }

    解决了优先级的问题,让我们继续讲解如何把运算符编译成汇编代码吧。

    赋值操作

    赋值操作是优先级最低的运算符。考虑诸如 a = (expession) 的表达式,在解析 = 之前,我们已经为变量 a 生成了如下的汇编代码:

    IMM <addr>
    LC/LI

    当解析完=右边的表达式后,相应的值会存放在 ax 中,此时,为了实际将这个值保存起来,我们需要类似下面的汇编代码:

    IMM <addr>
    PUSH
    SC/SI

    明白了这点,也就能理解下面的源代码了:

    tmp = expr_type;
    if (token == Assign) {
    // var = expr;
    match(Assign);
    if (*text == LC || *text == LI) {
    *text = PUSH; // save the lvalue's pointer
    } else {
    printf("%d: bad lvalue in assignment\\n", line);
    exit(-1);
    }
    expression(Assign);

    expr_type = tmp;
    *++text = (expr_type == CHAR) ? SC : SI;
    }

    三目运算符

    这是 C 语言中唯一的一个三元运算符: ? :,它相当于一个小型的 If 语句,所以生成的代码也类似于 If 语句,这里就不多作解释。

    else if (token == Cond) {
    // expr ? a : b;
    match(Cond);
    *++text = JZ;
    addr = ++text;
    expression(Assign);
    if (token == ':') {
    match(':');
    } else {
    printf("%d: missing colon in conditional\\n", line);
    exit(-1);
    }
    *addr = (int)(text + 3);
    *++text = JMP;
    addr = ++text;
    expression(Cond);
    *addr = (int)(text + 1);
    }

    逻辑运算符

    这包括 ||&&。它们对应的汇编代码如下:

    <expr1> || <expr2>     <expr1> && <expr2>

    ...<expr1>... ...<expr1>...
    JNZ b JZ b
    ...<expr2>... ...<expr2>...
    b: b:

    所以源码如下:

    else if (token == Lor) {
    // logic or
    match(Lor);
    *++text = JNZ;
    addr = ++text;
    expression(Lan);
    *addr = (int)(text + 1);
    expr_type = INT;
    }
    else if (token == Lan) {
    // logic and
    match(Lan);
    *++text = JZ;
    addr = ++text;
    expression(Or);
    *addr = (int)(text + 1);
    expr_type = INT;
    }

    数学运算符

    它们包括 |, ^, &, ==, != <=, >=, <, >, <<, >>, +, -, *, /, %。它们的实现都很类似,我们以异或 ^ 为例:

    <expr1> ^ <expr2>

    ...<expr1>... <- now the result is on ax
    PUSH
    ...<expr2>... <- now the value of <expr2> is on ax
    XOR

    所以它对应的代码为:

    else if (token == Xor) {
    // bitwise xor
    match(Xor);
    *++text = PUSH;
    expression(And);
    *++text = XOR;
    expr_type = INT;
    }

    其它的我们便不再详述。但这当中还有一个问题,就是指针的加减。在 C 语言中,指针加上数值等于将指针移位,且根据不同的类型移动的位移不同。如 a + 1,如果 achar * 型,则移动一字节,而如果 aint * 型,则移动 4 个字节(32位系统)。

    另外,在作指针减法时,如果是两个指针相减(相同类型),则结果是两个指针间隔的元素个数。因此要有特殊的处理。

    下面以加法为例,对应的汇编代码为:

    <expr1> + <expr2>

    normal pointer

    <expr1> <expr1>
    PUSH PUSH
    <expr2> <expr2> |
    ADD PUSH | <expr2> * <unit>
    IMM <unit> |
    MUL |
    ADD

    即当 <expr1> 是指针时,要根据它的类型放大 <expr2> 的值,因此对应的源码如下:

    else if (token == Add) {
    // add
    match(Add);
    *++text = PUSH;
    expression(Mul);

    expr_type = tmp;
    if (expr_type > PTR) {
    // pointer type, and not `char *`
    *++text = PUSH;
    *++text = IMM;
    *++text = sizeof(int);
    *++text = MUL;
    }
    *++text = ADD;
    }

    相应的减法的代码就不贴了,可以自己实现看看,也可以看文末给出的链接。

    自增自减

    这次是后缀形式的,即 p++p--。与前缀形式不同的是,在执行自增自减后, ax上需要保留原来的值。所以我们首先执行类似前缀自增自减的操作,再将 ax 中的值执行减/增的操作。

    // 前缀形式 生成汇编代码
    *++text = PUSH;
    *++text = IMM;
    *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
    *++text = (tmp == Inc) ? ADD : SUB;
    *++text = (expr_type == CHAR) ? SC : SI;

    // 后缀形式 生成汇编代码
    *++text = PUSH;
    *++text = IMM;
    *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
    *++text = (token == Inc) ? ADD : SUB;
    *++text = (expr_type == CHAR) ? SC : SI;
    *++text = PUSH; //
    *++text = IMM; // 执行相反的增/减操作
    *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char); //
    *++text = (token == Inc) ? SUB : ADD; //

    数组取值操作

    在学习 C 语言的时候你可能已经知道了,诸如 a[10] 的操作等价于 *(a + 10)。因此我们要做的就是生成类似的汇编代码:

    else if (token == Brak) {
    // array access var[xx]
    match(Brak);
    *++text = PUSH;
    expression(Assign);
    match(']');

    if (tmp > PTR) {
    // pointer, `not char *`
    *++text = PUSH;
    *++text = IMM;
    *++text = sizeof(int);
    *++text = MUL;
    }
    else if (tmp < PTR) {
    printf("%d: pointer type expected\\n", line);
    exit(-1);
    }
    expr_type = tmp - PTR;
    *++text = ADD;
    *++text = (expr_type == CHAR) ? LC : LI;
    }

    代码

    除了上述对表达式的解析外,我们还需要初始化虚拟机的栈,我们可以正确调用 main 函数,且当 main 函数结束时退出进程。

    int *tmp;
    // setup stack
    sp = (int *)((int)stack + poolsize);
    *--sp = EXIT; // call exit if main returns
    *--sp = PUSH; tmp = sp;
    *--sp = argc;
    *--sp = (int)argv;
    *--sp = (int)tmp;

    当然,最后要注意的一点是:所有的变量定义必须放在语句之前。

    ',104),B={href:"https://github.com/lotabout/write-a-C-interpreter/tree/step-6",target:"_blank",rel:"noopener noreferrer"},I=p('
    git clone -b step-6 https://github.com/lotabout/write-a-C-interpreter

    通过 gcc -o xc-tutor xc-tutor.c 进行编译。并执行 ./xc-tutor hello.c 查看结果。

    正如我们保证的那样,我们的代码是自举的,能自己编译自己,所以你可以执行 ./xc-tutor xc-tutor.c hello.c。可以看到和之前有同样的输出。

    小结

    本章我们进行了最后的解析,解析表达式。本章有两个难点:

    1. 如何通过递归调用 expression 来实现运算符的优先级。
    2. 如何为每个运算符生成对应的汇编代码。

    尽管代码看起来比较简单(虽然多),但其中用到的原理还是需要仔细推敲的。

    最后,恭喜你!通过一步步的学习,自己实现了一个C语言的编译器(好吧,是解释器)。

    ',8);function M(S,v){const n=t("ExternalLinkIcon");return c(),r("div",null,[o,s("p",null,[a("本文转自 "),s("a",b,[a("https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501"),e(n)]),a(",如有侵权,请联系删除。")]),d,s("ol",null,[s("li",null,[s("a",h,[a("手把手教你构建 C 语言编译器(0)——前言"),e(n)])]),s("li",null,[s("a",m,[a("手把手教你构建 C 语言编译器(1)——设计"),e(n)])]),s("li",null,[s("a",y,[a("手把手教你构建 C 语言编译器(2)——虚拟机"),e(n)])]),s("li",null,[s("a",x,[a("手把手教你构建 C 语言编译器(3)——词法分析器"),e(n)])]),s("li",null,[s("a",u,[a("手把手教你构建 C 语言编译器(4)——递归下降"),e(n)])]),s("li",null,[s("a",f,[a("手把手教你构建 C 语言编译器(5)——变量定义"),e(n)])]),s("li",null,[s("a",k,[a("手把手教你构建 C 语言编译器(6)——函数定义"),e(n)])]),s("li",null,[s("a",E,[a("手把手教你构建 C 语言编译器(7)——语句"),e(n)])]),s("li",null,[s("a",g,[a("手把手教你构建 C 语言编译器(8)——表达式"),e(n)])]),s("li",null,[s("a",C,[a("手把手教你构建 C 语言编译器(9)——总结"),e(n)])])]),_,s("p",null,[a("C 语言定义了各种表达式的优先级,可以参考 "),s("a",A,[a("C 语言运算符优先级"),e(n)]),a("。")]),w,s("p",null,[a("本章的代码可以在 "),s("a",B,[a("Github"),e(n)]),a(" 上下载,也可以直接 clone")]),I])}const L=l(i,[["render",M],["__file","8.html.vue"]]),F=JSON.parse('{"path":"/tech/designASimpileCCompiler/8.html","title":"手把手教你构建 C 语言编译器(8)——表达式","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(8)——表达式","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(8)- 表达式 Table of Contents 1. 运算符的优先级 2. 一元运算符 2.1. 常...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/8.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(8)——表达式"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(8)- 表达式 Table of Contents 1. 运算符的优先级 2. 一元运算符 2.1. 常..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(8)——表达式\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"运算符的优先级","slug":"运算符的优先级","link":"#运算符的优先级","children":[]},{"level":2,"title":"一元运算符","slug":"一元运算符","link":"#一元运算符","children":[{"level":3,"title":"常量","slug":"常量","link":"#常量","children":[]},{"level":3,"title":"sizeof","slug":"sizeof","link":"#sizeof","children":[]},{"level":3,"title":"变量与函数调用","slug":"变量与函数调用","link":"#变量与函数调用","children":[]},{"level":3,"title":"强制转换","slug":"强制转换","link":"#强制转换","children":[]},{"level":3,"title":"指针取值","slug":"指针取值","link":"#指针取值","children":[]},{"level":3,"title":"取址操作","slug":"取址操作","link":"#取址操作","children":[]},{"level":3,"title":"逻辑取反","slug":"逻辑取反","link":"#逻辑取反","children":[]},{"level":3,"title":"按位取反","slug":"按位取反","link":"#按位取反","children":[]},{"level":3,"title":"正负号","slug":"正负号","link":"#正负号","children":[]},{"level":3,"title":"自增自减","slug":"自增自减","link":"#自增自减","children":[]}]},{"level":2,"title":"二元运算符","slug":"二元运算符","link":"#二元运算符","children":[{"level":3,"title":"赋值操作","slug":"赋值操作","link":"#赋值操作","children":[]},{"level":3,"title":"三目运算符","slug":"三目运算符","link":"#三目运算符","children":[]},{"level":3,"title":"逻辑运算符","slug":"逻辑运算符","link":"#逻辑运算符","children":[]},{"level":3,"title":"数学运算符","slug":"数学运算符","link":"#数学运算符","children":[]},{"level":3,"title":"自增自减","slug":"自增自减-1","link":"#自增自减-1","children":[]},{"level":3,"title":"数组取值操作","slug":"数组取值操作","link":"#数组取值操作","children":[]}]},{"level":2,"title":"代码","slug":"代码","link":"#代码","children":[]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"readingTime":{"minutes":29.67,"words":8900},"filePathRelative":"tech/designASimpileCCompiler/8.md","excerpt":"\\n

    本文转自 https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(8)- 表达式

    \\n

    Table of Contents

    ","autoDesc":true}');export{L as comp,F as data}; diff --git a/assets/9.html-CGdd1xtP.js b/assets/9.html-DOIgY3Qt.js similarity index 99% rename from assets/9.html-CGdd1xtP.js rename to assets/9.html-DOIgY3Qt.js index ae7d14e..df18252 100644 --- a/assets/9.html-CGdd1xtP.js +++ b/assets/9.html-DOIgY3Qt.js @@ -1 +1 @@ -import{_ as o,r as l,o as i,c as p,b as e,e as t,d as a,f as n}from"./app-B69Gl_S-.js";const h={},s=e("h1",{id:"转载声明",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#转载声明"},[e("span",null,"转载声明")])],-1),c={href:"https://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},E=n('

    原文内容

    手把手教你构建 C 语言编译器(9)- 总结

    Table of Contents

    1. 1. 虚拟机与目标代码
    2. 2. 词法分析
    3. 3. 语法分析
    4. 4. 关于编代码
    5. 5. 结语

    恭喜你完成了自己的 C 语言编译器,本章中我们发一发牢骚,说一说编写编译器值得注意的一些问题;编写编译器时遇到的一些难题。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),b={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},d={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},B={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},k=n('

    虚拟机与目标代码

    整个系列的一开始,我们就着手虚拟机的实现。不知道你是否有同感,这部分对于整个编译器的编写其实是十分重要的。我认为至少占了重要程度的50%。

    这里要说明这样一个观点,学习编译原理时常常着眼于词法分析和语法分析,而忽略了同样重要的代码生成。对于学习或考试而言或许可以,但实际编译项目时,最为重要的是能“跑起来”,所以我们需要给予代码生成高度的重视。

    同时我们也看到,在后期解析语句和表达式时,难点已经不再是语法分析了,而是如何为运算符生成相应的汇编代码。

    词法分析

    我们用了很暴力的手段编写了我们的词法分析器,我认为这并无不可。

    但你依旧可以学习相关的知识,了解自动生成词法分析器的原理,它涉及到了“正则表达式”,“状态机”等等知识。相信这部分的知识能够很大程度上提高你的编程水平。

    同时,如果今后你仍然想编写编译器,不妨试试这些自动生成工具。

    语法分析

    长期以来,语法分析对我而言一直是迷一样的存在,直到真正用递归下降的方式实现了一个。

    我们用了专门的一章讲解了“递归下降”与 BNF 文法的关系。希望能减少你对理论的厌恶。至少,实现起来并不是太难。

    如果有兴趣,可以学习学习这些文法,因为已经有许多自动生成的工具支持它们。这样你就不需要重复造轮子。可以看看 yacc 等工具,更先进的版本是 bsion。同时其它语言也有许多类似的支持。

    题外话,最近知道了一个叫“PEG 文法”的表示方法,无论是读起来,还是实现起来,都比 BNF 要容易,你也可以学习看看。

    关于编代码

    这也是我自己的感慨吧。无论多好的教程,想要完全理解它,最好的方式恐怕还是要自己实现它。

    只是在编写代码的过程中,我们会遇到许多的挫折,例如需要考虑许多细节,或是调试起来十分困难。但也只有真正静下心来去克服它,我们才能有所成长吧。

    例如在编写表达式的解析时,大量重复的代码特别让人崩溃。还有就是调试编译器,简直痛苦地无话可说。

    P.S. 如果你按这个系列自己编写代码,记得事先写一些用于输出汇编代码的函数,很有帮助的。

    还有就是写这个系列的文章,开始的冲动过了之后,每写一篇都特别心烦,希望文章本身没有受我的这种情绪影响吧。

    结语

    编程有趣又无趣,只有身在其中的我们才能体会吧。

    ',21);function w(x,y){const r=l("ExternalLinkIcon");return i(),p("div",null,[s,e("p",null,[t("本文转自 "),e("a",c,[t("https://lotabout.me/2016/write-a-C-interpreter-9/"),a(r)]),t(",如有侵权,请联系删除。")]),E,e("ol",null,[e("li",null,[e("a",b,[t("手把手教你构建 C 语言编译器(0)——前言"),a(r)])]),e("li",null,[e("a",d,[t("手把手教你构建 C 语言编译器(1)——设计"),a(r)])]),e("li",null,[e("a",u,[t("手把手教你构建 C 语言编译器(2)——虚拟机"),a(r)])]),e("li",null,[e("a",f,[t("手把手教你构建 C 语言编译器(3)——词法分析器"),a(r)])]),e("li",null,[e("a",C,[t("手把手教你构建 C 语言编译器(4)——递归下降"),a(r)])]),e("li",null,[e("a",_,[t("手把手教你构建 C 语言编译器(5)——变量定义"),a(r)])]),e("li",null,[e("a",m,[t("手把手教你构建 C 语言编译器(6)——函数定义"),a(r)])]),e("li",null,[e("a",B,[t("手把手教你构建 C 语言编译器(7)——语句"),a(r)])]),e("li",null,[e("a",A,[t("手把手教你构建 C 语言编译器(8)——表达式"),a(r)])]),e("li",null,[e("a",g,[t("手把手教你构建 C 语言编译器(9)——总结"),a(r)])])]),k])}const D=o(h,[["render",w],["__file","9.html.vue"]]),v=JSON.parse('{"path":"/tech/designASimpileCCompiler/9.html","title":"手把手教你构建 C 语言编译器(9)——总结","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(9)——总结","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-9/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(9)- 总结 Table of Contents 1. 虚拟机与目标代码 2. 词法分析 3. 语法分析 4. 关于编代码 5. 结语 恭喜你完成了自己的 C 语言编译...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/9.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(9)——总结"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-9/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(9)- 总结 Table of Contents 1. 虚拟机与目标代码 2. 词法分析 3. 语法分析 4. 关于编代码 5. 结语 恭喜你完成了自己的 C 语言编译..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(9)——总结\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"虚拟机与目标代码","slug":"虚拟机与目标代码","link":"#虚拟机与目标代码","children":[]},{"level":2,"title":"词法分析","slug":"词法分析","link":"#词法分析","children":[]},{"level":2,"title":"语法分析","slug":"语法分析","link":"#语法分析","children":[]},{"level":2,"title":"关于编代码","slug":"关于编代码","link":"#关于编代码","children":[]},{"level":2,"title":"结语","slug":"结语","link":"#结语","children":[]}],"readingTime":{"minutes":4.66,"words":1397},"filePathRelative":"tech/designASimpileCCompiler/9.md","excerpt":"\\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-9/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(9)- 总结

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 虚拟机与目标代码
    2. \\n
    3. 2. 词法分析
    4. \\n
    5. 3. 语法分析
    6. \\n
    7. 4. 关于编代码
    8. \\n
    9. 5. 结语
    10. \\n
    ","autoDesc":true}');export{D as comp,v as data}; +import{_ as o,r as l,o as i,c as p,b as e,e as t,d as a,f as n}from"./app-B4LGNJZ0.js";const h={},s=e("h1",{id:"转载声明",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#转载声明"},[e("span",null,"转载声明")])],-1),c={href:"https://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},E=n('

    原文内容

    手把手教你构建 C 语言编译器(9)- 总结

    Table of Contents

    1. 1. 虚拟机与目标代码
    2. 2. 词法分析
    3. 3. 语法分析
    4. 4. 关于编代码
    5. 5. 结语

    恭喜你完成了自己的 C 语言编译器,本章中我们发一发牢骚,说一说编写编译器值得注意的一些问题;编写编译器时遇到的一些难题。

    手把手教你构建 C 语言编译器系列共有10个部分:

    ',6),b={href:"http://lotabout.me/2015/write-a-C-interpreter-0/",target:"_blank",rel:"noopener noreferrer"},d={href:"http://lotabout.me/2015/write-a-C-interpreter-1/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://lotabout.me/2015/write-a-C-interpreter-2/",target:"_blank",rel:"noopener noreferrer"},f={href:"http://lotabout.me/2015/write-a-C-interpreter-3/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://lotabout.me/2016/write-a-C-interpreter-4/",target:"_blank",rel:"noopener noreferrer"},_={href:"http://lotabout.me/2016/write-a-C-interpreter-5/",target:"_blank",rel:"noopener noreferrer"},m={href:"http://lotabout.me/2016/write-a-C-interpreter-6/",target:"_blank",rel:"noopener noreferrer"},B={href:"http://lotabout.me/2016/write-a-C-interpreter-7/",target:"_blank",rel:"noopener noreferrer"},A={href:"http://lotabout.me/2016/write-a-C-interpreter-8/",target:"_blank",rel:"noopener noreferrer"},g={href:"http://lotabout.me/2016/write-a-C-interpreter-9/",target:"_blank",rel:"noopener noreferrer"},k=n('

    虚拟机与目标代码

    整个系列的一开始,我们就着手虚拟机的实现。不知道你是否有同感,这部分对于整个编译器的编写其实是十分重要的。我认为至少占了重要程度的50%。

    这里要说明这样一个观点,学习编译原理时常常着眼于词法分析和语法分析,而忽略了同样重要的代码生成。对于学习或考试而言或许可以,但实际编译项目时,最为重要的是能“跑起来”,所以我们需要给予代码生成高度的重视。

    同时我们也看到,在后期解析语句和表达式时,难点已经不再是语法分析了,而是如何为运算符生成相应的汇编代码。

    词法分析

    我们用了很暴力的手段编写了我们的词法分析器,我认为这并无不可。

    但你依旧可以学习相关的知识,了解自动生成词法分析器的原理,它涉及到了“正则表达式”,“状态机”等等知识。相信这部分的知识能够很大程度上提高你的编程水平。

    同时,如果今后你仍然想编写编译器,不妨试试这些自动生成工具。

    语法分析

    长期以来,语法分析对我而言一直是迷一样的存在,直到真正用递归下降的方式实现了一个。

    我们用了专门的一章讲解了“递归下降”与 BNF 文法的关系。希望能减少你对理论的厌恶。至少,实现起来并不是太难。

    如果有兴趣,可以学习学习这些文法,因为已经有许多自动生成的工具支持它们。这样你就不需要重复造轮子。可以看看 yacc 等工具,更先进的版本是 bsion。同时其它语言也有许多类似的支持。

    题外话,最近知道了一个叫“PEG 文法”的表示方法,无论是读起来,还是实现起来,都比 BNF 要容易,你也可以学习看看。

    关于编代码

    这也是我自己的感慨吧。无论多好的教程,想要完全理解它,最好的方式恐怕还是要自己实现它。

    只是在编写代码的过程中,我们会遇到许多的挫折,例如需要考虑许多细节,或是调试起来十分困难。但也只有真正静下心来去克服它,我们才能有所成长吧。

    例如在编写表达式的解析时,大量重复的代码特别让人崩溃。还有就是调试编译器,简直痛苦地无话可说。

    P.S. 如果你按这个系列自己编写代码,记得事先写一些用于输出汇编代码的函数,很有帮助的。

    还有就是写这个系列的文章,开始的冲动过了之后,每写一篇都特别心烦,希望文章本身没有受我的这种情绪影响吧。

    结语

    编程有趣又无趣,只有身在其中的我们才能体会吧。

    ',21);function w(x,y){const r=l("ExternalLinkIcon");return i(),p("div",null,[s,e("p",null,[t("本文转自 "),e("a",c,[t("https://lotabout.me/2016/write-a-C-interpreter-9/"),a(r)]),t(",如有侵权,请联系删除。")]),E,e("ol",null,[e("li",null,[e("a",b,[t("手把手教你构建 C 语言编译器(0)——前言"),a(r)])]),e("li",null,[e("a",d,[t("手把手教你构建 C 语言编译器(1)——设计"),a(r)])]),e("li",null,[e("a",u,[t("手把手教你构建 C 语言编译器(2)——虚拟机"),a(r)])]),e("li",null,[e("a",f,[t("手把手教你构建 C 语言编译器(3)——词法分析器"),a(r)])]),e("li",null,[e("a",C,[t("手把手教你构建 C 语言编译器(4)——递归下降"),a(r)])]),e("li",null,[e("a",_,[t("手把手教你构建 C 语言编译器(5)——变量定义"),a(r)])]),e("li",null,[e("a",m,[t("手把手教你构建 C 语言编译器(6)——函数定义"),a(r)])]),e("li",null,[e("a",B,[t("手把手教你构建 C 语言编译器(7)——语句"),a(r)])]),e("li",null,[e("a",A,[t("手把手教你构建 C 语言编译器(8)——表达式"),a(r)])]),e("li",null,[e("a",g,[t("手把手教你构建 C 语言编译器(9)——总结"),a(r)])])]),k])}const D=o(h,[["render",w],["__file","9.html.vue"]]),v=JSON.parse('{"path":"/tech/designASimpileCCompiler/9.html","title":"手把手教你构建 C 语言编译器(9)——总结","lang":"zh-CN","frontmatter":{"title":"手把手教你构建 C 语言编译器(9)——总结","category":["编译原理"],"tag":["c","编译器","解释器"],"description":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-9/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(9)- 总结 Table of Contents 1. 虚拟机与目标代码 2. 词法分析 3. 语法分析 4. 关于编代码 5. 结语 恭喜你完成了自己的 C 语言编译...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/designASimpileCCompiler/9.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"手把手教你构建 C 语言编译器(9)——总结"}],["meta",{"property":"og:description","content":"转载声明 本文转自 https://lotabout.me/2016/write-a-C-interpreter-9/,如有侵权,请联系删除。 原文内容 手把手教你构建 C 语言编译器(9)- 总结 Table of Contents 1. 虚拟机与目标代码 2. 词法分析 3. 语法分析 4. 关于编代码 5. 结语 恭喜你完成了自己的 C 语言编译..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:tag","content":"c"}],["meta",{"property":"article:tag","content":"编译器"}],["meta",{"property":"article:tag","content":"解释器"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"手把手教你构建 C 语言编译器(9)——总结\\",\\"image\\":[\\"\\"],\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"虚拟机与目标代码","slug":"虚拟机与目标代码","link":"#虚拟机与目标代码","children":[]},{"level":2,"title":"词法分析","slug":"词法分析","link":"#词法分析","children":[]},{"level":2,"title":"语法分析","slug":"语法分析","link":"#语法分析","children":[]},{"level":2,"title":"关于编代码","slug":"关于编代码","link":"#关于编代码","children":[]},{"level":2,"title":"结语","slug":"结语","link":"#结语","children":[]}],"readingTime":{"minutes":4.66,"words":1397},"filePathRelative":"tech/designASimpileCCompiler/9.md","excerpt":"\\n

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-9/,如有侵权,请联系删除。

    \\n

    原文内容

    \\n

    手把手教你构建 C 语言编译器(9)- 总结

    \\n

    Table of Contents

    \\n
      \\n
    1. 1. 虚拟机与目标代码
    2. \\n
    3. 2. 词法分析
    4. \\n
    5. 3. 语法分析
    6. \\n
    7. 4. 关于编代码
    8. \\n
    9. 5. 结语
    10. \\n
    ","autoDesc":true}');export{D as comp,v as data}; diff --git a/assets/DesignPrinciples.html-BLnXHGN8.js b/assets/DesignPrinciples.html-BAjp9viD.js similarity index 99% rename from assets/DesignPrinciples.html-BLnXHGN8.js rename to assets/DesignPrinciples.html-BAjp9viD.js index 8ba4c1e..86b27e4 100644 --- a/assets/DesignPrinciples.html-BLnXHGN8.js +++ b/assets/DesignPrinciples.html-BAjp9viD.js @@ -1,4 +1,4 @@ -import{_ as a,o as e,c as t,a as n,b as l,f as s}from"./app-B69Gl_S-.js";const i={},p=l("p",null,"类的设计原则有七个,包括:开闭原则、里氏代换原则、迪米特原则(最少知道原则)、单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则。",-1),c=s('

    关系

    七大原则之间并不是相互孤立的,而是相互关联的。一个原则可以是另一个原则的加强或基础。违反其中一个原则,可能同时违反其他原则。

    开闭原则是面向对象可复用设计的基石,其他设计原则是实现开闭原则的手段和工具。

    通常,可以将这七个原则分为以下两部分:

    开闭原则

    根据开闭原则,在设计一个系统模块的时候,应该可以在不修改原模块的基础上,扩展其功能。

    实现方法

    1. 使用抽象类和接口:通过定义抽象类和接口,可以在不修改现有代码的情况下,增加新的实现。
    2. 使用设计模式:例如策略模式、装饰者模式等,可以在不改变原有代码的情况下,动态地扩展对象的行为。
    3. 遵循单一职责原则:确保每个类只有一个职责,这样在扩展功能时,不会影响其他功能。
    ',10),o=s(`

    里氏代换原则

    里氏代换原则

    实现方法

    1. 使用抽象类和接口:确保子类实现父类的抽象方法,而不是重写父类的具体方法。
    2. 遵循契约:子类的方法签名应与父类一致,返回类型应与父类相同或是其子类型。
    3. 遵循行为一致性:子类应保持父类的行为,不应引入违反父类预期的新行为。

    示例

    迪米特原则

    迪米特原则

    一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立;降低类之间的耦合度,提高模块的相对独立性。

    实现方法

    1. 引入中介者模式:通过中介者对象来管理对象之间的交互,减少对象之间的直接依赖。
    2. 使用门面模式:通过门面对象提供统一的接口,隐藏系统的复杂性,减少对象之间的直接交互。
    3. 限制公开方法:尽量减少类的公开方法,避免不必要的外部依赖。

    单一职责原则

    实现方法

    1. 职责分离:将不同的职责分离到不同的类中,每个类只负责一个职责。
    2. 模块化设计:通过模块化设计,将不同的功能模块分开,确保每个模块只负责一个功能。
    3. 重构:在发现类有多个职责时,及时进行重构,将不同的职责分离到不同的类中。

    示例

    // 不符合单一职责原则的类
    +import{_ as a,o as e,c as t,a as n,b as l,f as s}from"./app-B4LGNJZ0.js";const i={},p=l("p",null,"类的设计原则有七个,包括:开闭原则、里氏代换原则、迪米特原则(最少知道原则)、单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则。",-1),c=s('

    关系

    七大原则之间并不是相互孤立的,而是相互关联的。一个原则可以是另一个原则的加强或基础。违反其中一个原则,可能同时违反其他原则。

    开闭原则是面向对象可复用设计的基石,其他设计原则是实现开闭原则的手段和工具。

    通常,可以将这七个原则分为以下两部分:

    • 设计目标:开闭原则、里氏代换原则、迪米特原则
    • 设计方法:单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则

    开闭原则

    • 对扩展开放
    • 对修改关闭

    根据开闭原则,在设计一个系统模块的时候,应该可以在不修改原模块的基础上,扩展其功能。

    实现方法

    1. 使用抽象类和接口:通过定义抽象类和接口,可以在不修改现有代码的情况下,增加新的实现。
    2. 使用设计模式:例如策略模式、装饰者模式等,可以在不改变原有代码的情况下,动态地扩展对象的行为。
    3. 遵循单一职责原则:确保每个类只有一个职责,这样在扩展功能时,不会影响其他功能。
    ',10),o=s(`

    里氏代换原则

    里氏代换原则

    • 里氏代换原则规定子类不得重写父类的普通方法,只能重写父类的抽象方法;即子类可以扩展父类的功能,但是不能改变父类原有的功能。
    • 派生类应当可以替换基类并出现在基类能够出现的任何地方,或者说如果我们把代码中使用基类的地方用它的派生类所代替,代码还能正常工作。

    实现方法

    1. 使用抽象类和接口:确保子类实现父类的抽象方法,而不是重写父类的具体方法。
    2. 遵循契约:子类的方法签名应与父类一致,返回类型应与父类相同或是其子类型。
    3. 遵循行为一致性:子类应保持父类的行为,不应引入违反父类预期的新行为。

    示例

    迪米特原则

    迪米特原则

    一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立;降低类之间的耦合度,提高模块的相对独立性。

    实现方法

    1. 引入中介者模式:通过中介者对象来管理对象之间的交互,减少对象之间的直接依赖。
    2. 使用门面模式:通过门面对象提供统一的接口,隐藏系统的复杂性,减少对象之间的直接交互。
    3. 限制公开方法:尽量减少类的公开方法,避免不必要的外部依赖。

    单一职责原则

    • 一个类只对应一个职责,其职责是引起该类变化的原因。
    • 如果一个类需要改变,改变它的理由永远只有一个。如果存在多个改变它的理由,就需要重新设计该类。

    实现方法

    1. 职责分离:将不同的职责分离到不同的类中,每个类只负责一个职责。
    2. 模块化设计:通过模块化设计,将不同的功能模块分开,确保每个模块只负责一个功能。
    3. 重构:在发现类有多个职责时,及时进行重构,将不同的职责分离到不同的类中。

    示例

    // 不符合单一职责原则的类
     class User {
       login() {
         // 登录逻辑
    diff --git "a/assets/Qt6 Maintence tools\344\270\215\350\203\275\345\256\211\350\243\205Qt5.html-CTk8exni.js" "b/assets/Qt6 Maintence tools\344\270\215\350\203\275\345\256\211\350\243\205Qt5.html-DdU1Gd4z.js"
    similarity index 97%
    rename from "assets/Qt6 Maintence tools\344\270\215\350\203\275\345\256\211\350\243\205Qt5.html-CTk8exni.js"
    rename to "assets/Qt6 Maintence tools\344\270\215\350\203\275\345\256\211\350\243\205Qt5.html-DdU1Gd4z.js"
    index 4152b48..7462137 100644
    --- "a/assets/Qt6 Maintence tools\344\270\215\350\203\275\345\256\211\350\243\205Qt5.html-CTk8exni.js"	
    +++ "b/assets/Qt6 Maintence tools\344\270\215\350\203\275\345\256\211\350\243\205Qt5.html-DdU1Gd4z.js"	
    @@ -1 +1 @@
    -import{_ as t,o as e,c as n,f as o}from"./app-B69Gl_S-.js";const i={},s=o('

    Qt

    Some Solutions to Questions about Qt

    The qt-5 isn't in Qt Maintenance tools

    1. Open the Qt Maintenance Tools.
    2. click the Archive in the Select-Component section.
    3. click the filter button and you can see the qt-5 in the list.
    ',4),a=[s];function l(c,h){return e(),n("div",null,a)}const p=t(i,[["render",l],["__file","Qt6 Maintence tools不能安装Qt5.html.vue"]]),u=JSON.parse(`{"path":"/tech/Qt6%20Maintence%20tools%E4%B8%8D%E8%83%BD%E5%AE%89%E8%A3%85Qt5.html","title":"Qt","lang":"zh-CN","frontmatter":{"title":"Qt","date":"2024-08-31T00:00:00.000Z","draft":false,"description":"Qt Some Solutions to Questions about Qt The qt-5 isn't in Qt Maintenance tools Open the Qt Maintenance Tools. click the Archive in the Select-Component section. click the filter...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/Qt6%20Maintence%20tools%E4%B8%8D%E8%83%BD%E5%AE%89%E8%A3%85Qt5.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"Qt"}],["meta",{"property":"og:description","content":"Qt Some Solutions to Questions about Qt The qt-5 isn't in Qt Maintenance tools Open the Qt Maintenance Tools. click the Archive in the Select-Component section. click the filter..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:published_time","content":"2024-08-31T00:00:00.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Qt\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-08-31T00:00:00.000Z\\",\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"Some Solutions to Questions about Qt","slug":"some-solutions-to-questions-about-qt","link":"#some-solutions-to-questions-about-qt","children":[{"level":3,"title":"The qt-5 isn't in Qt Maintenance tools","slug":"the-qt-5-isn-t-in-qt-maintenance-tools","link":"#the-qt-5-isn-t-in-qt-maintenance-tools","children":[]}]}],"readingTime":{"minutes":0.18,"words":54},"filePathRelative":"tech/Qt6 Maintence tools不能安装Qt5.md","localizedDate":"2024年8月31日","excerpt":"\\n

    Some Solutions to Questions about Qt

    \\n

    The qt-5 isn't in Qt Maintenance tools

    \\n
      \\n
    1. Open the Qt Maintenance Tools.
    2. \\n
    3. click the Archive in the Select-Component section.
    4. \\n
    5. click the filter button and you can see the qt-5 in the list.
    6. \\n
    \\n","autoDesc":true}`);export{p as comp,u as data}; +import{_ as t,o as e,c as n,f as o}from"./app-B4LGNJZ0.js";const i={},s=o('

    Qt

    Some Solutions to Questions about Qt

    The qt-5 isn't in Qt Maintenance tools

    1. Open the Qt Maintenance Tools.
    2. click the Archive in the Select-Component section.
    3. click the filter button and you can see the qt-5 in the list.
    ',4),a=[s];function l(c,h){return e(),n("div",null,a)}const p=t(i,[["render",l],["__file","Qt6 Maintence tools不能安装Qt5.html.vue"]]),u=JSON.parse(`{"path":"/tech/Qt6%20Maintence%20tools%E4%B8%8D%E8%83%BD%E5%AE%89%E8%A3%85Qt5.html","title":"Qt","lang":"zh-CN","frontmatter":{"title":"Qt","date":"2024-08-31T00:00:00.000Z","draft":false,"description":"Qt Some Solutions to Questions about Qt The qt-5 isn't in Qt Maintenance tools Open the Qt Maintenance Tools. click the Archive in the Select-Component section. click the filter...","gitInclude":[],"head":[["meta",{"property":"og:url","content":"https://github.com/King-sj/tech/Qt6%20Maintence%20tools%E4%B8%8D%E8%83%BD%E5%AE%89%E8%A3%85Qt5.html"}],["meta",{"property":"og:site_name","content":"blog"}],["meta",{"property":"og:title","content":"Qt"}],["meta",{"property":"og:description","content":"Qt Some Solutions to Questions about Qt The qt-5 isn't in Qt Maintenance tools Open the Qt Maintenance Tools. click the Archive in the Select-Component section. click the filter..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"KSJ"}],["meta",{"property":"article:published_time","content":"2024-08-31T00:00:00.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Qt\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-08-31T00:00:00.000Z\\",\\"dateModified\\":null,\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"KSJ\\",\\"url\\":\\"https://github.com/King-sj\\"}]}"]]},"headers":[{"level":2,"title":"Some Solutions to Questions about Qt","slug":"some-solutions-to-questions-about-qt","link":"#some-solutions-to-questions-about-qt","children":[{"level":3,"title":"The qt-5 isn't in Qt Maintenance tools","slug":"the-qt-5-isn-t-in-qt-maintenance-tools","link":"#the-qt-5-isn-t-in-qt-maintenance-tools","children":[]}]}],"readingTime":{"minutes":0.18,"words":54},"filePathRelative":"tech/Qt6 Maintence tools不能安装Qt5.md","localizedDate":"2024年8月31日","excerpt":"\\n

    Some Solutions to Questions about Qt

    \\n

    The qt-5 isn't in Qt Maintenance tools

    \\n
      \\n
    1. Open the Qt Maintenance Tools.
    2. \\n
    3. click the Archive in the Select-Component section.
    4. \\n
    5. click the filter button and you can see the qt-5 in the list.
    6. \\n
    \\n","autoDesc":true}`);export{p as comp,u as data}; diff --git a/assets/SearchResult-DAas8KH8.js b/assets/SearchResult-DAas8KH8.js new file mode 100644 index 0000000..d2db279 --- /dev/null +++ b/assets/SearchResult-DAas8KH8.js @@ -0,0 +1 @@ +import{u as U,g as te,h as se,i as M,j as ae,P as le,t as re,k as ie,l as x,m as F,n as ne,p as Y,q as s,s as ce,R as O,v as oe,x as Ee,y as ue,C as he,z as me,A as Be,B as pe,D as ge,E as Ae,F as de,G as ve,H as T,I as $,J as ye,K as f,L as De}from"./app-B4LGNJZ0.js";const Ce=["/","/intro.html","/essays/","/essays/%E5%91%BD%E9%87%8C%E6%9C%89%E6%97%B6%E7%BB%88%E9%A1%BB%E6%9C%89.html","/projects/carrobot.html","/projects/","/resources/","/tech/Qt6%20Maintence%20tools%E4%B8%8D%E8%83%BD%E5%AE%89%E8%A3%85Qt5.html","/tech/android%20studio%E6%8D%A2%E6%BA%90.html","/tech/cicd.html","/tech/docker_nginx%E9%83%A8%E7%BD%B2web%E9%A1%B9%E7%9B%AE.html","/tech/docker.html","/tech/figure_bed.html","/tech/github%20%E5%B7%A5%E4%BD%9C%E6%B5%81.html","/tech/","/tech/mailserver.html","/tech/modint.html","/tech/python_requrements.html","/tech/steam%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91.html","/tech/style.html","/tech/tensorflow%E8%B8%A9%E5%9D%91.html","/tech/vsocde%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%B0%8F%E8%AE%B0.html","/tech/%E3%80%8C%E7%AE%97%E6%9C%AF%E5%85%AC%E7%90%86%E7%B3%BB%E7%BB%9F%201%E3%80%8D%E8%87%AA%E7%84%B6%E6%95%B0.html","/tech/%E4%BD%BF%E7%94%A8capacitor%E5%92%8Cionic%E5%B0%86vue%E9%A1%B9%E7%9B%AE%E8%BF%81%E7%A7%BB%E5%88%B0mobile%E7%AB%AF.html","/tech/%E5%8D%8F%E7%A8%8B(asyncio)%E5%88%B0%E5%BA%95%E9%9C%80%E4%B8%8D%E9%9C%80%E8%A6%81%E5%8A%A0%E9%94%81.html","/tech/%E5%8D%8F%E7%A8%8B.html","/tech/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84PPT.html","/tech/%E9%97%AD%E5%8C%85%E5%AE%9E%E7%8E%B0%E7%B1%BB.html","/tutorials/","/tutorials/typora%E6%BF%80%E6%B4%BB.html","/resources/compile/","/resources/html2md/","/tech/DesignPatterns/DesignPrinciples.html","/tech/DesignPatterns/abstractFactory.html","/tech/DesignPatterns/adapter.html","/tech/DesignPatterns/bridge.html","/tech/DesignPatterns/builder.html","/tech/DesignPatterns/chainOfResponsibility.html","/tech/DesignPatterns/command.html","/tech/DesignPatterns/composite.html","/tech/DesignPatterns/decorator.html","/tech/DesignPatterns/end.html","/tech/DesignPatterns/facade.html","/tech/DesignPatterns/factory_method.html","/tech/DesignPatterns/flyweight.html","/tech/DesignPatterns/","/tech/DesignPatterns/interpreter.html","/tech/DesignPatterns/iterator.html","/tech/DesignPatterns/mediator.html","/tech/DesignPatterns/memento.html","/tech/DesignPatterns/observer.html","/tech/DesignPatterns/prototype.html","/tech/DesignPatterns/proxy.html","/tech/DesignPatterns/singleton.html","/tech/DesignPatterns/state.html","/tech/DesignPatterns/strategy.html","/tech/DesignPatterns/template_method.html","/tech/DesignPatterns/visitor.html","/tech/designASimpileCCompiler/0.html","/tech/designASimpileCCompiler/1.html","/tech/designASimpileCCompiler/2.html","/tech/designASimpileCCompiler/3.html","/tech/designASimpileCCompiler/4.html","/tech/designASimpileCCompiler/5.html","/tech/designASimpileCCompiler/6.html","/tech/designASimpileCCompiler/7.html","/tech/designASimpileCCompiler/8.html","/tech/designASimpileCCompiler/9.html","/404.html","/tech/designASimpileCCompiler/","/category/","/category/build-tools/","/category/cicd/","/category/nginx/","/category/%E5%A5%87%E6%8A%80%E6%B7%AB%E5%B7%A7/","/category/docker/","/category/oi/","/category/python/","/category/%E9%A3%8E%E6%A0%BC/","/category/%E6%95%B0%E5%AD%A6/","/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","/category/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/","/tag/","/tag/cicd/","/tag/%E9%83%A8%E7%BD%B2/","/tag/docker/","/tag/modint/","/tag/%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/","/tag/%E9%A3%8E%E6%A0%BC/","/tag/%E5%85%AC%E7%90%86%E7%B3%BB%E7%BB%9F/","/tag/mobile/","/tag/%E6%BF%80%E6%B4%BB/","/tag/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","/tag/typescript/","/tag/%E7%94%9F%E6%88%90%E5%AE%9E%E4%BE%8B/","/tag/%E5%88%86%E5%BC%80%E8%80%83%E8%99%91/","/tag/%E6%8E%A8%E5%8D%B8%E8%B4%A3%E4%BB%BB/","/tag/%E7%94%A8%E7%B1%BB%E5%AE%9E%E7%8E%B0/","/tag/%E5%AE%B9%E5%99%A8%E4%B8%8E%E5%86%85%E5%AE%B9%E7%9A%84%E4%B8%80%E8%87%B4%E6%80%A7/","/tag/%E7%AE%80%E5%8D%95%E5%8C%96/","/tag/%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95/","/tag/%E4%BA%A4%E7%BB%99%E5%AD%90%E7%B1%BB/","/tag/%E9%81%BF%E5%85%8D%E6%B5%AA%E8%B4%B9/","/tag/%E7%AE%A1%E7%90%86%E7%8A%B6%E6%80%81/","/tag/%E6%95%B4%E4%BD%93%E7%9A%84%E6%9B%BF%E6%8D%A2%E7%AE%97%E6%B3%95/","/tag/%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%B9%B6%E5%A4%84%E7%90%86%E6%95%B0%E6%8D%AE/","/tag/c/","/tag/%E7%BC%96%E8%AF%91%E5%99%A8/","/tag/%E8%A7%A3%E9%87%8A%E5%99%A8/","/article/","/star/","/timeline/"],Pe="SEARCH_PRO_QUERY_HISTORY",A=U(Pe,[]),Fe=()=>{const{queryHistoryCount:a}=f,l=a>0;return{enabled:l,queryHistory:A,addQueryHistory:r=>{l&&(A.value=Array.from(new Set([r,...A.value.slice(0,a-1)])))},removeQueryHistory:r=>{A.value=[...A.value.slice(0,r),...A.value.slice(r+1)]}}},q=a=>Ce[a.id]+("anchor"in a?`#${a.anchor}`:""),fe="SEARCH_PRO_RESULT_HISTORY",{resultHistoryCount:I}=f,d=U(fe,[]),He=()=>{const a=I>0;return{enabled:a,resultHistory:d,addResultHistory:l=>{if(a){const r={link:q(l),display:l.display};"header"in l&&(r.header=l.header),d.value=[r,...d.value.slice(0,I-1)]}},removeResultHistory:l=>{d.value=[...d.value.slice(0,l),...d.value.slice(l+1)]}}},Se=a=>{const l=he(),r=M(),H=me(),n=x(0),D=F(()=>n.value>0),B=Be([]);return pe(()=>{const{search:p,terminate:S}=ge(),v=ye(E=>{const y=E.join(" "),{searchFilter:k=m=>m,splitWord:R,suggestionsFilter:L,...g}=l.value;y?(n.value+=1,p(E.join(" "),r.value,g).then(m=>k(m,y,r.value,H.value)).then(m=>{n.value-=1,B.value=m}).catch(m=>{console.warn(m),n.value-=1,n.value||(B.value=[])})):B.value=[]},f.searchDelay-f.suggestDelay);Y([a,r],([E])=>v(E),{immediate:!0}),Ae(()=>{S()})}),{isSearching:D,results:B}};var Re=te({name:"SearchResult",props:{queries:{type:Array,required:!0},isFocusing:Boolean},emits:["close","updateQuery"],setup(a,{emit:l}){const r=se(),H=M(),n=ae(le),{enabled:D,addQueryHistory:B,queryHistory:p,removeQueryHistory:S}=Fe(),{enabled:v,resultHistory:E,addResultHistory:y,removeResultHistory:k}=He(),R=D||v,L=re(a,"queries"),{results:g,isSearching:m}=Se(L),i=ie({isQuery:!0,index:0}),u=x(0),h=x(0),_=F(()=>R&&(p.value.length>0||E.value.length>0)),b=F(()=>g.value.length>0),w=F(()=>g.value[u.value]||null),z=()=>{const{isQuery:e,index:t}=i;t===0?(i.isQuery=!e,i.index=e?E.value.length-1:p.value.length-1):i.index=t-1},G=()=>{const{isQuery:e,index:t}=i;t===(e?p.value.length-1:E.value.length-1)?(i.isQuery=!e,i.index=0):i.index=t+1},J=()=>{u.value=u.value>0?u.value-1:g.value.length-1,h.value=w.value.contents.length-1},K=()=>{u.value=u.value{h.value{h.value>0?h.value-=1:J()},Q=e=>e.map(t=>De(t)?t:s(t[0],t[1])),W=e=>{if(e.type==="customField"){const t=de[e.index]||"$content",[c,P=""]=ve(t)?t[H.value].split("$content"):t.split("$content");return e.display.map(o=>s("div",Q([c,...o,P])))}return e.display.map(t=>s("div",Q(t)))},C=()=>{u.value=0,h.value=0,l("updateQuery",""),l("close")},X=()=>D?s("ul",{class:"search-pro-result-list"},s("li",{class:"search-pro-result-list-item"},[s("div",{class:"search-pro-result-title"},n.value.queryHistory),p.value.map((e,t)=>s("div",{class:["search-pro-result-item",{active:i.isQuery&&i.index===t}],onClick:()=>{l("updateQuery",e)}},[s(T,{class:"search-pro-result-type"}),s("div",{class:"search-pro-result-content"},e),s("button",{class:"search-pro-remove-icon",innerHTML:$,onClick:c=>{c.preventDefault(),c.stopPropagation(),S(t)}})]))])):null,Z=()=>v?s("ul",{class:"search-pro-result-list"},s("li",{class:"search-pro-result-list-item"},[s("div",{class:"search-pro-result-title"},n.value.resultHistory),E.value.map((e,t)=>s(O,{to:e.link,class:["search-pro-result-item",{active:!i.isQuery&&i.index===t}],onClick:()=>{C()}},()=>[s(T,{class:"search-pro-result-type"}),s("div",{class:"search-pro-result-content"},[e.header?s("div",{class:"content-header"},e.header):null,s("div",e.display.map(c=>Q(c)).flat())]),s("button",{class:"search-pro-remove-icon",innerHTML:$,onClick:c=>{c.preventDefault(),c.stopPropagation(),k(t)}})]))])):null;return ne("keydown",e=>{if(a.isFocusing){if(b.value){if(e.key==="ArrowUp")N();else if(e.key==="ArrowDown")V();else if(e.key==="Enter"){const t=w.value.contents[h.value];B(a.queries.join(" ")),y(t),r.push(q(t)),C()}}else if(v){if(e.key==="ArrowUp")z();else if(e.key==="ArrowDown")G();else if(e.key==="Enter"){const{index:t}=i;i.isQuery?(l("updateQuery",p.value[t]),e.preventDefault()):(r.push(E.value[t].link),C())}}}}),Y([u,h],()=>{var e;(e=document.querySelector(".search-pro-result-list-item.active .search-pro-result-item.active"))==null||e.scrollIntoView(!1)},{flush:"post"}),()=>s("div",{class:["search-pro-result-wrapper",{empty:a.queries.length?!b.value:!_.value}],id:"search-pro-results"},a.queries.length?m.value?s(ce,{hint:n.value.searching}):b.value?s("ul",{class:"search-pro-result-list"},g.value.map(({title:e,contents:t},c)=>{const P=u.value===c;return s("li",{class:["search-pro-result-list-item",{active:P}]},[s("div",{class:"search-pro-result-title"},e||n.value.defaultTitle),t.map((o,ee)=>{const j=P&&h.value===ee;return s(O,{to:q(o),class:["search-pro-result-item",{active:j,"aria-selected":j}],onClick:()=>{B(a.queries.join(" ")),y(o),C()}},()=>[o.type==="text"?null:s(o.type==="title"?oe:o.type==="heading"?Ee:ue,{class:"search-pro-result-type"}),s("div",{class:"search-pro-result-content"},[o.type==="text"&&o.header?s("div",{class:"content-header"},o.header):null,s("div",W(o))])])})])})):n.value.emptyResult:R?_.value?[X(),Z()]:n.value.emptyHistory:n.value.emptyResult)}});export{Re as default}; diff --git a/assets/SearchResult-DUP77o_D.js b/assets/SearchResult-DUP77o_D.js deleted file mode 100644 index 30669e9..0000000 --- a/assets/SearchResult-DUP77o_D.js +++ /dev/null @@ -1 +0,0 @@ -import{u as U,g as te,h as se,i as M,j as ae,P as le,t as re,k as ie,l as x,m as F,n as ne,p as Y,q as s,s as ce,R as T,v as Ee,x as oe,y as ue,C as he,z as me,A as Be,B as pe,D as ge,E as Ae,F as de,G as ve,H as j,I as $,J as ye,K as f,L as De}from"./app-B69Gl_S-.js";const Ce=["/","/intro.html","/essays/","/essays/%E5%91%BD%E9%87%8C%E6%9C%89%E6%97%B6%E7%BB%88%E9%A1%BB%E6%9C%89.html","/projects/","/resources/","/tech/Qt6%20Maintence%20tools%E4%B8%8D%E8%83%BD%E5%AE%89%E8%A3%85Qt5.html","/tech/android%20studio%E6%8D%A2%E6%BA%90.html","/tech/cicd.html","/tech/docker_nginx%E9%83%A8%E7%BD%B2web%E9%A1%B9%E7%9B%AE.html","/tech/docker.html","/tech/figure_bed.html","/tech/github%20%E5%B7%A5%E4%BD%9C%E6%B5%81.html","/tech/","/tech/mailserver.html","/tech/modint.html","/tech/python_requrements.html","/tech/steam%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91.html","/tech/style.html","/tech/tensorflow%E8%B8%A9%E5%9D%91.html","/tech/vsocde%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%B0%8F%E8%AE%B0.html","/tech/%E3%80%8C%E7%AE%97%E6%9C%AF%E5%85%AC%E7%90%86%E7%B3%BB%E7%BB%9F%201%E3%80%8D%E8%87%AA%E7%84%B6%E6%95%B0.html","/tech/%E4%BD%BF%E7%94%A8capacitor%E5%92%8Cionic%E5%B0%86vue%E9%A1%B9%E7%9B%AE%E8%BF%81%E7%A7%BB%E5%88%B0mobile%E7%AB%AF.html","/tech/%E5%8D%8F%E7%A8%8B(asyncio)%E5%88%B0%E5%BA%95%E9%9C%80%E4%B8%8D%E9%9C%80%E8%A6%81%E5%8A%A0%E9%94%81.html","/tech/%E5%8D%8F%E7%A8%8B.html","/tech/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84PPT.html","/tech/%E9%97%AD%E5%8C%85%E5%AE%9E%E7%8E%B0%E7%B1%BB.html","/tutorials/","/tutorials/typora%E6%BF%80%E6%B4%BB.html","/resources/html2md/","/tech/DesignPatterns/DesignPrinciples.html","/tech/DesignPatterns/abstractFactory.html","/tech/DesignPatterns/adapter.html","/tech/DesignPatterns/bridge.html","/tech/DesignPatterns/builder.html","/tech/DesignPatterns/chainOfResponsibility.html","/tech/DesignPatterns/command.html","/tech/DesignPatterns/composite.html","/tech/DesignPatterns/decorator.html","/tech/DesignPatterns/end.html","/tech/DesignPatterns/facade.html","/tech/DesignPatterns/factory_method.html","/tech/DesignPatterns/flyweight.html","/tech/DesignPatterns/","/tech/DesignPatterns/interpreter.html","/tech/DesignPatterns/iterator.html","/tech/DesignPatterns/mediator.html","/tech/DesignPatterns/memento.html","/tech/DesignPatterns/observer.html","/tech/DesignPatterns/prototype.html","/tech/DesignPatterns/proxy.html","/tech/DesignPatterns/singleton.html","/tech/DesignPatterns/state.html","/tech/DesignPatterns/strategy.html","/tech/DesignPatterns/template_method.html","/tech/DesignPatterns/visitor.html","/tech/designASimpileCCompiler/0.html","/tech/designASimpileCCompiler/1.html","/tech/designASimpileCCompiler/2.html","/tech/designASimpileCCompiler/3.html","/tech/designASimpileCCompiler/4.html","/tech/designASimpileCCompiler/5.html","/tech/designASimpileCCompiler/6.html","/tech/designASimpileCCompiler/7.html","/tech/designASimpileCCompiler/8.html","/tech/designASimpileCCompiler/9.html","/404.html","/tech/designASimpileCCompiler/","/category/","/category/build-tools/","/category/cicd/","/category/nginx/","/category/%E5%A5%87%E6%8A%80%E6%B7%AB%E5%B7%A7/","/category/docker/","/category/oi/","/category/python/","/category/%E9%A3%8E%E6%A0%BC/","/category/%E6%95%B0%E5%AD%A6/","/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","/category/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/","/tag/","/tag/cicd/","/tag/%E9%83%A8%E7%BD%B2/","/tag/docker/","/tag/modint/","/tag/%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/","/tag/%E9%A3%8E%E6%A0%BC/","/tag/%E5%85%AC%E7%90%86%E7%B3%BB%E7%BB%9F/","/tag/mobile/","/tag/%E6%BF%80%E6%B4%BB/","/tag/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","/tag/typescript/","/tag/%E7%94%9F%E6%88%90%E5%AE%9E%E4%BE%8B/","/tag/%E5%88%86%E5%BC%80%E8%80%83%E8%99%91/","/tag/%E6%8E%A8%E5%8D%B8%E8%B4%A3%E4%BB%BB/","/tag/%E7%94%A8%E7%B1%BB%E5%AE%9E%E7%8E%B0/","/tag/%E5%AE%B9%E5%99%A8%E4%B8%8E%E5%86%85%E5%AE%B9%E7%9A%84%E4%B8%80%E8%87%B4%E6%80%A7/","/tag/%E7%AE%80%E5%8D%95%E5%8C%96/","/tag/%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95/","/tag/%E4%BA%A4%E7%BB%99%E5%AD%90%E7%B1%BB/","/tag/%E9%81%BF%E5%85%8D%E6%B5%AA%E8%B4%B9/","/tag/%E7%AE%A1%E7%90%86%E7%8A%B6%E6%80%81/","/tag/%E6%95%B4%E4%BD%93%E7%9A%84%E6%9B%BF%E6%8D%A2%E7%AE%97%E6%B3%95/","/tag/%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%B9%B6%E5%A4%84%E7%90%86%E6%95%B0%E6%8D%AE/","/tag/c/","/tag/%E7%BC%96%E8%AF%91%E5%99%A8/","/tag/%E8%A7%A3%E9%87%8A%E5%99%A8/","/article/","/star/","/timeline/"],Pe="SEARCH_PRO_QUERY_HISTORY",A=U(Pe,[]),Fe=()=>{const{queryHistoryCount:a}=f,l=a>0;return{enabled:l,queryHistory:A,addQueryHistory:r=>{l&&(A.value=Array.from(new Set([r,...A.value.slice(0,a-1)])))},removeQueryHistory:r=>{A.value=[...A.value.slice(0,r),...A.value.slice(r+1)]}}},q=a=>Ce[a.id]+("anchor"in a?`#${a.anchor}`:""),fe="SEARCH_PRO_RESULT_HISTORY",{resultHistoryCount:I}=f,d=U(fe,[]),He=()=>{const a=I>0;return{enabled:a,resultHistory:d,addResultHistory:l=>{if(a){const r={link:q(l),display:l.display};"header"in l&&(r.header=l.header),d.value=[r,...d.value.slice(0,I-1)]}},removeResultHistory:l=>{d.value=[...d.value.slice(0,l),...d.value.slice(l+1)]}}},Se=a=>{const l=he(),r=M(),H=me(),n=x(0),D=F(()=>n.value>0),B=Be([]);return pe(()=>{const{search:p,terminate:S}=ge(),v=ye(o=>{const y=o.join(" "),{searchFilter:k=m=>m,splitWord:R,suggestionsFilter:L,...g}=l.value;y?(n.value+=1,p(o.join(" "),r.value,g).then(m=>k(m,y,r.value,H.value)).then(m=>{n.value-=1,B.value=m}).catch(m=>{console.warn(m),n.value-=1,n.value||(B.value=[])})):B.value=[]},f.searchDelay-f.suggestDelay);Y([a,r],([o])=>v(o),{immediate:!0}),Ae(()=>{S()})}),{isSearching:D,results:B}};var Re=te({name:"SearchResult",props:{queries:{type:Array,required:!0},isFocusing:Boolean},emits:["close","updateQuery"],setup(a,{emit:l}){const r=se(),H=M(),n=ae(le),{enabled:D,addQueryHistory:B,queryHistory:p,removeQueryHistory:S}=Fe(),{enabled:v,resultHistory:o,addResultHistory:y,removeResultHistory:k}=He(),R=D||v,L=re(a,"queries"),{results:g,isSearching:m}=Se(L),i=ie({isQuery:!0,index:0}),u=x(0),h=x(0),_=F(()=>R&&(p.value.length>0||o.value.length>0)),b=F(()=>g.value.length>0),w=F(()=>g.value[u.value]||null),z=()=>{const{isQuery:e,index:t}=i;t===0?(i.isQuery=!e,i.index=e?o.value.length-1:p.value.length-1):i.index=t-1},G=()=>{const{isQuery:e,index:t}=i;t===(e?p.value.length-1:o.value.length-1)?(i.isQuery=!e,i.index=0):i.index=t+1},J=()=>{u.value=u.value>0?u.value-1:g.value.length-1,h.value=w.value.contents.length-1},K=()=>{u.value=u.value{h.value{h.value>0?h.value-=1:J()},Q=e=>e.map(t=>De(t)?t:s(t[0],t[1])),W=e=>{if(e.type==="customField"){const t=de[e.index]||"$content",[c,P=""]=ve(t)?t[H.value].split("$content"):t.split("$content");return e.display.map(E=>s("div",Q([c,...E,P])))}return e.display.map(t=>s("div",Q(t)))},C=()=>{u.value=0,h.value=0,l("updateQuery",""),l("close")},X=()=>D?s("ul",{class:"search-pro-result-list"},s("li",{class:"search-pro-result-list-item"},[s("div",{class:"search-pro-result-title"},n.value.queryHistory),p.value.map((e,t)=>s("div",{class:["search-pro-result-item",{active:i.isQuery&&i.index===t}],onClick:()=>{l("updateQuery",e)}},[s(j,{class:"search-pro-result-type"}),s("div",{class:"search-pro-result-content"},e),s("button",{class:"search-pro-remove-icon",innerHTML:$,onClick:c=>{c.preventDefault(),c.stopPropagation(),S(t)}})]))])):null,Z=()=>v?s("ul",{class:"search-pro-result-list"},s("li",{class:"search-pro-result-list-item"},[s("div",{class:"search-pro-result-title"},n.value.resultHistory),o.value.map((e,t)=>s(T,{to:e.link,class:["search-pro-result-item",{active:!i.isQuery&&i.index===t}],onClick:()=>{C()}},()=>[s(j,{class:"search-pro-result-type"}),s("div",{class:"search-pro-result-content"},[e.header?s("div",{class:"content-header"},e.header):null,s("div",e.display.map(c=>Q(c)).flat())]),s("button",{class:"search-pro-remove-icon",innerHTML:$,onClick:c=>{c.preventDefault(),c.stopPropagation(),k(t)}})]))])):null;return ne("keydown",e=>{if(a.isFocusing){if(b.value){if(e.key==="ArrowUp")N();else if(e.key==="ArrowDown")V();else if(e.key==="Enter"){const t=w.value.contents[h.value];B(a.queries.join(" ")),y(t),r.push(q(t)),C()}}else if(v){if(e.key==="ArrowUp")z();else if(e.key==="ArrowDown")G();else if(e.key==="Enter"){const{index:t}=i;i.isQuery?(l("updateQuery",p.value[t]),e.preventDefault()):(r.push(o.value[t].link),C())}}}}),Y([u,h],()=>{var e;(e=document.querySelector(".search-pro-result-list-item.active .search-pro-result-item.active"))==null||e.scrollIntoView(!1)},{flush:"post"}),()=>s("div",{class:["search-pro-result-wrapper",{empty:a.queries.length?!b.value:!_.value}],id:"search-pro-results"},a.queries.length?m.value?s(ce,{hint:n.value.searching}):b.value?s("ul",{class:"search-pro-result-list"},g.value.map(({title:e,contents:t},c)=>{const P=u.value===c;return s("li",{class:["search-pro-result-list-item",{active:P}]},[s("div",{class:"search-pro-result-title"},e||n.value.defaultTitle),t.map((E,ee)=>{const O=P&&h.value===ee;return s(T,{to:q(E),class:["search-pro-result-item",{active:O,"aria-selected":O}],onClick:()=>{B(a.queries.join(" ")),y(E),C()}},()=>[E.type==="text"?null:s(E.type==="title"?Ee:E.type==="heading"?oe:ue,{class:"search-pro-result-type"}),s("div",{class:"search-pro-result-content"},[E.type==="text"&&E.header?s("div",{class:"content-header"},E.header):null,s("div",W(E))])])})])})):n.value.emptyResult:R?_.value?[X(),Z()]:n.value.emptyHistory:n.value.emptyResult)}});export{Re as default}; diff --git a/assets/abstractFactory.html-DS3CgVqp.js b/assets/abstractFactory.html-D_oh5f2L.js similarity index 99% rename from assets/abstractFactory.html-DS3CgVqp.js rename to assets/abstractFactory.html-D_oh5f2L.js index 27ab4cf..f932ca9 100644 --- a/assets/abstractFactory.html-DS3CgVqp.js +++ b/assets/abstractFactory.html-D_oh5f2L.js @@ -1,4 +1,4 @@ -import{_ as s,o as a,c as t,a as p,b as n,f as o}from"./app-B69Gl_S-.js";const e={},c=n("h2",{id:"为什么要使用抽象工厂模式",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#为什么要使用抽象工厂模式"},[n("span",null,"为什么要使用抽象工厂模式")])],-1),l=n("p",null,"抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而无需明确指定具体类。它通过定义一个创建对象的接口来实现,这样子类可以决定实例化哪个类。抽象工厂模式使得一个类的实例化延迟到其子类。",-1),i=o(`
    1. 解耦:抽象工厂模式通过将对象的创建过程抽象化,减少了客户端代码与具体类之间的耦合。
    2. 一致性:确保同一工厂创建的一系列对象具有一致的接口和行为。
    3. 扩展性:可以方便地增加新的产品族而不影响现有代码。

    示例代码

    // factory.ts
    +import{_ as s,o as a,c as t,a as p,b as n,f as o}from"./app-B4LGNJZ0.js";const e={},c=n("h2",{id:"为什么要使用抽象工厂模式",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#为什么要使用抽象工厂模式"},[n("span",null,"为什么要使用抽象工厂模式")])],-1),l=n("p",null,"抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而无需明确指定具体类。它通过定义一个创建对象的接口来实现,这样子类可以决定实例化哪个类。抽象工厂模式使得一个类的实例化延迟到其子类。",-1),i=o(`
    1. 解耦:抽象工厂模式通过将对象的创建过程抽象化,减少了客户端代码与具体类之间的耦合。
    2. 一致性:确保同一工厂创建的一系列对象具有一致的接口和行为。
    3. 扩展性:可以方便地增加新的产品族而不影响现有代码。

    示例代码

    // factory.ts
     // 抽象工厂
     import { Link } from './link';
     import { Tray } from './tray';
    diff --git a/assets/adapter.html-B2UlYWFL.js b/assets/adapter.html-x6kHt0VN.js
    similarity index 99%
    rename from assets/adapter.html-B2UlYWFL.js
    rename to assets/adapter.html-x6kHt0VN.js
    index 2716586..ee6cf2e 100644
    --- a/assets/adapter.html-B2UlYWFL.js
    +++ b/assets/adapter.html-x6kHt0VN.js
    @@ -1,4 +1,4 @@
    -import{_ as n,o as s,c as a,f as t}from"./app-B69Gl_S-.js";const p={},e=t(`

    适配器模式

    适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

    为什么要用适配器模式

    在软件开发中,经常会遇到需要使用一些现有的类,但它们的接口并不符合当前系统的需求。适配器模式通过创建一个适配器类,将现有类的接口转换为所需的接口,从而使得现有类可以在新的环境中使用。

    类比

    类比

    比喻示例程序
    实际需求交流100VBanner
    变换装置适配器PrintBanner
    需求直流12VPrint

    示例程序

    Adapter 有两种方式实现

    // banner.ts
    +import{_ as n,o as s,c as a,f as t}from"./app-B4LGNJZ0.js";const p={},e=t(`

    适配器模式

    适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

    为什么要用适配器模式

    在软件开发中,经常会遇到需要使用一些现有的类,但它们的接口并不符合当前系统的需求。适配器模式通过创建一个适配器类,将现有类的接口转换为所需的接口,从而使得现有类可以在新的环境中使用。

    类比

    类比

    比喻示例程序
    实际需求交流100VBanner
    变换装置适配器PrintBanner
    需求直流12VPrint

    示例程序

    Adapter 有两种方式实现

    // banner.ts
     export class Banner {
       private string: string;
       constructor(string: string) {
    diff --git "a/assets/android studio\346\215\242\346\272\220.html-CxjDu5df.js" "b/assets/android studio\346\215\242\346\272\220.html-DkRIM6mj.js"
    similarity index 98%
    rename from "assets/android studio\346\215\242\346\272\220.html-CxjDu5df.js"
    rename to "assets/android studio\346\215\242\346\272\220.html-DkRIM6mj.js"
    index b5e0927..b8dde58 100644
    --- "a/assets/android studio\346\215\242\346\272\220.html-CxjDu5df.js"	
    +++ "b/assets/android studio\346\215\242\346\272\220.html-DkRIM6mj.js"	
    @@ -1,4 +1,4 @@
    -import{_ as i,r as o,o as a,c as l,b as e,e as n,d as r,f as s}from"./app-B69Gl_S-.js";const d={},u=e("h1",{id:"gradle",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#gradle"},[e("span",null,"gradle")])],-1),c={href:"https://mirrors.cloud.tencent.com/gradle/",target:"_blank",rel:"noopener noreferrer"},p=e("p",null,"Android Studio下载gradle太慢可换源",-1),m={href:"https://blog.csdn.net/qq_43811536/article/details/139447518",target:"_blank",rel:"noopener noreferrer"},v=s(`

    修改 settings.gradle.kts

    pluginManagement {
    +import{_ as i,r as o,o as a,c as l,b as e,e as n,d as r,f as s}from"./app-B4LGNJZ0.js";const d={},u=e("h1",{id:"gradle",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#gradle"},[e("span",null,"gradle")])],-1),c={href:"https://mirrors.cloud.tencent.com/gradle/",target:"_blank",rel:"noopener noreferrer"},p=e("p",null,"Android Studio下载gradle太慢可换源",-1),m={href:"https://blog.csdn.net/qq_43811536/article/details/139447518",target:"_blank",rel:"noopener noreferrer"},v=s(`

    修改 settings.gradle.kts

    pluginManagement {
         repositories {
             maven { url=uri ("https://jitpack.io") }
             maven { url=uri ("https://maven.aliyun.com/repository/releases") }
    diff --git a/assets/app-B69Gl_S-.js b/assets/app-B4LGNJZ0.js
    similarity index 63%
    rename from assets/app-B69Gl_S-.js
    rename to assets/app-B4LGNJZ0.js
    index 89e4f2c..aba8b3f 100644
    --- a/assets/app-B69Gl_S-.js
    +++ b/assets/app-B4LGNJZ0.js
    @@ -14,15 +14,18 @@
     * @vue/runtime-dom v3.4.27
     * (c) 2018-present Yuxi (Evan) You and Vue contributors
     * @license MIT
    -**/const K0="http://www.w3.org/2000/svg",J0="http://www.w3.org/1998/Math/MathML",Yt=typeof document<"u"?document:null,bs=Yt&&Yt.createElement("template"),Q0={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const a=t==="svg"?Yt.createElementNS(K0,e):t==="mathml"?Yt.createElementNS(J0,e):Yt.createElement(e,n?{is:n}:void 0);return e==="select"&&r&&r.multiple!=null&&a.setAttribute("multiple",r.multiple),a},createText:e=>Yt.createTextNode(e),createComment:e=>Yt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Yt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,a,o){const l=n?n.previousSibling:t.lastChild;if(a&&(a===o||a.nextSibling))for(;t.insertBefore(a.cloneNode(!0),n),!(a===o||!(a=a.nextSibling)););else{bs.innerHTML=r==="svg"?`${e}`:r==="mathml"?`${e}`:e;const i=bs.content;if(r==="svg"||r==="mathml"){const c=i.firstChild;for(;c.firstChild;)i.appendChild(c.firstChild);i.removeChild(c)}t.insertBefore(i,n)}return[l?l.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},zt="transition",Qn="animation",Hn=Symbol("_vtc"),Rt=(e,{slots:t})=>s(a0,Tc(e),t);Rt.displayName="Transition";const kc={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Y0=Rt.props=Pe({},Xi,kc),fn=(e,t=[])=>{te(e)?e.forEach(n=>n(...t)):e&&e(...t)},Es=e=>e?te(e)?e.some(t=>t.length>1):e.length>1:!1;function Tc(e){const t={};for(const R in e)R in kc||(t[R]=e[R]);if(e.css===!1)return t;const{name:n="v",type:r,duration:a,enterFromClass:o=`${n}-enter-from`,enterActiveClass:l=`${n}-enter-active`,enterToClass:i=`${n}-enter-to`,appearFromClass:c=o,appearActiveClass:u=l,appearToClass:d=i,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,v=Z0(a),_=v&&v[0],E=v&&v[1],{onBeforeEnter:b,onEnter:C,onEnterCancelled:y,onLeave:A,onLeaveCancelled:D,onBeforeAppear:P=b,onAppear:H=C,onAppearCancelled:O=y}=t,Q=(R,X,Le)=>{Wt(R,X?d:i),Wt(R,X?u:l),Le&&Le()},I=(R,X)=>{R._isLeaving=!1,Wt(R,f),Wt(R,h),Wt(R,p),X&&X()},U=R=>(X,Le)=>{const Ae=R?H:C,W=()=>Q(X,R,Le);fn(Ae,[X,W]),_s(()=>{Wt(X,R?c:o),Dt(X,R?d:i),Es(Ae)||ws(X,r,_,W)})};return Pe(t,{onBeforeEnter(R){fn(b,[R]),Dt(R,o),Dt(R,l)},onBeforeAppear(R){fn(P,[R]),Dt(R,c),Dt(R,u)},onEnter:U(!1),onAppear:U(!0),onLeave(R,X){R._isLeaving=!0;const Le=()=>I(R,X);Dt(R,f),Dt(R,p),xc(),_s(()=>{R._isLeaving&&(Wt(R,f),Dt(R,h),Es(A)||ws(R,r,E,Le))}),fn(A,[R,Le])},onEnterCancelled(R){Q(R,!1),fn(y,[R])},onAppearCancelled(R){Q(R,!0),fn(O,[R])},onLeaveCancelled(R){I(R),fn(D,[R])}})}function Z0(e){if(e==null)return null;if(Be(e))return[Ua(e.enter),Ua(e.leave)];{const t=Ua(e);return[t,t]}}function Ua(e){return n2(e)}function Dt(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Hn]||(e[Hn]=new Set)).add(t)}function Wt(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[Hn];n&&(n.delete(t),n.size||(e[Hn]=void 0))}function _s(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let X0=0;function ws(e,t,n,r){const a=e._endId=++X0,o=()=>{a===e._endId&&r()};if(n)return setTimeout(o,n);const{type:l,timeout:i,propCount:c}=Sc(e,t);if(!l)return r();const u=l+"end";let d=0;const f=()=>{e.removeEventListener(u,p),o()},p=h=>{h.target===e&&++d>=c&&f()};setTimeout(()=>{d(n[v]||"").split(", "),a=r(`${zt}Delay`),o=r(`${zt}Duration`),l=As(a,o),i=r(`${Qn}Delay`),c=r(`${Qn}Duration`),u=As(i,c);let d=null,f=0,p=0;t===zt?l>0&&(d=zt,f=l,p=o.length):t===Qn?u>0&&(d=Qn,f=u,p=c.length):(f=Math.max(l,u),d=f>0?l>u?zt:Qn:null,p=d?d===zt?o.length:c.length:0);const h=d===zt&&/\b(transform|all)(,|$)/.test(r(`${zt}Property`).toString());return{type:d,timeout:f,propCount:p,hasTransform:h}}function As(e,t){for(;e.lengthCs(n)+Cs(e[r])))}function Cs(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function xc(){return document.body.offsetHeight}function ed(e,t,n){const r=e[Hn];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const ks=Symbol("_vod"),td=Symbol("_vsh"),nd=Symbol(""),rd=/(^|;)\s*display\s*:/;function ad(e,t,n){const r=e.style,a=Re(n);let o=!1;if(n&&!a){if(t)if(Re(t))for(const l of t.split(";")){const i=l.slice(0,l.indexOf(":")).trim();n[i]==null&&sa(r,i,"")}else for(const l in t)n[l]==null&&sa(r,l,"");for(const l in n)l==="display"&&(o=!0),sa(r,l,n[l])}else if(a){if(t!==n){const l=r[nd];l&&(n+=";"+l),r.cssText=n,o=rd.test(n)}}else t&&e.removeAttribute("style");ks in e&&(e[ks]=o?r.display:"",e[td]&&(r.display="none"))}const Ts=/\s*!important$/;function sa(e,t,n){if(te(n))n.forEach(r=>sa(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=od(e,t);Ts.test(n)?e.setProperty(jn(r),n.replace(Ts,""),"important"):e[r]=n}}const Ss=["Webkit","Moz","ms"],Wa={};function od(e,t){const n=Wa[t];if(n)return n;let r=qe(t);if(r!=="filter"&&r in e)return Wa[t]=r;r=_n(r);for(let a=0;aKa||(fd.then(()=>Ka=0),Ka=Date.now());function hd(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;ft(md(r,n.value),t,5,[r])};return n.value=e,n.attached=pd(),n}function md(e,t){if(te(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>a=>!a._stopped&&r&&r(a))}else return t}const Is=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,vd=(e,t,n,r,a,o,l,i,c)=>{const u=a==="svg";t==="class"?ed(e,r,u):t==="style"?ad(e,n,r):Sr(t)?Uo(t)||ud(e,t,n,r,l):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):gd(e,t,r,u))?sd(e,t,r,o,l,i,c):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),ld(e,t,r,u))};function gd(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t in e&&Is(t)&&re(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const a=e.tagName;if(a==="IMG"||a==="VIDEO"||a==="CANVAS"||a==="SOURCE")return!1}return Is(t)&&Re(n)?!1:t in e}const Lc=new WeakMap,Bc=new WeakMap,ga=Symbol("_moveCb"),Ps=Symbol("_enterCb"),Ic={name:"TransitionGroup",props:Pe({},Y0,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=ln(),r=Zi();let a,o;return rc(()=>{if(!a.length)return;const l=e.moveClass||`${e.name||"v"}-move`;if(!Ad(a[0].el,n.vnode.el,l))return;a.forEach(Ed),a.forEach(_d);const i=a.filter(wd);xc(),i.forEach(c=>{const u=c.el,d=u.style;Dt(u,l),d.transform=d.webkitTransform=d.transitionDuration="";const f=u[ga]=p=>{p&&p.target!==u||(!p||/transform$/.test(p.propertyName))&&(u.removeEventListener("transitionend",f),u[ga]=null,Wt(u,l))};u.addEventListener("transitionend",f)})}),()=>{const l=pe(e),i=Tc(l);let c=l.tag||nt;if(a=[],o)for(let u=0;udelete e.mode;Ic.props;const bd=Ic;function Ed(e){const t=e.el;t[ga]&&t[ga](),t[Ps]&&t[Ps]()}function _d(e){Bc.set(e,e.el.getBoundingClientRect())}function wd(e){const t=Lc.get(e),n=Bc.get(e),r=t.left-n.left,a=t.top-n.top;if(r||a){const o=e.el.style;return o.transform=o.webkitTransform=`translate(${r}px,${a}px)`,o.transitionDuration="0s",e}}function Ad(e,t,n){const r=e.cloneNode(),a=e[Hn];a&&a.forEach(i=>{i.split(/\s+/).forEach(c=>c&&r.classList.remove(c))}),n.split(/\s+/).forEach(i=>i&&r.classList.add(i)),r.style.display="none";const o=t.nodeType===1?t:t.parentNode;o.appendChild(r);const{hasTransform:l}=Sc(r);return o.removeChild(r),l}const Cd=Pe({patchProp:vd},Q0);let Ja,Ds=!1;function kd(){return Ja=Ds?Ja:B0(Cd),Ds=!0,Ja}const Td=(...e)=>{const t=kd().createApp(...e),{mount:n}=t;return t.mount=r=>{const a=xd(r);if(a)return n(a,!0,Sd(a))},t};function Sd(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function xd(e){return Re(e)?document.querySelector(e):e}var Ld=["link","meta","script","style","noscript","template"],Bd=["title","base"],Id=([e,t,n])=>Bd.includes(e)?e:Ld.includes(e)?e==="meta"&&t.name?`${e}.${t.name}`:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,Object.entries(t).map(([r,a])=>typeof a=="boolean"?a?[r,""]:null:[r,a]).filter(r=>r!=null).sort(([r],[a])=>r.localeCompare(a)),n]):null,Pd=e=>{const t=new Set,n=[];return e.forEach(r=>{const a=Id(r);a&&!t.has(a)&&(t.add(a),n.push(r))}),n},Dd=e=>e[0]==="/"?e:`/${e}`,Pc=e=>e[e.length-1]==="/"||e.endsWith(".html")?e:`${e}/`,sn=e=>/^(https?:)?\/\//.test(e),Od=/.md((\?|#).*)?$/,xa=(e,t="/")=>!!(sn(e)||e.startsWith("/")&&!e.startsWith(t)&&!Od.test(e)),La=e=>/^[a-z][a-z0-9+.-]*:/.test(e),qn=e=>Object.prototype.toString.call(e)==="[object Object]",Md=e=>{const[t,...n]=e.split(/(\?|#)/);if(!t||t.endsWith("/"))return e;let r=t.replace(/(^|\/)README.md$/i,"$1index.html");return r.endsWith(".md")?r=r.substring(0,r.length-3)+".html":r.endsWith(".html")||(r=r+".html"),r.endsWith("/index.html")&&(r=r.substring(0,r.length-10)),r+n.join("")},pl=e=>e[e.length-1]==="/"?e.slice(0,-1):e,Dc=e=>e[0]==="/"?e.slice(1):e,Rd=(e,t)=>{const n=Object.keys(e).sort((r,a)=>{const o=a.split("/").length-r.split("/").length;return o!==0?o:a.length-r.length});for(const r of n)if(t.startsWith(r))return r;return"/"},$d=e=>typeof e=="function",we=e=>typeof e=="string";const Fd="modulepreload",Hd=function(e){return"/"+e},Os={},T=function(t,n,r){let a=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),l=(o==null?void 0:o.nonce)||(o==null?void 0:o.getAttribute("nonce"));a=Promise.all(n.map(i=>{if(i=Hd(i),i in Os)return;Os[i]=!0;const c=i.endsWith(".css"),u=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${i}"]${u}`))return;const d=document.createElement("link");if(d.rel=c?"stylesheet":Fd,c||(d.as="script",d.crossOrigin=""),d.href=i,l&&d.setAttribute("nonce",l),document.head.appendChild(d),c)return new Promise((f,p)=>{d.addEventListener("load",f),d.addEventListener("error",()=>p(new Error(`Unable to preload CSS for ${i}`)))})}))}return a.then(()=>t()).catch(o=>{const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=o,window.dispatchEvent(l),!l.defaultPrevented)throw o})},Nd=JSON.parse('{"/tech/docker+nginx%E9%83%A8%E7%BD%B2web%E9%A1%B9%E7%9B%AE.html":"/tech/docker_nginx%E9%83%A8%E7%BD%B2web%E9%A1%B9%E7%9B%AE.html"}'),Vd=Object.fromEntries([["/",{loader:()=>T(()=>import("./index.html-BjAw7f-F.js"),[]),meta:{t:"主页",i:"home"}}],["/intro.html",{loader:()=>T(()=>import("./intro.html-CEHYbhC9.js"),[]),meta:{t:"介绍页",i:"circle-info"}}],["/essays/",{loader:()=>T(()=>import("./index.html-DzJoMr4P.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:`
    +**/const K0="http://www.w3.org/2000/svg",J0="http://www.w3.org/1998/Math/MathML",Yt=typeof document<"u"?document:null,bs=Yt&&Yt.createElement("template"),Q0={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const a=t==="svg"?Yt.createElementNS(K0,e):t==="mathml"?Yt.createElementNS(J0,e):Yt.createElement(e,n?{is:n}:void 0);return e==="select"&&r&&r.multiple!=null&&a.setAttribute("multiple",r.multiple),a},createText:e=>Yt.createTextNode(e),createComment:e=>Yt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Yt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,a,o){const l=n?n.previousSibling:t.lastChild;if(a&&(a===o||a.nextSibling))for(;t.insertBefore(a.cloneNode(!0),n),!(a===o||!(a=a.nextSibling)););else{bs.innerHTML=r==="svg"?`${e}`:r==="mathml"?`${e}`:e;const i=bs.content;if(r==="svg"||r==="mathml"){const c=i.firstChild;for(;c.firstChild;)i.appendChild(c.firstChild);i.removeChild(c)}t.insertBefore(i,n)}return[l?l.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},zt="transition",Qn="animation",Hn=Symbol("_vtc"),Rt=(e,{slots:t})=>s(a0,Tc(e),t);Rt.displayName="Transition";const kc={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Y0=Rt.props=Pe({},Xi,kc),fn=(e,t=[])=>{te(e)?e.forEach(n=>n(...t)):e&&e(...t)},Es=e=>e?te(e)?e.some(t=>t.length>1):e.length>1:!1;function Tc(e){const t={};for(const R in e)R in kc||(t[R]=e[R]);if(e.css===!1)return t;const{name:n="v",type:r,duration:a,enterFromClass:o=`${n}-enter-from`,enterActiveClass:l=`${n}-enter-active`,enterToClass:i=`${n}-enter-to`,appearFromClass:c=o,appearActiveClass:u=l,appearToClass:d=i,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,v=Z0(a),_=v&&v[0],E=v&&v[1],{onBeforeEnter:b,onEnter:C,onEnterCancelled:y,onLeave:A,onLeaveCancelled:D,onBeforeAppear:P=b,onAppear:H=C,onAppearCancelled:O=y}=t,Q=(R,X,Le)=>{Wt(R,X?d:i),Wt(R,X?u:l),Le&&Le()},I=(R,X)=>{R._isLeaving=!1,Wt(R,f),Wt(R,h),Wt(R,p),X&&X()},U=R=>(X,Le)=>{const Ae=R?H:C,W=()=>Q(X,R,Le);fn(Ae,[X,W]),_s(()=>{Wt(X,R?c:o),Dt(X,R?d:i),Es(Ae)||ws(X,r,_,W)})};return Pe(t,{onBeforeEnter(R){fn(b,[R]),Dt(R,o),Dt(R,l)},onBeforeAppear(R){fn(P,[R]),Dt(R,c),Dt(R,u)},onEnter:U(!1),onAppear:U(!0),onLeave(R,X){R._isLeaving=!0;const Le=()=>I(R,X);Dt(R,f),Dt(R,p),xc(),_s(()=>{R._isLeaving&&(Wt(R,f),Dt(R,h),Es(A)||ws(R,r,E,Le))}),fn(A,[R,Le])},onEnterCancelled(R){Q(R,!1),fn(y,[R])},onAppearCancelled(R){Q(R,!0),fn(O,[R])},onLeaveCancelled(R){I(R),fn(D,[R])}})}function Z0(e){if(e==null)return null;if(Be(e))return[Ua(e.enter),Ua(e.leave)];{const t=Ua(e);return[t,t]}}function Ua(e){return n2(e)}function Dt(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Hn]||(e[Hn]=new Set)).add(t)}function Wt(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[Hn];n&&(n.delete(t),n.size||(e[Hn]=void 0))}function _s(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let X0=0;function ws(e,t,n,r){const a=e._endId=++X0,o=()=>{a===e._endId&&r()};if(n)return setTimeout(o,n);const{type:l,timeout:i,propCount:c}=Sc(e,t);if(!l)return r();const u=l+"end";let d=0;const f=()=>{e.removeEventListener(u,p),o()},p=h=>{h.target===e&&++d>=c&&f()};setTimeout(()=>{d(n[v]||"").split(", "),a=r(`${zt}Delay`),o=r(`${zt}Duration`),l=As(a,o),i=r(`${Qn}Delay`),c=r(`${Qn}Duration`),u=As(i,c);let d=null,f=0,p=0;t===zt?l>0&&(d=zt,f=l,p=o.length):t===Qn?u>0&&(d=Qn,f=u,p=c.length):(f=Math.max(l,u),d=f>0?l>u?zt:Qn:null,p=d?d===zt?o.length:c.length:0);const h=d===zt&&/\b(transform|all)(,|$)/.test(r(`${zt}Property`).toString());return{type:d,timeout:f,propCount:p,hasTransform:h}}function As(e,t){for(;e.lengthCs(n)+Cs(e[r])))}function Cs(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function xc(){return document.body.offsetHeight}function ed(e,t,n){const r=e[Hn];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const ks=Symbol("_vod"),td=Symbol("_vsh"),nd=Symbol(""),rd=/(^|;)\s*display\s*:/;function ad(e,t,n){const r=e.style,a=Re(n);let o=!1;if(n&&!a){if(t)if(Re(t))for(const l of t.split(";")){const i=l.slice(0,l.indexOf(":")).trim();n[i]==null&&sa(r,i,"")}else for(const l in t)n[l]==null&&sa(r,l,"");for(const l in n)l==="display"&&(o=!0),sa(r,l,n[l])}else if(a){if(t!==n){const l=r[nd];l&&(n+=";"+l),r.cssText=n,o=rd.test(n)}}else t&&e.removeAttribute("style");ks in e&&(e[ks]=o?r.display:"",e[td]&&(r.display="none"))}const Ts=/\s*!important$/;function sa(e,t,n){if(te(n))n.forEach(r=>sa(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=od(e,t);Ts.test(n)?e.setProperty(jn(r),n.replace(Ts,""),"important"):e[r]=n}}const Ss=["Webkit","Moz","ms"],Wa={};function od(e,t){const n=Wa[t];if(n)return n;let r=qe(t);if(r!=="filter"&&r in e)return Wa[t]=r;r=_n(r);for(let a=0;aKa||(fd.then(()=>Ka=0),Ka=Date.now());function hd(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;ft(md(r,n.value),t,5,[r])};return n.value=e,n.attached=pd(),n}function md(e,t){if(te(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>a=>!a._stopped&&r&&r(a))}else return t}const Is=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,vd=(e,t,n,r,a,o,l,i,c)=>{const u=a==="svg";t==="class"?ed(e,r,u):t==="style"?ad(e,n,r):Sr(t)?Uo(t)||ud(e,t,n,r,l):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):gd(e,t,r,u))?sd(e,t,r,o,l,i,c):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),ld(e,t,r,u))};function gd(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t in e&&Is(t)&&re(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const a=e.tagName;if(a==="IMG"||a==="VIDEO"||a==="CANVAS"||a==="SOURCE")return!1}return Is(t)&&Re(n)?!1:t in e}const Lc=new WeakMap,Bc=new WeakMap,ga=Symbol("_moveCb"),Ps=Symbol("_enterCb"),Ic={name:"TransitionGroup",props:Pe({},Y0,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=ln(),r=Zi();let a,o;return rc(()=>{if(!a.length)return;const l=e.moveClass||`${e.name||"v"}-move`;if(!Ad(a[0].el,n.vnode.el,l))return;a.forEach(Ed),a.forEach(_d);const i=a.filter(wd);xc(),i.forEach(c=>{const u=c.el,d=u.style;Dt(u,l),d.transform=d.webkitTransform=d.transitionDuration="";const f=u[ga]=p=>{p&&p.target!==u||(!p||/transform$/.test(p.propertyName))&&(u.removeEventListener("transitionend",f),u[ga]=null,Wt(u,l))};u.addEventListener("transitionend",f)})}),()=>{const l=pe(e),i=Tc(l);let c=l.tag||nt;if(a=[],o)for(let u=0;udelete e.mode;Ic.props;const bd=Ic;function Ed(e){const t=e.el;t[ga]&&t[ga](),t[Ps]&&t[Ps]()}function _d(e){Bc.set(e,e.el.getBoundingClientRect())}function wd(e){const t=Lc.get(e),n=Bc.get(e),r=t.left-n.left,a=t.top-n.top;if(r||a){const o=e.el.style;return o.transform=o.webkitTransform=`translate(${r}px,${a}px)`,o.transitionDuration="0s",e}}function Ad(e,t,n){const r=e.cloneNode(),a=e[Hn];a&&a.forEach(i=>{i.split(/\s+/).forEach(c=>c&&r.classList.remove(c))}),n.split(/\s+/).forEach(i=>i&&r.classList.add(i)),r.style.display="none";const o=t.nodeType===1?t:t.parentNode;o.appendChild(r);const{hasTransform:l}=Sc(r);return o.removeChild(r),l}const Cd=Pe({patchProp:vd},Q0);let Ja,Ds=!1;function kd(){return Ja=Ds?Ja:B0(Cd),Ds=!0,Ja}const Td=(...e)=>{const t=kd().createApp(...e),{mount:n}=t;return t.mount=r=>{const a=xd(r);if(a)return n(a,!0,Sd(a))},t};function Sd(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function xd(e){return Re(e)?document.querySelector(e):e}var Ld=["link","meta","script","style","noscript","template"],Bd=["title","base"],Id=([e,t,n])=>Bd.includes(e)?e:Ld.includes(e)?e==="meta"&&t.name?`${e}.${t.name}`:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,Object.entries(t).map(([r,a])=>typeof a=="boolean"?a?[r,""]:null:[r,a]).filter(r=>r!=null).sort(([r],[a])=>r.localeCompare(a)),n]):null,Pd=e=>{const t=new Set,n=[];return e.forEach(r=>{const a=Id(r);a&&!t.has(a)&&(t.add(a),n.push(r))}),n},Dd=e=>e[0]==="/"?e:`/${e}`,Pc=e=>e[e.length-1]==="/"||e.endsWith(".html")?e:`${e}/`,sn=e=>/^(https?:)?\/\//.test(e),Od=/.md((\?|#).*)?$/,xa=(e,t="/")=>!!(sn(e)||e.startsWith("/")&&!e.startsWith(t)&&!Od.test(e)),La=e=>/^[a-z][a-z0-9+.-]*:/.test(e),qn=e=>Object.prototype.toString.call(e)==="[object Object]",Md=e=>{const[t,...n]=e.split(/(\?|#)/);if(!t||t.endsWith("/"))return e;let r=t.replace(/(^|\/)README.md$/i,"$1index.html");return r.endsWith(".md")?r=r.substring(0,r.length-3)+".html":r.endsWith(".html")||(r=r+".html"),r.endsWith("/index.html")&&(r=r.substring(0,r.length-10)),r+n.join("")},pl=e=>e[e.length-1]==="/"?e.slice(0,-1):e,Dc=e=>e[0]==="/"?e.slice(1):e,Rd=(e,t)=>{const n=Object.keys(e).sort((r,a)=>{const o=a.split("/").length-r.split("/").length;return o!==0?o:a.length-r.length});for(const r of n)if(t.startsWith(r))return r;return"/"},$d=e=>typeof e=="function",we=e=>typeof e=="string";const Fd="modulepreload",Hd=function(e){return"/"+e},Os={},T=function(t,n,r){let a=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),l=(o==null?void 0:o.nonce)||(o==null?void 0:o.getAttribute("nonce"));a=Promise.all(n.map(i=>{if(i=Hd(i),i in Os)return;Os[i]=!0;const c=i.endsWith(".css"),u=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${i}"]${u}`))return;const d=document.createElement("link");if(d.rel=c?"stylesheet":Fd,c||(d.as="script",d.crossOrigin=""),d.href=i,l&&d.setAttribute("nonce",l),document.head.appendChild(d),c)return new Promise((f,p)=>{d.addEventListener("load",f),d.addEventListener("error",()=>p(new Error(`Unable to preload CSS for ${i}`)))})}))}return a.then(()=>t()).catch(o=>{const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=o,window.dispatchEvent(l),!l.defaultPrevented)throw o})},Nd=JSON.parse('{"/tech/docker+nginx%E9%83%A8%E7%BD%B2web%E9%A1%B9%E7%9B%AE.html":"/tech/docker_nginx%E9%83%A8%E7%BD%B2web%E9%A1%B9%E7%9B%AE.html"}'),Vd=Object.fromEntries([["/",{loader:()=>T(()=>import("./index.html-Bq7J9cN0.js"),[]),meta:{t:"主页",i:"home"}}],["/intro.html",{loader:()=>T(()=>import("./intro.html-DcelwrZW.js"),[]),meta:{t:"介绍页",i:"circle-info"}}],["/essays/",{loader:()=>T(()=>import("./index.html-CdwZ1P48.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:`
     

    这里是所有个人感想、生活记录等非技术类文章。

    -`,r:{minutes:.13,words:39},t:"随笔",y:"a"}}],["/essays/%E5%91%BD%E9%87%8C%E6%9C%89%E6%97%B6%E7%BB%88%E9%A1%BB%E6%9C%89.html",{loader:()=>T(()=>import("./命里有时终须有.html-DOvT7-xg.js"),[]),meta:{d:17302464e5,l:"2024年10月30日",e:`
    alt text
    +`,r:{minutes:.13,words:40},t:"随笔",y:"a"}}],["/essays/%E5%91%BD%E9%87%8C%E6%9C%89%E6%97%B6%E7%BB%88%E9%A1%BB%E6%9C%89.html",{loader:()=>T(()=>import("./命里有时终须有.html-B46pNP68.js"),[]),meta:{d:17302464e5,l:"2024年10月30日",e:`
    alt text

    昨日在玩“三国杀:一将成名”时,我抽奖一发就中了大奖——“清河公主”传说皮。这让我不禁感慨:“命里有时终须有,命里无时莫强求。”这句话真是道出了人生的真谛,有些事情是强求不来的,顺其自然反而能得到意想不到的收获。

    -`,r:{minutes:.35,words:104},t:"命里有时终须有",y:"a"}}],["/projects/",{loader:()=>T(()=>import("./index.html-BjDh3PHW.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:` +`,r:{minutes:.35,words:104},t:"命里有时终须有",y:"a"}}],["/projects/carrobot.html",{loader:()=>T(()=>import("./carrobot.html-B7set7IG.js"),[]),meta:{d:17309376e5,l:"2024年11月7日",e:`

    项目源码

    +

    因为想要创新学分,所以选了创新创业智能车课程,项目要求完成智能车的循迹和避障。

    +

    由于笔者不喜欢使用VPL的图形化(报用qWq), 故而使用手写代码的方式实现。

    +`,r:{minutes:4.03,words:1210},t:"北京邮电大学创新创业智能车循迹python版",y:"a"}}],["/projects/",{loader:()=>T(()=>import("./index.html-jwKIpsPi.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:`

    这里是你参与或开发的项目介绍和进展。

    -`,r:{minutes:.12,words:36},t:"项目",y:"a"}}],["/resources/",{loader:()=>T(()=>import("./index.html-Dv6rE_pN.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:` +`,r:{minutes:.12,words:37},t:"项目",y:"a"}}],["/resources/",{loader:()=>T(()=>import("./index.html-DtPJaw_H.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:`

    这里是推荐的书籍、网站、工具等资源。

    -`,r:{minutes:.58,words:175},t:"资源",y:"a"}}],["/tech/Qt6%20Maintence%20tools%E4%B8%8D%E8%83%BD%E5%AE%89%E8%A3%85Qt5.html",{loader:()=>T(()=>import("./Qt6 Maintence tools不能安装Qt5.html-CTk8exni.js"),[]),meta:{d:17250624e5,l:"2024年8月31日",e:` +`,r:{minutes:.58,words:175},t:"资源",y:"a"}}],["/tech/Qt6%20Maintence%20tools%E4%B8%8D%E8%83%BD%E5%AE%89%E8%A3%85Qt5.html",{loader:()=>T(()=>import("./Qt6 Maintence tools不能安装Qt5.html-DdU1Gd4z.js"),[]),meta:{d:17250624e5,l:"2024年8月31日",e:`

    Some Solutions to Questions about Qt

    The qt-5 isn't in Qt Maintenance tools

      @@ -30,60 +33,60 @@
    1. click the Archive in the Select-Component section.
    2. click the filter button and you can see the qt-5 in the list.
    -`,r:{minutes:.18,words:54},t:"Qt",y:"a"}}],["/tech/android%20studio%E6%8D%A2%E6%BA%90.html",{loader:()=>T(()=>import("./android studio换源.html-CxjDu5df.js"),[]),meta:{d:17225568e5,l:"2024年8月2日",c:["build-tools"],e:` +`,r:{minutes:.18,words:54},t:"Qt",y:"a"}}],["/tech/android%20studio%E6%8D%A2%E6%BA%90.html",{loader:()=>T(()=>import("./android studio换源.html-DkRIM6mj.js"),[]),meta:{d:17225568e5,l:"2024年8月2日",c:["build-tools"],e:`

    国内镜像

    Android Studio下载gradle太慢可换源

    android.plugin version 下载错误查看

    -

    修改 settings.gradle.kts

    `,r:{minutes:.46,words:139},t:"android studio换源",y:"a"}}],["/tech/cicd.html",{loader:()=>T(()=>import("./cicd.html-CedraPRV.js"),[]),meta:{d:17225568e5,l:"2024年8月2日",c:["CI/CD"],g:["CI/CD"],e:` +

    修改 settings.gradle.kts

    `,r:{minutes:.46,words:139},t:"android studio换源",y:"a"}}],["/tech/cicd.html",{loader:()=>T(()=>import("./cicd.html-BxLHD3yn.js"),[]),meta:{d:17225568e5,l:"2024年8月2日",c:["CI/CD"],g:["CI/CD"],e:`

    本地调试工具 act

    需要创建配置文件

    也可另外指定

    act --var-file "./.act/.vars" --secret-file "./.act/.secrets" --env-file "./.act/.env" {{other}}
    -
    `,r:{minutes:.16,words:48},t:"CI/CD",y:"a"}}],["/tech/docker_nginx%E9%83%A8%E7%BD%B2web%E9%A1%B9%E7%9B%AE.html",{loader:()=>T(()=>import("./docker_nginx部署web项目.html-4IxhKq9E.js"),[]),meta:{a:"King-sj",d:17264448e5,l:"2024年9月16日",c:["nginx"],g:["部署"],e:` +
    `,r:{minutes:.16,words:48},t:"CI/CD",y:"a"}}],["/tech/docker_nginx%E9%83%A8%E7%BD%B2web%E9%A1%B9%E7%9B%AE.html",{loader:()=>T(()=>import("./docker_nginx部署web项目.html-CZONKQdf.js"),[]),meta:{a:"King-sj",d:17264448e5,l:"2024年9月16日",c:["nginx"],g:["部署"],e:`

    本文将介绍如何使用 Docker、Nginx 和 acme.sh 部署一个 Vue 和 Flask 项目。我们将详细讲解环境配置、项目结构、Nginx 配置、Docker 配置以及如何升级到 HTTPS。

    -`,r:{minutes:2.63,words:789},t:"docker + nginx + acme.sh 部署 vue/flask 项目",y:"a"}}],["/tech/docker.html",{loader:()=>T(()=>import("./docker.html-B-hYGcsZ.js"),[]),meta:{d:17260992e5,l:"2024年9月12日",c:["奇技淫巧","docker"],g:["docker"],e:`

    GitHub Container Registry 加速

    +`,r:{minutes:2.63,words:789},t:"docker + nginx + acme.sh 部署 vue/flask 项目",y:"a"}}],["/tech/docker.html",{loader:()=>T(()=>import("./docker.html-DtdutZB7.js"),[]),meta:{d:17260992e5,l:"2024年9月12日",c:["奇技淫巧","docker"],g:["docker"],e:`

    GitHub Container Registry 加速

    ghcr.io简介

    • ghcr.io 是 GitHub Container Registry 的域名。GitHub Container Registry 是 GitHub 提供的容器镜像注册表服务,允许开发者在 GitHub 上存储、管理和分享 Docker 镜像。它与 GitHub 代码仓库紧密集成,可以使用相同的权限管理、团队协作和版本控制工具来管理容器镜像。
    • 通过 GitHub Container Registry,开发者可以方便地将他们的容器镜像与代码仓库关联起来,这样就可以在同一个平台上管理代码和镜像。这种集成性使得持续集成/持续交付 (CI/CD) 流程更加简化和统一,开发团队可以更容易地构建、测试和部署应用程序。
    • 对于使用 GitHub 作为代码托管平台的开发者来说,GitHub Container Registry 提供一个便捷且强大的容器镜像管理解决方案。通过该服务,可以更轻松地构建和部署容器化的应用程序,从而加速开发和交付周期。
    • -
    `,r:{minutes:1.11,words:334},t:"",y:"a"}}],["/tech/figure_bed.html",{loader:()=>T(()=>import("./figure_bed.html-BME-hebA.js"),[]),meta:{d:17304192e5,l:"2024年11月1日",e:`

    参考

    +`,r:{minutes:1.11,words:334},t:"",y:"a"}}],["/tech/figure_bed.html",{loader:()=>T(()=>import("./figure_bed.html-vyc89ngd.js"),[]),meta:{d:17304192e5,l:"2024年11月1日",e:`

    参考

    • https://zhuanlan.zhihu.com/p/347342082
    -`,r:{minutes:.04,words:12},t:"github 图床",y:"a"}}],["/tech/github%20%E5%B7%A5%E4%BD%9C%E6%B5%81.html",{loader:()=>T(()=>import("./github 工作流.html-CFTCrRk5.js"),[]),meta:{e:` +`,r:{minutes:.04,words:12},t:"github 图床",y:"a"}}],["/tech/github%20%E5%B7%A5%E4%BD%9C%E6%B5%81.html",{loader:()=>T(()=>import("./github 工作流.html-DJORqAx-.js"),[]),meta:{e:`

    十分钟学会正确的github工作流,和开源作者们使用同一套流程

    相关信息

    TODO

    -`,r:{minutes:.11,words:34},t:"github 工作流程",y:"a"}}],["/tech/",{loader:()=>T(()=>import("./index.html-Dakgazin.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:` +`,r:{minutes:.11,words:34},t:"github 工作流程",y:"a"}}],["/tech/",{loader:()=>T(()=>import("./index.html-CqYdKjYM.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:`

    这里是所有与技术相关的文章,包括编程、工具使用、技术分享等内容。

    -`,r:{minutes:.16,words:49},t:"技术",y:"a"}}],["/tech/mailserver.html",{loader:()=>T(()=>import("./mailserver.html-DITZCknd.js"),[]),meta:{d:17304192e5,l:"2024年11月1日",e:`

    参见:

    +`,r:{minutes:.16,words:49},t:"技术",y:"a"}}],["/tech/mailserver.html",{loader:()=>T(()=>import("./mailserver.html-DO5Xbwgi.js"),[]),meta:{d:17304192e5,l:"2024年11月1日",e:`

    参见:

    • https://docker-mailserver.github.io/docker-mailserver/latest/
    • https://blog.csdn.net/qq_25866579/article/details/140717115
    • https://zhuanlan.zhihu.com/p/609639797

    值得注意的是docker-mailserver 并不支持通过ui注册/修改等操作, 当可以自行实现(后端通过shell命令即可

    -

    使用python 发送示例

    `,r:{minutes:.55,words:164},t:"自建mail server",y:"a"}}],["/tech/modint.html",{loader:()=>T(()=>import("./modint.html-M2GwLZ2U.js"),[]),meta:{d:16909344e5,l:"2023年8月2日",c:["OI"],g:["MODInt"],e:` +

    使用python 发送示例

    `,r:{minutes:.55,words:164},t:"自建mail server",y:"a"}}],["/tech/modint.html",{loader:()=>T(()=>import("./modint.html-DNrISIPe.js"),[]),meta:{d:16909344e5,l:"2023年8月2日",c:["OI"],g:["MODInt"],e:`

    在 OI 中,有大量的题目要求对一些数字取模,这便是本文写作的背景。

    背景介绍

    这些题目要么是因为答案太大,不方便输出结果,例如许多计数 dp;要么是因为答案是浮点数,出题人不愿意写一个确定精度的 Special Judge,例如很多期望概率题;要么是因为这道题目直接考察了模的性质和运用,比如大量的 998244353 类的多项式题目。

    -`,r:{minutes:2.4,words:721},t:"「泛型与 OI」modint",y:"a"}}],["/tech/python_requrements.html",{loader:()=>T(()=>import("./python_requrements.html-Va55tBYB.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["python"],g:["踩坑记录"],e:`

    Python 生成 requirements有两种方式

    +`,r:{minutes:2.4,words:721},t:"「泛型与 OI」modint",y:"a"}}],["/tech/python_requrements.html",{loader:()=>T(()=>import("./python_requrements.html-D_iboyN4.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["python"],g:["踩坑记录"],e:`

    Python 生成 requirements有两种方式

    • pip list --format=freeze > requirements.txt (推荐做法)
    • pip freeze > requirements.txt (不推荐,会带有本地路径)
    -`,r:{minutes:.15,words:45},t:"python requirements.txt",y:"a"}}],["/tech/steam%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91.html",{loader:()=>T(()=>import("./steam模组开发.html-DQKsmuFu.js"),[]),meta:{d:172584e7,l:"2024年9月9日",e:`

    This is the content of the game plugin post.

    +`,r:{minutes:.15,words:45},t:"python requirements.txt",y:"a"}}],["/tech/steam%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91.html",{loader:()=>T(()=>import("./steam模组开发.html-BnsNbJlM.js"),[]),meta:{d:172584e7,l:"2024年9月9日",e:`

    This is the content of the game plugin post.

    `,r:{minutes:.12,words:36},t:"Game Plugin",y:"a"}}],["/tech/style.html",{loader:()=>T(()=>import("./style.html-CXxr9xzn.js"),[]),meta:{d:17245872e5,l:"2024年8月25日",c:"风格",g:"风格",e:` +`,r:{minutes:.12,words:36},t:"Game Plugin",y:"a"}}],["/tech/style.html",{loader:()=>T(()=>import("./style.html-4qtN5eOW.js"),[]),meta:{d:17245872e5,l:"2024年8月25日",c:"风格",g:"风格",e:`

    如何实现风格统一? 可以使用*.editorconfig*文件来实现风格统一。

      @@ -98,8 +101,8 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -
    `,r:{minutes:.31,words:94},t:"风格统一",y:"a"}}],["/tech/tensorflow%E8%B8%A9%E5%9D%91.html",{loader:()=>T(()=>import("./tensorflow踩坑.html-BAaphJ4H.js"),[]),meta:{d:17304192e5,l:"2024年11月1日",e:`

    Pytorch 用于快速原型验证(学术研究), TensorFlow 用于工业生产部署

    -`,r:{minutes:.79,words:236},t:"tensorflow 踩坑记录",y:"a"}}],["/tech/vsocde%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%B0%8F%E8%AE%B0.html",{loader:()=>T(()=>import("./vsocde插件开发小记.html-DtdCKw5q.js"),[]),meta:{e:` +
    `,r:{minutes:.31,words:94},t:"风格统一",y:"a"}}],["/tech/tensorflow%E8%B8%A9%E5%9D%91.html",{loader:()=>T(()=>import("./tensorflow踩坑.html-CtBHw7dX.js"),[]),meta:{d:17304192e5,l:"2024年11月1日",e:`

    Pytorch 用于快速原型验证(学术研究), TensorFlow 用于工业生产部署

    +`,r:{minutes:.79,words:236},t:"tensorflow 踩坑记录",y:"a"}}],["/tech/vsocde%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%B0%8F%E8%AE%B0.html",{loader:()=>T(()=>import("./vsocde插件开发小记.html-BytDpa8J.js"),[]),meta:{e:`

    // TODO https://code.visualstudio.com/api/get-started/your-first-extension

    tips

    @@ -107,19 +110,19 @@ https://code.visualstudio.com/api/get-started/your-first-extension

  • 若activate function执行时间过长,会导致Activating extension 'undefined_publisher.kcodetime' failed: AggregateError., 从而启动失败
  • 若deactivate function执行时间超过5s, 会被强行终止,导致插件无法正常退出。
  • 关闭vscode 不会触发onDidCloseTextDocument事件
  • -`,r:{minutes:.23,words:69},t:"vscode plugin",y:"a"}}],["/tech/%E3%80%8C%E7%AE%97%E6%9C%AF%E5%85%AC%E7%90%86%E7%B3%BB%E7%BB%9F%201%E3%80%8D%E8%87%AA%E7%84%B6%E6%95%B0.html",{loader:()=>T(()=>import("./「算术公理系统 1」自然数.html-CtzEDcR8.js"),[]),meta:{d:16888608e5,l:"2023年7月9日",c:["数学"],g:["公理系统"],e:` +`,r:{minutes:.23,words:69},t:"vscode plugin",y:"a"}}],["/tech/%E3%80%8C%E7%AE%97%E6%9C%AF%E5%85%AC%E7%90%86%E7%B3%BB%E7%BB%9F%201%E3%80%8D%E8%87%AA%E7%84%B6%E6%95%B0.html",{loader:()=>T(()=>import("./「算术公理系统 1」自然数.html-NHamcIiY.js"),[]),meta:{d:16888608e5,l:"2023年7月9日",c:["数学"],g:["公理系统"],e:`

    假设存在一个算数系统的模型满足 Peano 公理,即假定 Peano 公理相容,在此承认次假设的基础之上,我们即可建立如今最常用的算术公理系统自然数的定义则是构建此算术公理系统的第一步。

    -`,r:{minutes:18.04,words:5412},t:"「算术公理系统 1」自然数",y:"a"}}],["/tech/%E4%BD%BF%E7%94%A8capacitor%E5%92%8Cionic%E5%B0%86vue%E9%A1%B9%E7%9B%AE%E8%BF%81%E7%A7%BB%E5%88%B0mobile%E7%AB%AF.html",{loader:()=>T(()=>import("./使用capacitor和ionic将vue项目迁移到mobile端.html-CjY4pFX9.js"),[]),meta:{g:["mobile"],e:` +`,r:{minutes:18.04,words:5412},t:"「算术公理系统 1」自然数",y:"a"}}],["/tech/%E4%BD%BF%E7%94%A8capacitor%E5%92%8Cionic%E5%B0%86vue%E9%A1%B9%E7%9B%AE%E8%BF%81%E7%A7%BB%E5%88%B0mobile%E7%AB%AF.html",{loader:()=>T(()=>import("./使用capacitor和ionic将vue项目迁移到mobile端.html-CKHKQibh.js"),[]),meta:{g:["mobile"],e:`

    当我们写完了vue项目后,想做一个一样的移动app,此时我们可以使用ionic无缝将其迁移到移动端。

    -`,r:{minutes:.73,words:218},t:"使用capacitor和ionic将vue项目迁移到mobile端",y:"a"}}],["/tech/%E5%8D%8F%E7%A8%8B(asyncio)%E5%88%B0%E5%BA%95%E9%9C%80%E4%B8%8D%E9%9C%80%E8%A6%81%E5%8A%A0%E9%94%81.html",{loader:()=>T(()=>import("./协程(asyncio)到底需不需要加锁.html-CTO2grll.js"),[]),meta:{e:` +`,r:{minutes:.73,words:218},t:"使用capacitor和ionic将vue项目迁移到mobile端",y:"a"}}],["/tech/%E5%8D%8F%E7%A8%8B(asyncio)%E5%88%B0%E5%BA%95%E9%9C%80%E4%B8%8D%E9%9C%80%E8%A6%81%E5%8A%A0%E9%94%81.html",{loader:()=>T(()=>import("./协程(asyncio)到底需不需要加锁.html-gfcHinKw.js"),[]),meta:{e:`

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_208

    -`,r:{minutes:10.04,words:3013},t:"并发异步编程之争:协程(asyncio)到底需不需要加锁?(线程/协程安全/挂起/主动切换)Python3",y:"a"}}],["/tech/%E5%8D%8F%E7%A8%8B.html",{loader:()=>T(()=>import("./协程.html-LdIMvjSl.js"),[]),meta:{e:` +`,r:{minutes:10.04,words:3013},t:"并发异步编程之争:协程(asyncio)到底需不需要加锁?(线程/协程安全/挂起/主动切换)Python3",y:"a"}}],["/tech/%E5%8D%8F%E7%A8%8B.html",{loader:()=>T(()=>import("./协程.html-BovqfV_l.js"),[]),meta:{e:`

    文档

    重要

    TODO

    -`,r:{minutes:.06,words:18},t:"协程",y:"a"}}],["/tech/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84PPT.html",{loader:()=>T(()=>import("./程序员的PPT.html-CmGk8DAJ.js"),[]),meta:{e:` +`,r:{minutes:.06,words:18},t:"协程",y:"a"}}],["/tech/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84PPT.html",{loader:()=>T(()=>import("./程序员的PPT.html-D4bbFarZ.js"),[]),meta:{e:`

    复杂演示推荐

    缺点

    不支持导出pptx

    @@ -133,26 +136,31 @@ https://code.visualstudio.com/api/get-started/your-first-extension

    https://sspai.com/post/55718

    https://segmentfault.com/a/1190000040806239

    ⚠️注意事项 -如果你的 VS Code 安装了 Markdown 渲染插件 Markdown Preview Enhanced,需要先将这个插件「禁用」或是「卸载」,转而使用 VS Code 后来集成的 Markdown 预览功能,才能正常看到渲染后的 PPT 页面。

    `,r:{minutes:.35,words:105},t:"Slidev",y:"a"}}],["/tech/%E9%97%AD%E5%8C%85%E5%AE%9E%E7%8E%B0%E7%B1%BB.html",{loader:()=>T(()=>import("./闭包实现类.html-DjTAmLiV.js"),[]),meta:{d:16909344e5,l:"2023年8月2日",c:["OI"],g:["MODInt"],e:` +如果你的 VS Code 安装了 Markdown 渲染插件 Markdown Preview Enhanced,需要先将这个插件「禁用」或是「卸载」,转而使用 VS Code 后来集成的 Markdown 预览功能,才能正常看到渲染后的 PPT 页面。

    `,r:{minutes:.35,words:105},t:"Slidev",y:"a"}}],["/tech/%E9%97%AD%E5%8C%85%E5%AE%9E%E7%8E%B0%E7%B1%BB.html",{loader:()=>T(()=>import("./闭包实现类.html-CeWxsqOK.js"),[]),meta:{d:16909344e5,l:"2023年8月2日",c:["OI"],g:["MODInt"],e:`

    你是否曾经想过,不用类也能封装数据和方法?这篇文章将带你走进闭包的奇妙世界,展示如何用闭包来实现这一点。通过一个有趣的代码示例,你将看到闭包是如何捕获变量并提供类似类的功能的。准备好了吗?让我们开始这段有趣的旅程吧!

    -`,r:{minutes:1.64,words:491},t:"闭包实现类",y:"a"}}],["/tutorials/",{loader:()=>T(()=>import("./index.html-BtJnl_OK.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:` +`,r:{minutes:1.64,words:491},t:"闭包实现类",y:"a"}}],["/tutorials/",{loader:()=>T(()=>import("./index.html-C9AeqWIc.js"),[]),meta:{d:17292096e5,l:"2024年10月18日",e:`

    这里是所有详细的教程和指南。

    -`,r:{minutes:.12,words:35},t:"教程",y:"a"}}],["/tutorials/typora%E6%BF%80%E6%B4%BB.html",{loader:()=>T(()=>import("./typora激活.html-BuwKOD8g.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",g:["激活"],e:` +`,r:{minutes:.12,words:35},t:"教程",y:"a"}}],["/tutorials/typora%E6%BF%80%E6%B4%BB.html",{loader:()=>T(()=>import("./typora激活.html-DUOLfN6Y.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",g:["激活"],e:`

    Typora 是一款简洁高效的 Markdown 编辑器,支持实时预览和多种导出格式,适用于编写笔记、文档和博客等。

    安装教程

    -`,r:{minutes:.82,words:247},t:"Typora激活教程",y:"a"}}],["/resources/html2md/",{loader:()=>T(()=>import("./index.html-CXhg3e64.js"),[]),meta:{e:` +`,r:{minutes:.82,words:247},t:"Typora激活教程",y:"a"}}],["/resources/compile/",{loader:()=>T(()=>import("./index.html-ybZqFA4D.js"),[]),meta:{d:17308512e5,l:"2024年11月6日",e:`
      +
    • anltr.grammar
    • +
    • bison/yacc
    • +
    • flex
    • +
    +`,r:{minutes:.05,words:14},t:"编译原理相关",y:"a"}}],["/resources/html2md/",{loader:()=>T(()=>import("./index.html-BlsqD0oa.js"),[]),meta:{e:`
    • https://github.com/mixmark-io/turndown
    • https://github.com/aaronsw/html2text
    -`,r:{minutes:.03,words:10},t:"html 转 md 工具",y:"a"}}],["/tech/DesignPatterns/DesignPrinciples.html",{loader:()=>T(()=>import("./DesignPrinciples.html-BLnXHGN8.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript"],e:`

    类的设计原则有七个,包括:开闭原则、里氏代换原则、迪米特原则(最少知道原则)、单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则。

    -`,r:{minutes:6.23,words:1868},t:"设计模式七大原则",y:"a"}}],["/tech/DesignPatterns/abstractFactory.html",{loader:()=>T(()=>import("./abstractFactory.html-DS3CgVqp.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","typescript","生成实例"],e:`

    为什么要使用抽象工厂模式

    +`,r:{minutes:.03,words:10},t:"html 转 md 工具",y:"a"}}],["/tech/DesignPatterns/DesignPrinciples.html",{loader:()=>T(()=>import("./DesignPrinciples.html-BAjp9viD.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript"],e:`

    类的设计原则有七个,包括:开闭原则、里氏代换原则、迪米特原则(最少知道原则)、单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则。

    +`,r:{minutes:6.23,words:1868},t:"设计模式七大原则",y:"a"}}],["/tech/DesignPatterns/abstractFactory.html",{loader:()=>T(()=>import("./abstractFactory.html-D_oh5f2L.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","typescript","生成实例"],e:`

    为什么要使用抽象工厂模式

    抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而无需明确指定具体类。它通过定义一个创建对象的接口来实现,这样子类可以决定实例化哪个类。抽象工厂模式使得一个类的实例化延迟到其子类。

    -`,r:{minutes:4.31,words:1293},t:"抽象工厂模式",y:"a"}}],["/tech/DesignPatterns/adapter.html",{loader:()=>T(()=>import("./adapter.html-B2UlYWFL.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript"],e:`

    适配器模式

    +`,r:{minutes:4.31,words:1293},t:"抽象工厂模式",y:"a"}}],["/tech/DesignPatterns/adapter.html",{loader:()=>T(()=>import("./adapter.html-x6kHt0VN.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript"],e:`

    适配器模式

    适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

    为什么要用适配器模式

    在软件开发中,经常会遇到需要使用一些现有的类,但它们的接口并不符合当前系统的需求。适配器模式通过创建一个适配器类,将现有类的接口转换为所需的接口,从而使得现有类可以在新的环境中使用。

    @@ -183,47 +191,47 @@ https://code.visualstudio.com/api/get-started/your-first-extension

    Print -`,r:{minutes:1.52,words:456},t:"适配器模式",y:"a"}}],["/tech/DesignPatterns/bridge.html",{loader:()=>T(()=>import("./bridge.html-LTDknqB7.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","分开考虑"],e:`

    Bridge 模式在“类的功能层次结构”和“类的实现层次结构”之间搭建桥梁。

    -`,r:{minutes:5.76,words:1728},t:"Bridge 模式",y:"a"}}],["/tech/DesignPatterns/builder.html",{loader:()=>T(()=>import("./builder.html-Ba33pTyP.js"),[]),meta:{d:17295552e5,l:"2024年10月22日",c:["设计模式"],g:["设计模式","typescript","生成实例"],e:`

    为什么要使用 Builder 模式

    +`,r:{minutes:1.52,words:456},t:"适配器模式",y:"a"}}],["/tech/DesignPatterns/bridge.html",{loader:()=>T(()=>import("./bridge.html-DzkNbynT.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","分开考虑"],e:`

    Bridge 模式在“类的功能层次结构”和“类的实现层次结构”之间搭建桥梁。

    +`,r:{minutes:5.76,words:1728},t:"Bridge 模式",y:"a"}}],["/tech/DesignPatterns/builder.html",{loader:()=>T(()=>import("./builder.html-B7MALN7e.js"),[]),meta:{d:17295552e5,l:"2024年10月22日",c:["设计模式"],g:["设计模式","typescript","生成实例"],e:`

    为什么要使用 Builder 模式

    Builder 模式通过将对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。它主要用于以下情况:

    1. 复杂对象的创建:当一个对象的构建过程非常复杂时,Builder 模式可以将构建过程分解为多个步骤,使代码更易于维护和理解。
    2. 不同的表示:当需要创建不同表示的对象时,Builder 模式允许使用相同的构建过程来生成不同的对象表示。
    3. 代码复用:通过将构建过程封装在 Director 类中,可以在不同的上下文中重用相同的构建逻辑。
    4. -
    `,r:{minutes:4.44,words:1333},t:"builder 模式",y:"a"}}],["/tech/DesignPatterns/chainOfResponsibility.html",{loader:()=>T(()=>import("./chainOfResponsibility.html-DhJtx4gW.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","推卸责任"],e:`

    “推卸责任"听起来有些贬义的意思,但是有时候也确实存在需要“推卸责任"的情况。例如,当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任。这种情况下,我们可以考虑将多个对象组成一条职责链,然后按照它们在职责链上的顺序一个一个地找出到底应该谁来负责处理。

    -`,r:{minutes:3.27,words:980},t:"chainOfResponsibility 模式",y:"a"}}],["/tech/DesignPatterns/command.html",{loader:()=>T(()=>import("./command.html-d4IhdjVF.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["设计模式"],g:["设计模式","TypeScript","用类实现"],e:`

    为什么使用此模式

    +`,r:{minutes:4.44,words:1333},t:"builder 模式",y:"a"}}],["/tech/DesignPatterns/chainOfResponsibility.html",{loader:()=>T(()=>import("./chainOfResponsibility.html-CpbeYqOk.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","推卸责任"],e:`

    “推卸责任"听起来有些贬义的意思,但是有时候也确实存在需要“推卸责任"的情况。例如,当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任。这种情况下,我们可以考虑将多个对象组成一条职责链,然后按照它们在职责链上的顺序一个一个地找出到底应该谁来负责处理。

    +`,r:{minutes:3.27,words:980},t:"chainOfResponsibility 模式",y:"a"}}],["/tech/DesignPatterns/command.html",{loader:()=>T(()=>import("./command.html-Bzqk5uGA.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["设计模式"],g:["设计模式","TypeScript","用类实现"],e:`

    为什么使用此模式

    Command 模式将请求封装成对象,使得可以用不同的请求、队列或者日志来参数化其他对象。Command 模式也支持可撤销的操作。

    示例代码

    -`,r:{minutes:2.91,words:874},t:"Command 模式",y:"a"}}],["/tech/DesignPatterns/composite.html",{loader:()=>T(()=>import("./composite.html-DOBxYen3.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","容器与内容的一致性"],e:`

    使用此设计模式的理由

    +`,r:{minutes:2.91,words:874},t:"Command 模式",y:"a"}}],["/tech/DesignPatterns/composite.html",{loader:()=>T(()=>import("./composite.html-D89dw1yo.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","容器与内容的一致性"],e:`

    使用此设计模式的理由

    Composite 模式允许你将对象组合成递归结构来表示“部分-整体”的层次结构。使用 Composite 模式,用户可以统一地对待单个对象和组合对象。例如,在文件系统中,目录和文件都可以被视为条目(Entry),目录可以包含其他目录和文件,而文件则是叶子节点。

    示例代码

    -`,r:{minutes:1.78,words:533},t:"Composite 模式",y:"a"}}],["/tech/DesignPatterns/decorator.html",{loader:()=>T(()=>import("./decorator.html-FfT8RlFX.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","容器与内容的一致性"],e:`

    在这个示例中,我们使用装饰器模式来动态地给对象添加职责。装饰器模式允许我们通过将对象放入包含行为的特殊封装对象中来扩展对象的功能,而无需修改原始类的代码。

    -`,r:{minutes:5.79,words:1737},t:"Decorator 模式",y:"a"}}],["/tech/DesignPatterns/end.html",{loader:()=>T(()=>import("./end.html-De4ifiEs.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["设计模式"],g:["设计模式","TypeScript"],e:`

    教程源码示例

    +`,r:{minutes:1.78,words:533},t:"Composite 模式",y:"a"}}],["/tech/DesignPatterns/decorator.html",{loader:()=>T(()=>import("./decorator.html-0jwtowLH.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","容器与内容的一致性"],e:`

    在这个示例中,我们使用装饰器模式来动态地给对象添加职责。装饰器模式允许我们通过将对象放入包含行为的特殊封装对象中来扩展对象的功能,而无需修改原始类的代码。

    +`,r:{minutes:5.79,words:1737},t:"Decorator 模式",y:"a"}}],["/tech/DesignPatterns/end.html",{loader:()=>T(()=>import("./end.html-CkqhlgwJ.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["设计模式"],g:["设计模式","TypeScript"],e:`

    教程源码示例

    使用设计模式的目的是重用代码,并在增加需求时尽量不修改已有的类。我们不应死记硬背这些设计模式,而应理解其背后的核心思想。设计模式的使用不是最终目的,而是为了更好地解决问题和优化代码结构。同时,我们也要避免过早地进行“工程化”,应根据实际需求灵活应用设计模式。

    -

    设计模式提供了一种通用的解决方案,可以帮助我们应对软件开发中的常见问题。通过学习和应用设计模式,我们可以提高代码的可读性、可维护性和可扩展性。然而,设计模式并不是万能的,它们也有其局限性和适用范围。在实际开发中,我们需要根据具体情况选择合适的设计模式,而不是盲目地套用。

    `,r:{minutes:1.48,words:445},t:"结语",y:"a"}}],["/tech/DesignPatterns/facade.html",{loader:()=>T(()=>import("./facade.html-Cj3lyBcS.js"),[]),meta:{d:17299008e5,l:"2024年10月26日",c:["设计模式"],g:["设计模式","TypeScript","简单化"],e:`

    当某个程序员得意地说出"啊,在调用那个类之前需要先调用这个类。在调用那个方法之前需要先在这个类中注册一下"的时候,就意味着我们需要引人Facade了。

    +

    设计模式提供了一种通用的解决方案,可以帮助我们应对软件开发中的常见问题。通过学习和应用设计模式,我们可以提高代码的可读性、可维护性和可扩展性。然而,设计模式并不是万能的,它们也有其局限性和适用范围。在实际开发中,我们需要根据具体情况选择合适的设计模式,而不是盲目地套用。

    `,r:{minutes:1.48,words:445},t:"结语",y:"a"}}],["/tech/DesignPatterns/facade.html",{loader:()=>T(()=>import("./facade.html-BODpRH45.js"),[]),meta:{d:17299008e5,l:"2024年10月26日",c:["设计模式"],g:["设计模式","TypeScript","简单化"],e:`

    当某个程序员得意地说出"啊,在调用那个类之前需要先调用这个类。在调用那个方法之前需要先在这个类中注册一下"的时候,就意味着我们需要引人Facade了。

    对于那些能够明确地用语言描述出来的知识,我们不应该将它们隐藏在自己脑袋中,而是应该用代码将它们表现出来。

    —————《图解设计模式》

    -`,r:{minutes:2.26,words:679},t:"facade 模式",y:"a"}}],["/tech/DesignPatterns/factory_method.html",{loader:()=>T(()=>import("./factory_method.html-B20phxz9.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript","工厂方法","交给子类"],e:`

    在 Factory Method 模式中,我们定义一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。Factory Method 使一个类的实例化延迟到其子类。

    -`,r:{minutes:1.67,words:501},t:"Factory Method 模式",y:"a"}}],["/tech/DesignPatterns/flyweight.html",{loader:()=>T(()=>import("./flyweight.html-CWx8TgTS.js"),[]),meta:{d:17300736e5,l:"2024年10月28日",c:["设计模式"],g:["设计模式","TypeScript","避免浪费"],e:`

    为什么使用 Flyweight 模式

    +`,r:{minutes:2.26,words:679},t:"facade 模式",y:"a"}}],["/tech/DesignPatterns/factory_method.html",{loader:()=>T(()=>import("./factory_method.html-NuMbQ7is.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript","工厂方法","交给子类"],e:`

    在 Factory Method 模式中,我们定义一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。Factory Method 使一个类的实例化延迟到其子类。

    +`,r:{minutes:1.67,words:501},t:"Factory Method 模式",y:"a"}}],["/tech/DesignPatterns/flyweight.html",{loader:()=>T(()=>import("./flyweight.html-DUTi0_Qx.js"),[]),meta:{d:17300736e5,l:"2024年10月28日",c:["设计模式"],g:["设计模式","TypeScript","避免浪费"],e:`

    为什么使用 Flyweight 模式

    Flyweight 模式是一种结构型设计模式,它通过共享尽可能多的相同对象来减少内存使用,从而提高性能。在需要生成大量细粒度对象的场景中,Flyweight 模式非常有用。通过共享对象,Flyweight 模式可以显著减少内存消耗,并提高应用程序的效率。

    实例代码

    -`,r:{minutes:8.16,words:2449},t:"Flyweight 模式",y:"a"}}],["/tech/DesignPatterns/",{loader:()=>T(()=>import("./index.html-B5GIg4Ij.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript"],e:`

    面向对象三大特性:封装、继承、多态。 设计模式需遵循面向对象的设计原则,由于笔者不太喜欢java, 并嫌弃cpp中没有interface, 遂使用ts作为实现语言(我才不用rust呢 😃 )

    -`,r:{minutes:.48,words:144},t:"设计模式",y:"a"}}],["/tech/DesignPatterns/interpreter.html",{loader:()=>T(()=>import("./interpreter.html-CansycaD.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["设计模式"],g:["设计模式","TypeScript","用类实现"],e:`

    为什么使用此模式

    +`,r:{minutes:8.16,words:2449},t:"Flyweight 模式",y:"a"}}],["/tech/DesignPatterns/",{loader:()=>T(()=>import("./index.html-Dj_dWWjR.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript"],e:`

    面向对象三大特性:封装、继承、多态。 设计模式需遵循面向对象的设计原则,由于笔者不太喜欢java, 并嫌弃cpp中没有interface, 遂使用ts作为实现语言(我才不用rust呢 😃 )

    +`,r:{minutes:.48,words:144},t:"设计模式",y:"a"}}],["/tech/DesignPatterns/interpreter.html",{loader:()=>T(()=>import("./interpreter.html-DfDQyPSV.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["设计模式"],g:["设计模式","TypeScript","用类实现"],e:`

    为什么使用此模式

    解释器模式(Interpreter Pattern)是一种行为设计模式,它定义了一种语言的文法表示,并定义一个解释器来解释该语言中的句子。使用解释器模式的原因包括:

    1. 简化语法解析:通过定义文法规则,可以轻松解析和执行特定的语言或指令集。
    2. 可扩展性:可以轻松添加新的语法规则或指令,而无需修改现有代码。
    3. 代码复用:通过将不同的语法规则封装在不同的类中,可以提高代码的复用性和可维护性。
    4. -
    `,r:{minutes:3.33,words:998},t:"Interpreter 模式",y:"a"}}],["/tech/DesignPatterns/iterator.html",{loader:()=>T(()=>import("./iterator.html-USsHv5Pr.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript"],e:`

    为什么要使用迭代器模式

    +`,r:{minutes:3.33,words:998},t:"Interpreter 模式",y:"a"}}],["/tech/DesignPatterns/iterator.html",{loader:()=>T(()=>import("./iterator.html-9EjNXcsT.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript"],e:`

    为什么要使用迭代器模式

    迭代器模式提供了一种方法来顺序访问集合中的元素,而无需暴露其底层表示。使用迭代器模式有以下几个优点:

    1. 简化代码:迭代器模式将遍历逻辑封装在迭代器对象中,使得客户端代码更加简洁和易读。
    2. 解耦集合和遍历:集合对象和遍历算法分离,增加了代码的灵活性和可维护性。可以在不修改集合对象的情况下,改变遍历算法。
    3. 统一接口:通过实现统一的迭代器接口,不同类型的集合可以使用相同的遍历方式,增强了代码的可扩展性。
    4. 支持多种遍历方式:可以根据需要实现不同的迭代器,以支持多种遍历方式,如正向遍历、反向遍历、过滤遍历等。
    5. -
    `,r:{minutes:2.86,words:857},t:"迭代器模式",y:"a"}}],["/tech/DesignPatterns/mediator.html",{loader:()=>T(()=>import("./mediator.html-RBFBa63Z.js"),[]),meta:{d:17299008e5,l:"2024年10月26日",c:["设计模式"],g:["设计模式","TypeScript","简单化"],e:`

    为什么使用此类

    +`,r:{minutes:2.86,words:857},t:"迭代器模式",y:"a"}}],["/tech/DesignPatterns/mediator.html",{loader:()=>T(()=>import("./mediator.html-C5vte2nl.js"),[]),meta:{d:17299008e5,l:"2024年10月26日",c:["设计模式"],g:["设计模式","TypeScript","简单化"],e:`

    为什么使用此类

    请大家想象一下一个乱糟糟的开发小组的工作状态。小组中的 10 个成员虽然一起协同工作,但是意见难以统一,总是互相指挥,导致工作进度始终滞后。不仅如此,他们还十分在意编码细节,经常为此争执不下。

    -`,r:{minutes:5.09,words:1526},t:"Mediator 模式",y:"a"}}],["/tech/DesignPatterns/memento.html",{loader:()=>T(()=>import("./memento.html-CbbBldep.js"),[]),meta:{d:17300736e5,l:"2024年10月28日",c:["设计模式"],g:["设计模式","TypeScript","管理状态"],e:`

    为什么使用 Memento 模式

    +`,r:{minutes:5.09,words:1526},t:"Mediator 模式",y:"a"}}],["/tech/DesignPatterns/memento.html",{loader:()=>T(()=>import("./memento.html-Dw4vFzUb.js"),[]),meta:{d:17300736e5,l:"2024年10月28日",c:["设计模式"],g:["设计模式","TypeScript","管理状态"],e:`

    为什么使用 Memento 模式

    Memento 模式的主要目的是在不破坏封装性的前提下,捕获和恢复对象的内部状态。它在以下情况下特别有用:

    • 需要保存和恢复对象的多个状态。
    • @@ -231,11 +239,11 @@ https://code.visualstudio.com/api/get-started/your-first-extension

    • 需要在不暴露对象实现细节的情况下保存对象状态。

    示例代码

    -

    在这个示例中,我们使用了 Memento 模式来管理 Gamer 对象的状态。Memento 模式允许我们在不暴露对象实现细节的情况下保存和恢复对象的状态。以下是示例代码:

    `,r:{minutes:4.19,words:1257},t:"Memento 模式",y:"a"}}],["/tech/DesignPatterns/observer.html",{loader:()=>T(()=>import("./observer.html-BvrPoU9w.js"),[]),meta:{d:17299008e5,l:"2024年10月26日",c:["设计模式"],g:["设计模式","TypeScript","管理状态"],e:`

    为什么使用观察者模式

    +

    在这个示例中,我们使用了 Memento 模式来管理 Gamer 对象的状态。Memento 模式允许我们在不暴露对象实现细节的情况下保存和恢复对象的状态。以下是示例代码:

    `,r:{minutes:4.19,words:1257},t:"Memento 模式",y:"a"}}],["/tech/DesignPatterns/observer.html",{loader:()=>T(()=>import("./observer.html-DCoBW7Y-.js"),[]),meta:{d:17299008e5,l:"2024年10月26日",c:["设计模式"],g:["设计模式","TypeScript","管理状态"],e:`

    为什么使用观察者模式

    观察者模式非常适合用于需要自动更新的场景。例如,在图形用户界面(GUI)应用程序中,当数据模型发生变化时,所有显示该数据的视图都需要自动更新。通过使用观察者模式,我们可以将这些视图注册为观察者,当数据模型发生变化时,它们会自动收到通知并更新显示。

    示例代码

    在这个示例中,我们展示了如何使用观察者模式来管理状态变化。观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。以下是 TypeScript 实现的代码示例:

    -`,r:{minutes:4.64,words:1392},t:"Observer 模式",y:"a"}}],["/tech/DesignPatterns/prototype.html",{loader:()=>T(()=>import("./prototype.html-Boe-0El2.js"),[]),meta:{d:17295552e5,l:"2024年10月22日",c:["设计模式"],g:["设计模式","typescript","生成实例"],e:`

    为什么要使用 Prototype 模式

    +`,r:{minutes:4.64,words:1392},t:"Observer 模式",y:"a"}}],["/tech/DesignPatterns/prototype.html",{loader:()=>T(()=>import("./prototype.html-Be3THUal.js"),[]),meta:{d:17295552e5,l:"2024年10月22日",c:["设计模式"],g:["设计模式","typescript","生成实例"],e:`

    为什么要使用 Prototype 模式

    Prototype 模式是一种创建型设计模式,它允许你复制现有对象而无需使代码依赖它们所属的类。使用 Prototype 模式可以:

    • 避免重复初始化对象的复杂过程。
    • @@ -245,14 +253,14 @@ https://code.visualstudio.com/api/get-started/your-first-extension

    • 难以根据类生成实例
    • 解耦框架与生成的实例
    -`,r:{minutes:1.6,words:480},t:"Prototype 模式",y:"a"}}],["/tech/DesignPatterns/proxy.html",{loader:()=>T(()=>import("./proxy.html-CSHBH0Xk.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["设计模式"],g:["设计模式","TypeScript","避免浪费"],e:`

    为什么使用代理模式

    +`,r:{minutes:1.6,words:480},t:"Prototype 模式",y:"a"}}],["/tech/DesignPatterns/proxy.html",{loader:()=>T(()=>import("./proxy.html-DydmfTHY.js"),[]),meta:{d:173016e7,l:"2024年10月29日",c:["设计模式"],g:["设计模式","TypeScript","避免浪费"],e:`

    为什么使用代理模式

    在面向对象编程中,“本人”和“代理人”都是对象。如果“本人”对象太忙了.有些工作无法自己亲自完成,就将其交给“代理人"对象负责。

    -`,r:{minutes:5.08,words:1524},t:"Proxy 模式",y:"a"}}],["/tech/DesignPatterns/singleton.html",{loader:()=>T(()=>import("./singleton.html-Pzh1ZzrO.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript","生成实例"],e:`

    为什么要使用 Singleton 模式

    +`,r:{minutes:5.08,words:1524},t:"Proxy 模式",y:"a"}}],["/tech/DesignPatterns/singleton.html",{loader:()=>T(()=>import("./singleton.html-CJylCsqx.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript","生成实例"],e:`

    为什么要使用 Singleton 模式

    Singleton 模式确保一个类只有一个实例,并提供一个全局访问点。它常用于需要控制资源访问的场景,例如数据库连接、日志记录器等。通过 Singleton 模式,可以避免创建多个实例带来的资源浪费和不一致性问题。

    -`,r:{minutes:1.52,words:457},t:"Singleton 模式",y:"a"}}],["/tech/DesignPatterns/state.html",{loader:()=>T(()=>import("./state.html-DsXXtCU9.js"),[]),meta:{d:17300736e5,l:"2024年10月28日",c:["设计模式"],g:["设计模式","TypeScript","管理状态"],e:`

    为什么使用 State 模式

    +`,r:{minutes:1.52,words:457},t:"Singleton 模式",y:"a"}}],["/tech/DesignPatterns/state.html",{loader:()=>T(()=>import("./state.html-D0BJywlY.js"),[]),meta:{d:17300736e5,l:"2024年10月28日",c:["设计模式"],g:["设计模式","TypeScript","管理状态"],e:`

    为什么使用 State 模式

    State 模式允许对象在其内部状态改变时改变其行为。它将与状态相关的行为封装在独立的类中,使得状态转换变得清晰且易于管理。使用 State 模式可以避免大量的条件语句,使代码更加简洁和可维护。

    示例代码

    -`,r:{minutes:11.72,words:3515},t:"State 模式",y:"a"}}],["/tech/DesignPatterns/strategy.html",{loader:()=>T(()=>import("./strategy.html-Cprd53d9.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","分开考虑","整体的替换算法"],e:`

    使用此设计模式的理由

    +`,r:{minutes:11.72,words:3515},t:"State 模式",y:"a"}}],["/tech/DesignPatterns/strategy.html",{loader:()=>T(()=>import("./strategy.html-BeQd57Ga.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","分开考虑","整体的替换算法"],e:`

    使用此设计模式的理由

    在这个示例程序中,我们使用了策略模式(Strategy Pattern)来实现不同的猜拳策略。策略模式的主要优点包括:

    1. 易于扩展:可以很容易地添加新的策略,而不需要修改现有的代码。
    2. @@ -260,11 +268,11 @@ https://code.visualstudio.com/api/get-started/your-first-extension

    3. 减少重复代码:通过使用策略模式,可以避免在多个地方重复相同的算法逻辑。

    示例程序

    -

    下面我们来看一段使用了 strategy 模式的示例程序。这段示例程序的功能是让电脑玩“猜拳"游戏。

    `,r:{minutes:4.53,words:1359},t:"Strategy 模式",y:"a"}}],["/tech/DesignPatterns/template_method.html",{loader:()=>T(()=>import("./template_method.html-Do5aF6Sl.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript","交给子类"],e:`

    在父类中定义处理流程的框架,在子类中实现具体处理。Template Method 模式的主要目的是为了定义一个算法的骨架,而将一些步骤的具体实现延迟到子类中。通过这种方式,子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

    -`,r:{minutes:2.33,words:698},t:"Template Method 模式",y:"a"}}],["/tech/DesignPatterns/visitor.html",{loader:()=>T(()=>import("./visitor.html-C6V612X1.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","访问数据结构并处理数据"],e:`

    在 Visitor 模式中,数据结构与处理被分离开来。我们编写一个表示“访问者”的类来访问数据结构中的元素,并把对各元素的处理交给访问者类。这样,当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可。

    -`,r:{minutes:6.29,words:1888},t:"Visitor 模式",y:"a"}}],["/tech/designASimpileCCompiler/0.html",{loader:()=>T(()=>import("./0.html-BMihHxnS.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:` +

    下面我们来看一段使用了 strategy 模式的示例程序。这段示例程序的功能是让电脑玩“猜拳"游戏。

    `,r:{minutes:4.53,words:1359},t:"Strategy 模式",y:"a"}}],["/tech/DesignPatterns/template_method.html",{loader:()=>T(()=>import("./template_method.html-B7oRZ-kS.js"),[]),meta:{d:17294688e5,l:"2024年10月21日",c:["设计模式"],g:["设计模式","typescript","交给子类"],e:`

    在父类中定义处理流程的框架,在子类中实现具体处理。Template Method 模式的主要目的是为了定义一个算法的骨架,而将一些步骤的具体实现延迟到子类中。通过这种方式,子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

    +`,r:{minutes:2.33,words:698},t:"Template Method 模式",y:"a"}}],["/tech/DesignPatterns/visitor.html",{loader:()=>T(()=>import("./visitor.html-DaIekGHb.js"),[]),meta:{d:17298144e5,l:"2024年10月25日",c:["设计模式"],g:["设计模式","TypeScript","访问数据结构并处理数据"],e:`

    在 Visitor 模式中,数据结构与处理被分离开来。我们编写一个表示“访问者”的类来访问数据结构中的元素,并把对各元素的处理交给访问者类。这样,当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可。

    +`,r:{minutes:6.29,words:1888},t:"Visitor 模式",y:"a"}}],["/tech/designASimpileCCompiler/0.html",{loader:()=>T(()=>import("./0.html-CC8GZdvL.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    本文转载自 手把手教你构建 C 语言编译器(0)- 前言,原作者 三点水,原文链接:原文链接,如有侵权,请联系删除。

    -

    转载工具:devtoolhelloworld

    `,r:{minutes:6.12,words:1835},t:"手把手教你构建 C 语言编译器(0)- 前言",y:"a"}}],["/tech/designASimpileCCompiler/1.html",{loader:()=>T(()=>import("./1.html-B2woGSgL.js"),[]),meta:{e:` +

    转载工具:devtoolhelloworld

    `,r:{minutes:6.12,words:1835},t:"手把手教你构建 C 语言编译器(0)- 前言",y:"a"}}],["/tech/designASimpileCCompiler/1.html",{loader:()=>T(()=>import("./1.html-C8e1pvTo.js"),[]),meta:{e:`

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-1/,如有侵权,请联系删除。

    原文内容

    手把手教你构建 C 语言编译器(1)- 设计

    @@ -272,7 +280,7 @@ https://code.visualstudio.com/api/get-started/your-first-extension

    1. 1. 编译器的构建流程
    2. 2. 编译器框架
    3. -
    `,r:{minutes:6.74,words:2023},t:"手把手教你构建 C 语言编译器(1)- 设计",y:"a"}}],["/tech/designASimpileCCompiler/2.html",{loader:()=>T(()=>import("./2.html-DuLx7xSk.js"),[]),meta:{e:`
    +`,r:{minutes:6.74,words:2023},t:"手把手教你构建 C 语言编译器(1)- 设计",y:"a"}}],["/tech/designASimpileCCompiler/2.html",{loader:()=>T(()=>import("./2.html-CfN7D4gf.js"),[]),meta:{e:`

    title: "手把手教你构建 C 语言编译器(2)——虚拟机" category:

      @@ -284,7 +292,7 @@ tag:

    转载声明

    -

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-2/,如有侵权,请联系删除。

    `,r:{minutes:25.04,words:7511},t:"转载声明",y:"a"}}],["/tech/designASimpileCCompiler/3.html",{loader:()=>T(()=>import("./3.html-CZB5joNY.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:` +

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-2/,如有侵权,请联系删除。

    `,r:{minutes:25.04,words:7511},t:"转载声明",y:"a"}}],["/tech/designASimpileCCompiler/3.html",{loader:()=>T(()=>import("./3.html-D9pPtuiP.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    本文转自 https://lotabout.me/2015/write-a-C-interpreter-3/,如有侵权,请联系删除。

    原文内容

    手把手教你构建 C 语言编译器(3)- 词法分析器

    @@ -308,7 +316,7 @@ tag:
  • 4. 代码
  • 5. 小结
  • -`,r:{minutes:25.21,words:7563},t:"手把手教你构建 C 语言编译器(3)——词法分析器",y:"a"}}],["/tech/designASimpileCCompiler/4.html",{loader:()=>T(()=>import("./4.html-C8TuLMaF.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    []

    +`,r:{minutes:25.21,words:7563},t:"手把手教你构建 C 语言编译器(3)——词法分析器",y:"a"}}],["/tech/designASimpileCCompiler/4.html",{loader:()=>T(()=>import("./4.html-DavKkBaH.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    []

    转载声明

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-4/,如有侵权,请联系删除。

    原文内容

    @@ -322,7 +330,7 @@ tag:
  • 5. 左递归
  • 6. 四则运算的实现
  • 7. 小结
  • -`,r:{minutes:11.33,words:3399},t:"手把手教你构建 C 语言编译器(4)——递归下降",y:"a"}}],["/tech/designASimpileCCompiler/5.html",{loader:()=>T(()=>import("./5.html-qLlLbRlw.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:` +`,r:{minutes:11.33,words:3399},t:"手把手教你构建 C 语言编译器(4)——递归下降",y:"a"}}],["/tech/designASimpileCCompiler/5.html",{loader:()=>T(()=>import("./5.html-DpkUOZDe.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-5/,如有侵权,请联系删除。

    原文内容

    手把手教你构建 C 语言编译器(5)- 变量定义

    @@ -339,7 +347,7 @@ tag:
  • 3. 代码
  • 4. 小结
  • -`,r:{minutes:10.71,words:3212},t:"手把手教你构建 C 语言编译器(5)——变量定义",y:"a"}}],["/tech/designASimpileCCompiler/6.html",{loader:()=>T(()=>import("./6.html-BPGbpgjm.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:` +`,r:{minutes:10.71,words:3212},t:"手把手教你构建 C 语言编译器(5)——变量定义",y:"a"}}],["/tech/designASimpileCCompiler/6.html",{loader:()=>T(()=>import("./6.html-4dTDTOb4.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-6/,如有侵权,请联系删除。

    原文内容

    手把手教你构建 C 语言编译器(6)- 函数定义

    @@ -356,7 +364,7 @@ tag:
  • 3. 代码
  • 4. 小结
  • -`,r:{minutes:12.61,words:3783},t:"手把手教你构建 C 语言编译器(6)——函数定义",y:"a"}}],["/tech/designASimpileCCompiler/7.html",{loader:()=>T(()=>import("./7.html-DopH3hey.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:` +`,r:{minutes:12.61,words:3783},t:"手把手教你构建 C 语言编译器(6)——函数定义",y:"a"}}],["/tech/designASimpileCCompiler/7.html",{loader:()=>T(()=>import("./7.html-BzOHeKw-.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-7/,如有侵权,请联系删除。

    原文内容

    手把手教你构建 C 语言编译器(7)- 语句

    @@ -372,11 +380,11 @@ tag:
  • 2. 代码
  • 3. 小结
  • -`,r:{minutes:6.99,words:2097},t:"手把手教你构建 C 语言编译器(7)——语句",y:"a"}}],["/tech/designASimpileCCompiler/8.html",{loader:()=>T(()=>import("./8.html-D1TnfZgI.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:` +`,r:{minutes:6.99,words:2097},t:"手把手教你构建 C 语言编译器(7)——语句",y:"a"}}],["/tech/designASimpileCCompiler/8.html",{loader:()=>T(()=>import("./8.html-C-sA1tiy.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    本文转自 https://blog.csdn.net/xzp740813/article/details/142961332?spm=1001.2014.3001.5501,如有侵权,请联系删除。

    原文内容

    手把手教你构建 C 语言编译器(8)- 表达式

    -

    Table of Contents

    `,r:{minutes:29.67,words:8900},t:"手把手教你构建 C 语言编译器(8)——表达式",y:"a"}}],["/tech/designASimpileCCompiler/9.html",{loader:()=>T(()=>import("./9.html-CGdd1xtP.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:` +

    Table of Contents

    `,r:{minutes:29.67,words:8900},t:"手把手教你构建 C 语言编译器(8)——表达式",y:"a"}}],["/tech/designASimpileCCompiler/9.html",{loader:()=>T(()=>import("./9.html-DOIgY3Qt.js"),[]),meta:{c:["编译原理"],g:["c","编译器","解释器"],e:`

    本文转自 https://lotabout.me/2016/write-a-C-interpreter-9/,如有侵权,请联系删除。

    原文内容

    手把手教你构建 C 语言编译器(9)- 总结

    @@ -387,15 +395,15 @@ tag:
  • 3. 语法分析
  • 4. 关于编代码
  • 5. 结语
  • -`,r:{minutes:4.66,words:1397},t:"手把手教你构建 C 语言编译器(9)——总结",y:"a"}}],["/404.html",{loader:()=>T(()=>import("./404.html-BC0rHQLu.js"),[]),meta:{t:""}}],["/tech/designASimpileCCompiler/",{loader:()=>T(()=>import("./index.html-Ak5uwHrg.js"),[]),meta:{t:"Design ASimpile CCompiler"}}],["/category/",{loader:()=>T(()=>import("./index.html-CO8-8uSt.js"),[]),meta:{t:"分类",I:!1}}],["/category/build-tools/",{loader:()=>T(()=>import("./index.html-B7IKPS0E.js"),[]),meta:{t:"build-tools 分类",I:!1}}],["/category/cicd/",{loader:()=>T(()=>import("./index.html-BK-iNYKv.js"),[]),meta:{t:"CI/CD 分类",I:!1}}],["/category/nginx/",{loader:()=>T(()=>import("./index.html-DDeLtmuh.js"),[]),meta:{t:"nginx 分类",I:!1}}],["/category/%E5%A5%87%E6%8A%80%E6%B7%AB%E5%B7%A7/",{loader:()=>T(()=>import("./index.html-BSJr7qwh.js"),[]),meta:{t:"奇技淫巧 分类",I:!1}}],["/category/docker/",{loader:()=>T(()=>import("./index.html-DRlrhC7d.js"),[]),meta:{t:"docker 分类",I:!1}}],["/category/oi/",{loader:()=>T(()=>import("./index.html-BXtqeyHq.js"),[]),meta:{t:"OI 分类",I:!1}}],["/category/python/",{loader:()=>T(()=>import("./index.html-UrkKwESw.js"),[]),meta:{t:"python 分类",I:!1}}],["/category/%E9%A3%8E%E6%A0%BC/",{loader:()=>T(()=>import("./index.html-C8X5E5_U.js"),[]),meta:{t:"风格 分类",I:!1}}],["/category/%E6%95%B0%E5%AD%A6/",{loader:()=>T(()=>import("./index.html-CZDJXbd3.js"),[]),meta:{t:"数学 分类",I:!1}}],["/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/",{loader:()=>T(()=>import("./index.html-B71I-yvI.js"),[]),meta:{t:"设计模式 分类",I:!1}}],["/category/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/",{loader:()=>T(()=>import("./index.html-ZxSgoRoh.js"),[]),meta:{t:"编译原理 分类",I:!1}}],["/tag/",{loader:()=>T(()=>import("./index.html-bipylPNF.js"),[]),meta:{t:"标签",I:!1}}],["/tag/cicd/",{loader:()=>T(()=>import("./index.html-DR-PJoSN.js"),[]),meta:{t:"标签: CI/CD",I:!1}}],["/tag/%E9%83%A8%E7%BD%B2/",{loader:()=>T(()=>import("./index.html-DDn6tClc.js"),[]),meta:{t:"标签: 部署",I:!1}}],["/tag/docker/",{loader:()=>T(()=>import("./index.html-BvZSJ2r-.js"),[]),meta:{t:"标签: docker",I:!1}}],["/tag/modint/",{loader:()=>T(()=>import("./index.html-BKjCcFT0.js"),[]),meta:{t:"标签: MODInt",I:!1}}],["/tag/%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/",{loader:()=>T(()=>import("./index.html-CSRSsNOG.js"),[]),meta:{t:"标签: 踩坑记录",I:!1}}],["/tag/%E9%A3%8E%E6%A0%BC/",{loader:()=>T(()=>import("./index.html-BLuB_Eoc.js"),[]),meta:{t:"标签: 风格",I:!1}}],["/tag/%E5%85%AC%E7%90%86%E7%B3%BB%E7%BB%9F/",{loader:()=>T(()=>import("./index.html-CLOegCHC.js"),[]),meta:{t:"标签: 公理系统",I:!1}}],["/tag/mobile/",{loader:()=>T(()=>import("./index.html-D2pTqZFy.js"),[]),meta:{t:"标签: mobile",I:!1}}],["/tag/%E6%BF%80%E6%B4%BB/",{loader:()=>T(()=>import("./index.html-DZYdDCPP.js"),[]),meta:{t:"标签: 激活",I:!1}}],["/tag/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/",{loader:()=>T(()=>import("./index.html-DzsdPiFk.js"),[]),meta:{t:"标签: 设计模式",I:!1}}],["/tag/typescript/",{loader:()=>T(()=>import("./index.html-CRzs15I0.js"),[]),meta:{t:"标签: typescript",I:!1}}],["/tag/%E7%94%9F%E6%88%90%E5%AE%9E%E4%BE%8B/",{loader:()=>T(()=>import("./index.html-BZLD09T1.js"),[]),meta:{t:"标签: 生成实例",I:!1}}],["/tag/typescript/",{loader:()=>T(()=>import("./index.html-CRzs15I0.js"),[]),meta:{t:"标签: TypeScript",I:!1}}],["/tag/%E5%88%86%E5%BC%80%E8%80%83%E8%99%91/",{loader:()=>T(()=>import("./index.html-CGsa9ffM.js"),[]),meta:{t:"标签: 分开考虑",I:!1}}],["/tag/%E6%8E%A8%E5%8D%B8%E8%B4%A3%E4%BB%BB/",{loader:()=>T(()=>import("./index.html-QNuKjuoZ.js"),[]),meta:{t:"标签: 推卸责任",I:!1}}],["/tag/%E7%94%A8%E7%B1%BB%E5%AE%9E%E7%8E%B0/",{loader:()=>T(()=>import("./index.html-9q-r5JHT.js"),[]),meta:{t:"标签: 用类实现",I:!1}}],["/tag/%E5%AE%B9%E5%99%A8%E4%B8%8E%E5%86%85%E5%AE%B9%E7%9A%84%E4%B8%80%E8%87%B4%E6%80%A7/",{loader:()=>T(()=>import("./index.html-BHNGjpbb.js"),[]),meta:{t:"标签: 容器与内容的一致性",I:!1}}],["/tag/%E7%AE%80%E5%8D%95%E5%8C%96/",{loader:()=>T(()=>import("./index.html-0LHdE1Bw.js"),[]),meta:{t:"标签: 简单化",I:!1}}],["/tag/%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95/",{loader:()=>T(()=>import("./index.html-kOhwCTdr.js"),[]),meta:{t:"标签: 工厂方法",I:!1}}],["/tag/%E4%BA%A4%E7%BB%99%E5%AD%90%E7%B1%BB/",{loader:()=>T(()=>import("./index.html-BlxatHgv.js"),[]),meta:{t:"标签: 交给子类",I:!1}}],["/tag/%E9%81%BF%E5%85%8D%E6%B5%AA%E8%B4%B9/",{loader:()=>T(()=>import("./index.html-ayClYbZ3.js"),[]),meta:{t:"标签: 避免浪费",I:!1}}],["/tag/%E7%AE%A1%E7%90%86%E7%8A%B6%E6%80%81/",{loader:()=>T(()=>import("./index.html-B3D_5fpm.js"),[]),meta:{t:"标签: 管理状态",I:!1}}],["/tag/%E6%95%B4%E4%BD%93%E7%9A%84%E6%9B%BF%E6%8D%A2%E7%AE%97%E6%B3%95/",{loader:()=>T(()=>import("./index.html-CcgdDCiF.js"),[]),meta:{t:"标签: 整体的替换算法",I:!1}}],["/tag/%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%B9%B6%E5%A4%84%E7%90%86%E6%95%B0%E6%8D%AE/",{loader:()=>T(()=>import("./index.html-B2T3kUK3.js"),[]),meta:{t:"标签: 访问数据结构并处理数据",I:!1}}],["/tag/c/",{loader:()=>T(()=>import("./index.html-BpjUVtkj.js"),[]),meta:{t:"标签: c",I:!1}}],["/tag/%E7%BC%96%E8%AF%91%E5%99%A8/",{loader:()=>T(()=>import("./index.html-BNAyU6dK.js"),[]),meta:{t:"标签: 编译器",I:!1}}],["/tag/%E8%A7%A3%E9%87%8A%E5%99%A8/",{loader:()=>T(()=>import("./index.html-VMxXqaQ8.js"),[]),meta:{t:"标签: 解释器",I:!1}}],["/article/",{loader:()=>T(()=>import("./index.html-DeH2SCB3.js"),[]),meta:{t:"文章",I:!1}}],["/star/",{loader:()=>T(()=>import("./index.html-Dh_a7h_3.js"),[]),meta:{t:"星标",I:!1}}],["/timeline/",{loader:()=>T(()=>import("./index.html-Bb8RnxYT.js"),[]),meta:{t:"时间轴",I:!1}}]]);/*! +`,r:{minutes:4.66,words:1397},t:"手把手教你构建 C 语言编译器(9)——总结",y:"a"}}],["/404.html",{loader:()=>T(()=>import("./404.html-CGMXaJuv.js"),[]),meta:{t:""}}],["/tech/designASimpileCCompiler/",{loader:()=>T(()=>import("./index.html-SwHQd6yQ.js"),[]),meta:{t:"Design ASimpile CCompiler"}}],["/category/",{loader:()=>T(()=>import("./index.html-TRJ6qSMT.js"),[]),meta:{t:"分类",I:!1}}],["/category/build-tools/",{loader:()=>T(()=>import("./index.html-BCqj2PVi.js"),[]),meta:{t:"build-tools 分类",I:!1}}],["/category/cicd/",{loader:()=>T(()=>import("./index.html-CE6ffKsf.js"),[]),meta:{t:"CI/CD 分类",I:!1}}],["/category/nginx/",{loader:()=>T(()=>import("./index.html-CVNN5Fwq.js"),[]),meta:{t:"nginx 分类",I:!1}}],["/category/%E5%A5%87%E6%8A%80%E6%B7%AB%E5%B7%A7/",{loader:()=>T(()=>import("./index.html-DhCS_0vf.js"),[]),meta:{t:"奇技淫巧 分类",I:!1}}],["/category/docker/",{loader:()=>T(()=>import("./index.html-BgifGsNR.js"),[]),meta:{t:"docker 分类",I:!1}}],["/category/oi/",{loader:()=>T(()=>import("./index.html-DOXbY5aY.js"),[]),meta:{t:"OI 分类",I:!1}}],["/category/python/",{loader:()=>T(()=>import("./index.html-ZzlVKqB8.js"),[]),meta:{t:"python 分类",I:!1}}],["/category/%E9%A3%8E%E6%A0%BC/",{loader:()=>T(()=>import("./index.html-BjeRhPA4.js"),[]),meta:{t:"风格 分类",I:!1}}],["/category/%E6%95%B0%E5%AD%A6/",{loader:()=>T(()=>import("./index.html-B1eL3fl0.js"),[]),meta:{t:"数学 分类",I:!1}}],["/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/",{loader:()=>T(()=>import("./index.html-DOZrz5tX.js"),[]),meta:{t:"设计模式 分类",I:!1}}],["/category/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/",{loader:()=>T(()=>import("./index.html-CL479cux.js"),[]),meta:{t:"编译原理 分类",I:!1}}],["/tag/",{loader:()=>T(()=>import("./index.html-mplfcIL_.js"),[]),meta:{t:"标签",I:!1}}],["/tag/cicd/",{loader:()=>T(()=>import("./index.html-DW1c3ODz.js"),[]),meta:{t:"标签: CI/CD",I:!1}}],["/tag/%E9%83%A8%E7%BD%B2/",{loader:()=>T(()=>import("./index.html-CzSwk-A5.js"),[]),meta:{t:"标签: 部署",I:!1}}],["/tag/docker/",{loader:()=>T(()=>import("./index.html-SMm58lXr.js"),[]),meta:{t:"标签: docker",I:!1}}],["/tag/modint/",{loader:()=>T(()=>import("./index.html-D8p9fsJ8.js"),[]),meta:{t:"标签: MODInt",I:!1}}],["/tag/%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/",{loader:()=>T(()=>import("./index.html-rivgPJ9m.js"),[]),meta:{t:"标签: 踩坑记录",I:!1}}],["/tag/%E9%A3%8E%E6%A0%BC/",{loader:()=>T(()=>import("./index.html-DwPUACaS.js"),[]),meta:{t:"标签: 风格",I:!1}}],["/tag/%E5%85%AC%E7%90%86%E7%B3%BB%E7%BB%9F/",{loader:()=>T(()=>import("./index.html-BwXv95cV.js"),[]),meta:{t:"标签: 公理系统",I:!1}}],["/tag/mobile/",{loader:()=>T(()=>import("./index.html-DeNCk-ye.js"),[]),meta:{t:"标签: mobile",I:!1}}],["/tag/%E6%BF%80%E6%B4%BB/",{loader:()=>T(()=>import("./index.html-hPg6lxSo.js"),[]),meta:{t:"标签: 激活",I:!1}}],["/tag/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/",{loader:()=>T(()=>import("./index.html-CYOJa5u_.js"),[]),meta:{t:"标签: 设计模式",I:!1}}],["/tag/typescript/",{loader:()=>T(()=>import("./index.html-JQfnAIea.js"),[]),meta:{t:"标签: typescript",I:!1}}],["/tag/%E7%94%9F%E6%88%90%E5%AE%9E%E4%BE%8B/",{loader:()=>T(()=>import("./index.html-Cw9y1ZOg.js"),[]),meta:{t:"标签: 生成实例",I:!1}}],["/tag/typescript/",{loader:()=>T(()=>import("./index.html-JQfnAIea.js"),[]),meta:{t:"标签: TypeScript",I:!1}}],["/tag/%E5%88%86%E5%BC%80%E8%80%83%E8%99%91/",{loader:()=>T(()=>import("./index.html-Dpy8imS-.js"),[]),meta:{t:"标签: 分开考虑",I:!1}}],["/tag/%E6%8E%A8%E5%8D%B8%E8%B4%A3%E4%BB%BB/",{loader:()=>T(()=>import("./index.html-i5A_pLCb.js"),[]),meta:{t:"标签: 推卸责任",I:!1}}],["/tag/%E7%94%A8%E7%B1%BB%E5%AE%9E%E7%8E%B0/",{loader:()=>T(()=>import("./index.html-yUxPRBBH.js"),[]),meta:{t:"标签: 用类实现",I:!1}}],["/tag/%E5%AE%B9%E5%99%A8%E4%B8%8E%E5%86%85%E5%AE%B9%E7%9A%84%E4%B8%80%E8%87%B4%E6%80%A7/",{loader:()=>T(()=>import("./index.html-DklcXGYl.js"),[]),meta:{t:"标签: 容器与内容的一致性",I:!1}}],["/tag/%E7%AE%80%E5%8D%95%E5%8C%96/",{loader:()=>T(()=>import("./index.html-BFj2MrdQ.js"),[]),meta:{t:"标签: 简单化",I:!1}}],["/tag/%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95/",{loader:()=>T(()=>import("./index.html-NUfXNLwU.js"),[]),meta:{t:"标签: 工厂方法",I:!1}}],["/tag/%E4%BA%A4%E7%BB%99%E5%AD%90%E7%B1%BB/",{loader:()=>T(()=>import("./index.html-BJX9B5Ij.js"),[]),meta:{t:"标签: 交给子类",I:!1}}],["/tag/%E9%81%BF%E5%85%8D%E6%B5%AA%E8%B4%B9/",{loader:()=>T(()=>import("./index.html-F61LMFJS.js"),[]),meta:{t:"标签: 避免浪费",I:!1}}],["/tag/%E7%AE%A1%E7%90%86%E7%8A%B6%E6%80%81/",{loader:()=>T(()=>import("./index.html-COyA9Sec.js"),[]),meta:{t:"标签: 管理状态",I:!1}}],["/tag/%E6%95%B4%E4%BD%93%E7%9A%84%E6%9B%BF%E6%8D%A2%E7%AE%97%E6%B3%95/",{loader:()=>T(()=>import("./index.html-DYx5t_sy.js"),[]),meta:{t:"标签: 整体的替换算法",I:!1}}],["/tag/%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%B9%B6%E5%A4%84%E7%90%86%E6%95%B0%E6%8D%AE/",{loader:()=>T(()=>import("./index.html-CryaZFG_.js"),[]),meta:{t:"标签: 访问数据结构并处理数据",I:!1}}],["/tag/c/",{loader:()=>T(()=>import("./index.html-y8ELyp0v.js"),[]),meta:{t:"标签: c",I:!1}}],["/tag/%E7%BC%96%E8%AF%91%E5%99%A8/",{loader:()=>T(()=>import("./index.html-auYNF0Cp.js"),[]),meta:{t:"标签: 编译器",I:!1}}],["/tag/%E8%A7%A3%E9%87%8A%E5%99%A8/",{loader:()=>T(()=>import("./index.html-DCZKDe-x.js"),[]),meta:{t:"标签: 解释器",I:!1}}],["/article/",{loader:()=>T(()=>import("./index.html-DERlmHfl.js"),[]),meta:{t:"文章",I:!1}}],["/star/",{loader:()=>T(()=>import("./index.html-DottIbHL.js"),[]),meta:{t:"星标",I:!1}}],["/timeline/",{loader:()=>T(()=>import("./index.html-BOaWfYA8.js"),[]),meta:{t:"时间轴",I:!1}}]]);/*! * vue-router v4.3.2 * (c) 2024 Eduardo San Martin Morote * @license MIT */const xn=typeof document<"u";function jd(e){return e.__esModule||e[Symbol.toStringTag]==="Module"}const me=Object.assign;function Qa(e,t){const n={};for(const r in t){const a=t[r];n[r]=Et(a)?a.map(e):e(a)}return n}const cr=()=>{},Et=Array.isArray,Oc=/#/g,zd=/&/g,qd=/\//g,Gd=/=/g,Ud=/\?/g,Mc=/\+/g,Wd=/%5B/g,Kd=/%5D/g,Rc=/%5E/g,Jd=/%60/g,$c=/%7B/g,Qd=/%7C/g,Fc=/%7D/g,Yd=/%20/g;function hl(e){return encodeURI(""+e).replace(Qd,"|").replace(Wd,"[").replace(Kd,"]")}function Zd(e){return hl(e).replace($c,"{").replace(Fc,"}").replace(Rc,"^")}function ko(e){return hl(e).replace(Mc,"%2B").replace(Yd,"+").replace(Oc,"%23").replace(zd,"%26").replace(Jd,"`").replace($c,"{").replace(Fc,"}").replace(Rc,"^")}function Xd(e){return ko(e).replace(Gd,"%3D")}function ef(e){return hl(e).replace(Oc,"%23").replace(Ud,"%3F")}function tf(e){return e==null?"":ef(e).replace(qd,"%2F")}function br(e){try{return decodeURIComponent(""+e)}catch{}return""+e}const nf=/\/$/,rf=e=>e.replace(nf,"");function Ya(e,t,n="/"){let r,a={},o="",l="";const i=t.indexOf("#");let c=t.indexOf("?");return i=0&&(c=-1),c>-1&&(r=t.slice(0,c),o=t.slice(c+1,i>-1?i:t.length),a=e(o)),i>-1&&(r=r||t.slice(0,i),l=t.slice(i,t.length)),r=sf(r??t,n),{fullPath:r+(o&&"?")+o+l,path:r,query:a,hash:br(l)}}function af(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function Ms(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function of(e,t,n){const r=t.matched.length-1,a=n.matched.length-1;return r>-1&&r===a&&Nn(t.matched[r],n.matched[a])&&Hc(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function Nn(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Hc(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!lf(e[n],t[n]))return!1;return!0}function lf(e,t){return Et(e)?Rs(e,t):Et(t)?Rs(t,e):e===t}function Rs(e,t){return Et(t)?e.length===t.length&&e.every((n,r)=>n===t[r]):e.length===1&&e[0]===t}function sf(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/"),a=r[r.length-1];(a===".."||a===".")&&r.push("");let o=n.length-1,l,i;for(l=0;l1&&o--;else break;return n.slice(0,o).join("/")+"/"+r.slice(l).join("/")}var Er;(function(e){e.pop="pop",e.push="push"})(Er||(Er={}));var ur;(function(e){e.back="back",e.forward="forward",e.unknown=""})(ur||(ur={}));function cf(e){if(!e)if(xn){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),rf(e)}const uf=/^[^#]+#/;function df(e,t){return e.replace(uf,"#")+t}function ff(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const Ba=()=>({left:window.scrollX,top:window.scrollY});function pf(e){let t;if("el"in e){const n=e.el,r=typeof n=="string"&&n.startsWith("#"),a=typeof n=="string"?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!a)return;t=ff(a,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function $s(e,t){return(history.state?history.state.position-t:-1)+e}const To=new Map;function hf(e,t){To.set(e,t)}function mf(e){const t=To.get(e);return To.delete(e),t}let vf=()=>location.protocol+"//"+location.host;function Nc(e,t){const{pathname:n,search:r,hash:a}=t,o=e.indexOf("#");if(o>-1){let i=a.includes(e.slice(o))?e.slice(o).length:1,c=a.slice(i);return c[0]!=="/"&&(c="/"+c),Ms(c,"")}return Ms(n,e)+r+a}function gf(e,t,n,r){let a=[],o=[],l=null;const i=({state:p})=>{const h=Nc(e,location),v=n.value,_=t.value;let E=0;if(p){if(n.value=h,t.value=p,l&&l===v){l=null;return}E=_?p.position-_.position:0}else r(h);a.forEach(b=>{b(n.value,v,{delta:E,type:Er.pop,direction:E?E>0?ur.forward:ur.back:ur.unknown})})};function c(){l=n.value}function u(p){a.push(p);const h=()=>{const v=a.indexOf(p);v>-1&&a.splice(v,1)};return o.push(h),h}function d(){const{history:p}=window;p.state&&p.replaceState(me({},p.state,{scroll:Ba()}),"")}function f(){for(const p of o)p();o=[],window.removeEventListener("popstate",i),window.removeEventListener("beforeunload",d)}return window.addEventListener("popstate",i),window.addEventListener("beforeunload",d,{passive:!0}),{pauseListeners:c,listen:u,destroy:f}}function Fs(e,t,n,r=!1,a=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:a?Ba():null}}function yf(e){const{history:t,location:n}=window,r={value:Nc(e,n)},a={value:t.state};a.value||o(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function o(c,u,d){const f=e.indexOf("#"),p=f>-1?(n.host&&document.querySelector("base")?e:e.slice(f))+c:vf()+e+c;try{t[d?"replaceState":"pushState"](u,"",p),a.value=u}catch(h){console.error(h),n[d?"replace":"assign"](p)}}function l(c,u){const d=me({},t.state,Fs(a.value.back,c,a.value.forward,!0),u,{position:a.value.position});o(c,d,!0),r.value=c}function i(c,u){const d=me({},a.value,t.state,{forward:c,scroll:Ba()});o(d.current,d,!0);const f=me({},Fs(r.value,c,null),{position:d.position+1},u);o(c,f,!1),r.value=c}return{location:r,state:a,push:i,replace:l}}function bf(e){e=cf(e);const t=yf(e),n=gf(e,t.state,t.location,t.replace);function r(o,l=!0){l||n.pauseListeners(),history.go(o)}const a=me({location:"",base:e,go:r,createHref:df.bind(null,e)},t,n);return Object.defineProperty(a,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(a,"state",{enumerable:!0,get:()=>t.state.value}),a}function Ef(e){return typeof e=="string"||e&&typeof e=="object"}function Vc(e){return typeof e=="string"||typeof e=="symbol"}const Ot={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},jc=Symbol("");var Hs;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(Hs||(Hs={}));function Vn(e,t){return me(new Error,{type:e,[jc]:!0},t)}function Pt(e,t){return e instanceof Error&&jc in e&&(t==null||!!(e.type&t))}const Ns="[^/]+?",_f={sensitive:!1,strict:!1,start:!0,end:!0},wf=/[.+*?^${}()[\]/\\]/g;function Af(e,t){const n=me({},_f,t),r=[];let a=n.start?"^":"";const o=[];for(const u of e){const d=u.length?[]:[90];n.strict&&!u.length&&(a+="/");for(let f=0;ft.length?t.length===1&&t[0]===80?1:-1:0}function kf(e,t){let n=0;const r=e.score,a=t.score;for(;n0&&t[t.length-1]<0}const Tf={type:0,value:""},Sf=/[a-zA-Z0-9_]/;function xf(e){if(!e)return[[]];if(e==="/")return[[Tf]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(h){throw new Error(`ERR (${n})/"${u}": ${h}`)}let n=0,r=n;const a=[];let o;function l(){o&&a.push(o),o=[]}let i=0,c,u="",d="";function f(){u&&(n===0?o.push({type:0,value:u}):n===1||n===2||n===3?(o.length>1&&(c==="*"||c==="+")&&t(`A repeatable param (${u}) must be alone in its segment. eg: '/:ids+.`),o.push({type:1,value:u,regexp:d,repeatable:c==="*"||c==="+",optional:c==="*"||c==="?"})):t("Invalid state to consume buffer"),u="")}function p(){u+=c}for(;i{l(C)}:cr}function l(d){if(Vc(d)){const f=r.get(d);f&&(r.delete(d),n.splice(n.indexOf(f),1),f.children.forEach(l),f.alias.forEach(l))}else{const f=n.indexOf(d);f>-1&&(n.splice(f,1),d.record.name&&r.delete(d.record.name),d.children.forEach(l),d.alias.forEach(l))}}function i(){return n}function c(d){let f=0;for(;f=0&&(d.record.path!==n[f].record.path||!zc(d,n[f]));)f++;n.splice(f,0,d),d.record.name&&!zs(d)&&r.set(d.record.name,d)}function u(d,f){let p,h={},v,_;if("name"in d&&d.name){if(p=r.get(d.name),!p)throw Vn(1,{location:d});_=p.record.name,h=me(js(f.params,p.keys.filter(C=>!C.optional).concat(p.parent?p.parent.keys.filter(C=>C.optional):[]).map(C=>C.name)),d.params&&js(d.params,p.keys.map(C=>C.name))),v=p.stringify(h)}else if(d.path!=null)v=d.path,p=n.find(C=>C.re.test(v)),p&&(h=p.parse(v),_=p.record.name);else{if(p=f.name?r.get(f.name):n.find(C=>C.re.test(f.path)),!p)throw Vn(1,{location:d,currentLocation:f});_=p.record.name,h=me({},f.params,d.params),v=p.stringify(h)}const E=[];let b=p;for(;b;)E.unshift(b.record),b=b.parent;return{name:_,path:v,params:h,matched:E,meta:Df(E)}}return e.forEach(d=>o(d)),{addRoute:o,resolve:u,removeRoute:l,getRoutes:i,getRecordMatcher:a}}function js(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}function If(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:Pf(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}}}function Pf(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const r in e.components)t[r]=typeof n=="object"?n[r]:n;return t}function zs(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function Df(e){return e.reduce((t,n)=>me(t,n.meta),{})}function qs(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];return n}function zc(e,t){return t.children.some(n=>n===e||zc(e,n))}function Of(e){const t={};if(e===""||e==="?")return t;const r=(e[0]==="?"?e.slice(1):e).split("&");for(let a=0;ao&&ko(o)):[r&&ko(r)]).forEach(o=>{o!==void 0&&(t+=(t.length?"&":"")+n,o!=null&&(t+="="+o))})}return t}function Mf(e){const t={};for(const n in e){const r=e[n];r!==void 0&&(t[n]=Et(r)?r.map(a=>a==null?null:""+a):r==null?r:""+r)}return t}const Rf=Symbol(""),Us=Symbol(""),Ia=Symbol(""),ml=Symbol(""),So=Symbol("");function Yn(){let e=[];function t(r){return e.push(r),()=>{const a=e.indexOf(r);a>-1&&e.splice(a,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function Zt(e,t,n,r,a,o=l=>l()){const l=r&&(r.enterCallbacks[a]=r.enterCallbacks[a]||[]);return()=>new Promise((i,c)=>{const u=p=>{p===!1?c(Vn(4,{from:n,to:t})):p instanceof Error?c(p):Ef(p)?c(Vn(2,{from:t,to:p})):(l&&r.enterCallbacks[a]===l&&typeof p=="function"&&l.push(p),i())},d=o(()=>e.call(r&&r.instances[a],t,n,u));let f=Promise.resolve(d);e.length<3&&(f=f.then(u)),f.catch(p=>c(p))})}function Za(e,t,n,r,a=o=>o()){const o=[];for(const l of e)for(const i in l.components){let c=l.components[i];if(!(t!=="beforeRouteEnter"&&!l.instances[i]))if($f(c)){const d=(c.__vccOpts||c)[t];d&&o.push(Zt(d,n,r,l,i,a))}else{let u=c();o.push(()=>u.then(d=>{if(!d)return Promise.reject(new Error(`Couldn't resolve component "${i}" at "${l.path}"`));const f=jd(d)?d.default:d;l.components[i]=f;const h=(f.__vccOpts||f)[t];return h&&Zt(h,n,r,l,i,a)()}))}}return o}function $f(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function Ws(e){const t=ve(Ia),n=ve(ml),r=w(()=>{const c=bt(e.to);return t.resolve(c)}),a=w(()=>{const{matched:c}=r.value,{length:u}=c,d=c[u-1],f=n.matched;if(!d||!f.length)return-1;const p=f.findIndex(Nn.bind(null,d));if(p>-1)return p;const h=Ks(c[u-2]);return u>1&&Ks(d)===h&&f[f.length-1].path!==h?f.findIndex(Nn.bind(null,c[u-2])):p}),o=w(()=>a.value>-1&&Vf(n.params,r.value.params)),l=w(()=>a.value>-1&&a.value===n.matched.length-1&&Hc(n.params,r.value.params));function i(c={}){return Nf(c)?t[bt(e.replace)?"replace":"push"](bt(e.to)).catch(cr):Promise.resolve()}return{route:r,href:w(()=>r.value.href),isActive:o,isExactActive:l,navigate:i}}const Ff=M({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:Ws,setup(e,{slots:t}){const n=Lr(Ws(e)),{options:r}=ve(Ia),a=w(()=>({[Js(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[Js(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const o=t.default&&t.default(n);return e.custom?o:s("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:a.value},o)}}}),Hf=Ff;function Nf(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Vf(e,t){for(const n in t){const r=t[n],a=e[n];if(typeof r=="string"){if(r!==a)return!1}else if(!Et(a)||a.length!==r.length||r.some((o,l)=>o!==a[l]))return!1}return!0}function Ks(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Js=(e,t,n)=>e??t??n,jf=M({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const r=ve(So),a=w(()=>e.route||r.value),o=ve(Us,0),l=w(()=>{let u=bt(o);const{matched:d}=a.value;let f;for(;(f=d[u])&&!f.components;)u++;return u}),i=w(()=>a.value.matched[l.value]);ht(Us,w(()=>l.value+1)),ht(Rf,i),ht(So,a);const c=z();return ae(()=>[c.value,i.value,e.name],([u,d,f],[p,h,v])=>{d&&(d.instances[f]=u,h&&h!==d&&u&&u===p&&(d.leaveGuards.size||(d.leaveGuards=h.leaveGuards),d.updateGuards.size||(d.updateGuards=h.updateGuards))),u&&d&&(!h||!Nn(d,h)||!p)&&(d.enterCallbacks[f]||[]).forEach(_=>_(u))},{flush:"post"}),()=>{const u=a.value,d=e.name,f=i.value,p=f&&f.components[d];if(!p)return Qs(n.default,{Component:p,route:u});const h=f.props[d],v=h?h===!0?u.params:typeof h=="function"?h(u):h:null,E=s(p,me({},v,t,{onVnodeUnmounted:b=>{b.component.isUnmounted&&(f.instances[d]=null)},ref:c}));return Qs(n.default,{Component:E,route:u})||E}}});function Qs(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const zf=jf;function qf(e){const t=Bf(e.routes,e),n=e.parseQuery||Of,r=e.stringifyQuery||Gs,a=e.history,o=Yn(),l=Yn(),i=Yn(),c=xe(Ot);let u=Ot;xn&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const d=Qa.bind(null,S=>""+S),f=Qa.bind(null,tf),p=Qa.bind(null,br);function h(S,q){let V,J;return Vc(S)?(V=t.getRecordMatcher(S),J=q):J=S,t.addRoute(J,V)}function v(S){const q=t.getRecordMatcher(S);q&&t.removeRoute(q)}function _(){return t.getRoutes().map(S=>S.record)}function E(S){return!!t.getRecordMatcher(S)}function b(S,q){if(q=me({},q||c.value),typeof S=="string"){const g=Ya(n,S,q.path),k=t.resolve({path:g.path},q),L=a.createHref(g.fullPath);return me(g,k,{params:p(k.params),hash:br(g.hash),redirectedFrom:void 0,href:L})}let V;if(S.path!=null)V=me({},S,{path:Ya(n,S.path,q.path).path});else{const g=me({},S.params);for(const k in g)g[k]==null&&delete g[k];V=me({},S,{params:f(g)}),q.params=f(q.params)}const J=t.resolve(V,q),ue=S.hash||"";J.params=d(p(J.params));const Ee=af(r,me({},S,{hash:Zd(ue),path:J.path})),m=a.createHref(Ee);return me({fullPath:Ee,hash:ue,query:r===Gs?Mf(S.query):S.query||{}},J,{redirectedFrom:void 0,href:m})}function C(S){return typeof S=="string"?Ya(n,S,c.value.path):me({},S)}function y(S,q){if(u!==S)return Vn(8,{from:q,to:S})}function A(S){return H(S)}function D(S){return A(me(C(S),{replace:!0}))}function P(S){const q=S.matched[S.matched.length-1];if(q&&q.redirect){const{redirect:V}=q;let J=typeof V=="function"?V(S):V;return typeof J=="string"&&(J=J.includes("?")||J.includes("#")?J=C(J):{path:J},J.params={}),me({query:S.query,hash:S.hash,params:J.path!=null?{}:S.params},J)}}function H(S,q){const V=u=b(S),J=c.value,ue=S.state,Ee=S.force,m=S.replace===!0,g=P(V);if(g)return H(me(C(g),{state:typeof g=="object"?me({},ue,g.state):ue,force:Ee,replace:m}),q||V);const k=V;k.redirectedFrom=q;let L;return!Ee&&of(r,J,V)&&(L=Vn(16,{to:k,from:J}),lt(J,J,!0,!1)),(L?Promise.resolve(L):I(k,J)).catch(x=>Pt(x)?Pt(x,2)?x:wt(x):K(x,k,J)).then(x=>{if(x){if(Pt(x,2))return H(me({replace:m},C(x.to),{state:typeof x.to=="object"?me({},ue,x.to.state):ue,force:Ee}),q||k)}else x=R(k,J,!0,m,ue);return U(k,J,x),x})}function O(S,q){const V=y(S,q);return V?Promise.reject(V):Promise.resolve()}function Q(S){const q=It.values().next().value;return q&&typeof q.runWithContext=="function"?q.runWithContext(S):S()}function I(S,q){let V;const[J,ue,Ee]=Gf(S,q);V=Za(J.reverse(),"beforeRouteLeave",S,q);for(const g of J)g.leaveGuards.forEach(k=>{V.push(Zt(k,S,q))});const m=O.bind(null,S,q);return V.push(m),Oe(V).then(()=>{V=[];for(const g of o.list())V.push(Zt(g,S,q));return V.push(m),Oe(V)}).then(()=>{V=Za(ue,"beforeRouteUpdate",S,q);for(const g of ue)g.updateGuards.forEach(k=>{V.push(Zt(k,S,q))});return V.push(m),Oe(V)}).then(()=>{V=[];for(const g of Ee)if(g.beforeEnter)if(Et(g.beforeEnter))for(const k of g.beforeEnter)V.push(Zt(k,S,q));else V.push(Zt(g.beforeEnter,S,q));return V.push(m),Oe(V)}).then(()=>(S.matched.forEach(g=>g.enterCallbacks={}),V=Za(Ee,"beforeRouteEnter",S,q,Q),V.push(m),Oe(V))).then(()=>{V=[];for(const g of l.list())V.push(Zt(g,S,q));return V.push(m),Oe(V)}).catch(g=>Pt(g,8)?g:Promise.reject(g))}function U(S,q,V){i.list().forEach(J=>Q(()=>J(S,q,V)))}function R(S,q,V,J,ue){const Ee=y(S,q);if(Ee)return Ee;const m=q===Ot,g=xn?history.state:{};V&&(J||m?a.replace(S.fullPath,me({scroll:m&&g&&g.scroll},ue)):a.push(S.fullPath,ue)),c.value=S,lt(S,q,V,m),wt()}let X;function Le(){X||(X=a.listen((S,q,V)=>{if(!At.listening)return;const J=b(S),ue=P(J);if(ue){H(me(ue,{replace:!0}),J).catch(cr);return}u=J;const Ee=c.value;xn&&hf($s(Ee.fullPath,V.delta),Ba()),I(J,Ee).catch(m=>Pt(m,12)?m:Pt(m,2)?(H(m.to,J).then(g=>{Pt(g,20)&&!V.delta&&V.type===Er.pop&&a.go(-1,!1)}).catch(cr),Promise.reject()):(V.delta&&a.go(-V.delta,!1),K(m,J,Ee))).then(m=>{m=m||R(J,Ee,!1),m&&(V.delta&&!Pt(m,8)?a.go(-V.delta,!1):V.type===Er.pop&&Pt(m,20)&&a.go(-1,!1)),U(J,Ee,m)}).catch(cr)}))}let Ae=Yn(),W=Yn(),ee;function K(S,q,V){wt(S);const J=W.list();return J.length?J.forEach(ue=>ue(S,q,V)):console.error(S),Promise.reject(S)}function De(){return ee&&c.value!==Ot?Promise.resolve():new Promise((S,q)=>{Ae.add([S,q])})}function wt(S){return ee||(ee=!S,Le(),Ae.list().forEach(([q,V])=>S?V(S):q()),Ae.reset()),S}function lt(S,q,V,J){const{scrollBehavior:ue}=e;if(!xn||!ue)return Promise.resolve();const Ee=!V&&mf($s(S.fullPath,0))||(J||!V)&&history.state&&history.state.scroll||null;return _t().then(()=>ue(S,q,Ee)).then(m=>m&&pf(m)).catch(m=>K(m,S,q))}const Fe=S=>a.go(S);let Xe;const It=new Set,At={currentRoute:c,listening:!0,addRoute:h,removeRoute:v,hasRoute:E,getRoutes:_,resolve:b,options:e,push:A,replace:D,go:Fe,back:()=>Fe(-1),forward:()=>Fe(1),beforeEach:o.add,beforeResolve:l.add,afterEach:i.add,onError:W.add,isReady:De,install(S){const q=this;S.component("RouterLink",Hf),S.component("RouterView",zf),S.config.globalProperties.$router=q,Object.defineProperty(S.config.globalProperties,"$route",{enumerable:!0,get:()=>bt(c)}),xn&&!Xe&&c.value===Ot&&(Xe=!0,A(a.location).catch(ue=>{}));const V={};for(const ue in Ot)Object.defineProperty(V,ue,{get:()=>c.value[ue],enumerable:!0});S.provide(Ia,q),S.provide(ml,Fi(V)),S.provide(So,c);const J=S.unmount;It.add(S),S.unmount=function(){It.delete(S),It.size<1&&(u=Ot,X&&X(),X=null,c.value=Ot,Xe=!1,ee=!1),J()}}};function Oe(S){return S.reduce((q,V)=>q.then(()=>Q(V)),Promise.resolve())}return At}function Gf(e,t){const n=[],r=[],a=[],o=Math.max(t.matched.length,e.matched.length);for(let l=0;lNn(u,i))?r.push(i):n.push(i));const c=e.matched[l];c&&(t.matched.find(u=>Nn(u,c))||a.push(c))}return[n,r,a]}function wn(){return ve(Ia)}function Ht(){return ve(ml)}var vl=Symbol(""),Bt=()=>{const e=ve(vl);if(!e)throw new Error("useClientData() is called without provider.");return e},Uf=()=>Bt().pageComponent,ge=()=>Bt().pageData,he=()=>Bt().pageFrontmatter,Wf=()=>Bt().pageHead,Pa=()=>Bt().pageLang,Kf=()=>Bt().pageLayout,Ye=()=>Bt().routeLocale,Jf=()=>Bt().routes,qc=()=>Bt().siteData,Gn=()=>Bt().siteLocaleData,Qf=Symbol(""),xo=xe(Nd),_r=xe(Vd),Gc=e=>{const t=Md(e);if(_r.value[t])return t;const n=encodeURI(t);return _r.value[n]?n:xo.value[t]||xo.value[n]||t},xt=e=>{const t=Gc(e),n=_r.value[t]??{..._r.value["/404.html"],notFound:!0};return{path:t,notFound:!1,...n}},Or=M({name:"ClientOnly",setup(e,t){const n=z(!1);return se(()=>{n.value=!0}),()=>{var r,a;return n.value?(a=(r=t.slots).default)==null?void 0:a.call(r):null}}}),Uc=M({name:"Content",props:{path:{type:String,required:!1,default:""}},setup(e){const t=Uf(),n=w(()=>{if(!e.path)return t.value;const r=xt(e.path);return tc(()=>r.loader().then(({comp:a})=>a))});return()=>s(n.value)}}),Ze=(e={})=>e,be=e=>sn(e)?e:`/${Dc(e)}`,Yf=e=>{if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget){const t=e.currentTarget.getAttribute("target");if(t!=null&&t.match(/\b_blank\b/i))return}return e.preventDefault(),!0}},Ie=({active:e=!1,activeClass:t="route-link-active",to:n,...r},{slots:a})=>{var c;const o=wn(),l=Gc(n),i=l.startsWith("#")||l.startsWith("?")?l:be(l);return s("a",{...r,class:["route-link",{[t]:e}],href:i,onClick:(u={})=>{Yf(u)?o.push(n).catch():Promise.resolve()}},(c=a.default)==null?void 0:c.call(a))};Ie.displayName="RouteLink";Ie.props={active:Boolean,activeClass:String,to:String};var Zf="Layout",Xf="en-US",pn=Lr({resolveLayouts:e=>e.reduce((t,n)=>({...t,...n.layouts}),{}),resolvePageHead:(e,t,n)=>{const r=we(t.description)?t.description:n.description,a=[...Array.isArray(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:r}]];return Pd(a)},resolvePageHeadTitle:(e,t)=>[e.title,t.title].filter(n=>!!n).join(" | "),resolvePageLang:(e,t)=>e.lang||t.lang||Xf,resolvePageLayout:(e,t)=>{const n=we(e.frontmatter.layout)?e.frontmatter.layout:Zf;if(!t[n])throw new Error(`[vuepress] Cannot resolve layout: ${n}`);return t[n]},resolveRouteLocale:(e,t)=>Rd(e,t),resolveSiteLocaleData:(e,t)=>{var n;return{...e,...e.locales[t],head:[...((n=e.locales[t])==null?void 0:n.head)??[],...e.head??[]]}}});const e3={};var rt=Uint8Array,In=Uint16Array,t3=Int32Array,Wc=new rt([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),Kc=new rt([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),n3=new rt([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Jc=function(e,t){for(var n=new In(31),r=0;r<31;++r)n[r]=t+=1<>1|(_e&21845)<<1;qt=(qt&52428)>>2|(qt&13107)<<2,qt=(qt&61680)>>4|(qt&3855)<<4,Lo[_e]=((qt&65280)>>8|(qt&255)<<8)>>1}var dr=function(e,t,n){for(var r=e.length,a=0,o=new In(t);a>c]=u}else for(i=new In(r),a=0;a>15-e[a]);return i},Mr=new rt(288);for(var _e=0;_e<144;++_e)Mr[_e]=8;for(var _e=144;_e<256;++_e)Mr[_e]=9;for(var _e=256;_e<280;++_e)Mr[_e]=7;for(var _e=280;_e<288;++_e)Mr[_e]=8;var Zc=new rt(32);for(var _e=0;_e<32;++_e)Zc[_e]=5;var l3=dr(Mr,9,1),s3=dr(Zc,5,1),Xa=function(e){for(var t=e[0],n=1;nt&&(t=e[n]);return t},vt=function(e,t,n){var r=t/8|0;return(e[r]|e[r+1]<<8)>>(t&7)&n},eo=function(e,t){var n=t/8|0;return(e[n]|e[n+1]<<8|e[n+2]<<16)>>(t&7)},i3=function(e){return(e+7)/8|0},Xc=function(e,t,n){return(t==null||t<0)&&(t=0),(n==null||n>e.length)&&(n=e.length),new rt(e.subarray(t,n))},c3=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],ct=function(e,t,n){var r=new Error(t||c3[e]);if(r.code=e,Error.captureStackTrace&&Error.captureStackTrace(r,ct),!n)throw r;return r},u3=function(e,t,n,r){var a=e.length,o=0;if(!a||t.f&&!t.l)return n||new rt(0);var l=!n,i=l||t.i!=2,c=t.i;l&&(n=new rt(a*3));var u=function(ue){var Ee=n.length;if(ue>Ee){var m=new rt(Math.max(Ee*2,ue));m.set(n),n=m}},d=t.f||0,f=t.p||0,p=t.b||0,h=t.l,v=t.d,_=t.m,E=t.n,b=a*8;do{if(!h){d=vt(e,f,1);var C=vt(e,f+1,3);if(f+=3,C)if(C==1)h=l3,v=s3,_=9,E=5;else if(C==2){var P=vt(e,f,31)+257,H=vt(e,f+10,15)+4,O=P+vt(e,f+5,31)+1;f+=14;for(var Q=new rt(O),I=new rt(19),U=0;U>4;if(y<16)Q[U++]=y;else{var W=0,ee=0;for(y==16?(ee=3+vt(e,f,3),f+=2,W=Q[U-1]):y==17?(ee=3+vt(e,f,7),f+=3):y==18&&(ee=11+vt(e,f,127),f+=7);ee--;)Q[U++]=W}}var K=Q.subarray(0,P),De=Q.subarray(P);_=Xa(K),E=Xa(De),h=dr(K,_,1),v=dr(De,E,1)}else ct(1);else{var y=i3(f)+4,A=e[y-4]|e[y-3]<<8,D=y+A;if(D>a){c&&ct(0);break}i&&u(p+A),n.set(e.subarray(y,D),p),t.b=p+=A,t.p=f=D*8,t.f=d;continue}if(f>b){c&&ct(0);break}}i&&u(p+131072);for(var wt=(1<<_)-1,lt=(1<>4;if(f+=W&15,f>b){c&&ct(0);break}if(W||ct(2),Xe<256)n[p++]=Xe;else if(Xe==256){Fe=f,h=null;break}else{var It=Xe-254;if(Xe>264){var U=Xe-257,At=Wc[U];It=vt(e,f,(1<>4;Oe||ct(3),f+=Oe&15;var De=o3[S];if(S>3){var At=Kc[S];De+=eo(e,f)&(1<b){c&&ct(0);break}i&&u(p+131072);var q=p+It;if(p>4>7||(e[0]<<8|e[1])%31)&&ct(6,"invalid zlib data"),(e[1]>>5&1)==+!t&&ct(6,"invalid zlib data: "+(e[1]&32?"need":"unexpected")+" dictionary"),(e[1]>>3&4)+2};function p3(e,t){return u3(e.subarray(f3(e,t),-4),{i:2},t,t)}var Bo=typeof TextDecoder<"u"&&new TextDecoder,h3=0;try{Bo.decode(d3,{stream:!0}),h3=1}catch{}var m3=function(e){for(var t="",n=0;;){var r=e[n++],a=(r>127)+(r>223)+(r>239);if(n+a>e.length)return{s:t,r:Xc(e,n-1)};a?a==3?(r=((r&15)<<18|(e[n++]&63)<<12|(e[n++]&63)<<6|e[n++]&63)-65536,t+=String.fromCharCode(55296|r>>10,56320|r&1023)):a&1?t+=String.fromCharCode((r&31)<<6|e[n++]&63):t+=String.fromCharCode((r&15)<<12|(e[n++]&63)<<6|e[n++]&63):t+=String.fromCharCode(r)}};function v3(e,t){{for(var n=new rt(e.length),r=0;r{var r;const n=(r=ln())==null?void 0:r.appContext.components;return n?e in n||qe(e)in n||_n(qe(e))in n:!1},y3=Object.keys;function eu(e,t){let n,r,a;const o=z(!0),l=()=>{o.value=!0,a()};ae(e,l,{flush:"sync"});const i=typeof t=="function"?t:t.get,c=typeof t=="function"?void 0:t.set,u=rl((d,f)=>(r=d,a=f,{get(){return o.value&&(n=i(),o.value=!1),r(),n},set(p){c==null||c(p)}}));return Object.isExtensible(u)&&(u.trigger=l),u}function Lt(e){return Ti()?(f2(e),!0):!1}function Me(e){return typeof e=="function"?e():bt(e)}const bn=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const b3=e=>e!=null,E3=Object.prototype.toString,_3=e=>E3.call(e)==="[object Object]",St=()=>{},Io=w3();function w3(){var e,t;return bn&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&(/iP(ad|hone|od)/.test(window.navigator.userAgent)||((t=window==null?void 0:window.navigator)==null?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function gl(e,t){function n(...r){return new Promise((a,o)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(a).catch(o)})}return n}const tu=e=>e();function A3(e,t={}){let n,r,a=St;const o=i=>{clearTimeout(i),a(),a=St};return i=>{const c=Me(e),u=Me(t.maxWait);return n&&o(n),c<=0||u!==void 0&&u<=0?(r&&(o(r),r=null),Promise.resolve(i())):new Promise((d,f)=>{a=t.rejectOnCancel?f:d,u&&!r&&(r=setTimeout(()=>{n&&o(n),r=null,d(i())},u)),n=setTimeout(()=>{r&&o(r),r=null,d(i())},c)})}}function C3(...e){let t=0,n,r=!0,a=St,o,l,i,c,u;!$e(e[0])&&typeof e[0]=="object"?{delay:l,trailing:i=!0,leading:c=!0,rejectOnCancel:u=!1}=e[0]:[l,i=!0,c=!0,u=!1]=e;const d=()=>{n&&(clearTimeout(n),n=void 0,a(),a=St)};return p=>{const h=Me(l),v=Date.now()-t,_=()=>o=p();return d(),h<=0?(t=Date.now(),_()):(v>h&&(c||!r)?(t=Date.now(),_()):i&&(o=new Promise((E,b)=>{a=u?b:E,n=setTimeout(()=>{t=Date.now(),r=!0,E(_()),d()},Math.max(0,h-v))})),!c&&!n&&(n=setTimeout(()=>r=!0,h)),r=!1,o)}}function k3(e=tu){const t=z(!0);function n(){t.value=!1}function r(){t.value=!0}const a=(...o)=>{t.value&&e(...o)};return{isActive:on(t),pause:n,resume:r,eventFilter:a}}function T3(e){let t;function n(){return t||(t=e()),t}return n.reset=async()=>{const r=t;t=void 0,r&&await r},n}function nu(e){return ln()}function S3(...e){if(e.length!==1)return zn(...e);const t=e[0];return typeof t=="function"?on(rl(()=>({get:t,set:St}))):z(t)}function ru(e,t=200,n={}){return gl(A3(t,n),e)}function x3(e,t=200,n=!1,r=!0,a=!1){return gl(C3(t,n,r,a),e)}function L3(e,t,n={}){const{eventFilter:r=tu,...a}=n;return ae(e,gl(r,t),a)}function B3(e,t,n={}){const{eventFilter:r,...a}=n,{eventFilter:o,pause:l,resume:i,isActive:c}=k3(r);return{stop:L3(e,t,{...a,eventFilter:o}),pause:l,resume:i,isActive:c}}function Da(e,t=!0,n){nu()?se(e,n):t?e():_t(e)}function I3(e,t){nu()&&Ft(e,t)}function P3(e,t=1e3,n={}){const{immediate:r=!0,immediateCallback:a=!1}=n;let o=null;const l=z(!1);function i(){o&&(clearInterval(o),o=null)}function c(){l.value=!1,i()}function u(){const d=Me(t);d<=0||(l.value=!0,a&&e(),i(),o=setInterval(e,d))}if(r&&bn&&u(),$e(t)||typeof t=="function"){const d=ae(t,()=>{l.value&&bn&&u()});Lt(d)}return Lt(c),{isActive:l,pause:c,resume:u}}function D3(e,t,n={}){const{immediate:r=!0}=n,a=z(!1);let o=null;function l(){o&&(clearTimeout(o),o=null)}function i(){a.value=!1,l()}function c(...u){l(),a.value=!0,o=setTimeout(()=>{a.value=!1,o=null,e(...u)},Me(t))}return r&&(a.value=!0,bn&&c()),Lt(i),{isPending:on(a),start:c,stop:i}}function wr(e=!1,t={}){const{truthyValue:n=!0,falsyValue:r=!1}=t,a=$e(e),o=z(e);function l(i){if(arguments.length)return o.value=i,o.value;{const c=Me(n);return o.value=o.value===c?Me(r):c,o.value}}return a?l:[o,l]}function Ke(e){var t;const n=Me(e);return(t=n==null?void 0:n.$el)!=null?t:n}const ot=bn?window:void 0,au=bn?window.document:void 0,ou=bn?window.navigator:void 0;function Se(...e){let t,n,r,a;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,r,a]=e,t=ot):[t,n,r,a]=e,!t)return St;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const o=[],l=()=>{o.forEach(d=>d()),o.length=0},i=(d,f,p,h)=>(d.addEventListener(f,p,h),()=>d.removeEventListener(f,p,h)),c=ae(()=>[Ke(t),Me(a)],([d,f])=>{if(l(),!d)return;const p=_3(f)?{...f}:f;o.push(...n.flatMap(h=>r.map(v=>i(d,h,v,p))))},{immediate:!0,flush:"post"}),u=()=>{c(),l()};return Lt(u),u}let Ys=!1;function lu(e,t,n={}){const{window:r=ot,ignore:a=[],capture:o=!0,detectIframe:l=!1}=n;if(!r)return St;Io&&!Ys&&(Ys=!0,Array.from(r.document.body.children).forEach(p=>p.addEventListener("click",St)),r.document.documentElement.addEventListener("click",St));let i=!0;const c=p=>a.some(h=>{if(typeof h=="string")return Array.from(r.document.querySelectorAll(h)).some(v=>v===p.target||p.composedPath().includes(v));{const v=Ke(h);return v&&(p.target===v||p.composedPath().includes(v))}}),d=[Se(r,"click",p=>{const h=Ke(e);if(!(!h||h===p.target||p.composedPath().includes(h))){if(p.detail===0&&(i=!c(p)),!i){i=!0;return}t(p)}},{passive:!0,capture:o}),Se(r,"pointerdown",p=>{const h=Ke(e);i=!c(p)&&!!(h&&!p.composedPath().includes(h))},{passive:!0}),l&&Se(r,"blur",p=>{setTimeout(()=>{var h;const v=Ke(e);((h=r.document.activeElement)==null?void 0:h.tagName)==="IFRAME"&&!(v!=null&&v.contains(r.document.activeElement))&&t(p)},0)})].filter(Boolean);return()=>d.forEach(p=>p())}function O3(){const e=z(!1),t=ln();return t&&se(()=>{e.value=!0},t),e}function Un(e){const t=O3();return w(()=>(t.value,!!e()))}function M3(e,t={}){const{immediate:n=!0,fpsLimit:r=void 0,window:a=ot}=t,o=z(!1),l=r?1e3/r:null;let i=0,c=null;function u(p){if(!o.value||!a)return;i||(i=p);const h=p-i;if(l&&hn&&"matchMedia"in n&&typeof n.matchMedia=="function");let a;const o=z(!1),l=u=>{o.value=u.matches},i=()=>{a&&("removeEventListener"in a?a.removeEventListener("change",l):a.removeListener(l))},c=Qi(()=>{r.value&&(i(),a=n.matchMedia(Me(e)),"addEventListener"in a?a.addEventListener("change",l):a.addListener(l),o.value=a.matches)});return Lt(()=>{c(),i(),a=void 0}),o}function Zs(e,t={}){const{controls:n=!1,navigator:r=ou}=t,a=Un(()=>r&&"permissions"in r);let o;const l=typeof e=="string"?{name:e}:e,i=z(),c=()=>{o&&(i.value=o.state)},u=T3(async()=>{if(a.value){if(!o)try{o=await r.permissions.query(l),Se(o,"change",c),c()}catch{i.value="prompt"}return o}});return u(),n?{state:i,isSupported:a,query:u}:i}function R3(e={}){const{navigator:t=ou,read:n=!1,source:r,copiedDuring:a=1500,legacy:o=!1}=e,l=Un(()=>t&&"clipboard"in t),i=Zs("clipboard-read"),c=Zs("clipboard-write"),u=w(()=>l.value||o),d=z(""),f=z(!1),p=D3(()=>f.value=!1,a);function h(){l.value&&b(i.value)?t.clipboard.readText().then(C=>{d.value=C}):d.value=E()}u.value&&n&&Se(["copy","cut"],h);async function v(C=Me(r)){u.value&&C!=null&&(l.value&&b(c.value)?await t.clipboard.writeText(C):_(C),d.value=C,f.value=!0,p.start())}function _(C){const y=document.createElement("textarea");y.value=C??"",y.style.position="absolute",y.style.opacity="0",document.body.appendChild(y),y.select(),document.execCommand("copy"),y.remove()}function E(){var C,y,A;return(A=(y=(C=document==null?void 0:document.getSelection)==null?void 0:C.call(document))==null?void 0:y.toString())!=null?A:""}function b(C){return C==="granted"||C==="prompt"}return{isSupported:u,text:d,copied:f,copy:v}}const Xr=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},ea="__vueuse_ssr_handlers__",$3=F3();function F3(){return ea in Xr||(Xr[ea]=Xr[ea]||{}),Xr[ea]}function H3(e,t){return $3[e]||t}function N3(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const V3={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},Xs="vueuse-storage";function cn(e,t,n,r={}){var a;const{flush:o="pre",deep:l=!0,listenToStorageChanges:i=!0,writeDefaults:c=!0,mergeDefaults:u=!1,shallow:d,window:f=ot,eventFilter:p,onError:h=I=>{console.error(I)},initOnMounted:v}=r,_=(d?xe:z)(typeof t=="function"?t():t);if(!n)try{n=H3("getDefaultStorage",()=>{var I;return(I=ot)==null?void 0:I.localStorage})()}catch(I){h(I)}if(!n)return _;const E=Me(t),b=N3(E),C=(a=r.serializer)!=null?a:V3[b],{pause:y,resume:A}=B3(_,()=>P(_.value),{flush:o,deep:l,eventFilter:p});f&&i&&Da(()=>{Se(f,"storage",O),Se(f,Xs,Q),v&&O()}),v||O();function D(I,U){f&&f.dispatchEvent(new CustomEvent(Xs,{detail:{key:e,oldValue:I,newValue:U,storageArea:n}}))}function P(I){try{const U=n.getItem(e);if(I==null)D(U,null),n.removeItem(e);else{const R=C.write(I);U!==R&&(n.setItem(e,R),D(U,R))}}catch(U){h(U)}}function H(I){const U=I?I.newValue:n.getItem(e);if(U==null)return c&&E!=null&&n.setItem(e,C.write(E)),E;if(!I&&u){const R=C.read(U);return typeof u=="function"?u(R,E):b==="object"&&!Array.isArray(R)?{...E,...R}:R}else return typeof U!="string"?U:C.read(U)}function O(I){if(!(I&&I.storageArea!==n)){if(I&&I.key==null){_.value=E;return}if(!(I&&I.key!==e)){y();try{(I==null?void 0:I.newValue)!==C.write(_.value)&&(_.value=H(I))}catch(U){h(U)}finally{I?_t(A):A()}}}}function Q(I){O(I.detail)}return _}function j3(e){return su("(prefers-color-scheme: dark)",e)}function z3(e,t,n={}){const{window:r=ot,...a}=n;let o;const l=Un(()=>r&&"MutationObserver"in r),i=()=>{o&&(o.disconnect(),o=void 0)},c=w(()=>{const p=Me(e),h=(Array.isArray(p)?p:[p]).map(Ke).filter(b3);return new Set(h)}),u=ae(()=>c.value,p=>{i(),l.value&&r&&p.size&&(o=new MutationObserver(t),p.forEach(h=>o.observe(h,a)))},{immediate:!0,flush:"post"}),d=()=>o==null?void 0:o.takeRecords(),f=()=>{i(),u()};return Lt(f),{isSupported:l,stop:f,takeRecords:d}}function q3(e,t,n={}){const{window:r=ot,...a}=n;let o;const l=Un(()=>r&&"ResizeObserver"in r),i=()=>{o&&(o.disconnect(),o=void 0)},c=w(()=>Array.isArray(e)?e.map(f=>Ke(f)):[Ke(e)]),u=ae(c,f=>{if(i(),l.value&&r){o=new ResizeObserver(t);for(const p of f)p&&o.observe(p,a)}},{immediate:!0,flush:"post"}),d=()=>{i(),u()};return Lt(d),{isSupported:l,stop:d}}function G3(e,t={width:0,height:0},n={}){const{window:r=ot,box:a="content-box"}=n,o=w(()=>{var f,p;return(p=(f=Ke(e))==null?void 0:f.namespaceURI)==null?void 0:p.includes("svg")}),l=z(t.width),i=z(t.height),{stop:c}=q3(e,([f])=>{const p=a==="border-box"?f.borderBoxSize:a==="content-box"?f.contentBoxSize:f.devicePixelContentBoxSize;if(r&&o.value){const h=Ke(e);if(h){const v=r.getComputedStyle(h);l.value=Number.parseFloat(v.width),i.value=Number.parseFloat(v.height)}}else if(p){const h=Array.isArray(p)?p:[p];l.value=h.reduce((v,{inlineSize:_})=>v+_,0),i.value=h.reduce((v,{blockSize:_})=>v+_,0)}else l.value=f.contentRect.width,i.value=f.contentRect.height},n);Da(()=>{const f=Ke(e);f&&(l.value="offsetWidth"in f?f.offsetWidth:t.width,i.value="offsetHeight"in f?f.offsetHeight:t.height)});const u=ae(()=>Ke(e),f=>{l.value=f?t.width:0,i.value=f?t.height:0});function d(){c(),u()}return{width:l,height:i,stop:d}}const ei=["fullscreenchange","webkitfullscreenchange","webkitendfullscreen","mozfullscreenchange","MSFullscreenChange"];function yl(e,t={}){const{document:n=au,autoExit:r=!1}=t,a=w(()=>{var b;return(b=Ke(e))!=null?b:n==null?void 0:n.querySelector("html")}),o=z(!1),l=w(()=>["requestFullscreen","webkitRequestFullscreen","webkitEnterFullscreen","webkitEnterFullScreen","webkitRequestFullScreen","mozRequestFullScreen","msRequestFullscreen"].find(b=>n&&b in n||a.value&&b in a.value)),i=w(()=>["exitFullscreen","webkitExitFullscreen","webkitExitFullScreen","webkitCancelFullScreen","mozCancelFullScreen","msExitFullscreen"].find(b=>n&&b in n||a.value&&b in a.value)),c=w(()=>["fullScreen","webkitIsFullScreen","webkitDisplayingFullscreen","mozFullScreen","msFullscreenElement"].find(b=>n&&b in n||a.value&&b in a.value)),u=["fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement"].find(b=>n&&b in n),d=Un(()=>a.value&&n&&l.value!==void 0&&i.value!==void 0&&c.value!==void 0),f=()=>u?(n==null?void 0:n[u])===a.value:!1,p=()=>{if(c.value){if(n&&n[c.value]!=null)return n[c.value];{const b=a.value;if((b==null?void 0:b[c.value])!=null)return!!b[c.value]}}return!1};async function h(){if(!(!d.value||!o.value)){if(i.value)if((n==null?void 0:n[i.value])!=null)await n[i.value]();else{const b=a.value;(b==null?void 0:b[i.value])!=null&&await b[i.value]()}o.value=!1}}async function v(){if(!d.value||o.value)return;p()&&await h();const b=a.value;l.value&&(b==null?void 0:b[l.value])!=null&&(await b[l.value](),o.value=!0)}async function _(){await(o.value?h():v())}const E=()=>{const b=p();(!b||b&&f())&&(o.value=b)};return Se(n,ei,E,!1),Se(()=>Ke(a),ei,E,!1),r&&Lt(h),{isSupported:d,isFullscreen:o,enter:v,exit:h,toggle:_}}function no(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function J5(e,t,n={}){const{window:r=ot}=n;return cn(e,t,r==null?void 0:r.localStorage,n)}function U3(e={}){const{controls:t=!1,interval:n="requestAnimationFrame"}=e,r=z(new Date),a=()=>r.value=new Date,o=n==="requestAnimationFrame"?M3(a,{immediate:!0}):P3(a,n,{immediate:!0});return t?{now:r,...o}:r}function ro(e,t=St,n={}){const{immediate:r=!0,manual:a=!1,type:o="text/javascript",async:l=!0,crossOrigin:i,referrerPolicy:c,noModule:u,defer:d,document:f=au,attrs:p={}}=n,h=z(null);let v=null;const _=C=>new Promise((y,A)=>{const D=O=>(h.value=O,y(O),O);if(!f){y(!1);return}let P=!1,H=f.querySelector(`script[src="${Me(e)}"]`);H?H.hasAttribute("data-loaded")&&D(H):(H=f.createElement("script"),H.type=o,H.async=l,H.src=Me(e),d&&(H.defer=d),i&&(H.crossOrigin=i),u&&(H.noModule=u),c&&(H.referrerPolicy=c),Object.entries(p).forEach(([O,Q])=>H==null?void 0:H.setAttribute(O,Q)),P=!0),H.addEventListener("error",O=>A(O)),H.addEventListener("abort",O=>A(O)),H.addEventListener("load",()=>{H.setAttribute("data-loaded","true"),t(H),D(H)}),P&&(H=f.head.appendChild(H)),C||D(H)}),E=(C=!0)=>(v||(v=_(C)),v),b=()=>{if(!f)return;v=null,h.value&&(h.value=null);const C=f.querySelector(`script[src="${Me(e)}"]`);C&&f.head.removeChild(C)};return r&&!a&&Da(E),a||I3(b),{scriptTag:h,load:E,unload:b}}function iu(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const ta=new WeakMap;function bl(e,t=!1){const n=z(t);let r=null;ae(S3(e),l=>{const i=no(Me(l));if(i){const c=i;ta.get(c)||ta.set(c,c.style.overflow),n.value&&(c.style.overflow="hidden")}},{immediate:!0});const a=()=>{const l=no(Me(e));!l||n.value||(Io&&(r=Se(l,"touchmove",i=>{W3(i)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},o=()=>{var l;const i=no(Me(e));!i||!n.value||(Io&&(r==null||r()),i.style.overflow=(l=ta.get(i))!=null?l:"",ta.delete(i),n.value=!1)};return Lt(o),w({get(){return n.value},set(l){l?a():o()}})}function cu(e,t,n={}){const{window:r=ot}=n;return cn(e,t,r==null?void 0:r.sessionStorage,n)}function K3(e={}){const{window:t=ot,behavior:n="auto"}=e;if(!t)return{x:z(0),y:z(0)};const r=z(t.scrollX),a=z(t.scrollY),o=w({get(){return r.value},set(i){scrollTo({left:i,behavior:n})}}),l=w({get(){return a.value},set(i){scrollTo({top:i,behavior:n})}});return Se(t,"scroll",()=>{r.value=t.scrollX,a.value=t.scrollY},{capture:!1,passive:!0}),{x:o,y:l}}function J3(e={}){const{window:t=ot,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:r=Number.POSITIVE_INFINITY,listenOrientation:a=!0,includeScrollbar:o=!0}=e,l=z(n),i=z(r),c=()=>{t&&(o?(l.value=t.innerWidth,i.value=t.innerHeight):(l.value=t.document.documentElement.clientWidth,i.value=t.document.documentElement.clientHeight))};if(c(),Da(c),Se("resize",c,{passive:!0}),a){const u=su("(orientation: portrait)");ae(u,()=>c())}return{width:l,height:i}}var Q3=M({name:"FontIcon",props:{icon:{type:String,default:""},color:{type:String,default:""},size:{type:[String,Number],default:""}},setup(e){const t=w(()=>{const r=["font-icon icon"],a=`${e.icon}`;return r.push(a),r}),n=w(()=>{const r={};return e.color&&(r.color=e.color),e.size&&(r["font-size"]=Number.isNaN(Number(e.size))?e.size:`${e.size}px`),y3(r).length?r:null});return()=>e.icon?s("span",{key:e.icon,class:t.value,style:n.value}):null}});const uu=({type:e="info",text:t="",vertical:n,color:r},{slots:a})=>{var o;return s("span",{class:["vp-badge",e,{diy:r}],style:{verticalAlign:n??!1,backgroundColor:r??!1}},((o=a.default)==null?void 0:o.call(a))??t)};uu.displayName="Badge";const du=({title:e,desc:t="",logo:n,background:r,color:a,link:o})=>{const l=[n?s("img",{class:"vp-card-logo",src:be(n),loading:"lazy","no-view":""}):null,s("div",{class:"vp-card-content"},[s("div",{class:"vp-card-title",innerHTML:e}),s("hr"),s("div",{class:"vp-card-desc",innerHTML:t})])],i={};return r&&(i.background=r),a&&(i.color=a),o?xa(o)?s("a",{class:"vp-card",href:o,target:"_blank",style:i},l):s(Ie,{to:o,class:"vp-card",style:i},()=>l):s("div",{class:"vp-card",style:i},l)};du.displayName="VPCard";const Y3=Ze({enhance:({app:e})=>{to("FontIcon")||e.component("FontIcon",Q3),to("Badge")||e.component("Badge",uu),to("VPCard")||e.component("VPCard",du)},setup:()=>{ro("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/js/brands.min.js",()=>{},{attrs:{"data-auto-replace-svg":"nest"}}),ro("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/js/solid.min.js",()=>{},{attrs:{"data-auto-replace-svg":"nest"}}),ro("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/js/fontawesome.min.js",()=>{},{attrs:{"data-auto-replace-svg":"nest"}})},rootComponents:[]}),ti=async(e,t)=>{const{path:n,query:r}=e.currentRoute.value,{scrollBehavior:a}=e.options;e.options.scrollBehavior=void 0,await e.replace({path:n,query:r,hash:t}),e.options.scrollBehavior=a},Z3=({headerLinkSelector:e,headerAnchorSelector:t,delay:n,offset:r=5})=>{const a=wn();Se("scroll",ru(()=>{var v,_;const l=Math.max(window.scrollY,document.documentElement.scrollTop,document.body.scrollTop);if(Math.abs(l-0)f.some(b=>b.hash===E.hash));for(let E=0;E=(((v=b.parentElement)==null?void 0:v.offsetTop)??0)-r,A=!C||l<(((_=C.parentElement)==null?void 0:_.offsetTop)??0)-r;if(!(y&&A))continue;const P=decodeURIComponent(a.currentRoute.value.hash),H=decodeURIComponent(b.hash);if(P===H)return;if(d){for(let O=E+1;O{const t=Ye();return w(()=>e[t.value]??{})},ap=(e,t)=>{var r;const n=(r=(t==null?void 0:t._instance)||ln())==null?void 0:r.appContext.components;return n?e in n||qe(e)in n||_n(qe(e))in n:!1},ao=e=>typeof e=="number",ni=(e,t)=>we(e)&&e.startsWith(t),op=(e,t)=>we(e)&&e.endsWith(t),lp=Object.entries,sp=Object.keys,ip=e=>new Promise(t=>setTimeout(t,e));let fu=e=>we(e.title)?{title:e.title}:null;const pu=Symbol(""),cp=e=>{fu=e},up=()=>ve(pu),dp=e=>{e.provide(pu,fu)};var fp={"/":{title:"目录",empty:"暂无目录"}};const pp=M({name:"Catalog",props:{base:{type:String,default:""},level:{type:Number,default:3},index:Boolean,hideHeading:Boolean},setup(e){const t=up(),n=El(fp),r=ge(),a=Jf(),o=qc(),i=xe(lp(a.value).map(([u,{meta:d}])=>{const f=t(d);if(!f)return null;const p=u.split("/").length;return{level:op(u,"/")?p-2:p-1,base:u.replace(/\/[^/]+\/?$/,"/"),path:u,...f}}).filter(u=>qn(u)&&we(u.title))),c=w(()=>{const u=e.base?Dd(Pc(e.base)):r.value.path.replace(/\/[^/]+$/,"/"),d=u.split("/").length-2,f=[];return i.value.filter(({level:p,path:h})=>{if(!ni(h,u)||h===u)return!1;if(u==="/"){const v=sp(o.value.locales).filter(_=>_!=="/");if(h==="/404.html"||v.some(_=>ni(h,_)))return!1}return p-d<=e.level}).sort(({title:p,level:h,order:v},{title:_,level:E,order:b})=>{const C=h-E;return C||(ao(v)?ao(b)?v>0?b>0?v-b:-1:b<0?v-b:1:v:ao(b)?b:p.localeCompare(_))}).forEach(p=>{var _;const{base:h,level:v}=p;switch(v-d){case 1:{f.push(p);break}case 2:{const E=f.find(b=>b.path===h);E&&(E.children??(E.children=[])).push(p);break}default:{const E=f.find(b=>b.path===h.replace(/\/[^/]+\/$/,"/"));if(E){const b=(_=E.children)==null?void 0:_.find(C=>C.path===h);b&&(b.children??(b.children=[])).push(p)}}}}),f});return()=>{const u=c.value.some(d=>d.children);return s("div",{class:["vp-catalog-wrapper",{index:e.index}]},[e.hideHeading?null:s("h2",{class:"vp-catalog-main-title"},n.value.title),c.value.length?s(e.index?"ol":"ul",{class:["vp-catalogs",{deep:u}]},c.value.map(({children:d=[],title:f,path:p,content:h})=>{const v=s(Ie,{class:"vp-catalog-title",to:p},()=>h?s(h):f);return s("li",{class:"vp-catalog"},u?[s("h3",{id:f,class:["vp-catalog-child-title",{"has-children":d.length}]},[s("a",{href:`#${f}`,class:"vp-catalog-header-anchor","aria-hidden":!0},"#"),v]),d.length?s(e.index?"ol":"ul",{class:"vp-child-catalogs"},d.map(({children:_=[],content:E,path:b,title:C})=>s("li",{class:"vp-child-catalog"},[s("div",{class:["vp-catalog-sub-title",{"has-children":_.length}]},[s("a",{href:`#${C}`,class:"vp-catalog-header-anchor"},"#"),s(Ie,{class:"vp-catalog-title",to:b},()=>E?s(E):C)]),_.length?s(e.index?"ol":"div",{class:e.index?"vp-sub-catalogs":"vp-sub-catalogs-wrapper"},_.map(({content:y,path:A,title:D})=>e.index?s("li",{class:"vp-sub-catalog"},s(Ie,{to:A},()=>y?s(y):D)):s(Ie,{class:"vp-sub-catalog-link",to:A},()=>y?s(y):D))):null]))):null]:s("div",{class:"vp-catalog-child-title"},v))})):s("p",{class:"vp-empty-catalog"},n.value.empty)])}}}),hp=Ze({enhance:({app:e})=>{dp(e),ap("Catalog",e)||e.component("Catalog",pp)}}),hu=e=>{const t=Ye();return w(()=>e[t.value]??{})},Mn=(e,t)=>{var r;const n=(r=ln())==null?void 0:r.appContext.components;return n?e in n||qe(e)in n||_n(qe(e))in n:!1},Po=Array.isArray,Oa=(e,t)=>we(e)&&e.startsWith(t),Ma=Object.entries,En=Object.keys,_l=e=>{if(e){if(typeof e=="number")return new Date(e);const t=Date.parse(e.toString());if(!Number.isNaN(t))return new Date(t)}return null},Rr=e=>Oa(e,"/"),mp="http://.",vp=(e,t)=>{if(Rr(e)||typeof t!="string")return xt(e);const n=t.slice(0,t.lastIndexOf("/"));return xt(new URL(`${n}/${encodeURI(e)}`,mp).pathname)},gp=e=>new Promise(t=>setTimeout(t,e));var yp={"/":{backToTop:"返回顶部"}};const bp=M({name:"BackToTop",setup(){const e=he(),t=hu(yp),n=xe(),{height:r}=G3(n),{height:a}=J3(),{y:o}=K3(),l=w(()=>e.value.backToTop!==!1&&o.value>100),i=w(()=>o.value/(r.value-a.value)*100);return se(()=>{n.value=document.body}),()=>s(Rt,{name:"back-to-top"},()=>l.value?s("button",{type:"button",class:"vp-back-to-top-button","aria-label":t.value.backToTop,onClick:()=>{window.scrollTo({top:0,behavior:"smooth"})}},[s("span",{class:"vp-scroll-progress",role:"progressbar","aria-labelledby":"loadinglabel","aria-valuenow":i.value},s("svg",s("circle",{cx:"26",cy:"26",r:"24",fill:"none",stroke:"currentColor","stroke-width":"4","stroke-dasharray":`${Math.PI*i.value*.48} ${Math.PI*(100-i.value)*.48}`}))),s("div",{class:"back-to-top-icon"})]):null)}}),Ep=Ze({rootComponents:[bp]}),_p=s("svg",{class:"external-link-icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[s("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}),s("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"})]),mu=M({name:"ExternalLinkIcon",props:{locales:{type:Object,default:()=>({})}},setup(e){const t=Ye(),n=w(()=>e.locales[t.value]??{openInNewWindow:"open in new window"});return()=>s("span",[_p,s("span",{class:"external-link-icon-sr-only"},n.value.openInNewWindow)])}});var wp={};const Ap=wp,Cp=Ze({enhance({app:e}){e.component("ExternalLinkIcon",s(mu,{locales:Ap}))}});/** * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress * @license MIT - */const de={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
    '},status:null,set:e=>{const t=de.isStarted();e=oo(e,de.settings.minimum,1),de.status=e===1?null:e;const n=de.render(!t),r=n.querySelector(de.settings.barSelector),a=de.settings.speed,o=de.settings.easing;return n.offsetWidth,kp(l=>{na(r,{transform:"translate3d("+ri(e)+"%,0,0)",transition:"all "+a+"ms "+o}),e===1?(na(n,{transition:"none",opacity:"1"}),n.offsetWidth,setTimeout(function(){na(n,{transition:"all "+a+"ms linear",opacity:"0"}),setTimeout(function(){de.remove(),l()},a)},a)):setTimeout(()=>l(),a)}),de},isStarted:()=>typeof de.status=="number",start:()=>{de.status||de.set(0);const e=()=>{setTimeout(()=>{de.status&&(de.trickle(),e())},de.settings.trickleSpeed)};return de.settings.trickle&&e(),de},done:e=>!e&&!de.status?de:de.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=de.status;return t?(typeof e!="number"&&(e=(1-t)*oo(Math.random()*t,.1,.95)),t=oo(t+e,0,.994),de.set(t)):de.start()},trickle:()=>de.inc(Math.random()*de.settings.trickleRate),render:e=>{if(de.isRendered())return document.getElementById("nprogress");ai(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=de.settings.template;const n=t.querySelector(de.settings.barSelector),r=e?"-100":ri(de.status||0),a=document.querySelector(de.settings.parent);return na(n,{transition:"all 0 linear",transform:"translate3d("+r+"%,0,0)"}),a!==document.body&&ai(a,"nprogress-custom-parent"),a==null||a.appendChild(t),t},remove:()=>{oi(document.documentElement,"nprogress-busy"),oi(document.querySelector(de.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&Tp(e)},isRendered:()=>!!document.getElementById("nprogress")},oo=(e,t,n)=>en?n:e,ri=e=>(-1+e)*100,kp=function(){const e=[];function t(){const n=e.shift();n&&n(t)}return function(n){e.push(n),e.length===1&&t()}}(),na=function(){const e=["Webkit","O","Moz","ms"],t={};function n(l){return l.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(i,c){return c.toUpperCase()})}function r(l){const i=document.body.style;if(l in i)return l;let c=e.length;const u=l.charAt(0).toUpperCase()+l.slice(1);let d;for(;c--;)if(d=e[c]+u,d in i)return d;return l}function a(l){return l=n(l),t[l]??(t[l]=r(l))}function o(l,i,c){i=a(i),l.style[i]=c}return function(l,i){for(const c in i){const u=i[c];u!==void 0&&Object.prototype.hasOwnProperty.call(i,c)&&o(l,c,u)}}}(),vu=(e,t)=>(typeof e=="string"?e:wl(e)).indexOf(" "+t+" ")>=0,ai=(e,t)=>{const n=wl(e),r=n+t;vu(n,t)||(e.className=r.substring(1))},oi=(e,t)=>{const n=wl(e);if(!vu(e,t))return;const r=n.replace(" "+t+" "," ");e.className=r.substring(1,r.length-1)},wl=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),Tp=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)},Sp=()=>{se(()=>{const e=wn(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(n=>{t.has(n.path)||de.start()}),e.afterEach(n=>{t.add(n.path),de.done()})})},xp=Ze({setup(){Sp()}}),Lp=JSON.parse(`{"encrypt":{"config":{"/encrypt/":["$2a$10$ChYC07LQmX6Ilyl.Cqgv4enXptxrrlQMQUFIOQHpyoqxfTPK/jtn."]}},"author":{"name":"KSJ","url":"https://github.com/King-sj"},"logo":"/logo.svg","repo":"https://github.com/King-sj/KBlog","docsDir":"src","footer":"黔ICP备2024025117号-1 | 版权所有 © 2024-至今 KSJ ","displayFooter":true,"copyright":"MPL-2.0 许可证","blog":{"description":"一位热衷于探索全栈开发领域的技术爱好者,擅长将前端与后端技术无缝结合,致力于打造高效、优雅的解决方案。","intro":"/intro.html","medias":{"Email":"mailto:2175616761@qq.com","GitHub":"https://github.com/King-sj","QQ":"https://wpa.qq.com/msgrd?v=3&uin=2175616761&site=qq&menu=yes"}},"locales":{"/":{"lang":"zh-CN","navbarLocales":{"langName":"简体中文","selectLangAriaLabel":"选择语言"},"metaLocales":{"author":"作者","date":"写作日期","origin":"原创","views":"访问量","category":"分类","tag":"标签","readingTime":"阅读时间","words":"字数","toc":"此页内容","prev":"上一页","next":"下一页","lastUpdated":"上次编辑于","contributors":"贡献者","editLink":"在 GitHub 上编辑此页","print":"打印"},"blogLocales":{"article":"文章","articleList":"文章列表","category":"分类","tag":"标签","timeline":"时间轴","timelineTitle":"昨日不在","all":"全部","intro":"个人介绍","star":"星标","empty":"$text 为空"},"paginationLocales":{"prev":"上一页","next":"下一页","navigate":"跳转到","action":"前往","errorText":"请输入 1 到 $page 之前的页码!"},"outlookLocales":{"themeColor":"主题色","darkmode":"外观","fullscreen":"全屏"},"encryptLocales":{"iconLabel":"文章已加密","placeholder":"输入密码","remember":"记住密码","errorHint":"请输入正确的密码"},"routeLocales":{"skipToContent":"跳至主要內容","notFoundTitle":"页面不存在","notFoundMsg":["这里什么也没有","我们是怎么来到这儿的?","这 是 四 零 四 !","看起来你访问了一个失效的链接"],"back":"返回上一页","home":"带我回家","openInNewWindow":"Open in new window"},"navbar":["/","/tech/","/essays/","/projects/","/tutorials/","/resources/",{"text":"友链","children":[{"text":"KSJ-Blog","link":"https://bupt.online"},{"text":"KSJ-Blog-GitHub","link":"https://king-sj.github.io"}]}],"sidebar":{"/":["","intro"],"/tech/":[{"text":"技术","children":"structure","collapsible":true}],"/essay/":[{"text":"随笔","children":"structure","collapsible":true}],"/tutorials/":[{"text":"教程","children":"structure","icon":"book","collapsible":true}],"/resources/":[{"text":"资源","children":"structure","icon":"book","collapsible":true}]}}}}`),Bp=z(Lp),gu=()=>Bp,yu=Symbol(""),Ip=()=>{const e=ve(yu);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},Pp=(e,t)=>{const{locales:n,...r}=e;return{...r,...n==null?void 0:n[t]}},Dp=Ze({enhance({app:e}){const t=gu(),n=e._context.provides[vl],r=w(()=>Pp(t.value,n.routeLocale.value));e.provide(yu,r),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return r.value}}})}});var Op={provider:"Giscus",lightTheme:"https://unpkg.com/vuepress-theme-hope@2.0.0-rc.38/templates/giscus/light.css",darkTheme:"https://unpkg.com/vuepress-theme-hope@2.0.0-rc.38/templates/giscus/dark.css",repo:"King-sj/KBlog",repoId:"R_kgDOL41Gpw",category:"Q&A",categoryId:"DIC_kwDOL41Gp84CiYFP",lazyLoading:!0};const Mp=Op;let Rp=Mp;const bu=Symbol(""),Eu=()=>ve(bu),$p=Eu,Fp=e=>{e.provide(bu,Rp)},_u=()=>s("svg",{xmlns:"http://www.w3.org/2000/svg",width:"32",height:"32",preserveAspectRatio:"xMidYMid",viewBox:"0 0 100 100"},[s("circle",{cx:"28",cy:"75",r:"11",fill:"currentColor"},s("animate",{attributeName:"fill-opacity",begin:"0s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"})),s("path",{fill:"none",stroke:"#88baf0","stroke-width":"10",d:"M28 47a28 28 0 0 1 28 28"},s("animate",{attributeName:"stroke-opacity",begin:"0.1s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"})),s("path",{fill:"none",stroke:"#88baf0","stroke-width":"10",d:"M28 25a50 50 0 0 1 50 50"},s("animate",{attributeName:"stroke-opacity",begin:"0.2s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"}))]);_u.displayName="LoadingIcon";const li=["ar","ca","da","de","en","eo","es","fa","fr","he","id","it","ja","ko","nl","pl","pt","ro","ru","th","tr","uk","uz","vi","zh-CN","zh-TW"],Hp=M({name:"GiscusComment",props:{identifier:{type:String,required:!0},darkmode:Boolean},setup(e){const t=$p(),n=Pa(),r=!!(t.repo&&t.repoId&&t.category&&t.categoryId),{repo:a,repoId:o,category:l,categoryId:i}=t,c=z(!1),u=w(()=>{if(li.includes(n.value))return n.value;const f=n.value.split("-")[0];return li.includes(f)?f:"en"}),d=w(()=>({repo:a,repoId:o,category:l,categoryId:i,lang:u.value,theme:e.darkmode?t.darkTheme||"dark":t.lightTheme||"light",mapping:t.mapping||"pathname",term:e.identifier,inputPosition:t.inputPosition||"top",reactionsEnabled:t.reactionsEnabled===!1?"0":"1",strict:t.strict===!1?"0":"1",loading:t.lazyLoading===!1?"eager":"lazy",emitMetadata:"0"}));return se(async()=>{await T(()=>import("./giscus--_FS5kYt.js"),[]),c.value=!0}),()=>r?s("div",{id:"comment",class:["giscus-wrapper",{"input-top":t.inputPosition!=="bottom"}]},c.value?s("giscus-widget",d.value):s(_u)):null}}),Np=M({name:"CommentService",props:{darkmode:Boolean},setup(e){const t=Eu(),n=ge(),r=he(),a=t.comment!==!1,o=w(()=>r.value.comment||a&&r.value.comment!==!1);return()=>s(Hp,{id:"vp-comment",identifier:r.value.commentID||n.value.path,darkmode:e.darkmode,style:{display:o.value?"block":"none"}})}}),Vp=Ze({enhance:({app:e})=>{Fp(e),e.component("CommentService",Np)}}),jp=/\b(?:Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini)/i,zp=()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator&&jp.test(navigator.userAgent),qp=({delay:e=500,duration:t=2e3,locales:n,selector:r,showInMobile:a})=>{const{copy:o,copied:l}=R3({legacy:!0,copiedDuring:t}),i=hu(n),c=ge(),u=p=>{if(!p.hasAttribute("copy-code-registered")){const h=document.createElement("button");h.type="button",h.classList.add("vp-copy-code-button"),h.innerHTML='
    ',h.setAttribute("aria-label",i.value.copy),h.setAttribute("data-copied",i.value.copied),p.parentElement&&p.parentElement.insertBefore(h,p),p.setAttribute("copy-code-registered","")}},d=()=>{_t().then(()=>gp(e)).then(()=>{r.forEach(p=>{document.querySelectorAll(p).forEach(u)})})},f=(p,h,v)=>{let{innerText:_=""}=h;/language-(shellscript|shell|bash|sh|zsh)/.test(p.classList.toString())&&(_=_.replace(/^ *(\$|>) /gm,"")),o(_).then(()=>{v.classList.add("copied"),ae(l,()=>{v.classList.remove("copied"),v.blur()},{once:!0})})};se(()=>{const p=!zp()||a;p&&d(),Se("click",h=>{const v=h.target;if(v.matches('div[class*="language-"] > button.copy')){const _=v.parentElement,E=v.nextElementSibling;E&&f(_,E,v)}else if(v.matches('div[class*="language-"] div.vp-copy-icon')){const _=v.parentElement,E=_.parentElement,b=_.nextElementSibling;b&&f(E,b,_)}}),ae(()=>c.value.path,()=>{p&&d()})})};var Gp={"/":{copy:"复制代码",copied:"已复制"}},Up=['.theme-hope-content div[class*="language-"] pre'];const Wp=500,Kp=2e3,Jp=Gp,Qp=Up,Yp=!1,Zp=Ze({setup:()=>{qp({selector:Qp,locales:Jp,duration:Kp,delay:Wp,showInMobile:Yp})}}),ra=cn("VUEPRESS_CODE_TAB_STORE",{});var Xp=M({name:"CodeTabs",props:{active:{type:Number,default:0},data:{type:Array,required:!0},id:{type:String,required:!0},tabId:{type:String,default:""}},slots:Object,setup(e,{slots:t}){const n=z(e.active),r=xe([]),a=()=>{e.tabId&&(ra.value[e.tabId]=e.data[n.value].id)},o=(u=n.value)=>{n.value=u{n.value=u>0?u-1:r.value.length-1,r.value[n.value].focus()},i=(u,d)=>{u.key===" "||u.key==="Enter"?(u.preventDefault(),n.value=d):u.key==="ArrowRight"?(u.preventDefault(),o()):u.key==="ArrowLeft"&&(u.preventDefault(),l()),e.tabId&&(ra.value[e.tabId]=e.data[n.value].id)},c=()=>{if(e.tabId){const u=e.data.findIndex(({id:d})=>ra.value[e.tabId]===d);if(u!==-1)return u}return e.active};return se(()=>{n.value=c(),ae(()=>ra.value[e.tabId],(u,d)=>{if(e.tabId&&u!==d){const f=e.data.findIndex(({id:p})=>p===u);f!==-1&&(n.value=f)}})}),()=>e.data.length?s("div",{class:"vp-code-tabs"},[s("div",{class:"vp-code-tabs-nav",role:"tablist"},e.data.map(({id:u},d)=>{const f=d===n.value;return s("button",{type:"button",ref:p=>{p&&(r.value[d]=p)},class:["vp-code-tab-nav",{active:f}],role:"tab","aria-controls":`codetab-${e.id}-${d}`,"aria-selected":f,onClick:()=>{n.value=d,a()},onKeydown:p=>i(p,d)},t[`title${d}`]({value:u,isActive:f}))})),e.data.map(({id:u},d)=>{const f=d===n.value;return s("div",{class:["vp-code-tab",{active:f}],id:`codetab-${e.id}-${d}`,role:"tabpanel","aria-expanded":f},[s("div",{class:"vp-code-tab-title"},t[`title${d}`]({value:u,isActive:f})),t[`tab${d}`]({value:u,isActive:f})])})]):null}});const ya=e=>{const t=atob(e);return g3(p3(v3(t)))},e4=e=>typeof e<"u",wu=Object.keys,t4=e=>typeof e<"u",n4=Object.keys,ie=({name:e="",color:t="currentColor"},{slots:n})=>{var r;return s("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${e}-icon`],viewBox:"0 0 1024 1024",fill:t,"aria-label":`${e} icon`},(r=n.default)==null?void 0:r.call(n))};ie.displayName="IconBase";const Al=({size:e=48,stroke:t=4,wrapper:n=!0,height:r=2*e})=>{const a=s("svg",{xmlns:"http://www.w3.org/2000/svg",width:e,height:e,preserveAspectRatio:"xMidYMid",viewBox:"25 25 50 50"},[s("animateTransform",{attributeName:"transform",type:"rotate",dur:"2s",keyTimes:"0;1",repeatCount:"indefinite",values:"0;360"}),s("circle",{cx:"50",cy:"50",r:"20",fill:"none",stroke:"currentColor","stroke-width":t,"stroke-linecap":"round"},[s("animate",{attributeName:"stroke-dasharray",dur:"1.5s",keyTimes:"0;0.5;1",repeatCount:"indefinite",values:"1,200;90,200;1,200"}),s("animate",{attributeName:"stroke-dashoffset",dur:"1.5s",keyTimes:"0;0.5;1",repeatCount:"indefinite",values:"0;-35px;-125px"})])]);return n?s("div",{class:"loading-icon-wrapper",style:`display:flex;align-items:center;justify-content:center;height:${r}px`},a):a};Al.displayName="LoadingIcon";const Au=(e,{slots:t})=>{var n;return(n=t.default)==null?void 0:n.call(t)},r4=e=>sn(e)?e:`https://github.com/${e}`,Cl=(e="")=>!sn(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,Cu=()=>s(ie,{name:"github"},()=>s("path",{d:"M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"}));Cu.displayName="GitHubIcon";const ku=()=>s(ie,{name:"gitee"},()=>s("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm242.97-533.34H482.39a23.7 23.7 0 0 0-23.7 23.7l-.03 59.28c0 13.08 10.59 23.7 23.7 23.7h165.96a23.7 23.7 0 0 1 23.7 23.7v11.85a71.1 71.1 0 0 1-71.1 71.1H375.71a23.7 23.7 0 0 1-23.7-23.7V423.11a71.1 71.1 0 0 1 71.1-71.1h331.8a23.7 23.7 0 0 0 23.7-23.7l.06-59.25a23.73 23.73 0 0 0-23.7-23.73H423.11a177.78 177.78 0 0 0-177.78 177.75v331.83c0 13.08 10.62 23.7 23.7 23.7h349.62a159.99 159.99 0 0 0 159.99-159.99V482.33a23.7 23.7 0 0 0-23.7-23.7z"}));ku.displayName="GiteeIcon";const Tu=()=>s(ie,{name:"bitbucket"},()=>s("path",{d:"M575.256 490.862c6.29 47.981-52.005 85.723-92.563 61.147-45.714-20.004-45.714-92.562-1.133-113.152 38.29-23.442 93.696 7.424 93.696 52.005zm63.451-11.996c-10.276-81.152-102.29-134.839-177.152-101.156-47.433 21.138-79.433 71.424-77.129 124.562 2.853 69.705 69.157 126.866 138.862 120.576S647.3 548.571 638.708 478.83zm136.558-309.723c-25.161-33.134-67.986-38.839-105.728-45.13-106.862-17.151-216.576-17.7-323.438 1.134-35.438 5.706-75.447 11.996-97.719 43.996 36.572 34.304 88.576 39.424 135.424 45.129 84.553 10.862 171.447 11.447 256 .585 47.433-5.705 99.987-10.276 135.424-45.714zm32.585 591.433c-16.018 55.99-6.839 131.438-66.304 163.986-102.29 56.576-226.304 62.867-338.87 42.862-59.43-10.862-129.135-29.696-161.72-85.723-14.3-54.858-23.442-110.848-32.585-166.84l3.438-9.142 10.276-5.157c170.277 112.567 408.576 112.567 579.438 0 26.844 8.01 6.84 40.558 6.29 60.014zm103.424-549.157c-19.42 125.148-41.728 249.71-63.415 374.272-6.29 36.572-41.728 57.162-71.424 72.558-106.862 53.724-231.424 62.866-348.562 50.286-79.433-8.558-160.585-29.696-225.134-79.433-30.28-23.443-30.28-63.415-35.986-97.134-20.005-117.138-42.862-234.277-57.161-352.585 6.839-51.42 64.585-73.728 107.447-89.71 57.16-21.138 118.272-30.866 178.87-36.571 129.134-12.58 261.157-8.01 386.304 28.562 44.581 13.13 92.563 31.415 122.844 69.705 13.714 17.7 9.143 40.01 6.29 60.014z"}));Tu.displayName="BitbucketIcon";const Su=()=>s(ie,{name:"source"},()=>s("path",{d:"M601.92 475.2c0 76.428-8.91 83.754-28.512 99.594-14.652 11.88-43.956 14.058-78.012 16.434-18.81 1.386-40.392 2.97-62.172 6.534-18.612 2.97-36.432 9.306-53.064 17.424V299.772c37.818-21.978 63.36-62.766 63.36-109.692 0-69.894-56.826-126.72-126.72-126.72S190.08 120.186 190.08 190.08c0 46.926 25.542 87.714 63.36 109.692v414.216c-37.818 21.978-63.36 62.766-63.36 109.692 0 69.894 56.826 126.72 126.72 126.72s126.72-56.826 126.72-126.72c0-31.086-11.286-59.598-29.7-81.576 13.266-9.504 27.522-17.226 39.996-19.206 16.038-2.574 32.868-3.762 50.688-5.148 48.312-3.366 103.158-7.326 148.896-44.55 61.182-49.698 74.25-103.158 75.24-187.902V475.2h-126.72zM316.8 126.72c34.848 0 63.36 28.512 63.36 63.36s-28.512 63.36-63.36 63.36-63.36-28.512-63.36-63.36 28.512-63.36 63.36-63.36zm0 760.32c-34.848 0-63.36-28.512-63.36-63.36s28.512-63.36 63.36-63.36 63.36 28.512 63.36 63.36-28.512 63.36-63.36 63.36zM823.68 158.4h-95.04V63.36h-126.72v95.04h-95.04v126.72h95.04v95.04h126.72v-95.04h95.04z"}));Su.displayName="SourceIcon";const a4=({link:e,type:t=Cl(e??"")})=>{if(!t)return null;const n=t.toLowerCase();return s(n==="bitbucket"?Tu:n==="github"?Cu:n==="gitlab"?"GitLab":n==="gitee"?ku:Su)};function o4(){const e=z(!1),t=ln();return t&&se(()=>{e.value=!0},t),e}function l4(e){return o4(),w(()=>!!e())}const s4=()=>l4(()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator),i4=()=>{const e=s4();return w(()=>e.value&&/\b(?:Android|iPhone)/i.test(navigator.userAgent))},c4=e=>[/\((ipad);[-\w),; ]+apple/i,/applecoremedia\/[\w.]+ \((ipad)/i,/\b(ipad)\d\d?,\d\d?[;\]].+ios/i].some(t=>t.test(e)),u4=e=>[/ip[honead]{2,4}\b(?:.*os ([\w]+) like mac|; opera)/i,/cfnetwork\/.+darwin/i].some(t=>t.test(e)),d4=e=>[/(mac os x) ?([\w. ]*)/i,/(macintosh|mac_powerpc\b)(?!.+haiku)/i].some(t=>t.test(e)),f4=(e,t=0)=>{let n=3735928559^t,r=1103547991^t;for(let a=0,o;a>>16,2246822507),n^=Math.imul(r^r>>>13,3266489909),r=Math.imul(r^r>>>16,2246822507),r^=Math.imul(n^n>>>13,3266489909),4294967296*(2097151&r)+(n>>>0)},Ra=(e,t)=>f4(e)%t;let p4=class{constructor(){this.messageElements={};const t="message-container",n=document.getElementById(t);n?this.containerElement=n:(this.containerElement=document.createElement("div"),this.containerElement.id=t,document.body.appendChild(this.containerElement))}pop(t,n=2e3){const r=document.createElement("div"),a=Date.now();return r.className="message move-in",r.innerHTML=t,this.containerElement.appendChild(r),this.messageElements[a]=r,n>0&&setTimeout(()=>{this.close(a)},n),a}close(t){if(t){const n=this.messageElements[t];n.classList.remove("move-in"),n.classList.add("move-out"),n.addEventListener("animationend",()=>{n.remove(),delete this.messageElements[t]})}else n4(this.messageElements).forEach(n=>this.close(Number(n)))}destroy(){document.body.removeChild(this.containerElement)}};const xu=/#.*$/u,h4=e=>{const t=xu.exec(e);return t?t[0]:""},si=e=>decodeURI(e).replace(xu,"").replace(/\/index\.html$/iu,"/").replace(/\.html$/iu,"").replace(/(README|index)?\.md$/iu,""),Lu=(e,t)=>{if(!t4(t))return!1;const n=si(e.path),r=si(t),a=h4(t);return a?a===e.hash&&(!r||n===r):n===r};var m4=e=>Object.prototype.toString.call(e)==="[object Object]",Ar=e=>typeof e=="string";const Bu=Array.isArray,ii=e=>m4(e)&&Ar(e.name),Cr=(e,t=!1)=>e?Bu(e)?e.map(n=>Ar(n)?{name:n}:ii(n)?n:null).filter(n=>n!==null):Ar(e)?[{name:e}]:ii(e)?[e]:(console.error(`Expect "author" to be \`AuthorInfo[] | AuthorInfo | string[] | string ${t?"":"| false"} | undefined\`, but got`,e),[]):[],Iu=(e,t)=>{if(e){if(Bu(e)&&e.every(Ar))return e;if(Ar(e))return[e];console.error(`Expect ${t} to be \`string[] | string | undefined\`, but got`,e)}return[]},Pu=e=>Iu(e,"category"),Du=e=>Iu(e,"tag"),v4='',g4='';var y4={useBabel:!1,jsLib:[],cssLib:[],codepenLayout:"left",codepenEditors:"101",babel:"https://unpkg.com/@babel/standalone/babel.min.js",vue:"https://unpkg.com/vue/dist/vue.global.prod.js",react:"https://unpkg.com/react/umd/react.production.min.js",reactDOM:"https://unpkg.com/react-dom/umd/react-dom.production.min.js"};const lo=y4,ci={html:{types:["html","slim","haml","md","markdown","vue"],map:{html:"none",vue:"none",md:"markdown"}},js:{types:["js","javascript","coffee","coffeescript","ts","typescript","ls","livescript"],map:{js:"none",javascript:"none",coffee:"coffeescript",ls:"livescript",ts:"typescript"}},css:{types:["css","less","sass","scss","stylus","styl"],map:{css:"none",styl:"stylus"}}},b4=(e,t,n)=>{const r=document.createElement(e);return qn(t)&&wu(t).forEach(a=>{if(a.indexOf("data"))r[a]=t[a];else{const o=a.replace("data","");r.dataset[o]=t[a]}}),r},kl=e=>({...lo,...e,jsLib:Array.from(new Set([...lo.jsLib||[],...e.jsLib??[]])),cssLib:Array.from(new Set([...lo.cssLib||[],...e.cssLib??[]]))}),Rn=(e,t)=>{if(e4(e[t]))return e[t];const n=new Promise(r=>{var o;const a=document.createElement("script");a.src=t,(o=document.querySelector("body"))==null||o.appendChild(a),a.onload=()=>{r()}});return e[t]=n,n},E4=(e,t)=>{if(t.css&&Array.from(e.childNodes).every(n=>n.nodeName!=="STYLE")){const n=b4("style",{innerHTML:t.css});e.appendChild(n)}},_4=(e,t,n)=>{const r=n.getScript();if(r&&Array.from(t.childNodes).every(a=>a.nodeName!=="SCRIPT")){const a=document.createElement("script");a.appendChild(document.createTextNode(`{const document=window.document.querySelector('#${e} .vp-code-demo-display').shadowRoot; + */const de={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
    '},status:null,set:e=>{const t=de.isStarted();e=oo(e,de.settings.minimum,1),de.status=e===1?null:e;const n=de.render(!t),r=n.querySelector(de.settings.barSelector),a=de.settings.speed,o=de.settings.easing;return n.offsetWidth,kp(l=>{na(r,{transform:"translate3d("+ri(e)+"%,0,0)",transition:"all "+a+"ms "+o}),e===1?(na(n,{transition:"none",opacity:"1"}),n.offsetWidth,setTimeout(function(){na(n,{transition:"all "+a+"ms linear",opacity:"0"}),setTimeout(function(){de.remove(),l()},a)},a)):setTimeout(()=>l(),a)}),de},isStarted:()=>typeof de.status=="number",start:()=>{de.status||de.set(0);const e=()=>{setTimeout(()=>{de.status&&(de.trickle(),e())},de.settings.trickleSpeed)};return de.settings.trickle&&e(),de},done:e=>!e&&!de.status?de:de.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=de.status;return t?(typeof e!="number"&&(e=(1-t)*oo(Math.random()*t,.1,.95)),t=oo(t+e,0,.994),de.set(t)):de.start()},trickle:()=>de.inc(Math.random()*de.settings.trickleRate),render:e=>{if(de.isRendered())return document.getElementById("nprogress");ai(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=de.settings.template;const n=t.querySelector(de.settings.barSelector),r=e?"-100":ri(de.status||0),a=document.querySelector(de.settings.parent);return na(n,{transition:"all 0 linear",transform:"translate3d("+r+"%,0,0)"}),a!==document.body&&ai(a,"nprogress-custom-parent"),a==null||a.appendChild(t),t},remove:()=>{oi(document.documentElement,"nprogress-busy"),oi(document.querySelector(de.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&Tp(e)},isRendered:()=>!!document.getElementById("nprogress")},oo=(e,t,n)=>en?n:e,ri=e=>(-1+e)*100,kp=function(){const e=[];function t(){const n=e.shift();n&&n(t)}return function(n){e.push(n),e.length===1&&t()}}(),na=function(){const e=["Webkit","O","Moz","ms"],t={};function n(l){return l.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(i,c){return c.toUpperCase()})}function r(l){const i=document.body.style;if(l in i)return l;let c=e.length;const u=l.charAt(0).toUpperCase()+l.slice(1);let d;for(;c--;)if(d=e[c]+u,d in i)return d;return l}function a(l){return l=n(l),t[l]??(t[l]=r(l))}function o(l,i,c){i=a(i),l.style[i]=c}return function(l,i){for(const c in i){const u=i[c];u!==void 0&&Object.prototype.hasOwnProperty.call(i,c)&&o(l,c,u)}}}(),vu=(e,t)=>(typeof e=="string"?e:wl(e)).indexOf(" "+t+" ")>=0,ai=(e,t)=>{const n=wl(e),r=n+t;vu(n,t)||(e.className=r.substring(1))},oi=(e,t)=>{const n=wl(e);if(!vu(e,t))return;const r=n.replace(" "+t+" "," ");e.className=r.substring(1,r.length-1)},wl=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),Tp=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)},Sp=()=>{se(()=>{const e=wn(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(n=>{t.has(n.path)||de.start()}),e.afterEach(n=>{t.add(n.path),de.done()})})},xp=Ze({setup(){Sp()}}),Lp=JSON.parse(`{"encrypt":{"config":{"/encrypt/":["$2a$10$TtCknAj0RDydnax15B6bcOYS2MHBADKC.6SScgjDTfkWEcpxyvq7G"]}},"author":{"name":"KSJ","url":"https://github.com/King-sj"},"logo":"/logo.svg","repo":"https://github.com/King-sj/KBlog","docsDir":"src","footer":"黔ICP备2024025117号-1 | 版权所有 © 2024-至今 KSJ ","displayFooter":true,"copyright":"MPL-2.0 许可证","blog":{"description":"一位热衷于探索全栈开发领域的技术爱好者,擅长将前端与后端技术无缝结合,致力于打造高效、优雅的解决方案。","intro":"/intro.html","medias":{"Email":"mailto:2175616761@qq.com","GitHub":"https://github.com/King-sj","QQ":"https://wpa.qq.com/msgrd?v=3&uin=2175616761&site=qq&menu=yes"}},"locales":{"/":{"lang":"zh-CN","navbarLocales":{"langName":"简体中文","selectLangAriaLabel":"选择语言"},"metaLocales":{"author":"作者","date":"写作日期","origin":"原创","views":"访问量","category":"分类","tag":"标签","readingTime":"阅读时间","words":"字数","toc":"此页内容","prev":"上一页","next":"下一页","lastUpdated":"上次编辑于","contributors":"贡献者","editLink":"在 GitHub 上编辑此页","print":"打印"},"blogLocales":{"article":"文章","articleList":"文章列表","category":"分类","tag":"标签","timeline":"时间轴","timelineTitle":"昨日不在","all":"全部","intro":"个人介绍","star":"星标","empty":"$text 为空"},"paginationLocales":{"prev":"上一页","next":"下一页","navigate":"跳转到","action":"前往","errorText":"请输入 1 到 $page 之前的页码!"},"outlookLocales":{"themeColor":"主题色","darkmode":"外观","fullscreen":"全屏"},"encryptLocales":{"iconLabel":"文章已加密","placeholder":"输入密码","remember":"记住密码","errorHint":"请输入正确的密码"},"routeLocales":{"skipToContent":"跳至主要內容","notFoundTitle":"页面不存在","notFoundMsg":["这里什么也没有","我们是怎么来到这儿的?","这 是 四 零 四 !","看起来你访问了一个失效的链接"],"back":"返回上一页","home":"带我回家","openInNewWindow":"Open in new window"},"navbar":["/","/tech/","/essays/","/projects/","/tutorials/","/resources/",{"text":"友链","children":[{"text":"KSJ-Blog","link":"https://bupt.online"},{"text":"KSJ-Blog-GitHub","link":"https://king-sj.github.io"}]}],"sidebar":{"/":["","intro"],"/tech/":[{"text":"技术","children":"structure","collapsible":true}],"/essay/":[{"text":"随笔","children":"structure","collapsible":true}],"/tutorials/":[{"text":"教程","children":"structure","icon":"book","collapsible":true}],"/resources/":[{"text":"资源","children":"structure","icon":"book","collapsible":true}]}}}}`),Bp=z(Lp),gu=()=>Bp,yu=Symbol(""),Ip=()=>{const e=ve(yu);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},Pp=(e,t)=>{const{locales:n,...r}=e;return{...r,...n==null?void 0:n[t]}},Dp=Ze({enhance({app:e}){const t=gu(),n=e._context.provides[vl],r=w(()=>Pp(t.value,n.routeLocale.value));e.provide(yu,r),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return r.value}}})}});var Op={provider:"Giscus",lightTheme:"https://unpkg.com/vuepress-theme-hope@2.0.0-rc.38/templates/giscus/light.css",darkTheme:"https://unpkg.com/vuepress-theme-hope@2.0.0-rc.38/templates/giscus/dark.css",repo:"King-sj/KBlog",repoId:"R_kgDOL41Gpw",category:"Q&A",categoryId:"DIC_kwDOL41Gp84CiYFP",lazyLoading:!0};const Mp=Op;let Rp=Mp;const bu=Symbol(""),Eu=()=>ve(bu),$p=Eu,Fp=e=>{e.provide(bu,Rp)},_u=()=>s("svg",{xmlns:"http://www.w3.org/2000/svg",width:"32",height:"32",preserveAspectRatio:"xMidYMid",viewBox:"0 0 100 100"},[s("circle",{cx:"28",cy:"75",r:"11",fill:"currentColor"},s("animate",{attributeName:"fill-opacity",begin:"0s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"})),s("path",{fill:"none",stroke:"#88baf0","stroke-width":"10",d:"M28 47a28 28 0 0 1 28 28"},s("animate",{attributeName:"stroke-opacity",begin:"0.1s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"})),s("path",{fill:"none",stroke:"#88baf0","stroke-width":"10",d:"M28 25a50 50 0 0 1 50 50"},s("animate",{attributeName:"stroke-opacity",begin:"0.2s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"}))]);_u.displayName="LoadingIcon";const li=["ar","ca","da","de","en","eo","es","fa","fr","he","id","it","ja","ko","nl","pl","pt","ro","ru","th","tr","uk","uz","vi","zh-CN","zh-TW"],Hp=M({name:"GiscusComment",props:{identifier:{type:String,required:!0},darkmode:Boolean},setup(e){const t=$p(),n=Pa(),r=!!(t.repo&&t.repoId&&t.category&&t.categoryId),{repo:a,repoId:o,category:l,categoryId:i}=t,c=z(!1),u=w(()=>{if(li.includes(n.value))return n.value;const f=n.value.split("-")[0];return li.includes(f)?f:"en"}),d=w(()=>({repo:a,repoId:o,category:l,categoryId:i,lang:u.value,theme:e.darkmode?t.darkTheme||"dark":t.lightTheme||"light",mapping:t.mapping||"pathname",term:e.identifier,inputPosition:t.inputPosition||"top",reactionsEnabled:t.reactionsEnabled===!1?"0":"1",strict:t.strict===!1?"0":"1",loading:t.lazyLoading===!1?"eager":"lazy",emitMetadata:"0"}));return se(async()=>{await T(()=>import("./giscus--_FS5kYt.js"),[]),c.value=!0}),()=>r?s("div",{id:"comment",class:["giscus-wrapper",{"input-top":t.inputPosition!=="bottom"}]},c.value?s("giscus-widget",d.value):s(_u)):null}}),Np=M({name:"CommentService",props:{darkmode:Boolean},setup(e){const t=Eu(),n=ge(),r=he(),a=t.comment!==!1,o=w(()=>r.value.comment||a&&r.value.comment!==!1);return()=>s(Hp,{id:"vp-comment",identifier:r.value.commentID||n.value.path,darkmode:e.darkmode,style:{display:o.value?"block":"none"}})}}),Vp=Ze({enhance:({app:e})=>{Fp(e),e.component("CommentService",Np)}}),jp=/\b(?:Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini)/i,zp=()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator&&jp.test(navigator.userAgent),qp=({delay:e=500,duration:t=2e3,locales:n,selector:r,showInMobile:a})=>{const{copy:o,copied:l}=R3({legacy:!0,copiedDuring:t}),i=hu(n),c=ge(),u=p=>{if(!p.hasAttribute("copy-code-registered")){const h=document.createElement("button");h.type="button",h.classList.add("vp-copy-code-button"),h.innerHTML='
    ',h.setAttribute("aria-label",i.value.copy),h.setAttribute("data-copied",i.value.copied),p.parentElement&&p.parentElement.insertBefore(h,p),p.setAttribute("copy-code-registered","")}},d=()=>{_t().then(()=>gp(e)).then(()=>{r.forEach(p=>{document.querySelectorAll(p).forEach(u)})})},f=(p,h,v)=>{let{innerText:_=""}=h;/language-(shellscript|shell|bash|sh|zsh)/.test(p.classList.toString())&&(_=_.replace(/^ *(\$|>) /gm,"")),o(_).then(()=>{v.classList.add("copied"),ae(l,()=>{v.classList.remove("copied"),v.blur()},{once:!0})})};se(()=>{const p=!zp()||a;p&&d(),Se("click",h=>{const v=h.target;if(v.matches('div[class*="language-"] > button.copy')){const _=v.parentElement,E=v.nextElementSibling;E&&f(_,E,v)}else if(v.matches('div[class*="language-"] div.vp-copy-icon')){const _=v.parentElement,E=_.parentElement,b=_.nextElementSibling;b&&f(E,b,_)}}),ae(()=>c.value.path,()=>{p&&d()})})};var Gp={"/":{copy:"复制代码",copied:"已复制"}},Up=['.theme-hope-content div[class*="language-"] pre'];const Wp=500,Kp=2e3,Jp=Gp,Qp=Up,Yp=!1,Zp=Ze({setup:()=>{qp({selector:Qp,locales:Jp,duration:Kp,delay:Wp,showInMobile:Yp})}}),ra=cn("VUEPRESS_CODE_TAB_STORE",{});var Xp=M({name:"CodeTabs",props:{active:{type:Number,default:0},data:{type:Array,required:!0},id:{type:String,required:!0},tabId:{type:String,default:""}},slots:Object,setup(e,{slots:t}){const n=z(e.active),r=xe([]),a=()=>{e.tabId&&(ra.value[e.tabId]=e.data[n.value].id)},o=(u=n.value)=>{n.value=u{n.value=u>0?u-1:r.value.length-1,r.value[n.value].focus()},i=(u,d)=>{u.key===" "||u.key==="Enter"?(u.preventDefault(),n.value=d):u.key==="ArrowRight"?(u.preventDefault(),o()):u.key==="ArrowLeft"&&(u.preventDefault(),l()),e.tabId&&(ra.value[e.tabId]=e.data[n.value].id)},c=()=>{if(e.tabId){const u=e.data.findIndex(({id:d})=>ra.value[e.tabId]===d);if(u!==-1)return u}return e.active};return se(()=>{n.value=c(),ae(()=>ra.value[e.tabId],(u,d)=>{if(e.tabId&&u!==d){const f=e.data.findIndex(({id:p})=>p===u);f!==-1&&(n.value=f)}})}),()=>e.data.length?s("div",{class:"vp-code-tabs"},[s("div",{class:"vp-code-tabs-nav",role:"tablist"},e.data.map(({id:u},d)=>{const f=d===n.value;return s("button",{type:"button",ref:p=>{p&&(r.value[d]=p)},class:["vp-code-tab-nav",{active:f}],role:"tab","aria-controls":`codetab-${e.id}-${d}`,"aria-selected":f,onClick:()=>{n.value=d,a()},onKeydown:p=>i(p,d)},t[`title${d}`]({value:u,isActive:f}))})),e.data.map(({id:u},d)=>{const f=d===n.value;return s("div",{class:["vp-code-tab",{active:f}],id:`codetab-${e.id}-${d}`,role:"tabpanel","aria-expanded":f},[s("div",{class:"vp-code-tab-title"},t[`title${d}`]({value:u,isActive:f})),t[`tab${d}`]({value:u,isActive:f})])})]):null}});const ya=e=>{const t=atob(e);return g3(p3(v3(t)))},e4=e=>typeof e<"u",wu=Object.keys,t4=e=>typeof e<"u",n4=Object.keys,ie=({name:e="",color:t="currentColor"},{slots:n})=>{var r;return s("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${e}-icon`],viewBox:"0 0 1024 1024",fill:t,"aria-label":`${e} icon`},(r=n.default)==null?void 0:r.call(n))};ie.displayName="IconBase";const Al=({size:e=48,stroke:t=4,wrapper:n=!0,height:r=2*e})=>{const a=s("svg",{xmlns:"http://www.w3.org/2000/svg",width:e,height:e,preserveAspectRatio:"xMidYMid",viewBox:"25 25 50 50"},[s("animateTransform",{attributeName:"transform",type:"rotate",dur:"2s",keyTimes:"0;1",repeatCount:"indefinite",values:"0;360"}),s("circle",{cx:"50",cy:"50",r:"20",fill:"none",stroke:"currentColor","stroke-width":t,"stroke-linecap":"round"},[s("animate",{attributeName:"stroke-dasharray",dur:"1.5s",keyTimes:"0;0.5;1",repeatCount:"indefinite",values:"1,200;90,200;1,200"}),s("animate",{attributeName:"stroke-dashoffset",dur:"1.5s",keyTimes:"0;0.5;1",repeatCount:"indefinite",values:"0;-35px;-125px"})])]);return n?s("div",{class:"loading-icon-wrapper",style:`display:flex;align-items:center;justify-content:center;height:${r}px`},a):a};Al.displayName="LoadingIcon";const Au=(e,{slots:t})=>{var n;return(n=t.default)==null?void 0:n.call(t)},r4=e=>sn(e)?e:`https://github.com/${e}`,Cl=(e="")=>!sn(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,Cu=()=>s(ie,{name:"github"},()=>s("path",{d:"M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"}));Cu.displayName="GitHubIcon";const ku=()=>s(ie,{name:"gitee"},()=>s("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm242.97-533.34H482.39a23.7 23.7 0 0 0-23.7 23.7l-.03 59.28c0 13.08 10.59 23.7 23.7 23.7h165.96a23.7 23.7 0 0 1 23.7 23.7v11.85a71.1 71.1 0 0 1-71.1 71.1H375.71a23.7 23.7 0 0 1-23.7-23.7V423.11a71.1 71.1 0 0 1 71.1-71.1h331.8a23.7 23.7 0 0 0 23.7-23.7l.06-59.25a23.73 23.73 0 0 0-23.7-23.73H423.11a177.78 177.78 0 0 0-177.78 177.75v331.83c0 13.08 10.62 23.7 23.7 23.7h349.62a159.99 159.99 0 0 0 159.99-159.99V482.33a23.7 23.7 0 0 0-23.7-23.7z"}));ku.displayName="GiteeIcon";const Tu=()=>s(ie,{name:"bitbucket"},()=>s("path",{d:"M575.256 490.862c6.29 47.981-52.005 85.723-92.563 61.147-45.714-20.004-45.714-92.562-1.133-113.152 38.29-23.442 93.696 7.424 93.696 52.005zm63.451-11.996c-10.276-81.152-102.29-134.839-177.152-101.156-47.433 21.138-79.433 71.424-77.129 124.562 2.853 69.705 69.157 126.866 138.862 120.576S647.3 548.571 638.708 478.83zm136.558-309.723c-25.161-33.134-67.986-38.839-105.728-45.13-106.862-17.151-216.576-17.7-323.438 1.134-35.438 5.706-75.447 11.996-97.719 43.996 36.572 34.304 88.576 39.424 135.424 45.129 84.553 10.862 171.447 11.447 256 .585 47.433-5.705 99.987-10.276 135.424-45.714zm32.585 591.433c-16.018 55.99-6.839 131.438-66.304 163.986-102.29 56.576-226.304 62.867-338.87 42.862-59.43-10.862-129.135-29.696-161.72-85.723-14.3-54.858-23.442-110.848-32.585-166.84l3.438-9.142 10.276-5.157c170.277 112.567 408.576 112.567 579.438 0 26.844 8.01 6.84 40.558 6.29 60.014zm103.424-549.157c-19.42 125.148-41.728 249.71-63.415 374.272-6.29 36.572-41.728 57.162-71.424 72.558-106.862 53.724-231.424 62.866-348.562 50.286-79.433-8.558-160.585-29.696-225.134-79.433-30.28-23.443-30.28-63.415-35.986-97.134-20.005-117.138-42.862-234.277-57.161-352.585 6.839-51.42 64.585-73.728 107.447-89.71 57.16-21.138 118.272-30.866 178.87-36.571 129.134-12.58 261.157-8.01 386.304 28.562 44.581 13.13 92.563 31.415 122.844 69.705 13.714 17.7 9.143 40.01 6.29 60.014z"}));Tu.displayName="BitbucketIcon";const Su=()=>s(ie,{name:"source"},()=>s("path",{d:"M601.92 475.2c0 76.428-8.91 83.754-28.512 99.594-14.652 11.88-43.956 14.058-78.012 16.434-18.81 1.386-40.392 2.97-62.172 6.534-18.612 2.97-36.432 9.306-53.064 17.424V299.772c37.818-21.978 63.36-62.766 63.36-109.692 0-69.894-56.826-126.72-126.72-126.72S190.08 120.186 190.08 190.08c0 46.926 25.542 87.714 63.36 109.692v414.216c-37.818 21.978-63.36 62.766-63.36 109.692 0 69.894 56.826 126.72 126.72 126.72s126.72-56.826 126.72-126.72c0-31.086-11.286-59.598-29.7-81.576 13.266-9.504 27.522-17.226 39.996-19.206 16.038-2.574 32.868-3.762 50.688-5.148 48.312-3.366 103.158-7.326 148.896-44.55 61.182-49.698 74.25-103.158 75.24-187.902V475.2h-126.72zM316.8 126.72c34.848 0 63.36 28.512 63.36 63.36s-28.512 63.36-63.36 63.36-63.36-28.512-63.36-63.36 28.512-63.36 63.36-63.36zm0 760.32c-34.848 0-63.36-28.512-63.36-63.36s28.512-63.36 63.36-63.36 63.36 28.512 63.36 63.36-28.512 63.36-63.36 63.36zM823.68 158.4h-95.04V63.36h-126.72v95.04h-95.04v126.72h95.04v95.04h126.72v-95.04h95.04z"}));Su.displayName="SourceIcon";const a4=({link:e,type:t=Cl(e??"")})=>{if(!t)return null;const n=t.toLowerCase();return s(n==="bitbucket"?Tu:n==="github"?Cu:n==="gitlab"?"GitLab":n==="gitee"?ku:Su)};function o4(){const e=z(!1),t=ln();return t&&se(()=>{e.value=!0},t),e}function l4(e){return o4(),w(()=>!!e())}const s4=()=>l4(()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator),i4=()=>{const e=s4();return w(()=>e.value&&/\b(?:Android|iPhone)/i.test(navigator.userAgent))},c4=e=>[/\((ipad);[-\w),; ]+apple/i,/applecoremedia\/[\w.]+ \((ipad)/i,/\b(ipad)\d\d?,\d\d?[;\]].+ios/i].some(t=>t.test(e)),u4=e=>[/ip[honead]{2,4}\b(?:.*os ([\w]+) like mac|; opera)/i,/cfnetwork\/.+darwin/i].some(t=>t.test(e)),d4=e=>[/(mac os x) ?([\w. ]*)/i,/(macintosh|mac_powerpc\b)(?!.+haiku)/i].some(t=>t.test(e)),f4=(e,t=0)=>{let n=3735928559^t,r=1103547991^t;for(let a=0,o;a>>16,2246822507),n^=Math.imul(r^r>>>13,3266489909),r=Math.imul(r^r>>>16,2246822507),r^=Math.imul(n^n>>>13,3266489909),4294967296*(2097151&r)+(n>>>0)},Ra=(e,t)=>f4(e)%t;let p4=class{constructor(){this.messageElements={};const t="message-container",n=document.getElementById(t);n?this.containerElement=n:(this.containerElement=document.createElement("div"),this.containerElement.id=t,document.body.appendChild(this.containerElement))}pop(t,n=2e3){const r=document.createElement("div"),a=Date.now();return r.className="message move-in",r.innerHTML=t,this.containerElement.appendChild(r),this.messageElements[a]=r,n>0&&setTimeout(()=>{this.close(a)},n),a}close(t){if(t){const n=this.messageElements[t];n.classList.remove("move-in"),n.classList.add("move-out"),n.addEventListener("animationend",()=>{n.remove(),delete this.messageElements[t]})}else n4(this.messageElements).forEach(n=>this.close(Number(n)))}destroy(){document.body.removeChild(this.containerElement)}};const xu=/#.*$/u,h4=e=>{const t=xu.exec(e);return t?t[0]:""},si=e=>decodeURI(e).replace(xu,"").replace(/\/index\.html$/iu,"/").replace(/\.html$/iu,"").replace(/(README|index)?\.md$/iu,""),Lu=(e,t)=>{if(!t4(t))return!1;const n=si(e.path),r=si(t),a=h4(t);return a?a===e.hash&&(!r||n===r):n===r};var m4=e=>Object.prototype.toString.call(e)==="[object Object]",Ar=e=>typeof e=="string";const Bu=Array.isArray,ii=e=>m4(e)&&Ar(e.name),Cr=(e,t=!1)=>e?Bu(e)?e.map(n=>Ar(n)?{name:n}:ii(n)?n:null).filter(n=>n!==null):Ar(e)?[{name:e}]:ii(e)?[e]:(console.error(`Expect "author" to be \`AuthorInfo[] | AuthorInfo | string[] | string ${t?"":"| false"} | undefined\`, but got`,e),[]):[],Iu=(e,t)=>{if(e){if(Bu(e)&&e.every(Ar))return e;if(Ar(e))return[e];console.error(`Expect ${t} to be \`string[] | string | undefined\`, but got`,e)}return[]},Pu=e=>Iu(e,"category"),Du=e=>Iu(e,"tag"),v4='',g4='';var y4={useBabel:!1,jsLib:[],cssLib:[],codepenLayout:"left",codepenEditors:"101",babel:"https://unpkg.com/@babel/standalone/babel.min.js",vue:"https://unpkg.com/vue/dist/vue.global.prod.js",react:"https://unpkg.com/react/umd/react.production.min.js",reactDOM:"https://unpkg.com/react-dom/umd/react-dom.production.min.js"};const lo=y4,ci={html:{types:["html","slim","haml","md","markdown","vue"],map:{html:"none",vue:"none",md:"markdown"}},js:{types:["js","javascript","coffee","coffeescript","ts","typescript","ls","livescript"],map:{js:"none",javascript:"none",coffee:"coffeescript",ls:"livescript",ts:"typescript"}},css:{types:["css","less","sass","scss","stylus","styl"],map:{css:"none",styl:"stylus"}}},b4=(e,t,n)=>{const r=document.createElement(e);return qn(t)&&wu(t).forEach(a=>{if(a.indexOf("data"))r[a]=t[a];else{const o=a.replace("data","");r.dataset[o]=t[a]}}),r},kl=e=>({...lo,...e,jsLib:Array.from(new Set([...lo.jsLib||[],...e.jsLib??[]])),cssLib:Array.from(new Set([...lo.cssLib||[],...e.cssLib??[]]))}),Rn=(e,t)=>{if(e4(e[t]))return e[t];const n=new Promise(r=>{var o;const a=document.createElement("script");a.src=t,(o=document.querySelector("body"))==null||o.appendChild(a),a.onload=()=>{r()}});return e[t]=n,n},E4=(e,t)=>{if(t.css&&Array.from(e.childNodes).every(n=>n.nodeName!=="STYLE")){const n=b4("style",{innerHTML:t.css});e.appendChild(n)}},_4=(e,t,n)=>{const r=n.getScript();if(r&&Array.from(t.childNodes).every(a=>a.nodeName!=="SCRIPT")){const a=document.createElement("script");a.appendChild(document.createTextNode(`{const document=window.document.querySelector('#${e} .vp-code-demo-display').shadowRoot; ${r}}`)),t.appendChild(a)}},w4=["html","js","css"],A4=e=>{const t=wu(e),n={html:[],js:[],css:[],isLegal:!1};return w4.forEach(r=>{const a=t.filter(o=>ci[r].types.includes(o));if(a.length){const o=a[0];n[r]=[e[o].replace(/^\n|\n$/g,""),ci[r].map[o]??o]}}),n.isLegal=(!n.html.length||n.html[1]==="none")&&(!n.js.length||n.js[1]==="none")&&(!n.css.length||n.css[1]==="none"),n},Ou=e=>e.replace(/
    /g,"
    ").replace(/<((\S+)[^<]*?)\s+\/>/g,"<$1>"),Mu=e=>`
    ${Ou(e)}
    `,C4=e=>`${e.replace("export default ","const $reactApp = ").replace(/App\.__style__(\s*)=(\s*)`([\s\S]*)?`/,"")}; -ReactDOM.createRoot(document.getElementById("app")).render(React.createElement($reactApp))`,k4=e=>e.replace(/export\s+default\s*\{(\n*[\s\S]*)\n*\}\s*;?$/u,"Vue.createApp({$1}).mount('#app')").replace(/export\s+default\s*define(Async)?Component\s*\(\s*\{(\n*[\s\S]*)\n*\}\s*\)\s*;?$/u,"Vue.createApp({$1}).mount('#app')").trim(),Ru=e=>`(function(exports){var module={};module.exports=exports;${e};return module.exports.__esModule?module.exports.default:module.exports;})({})`,T4=(e,t)=>{const n=kl(t),r=e.js[0]??"";return{...n,html:Ou(e.html[0]??""),js:r,css:e.css[0]??"",isLegal:e.isLegal,getScript:()=>{var a;return n.useBabel?((a=window.Babel.transform(r,{presets:["es2015"]}))==null?void 0:a.code)??"":r}}},S4=/