|
6 | 6 | import logging
|
7 | 7 | import os
|
8 | 8 | import platform
|
| 9 | +import re |
9 | 10 | import typing
|
10 | 11 | from json.decoder import JSONDecodeError
|
11 | 12 | from subprocess import PIPE, Popen
|
@@ -168,16 +169,77 @@ def parse_pragma(solidity_code):
|
168 | 169 | all_versions = solcx.get_installed_solc_versions()
|
169 | 170 |
|
170 | 171 |
|
171 |
| -def extract_version(file: typing.Optional[str]): |
172 |
| - if file is None: |
| 172 | +VOID_START = re.compile("//|/\\*|\"|'") |
| 173 | +QUOTE_END = re.compile("(?<!\\\\)'") |
| 174 | +DQUOTE_END = re.compile('(?<!\\\\)"') |
| 175 | + |
| 176 | + |
| 177 | +def remove_comments_strings(program: str) -> str: |
| 178 | + """Return program without Solidity comments and strings |
| 179 | +
|
| 180 | + :param str program: Solidity program with lines separated by \\n |
| 181 | + :return: program with strings emptied and comments removed |
| 182 | + :rtype: str |
| 183 | + """ |
| 184 | + result = "" |
| 185 | + while True: |
| 186 | + match_start_of_void = VOID_START.search(program) |
| 187 | + if not match_start_of_void: |
| 188 | + result += program |
| 189 | + break |
| 190 | + else: |
| 191 | + result += program[: match_start_of_void.start()] |
| 192 | + if match_start_of_void[0] == "//": |
| 193 | + end = program.find("\n", match_start_of_void.end()) |
| 194 | + program = "" if end == -1 else program[end:] |
| 195 | + elif match_start_of_void[0] == "/*": |
| 196 | + end = program.find("*/", match_start_of_void.end()) |
| 197 | + result += " " |
| 198 | + program = "" if end == -1 else program[end + 2 :] |
| 199 | + else: |
| 200 | + if match_start_of_void[0] == "'": |
| 201 | + match_end_of_string = QUOTE_END.search( |
| 202 | + program[match_start_of_void.end() :] |
| 203 | + ) |
| 204 | + else: |
| 205 | + match_end_of_string = DQUOTE_END.search( |
| 206 | + program[match_start_of_void.end() :] |
| 207 | + ) |
| 208 | + if not match_end_of_string: # unclosed string |
| 209 | + break |
| 210 | + program = program[ |
| 211 | + match_start_of_void.end() + match_end_of_string.end() : |
| 212 | + ] |
| 213 | + return result |
| 214 | + |
| 215 | + |
| 216 | +def extract_version_line(program: typing.Optional[str]) -> typing.Optional[str]: |
| 217 | + if not program: |
173 | 218 | return None
|
174 |
| - version_line = None |
175 |
| - for line in file.split("\n"): |
176 |
| - if "pragma solidity" not in line: |
177 |
| - continue |
178 |
| - version_line = line.rstrip() |
179 |
| - break |
180 |
| - if version_line is None: |
| 219 | + |
| 220 | + # normalize line endings |
| 221 | + if "\n" in program: |
| 222 | + program = program.replace("\r", "") |
| 223 | + else: |
| 224 | + program = program.replace("\r", "\n") |
| 225 | + |
| 226 | + # extract regular pragma |
| 227 | + program_wo_comments_strings = remove_comments_strings(program) |
| 228 | + for line in program_wo_comments_strings.split("\n"): |
| 229 | + if "pragma solidity" in line: |
| 230 | + return line.rstrip() |
| 231 | + |
| 232 | + # extract pragma from comments |
| 233 | + for line in program.split("\n"): |
| 234 | + if "pragma solidity" in line: |
| 235 | + return line.rstrip() |
| 236 | + |
| 237 | + return None |
| 238 | + |
| 239 | + |
| 240 | +def extract_version(program: typing.Optional[str]) -> typing.Optional[str]: |
| 241 | + version_line = extract_version_line(program) |
| 242 | + if not version_line: |
181 | 243 | return None
|
182 | 244 |
|
183 | 245 | assert "pragma solidity" in version_line
|
@@ -212,11 +274,11 @@ def extract_version(file: typing.Optional[str]):
|
212 | 274 |
|
213 | 275 |
|
214 | 276 | def extract_binary(file: str) -> Tuple[str, str]:
|
215 |
| - file_data = None |
| 277 | + program = None |
216 | 278 | with open(file) as f:
|
217 |
| - file_data = f.read() |
| 279 | + program = f.read() |
218 | 280 |
|
219 |
| - version = extract_version(file_data) |
| 281 | + version = extract_version(program) |
220 | 282 |
|
221 | 283 | if version is None:
|
222 | 284 | return os.environ.get("SOLC") or "solc", version
|
|
0 commit comments