( script: Script, locationData: FullLocationData, logs: LogEntry[], requires: RequireStatement[], sourceTags: SourceTagEntry[], constructorParamLength: number, optimisations: string[][], )
| 274 | } |
| 275 | |
| 276 | function replaceOps( |
| 277 | script: Script, |
| 278 | locationData: FullLocationData, |
| 279 | logs: LogEntry[], |
| 280 | requires: RequireStatement[], |
| 281 | sourceTags: SourceTagEntry[], |
| 282 | constructorParamLength: number, |
| 283 | optimisations: string[][], |
| 284 | ): ReplaceOpsResult { |
| 285 | let asm = scriptToAsm(script); |
| 286 | let newLocationData = [...locationData]; |
| 287 | let newLogs = [...logs]; |
| 288 | let newRequires = [...requires]; |
| 289 | let newSourceTags = [...sourceTags]; |
| 290 | |
| 291 | optimisations.forEach(([pattern, replacement]) => { |
| 292 | let processedAsm = ''; |
| 293 | let asmToSearch = asm; |
| 294 | |
| 295 | // We add a space or end of string to the end of the pattern to ensure that we match the whole pattern |
| 296 | // (no partial matches) |
| 297 | const regex = new RegExp(`${pattern}(\\s|$)`, 'g'); |
| 298 | |
| 299 | let matchIndex = asmToSearch.search(regex); |
| 300 | while (matchIndex !== -1) { |
| 301 | // We add the part before the match to the processed asm |
| 302 | processedAsm = mergeAsm(processedAsm, asmToSearch.slice(0, matchIndex)); |
| 303 | |
| 304 | // We count the number of spaces in the processed asm + 1, which is equal to the script index |
| 305 | // We do the same thing to calculate the number of opcodes in the pattern and replacement |
| 306 | const scriptIndex = processedAsm === '' ? 0 : [...processedAsm.matchAll(/\s+/g)].length + 1; |
| 307 | const patternLength = [...pattern.matchAll(/\s+/g)].length + 1; |
| 308 | const replacementLength = replacement === '' ? 0 : [...replacement.matchAll(/\s+/g)].length + 1; |
| 309 | |
| 310 | // We get the locationData entries for every opcode in the pattern |
| 311 | const patternLocations = newLocationData.slice(scriptIndex, scriptIndex + patternLength); |
| 312 | |
| 313 | // We get the lowest start location and highest end location of the pattern |
| 314 | const lowestStart = getLowestStartLocation(patternLocations); |
| 315 | const highestEnd = getHighestEndLocation(patternLocations); |
| 316 | |
| 317 | // Initially we set the position hint to END if any of the pattern locations have a position hint of END |
| 318 | // It turned out that this was not the correct approach in the case of OP_NOT OP_IF => OP_NOTIF, |
| 319 | // because OP_IF and OP_NOTIF are START opcodes, and OP_NOT is an END opcode. |
| 320 | // After reviewing the entire list of optimisations, we set the position hint to the last location's position hint |
| 321 | // which we believe to be the correct approach, but it is hard to reason about. |
| 322 | // We've also consulted with AI (o3-max) to help us reason about this, and it seems to be the correct approach. |
| 323 | const positionHint = patternLocations.at(-1)?.positionHint ?? PositionHint.START; |
| 324 | |
| 325 | // We merge the lowest start and highest end locations into a single location data entry |
| 326 | const mergedLocation = { |
| 327 | location: { |
| 328 | start: lowestStart.location.start, |
| 329 | end: highestEnd.location.end, |
| 330 | }, |
| 331 | positionHint, |
| 332 | }; |
| 333 |
no test coverage detected