TÓM TẮT
- 1 Mở Đầu
- 2 1. Tổng Quan Về Cây Syntax
- 3 2. Các Thành Phần Cơ Bản Khi Vẽ Cây Syntax
- 4 3. Quy Trình Vẽ Cây Syntax Từ Đầu Đến Cuối
- 5 4. Ví Dụ Thực Tế: Vẽ Cây Syntax Cho Biểu Thức 3 + 4 (2 - 1)
- 6 5. Công Cụ Hỗ Trợ Vẽ Cây Syntax
- 7 6. Kỹ Thuật Nâng Cao Khi Vẽ Cây Syntax
- 8 7. Thực Hành: Xây Dựng Parser và AST Bằng Python
- 9 8. Những Sai Lầm Thường Gặp Khi Vẽ Cây Syntax
- 10 9. Ứng Dụng Thực Tế Của Cây Syntax
- 11 10. Tổng Kết
Mở Đầu
Trong lĩnh vực khoa học máy tính, đặc biệt là trong việc xây dựng trình biên dịch, xử lý ngôn ngữ tự nhiên và thiết kế các hệ thống phân tích cú pháp, cây syntax (syntax tree) – còn được gọi là cây phân tích cú pháp (parse tree) hoặc cây trừu tượng (abstract syntax tree – AST) – đóng một vai trò then chốt. Việc vẽ cây syntax không chỉ giúp chúng ta hình dung cấu trúc ngữ pháp của một câu, một biểu thức hay một đoạn mã, mà còn là nền tảng để thực hiện các bước tiếp theo như kiểm tra ngữ nghĩa, tối ưu hoá code, hoặc sinh mã máy.
Bài viết này sẽ đưa bạn từ các khái niệm cơ bản về cây syntax, cách xây dựng một cây syntax cho các ngôn ngữ đơn giản, công cụ hỗ trợ và kỹ thuật nâng cao để vẽ và xử lý cây syntax trong các dự án thực tế. Mục tiêu là cung cấp một hướng dẫn chi tiết, đủ chiều sâu để người mới bắt đầu có thể tự tin tạo ra cây syntax, đồng thời cung cấp các mẹo và thực tiễn tốt nhất cho các lập trình viên trung cấp và cao cấp.
Lưu ý: Bài viết này tập trung vào cách vẽ cây syntax (cách mô tả, cách biểu diễn bằng đồ thị, cách triển khai trong code) chứ không chỉ là lý thuyết về ngữ pháp. Các phần sẽ bao gồm ví dụ thực tế, hình minh hoạ (được mô tả bằng ký tự ASCII) và mã nguồn mẫu bằng Python, Java và JavaScript.
1. Tổng Quan Về Cây Syntax
1.1 Định Nghĩa
-
Cây Syntax (Parse Tree): Là một cây có gốc (root) đại diện cho khởi đầu của một ngữ pháp, các nút con đại diện cho các quy tắc sản xuất (production rules), và các lá (leaf) thường là các token (đơn vị từ vựng) thu được sau quá trình lexical analysis (phân tích từ vựng).
-
Cây Trừu Tượng (AST – Abstract Syntax Tree): Là một dạng rút gọn của parse tree, chỉ giữ lại những thông tin quan trọng cho việc biên dịch hoặc thực thi, bỏ bớt các nút không cần thiết như dấu ngoặc, từ khóa không ảnh hưởng tới ý nghĩa.

Cách Vẽ Cây Syntax Có thể bạn quan tâm: Cách Vẽ Cánh Chim Đẹp Và Chi Tiết: Hướng Dẫn Từng Bước Cho Người Mới Bắt Đầu
1.2 Vai Trò Của Cây Syntax
| Giai Đoạn | Mục Đích | Cây Syntax Đóng Góp |
|---|---|---|
| Parsing (phân tích cú pháp) | Kiểm tra câu (statement) có tuân theo ngữ pháp không | Xây dựng cây để kiểm tra cấu trúc |
| Semantic Analysis (phân tích ngữ nghĩa) | Kiểm tra kiểu dữ liệu, phạm vi biến | Duyệt cây để thu thập thông tin |
| Optimization | Rút gọn biểu thức, loại bỏ code chết | Thay đổi cấu trúc cây để tối ưu |
| Code Generation | Sinh mã máy hoặc bytecode | Duyệt cây để tạo ra lệnh máy |
2. Các Thành Phần Cơ Bản Khi Vẽ Cây Syntax
2.1 Ngữ Pháp (Grammar)
Ngữ pháp thường được mô tả bằng BNF (Backus–Naur Form) hoặc EBNF (Extended BNF). Ví dụ, một ngữ pháp đơn giản cho biểu thức số học:
Expr ::= Term (('+' | '-') Term)
Term ::= Factor (('' | '/') Factor)
Factor ::= Number | '(' Expr ')'
Number ::= 0-9+
2.2 Token
Token là các đơn vị từ vựng sau quá trình lexical analysis. Với ví dụ trên, các token có thể là:
| Token | Giá Trị | Loại |
|---|---|---|
NUMBER |
123 |
Số nguyên |
PLUS |
+ |
Toán tử cộng |
MINUS |
- |
Toán tử trừ |
STAR |
|
Toán tử nhân |
SLASH |
/ |
Toán tử chia |
LPAREN |
( |
Dấu mở ngoặc |
RPAREN |
) |
Dấu đóng ngoặc |
2.3 Các Kiểu Nút (Node Types)
- Nút Gốc (Root Node): Thường là
ProgramhoặcExprtùy vào ngữ pháp. - Nút Nội (Internal Node): Đại diện cho một quy tắc sản xuất, ví dụ
Add,Mul,Neg. - Nút Lá (Leaf Node): Đại diện cho token, ví dụ
NUMBER(5).
3. Quy Trình Vẽ Cây Syntax Từ Đầu Đến Cuối

