@harmoniclabs/pebble 0.1.8 → 0.1.9
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/dist/ast/nodes/expr/IsExpr.d.ts +10 -1
- package/dist/ast/nodes/expr/IsExpr.js +9 -13
- package/dist/ast/nodes/expr/PebbleExpr.d.ts +2 -1
- package/dist/ast/nodes/expr/PebbleExpr.js +2 -1
- package/dist/ast/nodes/statements/declarations/ContractDecl.d.ts +3 -1
- package/dist/ast/nodes/statements/declarations/ContractDecl.js +3 -1
- package/dist/ast/nodes/statements/declarations/StateDecl.d.ts +12 -0
- package/dist/ast/nodes/statements/declarations/StateDecl.js +12 -0
- package/dist/compiler/AstCompiler/internal/_deriveContractBody/_deriveContractBody.js +155 -6
- package/dist/compiler/AstCompiler/internal/exprs/_compileCaseExpr.d.ts +1 -1
- package/dist/compiler/AstCompiler/internal/exprs/_compileCaseExpr.js +30 -4
- package/dist/compiler/AstCompiler/internal/exprs/_compileExpr.js +4 -1
- package/dist/compiler/AstCompiler/internal/exprs/_compileIsExpr.d.ts +5 -1
- package/dist/compiler/AstCompiler/internal/exprs/_compileIsExpr.js +20 -41
- package/dist/compiler/AstCompiler/internal/exprs/_compileTernaryExpr.js +7 -2
- package/dist/compiler/AstCompiler/internal/statements/_compileAssertStmt.js +4 -0
- package/dist/compiler/AstCompiler/internal/statements/_compileIfStmt.js +7 -2
- package/dist/compiler/AstCompiler/internal/statements/_compileMatchStmt.d.ts +1 -1
- package/dist/compiler/AstCompiler/internal/statements/_compileMatchStmt.js +17 -4
- package/dist/compiler/AstCompiler/scope/AstScope.d.ts +12 -0
- package/dist/compiler/AstCompiler/scope/AstScope.js +25 -2
- package/dist/compiler/AstCompiler/utils/extractIsNarrowings.d.ts +30 -0
- package/dist/compiler/AstCompiler/utils/extractIsNarrowings.js +110 -0
- package/dist/compiler/Compiler.js +7 -3
- package/dist/compiler/TirCompiler/expressify/expressifyVars.js +6 -0
- package/dist/compiler/tir/expressions/TirCallExpr.js +4 -0
- package/dist/compiler/tir/expressions/TirCaseExpr.js +38 -7
- package/dist/compiler/tir/expressions/TirExpr.d.ts +2 -1
- package/dist/compiler/tir/expressions/TirExpr.js +2 -1
- package/dist/compiler/tir/expressions/TirFuncExpr.js +4 -1
- package/dist/compiler/tir/expressions/TirIsExpr.d.ts +23 -0
- package/dist/compiler/tir/expressions/TirIsExpr.js +87 -0
- package/dist/compiler/tir/types/TirStructType.d.ts +33 -2
- package/dist/compiler/tir/types/TirStructType.js +69 -4
- package/dist/compiler/tir/types/utils/canAssignTo.js +14 -0
- package/dist/parser/Parser.d.ts +2 -0
- package/dist/parser/Parser.js +87 -22
- package/dist/tokenizer/Token.d.ts +79 -78
- package/dist/tokenizer/Token.js +79 -78
- package/dist/tokenizer/utils/tokenFromKeyword.js +7 -2
- package/dist/tokenizer/utils/tokenIsAlsoIdentifier.js +1 -0
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Identifier } from "../../../../ast/nodes/common/Identifier.js";
|
|
2
|
+
import { ParentesizedExpr } from "../../../../ast/nodes/expr/ParentesizedExpr.js";
|
|
1
3
|
import { ArrayLikeDeconstr } from "../../../../ast/nodes/statements/declarations/VarDecl/ArrayLikeDeconstr.js";
|
|
2
4
|
import { NamedDeconstructVarDecl } from "../../../../ast/nodes/statements/declarations/VarDecl/NamedDeconstructVarDecl.js";
|
|
3
5
|
import { SimpleVarDecl } from "../../../../ast/nodes/statements/declarations/VarDecl/SimpleVarDecl.js";
|
|
@@ -14,6 +16,11 @@ import { wrapManyStatements } from "../../utils/wrapManyStatementsOrReturnSame.j
|
|
|
14
16
|
import { _compileExpr } from "../exprs/_compileExpr.js";
|
|
15
17
|
import { _compileStatement } from "./_compileStatement.js";
|
|
16
18
|
import { _compileNamedDeconstructVarDecl, _compileSingleDeconstructVarDecl } from "./_compileVarStmt.js";
|
|
19
|
+
function unwrapToIdentifierName(expr) {
|
|
20
|
+
while (expr instanceof ParentesizedExpr)
|
|
21
|
+
expr = expr.expr;
|
|
22
|
+
return expr instanceof Identifier ? expr.text : undefined;
|
|
23
|
+
}
|
|
17
24
|
export function _compileMatchStmt(ctx, stmt) {
|
|
18
25
|
if (!ctx.functionCtx)
|
|
19
26
|
return ctx.error(DiagnosticCode.A_match_statement_can_only_be_used_within_a_function_body, stmt.range);
|
|
@@ -33,10 +40,11 @@ export function _compileMatchStmt(ctx, stmt) {
|
|
|
33
40
|
const missingCtors = ctorNames.slice();
|
|
34
41
|
if (stmt.cases.length === 0)
|
|
35
42
|
return ctx.error(DiagnosticCode.A_match_statement_must_have_at_least_one_case, stmt.range);
|
|
43
|
+
const matchedVarName = unwrapToIdentifierName(stmt.matchExpr);
|
|
36
44
|
const cases = [];
|
|
37
45
|
const constrNamesAlreadySpecified = [];
|
|
38
46
|
for (const matchCase of stmt.cases) {
|
|
39
|
-
const branch = _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, constrNamesAlreadySpecified);
|
|
47
|
+
const branch = _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, constrNamesAlreadySpecified, matchedVarName);
|
|
40
48
|
if (!branch)
|
|
41
49
|
return undefined;
|
|
42
50
|
/*
|
|
@@ -67,7 +75,7 @@ export function _compileMatchStmt(ctx, stmt) {
|
|
|
67
75
|
}
|
|
68
76
|
return [new TirMatchStmt(matchExpr, cases, wildcardCase, stmt.range)];
|
|
69
77
|
}
|
|
70
|
-
export function _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, constrNamesAlreadySpecified) {
|
|
78
|
+
export function _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, constrNamesAlreadySpecified, matchedVarName) {
|
|
71
79
|
/*
|
|
72
80
|
const pattern = _compileVarDecl( ctx, matchCase.pattern, deconstructableType );
|
|
73
81
|
if( !pattern ) return undefined;
|
|
@@ -101,10 +109,15 @@ export function _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, co
|
|
|
101
109
|
constrNamesAlreadySpecified.push(deconstructedCtorName);
|
|
102
110
|
if (deconstructableType instanceof TirSoPStructType
|
|
103
111
|
|| deconstructableType instanceof TirDataStructType) {
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
112
|
+
const localIdx = deconstructableType.constructors.findIndex(c => c.name === deconstructedCtorName);
|
|
113
|
+
if (localIdx < 0)
|
|
106
114
|
return ctx.error(DiagnosticCode.Unknown_0_constructor_1, pattern.name.range, deconstructableType.toString(), deconstructedCtorName);
|
|
107
115
|
const branchCtx = ctx.newBranchChildScope();
|
|
116
|
+
// narrow the matched variable (if it's a plain identifier) to the matched constructor
|
|
117
|
+
if (matchedVarName) {
|
|
118
|
+
const parentIdx = deconstructableType.parentCtorIdx(localIdx);
|
|
119
|
+
branchCtx.scope.narrowVariable(matchedVarName, deconstructableType.narrowTo([parentIdx]));
|
|
120
|
+
}
|
|
108
121
|
const branchArg = _compileNamedDeconstructVarDecl(branchCtx, pattern, deconstructableType);
|
|
109
122
|
if (!branchArg)
|
|
110
123
|
return undefined;
|
|
@@ -74,12 +74,24 @@ export declare class AstScope {
|
|
|
74
74
|
* ast name -> variable infos (name, type, isConstant)
|
|
75
75
|
*/
|
|
76
76
|
readonly variables: Map<string, IVariableInfos>;
|
|
77
|
+
/**
|
|
78
|
+
* Flow-sensitive type narrowings active in this scope.
|
|
79
|
+
* Variable name -> narrowed type (overrides the type recorded in `variables`
|
|
80
|
+
* or any parent scope's narrowing/binding when resolving this variable).
|
|
81
|
+
*/
|
|
82
|
+
readonly narrowings: Map<string, TirType>;
|
|
77
83
|
readonly aviableConstructors: Map<string, IAvaiableConstructor>;
|
|
78
84
|
private _isReadonly;
|
|
79
85
|
readonly infos: ScopeInfos;
|
|
80
86
|
constructor(parent: AstScope | undefined, program: TypedProgram, infos: Partial<ScopeInfos>);
|
|
81
87
|
defineValue(valueInfos: IVariableInfos): boolean;
|
|
82
88
|
resolveValue(name: string): ResolveValueResult | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Records a flow-sensitive narrowing for `name` in THIS scope.
|
|
91
|
+
* Subsequent `resolveValue(name)` calls (in this scope or its
|
|
92
|
+
* descendants, until shadowed) will return the narrowed type.
|
|
93
|
+
*/
|
|
94
|
+
narrowVariable(name: string, narrowedType: TirType): void;
|
|
83
95
|
allVariables(): string[];
|
|
84
96
|
defineUnambigousType(name: string, tirTypeKey: string, allowsDataEncoding: boolean, methodsNames: Map<AstFuncName, TirFuncName>): boolean;
|
|
85
97
|
defineType(name: string, possibleTirTypes: PossibleTirTypes): boolean;
|
|
@@ -52,6 +52,12 @@ export class AstScope {
|
|
|
52
52
|
* ast name -> variable infos (name, type, isConstant)
|
|
53
53
|
*/
|
|
54
54
|
variables = new Map();
|
|
55
|
+
/**
|
|
56
|
+
* Flow-sensitive type narrowings active in this scope.
|
|
57
|
+
* Variable name -> narrowed type (overrides the type recorded in `variables`
|
|
58
|
+
* or any parent scope's narrowing/binding when resolving this variable).
|
|
59
|
+
*/
|
|
60
|
+
narrowings = new Map();
|
|
55
61
|
aviableConstructors = new Map();
|
|
56
62
|
_isReadonly = false;
|
|
57
63
|
infos;
|
|
@@ -75,10 +81,13 @@ export class AstScope {
|
|
|
75
81
|
return true;
|
|
76
82
|
}
|
|
77
83
|
resolveValue(name) {
|
|
84
|
+
const narrowed = this.narrowings.get(name);
|
|
78
85
|
const localValue = this.variables.get(name);
|
|
79
86
|
if (localValue)
|
|
80
87
|
return {
|
|
81
|
-
variableInfos:
|
|
88
|
+
variableInfos: narrowed
|
|
89
|
+
? { ...localValue, type: narrowed }
|
|
90
|
+
: localValue,
|
|
82
91
|
isDefinedOutsideFuncScope: false
|
|
83
92
|
};
|
|
84
93
|
if (this.parent) {
|
|
@@ -86,12 +95,24 @@ export class AstScope {
|
|
|
86
95
|
if (!parentValue)
|
|
87
96
|
return undefined;
|
|
88
97
|
return {
|
|
89
|
-
variableInfos:
|
|
98
|
+
variableInfos: narrowed
|
|
99
|
+
? { ...parentValue.variableInfos, type: narrowed }
|
|
100
|
+
: parentValue.variableInfos,
|
|
90
101
|
isDefinedOutsideFuncScope: parentValue.isDefinedOutsideFuncScope || this.infos.isFunctionDeclScope
|
|
91
102
|
};
|
|
92
103
|
}
|
|
93
104
|
return undefined;
|
|
94
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Records a flow-sensitive narrowing for `name` in THIS scope.
|
|
108
|
+
* Subsequent `resolveValue(name)` calls (in this scope or its
|
|
109
|
+
* descendants, until shadowed) will return the narrowed type.
|
|
110
|
+
*/
|
|
111
|
+
narrowVariable(name, narrowedType) {
|
|
112
|
+
if (this._isReadonly)
|
|
113
|
+
return;
|
|
114
|
+
this.narrowings.set(name, narrowedType);
|
|
115
|
+
}
|
|
95
116
|
allVariables() {
|
|
96
117
|
return (this.parent?.allVariables() ?? []).concat(Array.from(this.variables.keys()));
|
|
97
118
|
}
|
|
@@ -185,6 +206,8 @@ export class AstScope {
|
|
|
185
206
|
const cloned = new AstScope(this.parent, this.program, this.infos);
|
|
186
207
|
for (const [key, value] of this.variables)
|
|
187
208
|
cloned.variables.set(key, { ...value });
|
|
209
|
+
for (const [key, value] of this.narrowings)
|
|
210
|
+
cloned.narrowings.set(key, value.clone());
|
|
188
211
|
for (const [key, value] of this.functions)
|
|
189
212
|
cloned.functions.set(key, value);
|
|
190
213
|
for (const [key, value] of this.types)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { TirExpr } from "../../tir/expressions/TirExpr.js";
|
|
2
|
+
import { TirStructType } from "../../tir/types/TirStructType.js";
|
|
3
|
+
export interface IsNarrowing {
|
|
4
|
+
varName: string;
|
|
5
|
+
/** indexes (in the parent struct's `constructors` array) still possible after the check */
|
|
6
|
+
parentCtorIdxs: number[];
|
|
7
|
+
/** original variable type (before narrowing); the parent struct, used to build the narrowed clone */
|
|
8
|
+
parentStructType: TirStructType;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Walk a boolean expression and collect flow-sensitive narrowings that
|
|
12
|
+
* can be assumed true when the expression evaluates to `polarity`.
|
|
13
|
+
*
|
|
14
|
+
* Rules:
|
|
15
|
+
* - `x is C` ⇒ on TRUE: x narrows to {C}; on FALSE: x narrows to "all but C"
|
|
16
|
+
* - `!cond` ⇒ flip polarity and recurse
|
|
17
|
+
* - `a && b` ⇒ on TRUE: union of narrowings from a and b; on FALSE: nothing
|
|
18
|
+
* - `a || b` ⇒ on TRUE: nothing; on FALSE: union of FALSE-narrowings from a and b
|
|
19
|
+
* - parens are transparent
|
|
20
|
+
*
|
|
21
|
+
* Multiple narrowings on the same variable intersect (most restrictive wins).
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractIsNarrowings(expr: TirExpr, polarity: boolean): IsNarrowing[];
|
|
24
|
+
/**
|
|
25
|
+
* Apply a list of narrowings to the current scope of `ctx`.
|
|
26
|
+
* Builds a narrowed clone of the parent struct and registers it
|
|
27
|
+
* via `scope.narrowVariable`.
|
|
28
|
+
*/
|
|
29
|
+
import type { AstScope } from "../scope/AstScope.js";
|
|
30
|
+
export declare function applyNarrowingsToScope(scope: AstScope, narrowings: IsNarrowing[]): void;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { TirIsExpr } from "../../tir/expressions/TirIsExpr.js";
|
|
2
|
+
import { TirParentesizedExpr } from "../../tir/expressions/TirParentesizedExpr.js";
|
|
3
|
+
import { TirUnaryExclamation } from "../../tir/expressions/unary/TirUnaryExclamation.js";
|
|
4
|
+
import { TirVariableAccessExpr } from "../../tir/expressions/TirVariableAccessExpr.js";
|
|
5
|
+
// NOTE: TirLogicalAndExpr / TirLogicalOrExpr come from TirBinaryExpr.ts which
|
|
6
|
+
// pulls in the IR -> UPLC compiler. Importing them here at the top would
|
|
7
|
+
// create a require-cycle (this file is loaded by `_compileAssertStmt.ts`
|
|
8
|
+
// during ast-compilation, before `compileIRToUPLC` finishes initializing).
|
|
9
|
+
// We detect them by constructor name instead.
|
|
10
|
+
import { TirDataStructType, TirSoPStructType } from "../../tir/types/TirStructType.js";
|
|
11
|
+
import { getStructType } from "../../tir/types/utils/canAssignTo.js";
|
|
12
|
+
/**
|
|
13
|
+
* Walk a boolean expression and collect flow-sensitive narrowings that
|
|
14
|
+
* can be assumed true when the expression evaluates to `polarity`.
|
|
15
|
+
*
|
|
16
|
+
* Rules:
|
|
17
|
+
* - `x is C` ⇒ on TRUE: x narrows to {C}; on FALSE: x narrows to "all but C"
|
|
18
|
+
* - `!cond` ⇒ flip polarity and recurse
|
|
19
|
+
* - `a && b` ⇒ on TRUE: union of narrowings from a and b; on FALSE: nothing
|
|
20
|
+
* - `a || b` ⇒ on TRUE: nothing; on FALSE: union of FALSE-narrowings from a and b
|
|
21
|
+
* - parens are transparent
|
|
22
|
+
*
|
|
23
|
+
* Multiple narrowings on the same variable intersect (most restrictive wins).
|
|
24
|
+
*/
|
|
25
|
+
export function extractIsNarrowings(expr, polarity) {
|
|
26
|
+
const acc = [];
|
|
27
|
+
collect(expr, polarity, acc);
|
|
28
|
+
return mergeIntersect(acc);
|
|
29
|
+
}
|
|
30
|
+
function collect(expr, polarity, acc) {
|
|
31
|
+
if (expr instanceof TirParentesizedExpr) {
|
|
32
|
+
collect(expr.expr, polarity, acc);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (expr instanceof TirUnaryExclamation) {
|
|
36
|
+
collect(expr.operand, !polarity, acc);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const ctorName = expr?.constructor?.name;
|
|
40
|
+
if (ctorName === "TirLogicalAndExpr") {
|
|
41
|
+
if (polarity) {
|
|
42
|
+
collect(expr.left, true, acc);
|
|
43
|
+
collect(expr.right, true, acc);
|
|
44
|
+
}
|
|
45
|
+
// FALSE side of `&&` tells us nothing
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (ctorName === "TirLogicalOrExpr") {
|
|
49
|
+
if (!polarity) {
|
|
50
|
+
collect(expr.left, false, acc);
|
|
51
|
+
collect(expr.right, false, acc);
|
|
52
|
+
}
|
|
53
|
+
// TRUE side of `||` tells us nothing
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (expr instanceof TirIsExpr) {
|
|
57
|
+
const inst = expr.instanceExpr;
|
|
58
|
+
if (!(inst instanceof TirVariableAccessExpr))
|
|
59
|
+
return;
|
|
60
|
+
const parentStructType = getStructType(inst.type);
|
|
61
|
+
if (!parentStructType)
|
|
62
|
+
return;
|
|
63
|
+
if (polarity) {
|
|
64
|
+
acc.push({
|
|
65
|
+
varName: inst.varName,
|
|
66
|
+
parentCtorIdxs: [expr.parentCtorIdx],
|
|
67
|
+
parentStructType
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// narrow to all OTHER parent constructor indexes still possible
|
|
72
|
+
const baseIdxs = parentStructType.narrowedFromParentCtorIdxs
|
|
73
|
+
?? parentStructType.constructors.map((_, i) => i);
|
|
74
|
+
const remaining = baseIdxs.filter(idx => idx !== expr.parentCtorIdx);
|
|
75
|
+
if (remaining.length === 0)
|
|
76
|
+
return; // narrowed away entirely; type unchanged
|
|
77
|
+
acc.push({
|
|
78
|
+
varName: inst.varName,
|
|
79
|
+
parentCtorIdxs: remaining,
|
|
80
|
+
parentStructType
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Merge multiple narrowings: per variable, take the intersection of
|
|
88
|
+
* possible parent constructor indexes (most restrictive).
|
|
89
|
+
*/
|
|
90
|
+
function mergeIntersect(narrowings) {
|
|
91
|
+
const byVar = new Map();
|
|
92
|
+
for (const n of narrowings) {
|
|
93
|
+
const existing = byVar.get(n.varName);
|
|
94
|
+
if (!existing) {
|
|
95
|
+
byVar.set(n.varName, { ...n, parentCtorIdxs: [...n.parentCtorIdxs] });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
existing.parentCtorIdxs = existing.parentCtorIdxs.filter(idx => n.parentCtorIdxs.includes(idx));
|
|
99
|
+
}
|
|
100
|
+
return [...byVar.values()].filter(n => n.parentCtorIdxs.length > 0);
|
|
101
|
+
}
|
|
102
|
+
export function applyNarrowingsToScope(scope, narrowings) {
|
|
103
|
+
for (const n of narrowings) {
|
|
104
|
+
if (!(n.parentStructType instanceof TirDataStructType)
|
|
105
|
+
&& !(n.parentStructType instanceof TirSoPStructType))
|
|
106
|
+
continue;
|
|
107
|
+
const narrowed = n.parentStructType.narrowTo(n.parentCtorIdxs);
|
|
108
|
+
scope.narrowVariable(n.varName, narrowed);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { compileUPLC, parseUPLC, UPLCProgram } from "@harmoniclabs/uplc";
|
|
1
|
+
import { compileUPLC, Force, parseUPLC, UPLCProgram } from "@harmoniclabs/uplc";
|
|
2
2
|
import { Machine } from "@harmoniclabs/plutus-machine";
|
|
3
3
|
import { DiagnosticEmitter } from "../diagnostics/DiagnosticEmitter.js";
|
|
4
4
|
import { defaultOptions } from "../IR/toUPLC/CompilerOptions.js";
|
|
@@ -98,7 +98,9 @@ export class Compiler extends DiagnosticEmitter {
|
|
|
98
98
|
}
|
|
99
99
|
const serialized = this._compileBackend(cfg, program);
|
|
100
100
|
const uplcProgram = parseUPLC(serialized);
|
|
101
|
-
|
|
101
|
+
// the run-wrapper is always a 0-arg function, which now
|
|
102
|
+
// compiles to `Delay(body)`; force it so the body executes.
|
|
103
|
+
return Machine.eval(new Force(uplcProgram.body));
|
|
102
104
|
}
|
|
103
105
|
async runRepl(config) {
|
|
104
106
|
const cfg = {
|
|
@@ -120,7 +122,9 @@ export class Compiler extends DiagnosticEmitter {
|
|
|
120
122
|
}
|
|
121
123
|
const serialized = this._compileBackend(cfg, program);
|
|
122
124
|
const uplcProgram = parseUPLC(serialized);
|
|
123
|
-
|
|
125
|
+
// the repl-wrapper is always a 0-arg function, which now
|
|
126
|
+
// compiles to `Delay(body)`; force it so the body executes.
|
|
127
|
+
return Machine.eval(new Force(uplcProgram.body));
|
|
124
128
|
}
|
|
125
129
|
_compileBackend(cfg, program) {
|
|
126
130
|
// backend starts here
|
|
@@ -15,6 +15,7 @@ import { TirLitVoidExpr } from "../../tir/expressions/litteral/TirLitVoidExpr.js
|
|
|
15
15
|
import { TirAssertAndContinueExpr } from "../../tir/expressions/TirAssertAndContinueExpr.js";
|
|
16
16
|
import { TirCallExpr } from "../../tir/expressions/TirCallExpr.js";
|
|
17
17
|
import { TirCaseExpr, TirCaseMatcher, TirWildcardCaseMatcher } from "../../tir/expressions/TirCaseExpr.js";
|
|
18
|
+
import { TirIsExpr } from "../../tir/expressions/TirIsExpr.js";
|
|
18
19
|
import { TirElemAccessExpr } from "../../tir/expressions/TirElemAccessExpr.js";
|
|
19
20
|
import { TirFailExpr } from "../../tir/expressions/TirFailExpr.js";
|
|
20
21
|
import { TirFromDataExpr } from "../../tir/expressions/TirFromDataExpr.js";
|
|
@@ -182,6 +183,10 @@ export function expressifyVars(ctx, expr) {
|
|
|
182
183
|
expr.ifFalse = ifFalse;
|
|
183
184
|
return expr;
|
|
184
185
|
}
|
|
186
|
+
if (expr instanceof TirIsExpr) {
|
|
187
|
+
expr.instanceExpr = expressifyVars(ctx, expr.instanceExpr);
|
|
188
|
+
return expr;
|
|
189
|
+
}
|
|
185
190
|
if (isTirBinaryExpr(expr)) {
|
|
186
191
|
const left = expressifyVars(ctx, expr.left);
|
|
187
192
|
const right = expressifyVars(ctx, expr.right);
|
|
@@ -260,6 +265,7 @@ function expressifyPropAccess(ctx, propAccessExpr) {
|
|
|
260
265
|
|| expr instanceof TirPropAccessExpr // `expressifyVars` is called recursively on the object before this check
|
|
261
266
|
// these result to native types
|
|
262
267
|
|| expr instanceof TirUnaryExclamation // boolean
|
|
268
|
+
|| expr instanceof TirIsExpr // boolean
|
|
263
269
|
|| expr instanceof TirUnaryPlus // int
|
|
264
270
|
|| expr instanceof TirUnaryMinus // int
|
|
265
271
|
|| expr instanceof TirUnaryTilde // bytes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _ir_apps } from "../../../IR/IRNodes/IRApp.js";
|
|
2
|
+
import { IRForced } from "../../../IR/IRNodes/IRForced.js";
|
|
2
3
|
import { mergeSortedStrArrInplace } from "../../../utils/array/mergeSortedStrArrInplace.js";
|
|
3
4
|
export class TirCallExpr {
|
|
4
5
|
func;
|
|
@@ -38,6 +39,9 @@ export class TirCallExpr {
|
|
|
38
39
|
}
|
|
39
40
|
get isConstant() { return false; }
|
|
40
41
|
toIR(ctx) {
|
|
42
|
+
if (this.args.length === 0) {
|
|
43
|
+
return new IRForced(this.func.toIR(ctx));
|
|
44
|
+
}
|
|
41
45
|
return _ir_apps(this.func.toIR(ctx), ...this.args.map(arg => arg.toIR(ctx)));
|
|
42
46
|
}
|
|
43
47
|
}
|
|
@@ -112,7 +112,14 @@ export class TirCaseExpr {
|
|
|
112
112
|
}
|
|
113
113
|
// TirSopStructType
|
|
114
114
|
const wildcardBodyIR = this.wildcardCase?.body.toIR(ctx) ?? new IRError();
|
|
115
|
-
|
|
115
|
+
// For narrowed SoP types, `matchExprType.constructors` may be a strict
|
|
116
|
+
// subset of the runtime universe. The IRCase branch array must be
|
|
117
|
+
// indexed by runtime (parent) ctor index, so we build it sized to the
|
|
118
|
+
// largest parent index we know about and fill missing slots with the
|
|
119
|
+
// wildcard / IRError.
|
|
120
|
+
const sopType = matchExprType instanceof TirSopOptT ? undefined : matchExprType;
|
|
121
|
+
const sopParentIdxs = sopType?.narrowedFromParentCtorIdxs;
|
|
122
|
+
const localBranches = matchExprType.constructors.map((ctor, ctorIdx) => {
|
|
116
123
|
const nFields = ctor.fields.length;
|
|
117
124
|
const branchCtx = ctx.newChild();
|
|
118
125
|
const branch = this.cases.find(c => c.pattern.constrName === ctor.name);
|
|
@@ -142,6 +149,21 @@ export class TirCaseExpr {
|
|
|
142
149
|
return branch.body.toIR(branchCtx);
|
|
143
150
|
return new IRFunc(introducedVars, branch.body.toIR(branchCtx));
|
|
144
151
|
});
|
|
152
|
+
// Map local branches into runtime-indexed slots. For un-narrowed types
|
|
153
|
+
// this is identity.
|
|
154
|
+
let branches;
|
|
155
|
+
if (sopParentIdxs) {
|
|
156
|
+
const maxParent = sopParentIdxs.reduce((m, x) => x > m ? x : m, -1);
|
|
157
|
+
branches = new Array(maxParent + 1);
|
|
158
|
+
for (let i = 0; i < branches.length; i++)
|
|
159
|
+
branches[i] = wildcardBodyIR;
|
|
160
|
+
for (let localIdx = 0; localIdx < localBranches.length; localIdx++) {
|
|
161
|
+
branches[sopParentIdxs[localIdx]] = localBranches[localIdx];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
branches = localBranches;
|
|
166
|
+
}
|
|
145
167
|
// branches at the end that are supposed to "just fail"
|
|
146
168
|
// can be omitted, as the CEK machine will fail if no branch for
|
|
147
169
|
// a given constructor is found
|
|
@@ -202,9 +224,12 @@ export class TirCaseExpr {
|
|
|
202
224
|
.filter(fName => pattern.fields.has(fName)));
|
|
203
225
|
if (usedFieldsCtorNames.length <= 0) {
|
|
204
226
|
ifThenElseMatchingStatements = _ir_lazyIfThenElse(
|
|
205
|
-
// condition
|
|
227
|
+
// condition (compare against the PARENT ctor index — the
|
|
228
|
+
// value's runtime tag is unaffected by narrowing)
|
|
206
229
|
_ir_apps(new IRVar(isConstrIdxSym), // isConstrIdx
|
|
207
|
-
IRConst.int(
|
|
230
|
+
IRConst.int(matchExprType instanceof TirDataStructType
|
|
231
|
+
? matchExprType.parentCtorIdx(ctorIdx)
|
|
232
|
+
: ctorIdx)),
|
|
208
233
|
// then
|
|
209
234
|
body.toIR(stmtCtx),
|
|
210
235
|
// else
|
|
@@ -229,9 +254,12 @@ export class TirCaseExpr {
|
|
|
229
254
|
IRNative.sndPair, new IRVar(unConstrStructSym) // unconstrStruct
|
|
230
255
|
)))), body.toIR(thenCtx));
|
|
231
256
|
ifThenElseMatchingStatements = _ir_lazyIfThenElse(
|
|
232
|
-
// condition
|
|
257
|
+
// condition (compare against the PARENT ctor index — the
|
|
258
|
+
// value's runtime tag is unaffected by narrowing)
|
|
233
259
|
_ir_apps(new IRVar(isConstrIdxSym), // isConstrIdx
|
|
234
|
-
IRConst.int(
|
|
260
|
+
IRConst.int(matchExprType instanceof TirDataStructType
|
|
261
|
+
? matchExprType.parentCtorIdx(ctorIdx)
|
|
262
|
+
: ctorIdx)),
|
|
235
263
|
// then
|
|
236
264
|
thenCase,
|
|
237
265
|
// else
|
|
@@ -257,9 +285,12 @@ export class TirCaseExpr {
|
|
|
257
285
|
), // fileds as data list
|
|
258
286
|
_ir_apps(new IRFunc(introducedVars, body.toIR(thenCtx)), ...extractedFields));
|
|
259
287
|
ifThenElseMatchingStatements = _ir_lazyIfThenElse(
|
|
260
|
-
// condition
|
|
288
|
+
// condition (compare against the PARENT ctor index — the
|
|
289
|
+
// value's runtime tag is unaffected by narrowing)
|
|
261
290
|
_ir_apps(new IRVar(isConstrIdxSym), // isConstrIdx
|
|
262
|
-
IRConst.int(
|
|
291
|
+
IRConst.int(matchExprType instanceof TirDataStructType
|
|
292
|
+
? matchExprType.parentCtorIdx(ctorIdx)
|
|
293
|
+
: ctorIdx)),
|
|
263
294
|
// then
|
|
264
295
|
thenCase,
|
|
265
296
|
// else
|
|
@@ -20,5 +20,6 @@ import { TirTraceIfFalseExpr } from "./TirTraceIfFalseExpr.js";
|
|
|
20
20
|
import { TirTraceExpr } from "./TirTraceExpr.js";
|
|
21
21
|
import { TirNativeFunc } from "./TirNativeFunc.js";
|
|
22
22
|
import { TirInlineClosedIR } from "./TirInlineClosedIR.js";
|
|
23
|
-
|
|
23
|
+
import { TirIsExpr } from "./TirIsExpr.js";
|
|
24
|
+
export type TirExpr = (TirUnaryPrefixExpr | TirLitteralExpr | TirParentesizedExpr | TirFuncExpr | TirCallExpr | TirCaseExpr | TirTypeConversionExpr | TirIsExpr | TirElemAccessExpr | TirTernaryExpr | TirPropAccessExpr | TirBinaryExpr | TirVariableAccessExpr | TirLettedExpr | TirNativeFunc | TirFailExpr | TirHoistedExpr | TirFromDataExpr | TirToDataExpr | TirAssertAndContinueExpr | TirTraceIfFalseExpr | TirTraceExpr | TirInlineClosedIR);
|
|
24
25
|
export declare function isTirExpr(thing: any): thing is TirExpr;
|
|
@@ -21,6 +21,7 @@ import { TirTraceIfFalseExpr } from "./TirTraceIfFalseExpr.js";
|
|
|
21
21
|
import { TirTraceExpr } from "./TirTraceExpr.js";
|
|
22
22
|
import { TirNativeFunc } from "./TirNativeFunc.js";
|
|
23
23
|
import { TirInlineClosedIR } from "./TirInlineClosedIR.js";
|
|
24
|
+
import { TirIsExpr } from "./TirIsExpr.js";
|
|
24
25
|
export function isTirExpr(thing) {
|
|
25
26
|
return isObject(thing) && (isTirUnaryPrefixExpr(thing)
|
|
26
27
|
|| isTirLitteralExpr(thing)
|
|
@@ -29,7 +30,7 @@ export function isTirExpr(thing) {
|
|
|
29
30
|
|| thing instanceof TirCallExpr
|
|
30
31
|
|| thing instanceof TirCaseExpr
|
|
31
32
|
|| thing instanceof TirTypeConversionExpr
|
|
32
|
-
|
|
33
|
+
|| thing instanceof TirIsExpr
|
|
33
34
|
|| thing instanceof TirElemAccessExpr
|
|
34
35
|
|| thing instanceof TirTernaryExpr
|
|
35
36
|
|| thing instanceof TirPropAccessExpr
|
|
@@ -4,6 +4,7 @@ import { IRRecursive } from "../../../IR/IRNodes/IRRecursive.js";
|
|
|
4
4
|
import { TirReturnStmt } from "../statements/TirReturnStmt.js";
|
|
5
5
|
import { TirFuncT } from "../types/TirNativeType/native/function.js";
|
|
6
6
|
import { IRFunc } from "../../../IR/IRNodes/IRFunc.js";
|
|
7
|
+
import { IRDelayed } from "../../../IR/IRNodes/IRDelayed.js";
|
|
7
8
|
export class TirFuncExpr {
|
|
8
9
|
name;
|
|
9
10
|
params;
|
|
@@ -101,7 +102,9 @@ export class TirFuncExpr {
|
|
|
101
102
|
if (isRecursive)
|
|
102
103
|
recursiveVarSym = ctx.defineRecursiveVar(this.name);
|
|
103
104
|
const introuducedVars = this.params.map(param => ctx.defineVar(param.name));
|
|
104
|
-
let irFunc =
|
|
105
|
+
let irFunc = this.params.length === 0
|
|
106
|
+
? new IRDelayed(expr.toIR(ctx))
|
|
107
|
+
: new IRFunc(introuducedVars, expr.toIR(ctx));
|
|
105
108
|
if (isRecursive)
|
|
106
109
|
irFunc = new IRRecursive(recursiveVarSym, irFunc);
|
|
107
110
|
return irFunc;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SourceRange } from "../../../ast/Source/SourceRange.js";
|
|
2
|
+
import type { IRTerm } from "../../../IR/IRTerm.js";
|
|
3
|
+
import { TirBoolT } from "../types/TirNativeType/native/bool.js";
|
|
4
|
+
import { ITirExpr } from "./ITirExpr.js";
|
|
5
|
+
import { TirExpr } from "./TirExpr.js";
|
|
6
|
+
import { ToIRTermCtx } from "./ToIRTermCtx.js";
|
|
7
|
+
export declare class TirIsExpr implements ITirExpr {
|
|
8
|
+
instanceExpr: TirExpr;
|
|
9
|
+
readonly ctorName: string;
|
|
10
|
+
/** index in the *parent* (un-narrowed) struct's `constructors` array */
|
|
11
|
+
readonly parentCtorIdx: number;
|
|
12
|
+
readonly range: SourceRange;
|
|
13
|
+
readonly type: TirBoolT;
|
|
14
|
+
constructor(instanceExpr: TirExpr, ctorName: string,
|
|
15
|
+
/** index in the *parent* (un-narrowed) struct's `constructors` array */
|
|
16
|
+
parentCtorIdx: number, range: SourceRange, boolType: TirBoolT);
|
|
17
|
+
toString(): string;
|
|
18
|
+
pretty(indent: number): string;
|
|
19
|
+
clone(): TirIsExpr;
|
|
20
|
+
deps(): string[];
|
|
21
|
+
get isConstant(): boolean;
|
|
22
|
+
toIR(ctx: ToIRTermCtx): IRTerm;
|
|
23
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { _ir_apps } from "../../../IR/IRNodes/IRApp.js";
|
|
2
|
+
import { IRCase } from "../../../IR/IRNodes/IRCase.js";
|
|
3
|
+
import { IRConst } from "../../../IR/IRNodes/IRConst.js";
|
|
4
|
+
import { IRFunc } from "../../../IR/IRNodes/IRFunc.js";
|
|
5
|
+
import { IRNative } from "../../../IR/IRNodes/IRNative/index.js";
|
|
6
|
+
import { TirDataStructType, TirSoPStructType } from "../types/TirStructType.js";
|
|
7
|
+
import { getUnaliased } from "../types/utils/getUnaliased.js";
|
|
8
|
+
export class TirIsExpr {
|
|
9
|
+
instanceExpr;
|
|
10
|
+
ctorName;
|
|
11
|
+
parentCtorIdx;
|
|
12
|
+
range;
|
|
13
|
+
type;
|
|
14
|
+
constructor(instanceExpr, ctorName,
|
|
15
|
+
/** index in the *parent* (un-narrowed) struct's `constructors` array */
|
|
16
|
+
parentCtorIdx, range, boolType) {
|
|
17
|
+
this.instanceExpr = instanceExpr;
|
|
18
|
+
this.ctorName = ctorName;
|
|
19
|
+
this.parentCtorIdx = parentCtorIdx;
|
|
20
|
+
this.range = range;
|
|
21
|
+
this.type = boolType;
|
|
22
|
+
}
|
|
23
|
+
toString() {
|
|
24
|
+
return `(${this.instanceExpr.toString()} is ${this.ctorName})`;
|
|
25
|
+
}
|
|
26
|
+
pretty(indent) {
|
|
27
|
+
return `(${this.instanceExpr.pretty(indent)} is ${this.ctorName})`;
|
|
28
|
+
}
|
|
29
|
+
clone() {
|
|
30
|
+
return new TirIsExpr(this.instanceExpr.clone(), this.ctorName, this.parentCtorIdx, this.range.clone(), this.type.clone());
|
|
31
|
+
}
|
|
32
|
+
deps() {
|
|
33
|
+
return this.instanceExpr.deps();
|
|
34
|
+
}
|
|
35
|
+
get isConstant() { return this.instanceExpr.isConstant; }
|
|
36
|
+
toIR(ctx) {
|
|
37
|
+
const structType = getUnaliased(this.instanceExpr.type);
|
|
38
|
+
if (structType instanceof TirDataStructType) {
|
|
39
|
+
// equalsInteger(parentCtorIdx, fstPair(unConstrData(<instance>)))
|
|
40
|
+
return _ir_apps(IRNative.equalsInteger, IRConst.int(this.parentCtorIdx), _ir_apps(IRNative.fstPair, _ir_apps(IRNative.unConstrData, this.instanceExpr.toIR(ctx))));
|
|
41
|
+
}
|
|
42
|
+
if (structType instanceof TirSoPStructType) {
|
|
43
|
+
// we need a branch in the IRCase per ORIGINAL parent constructor.
|
|
44
|
+
// the type system has narrowed `instanceExpr.type`, but at runtime
|
|
45
|
+
// the value is still a Constr from the parent universe; if the
|
|
46
|
+
// current type is narrowed, we use its parent-idx mapping.
|
|
47
|
+
const localToParent = structType.narrowedFromParentCtorIdxs
|
|
48
|
+
?? structType.constructors.map((_, i) => i);
|
|
49
|
+
// We need to size the IRCase to the maximum parent index we know about.
|
|
50
|
+
// For pure narrowing of an SoP value at runtime, we still see ALL
|
|
51
|
+
// parent constructors. Without access to the un-narrowed type here,
|
|
52
|
+
// we trust the constructor list reflects the runtime layout when
|
|
53
|
+
// `structType` is un-narrowed; when narrowed we still ALSO need to
|
|
54
|
+
// produce a complete IRCase for the underlying value.
|
|
55
|
+
//
|
|
56
|
+
// For the un-narrowed case (structType.constructors enumerates all
|
|
57
|
+
// runtime variants) this is correct. For a narrowed SoP (where the
|
|
58
|
+
// local constructors array is smaller than the runtime arity), we
|
|
59
|
+
// must emit a branch for every original parent slot up to `max + 1`
|
|
60
|
+
// and treat anything outside the narrowed set as "false".
|
|
61
|
+
const maxParentIdx = localToParent.reduce((m, x) => x > m ? x : m, -1);
|
|
62
|
+
const branchCount = maxParentIdx + 1;
|
|
63
|
+
const branches = new Array(branchCount);
|
|
64
|
+
for (let parentIdx = 0; parentIdx < branchCount; parentIdx++) {
|
|
65
|
+
// find the local constructor for this parent index, if any
|
|
66
|
+
const localIdx = localToParent.indexOf(parentIdx);
|
|
67
|
+
const matches = parentIdx === this.parentCtorIdx;
|
|
68
|
+
const body = IRConst.bool(matches);
|
|
69
|
+
// we don't know the field arity for parent slots that have
|
|
70
|
+
// been narrowed away. Use the local ctor if present;
|
|
71
|
+
// otherwise no params (best-effort: narrowed-away slots
|
|
72
|
+
// shouldn't be reachable at runtime in well-typed code).
|
|
73
|
+
const fields = localIdx >= 0 ? structType.constructors[localIdx].fields.length : 0;
|
|
74
|
+
if (fields <= 0) {
|
|
75
|
+
branches[parentIdx] = body;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const introduced = Array(fields).fill(0).map(() => Symbol("_"));
|
|
79
|
+
branches[parentIdx] = new IRFunc(introduced, body);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return new IRCase(this.instanceExpr.toIR(ctx), branches);
|
|
83
|
+
}
|
|
84
|
+
throw new Error("`is` operator can only be applied to struct types; got: "
|
|
85
|
+
+ this.instanceExpr.type.toString());
|
|
86
|
+
}
|
|
87
|
+
}
|