-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdim_module_resolver.py
More file actions
143 lines (117 loc) · 4.42 KB
/
dim_module_resolver.py
File metadata and controls
143 lines (117 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# dim_module_resolver.py — Module Resolution for Dim
#
# Handles finding, parsing, and caching of imported modules.
# Supports relative and absolute module paths.
import os
from typing import Dict, List, Optional, Set
from dataclasses import dataclass, field
from dim_ast import Program, Statement, FunctionDef, StructDef, EnumDef, ImportStmt
from dim_lexer import Lexer
from dim_parser import Parser
from dim_semantic import SemanticAnalyzer
from dim_diagnostic import DiagnosticBag
@dataclass
class Module:
name: str
path: str
program: Program
exports: Dict[str, Statement] = field(default_factory=dict)
symbols: Dict[str, object] = field(default_factory=dict)
imported: bool = False
class ModuleResolver:
"""
Resolves import statements to loaded modules.
Maintains a cache of already-loaded modules to avoid duplicate parsing.
"""
def __init__(self, entry_file: str):
self.entry_file = os.path.abspath(entry_file)
self.entry_dir = os.path.dirname(self.entry_file)
self._cache: Dict[str, Module] = {}
self._loading: Set[str] = set()
self._stdlib_dir = self._find_stdlib_dir()
def _find_stdlib_dir(self) -> Optional[str]:
candidates = [
os.path.join(os.path.dirname(self.entry_file), "std"),
os.path.join(os.path.dirname(__file__), "std"),
os.path.join(os.getcwd(), "std"),
]
for candidate in candidates:
if candidate and os.path.isdir(candidate):
return os.path.normpath(candidate)
return None
def _get_module_path(self, path: List[str]) -> Optional[str]:
if path[0] == "std":
if self._stdlib_dir:
return os.path.join(self._stdlib_dir, *path[1:]) + ".dim"
return None
module_path = os.path.join(self.entry_dir, *path) + ".dim"
if os.path.isfile(module_path):
return module_path
module_path = os.path.join(self.entry_dir, *path, "__init__.dim")
if os.path.isfile(module_path):
return module_path
return None
def resolve(
self, import_stmt: ImportStmt, source: str, filename: str
) -> Optional[Module]:
path = import_stmt.path
module_name = ".".join(path)
if module_name in self._cache:
return self._cache[module_name]
if module_name in self._loading:
return None
file_path = self._get_module_path(path)
if not file_path:
return None
self._loading.add(module_name)
try:
with open(file_path, "r", encoding="utf-8") as f:
module_source = f.read()
except Exception:
self._loading.discard(module_name)
return None
lexer = Lexer(module_source, file_path)
tokens = lexer.tokenize()
if lexer.diag.has_errors:
self._loading.discard(module_name)
return None
parser = Parser(tokens, module_source, file_path)
program = parser.parse_program()
if parser.diag.has_errors:
self._loading.discard(module_name)
return None
sem = SemanticAnalyzer(module_source, file_path)
sem.analyze(program)
if sem.diag.has_errors:
self._loading.discard(module_name)
return None
exports = self._collect_exports(program)
module = Module(
name=module_name,
path=file_path,
program=program,
exports=exports,
imported=True,
)
self._cache[module_name] = module
self._loading.discard(module_name)
return module
def _collect_exports(self, program: Program) -> Dict[str, Statement]:
exports = {}
for stmt in program.statements:
if isinstance(stmt, FunctionDef):
exports[stmt.name] = stmt
elif isinstance(stmt, StructDef):
exports[stmt.name] = stmt
elif isinstance(stmt, EnumDef):
exports[stmt.name] = stmt
elif isinstance(stmt, ImportStmt) and stmt.alias:
exports[stmt.alias] = stmt
return exports
def resolve_program(
self, program: Program, source: str, filename: str
) -> Dict[str, Module]:
for stmt in program.statements:
if isinstance(stmt, ImportStmt):
self.resolve(stmt, source, filename)
return self._cache