stone-lang 0.1.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 +52 -0
- package/StoneEngine.js +879 -0
- package/StoneEngineService.js +1727 -0
- package/adapters/FileSystemAdapter.js +230 -0
- package/adapters/OutputAdapter.js +208 -0
- package/adapters/index.js +6 -0
- package/cli/CLIOutputAdapter.js +196 -0
- package/cli/DaemonClient.js +349 -0
- package/cli/JSONOutputAdapter.js +135 -0
- package/cli/ReplSession.js +567 -0
- package/cli/ViewerServer.js +590 -0
- package/cli/commands/check.js +84 -0
- package/cli/commands/daemon.js +189 -0
- package/cli/commands/kill.js +66 -0
- package/cli/commands/package.js +713 -0
- package/cli/commands/ps.js +65 -0
- package/cli/commands/run.js +537 -0
- package/cli/entry.js +169 -0
- package/cli/index.js +14 -0
- package/cli/stonec.js +358 -0
- package/cli/test-compiler.js +181 -0
- package/cli/viewer/index.html +495 -0
- package/daemon/IPCServer.js +455 -0
- package/daemon/ProcessManager.js +327 -0
- package/daemon/ProcessRunner.js +307 -0
- package/daemon/daemon.js +398 -0
- package/daemon/index.js +16 -0
- package/frontend/analysis/index.js +5 -0
- package/frontend/analysis/livenessAnalyzer.js +568 -0
- package/frontend/analysis/treeShaker.js +265 -0
- package/frontend/index.js +20 -0
- package/frontend/parsing/astBuilder.js +2196 -0
- package/frontend/parsing/index.js +7 -0
- package/frontend/parsing/sonParser.js +592 -0
- package/frontend/parsing/stoneAstTypes.js +703 -0
- package/frontend/parsing/terminal-registry.js +435 -0
- package/frontend/parsing/tokenizer.js +692 -0
- package/frontend/type-checker/OverloadedFunctionType.js +43 -0
- package/frontend/type-checker/TypeEnvironment.js +165 -0
- package/frontend/type-checker/bidirectionalInference.js +149 -0
- package/frontend/type-checker/index.js +10 -0
- package/frontend/type-checker/moduleAnalysis.js +248 -0
- package/frontend/type-checker/operatorMappings.js +35 -0
- package/frontend/type-checker/overloadResolution.js +605 -0
- package/frontend/type-checker/typeChecker.js +452 -0
- package/frontend/type-checker/typeCompatibility.js +389 -0
- package/frontend/type-checker/visitors/controlFlow.js +483 -0
- package/frontend/type-checker/visitors/functions.js +604 -0
- package/frontend/type-checker/visitors/index.js +38 -0
- package/frontend/type-checker/visitors/literals.js +341 -0
- package/frontend/type-checker/visitors/modules.js +159 -0
- package/frontend/type-checker/visitors/operators.js +109 -0
- package/frontend/type-checker/visitors/statements.js +768 -0
- package/frontend/types/index.js +5 -0
- package/frontend/types/operatorMap.js +134 -0
- package/frontend/types/types.js +2046 -0
- package/frontend/utils/errorCollector.js +244 -0
- package/frontend/utils/index.js +5 -0
- package/frontend/utils/moduleResolver.js +479 -0
- package/package.json +50 -0
- package/packages/browserCache.js +359 -0
- package/packages/fetcher.js +236 -0
- package/packages/index.js +130 -0
- package/packages/lockfile.js +271 -0
- package/packages/manifest.js +291 -0
- package/packages/packageResolver.js +356 -0
- package/packages/resolver.js +310 -0
- package/packages/semver.js +635 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OverloadedFunctionType - represents overloaded functions with multiple signatures
|
|
3
|
+
*
|
|
4
|
+
* Used for primitives that can be called with different argument types,
|
|
5
|
+
* and also for user-defined function overloads.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents an overloaded function with multiple signatures.
|
|
10
|
+
* Used for primitives that can be called with different argument types,
|
|
11
|
+
* and now also for user-defined function overloads.
|
|
12
|
+
*/
|
|
13
|
+
export class OverloadedFunctionType {
|
|
14
|
+
constructor(name, overloads = [], userOverloads = []) {
|
|
15
|
+
this.name = name;
|
|
16
|
+
this.overloads = overloads; // Array of primitive functions with .stone signatures
|
|
17
|
+
this.userOverloads = userOverloads; // Array of { funcType, node, overloadId } for user-defined overloads
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Add a user-defined function overload
|
|
22
|
+
* @param {FunctionType} funcType - The function's type signature
|
|
23
|
+
* @param {Object} node - The AST node of the function definition
|
|
24
|
+
*/
|
|
25
|
+
addUserOverload(funcType, node) {
|
|
26
|
+
// Use already-set overloadId from node, or from funcType's stored value
|
|
27
|
+
const overloadId = node.overloadId || funcType._overloadId || `${this.name}_${this.userOverloads.length}`;
|
|
28
|
+
if (!node.overloadId) {
|
|
29
|
+
node.overloadId = overloadId; // Store on AST for executor registration
|
|
30
|
+
}
|
|
31
|
+
this.userOverloads.push({ funcType, node, overloadId });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
toString() {
|
|
35
|
+
const primSigs = this.overloads
|
|
36
|
+
.filter(fn => fn.stone)
|
|
37
|
+
.map(fn => `(${fn.stone.in.join(", ")}) -> ${fn.stone.out}`);
|
|
38
|
+
const userSigs = this.userOverloads
|
|
39
|
+
.map(({ funcType }) => funcType.toString());
|
|
40
|
+
const allSigs = [...primSigs, ...userSigs].join(" | ");
|
|
41
|
+
return `overloaded[${this.name}]: ${allSigs}`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Environment - tracks type bindings in scope
|
|
3
|
+
*
|
|
4
|
+
* Manages variable bindings, type aliases, declared types, extensions, and immutability
|
|
5
|
+
* with parent-child hierarchy for nested scopes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extension info - tracks metadata for imported extension methods
|
|
10
|
+
*/
|
|
11
|
+
export class ExtensionInfo {
|
|
12
|
+
constructor(name, selfType, functionType, functionRef = null) {
|
|
13
|
+
this.name = name; // Method name (e.g., "T", "shift")
|
|
14
|
+
this.selfType = selfType; // TypeAnnotation for self parameter
|
|
15
|
+
this.functionType = functionType; // Full function type
|
|
16
|
+
this.functionRef = functionRef; // Reference to compiled function (set later)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Type environment - tracks type bindings in scope
|
|
22
|
+
*/
|
|
23
|
+
export class TypeEnvironment {
|
|
24
|
+
constructor(parent = null) {
|
|
25
|
+
this.bindings = new Map();
|
|
26
|
+
this.typeAliases = new Map();
|
|
27
|
+
this.declaredTypes = new Map(); // Track type annotations for deferred resolution
|
|
28
|
+
this.immutableBindings = new Set(); // Track immutable bindings (cannot be rebound)
|
|
29
|
+
this.extensions = new Map(); // Track imported extension methods: name -> ExtensionInfo
|
|
30
|
+
this.parent = parent;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Define a new binding
|
|
35
|
+
*/
|
|
36
|
+
define(name, type) {
|
|
37
|
+
this.bindings.set(name, type);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Look up a binding
|
|
42
|
+
*/
|
|
43
|
+
lookup(name) {
|
|
44
|
+
if (this.bindings.has(name)) {
|
|
45
|
+
return this.bindings.get(name);
|
|
46
|
+
}
|
|
47
|
+
if (this.parent) {
|
|
48
|
+
return this.parent.lookup(name);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Define a type alias
|
|
55
|
+
*/
|
|
56
|
+
defineTypeAlias(name, type) {
|
|
57
|
+
this.typeAliases.set(name, type);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Look up a type alias
|
|
62
|
+
*/
|
|
63
|
+
lookupTypeAlias(name) {
|
|
64
|
+
if (this.typeAliases.has(name)) {
|
|
65
|
+
return this.typeAliases.get(name);
|
|
66
|
+
}
|
|
67
|
+
if (this.parent) {
|
|
68
|
+
return this.parent.lookupTypeAlias(name);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Define a declared type (from type annotation)
|
|
75
|
+
* Used for deferred type resolution of empty arrays
|
|
76
|
+
*/
|
|
77
|
+
defineDeclared(name, type) {
|
|
78
|
+
this.declaredTypes.set(name, type);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Look up a declared type
|
|
83
|
+
* For primed variables (e.g., arr'), also checks the base name (arr)
|
|
84
|
+
*/
|
|
85
|
+
lookupDeclared(name) {
|
|
86
|
+
if (this.declaredTypes.has(name)) {
|
|
87
|
+
return this.declaredTypes.get(name);
|
|
88
|
+
}
|
|
89
|
+
// For primed variables, check base name (arr' → arr)
|
|
90
|
+
if (name.endsWith("'")) {
|
|
91
|
+
const baseName = name.slice(0, -1);
|
|
92
|
+
if (this.declaredTypes.has(baseName)) {
|
|
93
|
+
return this.declaredTypes.get(baseName);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (this.parent) {
|
|
97
|
+
return this.parent.lookupDeclared(name);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a binding exists
|
|
104
|
+
*/
|
|
105
|
+
has(name) {
|
|
106
|
+
return this.bindings.has(name) || (this.parent && this.parent.has(name));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if a binding exists in the current scope only (not parent)
|
|
111
|
+
*/
|
|
112
|
+
hasInCurrentScope(name) {
|
|
113
|
+
return this.bindings.has(name);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Mark a binding as immutable (cannot be rebound)
|
|
118
|
+
*/
|
|
119
|
+
markImmutable(name) {
|
|
120
|
+
this.immutableBindings.add(name);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if a binding is immutable in the scope chain
|
|
125
|
+
*/
|
|
126
|
+
isImmutable(name) {
|
|
127
|
+
if (this.immutableBindings.has(name)) return true;
|
|
128
|
+
if (this.parent) return this.parent.isImmutable(name);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a child environment
|
|
134
|
+
*/
|
|
135
|
+
createChild() {
|
|
136
|
+
return new TypeEnvironment(this);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Define an extension method
|
|
141
|
+
*/
|
|
142
|
+
defineExtension(name, extensionInfo) {
|
|
143
|
+
this.extensions.set(name, extensionInfo);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Look up an extension method by name
|
|
148
|
+
*/
|
|
149
|
+
lookupExtension(name) {
|
|
150
|
+
if (this.extensions.has(name)) {
|
|
151
|
+
return this.extensions.get(name);
|
|
152
|
+
}
|
|
153
|
+
if (this.parent) {
|
|
154
|
+
return this.parent.lookupExtension(name);
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check if an extension exists in the current scope only (not parent)
|
|
161
|
+
*/
|
|
162
|
+
hasExtensionInCurrentScope(name) {
|
|
163
|
+
return this.extensions.has(name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bidirectional Inference Methods - mixin for TypeChecker
|
|
3
|
+
*
|
|
4
|
+
* Methods for collecting return statements, unifying return types,
|
|
5
|
+
* and back-propagating types to incomplete expressions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
TypeVariable,
|
|
10
|
+
UNIT,
|
|
11
|
+
unify,
|
|
12
|
+
formatType,
|
|
13
|
+
} from "../types/types.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Bidirectional inference methods to be mixed into TypeChecker.prototype
|
|
17
|
+
*/
|
|
18
|
+
export const bidirectionalInferenceMethods = {
|
|
19
|
+
/**
|
|
20
|
+
* Recursively collect all return statements from a function body
|
|
21
|
+
* Uses types already computed during the first pass (stored on node.inferredType)
|
|
22
|
+
* Returns array of { node, type, isIncomplete }
|
|
23
|
+
*/
|
|
24
|
+
collectReturnStatements(body) {
|
|
25
|
+
const returns = [];
|
|
26
|
+
|
|
27
|
+
const collect = (statements) => {
|
|
28
|
+
for (const stmt of statements) {
|
|
29
|
+
if (stmt.type === "ReturnStatement") {
|
|
30
|
+
// Use the type already computed during the first pass
|
|
31
|
+
// Do NOT re-visit, as that may give incorrect results
|
|
32
|
+
const valueType = stmt.inferredType || UNIT;
|
|
33
|
+
const isIncomplete = this.isIncompleteType(valueType);
|
|
34
|
+
returns.push({ node: stmt, type: valueType, isIncomplete });
|
|
35
|
+
} else if (stmt.type === "BranchExpression") {
|
|
36
|
+
// Recurse into branches
|
|
37
|
+
for (const path of stmt.paths) {
|
|
38
|
+
collect(path.body);
|
|
39
|
+
}
|
|
40
|
+
} else if (stmt.type === "LoopExpression") {
|
|
41
|
+
collect(stmt.body);
|
|
42
|
+
} else if (stmt.type === "BlockExpression") {
|
|
43
|
+
collect(stmt.statements || []);
|
|
44
|
+
} else if (stmt.type === "BindingStructure") {
|
|
45
|
+
// Handle block expressions like { if (...) { return x } else { return y } }
|
|
46
|
+
for (const binding of stmt.bindings || []) {
|
|
47
|
+
if (binding.value) {
|
|
48
|
+
collect([binding.value]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (stmt.trailingExpr) {
|
|
52
|
+
collect([stmt.trailingExpr]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Continue visiting other statements (they might contain nested returns)
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
collect(body);
|
|
60
|
+
return returns;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Unify all return types, finding common type or reporting errors
|
|
65
|
+
* Returns { unifiedType, errors }
|
|
66
|
+
*/
|
|
67
|
+
unifyReturnTypes(returnInfos) {
|
|
68
|
+
if (returnInfos.length === 0) {
|
|
69
|
+
return { unifiedType: UNIT, errors: [] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const errors = [];
|
|
73
|
+
|
|
74
|
+
// Separate complete and incomplete types
|
|
75
|
+
// Exclude guard passthrough returns - these return the input unchanged
|
|
76
|
+
// when it's already the correct type, so they shouldn't constrain the return type
|
|
77
|
+
const complete = returnInfos.filter(r => !r.isIncomplete && !r.isGuardPassthrough);
|
|
78
|
+
const incomplete = returnInfos.filter(r => r.isIncomplete && !r.isGuardPassthrough);
|
|
79
|
+
|
|
80
|
+
// If no returns at all
|
|
81
|
+
if (complete.length === 0 && incomplete.length === 0) {
|
|
82
|
+
return { unifiedType: UNIT, errors };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If all returns are incomplete, try to unify them
|
|
86
|
+
// This allows polymorphic functions like reverse(arr) that return types dependent on parameters
|
|
87
|
+
if (complete.length === 0) {
|
|
88
|
+
// Try to unify all incomplete types - if they conflict, error
|
|
89
|
+
let unifiedIncomplete = incomplete[0].type;
|
|
90
|
+
for (let i = 1; i < incomplete.length; i++) {
|
|
91
|
+
const result = unify(unifiedIncomplete, incomplete[i].type);
|
|
92
|
+
if (!result.success) {
|
|
93
|
+
errors.push({
|
|
94
|
+
message: `Return type mismatch: ${formatType(this.toStructuredType(unifiedIncomplete))} vs ${formatType(this.toStructuredType(incomplete[i].type))}`,
|
|
95
|
+
location: incomplete[i].node.location
|
|
96
|
+
});
|
|
97
|
+
} else if (result.unifiedType) {
|
|
98
|
+
// Use the unified type from the result if provided (e.g., LiteralUnionType)
|
|
99
|
+
unifiedIncomplete = result.unifiedType;
|
|
100
|
+
} else if (unifiedIncomplete instanceof TypeVariable) {
|
|
101
|
+
unifiedIncomplete = unifiedIncomplete.resolve();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { unifiedType: unifiedIncomplete, errors };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Unify all complete types
|
|
108
|
+
let unifiedType = complete[0].type;
|
|
109
|
+
for (let i = 1; i < complete.length; i++) {
|
|
110
|
+
const result = unify(unifiedType, complete[i].type);
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
errors.push({
|
|
113
|
+
message: `Return type mismatch: ${formatType(unifiedType)} vs ${formatType(complete[i].type)}`,
|
|
114
|
+
location: complete[i].node.location
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
// Use the unified type from the result if provided (e.g., LiteralUnionType from merging literals)
|
|
118
|
+
if (result.unifiedType) {
|
|
119
|
+
unifiedType = result.unifiedType;
|
|
120
|
+
} else if (unifiedType instanceof TypeVariable) {
|
|
121
|
+
// Resolve if it's a TypeVariable
|
|
122
|
+
unifiedType = unifiedType.resolve();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { unifiedType, errors };
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Back-propagate unified type to incomplete return expressions
|
|
132
|
+
*/
|
|
133
|
+
resolveIncompleteTypes(returnInfos, unifiedType) {
|
|
134
|
+
for (const { node, type, isIncomplete, isGuardPassthrough } of returnInfos) {
|
|
135
|
+
// Skip guard passthrough returns - they return input unchanged
|
|
136
|
+
// and shouldn't be constrained to the converted output type
|
|
137
|
+
if (isIncomplete && !isGuardPassthrough) {
|
|
138
|
+
// Unify the incomplete type with the resolved type
|
|
139
|
+
unify(type, unifiedType);
|
|
140
|
+
|
|
141
|
+
// Update the AST node's inferred type
|
|
142
|
+
node.inferredType = unifiedType;
|
|
143
|
+
if (node.value) {
|
|
144
|
+
node.value.inferredType = unifiedType;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend Type Checker - Barrel exports
|
|
3
|
+
*/
|
|
4
|
+
export { TypeChecker, OverloadedFunctionType } from './typeChecker.js';
|
|
5
|
+
export { TypeEnvironment, ExtensionInfo } from './TypeEnvironment.js';
|
|
6
|
+
export { typeCompatibilityMethods } from './typeCompatibility.js';
|
|
7
|
+
export { bidirectionalInferenceMethods } from './bidirectionalInference.js';
|
|
8
|
+
export { overloadResolutionMethods } from './overloadResolution.js';
|
|
9
|
+
export { moduleAnalysisMethods } from './moduleAnalysis.js';
|
|
10
|
+
export { BINARY_OP_NAMES, UNARY_OP_NAMES } from './operatorMappings.js';
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Analysis Methods - mixin for TypeChecker
|
|
3
|
+
*
|
|
4
|
+
* Methods for analyzing module imports and exports to determine types
|
|
5
|
+
* for static type checking.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
FunctionType,
|
|
10
|
+
RecordType,
|
|
11
|
+
freshTypeVar,
|
|
12
|
+
} from "../types/types.js";
|
|
13
|
+
import { TypeEnvironment } from "./TypeEnvironment.js";
|
|
14
|
+
import { OverloadedFunctionType } from "./OverloadedFunctionType.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Module analysis methods to be mixed into TypeChecker.prototype
|
|
18
|
+
*/
|
|
19
|
+
export const moduleAnalysisMethods = {
|
|
20
|
+
/**
|
|
21
|
+
* Get export types for a module.
|
|
22
|
+
* Returns a Map<exportName, Type> for all exports in the module.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} modulePath - The module path (e.g., "primitives", "math", "array")
|
|
25
|
+
* @returns {Map<string, Object>|null} Map of export names to their types
|
|
26
|
+
*/
|
|
27
|
+
getModuleExportTypes(modulePath) {
|
|
28
|
+
// Check cache first
|
|
29
|
+
if (this.moduleTypeCache.has(modulePath)) {
|
|
30
|
+
return this.moduleTypeCache.get(modulePath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Handle 'primitives' module specially - these are JS-wrapped overloaded functions
|
|
34
|
+
if (modulePath === "primitives") {
|
|
35
|
+
const exports = this.analyzePrimitivesModule();
|
|
36
|
+
this.moduleTypeCache.set(modulePath, exports);
|
|
37
|
+
return exports;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Handle 'plots' module - runtime terminal constructors
|
|
41
|
+
if (modulePath === "plots") {
|
|
42
|
+
const exports = this.analyzePlotsModule();
|
|
43
|
+
this.moduleTypeCache.set(modulePath, exports);
|
|
44
|
+
return exports;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// For .stn modules, we need the moduleResolver and parser
|
|
48
|
+
if (!this.moduleResolver || !this.parser) {
|
|
49
|
+
return null; // Can't analyze without these - fall back to type variables
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if it's a .stn builtin module
|
|
53
|
+
if (this.moduleResolver.isStnBuiltinModule(modulePath)) {
|
|
54
|
+
const exports = this.analyzeStnModule(modulePath);
|
|
55
|
+
if (exports) {
|
|
56
|
+
this.moduleTypeCache.set(modulePath, exports);
|
|
57
|
+
}
|
|
58
|
+
return exports;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// For file-based modules, we'd need async loading which isn't supported
|
|
62
|
+
// in the synchronous type checker. Return null to fall back to type variables.
|
|
63
|
+
return null;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Analyze the primitives module and extract types for all exports.
|
|
68
|
+
* Primitives are overloaded functions from the OVERLOADS registry.
|
|
69
|
+
*/
|
|
70
|
+
analyzePrimitivesModule() {
|
|
71
|
+
const exports = new Map();
|
|
72
|
+
|
|
73
|
+
// Each overload set in OVERLOADS is an exportable primitive function
|
|
74
|
+
for (const [name, overloadSet] of Object.entries(this.overloads)) {
|
|
75
|
+
if (overloadSet && overloadSet.length > 0) {
|
|
76
|
+
// Create an OverloadedFunctionType for functions with multiple signatures
|
|
77
|
+
exports.set(name, new OverloadedFunctionType(name, overloadSet));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return exports;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Analyze the plots module and extract types for terminal constructors.
|
|
86
|
+
* Exports graph2d and graph3d functions.
|
|
87
|
+
*
|
|
88
|
+
* Terminal constructors take a config record and return a terminal handle.
|
|
89
|
+
* The terminal handle has dynamic methods (add, remove, etc.) that can't be
|
|
90
|
+
* statically typed, so we use type variables to allow any operations.
|
|
91
|
+
*/
|
|
92
|
+
analyzePlotsModule() {
|
|
93
|
+
const exports = new Map();
|
|
94
|
+
|
|
95
|
+
// Use type variables for config and terminal handle since they're dynamically typed
|
|
96
|
+
// Config can be any record, terminal handle has runtime-defined methods
|
|
97
|
+
const configType = freshTypeVar();
|
|
98
|
+
const terminalHandleType = freshTypeVar();
|
|
99
|
+
|
|
100
|
+
// graph2d(config) -> TerminalHandle
|
|
101
|
+
exports.set("graph2d", new FunctionType([configType], terminalHandleType));
|
|
102
|
+
// graph3d(config) -> TerminalHandle
|
|
103
|
+
exports.set("graph3d", new FunctionType([freshTypeVar()], freshTypeVar()));
|
|
104
|
+
|
|
105
|
+
return exports;
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Analyze a .stn module and extract export types.
|
|
110
|
+
* Recursively type-checks the module to determine export types.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} modulePath - The module path
|
|
113
|
+
* @returns {Map<string, Object>|null} Map of export names to types
|
|
114
|
+
*/
|
|
115
|
+
analyzeStnModule(modulePath) {
|
|
116
|
+
if (!this.moduleResolver || !this.parser) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const stnContent = this.moduleResolver.getStnBuiltinContent(modulePath);
|
|
122
|
+
if (!stnContent) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Parse the module
|
|
127
|
+
const ast = this.parser(stnContent, { filename: `<builtin:${modulePath}>` });
|
|
128
|
+
if (!ast || !ast.exports) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Create a fresh environment for the module
|
|
133
|
+
const moduleEnv = new TypeEnvironment();
|
|
134
|
+
this.registerBuiltins(moduleEnv);
|
|
135
|
+
|
|
136
|
+
// First pass: process imports to get types from other modules
|
|
137
|
+
for (const stmt of ast.statements) {
|
|
138
|
+
if (stmt.type === "ImportStatement") {
|
|
139
|
+
this.analyzeModuleImport(stmt, moduleEnv);
|
|
140
|
+
} else if (stmt.type === "NamespaceImportStatement") {
|
|
141
|
+
this.visit(stmt, moduleEnv);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Second pass: process all statements to build environment
|
|
146
|
+
// This includes ExportStatements which are in both ast.statements and ast.exports
|
|
147
|
+
for (const stmt of ast.statements) {
|
|
148
|
+
if (stmt.type !== "ImportStatement" && stmt.type !== "NamespaceImportStatement") {
|
|
149
|
+
this.visit(stmt, moduleEnv);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Extract export types and extension info
|
|
154
|
+
const exports = new Map();
|
|
155
|
+
const extensionExports = new Set(); // Track which exports are extensions
|
|
156
|
+
|
|
157
|
+
for (const exportStmt of ast.exports) {
|
|
158
|
+
if (exportStmt.type === "ExportStatement") {
|
|
159
|
+
// Direct export: export fn foo() or export x = 5
|
|
160
|
+
const decl = exportStmt.declaration;
|
|
161
|
+
if (decl.type === "FunctionDefinition") {
|
|
162
|
+
// For overloaded functions, the env already has the OverloadedFunctionType
|
|
163
|
+
// We only need to add once per name
|
|
164
|
+
if (!exports.has(decl.name)) {
|
|
165
|
+
const funcType = moduleEnv.lookup(decl.name);
|
|
166
|
+
if (funcType) {
|
|
167
|
+
exports.set(decl.name, funcType);
|
|
168
|
+
}
|
|
169
|
+
// Track if this is an extension function
|
|
170
|
+
if (decl.isExtension) {
|
|
171
|
+
extensionExports.add(decl.name);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else if (decl.type === "Assignment") {
|
|
175
|
+
const varType = moduleEnv.lookup(decl.target);
|
|
176
|
+
if (varType) {
|
|
177
|
+
exports.set(decl.target, varType);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} else if (exportStmt.type === "SelectiveReExportStatement") {
|
|
181
|
+
// Re-export: export add, sub from primitives
|
|
182
|
+
const sourceExports = this.getModuleExportTypes(exportStmt.modulePath);
|
|
183
|
+
if (sourceExports) {
|
|
184
|
+
for (const spec of exportStmt.specifiers) {
|
|
185
|
+
const sourceType = sourceExports.get(spec.name);
|
|
186
|
+
if (sourceType) {
|
|
187
|
+
const exportName = spec.alias || spec.name;
|
|
188
|
+
exports.set(exportName, sourceType);
|
|
189
|
+
// Check if the source export is an extension
|
|
190
|
+
if (sourceExports._extensions && sourceExports._extensions.has(spec.name)) {
|
|
191
|
+
extensionExports.add(exportName);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// NamespaceReExportStatement handled differently (exports namespace object)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Attach extension info to the exports map
|
|
201
|
+
exports._extensions = extensionExports;
|
|
202
|
+
return exports;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
// Module analysis failed - log but don't throw
|
|
205
|
+
this.addWarning(`Failed to analyze module '${modulePath}': ${error.message}`, null);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Process an import statement during module analysis.
|
|
212
|
+
* Registers imported names with their types from the source module.
|
|
213
|
+
* Reports errors for imports that don't exist in the module.
|
|
214
|
+
*/
|
|
215
|
+
analyzeModuleImport(importStmt, env) {
|
|
216
|
+
// Register complex module overloads when any import from complex module is seen
|
|
217
|
+
// This allows operators like + - * / to work with complex numbers
|
|
218
|
+
if (importStmt.modulePath === 'complex') {
|
|
219
|
+
this._registerComplexOverloads();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const sourceExports = this.getModuleExportTypes(importStmt.modulePath);
|
|
223
|
+
|
|
224
|
+
for (const spec of importStmt.specifiers) {
|
|
225
|
+
// Support both spec.name (old format) and spec.importedName (new format)
|
|
226
|
+
const importedName = spec.importedName || spec.name;
|
|
227
|
+
const localName = spec.localName || importedName;
|
|
228
|
+
|
|
229
|
+
if (sourceExports) {
|
|
230
|
+
const sourceType = sourceExports.get(importedName);
|
|
231
|
+
if (sourceType) {
|
|
232
|
+
env.define(localName, sourceType);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// Module was analyzed but import name doesn't exist - report error
|
|
236
|
+
this.addError(
|
|
237
|
+
`'${importedName}' is not exported from module '${importStmt.modulePath}'`,
|
|
238
|
+
spec.location || importStmt.location
|
|
239
|
+
);
|
|
240
|
+
// Still define with type variable to allow continued type checking
|
|
241
|
+
env.define(localName, freshTypeVar());
|
|
242
|
+
} else {
|
|
243
|
+
// Module couldn't be analyzed - fall back to type variable silently
|
|
244
|
+
env.define(localName, freshTypeVar());
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator Mappings - maps operators to overload function names
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Map binary operators to overload function names
|
|
7
|
+
*/
|
|
8
|
+
export const BINARY_OP_NAMES = {
|
|
9
|
+
"+": "__op_add",
|
|
10
|
+
"-": "__op_sub",
|
|
11
|
+
"*": "__op_mul",
|
|
12
|
+
"/": "__op_div",
|
|
13
|
+
"%": "__op_mod",
|
|
14
|
+
"^": "__op_pow",
|
|
15
|
+
".+": "__op_elem_add",
|
|
16
|
+
".-": "__op_elem_sub",
|
|
17
|
+
".*": "__op_elem_mul",
|
|
18
|
+
"./": "__op_elem_div",
|
|
19
|
+
".^": "__op_elem_pow",
|
|
20
|
+
"@": "__op_matmul",
|
|
21
|
+
"<": "__op_lt",
|
|
22
|
+
">": "__op_gt",
|
|
23
|
+
"<=": "__op_lte",
|
|
24
|
+
">=": "__op_gte",
|
|
25
|
+
"==": "__op_eq",
|
|
26
|
+
"!=": "__op_neq",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Map unary operators to overload function names
|
|
31
|
+
*/
|
|
32
|
+
export const UNARY_OP_NAMES = {
|
|
33
|
+
"-": "__op_neg",
|
|
34
|
+
"!": "__op_not",
|
|
35
|
+
};
|