@cashscript/utils 0.13.1 → 0.14.0-next.0

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.
@@ -48,6 +48,11 @@ const PROLOGUE_KINDS = [
48
48
  SourceTagKind.LOCKTIME_GUARD,
49
49
  SourceTagKind.PARAMETER_VALIDATION,
50
50
  ];
51
+ // Compiler-injected epilogue checks: emitted after the function/loop body
52
+ const EPILOGUE_KINDS = [
53
+ SourceTagKind.SCOPE_CLEANUP,
54
+ SourceTagKind.LOOP_CONDITION,
55
+ ];
51
56
  function buildInsertions(locationData, sourceLines, sourceTags) {
52
57
  const tags = (sourceTags ? parseSourceTags(sourceTags) : []);
53
58
  return tags.map((tag) => {
@@ -70,6 +75,14 @@ function deriveAnchor(tag, tags, locationData, sourceLines) {
70
75
  indent: lineIndent(sourceLines, firstBodyLine),
71
76
  };
72
77
  }
78
+ // Scope cleanup and loop-back condition tags always get inserted right before the scope's closing brace.
79
+ if (EPILOGUE_KINDS.includes(tag.kind)) {
80
+ const braceLine = getDisplayLine(locationData[tag.startIndex]);
81
+ return {
82
+ insertAfterLine: braceLine - 1,
83
+ indent: deriveIndent(locationData[tag.startIndex].location.start.line, sourceLines),
84
+ };
85
+ }
73
86
  return {
74
87
  insertAfterLine: getDisplayLine(locationData[Math.max(tag.startIndex - 1, 0)]),
75
88
  indent: deriveIndent(getDisplayLine(locationData[tag.startIndex]), sourceLines),
@@ -113,6 +126,10 @@ function tagDescription(tag, locationData, sourceLines) {
113
126
  const parameter = deriveSourceText(tag, locationData, sourceLines);
114
127
  return `>>> parameter type check${parameter ? ` (${parameter})` : ''}`;
115
128
  }
129
+ case SourceTagKind.SCOPE_CLEANUP:
130
+ return '>>> scope cleanup';
131
+ case SourceTagKind.LOOP_CONDITION:
132
+ return '>>> loop condition check';
116
133
  case SourceTagKind.FOR_UPDATE:
117
134
  default:
118
135
  return `>>> for-loop update (${deriveSourceText(tag, locationData, sourceLines)})`;
package/dist/script.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { encodeDataPush, hexToBin, disassembleBytecodeBch, flattenBinArray, encodeAuthenticationInstructions, decodeAuthenticationInstructions, OpcodesBch, } from '@bitauth/libauth';
2
2
  import OptimisationsEquivFile from './cashproof-optimisations.js';
3
3
  import { optimisationReplacements } from './optimisations.js';
4
- import { PositionHint } from './types.js';
4
+ import { range } from './data.js';
5
+ import { PositionHint, SourceTagKind } from './types.js';
5
6
  export const Op = OpcodesBch;
6
7
  export function scriptToAsm(script) {
7
8
  return bytecodeToAsm(scriptToBytecode(script));
@@ -120,7 +121,24 @@ export function optimiseBytecode(script, locationData, logs, requires, sourceTag
120
121
  requires = newRequires;
121
122
  sourceTags = newSourceTags;
122
123
  }
123
- return { script, locationData, logs, requires, sourceTags };
124
+ return { script, locationData, logs, requires, sourceTags: reconcileScopeCleanupTags(script, sourceTags) };
125
+ }
126
+ const SCOPE_CLEANUP_OPCODES = [Op.OP_DROP, Op.OP_NIP, Op.OP_2DROP];
127
+ // Make sure that scope cleanup tags are only displayed if they did not merge with other tags or drift
128
+ // due to compiler optimisations.
129
+ function reconcileScopeCleanupTags(script, sourceTags) {
130
+ const otherTags = sourceTags.filter((tag) => tag.kind !== SourceTagKind.SCOPE_CLEANUP);
131
+ return sourceTags.filter((tag) => {
132
+ if (tag.kind !== SourceTagKind.SCOPE_CLEANUP)
133
+ return true;
134
+ const isOnlyCleanupOpcodes = range(tag.startIndex, tag.endIndex)
135
+ .every((index) => SCOPE_CLEANUP_OPCODES.includes(script[index]));
136
+ if (!isOnlyCleanupOpcodes)
137
+ return false;
138
+ const overlapsOtherTag = otherTags
139
+ .some((other) => tag.startIndex <= other.endIndex && other.startIndex <= tag.endIndex);
140
+ return !overlapsOtherTag;
141
+ });
124
142
  }
125
143
  export function optimiseBytecodeOld(script, runs = 1000) {
126
144
  const optimisations = OptimisationsEquivFile
package/dist/types.d.ts CHANGED
@@ -24,7 +24,8 @@ export declare enum PrimitiveType {
24
24
  PUBKEY = "pubkey",
25
25
  SIG = "sig",
26
26
  DATASIG = "datasig",
27
- ANY = "any"
27
+ ANY = "any",
28
+ VOID = "void"
28
29
  }
29
30
  export declare function explicitlyCastable(from?: Type, to?: Type): boolean;
30
31
  export declare function implicitlyCastable(actual?: Type, expected?: Type): boolean;
@@ -54,7 +55,9 @@ export declare enum PositionHint {
54
55
  export declare enum SourceTagKind {
55
56
  FOR_UPDATE = "fu",
56
57
  LOCKTIME_GUARD = "lg",
57
- PARAMETER_VALIDATION = "pv"
58
+ PARAMETER_VALIDATION = "pv",
59
+ SCOPE_CLEANUP = "sc",
60
+ LOOP_CONDITION = "lc"
58
61
  }
59
62
  export interface SourceTagEntry {
60
63
  startIndex: number;
package/dist/types.js CHANGED
@@ -38,6 +38,7 @@ export var PrimitiveType;
38
38
  PrimitiveType["SIG"] = "sig";
39
39
  PrimitiveType["DATASIG"] = "datasig";
40
40
  PrimitiveType["ANY"] = "any";
41
+ PrimitiveType["VOID"] = "void";
41
42
  })(PrimitiveType || (PrimitiveType = {}));
42
43
  const ExplicitlyCastableTo = {
43
44
  [PrimitiveType.INT]: [PrimitiveType.INT, PrimitiveType.BOOL],
@@ -47,10 +48,14 @@ const ExplicitlyCastableTo = {
47
48
  [PrimitiveType.SIG]: [PrimitiveType.SIG],
48
49
  [PrimitiveType.DATASIG]: [PrimitiveType.DATASIG],
49
50
  [PrimitiveType.ANY]: [],
51
+ [PrimitiveType.VOID]: [],
50
52
  };
51
53
  export function explicitlyCastable(from, to) {
52
54
  if (!from || !to)
53
55
  return false;
56
+ // `void` is not a real value type, so it can never participate in a cast
57
+ if (from === PrimitiveType.VOID || to === PrimitiveType.VOID)
58
+ return false;
54
59
  // Tuples can't be cast
55
60
  if (from instanceof TupleType || to instanceof TupleType)
56
61
  return false;
@@ -104,6 +109,9 @@ export function explicitlyCastable(from, to) {
104
109
  export function implicitlyCastable(actual, expected) {
105
110
  if (!actual || !expected)
106
111
  return false;
112
+ // `void` is not a real value type, so it can never be assigned to or from (not even to `any`)
113
+ if (actual === PrimitiveType.VOID || expected === PrimitiveType.VOID)
114
+ return false;
107
115
  if (actual instanceof TupleType && expected instanceof TupleType) {
108
116
  const leftIsCompatible = implicitlyCastable(actual.leftType, expected.leftType);
109
117
  const rightIsCompatible = implicitlyCastable(actual.rightType, expected.rightType);
@@ -197,5 +205,7 @@ export var SourceTagKind;
197
205
  SourceTagKind["FOR_UPDATE"] = "fu";
198
206
  SourceTagKind["LOCKTIME_GUARD"] = "lg";
199
207
  SourceTagKind["PARAMETER_VALIDATION"] = "pv";
208
+ SourceTagKind["SCOPE_CLEANUP"] = "sc";
209
+ SourceTagKind["LOOP_CONDITION"] = "lc";
200
210
  })(SourceTagKind || (SourceTagKind = {}));
201
211
  //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cashscript/utils",
3
- "version": "0.13.1",
3
+ "version": "0.14.0-next.0",
4
4
  "description": "CashScript utilities and types",
5
5
  "keywords": [
6
6
  "bitcoin cash",
@@ -48,5 +48,5 @@
48
48
  "typescript": "^5.9.2",
49
49
  "vitest": "^4.0.15"
50
50
  },
51
- "gitHead": "b5248b51a2ef93d750dcf916743eac9b79f62b5b"
51
+ "gitHead": "790d4a2c608ef492a9a1f886b1489d2e4ffe95e2"
52
52
  }