|
22 | 22 | from __future__ import annotations |
23 | 23 | import inspect |
24 | 24 | from pathlib import Path |
25 | | -from typing import Any, Dict, List, Optional |
| 25 | +from typing import Any, Dict, List, Optional, Tuple |
26 | 26 | import gzip |
27 | 27 |
|
28 | 28 | from pydantic import BaseModel |
@@ -168,6 +168,29 @@ def build(self): |
168 | 168 | return cls |
169 | 169 |
|
170 | 170 |
|
| 171 | +def byte_offsets(source: str, start_line: int, start_col: int, |
| 172 | + end_line: int, end_col: int) -> Tuple[int, int]: |
| 173 | + """Convert (1-based line, 0-based col) ast positions to utf-8 byte offsets |
| 174 | + into `source`. `col` is a character offset within the line (ast semantics); |
| 175 | + we re-encode the line prefix to bytes so multibyte chars are handled.""" |
| 176 | + lines = source.splitlines(keepends=True) |
| 177 | + def offset(line: int, col: int) -> int: |
| 178 | + prefix_bytes = len("".join(lines[: line - 1]).encode("utf-8")) |
| 179 | + col_bytes = len(lines[line - 1][:col].encode("utf-8")) if line - 1 < len(lines) else 0 |
| 180 | + return prefix_bytes + col_bytes |
| 181 | + return offset(start_line, start_col), offset(end_line, end_col) |
| 182 | + |
| 183 | + |
| 184 | +@builder |
| 185 | +@msgpk |
| 186 | +class Span(BaseModel): |
| 187 | + """Where a node lives in source. `start`/`end` are [line, col] (1-based line, |
| 188 | + 0-based col, ast semantics); `bytes` are utf-8 offsets into module.source.""" |
| 189 | + start: Tuple[int, int] |
| 190 | + end: Tuple[int, int] |
| 191 | + bytes: Tuple[int, int] |
| 192 | + |
| 193 | + |
171 | 194 | @builder |
172 | 195 | @msgpk |
173 | 196 | class PyImport(BaseModel): |
|
0 commit comments