php-parser-py 0.1.1__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,277 @@
1
+ Metadata-Version: 2.3
2
+ Name: php-parser-py
3
+ Version: 1.0.0
4
+ Summary: Python wrapper for PHP-Parser using cpg2py
5
+ Author: samhsu-dev
6
+ Author-email: samhsu-dev <yxu166@jh.edu>
7
+ Requires-Dist: cpg2py>=1.1.0
8
+ Requires-Dist: static-php-py
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+
12
+ # php-parser-py
13
+
14
+ Python wrapper for PHP-Parser using cpg2py's graph framework.
15
+
16
+ ## Overview
17
+
18
+ `php-parser-py` provides a Python interface to [PHP-Parser](https://github.com/nikic/PHP-Parser), enabling Abstract Syntax Tree (AST) parsing and manipulation of PHP code. The library integrates with [cpg2py](https://github.com/jhu-seclab/cpg2py)'s graph framework for powerful graph-based code analysis.
19
+
20
+ ## Features
21
+
22
+ - **Native PHP-Parser Integration**: Delegates all parsing and code generation to PHP-Parser for maximum compatibility
23
+ - **Zero-Configuration PHP**: Bundled PHP binaries via static-php-py - no system PHP installation required
24
+ - **Graph-Based Analysis**: Uses cpg2py's graph framework for AST traversal and querying
25
+ - **Dynamic Node Types**: No hardcoded PHP node types - all types come from PHP-Parser's JSON output
26
+ - **Lossless Round-Trip**: Parse → Modify → Generate code with full fidelity
27
+ - **Cross-Platform Support**: Pre-built PHP 8.4 binaries for Linux (x86_64/aarch64), macOS (x86_64/arm64), and Windows (x64)
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install php-parser-py
33
+ ```
34
+
35
+ **Note**: Platform-specific wheels include pre-built PHP 8.4 binaries. No separate PHP installation required!
36
+
37
+ ## Quick Start
38
+
39
+ ### Parse PHP Code (Without Project Structure)
40
+
41
+ ```python
42
+ from php_parser_py import parse_code
43
+
44
+ # Parse PHP code into raw statement nodes
45
+ code = """<?php
46
+ function greet($name) {
47
+ echo "Hello, $name!";
48
+ }
49
+ """
50
+
51
+ nodes = parse_code(code)
52
+ # Returns list of Node instances (no project/file structure)
53
+
54
+ for node in nodes:
55
+ print(f"Node type: {node.label}")
56
+ ```
57
+
58
+ ### Parse PHP File (With Project Structure)
59
+
60
+ ```python
61
+ from php_parser_py import parse_file
62
+
63
+ # Parse a single file with project/file structure
64
+ ast = parse_file("example.php")
65
+
66
+ # Access project and file nodes
67
+ project = ast.project_node
68
+ files = ast.files()
69
+ print(f"Project has {len(files)} file(s)")
70
+
71
+ # Get file containing a specific node
72
+ stmt_node = ast.first_node(lambda n: n.label == "Stmt_Function")
73
+ file_node = ast.get_file(stmt_node.id)
74
+ print(f"Function is in file: {file_node.get_property('filePath')}")
75
+ ```
76
+
77
+ ### Parse Multiple Files (Project Structure)
78
+
79
+ ```python
80
+ from php_parser_py import parse_project
81
+
82
+ # Parse multiple files into a single AST
83
+ ast = parse_project(["file1.php", "file2.php", "file3.php"])
84
+
85
+ # Access all files
86
+ for file_node in ast.files():
87
+ print(f"File: {file_node.get_property('filePath')}")
88
+ # Get statements in this file
89
+ for stmt in ast.children(file_node):
90
+ print(f" Statement: {stmt.label}")
91
+
92
+ # Access project and file paths
93
+ ast = parse_project(["file1.php", "subdir/file2.php"])
94
+ project = ast.project_node
95
+ print(f"Project root: {project.get_property('path')}")
96
+
97
+ for file_node in ast.files():
98
+ print(f"File relative path: {file_node.get_property('path')}")
99
+ print(f"File absolute path: {file_node.get_property('filePath')}")
100
+ ```
101
+
102
+ ### Traverse AST
103
+
104
+ ```python
105
+ from php_parser_py import parse_file
106
+
107
+ ast = parse_file("example.php")
108
+
109
+ # Find all echo statements
110
+ for echo in ast.nodes(lambda n: n.label == "Stmt_Echo"):
111
+ print(f"Echo at line {echo.get_property('startLine')}")
112
+
113
+ # Get children of a node (via PARENT_OF edges)
114
+ for child in ast.succ(some_node):
115
+ print(f"Child type: {child.label}")
116
+
117
+ # Get all descendants
118
+ for descendant in ast.descendants(some_node):
119
+ print(f"Descendant: {descendant.label}")
120
+
121
+ # Get file containing a node
122
+ stmt_node = ast.first_node(lambda n: n.label == "Stmt_Function")
123
+ file_node = ast.get_file(stmt_node.id)
124
+ if file_node:
125
+ print(f"Function is in: {file_node.get_property('filePath')}")
126
+
127
+ # Access project structure
128
+ project = ast.project_node
129
+ files = ast.files()
130
+ for file_node in files:
131
+ print(f"File: {file_node.get_property('filePath')}")
132
+ ```
133
+
134
+ ### Generate PHP Code
135
+
136
+ ```python
137
+ from php_parser_py import parse_file, parse_project, PrettyPrinter
138
+
139
+ # Parse single file
140
+ ast = parse_file("example.php")
141
+
142
+ # Generate code (returns dict mapping file paths to code)
143
+ printer = PrettyPrinter()
144
+ generated = printer.print(ast)
145
+ for file_path, code in generated.items():
146
+ print(f"File: {file_path}")
147
+ print(code)
148
+
149
+ # Parse multiple files
150
+ ast = parse_project(["file1.php", "file2.php"])
151
+
152
+ # Generate code for all files
153
+ generated = printer.print(ast)
154
+ # Returns: {"file1.php": "...", "file2.php": "..."}
155
+ ```
156
+
157
+ ## Advanced Usage
158
+
159
+ ### Custom PHP Binary
160
+
161
+ If you need to use a specific PHP version or custom binary:
162
+
163
+ ```python
164
+ from php_parser_py import Parser
165
+ from pathlib import Path
166
+
167
+ # Use local PHP binary
168
+ parser = Parser(php_binary_path=Path("/usr/local/bin/php"))
169
+ ast = parser.parse("<?php echo 'hello';")
170
+
171
+ # Or download from remote URL
172
+ parser = Parser(php_binary_url="https://example.com/php-8.4.zip")
173
+ ast = parser.parse("<?php echo 'hello';")
174
+ ```
175
+
176
+ ## API Reference
177
+
178
+ ### Main Functions
179
+
180
+ - **`parse_code(code: str) -> list[Node]`**: Parse PHP code string into raw statement nodes (no project/file structure)
181
+ - **`parse(code: str) -> AST`**: Parse PHP code string and return AST with project/file structure (backward compatibility)
182
+ - **`parse_file(path: str) -> AST`**: Parse a single PHP file and return AST with project/file structure
183
+ - **`parse_project(paths: list[str]) -> AST`**: Parse multiple PHP files into a single AST with project structure
184
+
185
+ ### Classes
186
+
187
+ - **`AST`**: Represents the complete AST as a graph
188
+ - `node(id)`: Get node by ID
189
+ - `nodes(predicate)`: Iterate nodes matching predicate
190
+ - `first_node(predicate)`: Get first matching node
191
+ - `succ(node, predicate)`: Get successor nodes (children via PARENT_OF edges)
192
+ - `prev(node, predicate)`: Get predecessor nodes (parents via PARENT_OF edges)
193
+ - `descendants(node, predicate)`: Get all descendants via BFS
194
+ - `ancestors(node, predicate)`: Get all ancestors via BFS
195
+ - `root_node`: Property returning the root/project node
196
+ - `project_node`: Property returning the project node (alias for root_node)
197
+ - `files()`: Get all file nodes in the project
198
+ - `get_file(node_id)`: Get the file node containing the given node
199
+ - `to_json()`: Reconstruct PHP-Parser JSON
200
+
201
+ - **`Node`**: Wraps a single AST node
202
+ - `id`: Node identifier
203
+ - `label`: Node type (e.g., "Stmt_Function")
204
+ - `node_type`: Alias for label (PHP-Parser node type)
205
+ - `properties`: All node properties
206
+ - `get_property(*names)`: Get property value
207
+
208
+ - **`Parser`**: Parses PHP code
209
+ - `__init__(php_binary_path=None, php_binary_url=None)`: Initialize parser
210
+ - `php_binary_path`: Optional path to local PHP binary
211
+ - `php_binary_url`: Optional URL to download PHP binary from
212
+ - `parse_code(code)`: Parse code string into list of nodes
213
+ - `parse(code)`: Parse code string into AST (backward compatibility)
214
+ - `parse_file(path)`: Parse file into AST with project/file structure
215
+ - `parse_project(paths)`: Parse multiple files into AST with project structure
216
+
217
+ - **`PrettyPrinter`**: Generates PHP code
218
+ - `__init__(php_binary_path=None, php_binary_url=None)`: Initialize printer
219
+ - `php_binary_path`: Optional path to local PHP binary
220
+ - `php_binary_url`: Optional URL to download PHP binary from
221
+ - `print(ast) -> dict[str, str]`: Generate code from AST, returns dictionary mapping file paths to generated code
222
+ - Returns: `{file_path: code_string, ...}` for each file in the AST
223
+ - If AST has no file structure, returns `{"": code_string}`
224
+
225
+ ### Exceptions
226
+
227
+ - **`ParseError`**: Raised when PHP-Parser reports syntax error
228
+ - **`RunnerError`**: Raised when PHP execution fails
229
+
230
+ ## AST Structure
231
+
232
+ The library supports three parsing modes:
233
+
234
+ 1. **Code Parsing** (`parse_code`): Returns flat list of statement nodes
235
+ - Node IDs: `node_1`, `node_2`, ...
236
+ - No project/file structure
237
+
238
+ 2. **File Parsing** (`parse_file`): Creates project → file → statements hierarchy
239
+ - Project node: `"project"` (fixed ID)
240
+ - File node: 8-character hex hash (e.g., `"a1b2c3d4"`)
241
+ - Statement nodes: `{hex}_1`, `{hex}_2`, ...
242
+
243
+ 3. **Project Parsing** (`parse_project`): Creates project → multiple files → statements hierarchy
244
+ - Single project node: `"project"` (fixed ID)
245
+ - Multiple file nodes: Each with unique hex hash
246
+ - Statement nodes: Prefixed with their file's hash
247
+
248
+ ## Architecture
249
+
250
+ The library follows a delegation-based architecture:
251
+
252
+ 1. **PHP-Parser** handles all parsing and code generation
253
+ 2. **cpg2py Storage** stores the AST as a graph
254
+ 3. **Python classes** provide convenient access to the graph
255
+
256
+ This design ensures:
257
+ - No need to maintain PHP node type definitions in Python
258
+ - Automatic compatibility with PHP-Parser updates
259
+ - Full access to PHP-Parser's features
260
+ - Support for multi-file project analysis with merge capabilities
261
+
262
+ ## Requirements
263
+
264
+ - Python >= 3.11
265
+ - cpg2py
266
+ - static-php-py (bundled PHP binaries)
267
+
268
+ ## License
269
+
270
+ MIT License - see LICENSE file for details.
271
+
272
+ ## Documentation
273
+
274
+ For detailed design documentation, see:
275
+ - [Design Document](docs/design.md)
276
+ - [Idea Document](docs/idea.md)
277
+ - [API Demo](docs/api_demo.ipynb)
@@ -0,0 +1,266 @@
1
+ # php-parser-py
2
+
3
+ Python wrapper for PHP-Parser using cpg2py's graph framework.
4
+
5
+ ## Overview
6
+
7
+ `php-parser-py` provides a Python interface to [PHP-Parser](https://github.com/nikic/PHP-Parser), enabling Abstract Syntax Tree (AST) parsing and manipulation of PHP code. The library integrates with [cpg2py](https://github.com/jhu-seclab/cpg2py)'s graph framework for powerful graph-based code analysis.
8
+
9
+ ## Features
10
+
11
+ - **Native PHP-Parser Integration**: Delegates all parsing and code generation to PHP-Parser for maximum compatibility
12
+ - **Zero-Configuration PHP**: Bundled PHP binaries via static-php-py - no system PHP installation required
13
+ - **Graph-Based Analysis**: Uses cpg2py's graph framework for AST traversal and querying
14
+ - **Dynamic Node Types**: No hardcoded PHP node types - all types come from PHP-Parser's JSON output
15
+ - **Lossless Round-Trip**: Parse → Modify → Generate code with full fidelity
16
+ - **Cross-Platform Support**: Pre-built PHP 8.4 binaries for Linux (x86_64/aarch64), macOS (x86_64/arm64), and Windows (x64)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install php-parser-py
22
+ ```
23
+
24
+ **Note**: Platform-specific wheels include pre-built PHP 8.4 binaries. No separate PHP installation required!
25
+
26
+ ## Quick Start
27
+
28
+ ### Parse PHP Code (Without Project Structure)
29
+
30
+ ```python
31
+ from php_parser_py import parse_code
32
+
33
+ # Parse PHP code into raw statement nodes
34
+ code = """<?php
35
+ function greet($name) {
36
+ echo "Hello, $name!";
37
+ }
38
+ """
39
+
40
+ nodes = parse_code(code)
41
+ # Returns list of Node instances (no project/file structure)
42
+
43
+ for node in nodes:
44
+ print(f"Node type: {node.label}")
45
+ ```
46
+
47
+ ### Parse PHP File (With Project Structure)
48
+
49
+ ```python
50
+ from php_parser_py import parse_file
51
+
52
+ # Parse a single file with project/file structure
53
+ ast = parse_file("example.php")
54
+
55
+ # Access project and file nodes
56
+ project = ast.project_node
57
+ files = ast.files()
58
+ print(f"Project has {len(files)} file(s)")
59
+
60
+ # Get file containing a specific node
61
+ stmt_node = ast.first_node(lambda n: n.label == "Stmt_Function")
62
+ file_node = ast.get_file(stmt_node.id)
63
+ print(f"Function is in file: {file_node.get_property('filePath')}")
64
+ ```
65
+
66
+ ### Parse Multiple Files (Project Structure)
67
+
68
+ ```python
69
+ from php_parser_py import parse_project
70
+
71
+ # Parse multiple files into a single AST
72
+ ast = parse_project(["file1.php", "file2.php", "file3.php"])
73
+
74
+ # Access all files
75
+ for file_node in ast.files():
76
+ print(f"File: {file_node.get_property('filePath')}")
77
+ # Get statements in this file
78
+ for stmt in ast.children(file_node):
79
+ print(f" Statement: {stmt.label}")
80
+
81
+ # Access project and file paths
82
+ ast = parse_project(["file1.php", "subdir/file2.php"])
83
+ project = ast.project_node
84
+ print(f"Project root: {project.get_property('path')}")
85
+
86
+ for file_node in ast.files():
87
+ print(f"File relative path: {file_node.get_property('path')}")
88
+ print(f"File absolute path: {file_node.get_property('filePath')}")
89
+ ```
90
+
91
+ ### Traverse AST
92
+
93
+ ```python
94
+ from php_parser_py import parse_file
95
+
96
+ ast = parse_file("example.php")
97
+
98
+ # Find all echo statements
99
+ for echo in ast.nodes(lambda n: n.label == "Stmt_Echo"):
100
+ print(f"Echo at line {echo.get_property('startLine')}")
101
+
102
+ # Get children of a node (via PARENT_OF edges)
103
+ for child in ast.succ(some_node):
104
+ print(f"Child type: {child.label}")
105
+
106
+ # Get all descendants
107
+ for descendant in ast.descendants(some_node):
108
+ print(f"Descendant: {descendant.label}")
109
+
110
+ # Get file containing a node
111
+ stmt_node = ast.first_node(lambda n: n.label == "Stmt_Function")
112
+ file_node = ast.get_file(stmt_node.id)
113
+ if file_node:
114
+ print(f"Function is in: {file_node.get_property('filePath')}")
115
+
116
+ # Access project structure
117
+ project = ast.project_node
118
+ files = ast.files()
119
+ for file_node in files:
120
+ print(f"File: {file_node.get_property('filePath')}")
121
+ ```
122
+
123
+ ### Generate PHP Code
124
+
125
+ ```python
126
+ from php_parser_py import parse_file, parse_project, PrettyPrinter
127
+
128
+ # Parse single file
129
+ ast = parse_file("example.php")
130
+
131
+ # Generate code (returns dict mapping file paths to code)
132
+ printer = PrettyPrinter()
133
+ generated = printer.print(ast)
134
+ for file_path, code in generated.items():
135
+ print(f"File: {file_path}")
136
+ print(code)
137
+
138
+ # Parse multiple files
139
+ ast = parse_project(["file1.php", "file2.php"])
140
+
141
+ # Generate code for all files
142
+ generated = printer.print(ast)
143
+ # Returns: {"file1.php": "...", "file2.php": "..."}
144
+ ```
145
+
146
+ ## Advanced Usage
147
+
148
+ ### Custom PHP Binary
149
+
150
+ If you need to use a specific PHP version or custom binary:
151
+
152
+ ```python
153
+ from php_parser_py import Parser
154
+ from pathlib import Path
155
+
156
+ # Use local PHP binary
157
+ parser = Parser(php_binary_path=Path("/usr/local/bin/php"))
158
+ ast = parser.parse("<?php echo 'hello';")
159
+
160
+ # Or download from remote URL
161
+ parser = Parser(php_binary_url="https://example.com/php-8.4.zip")
162
+ ast = parser.parse("<?php echo 'hello';")
163
+ ```
164
+
165
+ ## API Reference
166
+
167
+ ### Main Functions
168
+
169
+ - **`parse_code(code: str) -> list[Node]`**: Parse PHP code string into raw statement nodes (no project/file structure)
170
+ - **`parse(code: str) -> AST`**: Parse PHP code string and return AST with project/file structure (backward compatibility)
171
+ - **`parse_file(path: str) -> AST`**: Parse a single PHP file and return AST with project/file structure
172
+ - **`parse_project(paths: list[str]) -> AST`**: Parse multiple PHP files into a single AST with project structure
173
+
174
+ ### Classes
175
+
176
+ - **`AST`**: Represents the complete AST as a graph
177
+ - `node(id)`: Get node by ID
178
+ - `nodes(predicate)`: Iterate nodes matching predicate
179
+ - `first_node(predicate)`: Get first matching node
180
+ - `succ(node, predicate)`: Get successor nodes (children via PARENT_OF edges)
181
+ - `prev(node, predicate)`: Get predecessor nodes (parents via PARENT_OF edges)
182
+ - `descendants(node, predicate)`: Get all descendants via BFS
183
+ - `ancestors(node, predicate)`: Get all ancestors via BFS
184
+ - `root_node`: Property returning the root/project node
185
+ - `project_node`: Property returning the project node (alias for root_node)
186
+ - `files()`: Get all file nodes in the project
187
+ - `get_file(node_id)`: Get the file node containing the given node
188
+ - `to_json()`: Reconstruct PHP-Parser JSON
189
+
190
+ - **`Node`**: Wraps a single AST node
191
+ - `id`: Node identifier
192
+ - `label`: Node type (e.g., "Stmt_Function")
193
+ - `node_type`: Alias for label (PHP-Parser node type)
194
+ - `properties`: All node properties
195
+ - `get_property(*names)`: Get property value
196
+
197
+ - **`Parser`**: Parses PHP code
198
+ - `__init__(php_binary_path=None, php_binary_url=None)`: Initialize parser
199
+ - `php_binary_path`: Optional path to local PHP binary
200
+ - `php_binary_url`: Optional URL to download PHP binary from
201
+ - `parse_code(code)`: Parse code string into list of nodes
202
+ - `parse(code)`: Parse code string into AST (backward compatibility)
203
+ - `parse_file(path)`: Parse file into AST with project/file structure
204
+ - `parse_project(paths)`: Parse multiple files into AST with project structure
205
+
206
+ - **`PrettyPrinter`**: Generates PHP code
207
+ - `__init__(php_binary_path=None, php_binary_url=None)`: Initialize printer
208
+ - `php_binary_path`: Optional path to local PHP binary
209
+ - `php_binary_url`: Optional URL to download PHP binary from
210
+ - `print(ast) -> dict[str, str]`: Generate code from AST, returns dictionary mapping file paths to generated code
211
+ - Returns: `{file_path: code_string, ...}` for each file in the AST
212
+ - If AST has no file structure, returns `{"": code_string}`
213
+
214
+ ### Exceptions
215
+
216
+ - **`ParseError`**: Raised when PHP-Parser reports syntax error
217
+ - **`RunnerError`**: Raised when PHP execution fails
218
+
219
+ ## AST Structure
220
+
221
+ The library supports three parsing modes:
222
+
223
+ 1. **Code Parsing** (`parse_code`): Returns flat list of statement nodes
224
+ - Node IDs: `node_1`, `node_2`, ...
225
+ - No project/file structure
226
+
227
+ 2. **File Parsing** (`parse_file`): Creates project → file → statements hierarchy
228
+ - Project node: `"project"` (fixed ID)
229
+ - File node: 8-character hex hash (e.g., `"a1b2c3d4"`)
230
+ - Statement nodes: `{hex}_1`, `{hex}_2`, ...
231
+
232
+ 3. **Project Parsing** (`parse_project`): Creates project → multiple files → statements hierarchy
233
+ - Single project node: `"project"` (fixed ID)
234
+ - Multiple file nodes: Each with unique hex hash
235
+ - Statement nodes: Prefixed with their file's hash
236
+
237
+ ## Architecture
238
+
239
+ The library follows a delegation-based architecture:
240
+
241
+ 1. **PHP-Parser** handles all parsing and code generation
242
+ 2. **cpg2py Storage** stores the AST as a graph
243
+ 3. **Python classes** provide convenient access to the graph
244
+
245
+ This design ensures:
246
+ - No need to maintain PHP node type definitions in Python
247
+ - Automatic compatibility with PHP-Parser updates
248
+ - Full access to PHP-Parser's features
249
+ - Support for multi-file project analysis with merge capabilities
250
+
251
+ ## Requirements
252
+
253
+ - Python >= 3.11
254
+ - cpg2py
255
+ - static-php-py (bundled PHP binaries)
256
+
257
+ ## License
258
+
259
+ MIT License - see LICENSE file for details.
260
+
261
+ ## Documentation
262
+
263
+ For detailed design documentation, see:
264
+ - [Design Document](docs/design.md)
265
+ - [Idea Document](docs/idea.md)
266
+ - [API Demo](docs/api_demo.ipynb)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "php-parser-py"
3
- version = "0.1.1"
3
+ version = "1.0.0"
4
4
  description = "Python wrapper for PHP-Parser using cpg2py"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,138 @@
1
+ """
2
+ php-parser-py: Python wrapper for PHP-Parser using cpg2py.
3
+
4
+ This package provides a Python interface to PHP-Parser, enabling AST parsing
5
+ and manipulation of PHP code.
6
+ """
7
+
8
+ from php_parser_py._parser import Parser as _Parser
9
+ from php_parser_py._printer import PrettyPrinter as _PrettyPrinter
10
+ from php_parser_py._resources import ensure_php_parser_extracted
11
+
12
+ # Ensure PHP-Parser is extracted on first import
13
+ ensure_php_parser_extracted()
14
+
15
+ # Import and expose public classes directly
16
+ from php_parser_py._ast import AST
17
+ from php_parser_py._node import Node
18
+ from php_parser_py._edge import Edge
19
+ from php_parser_py.exceptions import ParseError, RunnerError
20
+
21
+
22
+ class Parser(_Parser):
23
+ """Parses PHP source code using PHP-Parser.
24
+
25
+ Invokes PHP-Parser to parse PHP code and converts the resulting JSON
26
+ to cpg2py Storage format for graph-based analysis.
27
+ """
28
+
29
+
30
+ class PrettyPrinter(_PrettyPrinter):
31
+ """Converts AST to PHP code using PHP-Parser's PrettyPrinter.
32
+
33
+ Reconstructs JSON from AST graph and invokes PHP-Parser to generate
34
+ formatted PHP source code.
35
+ """
36
+
37
+
38
+ def parse_code(code: str) -> list[Node]:
39
+ """Parse PHP code string into a list of top-level statement nodes.
40
+
41
+ Does not create project or file nodes. Returns raw statement nodes.
42
+
43
+ Args:
44
+ code: PHP source code string.
45
+
46
+ Returns:
47
+ List of Node instances representing top-level statements.
48
+
49
+ Raises:
50
+ ParseError: If PHP-Parser reports syntax error.
51
+ """
52
+ parser = Parser()
53
+ return parser.parse_code(code)
54
+
55
+
56
+ def parse(code: str) -> AST:
57
+ """Parse PHP code string into an AST (backward compatibility).
58
+
59
+ This is a convenience function that creates a temporary project/file structure.
60
+ For code-only parsing without project structure, use parse_code() instead.
61
+
62
+ Args:
63
+ code: PHP source code string.
64
+
65
+ Returns:
66
+ AST instance with project -> file -> statements hierarchy.
67
+
68
+ Raises:
69
+ ParseError: If PHP-Parser reports syntax error.
70
+ """
71
+ # Create a temporary file-like structure for backward compatibility
72
+ import tempfile
73
+ from pathlib import Path
74
+
75
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.php', delete=False) as f:
76
+ f.write(code)
77
+ temp_path = f.name
78
+
79
+ try:
80
+ return parse_file(temp_path)
81
+ finally:
82
+ Path(temp_path).unlink()
83
+
84
+
85
+ def parse_file(path: str) -> AST:
86
+ """Read and parse a single PHP file into an AST.
87
+
88
+ Creates a project node and a file node. The file node contains
89
+ all statements from the file.
90
+
91
+ Args:
92
+ path: File path string.
93
+
94
+ Returns:
95
+ AST instance with project -> file -> statements hierarchy.
96
+
97
+ Raises:
98
+ ParseError: If PHP-Parser reports syntax error.
99
+ FileNotFoundError: If file does not exist.
100
+ """
101
+ parser = Parser()
102
+ return parser.parse_file(path)
103
+
104
+
105
+ def parse_project(paths: list[str], project_path: str | None = None) -> AST:
106
+ """Parse multiple PHP files into an AST.
107
+
108
+ Creates a project node and multiple file nodes. Each file node
109
+ contains statements from its corresponding file.
110
+
111
+ Args:
112
+ paths: List of file path strings.
113
+ project_path: Optional project root path. If not provided, computed as common parent.
114
+
115
+ Returns:
116
+ AST instance with project -> files -> statements hierarchy.
117
+
118
+ Raises:
119
+ ParseError: If any file has syntax errors.
120
+ FileNotFoundError: If any file does not exist.
121
+ """
122
+ parser = Parser()
123
+ return parser.parse_project(paths, project_path=project_path)
124
+
125
+
126
+ __version__ = "0.1.0"
127
+ __all__ = [
128
+ "AST",
129
+ "Node",
130
+ "Edge",
131
+ "Parser",
132
+ "PrettyPrinter",
133
+ "ParseError",
134
+ "RunnerError",
135
+ "parse_code",
136
+ "parse_file",
137
+ "parse_project",
138
+ ]