Có thể bạn quan tâm: Cách Vẽ Cái Võng: Hướng Dẫn Chi Tiết Từ Cơ Bản Đến Nâng Cao
Bước 1: Xác Định Ngữ Pháp
- Chọn ngữ pháp phù hợp với ngôn ngữ hoặc biểu thức cần phân tích.
- Kiểm tra khả năng không mơ hồ (unambiguity) và LL(1) / LR(1) để quyết định công cụ parser.
Bước 2: Thực Hiện Lexical Analysis
- Viết lexer (có thể dùng regex) để chuyển chuỗi đầu vào thành danh sách token.
- Mỗi token bao gồm type, lexeme, vị trí (line, column).
Bước 3: Xây Dựng Parser
- Recursive Descent Parser (cho ngữ pháp LL) hoặc LR Parser (sử dụng công cụ như Yacc/Bison).
- Parser sẽ gọi các hàm tương ứng với từng quy tắc ngữ pháp và tạo nút trong cây.
Bước 4: Tạo Cây Syntax
- Khi một quy tắc được khớp, khởi tạo một nút (node object) và đính kèm các con (child nodes) trả về từ các hàm con.
- Đối với AST, loại bỏ các nút không cần (ví dụ:
LPAREN,RPAREN).
Bước 5: Kiểm Tra và In Cây
- Dùng đệ quy để duyệt cây và in ra dạng cây (ASCII, Graphviz DOT, hoặc GUI).
- Kiểm tra độ sâu và độ cân bằng để phát hiện lỗi ngữ pháp.
Bước 6: Áp Dụng Các Giai Đoạn Tiếp Theo
- Semantic analysis, optimization, code generation dựa trên cây đã vẽ.
4. Ví Dụ Thực Tế: Vẽ Cây Syntax Cho Biểu Thức 3 + 4 (2 - 1)
4.1 Bước 1: Lexical Analysis

Có thể bạn quan tâm: Cách Vẽ Cái Trống: Hướng Dẫn Từng Bước Đầy Đủ Cho Người Mới Bắt Đầu
| Index | Token | Lexeme |
|---|---|---|
| 0 | NUMBER | 3 |
| 1 | PLUS | + |
| 2 | NUMBER | 4 |
| 3 | STAR | |
| 4 | LPAREN | ( |
| 5 | NUMBER | 2 |
| 6 | MINUS | – |
| 7 | NUMBER | 1 |
| 8 | RPAREN | ) |
4.2 Bước 2: Parsing (Recursive Descent)
# Pseudo-code Python
def parse_expr(): node = parse_term() while current_token.type in (PLUS, MINUS): op = current_token advance() right = parse_term() node = BinOpNode(op, node, right) # tạo nút nhị phân return node
4.3 Bước 3: Cây Syntax (Parse Tree)
Expr
├─ Term
│ └─ Factor
│ └─ NUMBER(3)
├─ PLUS
└─ Term ├─ Factor │ └─ NUMBER(4) ├─ STAR └─ Factor ├─ LPAREN └─ Expr ├─ Term │ └─ Factor │ └─ NUMBER(2) ├─ MINUS └─ Term └─ Factor └─ NUMBER(1) └─ RPAREN
4.4 Bước 4: AST (Rút Gọn)
Add
├─ Number(3)
└─ Mul ├─ Number(4) └─ Sub ├─ Number(2) └─ Number(1)
Lưu ý: AST bỏ qua các nút
LPAREN/RPARENvà các quy tắcTerm/Factorkhông mang thông tin ngữ nghĩa.
5. Công Cụ Hỗ Trợ Vẽ Cây Syntax
5.1 Graphviz
- DOT language cho phép mô tả cây dưới dạng đồ thị có hướng.
- Ví dụ:
digraph AST { node shape=box; Add -> Number3; Add -> Mul; Mul -> Number4; Mul -> Sub; Sub -> Number2; Sub -> Number1;
}
- Sử dụng lệnh
dot -Tpng ast.dot -o ast.pngđể tạo hình ảnh.
5.2 ANTLR
- ANTLR (ANother Tool for Language Recognition) tự động sinh lexer, parser và cung cấp API để lấy AST.
- Đoạn code Java:
ExprContext tree = parser.expr(); // parse input
ParseTreeWalker walker = new ParseTreeWalker();
MyListener listener = new MyListener();
walker.walk(listener, tree); // listener sẽ xây dựng AST
- ANTLR cũng hỗ trợ xuất cây dưới dạng DOT bằng
-treeflag.
5.3 Python ast Module
- Python có sẵn module
astđể phân tích mã nguồn Python và trả về AST. - Ví dụ:
import ast, astor
source = "3 + 4 (2 - 1)"
tree = ast.parse(source, mode='eval')
print(ast.dump(tree, indent=4))
5.4 Visual Studio Code Extensions

