token-savior-recall 2.0.0__py3-none-any.whl
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.
- token_savior/__init__.py +3 -0
- token_savior/annotator.py +113 -0
- token_savior/brace_matcher.py +367 -0
- token_savior/breaking_changes.py +542 -0
- token_savior/c_annotator.py +697 -0
- token_savior/cache_ops.py +256 -0
- token_savior/checkpoint_ops.py +197 -0
- token_savior/community.py +145 -0
- token_savior/compact_ops.py +101 -0
- token_savior/complexity.py +136 -0
- token_savior/conf_annotator.py +94 -0
- token_savior/config_analyzer.py +971 -0
- token_savior/context_packer.py +120 -0
- token_savior/cross_project.py +132 -0
- token_savior/csharp_annotator.py +685 -0
- token_savior/dashboard.py +981 -0
- token_savior/dead_code.py +195 -0
- token_savior/docker_analyzer.py +296 -0
- token_savior/dockerfile_annotator.py +102 -0
- token_savior/edit_ops.py +168 -0
- token_savior/edit_verifier.py +161 -0
- token_savior/entry_points.py +90 -0
- token_savior/env_annotator.py +56 -0
- token_savior/generic_annotator.py +21 -0
- token_savior/git_ops.py +90 -0
- token_savior/git_tracker.py +248 -0
- token_savior/go_annotator.py +511 -0
- token_savior/graph_ranker.py +109 -0
- token_savior/hcl_annotator.py +143 -0
- token_savior/impacted_tests.py +261 -0
- token_savior/ini_annotator.py +148 -0
- token_savior/json_annotator.py +161 -0
- token_savior/markov_prefetcher.py +105 -0
- token_savior/memory_db.py +2345 -0
- token_savior/memory_schema.sql +191 -0
- token_savior/models.py +249 -0
- token_savior/program_slicer.py +160 -0
- token_savior/project_actions.py +274 -0
- token_savior/project_indexer.py +1015 -0
- token_savior/python_annotator.py +298 -0
- token_savior/query_api.py +1460 -0
- token_savior/rust_annotator.py +738 -0
- token_savior/semantic_hasher.py +92 -0
- token_savior/server.py +2259 -0
- token_savior/slot_manager.py +373 -0
- token_savior/symbol_hash.py +124 -0
- token_savior/text_annotator.py +118 -0
- token_savior/toml_annotator.py +105 -0
- token_savior/tool_schemas.py +1339 -0
- token_savior/typescript_annotator.py +568 -0
- token_savior/workflow_ops.py +174 -0
- token_savior/xml_annotator.py +108 -0
- token_savior/yaml_annotator.py +143 -0
- token_savior_recall-2.0.0.dist-info/METADATA +470 -0
- token_savior_recall-2.0.0.dist-info/RECORD +58 -0
- token_savior_recall-2.0.0.dist-info/WHEEL +4 -0
- token_savior_recall-2.0.0.dist-info/entry_points.txt +4 -0
- token_savior_recall-2.0.0.dist-info/licenses/LICENSE +21 -0
token_savior/__init__.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Dispatch layer that selects the appropriate annotator by file type."""
|
|
2
|
+
|
|
3
|
+
from token_savior.c_annotator import annotate_c
|
|
4
|
+
from token_savior.conf_annotator import annotate_conf
|
|
5
|
+
from token_savior.dockerfile_annotator import annotate_dockerfile
|
|
6
|
+
from token_savior.csharp_annotator import annotate_csharp
|
|
7
|
+
from token_savior.env_annotator import annotate_env
|
|
8
|
+
from token_savior.generic_annotator import annotate_generic
|
|
9
|
+
from token_savior.go_annotator import annotate_go
|
|
10
|
+
from token_savior.hcl_annotator import annotate_hcl
|
|
11
|
+
from token_savior.ini_annotator import annotate_ini
|
|
12
|
+
from token_savior.json_annotator import annotate_json
|
|
13
|
+
from token_savior.models import AnnotatorProtocol, StructuralMetadata
|
|
14
|
+
from token_savior.python_annotator import annotate_python
|
|
15
|
+
from token_savior.rust_annotator import annotate_rust
|
|
16
|
+
from token_savior.text_annotator import annotate_text
|
|
17
|
+
from token_savior.toml_annotator import annotate_toml
|
|
18
|
+
from token_savior.typescript_annotator import annotate_typescript
|
|
19
|
+
from token_savior.xml_annotator import annotate_xml
|
|
20
|
+
from token_savior.yaml_annotator import annotate_yaml
|
|
21
|
+
|
|
22
|
+
_EXTENSION_MAP: dict[str, str] = {
|
|
23
|
+
".c": "c",
|
|
24
|
+
".h": "c",
|
|
25
|
+
".glsl": "c",
|
|
26
|
+
".vert": "c",
|
|
27
|
+
".frag": "c",
|
|
28
|
+
".comp": "c",
|
|
29
|
+
".py": "python",
|
|
30
|
+
".pyw": "python",
|
|
31
|
+
".md": "text",
|
|
32
|
+
".txt": "text",
|
|
33
|
+
".rst": "text",
|
|
34
|
+
".ts": "typescript",
|
|
35
|
+
".tsx": "typescript",
|
|
36
|
+
".js": "javascript",
|
|
37
|
+
".jsx": "javascript",
|
|
38
|
+
".go": "go",
|
|
39
|
+
".rs": "rust",
|
|
40
|
+
".cs": "csharp",
|
|
41
|
+
".json": "json",
|
|
42
|
+
".yaml": "yaml",
|
|
43
|
+
".yml": "yaml",
|
|
44
|
+
".toml": "toml",
|
|
45
|
+
".ini": "ini",
|
|
46
|
+
".cfg": "ini",
|
|
47
|
+
".properties": "ini",
|
|
48
|
+
".env": "env",
|
|
49
|
+
".xml": "xml",
|
|
50
|
+
".plist": "xml",
|
|
51
|
+
".svg": "xml",
|
|
52
|
+
".xhtml": "xml",
|
|
53
|
+
".hcl": "hcl",
|
|
54
|
+
".tf": "hcl",
|
|
55
|
+
".conf": "conf",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_ANNOTATOR_MAP: dict[str, AnnotatorProtocol] = {
|
|
60
|
+
"c": annotate_c,
|
|
61
|
+
"python": annotate_python,
|
|
62
|
+
"text": annotate_text,
|
|
63
|
+
"typescript": annotate_typescript,
|
|
64
|
+
"javascript": annotate_typescript,
|
|
65
|
+
"go": annotate_go,
|
|
66
|
+
"rust": annotate_rust,
|
|
67
|
+
"csharp": annotate_csharp,
|
|
68
|
+
"json": annotate_json,
|
|
69
|
+
"yaml": annotate_yaml,
|
|
70
|
+
"ini": annotate_ini,
|
|
71
|
+
"xml": annotate_xml,
|
|
72
|
+
"hcl": annotate_hcl,
|
|
73
|
+
"toml": annotate_toml,
|
|
74
|
+
"env": annotate_env,
|
|
75
|
+
"conf": annotate_conf,
|
|
76
|
+
"dockerfile": annotate_dockerfile,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def annotate(
|
|
81
|
+
text: str,
|
|
82
|
+
source_name: str = "<source>",
|
|
83
|
+
file_type: str | None = None,
|
|
84
|
+
) -> StructuralMetadata:
|
|
85
|
+
"""Annotate text with structural metadata.
|
|
86
|
+
|
|
87
|
+
Dispatch rules:
|
|
88
|
+
- file_type overrides extension-based detection
|
|
89
|
+
- Extension -> _EXTENSION_MAP -> language name -> _ANNOTATOR_MAP -> annotator
|
|
90
|
+
- Dockerfile detected by filename pattern
|
|
91
|
+
- Otherwise -> generic annotator (line-only)
|
|
92
|
+
"""
|
|
93
|
+
if file_type is None:
|
|
94
|
+
import os as _os
|
|
95
|
+
|
|
96
|
+
_basename = _os.path.basename(source_name)
|
|
97
|
+
if (
|
|
98
|
+
_basename == "Dockerfile"
|
|
99
|
+
or _basename.lower() == "dockerfile"
|
|
100
|
+
or _basename.startswith("Dockerfile.")
|
|
101
|
+
or _basename.lower().endswith(".dockerfile")
|
|
102
|
+
):
|
|
103
|
+
file_type = "dockerfile"
|
|
104
|
+
else:
|
|
105
|
+
dot_idx = source_name.rfind(".")
|
|
106
|
+
if dot_idx >= 0:
|
|
107
|
+
ext = source_name[dot_idx:].lower()
|
|
108
|
+
file_type = _EXTENSION_MAP.get(ext)
|
|
109
|
+
|
|
110
|
+
annotator = _ANNOTATOR_MAP.get(file_type, annotate_generic)
|
|
111
|
+
if not callable(annotator):
|
|
112
|
+
raise TypeError(f"Annotator for '{file_type}' is not callable: {annotator!r}")
|
|
113
|
+
return annotator(text, source_name)
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""Shared brace-matching helpers for C-family annotators.
|
|
2
|
+
|
|
3
|
+
Each function finds the 0-based line where the outermost ``{`` closes,
|
|
4
|
+
correctly skipping language-specific strings, char literals, and comments.
|
|
5
|
+
The implementations are intentionally separate because the lexical rules
|
|
6
|
+
differ significantly between languages (nested comments in Rust, verbatim
|
|
7
|
+
strings in C#, backtick raw strings in Go, etc.).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_brace_end_c(lines: list[str], start_line_0: int) -> int:
|
|
14
|
+
"""Find the 0-based line where the outermost brace closes,
|
|
15
|
+
skipping strings, char literals, and comments."""
|
|
16
|
+
depth = 0
|
|
17
|
+
found_open = False
|
|
18
|
+
in_block_comment = False
|
|
19
|
+
for idx in range(start_line_0, len(lines)):
|
|
20
|
+
line = lines[idx]
|
|
21
|
+
i = 0
|
|
22
|
+
while i < len(line):
|
|
23
|
+
ch = line[i]
|
|
24
|
+
# Block comment handling (C does NOT nest /* */)
|
|
25
|
+
if in_block_comment:
|
|
26
|
+
if ch == "*" and i + 1 < len(line) and line[i + 1] == "/":
|
|
27
|
+
in_block_comment = False
|
|
28
|
+
i += 2
|
|
29
|
+
continue
|
|
30
|
+
i += 1
|
|
31
|
+
continue
|
|
32
|
+
# Line comment
|
|
33
|
+
if ch == "/" and i + 1 < len(line):
|
|
34
|
+
if line[i + 1] == "/":
|
|
35
|
+
break # rest is line comment
|
|
36
|
+
if line[i + 1] == "*":
|
|
37
|
+
in_block_comment = True
|
|
38
|
+
i += 2
|
|
39
|
+
continue
|
|
40
|
+
# String literal
|
|
41
|
+
if ch == '"':
|
|
42
|
+
i += 1
|
|
43
|
+
while i < len(line):
|
|
44
|
+
if line[i] == "\\":
|
|
45
|
+
i += 2
|
|
46
|
+
continue
|
|
47
|
+
if line[i] == '"':
|
|
48
|
+
i += 1
|
|
49
|
+
break
|
|
50
|
+
i += 1
|
|
51
|
+
continue
|
|
52
|
+
# Char literal
|
|
53
|
+
if ch == "'":
|
|
54
|
+
i += 1
|
|
55
|
+
if i < len(line) and line[i] == "\\":
|
|
56
|
+
i += 2 # skip escaped char
|
|
57
|
+
elif i < len(line):
|
|
58
|
+
i += 1 # skip char
|
|
59
|
+
if i < len(line) and line[i] == "'":
|
|
60
|
+
i += 1
|
|
61
|
+
continue
|
|
62
|
+
if ch == "{":
|
|
63
|
+
depth += 1
|
|
64
|
+
found_open = True
|
|
65
|
+
elif ch == "}":
|
|
66
|
+
depth -= 1
|
|
67
|
+
if found_open and depth == 0:
|
|
68
|
+
return idx
|
|
69
|
+
i += 1
|
|
70
|
+
return len(lines) - 1
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def find_brace_end_csharp(lines: list[str], start_line_0: int) -> int:
|
|
74
|
+
"""Find the 0-based line where the outermost brace closes,
|
|
75
|
+
skipping strings, verbatim strings, interpolated strings, char literals, and comments."""
|
|
76
|
+
depth = 0
|
|
77
|
+
found_open = False
|
|
78
|
+
in_block_comment = False
|
|
79
|
+
for idx in range(start_line_0, len(lines)):
|
|
80
|
+
line = lines[idx]
|
|
81
|
+
i = 0
|
|
82
|
+
while i < len(line):
|
|
83
|
+
ch = line[i]
|
|
84
|
+
# Block comment handling
|
|
85
|
+
if in_block_comment:
|
|
86
|
+
if ch == "*" and i + 1 < len(line) and line[i + 1] == "/":
|
|
87
|
+
in_block_comment = False
|
|
88
|
+
i += 2
|
|
89
|
+
continue
|
|
90
|
+
i += 1
|
|
91
|
+
continue
|
|
92
|
+
# Line comment
|
|
93
|
+
if ch == "/" and i + 1 < len(line):
|
|
94
|
+
if line[i + 1] == "/":
|
|
95
|
+
break # rest is line comment
|
|
96
|
+
if line[i + 1] == "*":
|
|
97
|
+
in_block_comment = True
|
|
98
|
+
i += 2
|
|
99
|
+
continue
|
|
100
|
+
# Verbatim/interpolated strings: $@"...", @$"...", @"...", $"..."
|
|
101
|
+
if ch in ("@", "$") and i + 1 < len(line):
|
|
102
|
+
# Check for $@" or @$" (interpolated verbatim)
|
|
103
|
+
if (
|
|
104
|
+
ch == "$" and line[i + 1] == "@" and i + 2 < len(line) and line[i + 2] == '"'
|
|
105
|
+
) or (
|
|
106
|
+
ch == "@" and line[i + 1] == "$" and i + 2 < len(line) and line[i + 2] == '"'
|
|
107
|
+
):
|
|
108
|
+
# Interpolated verbatim string — "" for escaped quote
|
|
109
|
+
i += 3
|
|
110
|
+
while i < len(line):
|
|
111
|
+
if line[i] == '"':
|
|
112
|
+
if i + 1 < len(line) and line[i + 1] == '"':
|
|
113
|
+
i += 2
|
|
114
|
+
continue
|
|
115
|
+
i += 1
|
|
116
|
+
break
|
|
117
|
+
i += 1
|
|
118
|
+
else:
|
|
119
|
+
# Multi-line verbatim string
|
|
120
|
+
idx += 1
|
|
121
|
+
while idx < len(lines):
|
|
122
|
+
line = lines[idx]
|
|
123
|
+
i = 0
|
|
124
|
+
while i < len(line):
|
|
125
|
+
if line[i] == '"':
|
|
126
|
+
if i + 1 < len(line) and line[i + 1] == '"':
|
|
127
|
+
i += 2
|
|
128
|
+
continue
|
|
129
|
+
i += 1
|
|
130
|
+
break
|
|
131
|
+
i += 1
|
|
132
|
+
else:
|
|
133
|
+
idx += 1
|
|
134
|
+
continue
|
|
135
|
+
break
|
|
136
|
+
else:
|
|
137
|
+
return len(lines) - 1
|
|
138
|
+
continue
|
|
139
|
+
# Verbatim string: @"..."
|
|
140
|
+
if ch == "@" and line[i + 1] == '"':
|
|
141
|
+
i += 2
|
|
142
|
+
while i < len(line):
|
|
143
|
+
if line[i] == '"':
|
|
144
|
+
if i + 1 < len(line) and line[i + 1] == '"':
|
|
145
|
+
i += 2
|
|
146
|
+
continue
|
|
147
|
+
i += 1
|
|
148
|
+
break
|
|
149
|
+
i += 1
|
|
150
|
+
else:
|
|
151
|
+
# Multi-line verbatim
|
|
152
|
+
idx += 1
|
|
153
|
+
while idx < len(lines):
|
|
154
|
+
line = lines[idx]
|
|
155
|
+
i = 0
|
|
156
|
+
while i < len(line):
|
|
157
|
+
if line[i] == '"':
|
|
158
|
+
if i + 1 < len(line) and line[i + 1] == '"':
|
|
159
|
+
i += 2
|
|
160
|
+
continue
|
|
161
|
+
i += 1
|
|
162
|
+
break
|
|
163
|
+
i += 1
|
|
164
|
+
else:
|
|
165
|
+
idx += 1
|
|
166
|
+
continue
|
|
167
|
+
break
|
|
168
|
+
else:
|
|
169
|
+
return len(lines) - 1
|
|
170
|
+
continue
|
|
171
|
+
# Interpolated string: $"..."
|
|
172
|
+
if ch == "$" and line[i + 1] == '"':
|
|
173
|
+
i += 2
|
|
174
|
+
while i < len(line):
|
|
175
|
+
if line[i] == "\\":
|
|
176
|
+
i += 2
|
|
177
|
+
continue
|
|
178
|
+
if line[i] == '"':
|
|
179
|
+
i += 1
|
|
180
|
+
break
|
|
181
|
+
i += 1
|
|
182
|
+
continue
|
|
183
|
+
# Regular string
|
|
184
|
+
if ch == '"':
|
|
185
|
+
i += 1
|
|
186
|
+
while i < len(line):
|
|
187
|
+
if line[i] == "\\":
|
|
188
|
+
i += 2
|
|
189
|
+
continue
|
|
190
|
+
if line[i] == '"':
|
|
191
|
+
i += 1
|
|
192
|
+
break
|
|
193
|
+
i += 1
|
|
194
|
+
continue
|
|
195
|
+
# Char literal
|
|
196
|
+
if ch == "'" and i + 1 < len(line):
|
|
197
|
+
if i + 2 < len(line) and line[i + 1] == "\\":
|
|
198
|
+
end = line.find("'", i + 2)
|
|
199
|
+
if end >= 0 and end <= i + 4:
|
|
200
|
+
i = end + 1
|
|
201
|
+
continue
|
|
202
|
+
elif i + 2 < len(line) and line[i + 2] == "'":
|
|
203
|
+
i += 3
|
|
204
|
+
continue
|
|
205
|
+
if ch == "{":
|
|
206
|
+
depth += 1
|
|
207
|
+
found_open = True
|
|
208
|
+
elif ch == "}":
|
|
209
|
+
depth -= 1
|
|
210
|
+
if found_open and depth == 0:
|
|
211
|
+
return idx
|
|
212
|
+
i += 1
|
|
213
|
+
return len(lines) - 1
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def find_brace_end_rust(lines: list[str], start_line_0: int) -> int:
|
|
217
|
+
"""Find the 0-based line where the outermost brace closes,
|
|
218
|
+
skipping strings, raw strings, char literals, and comments."""
|
|
219
|
+
depth = 0
|
|
220
|
+
found_open = False
|
|
221
|
+
in_block_comment = 0 # nesting depth for /* */
|
|
222
|
+
for idx in range(start_line_0, len(lines)):
|
|
223
|
+
line = lines[idx]
|
|
224
|
+
i = 0
|
|
225
|
+
while i < len(line):
|
|
226
|
+
ch = line[i]
|
|
227
|
+
# Block comment handling (Rust supports nested /* */)
|
|
228
|
+
if in_block_comment > 0:
|
|
229
|
+
if ch == "/" and i + 1 < len(line) and line[i + 1] == "*":
|
|
230
|
+
in_block_comment += 1
|
|
231
|
+
i += 2
|
|
232
|
+
continue
|
|
233
|
+
if ch == "*" and i + 1 < len(line) and line[i + 1] == "/":
|
|
234
|
+
in_block_comment -= 1
|
|
235
|
+
i += 2
|
|
236
|
+
continue
|
|
237
|
+
i += 1
|
|
238
|
+
continue
|
|
239
|
+
# Line comment
|
|
240
|
+
if ch == "/" and i + 1 < len(line):
|
|
241
|
+
if line[i + 1] == "/":
|
|
242
|
+
break # rest is line comment
|
|
243
|
+
if line[i + 1] == "*":
|
|
244
|
+
in_block_comment += 1
|
|
245
|
+
i += 2
|
|
246
|
+
continue
|
|
247
|
+
# Raw string: r#"..."#, r##"..."##, etc.
|
|
248
|
+
if ch == "r" and i + 1 < len(line) and line[i + 1] in ('"', "#"):
|
|
249
|
+
hash_count = 0
|
|
250
|
+
j = i + 1
|
|
251
|
+
while j < len(line) and line[j] == "#":
|
|
252
|
+
hash_count += 1
|
|
253
|
+
j += 1
|
|
254
|
+
if j < len(line) and line[j] == '"':
|
|
255
|
+
j += 1
|
|
256
|
+
# Find closing "###
|
|
257
|
+
closing = '"' + "#" * hash_count
|
|
258
|
+
while True:
|
|
259
|
+
pos = line.find(closing, j)
|
|
260
|
+
if pos >= 0:
|
|
261
|
+
i = pos + len(closing)
|
|
262
|
+
break
|
|
263
|
+
# Span to next line
|
|
264
|
+
idx += 1
|
|
265
|
+
if idx >= len(lines):
|
|
266
|
+
return len(lines) - 1
|
|
267
|
+
line = lines[idx]
|
|
268
|
+
j = 0
|
|
269
|
+
continue
|
|
270
|
+
# Regular string
|
|
271
|
+
if ch == '"':
|
|
272
|
+
i += 1
|
|
273
|
+
while i < len(line):
|
|
274
|
+
if line[i] == "\\":
|
|
275
|
+
i += 2
|
|
276
|
+
continue
|
|
277
|
+
if line[i] == '"':
|
|
278
|
+
i += 1
|
|
279
|
+
break
|
|
280
|
+
i += 1
|
|
281
|
+
continue
|
|
282
|
+
# Char literal (skip 'a', '\n', etc. but not lifetime 'a)
|
|
283
|
+
if ch == "'" and i + 1 < len(line):
|
|
284
|
+
# Lifetime check: 'a where next is alpha and followed by non-'
|
|
285
|
+
# Char literal: 'x' or '\n'
|
|
286
|
+
if i + 2 < len(line) and line[i + 1] == "\\":
|
|
287
|
+
# Escaped char literal like '\n'
|
|
288
|
+
end = line.find("'", i + 2)
|
|
289
|
+
if end >= 0 and end <= i + 4:
|
|
290
|
+
i = end + 1
|
|
291
|
+
continue
|
|
292
|
+
elif i + 2 < len(line) and line[i + 2] == "'":
|
|
293
|
+
# Simple char literal like 'a'
|
|
294
|
+
i += 3
|
|
295
|
+
continue
|
|
296
|
+
# Otherwise it's a lifetime, skip
|
|
297
|
+
if ch == "{":
|
|
298
|
+
depth += 1
|
|
299
|
+
found_open = True
|
|
300
|
+
elif ch == "}":
|
|
301
|
+
depth -= 1
|
|
302
|
+
if found_open and depth == 0:
|
|
303
|
+
return idx
|
|
304
|
+
i += 1
|
|
305
|
+
return len(lines) - 1
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def find_brace_end_go(lines: list[str], start_line_0: int) -> int:
|
|
309
|
+
"""Find the 0-based line where the outermost brace closes, skipping strings/comments."""
|
|
310
|
+
depth = 0
|
|
311
|
+
found_open = False
|
|
312
|
+
in_block_comment = False
|
|
313
|
+
for idx in range(start_line_0, len(lines)):
|
|
314
|
+
line = lines[idx]
|
|
315
|
+
i = 0
|
|
316
|
+
while i < len(line):
|
|
317
|
+
ch = line[i]
|
|
318
|
+
if in_block_comment:
|
|
319
|
+
if ch == "*" and i + 1 < len(line) and line[i + 1] == "/":
|
|
320
|
+
in_block_comment = False
|
|
321
|
+
i += 2
|
|
322
|
+
continue
|
|
323
|
+
i += 1
|
|
324
|
+
continue
|
|
325
|
+
if ch == "/" and i + 1 < len(line):
|
|
326
|
+
if line[i + 1] == "/":
|
|
327
|
+
break # rest is line comment
|
|
328
|
+
if line[i + 1] == "*":
|
|
329
|
+
in_block_comment = True
|
|
330
|
+
i += 2
|
|
331
|
+
continue
|
|
332
|
+
if ch == '"':
|
|
333
|
+
i += 1
|
|
334
|
+
while i < len(line) and line[i] != '"':
|
|
335
|
+
if line[i] == "\\":
|
|
336
|
+
i += 1
|
|
337
|
+
i += 1
|
|
338
|
+
i += 1
|
|
339
|
+
continue
|
|
340
|
+
if ch == "`":
|
|
341
|
+
# raw string can span lines - scan to end
|
|
342
|
+
i += 1
|
|
343
|
+
while True:
|
|
344
|
+
while i < len(line):
|
|
345
|
+
if line[i] == "`":
|
|
346
|
+
i += 1
|
|
347
|
+
break
|
|
348
|
+
i += 1
|
|
349
|
+
else:
|
|
350
|
+
# continue to next line
|
|
351
|
+
idx += 1
|
|
352
|
+
if idx >= len(lines):
|
|
353
|
+
return len(lines) - 1
|
|
354
|
+
line = lines[idx]
|
|
355
|
+
i = 0
|
|
356
|
+
continue
|
|
357
|
+
break
|
|
358
|
+
continue
|
|
359
|
+
if ch == "{":
|
|
360
|
+
depth += 1
|
|
361
|
+
found_open = True
|
|
362
|
+
elif ch == "}":
|
|
363
|
+
depth -= 1
|
|
364
|
+
if found_open and depth == 0:
|
|
365
|
+
return idx
|
|
366
|
+
i += 1
|
|
367
|
+
return len(lines) - 1
|