• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# coding=utf-8
2#
3# Copyright (c) 2025 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Manages source files."""
17
18from abc import ABC, abstractmethod
19from collections.abc import Iterable
20from dataclasses import dataclass
21from io import StringIO
22from pathlib import Path
23from typing import NamedTuple
24
25from typing_extensions import override
26
27
28@dataclass(frozen=True)
29class SourceBase(ABC):
30    """Base class reprensenting all kinds of source code."""
31
32    @property
33    @abstractmethod
34    def source_identifier(self) -> str:
35        ...
36
37    @property
38    @abstractmethod
39    def pkg_name(self) -> str:
40        ...
41
42    @abstractmethod
43    def read(self) -> str:
44        ...
45
46
47@dataclass(frozen=True)
48class SourceFile(SourceBase):
49    """Represents a file-based source code."""
50
51    path: Path
52
53    @property
54    @override
55    def source_identifier(self) -> str:
56        return str(self.path)
57
58    @property
59    @override
60    def pkg_name(self) -> str:
61        return self.path.stem
62
63    @override
64    def read(self) -> str:
65        with open(self.path, encoding="utf-8") as f:
66            return f.read()
67
68
69@dataclass(frozen=True)
70class SourceBuffer(SourceBase):
71    """Represents a string-based source code."""
72
73    name: str
74    buf: StringIO
75
76    @property
77    @override
78    def source_identifier(self) -> str:
79        return f"<source-buffer-{self.pkg_name}>"
80
81    @property
82    @override
83    def pkg_name(self) -> str:
84        return self.name
85
86    @override
87    def read(self) -> str:
88        return self.buf.getvalue()
89
90
91class SourceManager:
92    """Manages all input files throughout the compilation."""
93
94    _source_collection: set[SourceBase]
95
96    def __init__(self):
97        self._source_collection = set()
98
99    @property
100    def sources(self) -> Iterable[SourceBase]:
101        return self._source_collection
102
103    def add_source(self, sb: SourceBase):
104        self._source_collection.add(sb)
105
106
107class TextPosition(NamedTuple):
108    """Represents a position within a file (1-based)."""
109
110    row: int
111    col: int
112
113    def __str__(self) -> str:
114        return f"{self.row}:{self.col}"
115
116
117class TextSpan(NamedTuple):
118    """Represents a region within a file (1-based)."""
119
120    start: TextPosition
121    stop: TextPosition
122
123    def __or__(self, other: "TextSpan") -> "TextSpan":
124        return TextSpan(
125            start=min(self.start, other.start),
126            stop=max(self.stop, other.stop),
127        )
128
129
130@dataclass
131class SourceLocation:
132    """Represents a location (either a position or a region) within a file."""
133
134    file: SourceBase
135    """Required: The source file associated with the location."""
136
137    span: TextSpan | None = None
138    """Optional: The span of the location within the file."""
139
140    def __str__(self) -> str:
141        res = self.file.source_identifier
142        if self.span:
143            res = f"{res}:{self.span.start}"
144        return res
145
146    @classmethod
147    def with_path(cls, path: Path) -> "SourceLocation":
148        """Returns a file-only source location, without any position information."""
149        return cls(SourceFile(path))