Có thể bạn quan tâm: Cách Vẽ Cái Giếng: Hướng Dẫn Chi Tiết Từ Cơ Bản Đến Nâng Cao
- CodeLLDB, Tree-sitter, và Syntax Tree Viewer giúp hiển thị cây syntax ngay trong IDE.
6. Kỹ Thuật Nâng Cao Khi Vẽ Cây Syntax
6.1 Xử Lý Ngữ Pháp Mơ Hồ (Ambiguous Grammar)
- Sử dụng precedence và associativity để giải quyết mơ hồ.
- Ví dụ, trong biểu thức
a - b - c, xác định left-associative để tạo cây(a - b) - c.
6.2 Tối Ưu Hóa AST
- Constant Folding: Tính toán các biểu thức hằng số ngay tại thời điểm xây dựng AST.
- Dead Code Elimination: Loại bỏ các nút không ảnh hưởng tới kết quả (ví dụ:
if (false) {...}).
6.3 Traversal Patterns
| Kiểu Duyệt | Mô Tả | Khi Dùng |
|---|---|---|
| Pre-order | Xử lý nút trước khi đi vào con | In‑order traversal cho biểu thức |
| Post-order | Xử lý con trước, rồi nút | Đánh giá biểu thức (postfix) |
| Breadth‑first | Duyệt theo mức độ | Kiểm tra cân bằng cây, visualisation |
6.4 Serialisation và Deserialisation
- JSON: Đưa AST thành JSON để truyền qua mạng hoặc lưu trữ.
- Protocol Buffers: Hiệu suất cao cho các hệ thống phân tán.
{ "type": "Add", "left": {"type":"Number","value":3}, "right":{ "type":"Mul", "left":{"type":"Number","value":4}, "right":{ "type":"Sub", "left":{"type":"Number","value":2}, "right":{"type":"Number","value":1} } }
}
6.5 Cây Syntax Trong Ngôn Ngữ Tự Nhiên (NLP)
- Dependency Tree và Constituency Tree là các dạng cây syntax cho câu tự nhiên.
- Công cụ Stanford Parser, spaCy cung cấp API để trích xuất và vẽ các cây này.
7. Thực Hành: Xây Dựng Parser và AST Bằng Python
7.1 Định Nghĩa Token

