pabst-checker 0.12.1 → 0.13.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.
package/README.md CHANGED
@@ -10,8 +10,9 @@ non-zero provided the second argument is an integer. Can you spot the error?
10
10
 
11
11
  ```ts
12
12
  /**
13
- * @ensures{nonzero} forall (x: bigint) (y: number),
13
+ * @ensures{nonzero} forall (x: bigint) (y: number) {
14
14
  * Number.isInteger(y) ==> foo(x, y) !== 0
15
+ * }
15
16
  */
16
17
  export function foo(x: bigint, y: number): number {
17
18
  return Number(x % 2n) + (y % 2) + 1;
@@ -144,14 +145,16 @@ spelling of an equation is a plain `Object.is` call).
144
145
 
145
146
  ```ts
146
147
  /**
147
- * @ensures{guarded} forall (x: int),
148
+ * @ensures{guarded} forall (x: int) {
148
149
  * isPrime(x) ∧ x > 2 → isOdd(x)
150
+ * }
149
151
  */
150
152
  ```
151
153
 
152
- - **Quantifier:** `forall` / `∀`, one-or-more binder groups, then a comma, then
153
- the body. Lean-style grouping `(x y: int)` is supported. Existential `∃` /
154
- `exists` is intentionally rejected (PBT cannot soundly confirm existence).
154
+ - **Quantifier:** `forall` / `∀`, one-or-more binder groups, then the body
155
+ in braces: `forall (x: int) { ... }`. Lean-style grouping `(x y: int)` is
156
+ supported. Existential `∃` / `exists` is intentionally rejected (PBT
157
+ cannot soundly confirm existence).
155
158
  - **Domains:** `int`, `nat`, `number`, `boolean`, `string`, `bigint`.
156
159
  A numeric domain (`int`, `nat`, `number`, `bigint`) may be constrained to
157
160
  an interval: `forall (x: int ∈ [1, 30])` (ASCII fallback: `in`). Each
@@ -27,12 +27,21 @@ export function parsePrefix(formula) {
27
27
  if (binders.length === 0) {
28
28
  throw new PabstError(`expected at least one binder group '(x: domain)' after forall`);
29
29
  }
30
- const comma = toks[i];
31
- if (!comma || comma.text !== ",") {
32
- const at = comma?.start ?? formula.length;
33
- throw new PabstError(`expected ',' separating binders from body, got: ${formula.slice(at, at + 60)}`);
30
+ const open = toks[i];
31
+ if (open && open.text === ",") {
32
+ throw new PabstError(`since pabst 0.13.0 the property body goes in braces instead of after ` +
33
+ `a comma: forall (x: int) { ... }`);
34
34
  }
35
- const body = formula.slice(comma.end).trim();
35
+ if (!open || open.text !== "{") {
36
+ const at = open?.start ?? formula.length;
37
+ throw new PabstError(`expected '{' to open the property body, got: ${formula.slice(at, at + 60)}`);
38
+ }
39
+ const close = braceExtent(toks, i, formula);
40
+ const after = toks[close + 1];
41
+ if (after) {
42
+ throw new PabstError(`unexpected text after the property body's closing '}': ${formula.slice(after.start, after.start + 60)}`);
43
+ }
44
+ const body = formula.slice(open.end, toks[close].start).trim();
36
45
  if (body.length === 0)
37
46
  throw new PabstError(`property body is empty`);
38
47
  return { binders, body };
@@ -99,6 +108,25 @@ function groupExtent(toks, open, formula, unterminatedSlash) {
99
108
  : "";
100
109
  throw new PabstError(`unbalanced parentheses in binder group: ${formula.slice(toks[open].start)}${hint}`);
101
110
  }
111
+ /** Index of the '}' matching the '{' at toks[open]. Only genuine brace
112
+ * tokens take part in the count: scanTokens packages string, regex, and
113
+ * template text into single tokens and rescans a substitution-closing '}'
114
+ * into a TemplateMiddle/TemplateTail, so every OpenBraceToken in the
115
+ * stream has a real CloseBraceToken partner. */
116
+ function braceExtent(toks, open, formula) {
117
+ let depth = 0;
118
+ for (let j = open; j < toks.length; j++) {
119
+ const kind = toks[j].kind;
120
+ if (kind === ts.SyntaxKind.OpenBraceToken)
121
+ depth++;
122
+ else if (kind === ts.SyntaxKind.CloseBraceToken) {
123
+ depth--;
124
+ if (depth === 0)
125
+ return j;
126
+ }
127
+ }
128
+ throw new PabstError(`unbalanced braces in property body: ${formula.slice(toks[open].start)}`);
129
+ }
102
130
  /** Index of the token closing a plausible two-endpoint interval starting at
103
131
  * toks[at], or -1 when none starts there — the group scan then proceeds as
104
132
  * usual and the guard text reaches parseRange for the precise complaint. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabst-checker",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Property-based testing from @ensures annotations, powered by fast-check.",
5
5
  "repository": {
6
6
  "type": "git",