{"slug":"tu-lexer-den-semantic-analysis-compiler-hieu-ma-nguon-nhu-the-nao","title":"Từ Lexer đến Semantic Analysis: Compiler hiểu mã nguồn như thế nào?","summary":"Compiler là chương trình chuyển đổi mã nguồn từ một biểu diễn sang một biểu diễn khác, thường từ ngôn ngữ lập trình bậc cao sang mã máy, assembly, bytecode hoặc một dạng biểu diễn trung gian.\n\nQuá trình biên dịch không chỉ là phép thay thế văn bản. Mã nguồn phải được nhận diện thành các đơn vị từ vựng, tổ chức thành cấu trúc cú pháp và kiểm tra các ràng buộc ngữ nghĩa.","excerpt":"Compiler là chương trình chuyển đổi mã nguồn từ một biểu diễn sang một biểu diễn khác, thường từ ngôn ngữ lập trình bậc cao sang mã máy, assembly, bytecode hoặc một dạng biểu diễn trung gian.\n\nQuá trình biên dịch không chỉ là phép thay thế văn bản. Mã nguồn phải được nhận diện thành các đơn vị từ vựng, tổ chức thành cấu trúc cú pháp và kiểm tra các ràng buộc ngữ nghĩa.","markdown":"# Từ Lexer đến Semantic Analysis: Compiler hiểu mã nguồn như thế nào?\n\nCompiler là chương trình chuyển đổi mã nguồn từ một biểu diễn sang một biểu diễn khác, thường từ ngôn ngữ lập trình bậc cao sang mã máy, assembly, bytecode hoặc một dạng biểu diễn trung gian.\n\nQuá trình biên dịch không chỉ là phép thay thế văn bản. Mã nguồn phải được nhận diện thành các đơn vị từ vựng, tổ chức thành cấu trúc cú pháp và kiểm tra các ràng buộc ngữ nghĩa.\n\nMột pipeline compiler có thể được giản lược như sau:\n\nSource Code\n↓\nLexical Analysis\n↓\nTokens\n↓\nParsing\n↓\nParse Tree / AST\n↓\nSemantic Analysis\n↓\nValidated Representation\n\n\nCác compiler thực tế có thể chia nhỏ, hợp nhất hoặc bổ sung nhiều giai đoạn khác. Mô hình trên chỉ biểu diễn các tầng cần thiết cho phạm vi bài viết.\n\n## 1. Compiler làm gì với mã nguồn?\n\nMã nguồn ban đầu là một chuỗi ký tự.\n\nresult = a + b * 2\n\n\nỞ dạng này, compiler chưa thể trực tiếp suy ra toàn bộ cấu trúc của biểu thức. Nó phải xác định:\n\ncác đơn vị từ vựng;\nloại của từng đơn vị;\nquan hệ cú pháp giữa chúng;\ncấu trúc phân cấp của biểu thức;\ncác ràng buộc ngữ nghĩa.\n\nBa tầng cần phân biệt là:\n\nKý tự → Đơn vị → Cấu trúc → Ngữ nghĩa\n\n\nNhận diện đúng các đơn vị không đồng nghĩa với xác định đúng cấu trúc. Xác định được cấu trúc cú pháp cũng không đồng nghĩa với cấu trúc đó hợp lệ về ngữ nghĩa.\n\n\n\n## 2. Token — đơn vị cơ bản của mã nguồn\n\n**Token** là một đơn vị từ vựng được compiler nhận diện trong mã nguồn.\n\nVí dụ:\n\nscore = value + 10\n\n\ncó thể được biểu diễn thành:\n\nIDENTIFIER(\"score\")\nASSIGN(\"=\")\nIDENTIFIER(\"value\")\nPLUS(\"+\")\nINTEGER(10)\n\n\nTrong đó:\n\n| Mã nguồn | Loại token |\n|---|---|\n| `score` | Identifier |\n| `=` | Assignment operator |\n| `value` | Identifier |\n| `+` | Arithmetic operator |\n| `10` | Integer literal |\n\nToken không nhất thiết tương ứng với một ký tự. `score` gồm nhiều ký tự nhưng tạo thành một token duy nhất.\n\nToken chủ yếu cung cấp thông tin về **loại đơn vị**, chưa xác định đầy đủ **vai trò cấu trúc** của đơn vị đó.\n\n\n\n## 3. Lexer và Lexical Analysis — từ ký tự thành token\n\n**Lexical Analysis** là quá trình phân tích chuỗi ký tự của mã nguồn thành các token. Thành phần thực hiện quá trình này thường được gọi là **lexer** hoặc **lexical analyzer**.\n\nCharacters\n↓\nLexer\n↓\nTokens\n\n\nVí dụ:\n\ntotal = price * 2\n\n\ncó thể được lexer chuyển thành:\n\nIDENTIFIER(\"total\")\nASSIGN\nIDENTIFIER(\"price\")\nMULTIPLY\nINTEGER(2)\n\n\nLexer thường xử lý các loại đơn vị như:\n\nidentifier;\nkeyword;\noperator;\nliteral;\ndelimiter.\n\nLexical analysis không có nhiệm vụ xác định toàn bộ quan hệ cú pháp giữa các token.\n\n**Lexer xác định các đơn vị. Parser xác định cấu trúc giữa các đơn vị.**\n\n\n\n## 4. Grammar — các quy tắc cấu tạo ngôn ngữ\n\n**Grammar** là hệ thống quy tắc hình thức mô tả cách các cấu trúc hợp lệ của một ngôn ngữ có thể được tạo thành.\n\nMột grammar giản lược có thể chứa các production rule:\n\nExpression → Expression + Expression\nExpression → Expression * Expression\nExpression → Identifier\nExpression → Integer\n\n\nKý hiệu:\n\nA → B C\n\n\ncó nghĩa rằng cấu trúc thuộc loại `A` có thể được tạo bởi các thành phần `B` và `C` theo quy tắc tương ứng.\n\nGrammar không chỉ mô tả token nào có thể xuất hiện. Nó mô tả **cách các token hoặc cấu trúc nhỏ hơn kết hợp thành cấu trúc lớn hơn**.\n\nTrong ngôn ngữ lập trình thực tế, grammar còn phải xử lý các vấn đề như precedence, associativity, grouping và nhiều loại cấu trúc khác.\n\n\n\n## 5. Parser — từ chuỗi token đến cấu trúc\n\n**Parser** là thành phần phân tích chuỗi token dựa trên grammar để xác định cấu trúc cú pháp của chương trình.\n\nXét:\n\na + b * c\n\n\nChuỗi token tuyến tính là:\n\na  +  b  *  c\n\n\nNhưng cấu trúc của biểu thức có thể là:\n\na + (b * c)\n\n\nBiểu diễn dưới dạng cây:\n\n+\n/ \\\na   *\n/ \\\nb   c\n\n\nCấu trúc này khác với:\n\n(a + b) * c\n\n\ntương ứng:\n\n*\n/ \\\n+   c\n/ \\\na   b\n\n\nParser vì vậy không chỉ nhận diện sự xuất hiện của `a`, `+`, `b`, `*`, `c`. Nó xác định **quan hệ phân cấp** giữa chúng.\n\n\n\n## 6. Parse Tree — cấu trúc tầng bậc của cú pháp\n\n**Parse Tree** là cây biểu diễn cách một chuỗi token được phân tích theo các production rule của grammar.\n\nVới:\n\na + b * c\n\n\nmột parse tree giản lược có thể là:\n\nExpression\n├── Expression\n│   └── Identifier(a)\n├── \"+\"\n└── Expression\n├── Expression\n│   └── Identifier(b)\n├── \"*\"\n└── Expression\n└── Identifier(c)\n\n\nParse tree thể hiện:\n\nthành phần nào kết hợp với thành phần nào;\nchúng tạo thành cấu trúc trung gian nào;\ncấu trúc lớn hơn được hình thành theo thứ bậc nào.\n\nChuỗi tuyến tính:\n\na + b * c\n\n\nvì vậy được chuyển thành cấu trúc phân cấp:\n\n+\n├── a\n└── *\n├── b\n└── c\n\n\n**Thứ tự tuyến tính và cấu trúc tầng bậc là hai loại thông tin khác nhau.**\n\n\n\n## 7. AST — Abstract Syntax Tree\n\n**Abstract Syntax Tree (AST)** là biểu diễn cây trừu tượng của cấu trúc cú pháp.\n\nAST thường loại bỏ những node hoặc chi tiết chỉ cần thiết cho quá trình parsing nhưng không cần thiết cho các giai đoạn xử lý tiếp theo.\n\nVí dụ parse tree:\n\nExpression\n├── Expression\n│   └── Identifier(a)\n├── \"+\"\n└── Expression\n├── Expression\n│   └── Identifier(b)\n├── \"*\"\n└── Expression\n└── Identifier(c)\n\n\ncó thể được rút gọn thành AST:\n\nAdd\n├── Identifier(a)\n└── Multiply\n├── Identifier(b)\n└── Identifier(c)\n\n\nAST giữ lại quan hệ quan trọng:\n\nMultiply(b, c)\n\n\nlà một cấu trúc con của:\n\nAdd(a, ...)\n\n\nCác giai đoạn semantic analysis, optimization hoặc code generation thường làm việc trên AST hoặc những biểu diễn trung gian được xây dựng từ nó.\n\n\n\n## 8. Parse Tree và AST khác nhau thế nào?\n\nParse tree và AST đều biểu diễn cấu trúc phân cấp nhưng có mục đích khác nhau.\n\n| Đặc điểm | Parse Tree | AST |\n|---|---|---|\n| Quan hệ với grammar | Trực tiếp | Trừu tượng hóa |\n| Production trung gian | Thường được giữ | Thường được lược bỏ |\n| Dấu câu và chi tiết cú pháp | Có thể được giữ | Có thể bị loại |\n| Mục tiêu | Biểu diễn quá trình phân tích | Biểu diễn cấu trúc cần xử lý |\n| Mức độ chi tiết | Cao hơn | Thấp hơn |\n\nKhông phải mọi compiler đều xây dựng hai cấu trúc này thành hai giai đoạn vật lý riêng biệt. Một parser có thể trực tiếp tạo AST.\n\n`Parse Tree` và `AST` vì vậy là hai khái niệm liên quan nhưng không đồng nhất.\n\n\n\n## 9. Candidate Parse và Ambiguity — một input, nhiều cách phân tích\n\nMột chuỗi token có thể cho phép nhiều cấu trúc cú pháp nếu grammar không cung cấp đủ điều kiện để xác định duy nhất một cấu trúc.\n\nVí dụ:\n\na + b * c\n\n\ncó hai candidate parse:\n\n(a + b) * c\n\n\nvà:\n\na + (b * c)\n\n\nHai cấu trúc tạo ra hai cây khác nhau và có thể tạo ra hai kết quả khác nhau.\n\nHiện tượng một input có nhiều cách phân tích được gọi là **syntactic ambiguity**.\n\nNgôn ngữ lập trình thường được thiết kế để loại bỏ hoặc kiểm soát ambiguity bằng:\n\nprecedence;\nassociativity;\ndấu ngoặc;\ngrammar design;\ncác quy tắc bổ sung của ngôn ngữ.\n\nMột nguyên tắc quan trọng có thể rút ra:\n\n**Surface form không tự nó xác định đầy đủ cấu trúc bên dưới.**\n\n\n\n## 10. Syntax hợp lệ chưa chắc Semantic hợp lệ\n\nXét biểu thức giả định:\n\n\"hello\" - 5\n\n\nNếu grammar cho phép:\n\nExpression → Expression - Expression\n\n\nparser có thể dựng:\n\nSubtract\n├── String(\"hello\")\n└── Integer(5)\n\n\nCấu trúc này thỏa quy tắc cú pháp của một binary expression.\n\nTuy nhiên, nếu ngôn ngữ chỉ định toán tử `-` cho các kiểu số, phép toán:\n\nString - Integer\n\n\nkhông hợp lệ.\n\nHai kết quả khác nhau:\n\nSyntax:   hợp lệ\nSemantic: không hợp lệ\n\n\nParser đã hoàn thành nhiệm vụ của nó. Lỗi chỉ xuất hiện khi hệ thống kiểm tra các ràng buộc nằm ngoài cấu trúc cú pháp đơn thuần.\n\n**Một cấu trúc có thể parse được nhưng vẫn không hợp lệ về ngữ nghĩa.**\n\n\n\n## 11. Semantic Analysis — kiểm tra ngữ nghĩa của cấu trúc\n\n**Semantic Analysis** là giai đoạn kiểm tra các thuộc tính và ràng buộc ngữ nghĩa của cấu trúc đã được phân tích cú pháp.\n\nTùy thiết kế ngôn ngữ, semantic analysis có thể kiểm tra:\n\nidentifier đã được khai báo hay chưa;\nphạm vi của identifier;\nkiểu dữ liệu;\ntính tương thích giữa các kiểu;\ntham số của function;\nkiểu trả về;\nkhả năng áp dụng operator;\ncác ràng buộc ngữ nghĩa khác.\n\nVí dụ:\n\nx: Integer\nx + 10\n\n\ncó thể hợp lệ.\n\nTrong khi:\n\nx: String\nx - 10\n\n\ncó thể không hợp lệ.\n\nHai biểu thức đều có thể có cùng cấu trúc cú pháp tổng quát:\n\nBinaryExpression\n├── LeftOperand\n├── Operator\n└── RightOperand\n\n\nSự khác biệt nằm ở các thuộc tính và quan hệ ngữ nghĩa của những node bên trong.\n\n\n\n## 12. Type Checking và Semantic Constraints\n\n**Type checking** là quá trình kiểm tra một biểu thức hoặc thao tác có tương thích với các kiểu dữ liệu liên quan hay không.\n\nGiả sử:\n\nadd(Int, Int) → Int\n\n\nLời gọi:\n\nadd(1, 2)\n\n\nthỏa yêu cầu:\n\nadd(Int, Int)\n\n\nTrong khi:\n\nadd(\"one\", 2)\n\n\ntạo ra:\n\nadd(String, Int)\n\n\nvà không thỏa signature đã định nghĩa.\n\nCó thể biểu diễn dưới dạng constraint:\n\nExpected:\nadd(Int, Int)\n\nReceived:\nadd(String, Int)\n\nResult:\nType mismatch\n\n\nKhái niệm tổng quát hơn là **semantic constraint**: một cấu trúc chỉ hợp lệ khi các thành phần của nó đáp ứng những điều kiện ngữ nghĩa nhất định.\n\nMột node vì vậy có thể xuất hiện tại vị trí được grammar cho phép nhưng vẫn không đáp ứng constraint của cấu trúc chứa nó.\n\n\n\n## 13. Syntax và Semantic khác nhau ở đâu?\n\n**Syntax** mô tả cách các đơn vị được tổ chức thành cấu trúc.\n\n**Semantic** xác định các thuộc tính, quan hệ và ràng buộc có ý nghĩa trên cấu trúc đó.\n\nVí dụ:\n\n\"hello\" - 5\n\n\nSyntax có thể tạo:\n\nSubtract\n├── String\n└── Integer\n\n\nSemantic analysis kiểm tra:\n\nsubtract(String, Integer)\n\n\nvà có thể bác bỏ nó.\n\nCó thể khái quát:\n\nTokens\n↓\nSyntactic Structure\n↓\nSemantic Validation\n\n\nBa tầng này không thay thế lẫn nhau.\n\nNhận diện token không đủ để xác định cấu trúc. Xác định cấu trúc không đủ để xác nhận ngữ nghĩa.\n\n\n\n## 14. Programming Language và Natural Language\n\nNgôn ngữ lập trình và ngôn ngữ tự nhiên không hình thành theo cùng một cơ chế.\n\nMột ngôn ngữ lập trình thường có specification hoặc tập quy tắc được xác định trước:\n\nLanguage Specification\n↓\nGrammar + Semantic Rules\n↓\nValid Programs\n\n\nChương trình được đánh giá dựa trên các quy tắc của ngôn ngữ.\n\nNgôn ngữ tự nhiên không vận hành theo chiều này. Một mô hình giản lược phù hợp hơn là:\n\nLanguage Usage\n↓\nObserved Data\n↓\nAnalysis\n↓\nGeneralization\n↓\nGrammatical Model\n\n\nNgười nói không cần một grammar do nhà ngôn ngữ học viết ra trước khi sử dụng ngôn ngữ. Grammar trong nghiên cứu ngôn ngữ học chủ yếu là kết quả của việc quan sát, mô tả và khái quát các hiện tượng ngôn ngữ.\n\nDữ liệu lịch sử cũng không hoàn chỉnh. Một cấu trúc có thể đã tồn tại trong khẩu ngữ trước khi được ghi nhận trong văn bản. Chứng cứ sớm nhất còn lại không nhất thiết là thời điểm cấu trúc đó xuất hiện.\n\nVì vậy, grammar của một programming language mang tính **quy định hệ thống** theo specification, trong khi grammar mô tả của ngôn ngữ tự nhiên chủ yếu là **mô hình được xây dựng từ dữ liệu ngôn ngữ**.\n\n\n\n## 15. Tại sao Parser và Semantic Analysis hữu ích khi nghiên cứu ngữ pháp tự nhiên?\n\nCác khái niệm của compiler cung cấp một mô hình đối chiếu để phân biệt ba vấn đề:\n\nNhận diện đơn vị\n↓\nXác định cấu trúc\n↓\nKiểm tra quan hệ ngữ nghĩa\n\n\nBa vấn đề này cũng xuất hiện trong phân tích ngôn ngữ tự nhiên.\n\nMột chuỗi từ có thể cho phép nhiều cách phân tầng. Một cấu trúc có thể phù hợp với một pattern hình thức nhưng không bảo toàn quan hệ ngữ nghĩa của phát ngôn. Một thành phần cũng có thể phù hợp với một vị trí cú pháp nhưng không đáp ứng yêu cầu ngữ nghĩa của thành phần khác.\n\nPhép đối chiếu này không đồng nhất:\n\nNatural Language = Programming Language\n\n\nvà cũng không đồng nhất:\n\nPhân tích ngữ pháp = Compiler\n\n\nNó chỉ cung cấp một hệ thuật ngữ và mô hình hình thức để phân biệt **đơn vị, cấu trúc và ngữ nghĩa**.\n\n\n\n## 16. Tổng kết: Token → Structure → Meaning\n\nQuá trình được trình bày trong bài có thể rút gọn thành:\n\nSource\n↓\nLexer\n↓\nTokens\n↓\nParser\n↓\nParse Tree / AST\n↓\nSemantic Analysis\n↓\nValidated Representation\n\n\nBa tầng quan trọng nhất là:\n\nToken\n↓\nStructure\n↓\nMeaning / Constraints\n\n\n**Lexer** xác định các đơn vị từ vựng.\n\n**Parser** xác định cấu trúc phân cấp giữa các đơn vị.\n\n**Semantic Analysis** kiểm tra các thuộc tính và ràng buộc ngữ nghĩa của cấu trúc.\n\nMột pattern có thể tạo ra một candidate structure. Candidate structure không tự động trở thành một phân tích hợp lệ chỉ vì nó có thể được biểu diễn dưới dạng cây.\n\n\n\n## 17. Đọc tiếp: Từ Parser đến 层次分析\n\nBài tiếp theo đối chiếu các khái niệm trên với phân tích ngữ pháp ngôn ngữ tự nhiên:\n\n**Từ Parser đến 层次分析: Cấu trúc cú pháp và quan hệ ngữ nghĩa trong phân tích ngữ pháp**\n\nTrọng tâm gồm:\n\nPattern\n↓\nCandidate Structure\n↓\nHierarchical Analysis\n↓\nSemantic Relations\n\n\nvới các cấu trúc tiếng Hán dùng để phân biệt **khả năng tạo thành một cụm về hình thức** và **quan hệ ngữ nghĩa thực sự giữa các thành phần**.","html":"<h1>Từ Lexer đến Semantic Analysis: Compiler hiểu mã nguồn như thế nào?</h1><p>Compiler là chương trình chuyển đổi mã nguồn từ một biểu diễn sang một biểu diễn khác, thường từ ngôn ngữ lập trình bậc cao sang mã máy, assembly, bytecode hoặc một dạng biểu diễn trung gian.</p><p>Quá trình biên dịch không chỉ là phép thay thế văn bản. Mã nguồn phải được nhận diện thành các đơn vị từ vựng, tổ chức thành cấu trúc cú pháp và kiểm tra các ràng buộc ngữ nghĩa.</p><p>Một pipeline compiler có thể được giản lược như sau:</p><pre><code class=\"language-text\">Source Code</code></pre><pre><code class=\"language-text\">    ↓</code></pre><pre><code class=\"language-text\">Lexical Analysis</code></pre><pre><code class=\"language-text\">    ↓</code></pre><pre><code class=\"language-text\">Tokens</code></pre><pre><code class=\"language-text\">    ↓</code></pre><pre><code class=\"language-text\">Parsing</code></pre><pre><code class=\"language-text\">    ↓</code></pre><pre><code class=\"language-text\">Parse Tree / AST</code></pre><pre><code class=\"language-text\">    ↓</code></pre><pre><code class=\"language-text\">Semantic Analysis</code></pre><pre><code class=\"language-text\">    ↓</code></pre><pre><code class=\"language-text\">Validated Representation</code></pre><p>Các compiler thực tế có thể chia nhỏ, hợp nhất hoặc bổ sung nhiều giai đoạn khác. Mô hình trên chỉ biểu diễn các tầng cần thiết cho phạm vi bài viết.</p><h2>1. Compiler làm gì với mã nguồn?</h2><p>Mã nguồn ban đầu là một chuỗi ký tự.</p><pre><code class=\"language-text\">result = a + b * 2</code></pre><p>Ở dạng này, compiler chưa thể trực tiếp suy ra toàn bộ cấu trúc của biểu thức. Nó phải xác định:</p><ul><li>các đơn vị từ vựng;</li><li>loại của từng đơn vị;</li><li>quan hệ cú pháp giữa chúng;</li><li>cấu trúc phân cấp của biểu thức;</li><li>các ràng buộc ngữ nghĩa.</li></ul><p>Ba tầng cần phân biệt là:</p><pre><code class=\"language-text\">Ký tự → Đơn vị → Cấu trúc → Ngữ nghĩa</code></pre><p>Nhận diện đúng các đơn vị không đồng nghĩa với xác định đúng cấu trúc. Xác định được cấu trúc cú pháp cũng không đồng nghĩa với cấu trúc đó hợp lệ về ngữ nghĩa.</p><h2>2. Token — đơn vị cơ bản của mã nguồn</h2><p><strong>Token</strong> là một đơn vị từ vựng được compiler nhận diện trong mã nguồn.</p><p>Ví dụ:</p><pre><code class=\"language-text\">score = value + 10</code></pre><p>có thể được biểu diễn thành:</p><pre><code class=\"language-text\">IDENTIFIER(\"score\")</code></pre><pre><code class=\"language-text\">ASSIGN&amp;quot;=&amp;quot;</code></pre><pre><code class=\"language-text\">IDENTIFIER(\"value\")</code></pre><pre><code class=\"language-text\">PLUS(\"+\")</code></pre><pre><code class=\"language-text\">INTEGER(10)</code></pre><p>Trong đó:</p><p>| Mã nguồn | Loại token |</p><p>|---|---|</p><p>| <code>score</code> | Identifier |</p><p>| <code>=</code> | Assignment operator |</p><p>| <code>value</code> | Identifier |</p><p>| <code>+</code> | Arithmetic operator |</p><p>| <code>10</code> | Integer literal |</p><p>Token không nhất thiết tương ứng với một ký tự. <code>score</code> gồm nhiều ký tự nhưng tạo thành một token duy nhất.</p><p>Token chủ yếu cung cấp thông tin về <strong>loại đơn vị</strong>, chưa xác định đầy đủ <strong>vai trò cấu trúc</strong> của đơn vị đó.</p><h2>3. Lexer và Lexical Analysis — từ ký tự thành token</h2><p><strong>Lexical Analysis</strong> là quá trình phân tích chuỗi ký tự của mã nguồn thành các token. Thành phần thực hiện quá trình này thường được gọi là <strong>lexer</strong> hoặc <strong>lexical analyzer</strong>.</p><pre><code class=\"language-text\">Characters</code></pre><pre><code class=\"language-text\">    ↓</code></pre><pre><code class=\"language-text\">  Lexer</code></pre><pre><code class=\"language-text\">    ↓</code></pre><pre><code class=\"language-text\"> Tokens</code></pre><p>Ví dụ:</p><pre><code class=\"language-text\">total = price * 2</code></pre><p>có thể được lexer chuyển thành:</p><pre><code class=\"language-text\">IDENTIFIER(\"total\")</code></pre><pre><code class=\"language-text\">ASSIGN</code></pre><pre><code class=\"language-text\">IDENTIFIER(\"price\")</code></pre><pre><code class=\"language-text\">MULTIPLY</code></pre><pre><code class=\"language-text\">INTEGER(2)</code></pre><p>Lexer thường xử lý các loại đơn vị như:</p><ul><li>identifier;</li><li>keyword;</li><li>operator;</li><li>literal;</li><li>delimiter.</li></ul><p>Lexical analysis không có nhiệm vụ xác định toàn bộ quan hệ cú pháp giữa các token.</p><blockquote><strong>Lexer xác định các đơn vị. Parser xác định cấu trúc giữa các đơn vị.</strong></blockquote><h2>4. Grammar — các quy tắc cấu tạo ngôn ngữ</h2><p><strong>Grammar</strong> là hệ thống quy tắc hình thức mô tả cách các cấu trúc hợp lệ của một ngôn ngữ có thể được tạo thành.</p><p>Một grammar giản lược có thể chứa các production rule:</p><pre><code class=\"language-text\">Expression → Expression + Expression</code></pre><pre><code class=\"language-text\">Expression → Expression * Expression</code></pre><pre><code class=\"language-text\">Expression → Identifier</code></pre><pre><code class=\"language-text\">Expression → Integer</code></pre><p>Ký hiệu:</p><pre><code class=\"language-text\">A → B C</code></pre><p>có nghĩa rằng cấu trúc thuộc loại <code>A</code> có thể được tạo bởi các thành phần <code>B</code> và <code>C</code> theo quy tắc tương ứng.</p><p>Grammar không chỉ mô tả token nào có thể xuất hiện. Nó mô tả <strong>cách các token hoặc cấu trúc nhỏ hơn kết hợp thành cấu trúc lớn hơn</strong>.</p><p>Trong ngôn ngữ lập trình thực tế, grammar còn phải xử lý các vấn đề như precedence, associativity, grouping và nhiều loại cấu trúc khác.</p><h2>5. Parser — từ chuỗi token đến cấu trúc</h2><p><strong>Parser</strong> là thành phần phân tích chuỗi token dựa trên grammar để xác định cấu trúc cú pháp của chương trình.</p><p>Xét:</p><pre><code class=\"language-text\">a + b * c</code></pre><p>Chuỗi token tuyến tính là:</p><pre><code class=\"language-text\">a  +  b  *  c</code></pre><p>Nhưng cấu trúc của biểu thức có thể là:</p><pre><code class=\"language-text\">a + (b * c)</code></pre><p>Biểu diễn dưới dạng cây:</p><pre><code class=\"language-text\">      +</code></pre><pre><code class=\"language-text\">     / \\</code></pre><pre><code class=\"language-text\">    a   *</code></pre><pre><code class=\"language-text\">       / \\</code></pre><pre><code class=\"language-text\">      b   c</code></pre><p>Cấu trúc này khác với:</p><pre><code class=\"language-text\">(a + b) * c</code></pre><p>tương ứng:</p><pre><code class=\"language-text\">        *</code></pre><pre><code class=\"language-text\">       / \\</code></pre><pre><code class=\"language-text\">      +   c</code></pre><pre><code class=\"language-text\">     / \\</code></pre><pre><code class=\"language-text\">    a   b</code></pre><p>Parser vì vậy không chỉ nhận diện sự xuất hiện của <code>a</code>, <code>+</code>, <code>b</code>, <code>*</code>, <code>c</code>. Nó xác định <strong>quan hệ phân cấp</strong> giữa chúng.</p><h2>6. Parse Tree — cấu trúc tầng bậc của cú pháp</h2><p><strong>Parse Tree</strong> là cây biểu diễn cách một chuỗi token được phân tích theo các production rule của grammar.</p><p>Với:</p><pre><code class=\"language-text\">a + b * c</code></pre><p>một parse tree giản lược có thể là:</p><pre><code class=\"language-text\">Expression</code></pre><pre><code class=\"language-text\">├── Expression</code></pre><pre><code class=\"language-text\">│   └── Identifiera</code></pre><pre><code class=\"language-text\">├── \"+\"</code></pre><pre><code class=\"language-text\">└── Expression</code></pre><pre><code class=\"language-text\">    ├── Expression</code></pre><pre><code class=\"language-text\">    │   └── Identifierb</code></pre><pre><code class=\"language-text\">    ├── \"*\"</code></pre><pre><code class=\"language-text\">    └── Expression</code></pre><pre><code class=\"language-text\">        └── Identifierc</code></pre><p>Parse tree thể hiện:</p><ol><li>thành phần nào kết hợp với thành phần nào;</li><li>chúng tạo thành cấu trúc trung gian nào;</li><li>cấu trúc lớn hơn được hình thành theo thứ bậc nào.</li></ol><p>Chuỗi tuyến tính:</p><pre><code class=\"language-text\">a + b * c</code></pre><p>vì vậy được chuyển thành cấu trúc phân cấp:</p><pre><code class=\"language-text\">+</code></pre><pre><code class=\"language-text\">├── a</code></pre><pre><code class=\"language-text\">└── *</code></pre><pre><code class=\"language-text\">    ├── b</code></pre><pre><code class=\"language-text\">    └── c</code></pre><p><strong>Thứ tự tuyến tính và cấu trúc tầng bậc là hai loại thông tin khác nhau.</strong></p><h2>7. AST — Abstract Syntax Tree</h2><p><strong>Abstract Syntax Tree <span class=\"math-inline\" data-formula=\"AST\"><span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\">A</span><span class=\"mord mathnormal\" style=\"margin-right:0.13889em;\">ST</span></span></span></span></span></strong> là biểu diễn cây trừu tượng của cấu trúc cú pháp.</p><p>AST thường loại bỏ những node hoặc chi tiết chỉ cần thiết cho quá trình parsing nhưng không cần thiết cho các giai đoạn xử lý tiếp theo.</p><p>Ví dụ parse tree:</p><pre><code class=\"language-text\">Expression</code></pre><pre><code class=\"language-text\">├── Expression</code></pre><pre><code class=\"language-text\">│   └── Identifiera</code></pre><pre><code class=\"language-text\">├── \"+\"</code></pre><pre><code class=\"language-text\">└── Expression</code></pre><pre><code class=\"language-text\">    ├── Expression</code></pre><pre><code class=\"language-text\">    │   └── Identifierb</code></pre><pre><code class=\"language-text\">    ├── \"*\"</code></pre><pre><code class=\"language-text\">    └── Expression</code></pre><pre><code class=\"language-text\">        └── Identifierc</code></pre><p>có thể được rút gọn thành AST:</p><pre><code class=\"language-text\">Add</code></pre><pre><code class=\"language-text\">├── Identifiera</code></pre><pre><code class=\"language-text\">└── Multiply</code></pre><pre><code class=\"language-text\">    ├── Identifierb</code></pre><pre><code class=\"language-text\">    └── Identifierc</code></pre><p>AST giữ lại quan hệ quan trọng:</p><pre><code class=\"language-text\">Multiply(b, c)</code></pre><p>là một cấu trúc con của:</p><pre><code class=\"language-text\">Add(a, ...)</code></pre><p>Các giai đoạn semantic analysis, optimization hoặc code generation thường làm việc trên AST hoặc những biểu diễn trung gian được xây dựng từ nó.</p><h2>8. Parse Tree và AST khác nhau thế nào?</h2><p>Parse tree và AST đều biểu diễn cấu trúc phân cấp nhưng có mục đích khác nhau.</p><p>| Đặc điểm | Parse Tree | AST |</p><p>|---|---|---|</p><p>| Quan hệ với grammar | Trực tiếp | Trừu tượng hóa |</p><p>| Production trung gian | Thường được giữ | Thường được lược bỏ |</p><p>| Dấu câu và chi tiết cú pháp | Có thể được giữ | Có thể bị loại |</p><p>| Mục tiêu | Biểu diễn quá trình phân tích | Biểu diễn cấu trúc cần xử lý |</p><p>| Mức độ chi tiết | Cao hơn | Thấp hơn |</p><p>Không phải mọi compiler đều xây dựng hai cấu trúc này thành hai giai đoạn vật lý riêng biệt. Một parser có thể trực tiếp tạo AST.</p><p><code>Parse Tree</code> và <code>AST</code> vì vậy là hai khái niệm liên quan nhưng không đồng nhất.</p><h2>9. Candidate Parse và Ambiguity — một input, nhiều cách phân tích</h2><p>Một chuỗi token có thể cho phép nhiều cấu trúc cú pháp nếu grammar không cung cấp đủ điều kiện để xác định duy nhất một cấu trúc.</p><p>Ví dụ:</p><pre><code class=\"language-text\">a + b * c</code></pre><p>có hai candidate parse:</p><pre><code class=\"language-text\">(a + b) * c</code></pre><p>và:</p><pre><code class=\"language-text\">a + (b * c)</code></pre><p>Hai cấu trúc tạo ra hai cây khác nhau và có thể tạo ra hai kết quả khác nhau.</p><p>Hiện tượng một input có nhiều cách phân tích được gọi là <strong>syntactic ambiguity</strong>.</p><p>Ngôn ngữ lập trình thường được thiết kế để loại bỏ hoặc kiểm soát ambiguity bằng:</p><ul><li>precedence;</li><li>associativity;</li><li>dấu ngoặc;</li><li>grammar design;</li><li>các quy tắc bổ sung của ngôn ngữ.</li></ul><p>Một nguyên tắc quan trọng có thể rút ra:</p><blockquote><strong>Surface form không tự nó xác định đầy đủ cấu trúc bên dưới.</strong></blockquote><h2>10. Syntax hợp lệ chưa chắc Semantic hợp lệ</h2><p>Xét biểu thức giả định:</p><pre><code class=\"language-text\">\"hello\" - 5</code></pre><p>Nếu grammar cho phép:</p><pre><code class=\"language-text\">Expression → Expression - Expression</code></pre><p>parser có thể dựng:</p><pre><code class=\"language-text\">Subtract</code></pre><pre><code class=\"language-text\">├── String(\"hello\")</code></pre><pre><code class=\"language-text\">└── Integer(5)</code></pre><p>Cấu trúc này thỏa quy tắc cú pháp của một binary expression.</p><p>Tuy nhiên, nếu ngôn ngữ chỉ định toán tử <code>-</code> cho các kiểu số, phép toán:</p><pre><code class=\"language-text\">String - Integer</code></pre><p>không hợp lệ.</p><p>Hai kết quả khác nhau:</p><pre><code class=\"language-text\">Syntax:   hợp lệ</code></pre><pre><code class=\"language-text\">Semantic: không hợp lệ</code></pre><p>Parser đã hoàn thành nhiệm vụ của nó. Lỗi chỉ xuất hiện khi hệ thống kiểm tra các ràng buộc nằm ngoài cấu trúc cú pháp đơn thuần.</p><blockquote><strong>Một cấu trúc có thể parse được nhưng vẫn không hợp lệ về ngữ nghĩa.</strong></blockquote><h2>11. Semantic Analysis — kiểm tra ngữ nghĩa của cấu trúc</h2><p><strong>Semantic Analysis</strong> là giai đoạn kiểm tra các thuộc tính và ràng buộc ngữ nghĩa của cấu trúc đã được phân tích cú pháp.</p><p>Tùy thiết kế ngôn ngữ, semantic analysis có thể kiểm tra:</p><ul><li>identifier đã được khai báo hay chưa;</li><li>phạm vi của identifier;</li><li>kiểu dữ liệu;</li><li>tính tương thích giữa các kiểu;</li><li>tham số của function;</li><li>kiểu trả về;</li><li>khả năng áp dụng operator;</li><li>các ràng buộc ngữ nghĩa khác.</li></ul><p>Ví dụ:</p><pre><code class=\"language-text\">x: Integer</code></pre><pre><code class=\"language-text\">x + 10</code></pre><p>có thể hợp lệ.</p><p>Trong khi:</p><pre><code class=\"language-text\">x: String</code></pre><pre><code class=\"language-text\">x - 10</code></pre><p>có thể không hợp lệ.</p><p>Hai biểu thức đều có thể có cùng cấu trúc cú pháp tổng quát:</p><pre><code class=\"language-text\">BinaryExpression</code></pre><pre><code class=\"language-text\">├── LeftOperand</code></pre><pre><code class=\"language-text\">├── Operator</code></pre><pre><code class=\"language-text\">└── RightOperand</code></pre><p>Sự khác biệt nằm ở các thuộc tính và quan hệ ngữ nghĩa của những node bên trong.</p><h2>12. Type Checking và Semantic Constraints</h2><p><strong>Type checking</strong> là quá trình kiểm tra một biểu thức hoặc thao tác có tương thích với các kiểu dữ liệu liên quan hay không.</p><p>Giả sử:</p><pre><code class=\"language-text\">add(Int, Int) → Int</code></pre><p>Lời gọi:</p><pre><code class=\"language-text\">add(1, 2)</code></pre><p>thỏa yêu cầu:</p><pre><code class=\"language-text\">add(Int, Int)</code></pre><p>Trong khi:</p><pre><code class=\"language-text\">add(\"one\", 2)</code></pre><p>tạo ra:</p><pre><code class=\"language-text\">add(String, Int)</code></pre><p>và không thỏa signature đã định nghĩa.</p><p>Có thể biểu diễn dưới dạng constraint:</p><pre><code class=\"language-text\">Expected:</code></pre><pre><code class=\"language-text\">    add(Int, Int)</code></pre><pre><code class=\"language-text\">Received:</code></pre><pre><code class=\"language-text\">    add(String, Int)</code></pre><pre><code class=\"language-text\">Result:</code></pre><pre><code class=\"language-text\">    Type mismatch</code></pre><p>Khái niệm tổng quát hơn là <strong>semantic constraint</strong>: một cấu trúc chỉ hợp lệ khi các thành phần của nó đáp ứng những điều kiện ngữ nghĩa nhất định.</p><p>Một node vì vậy có thể xuất hiện tại vị trí được grammar cho phép nhưng vẫn không đáp ứng constraint của cấu trúc chứa nó.</p><h2>13. Syntax và Semantic khác nhau ở đâu?</h2><p><strong>Syntax</strong> mô tả cách các đơn vị được tổ chức thành cấu trúc.</p><p><strong>Semantic</strong> xác định các thuộc tính, quan hệ và ràng buộc có ý nghĩa trên cấu trúc đó.</p><p>Ví dụ:</p><pre><code class=\"language-text\">\"hello\" - 5</code></pre><p>Syntax có thể tạo:</p><pre><code class=\"language-text\">Subtract</code></pre><pre><code class=\"language-text\">├── String</code></pre><pre><code class=\"language-text\">└── Integer</code></pre><p>Semantic analysis kiểm tra:</p><pre><code class=\"language-text\">subtract(String, Integer)</code></pre><p>và có thể bác bỏ nó.</p><p>Có thể khái quát:</p><pre><code class=\"language-text\">Tokens</code></pre><pre><code class=\"language-text\">   ↓</code></pre><pre><code class=\"language-text\">Syntactic Structure</code></pre><pre><code class=\"language-text\">   ↓</code></pre><pre><code class=\"language-text\">Semantic Validation</code></pre><p>Ba tầng này không thay thế lẫn nhau.</p><p>Nhận diện token không đủ để xác định cấu trúc. Xác định cấu trúc không đủ để xác nhận ngữ nghĩa.</p><h2>14. Programming Language và Natural Language</h2><p>Ngôn ngữ lập trình và ngôn ngữ tự nhiên không hình thành theo cùng một cơ chế.</p><p>Một ngôn ngữ lập trình thường có specification hoặc tập quy tắc được xác định trước:</p><pre><code class=\"language-text\">Language Specification</code></pre><pre><code class=\"language-text\">        ↓</code></pre><pre><code class=\"language-text\">Grammar + Semantic Rules</code></pre><pre><code class=\"language-text\">        ↓</code></pre><pre><code class=\"language-text\">Valid Programs</code></pre><p>Chương trình được đánh giá dựa trên các quy tắc của ngôn ngữ.</p><p>Ngôn ngữ tự nhiên không vận hành theo chiều này. Một mô hình giản lược phù hợp hơn là:</p><pre><code class=\"language-text\">Language Usage</code></pre><pre><code class=\"language-text\">      ↓</code></pre><pre><code class=\"language-text\">Observed Data</code></pre><pre><code class=\"language-text\">      ↓</code></pre><pre><code class=\"language-text\">Analysis</code></pre><pre><code class=\"language-text\">      ↓</code></pre><pre><code class=\"language-text\">Generalization</code></pre><pre><code class=\"language-text\">      ↓</code></pre><pre><code class=\"language-text\">Grammatical Model</code></pre><p>Người nói không cần một grammar do nhà ngôn ngữ học viết ra trước khi sử dụng ngôn ngữ. Grammar trong nghiên cứu ngôn ngữ học chủ yếu là kết quả của việc quan sát, mô tả và khái quát các hiện tượng ngôn ngữ.</p><p>Dữ liệu lịch sử cũng không hoàn chỉnh. Một cấu trúc có thể đã tồn tại trong khẩu ngữ trước khi được ghi nhận trong văn bản. Chứng cứ sớm nhất còn lại không nhất thiết là thời điểm cấu trúc đó xuất hiện.</p><p>Vì vậy, grammar của một programming language mang tính <strong>quy định hệ thống</strong> theo specification, trong khi grammar mô tả của ngôn ngữ tự nhiên chủ yếu là <strong>mô hình được xây dựng từ dữ liệu ngôn ngữ</strong>.</p><h2>15. Tại sao Parser và Semantic Analysis hữu ích khi nghiên cứu ngữ pháp tự nhiên?</h2><p>Các khái niệm của compiler cung cấp một mô hình đối chiếu để phân biệt ba vấn đề:</p><pre><code class=\"language-text\">Nhận diện đơn vị</code></pre><pre><code class=\"language-text\">        ↓</code></pre><pre><code class=\"language-text\">Xác định cấu trúc</code></pre><pre><code class=\"language-text\">        ↓</code></pre><pre><code class=\"language-text\">Kiểm tra quan hệ ngữ nghĩa</code></pre><p>Ba vấn đề này cũng xuất hiện trong phân tích ngôn ngữ tự nhiên.</p><p>Một chuỗi từ có thể cho phép nhiều cách phân tầng. Một cấu trúc có thể phù hợp với một pattern hình thức nhưng không bảo toàn quan hệ ngữ nghĩa của phát ngôn. Một thành phần cũng có thể phù hợp với một vị trí cú pháp nhưng không đáp ứng yêu cầu ngữ nghĩa của thành phần khác.</p><p>Phép đối chiếu này không đồng nhất:</p><pre><code class=\"language-text\">Natural Language = Programming Language</code></pre><p>và cũng không đồng nhất:</p><pre><code class=\"language-text\">Phân tích ngữ pháp = Compiler</code></pre><p>Nó chỉ cung cấp một hệ thuật ngữ và mô hình hình thức để phân biệt <strong>đơn vị, cấu trúc và ngữ nghĩa</strong>.</p><h2>16. Tổng kết: Token → Structure → Meaning</h2><p>Quá trình được trình bày trong bài có thể rút gọn thành:</p><pre><code class=\"language-text\">Source</code></pre><pre><code class=\"language-text\">  ↓</code></pre><pre><code class=\"language-text\">Lexer</code></pre><pre><code class=\"language-text\">  ↓</code></pre><pre><code class=\"language-text\">Tokens</code></pre><pre><code class=\"language-text\">  ↓</code></pre><pre><code class=\"language-text\">Parser</code></pre><pre><code class=\"language-text\">  ↓</code></pre><pre><code class=\"language-text\">Parse Tree / AST</code></pre><pre><code class=\"language-text\">  ↓</code></pre><pre><code class=\"language-text\">Semantic Analysis</code></pre><pre><code class=\"language-text\">  ↓</code></pre><pre><code class=\"language-text\">Validated Representation</code></pre><p>Ba tầng quan trọng nhất là:</p><pre><code class=\"language-text\">Token</code></pre><pre><code class=\"language-text\">  ↓</code></pre><pre><code class=\"language-text\">Structure</code></pre><pre><code class=\"language-text\">  ↓</code></pre><pre><code class=\"language-text\">Meaning / Constraints</code></pre><p><strong>Lexer</strong> xác định các đơn vị từ vựng.</p><p><strong>Parser</strong> xác định cấu trúc phân cấp giữa các đơn vị.</p><p><strong>Semantic Analysis</strong> kiểm tra các thuộc tính và ràng buộc ngữ nghĩa của cấu trúc.</p><p>Một pattern có thể tạo ra một candidate structure. Candidate structure không tự động trở thành một phân tích hợp lệ chỉ vì nó có thể được biểu diễn dưới dạng cây.</p><h2>17. Đọc tiếp: Từ Parser đến 层次分析</h2><p>Bài tiếp theo đối chiếu các khái niệm trên với phân tích ngữ pháp ngôn ngữ tự nhiên:</p><p><strong>Từ Parser đến 层次分析: Cấu trúc cú pháp và quan hệ ngữ nghĩa trong phân tích ngữ pháp</strong></p><p>Trọng tâm gồm:</p><pre><code class=\"language-text\">Pattern</code></pre><pre><code class=\"language-text\">   ↓</code></pre><pre><code class=\"language-text\">Candidate Structure</code></pre><pre><code class=\"language-text\">   ↓</code></pre><pre><code class=\"language-text\">Hierarchical Analysis</code></pre><pre><code class=\"language-text\">   ↓</code></pre><pre><code class=\"language-text\">Semantic Relations</code></pre><p>với các cấu trúc tiếng Hán dùng để phân biệt <strong>khả năng tạo thành một cụm về hình thức</strong> và <strong>quan hệ ngữ nghĩa thực sự giữa các thành phần</strong>.</p>","tags":[],"author":"virgoricomp_102605","publishedAt":"2026-09-27T20:03:16.850Z","updatedAt":"2026-09-27T23:09:02.836Z","published_at":"2026-09-27T20:03:16.850Z","updated_at":"2026-09-27T23:09:02.836Z","view_count":10,"canonical":"https://wiki.quizzman.com/wiki/tu-lexer-den-semantic-analysis-compiler-hieu-ma-nguon-nhu-the-nao","url":"https://wiki.quizzman.com/wiki/tu-lexer-den-semantic-analysis-compiler-hieu-ma-nguon-nhu-the-nao","markdownUrl":"https://wiki.quizzman.com/api/articles/tu-lexer-den-semantic-analysis-compiler-hieu-ma-nguon-nhu-the-nao.md","apiUrl":"https://wiki.quizzman.com/api/articles/tu-lexer-den-semantic-analysis-compiler-hieu-ma-nguon-nhu-the-nao"}