import re
Token = collections.namedtuple('Token', 'type', 'value') TOKEN_SPEC = ('NUMBER', r'\d+(\.\d)?'), # Integer or decimal ('PLUS', r'\+'), ('MINUS', r'-'), ('STAR', r'\'), ('SLASH', r'/'), ('LPAREN', r'\('), ('RPAREN', r'\)'), ('SKIP', r' \t+'), # Skip whitespace ('MISMATCH', r'.'), # Any other character def lexer(text): token_regex = '|'.join('(?P<%s>%s)' % pair for pair in TOKEN_SPEC) for mo in re.finditer(token_regex, text): kind = mo.lastgroup value = mo.group() if kind == 'NUMBER': yield Token('NUMBER', float(value) if '.' in value else int(value)) elif kind == 'SKIP': continue elif kind == 'MISMATCH': raise RuntimeError(f'Unexpected character: {value}') else: yield Token(kind, value)
7.2 Parser (Recursive Descent)
class Parser: def __init__(self, tokens): self.tokens = list(tokens) self.pos = 0 def peek(self): return self.tokensself.pos if self.pos < len(self.tokens) else Token('EOF', None) def advance(self): self.pos += 1 # Factor -> NUMBER | '(' Expr ')' def factor(self): tok = self.peek() if tok.type == 'NUMBER': self.advance() return {'type':'Number', 'value':tok.value} elif tok.type == 'LPAREN': self.advance() node = self.expr() if self.peek().type != 'RPAREN': raise SyntaxError('Expected )') self.advance() return node else: raise SyntaxError('Expected number or (') # Term -> Factor (( '' | '/' ) Factor) def term(self): node = self.factor() while self.peek().type in ('STAR', 'SLASH'): op = self.peek() self.advance() right = self.factor() node = {'type':'Mul' if op.type == 'STAR' else 'Div', 'left':node, 'right':right} return node # Expr -> Term (( '+' | '-' ) Term) def expr(self): node = self.term() while self.peek().type in ('PLUS', 'MINUS'): op = self.peek() self.advance() right = self.term() node = {'type':'Add' if op.type == 'PLUS' else 'Sub', 'left':node, 'right':right} return node def parse(text): return Parser(lexer(text)).expr()
7.3 In Cây AST
def print_ast(node, indent=0): space = ' 'indent if node'type' == 'Number': print(f"{space}Number({node'value'})") else: print(f"{space}{node'type'}") print_ast(node'left', indent+1) print_ast(node'right', indent+1) ast = parse('3 + 4 (2 - 1)')
print_ast(ast)
Kết quả
Add Number(3) Mul Number(4) Sub Number(2) Number(1)
7.4 Tích Hợp Với Graphviz
from graphviz import Digraph def ast_to_dot(node, dot=None, parent_id=None): if dot is None: dot = Digraph() node_id = str(id(node)) label = node'type' if node'type' != 'Number' else f"Number({node'value'})" dot.node(node_id, label) if parent_id: dot.edge(parent_id, node_id) if node'type' != 'Number': ast_to_dot(node'left', dot, node_id) ast_to_dot(node'right', dot, node_id) return dot dot = ast_to_dot(ast)
dot.render('ast_example', format='png', cleanup=True)
File
ast_example.pngsẽ hiển thị cây AST trực quan.
8. Những Sai Lầm Thường Gặp Khi Vẽ Cây Syntax
| Sai Lầm | Mô Tả | Cách Khắc Phục |
|---|---|---|
| Nhầm lẫn Parse Tree và AST | Giữ lại các nút không cần (dấu ngoặc) dẫn tới cây quá lớn. | Rút gọn thành AST ngay sau khi parse. |
| Không Xử Lý Ưu Tiên (Precedence) | a + b c thành (+ a ( b c)) thay vì (+ a ( b c))? (có thể nhầm) |
Định nghĩa bảng precedence, dùng parser generator hỗ trợ. |
| Quên Kiểm Tra EOF | Parser dừng giữa chừng, để token thừa. | Luôn kiểm tra token.type == EOF sau parse. |
| Tree Traversal Sai Thứ Tự | Đánh giá biểu thức bằng pre-order gây lỗi. | Sử dụng post-order cho evaluation. |
| Không Đóng Gói Thông Tin Vị Trí | Khi báo lỗi không có vị trí chính xác. | Mỗi token nên lưu line, column. |
9. Ứng Dụng Thực Tế Của Cây Syntax
- Compilers: GCC, Clang sử dụng AST để tối ưu hoá và sinh mã máy.
- Interpreters: Python, JavaScript engine (V8) xây dựng bytecode từ AST.
- Static Analyzers: SonarQube, ESLint phân tích AST để phát hiện lỗi.
- Query Optimizer: SQL engines (PostgreSQL) chuyển query thành cây biểu thức.
- NLP: Stanford Parser, spaCy tạo cây cú pháp để phân tích cú pháp câu.
10. Tổng Kết
- Cây syntax là công cụ nền tảng để hiểu và xử lý cấu trúc ngôn ngữ, từ ngôn ngữ lập trình tới ngôn ngữ tự nhiên.
- Quá trình vẽ cây bao gồm: định nghĩa ngữ pháp → lexical analysis → parsing → tạo cây → tối ưu hoá.
- Các công cụ như Graphviz, ANTLR, Python
ast, và spaCy giúp tự động hoá và trực quan hoá quá trình. - Hiểu rõ các kiểu nút, cách duyệt, và kỹ thuật tối ưu sẽ nâng cao hiệu quả phát triển hệ thống biên dịch, interpreter, và các công cụ phân tích tĩnh.
- Tránh các sai lầm phổ biến và áp dụng các best practices để tạo ra cây syntax sạch, dễ bảo trì và mở rộng.
Hy vọng bài viết đã cung cấp cho bạn một hướng dẫn toàn diện về cách vẽ cây syntax, từ lý thuyết nền tảng đến thực tiễn triển khai. Khi bạn đã nắm vững các bước và công cụ, việc xây dựng parser và AST cho bất kỳ ngôn ngữ nào sẽ trở nên đơn giản và hiệu quả hơn. Chúc bạn thành công trong các dự án phân tích cú pháp sắp tới!
