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.
Files changed (68) hide show
  1. package/README.md +52 -0
  2. package/StoneEngine.js +879 -0
  3. package/StoneEngineService.js +1727 -0
  4. package/adapters/FileSystemAdapter.js +230 -0
  5. package/adapters/OutputAdapter.js +208 -0
  6. package/adapters/index.js +6 -0
  7. package/cli/CLIOutputAdapter.js +196 -0
  8. package/cli/DaemonClient.js +349 -0
  9. package/cli/JSONOutputAdapter.js +135 -0
  10. package/cli/ReplSession.js +567 -0
  11. package/cli/ViewerServer.js +590 -0
  12. package/cli/commands/check.js +84 -0
  13. package/cli/commands/daemon.js +189 -0
  14. package/cli/commands/kill.js +66 -0
  15. package/cli/commands/package.js +713 -0
  16. package/cli/commands/ps.js +65 -0
  17. package/cli/commands/run.js +537 -0
  18. package/cli/entry.js +169 -0
  19. package/cli/index.js +14 -0
  20. package/cli/stonec.js +358 -0
  21. package/cli/test-compiler.js +181 -0
  22. package/cli/viewer/index.html +495 -0
  23. package/daemon/IPCServer.js +455 -0
  24. package/daemon/ProcessManager.js +327 -0
  25. package/daemon/ProcessRunner.js +307 -0
  26. package/daemon/daemon.js +398 -0
  27. package/daemon/index.js +16 -0
  28. package/frontend/analysis/index.js +5 -0
  29. package/frontend/analysis/livenessAnalyzer.js +568 -0
  30. package/frontend/analysis/treeShaker.js +265 -0
  31. package/frontend/index.js +20 -0
  32. package/frontend/parsing/astBuilder.js +2196 -0
  33. package/frontend/parsing/index.js +7 -0
  34. package/frontend/parsing/sonParser.js +592 -0
  35. package/frontend/parsing/stoneAstTypes.js +703 -0
  36. package/frontend/parsing/terminal-registry.js +435 -0
  37. package/frontend/parsing/tokenizer.js +692 -0
  38. package/frontend/type-checker/OverloadedFunctionType.js +43 -0
  39. package/frontend/type-checker/TypeEnvironment.js +165 -0
  40. package/frontend/type-checker/bidirectionalInference.js +149 -0
  41. package/frontend/type-checker/index.js +10 -0
  42. package/frontend/type-checker/moduleAnalysis.js +248 -0
  43. package/frontend/type-checker/operatorMappings.js +35 -0
  44. package/frontend/type-checker/overloadResolution.js +605 -0
  45. package/frontend/type-checker/typeChecker.js +452 -0
  46. package/frontend/type-checker/typeCompatibility.js +389 -0
  47. package/frontend/type-checker/visitors/controlFlow.js +483 -0
  48. package/frontend/type-checker/visitors/functions.js +604 -0
  49. package/frontend/type-checker/visitors/index.js +38 -0
  50. package/frontend/type-checker/visitors/literals.js +341 -0
  51. package/frontend/type-checker/visitors/modules.js +159 -0
  52. package/frontend/type-checker/visitors/operators.js +109 -0
  53. package/frontend/type-checker/visitors/statements.js +768 -0
  54. package/frontend/types/index.js +5 -0
  55. package/frontend/types/operatorMap.js +134 -0
  56. package/frontend/types/types.js +2046 -0
  57. package/frontend/utils/errorCollector.js +244 -0
  58. package/frontend/utils/index.js +5 -0
  59. package/frontend/utils/moduleResolver.js +479 -0
  60. package/package.json +50 -0
  61. package/packages/browserCache.js +359 -0
  62. package/packages/fetcher.js +236 -0
  63. package/packages/index.js +130 -0
  64. package/packages/lockfile.js +271 -0
  65. package/packages/manifest.js +291 -0
  66. package/packages/packageResolver.js +356 -0
  67. package/packages/resolver.js +310 -0
  68. package/packages/semver.js +635 -0
@@ -0,0 +1,483 @@
1
+ /**
2
+ * Control Flow Visitors - mixin for TypeChecker
3
+ *
4
+ * Visitors for loops, branches, and control flow constructs.
5
+ */
6
+
7
+ import {
8
+ FunctionType,
9
+ RecordType,
10
+ ArrayType,
11
+ TypeVariable,
12
+ NUM,
13
+ UNIT,
14
+ freshTypeVar,
15
+ typeEquals,
16
+ unify,
17
+ formatType,
18
+ matchType,
19
+ canPromote,
20
+ fromTypeAnnotation,
21
+ } from "../../types/types.js";
22
+
23
+ /**
24
+ * Control flow visitor methods to be mixed into TypeChecker.prototype
25
+ */
26
+ export const controlFlowVisitors = {
27
+ /**
28
+ * Visit a loop expression
29
+ * Validates that state variable transitions preserve types
30
+ */
31
+ visitLoopExpression(node, env) {
32
+ const loopEnv = env.createChild();
33
+ const stateTypes = new Map();
34
+
35
+ // Helper: detect if an expression references a given identifier name
36
+ // Used to flag self-referential loop initializers (x = x) which evaluate to 'none'
37
+ const exprContainsIdentifier = (expr, name, parent = null, parentKey = null) => {
38
+ if (!expr || typeof expr !== "object") return false;
39
+ if (expr.type === "Identifier" && expr.name === name) {
40
+ // Ignore property positions like fwd.M (MemberExpression.property) where the identifier is a field name
41
+ const isMemberProp = (parent?.type === "MemberExpression" || parent?.type === "MemberAccess") &&
42
+ parent.property === expr &&
43
+ parent.computed !== true;
44
+ // Ignore identifier nodes that serve as object/record keys (common key property names)
45
+ const isObjectKey = typeof parentKey === "string" && /name|key|field/i.test(parentKey);
46
+ if (!isMemberProp && !isObjectKey) {
47
+ return true;
48
+ }
49
+ }
50
+ for (const key of Object.keys(expr)) {
51
+ const val = expr[key];
52
+ if (Array.isArray(val)) {
53
+ if (val.some(child => exprContainsIdentifier(child, name, expr, key))) return true;
54
+ } else if (val && typeof val === "object") {
55
+ if (exprContainsIdentifier(val, name, expr, key)) return true;
56
+ }
57
+ }
58
+ return false;
59
+ };
60
+
61
+ // Initialize state variables and their primed versions
62
+ for (const variable of node.variables) {
63
+ let varType;
64
+ // Disallow shadowing outer user-defined bindings (Stone has no shadowing)
65
+ // But allow loop state variables to have the same name as built-in functions
66
+ // (e.g., a loop state variable named 'sum' shouldn't conflict with the built-in sum function)
67
+ const existingBinding = env.lookup(variable.name);
68
+ if (existingBinding && !(existingBinding instanceof FunctionType)) {
69
+ this.addError(
70
+ `Loop variable '${variable.name}' shadows an outer binding. Stone disallows shadowing; use a distinct name.`,
71
+ variable.location || node.location
72
+ );
73
+ }
74
+ // Prevent self-referential loop initializers: h = h yields 'none' at runtime
75
+ const outerBinding = env.lookup(variable.name); // allow referencing an outer binding with same name
76
+ if (!outerBinding && variable.initialValue && exprContainsIdentifier(variable.initialValue, variable.name)) {
77
+ this.addError(
78
+ `Loop variable '${variable.name}' is initialized with itself. ` +
79
+ `Loop state initializers cannot reference their own name; compute the value in a separate binding (e.g., 'h_init') and use that instead.`,
80
+ variable.initialValue.location || node.location
81
+ );
82
+ }
83
+ if (variable.typeAnnotation) {
84
+ varType = fromTypeAnnotation(variable.typeAnnotation, env);
85
+ // If there's also an initial value, verify it matches the annotation
86
+ if (variable.initialValue) {
87
+ const initType = this.visit(variable.initialValue, env);
88
+ const unifyResult = unify(varType, initType);
89
+ if (!unifyResult.success) {
90
+ this.addError(
91
+ `Loop variable '${variable.name}' declared as ${varType}, but initialized with ${initType}`,
92
+ node.location
93
+ );
94
+ }
95
+ }
96
+ } else if (variable.initialValue) {
97
+ varType = this.visit(variable.initialValue, env);
98
+ } else {
99
+ varType = freshTypeVar();
100
+ }
101
+ stateTypes.set(variable.name, varType);
102
+ loopEnv.define(variable.name, varType);
103
+ // Define primed version with same type (x' must have same type as x)
104
+ loopEnv.define(variable.name + "'", varType);
105
+ }
106
+
107
+ // Process drivers - track for back-propagation of element types
108
+ const forDrivers = []; // [{iterableType, elemTypeVar, pattern}]
109
+ if (node.drivers) {
110
+ for (const driver of node.drivers) {
111
+ if (driver.type === "for") {
112
+ // For driver introduces iteration variable
113
+ const iterableType = this.visit(driver.iterable, loopEnv);
114
+ let elemType;
115
+ if (iterableType instanceof ArrayType) {
116
+ elemType = iterableType.rank === 1
117
+ ? iterableType.elementType
118
+ : new ArrayType(iterableType.elementType, iterableType.rank - 1);
119
+ } else {
120
+ // Unknown iterable type - use fresh TypeVariable and track for back-propagation
121
+ elemType = freshTypeVar();
122
+ if (iterableType instanceof TypeVariable) {
123
+ forDrivers.push({ iterableType, elemType, pattern: driver.pattern });
124
+ }
125
+ }
126
+ loopEnv.define(driver.pattern, elemType);
127
+ }
128
+ // While driver doesn't introduce variables
129
+ }
130
+ }
131
+
132
+ // Type check body with state type validation
133
+ // Track transition types for each state variable to ensure consistency
134
+ const transitionTypes = new Map(); // baseName -> [actualType, ...]
135
+ const assignedPrimes = new Set(); // Track which x' have been assigned
136
+
137
+ // Create a custom environment that tracks prime variable references
138
+ // We'll use this to detect references to x' before assignment
139
+ const primeRefEnv = loopEnv.createChild();
140
+
141
+ // Helper to check if an expression references unassigned primed variables
142
+ const checkPrimeReferences = (exprNode, location) => {
143
+ if (!exprNode) return;
144
+
145
+ if (exprNode.type === "Identifier" && exprNode.name.endsWith("'")) {
146
+ const baseName = exprNode.name.slice(0, -1);
147
+ if (stateTypes.has(baseName) && !assignedPrimes.has(baseName)) {
148
+ this.addError(
149
+ `Cannot reference '${exprNode.name}' before it is assigned in the loop body. ` +
150
+ `State transition '${exprNode.name}' must be assigned before it can be read.`,
151
+ exprNode.location || location
152
+ );
153
+ }
154
+ }
155
+
156
+ // Recursively check child nodes
157
+ if (exprNode.left) checkPrimeReferences(exprNode.left, location);
158
+ if (exprNode.right) checkPrimeReferences(exprNode.right, location);
159
+ if (exprNode.operand) checkPrimeReferences(exprNode.operand, location);
160
+ if (exprNode.args) exprNode.args.forEach(arg => checkPrimeReferences(arg, location));
161
+ if (exprNode.value) checkPrimeReferences(exprNode.value, location);
162
+ if (exprNode.object) checkPrimeReferences(exprNode.object, location);
163
+ if (exprNode.property && exprNode.computed) checkPrimeReferences(exprNode.property, location);
164
+ if (exprNode.condition) checkPrimeReferences(exprNode.condition, location);
165
+ if (exprNode.elements) exprNode.elements.forEach(el => checkPrimeReferences(el, location));
166
+ if (exprNode.bindings) exprNode.bindings.forEach(b => checkPrimeReferences(b.value, location));
167
+ if (exprNode.trailingExpr) checkPrimeReferences(exprNode.trailingExpr, location);
168
+ if (exprNode.paths) {
169
+ exprNode.paths.forEach(path => {
170
+ if (path.condition) checkPrimeReferences(path.condition, location);
171
+ if (path.body) path.body.forEach(stmt => checkPrimeReferences(stmt, location));
172
+ });
173
+ }
174
+ };
175
+
176
+ for (const stmt of node.body) {
177
+ // Check for state variable transitions (assignments to primed variables)
178
+ if (stmt.type === "Assignment" && stmt.target?.endsWith?.("'")) {
179
+ const baseName = stmt.target.slice(0, -1);
180
+ if (stateTypes.has(baseName)) {
181
+ // Check for multiple assignments to the same x'
182
+ if (assignedPrimes.has(baseName)) {
183
+ this.addError(
184
+ `State variable '${baseName}'' is assigned multiple times in the loop body. ` +
185
+ `Each state variable can only have one transition per iteration.`,
186
+ stmt.location
187
+ );
188
+ }
189
+
190
+ // Check for references to unassigned primed variables in the RHS
191
+ checkPrimeReferences(stmt.value, stmt.location);
192
+
193
+ const expectedType = stateTypes.get(baseName);
194
+ const actualType = this.visit(stmt.value, loopEnv);
195
+
196
+ // Mark this prime as assigned AFTER checking the RHS
197
+ assignedPrimes.add(baseName);
198
+
199
+ // Track all transition types for this variable
200
+ if (!transitionTypes.has(baseName)) {
201
+ transitionTypes.set(baseName, []);
202
+ }
203
+ transitionTypes.get(baseName).push({ type: actualType, location: stmt.location });
204
+
205
+ // Unify the actual type with the expected type
206
+ // This ensures type inference works for empty arrays and catches mismatches
207
+ const unifyResult = unify(expectedType, actualType);
208
+ if (!unifyResult.success) {
209
+ const expectedStr = formatType(this.toStructuredType(expectedType));
210
+ const actualStr = formatType(this.toStructuredType(actualType));
211
+ this.addError(
212
+ `Loop state type mismatch: '${baseName}' is ${expectedStr}, ` +
213
+ `transition '${baseName}'' is ${actualStr}`,
214
+ stmt.location
215
+ );
216
+ }
217
+ continue;
218
+ } else {
219
+ // Check if this is an outer loop's state variable
220
+ // If the primed variable is accessible but not in current loop's state,
221
+ // it must be from an enclosing loop
222
+ const primedVarType = loopEnv.lookup(stmt.target);
223
+ if (primedVarType !== null) {
224
+ this.addError(
225
+ `Cannot update outer loop state variable '${baseName}' from inner loop. ` +
226
+ `State transitions (using '${baseName}\\'') only work for the current loop's state variables. ` +
227
+ `To accumulate values across nested loops, collect results in the inner loop and merge in the outer loop.`,
228
+ stmt.location
229
+ );
230
+ }
231
+ }
232
+ }
233
+
234
+ // For non-prime assignments, also check for prime references
235
+ if (stmt.type === "Assignment" && stmt.value) {
236
+ checkPrimeReferences(stmt.value, stmt.location);
237
+ }
238
+
239
+ this.visit(stmt, loopEnv);
240
+ }
241
+
242
+ // Verify all transitions for each variable produce consistent types
243
+ for (const [baseName, transitions] of transitionTypes) {
244
+ if (transitions.length > 1) {
245
+ const firstType = transitions[0].type;
246
+ for (let i = 1; i < transitions.length; i++) {
247
+ const otherType = transitions[i].type;
248
+ // After unification, check if they resolved to the same type
249
+ const resolved1 = firstType instanceof TypeVariable ? firstType.resolve() : firstType;
250
+ const resolved2 = otherType instanceof TypeVariable ? otherType.resolve() : otherType;
251
+ if (!typeEquals(resolved1, resolved2)) {
252
+ // Types don't match - report error
253
+ const type1Str = formatType(this.toStructuredType(resolved1));
254
+ const type2Str = formatType(this.toStructuredType(resolved2));
255
+ this.addError(
256
+ `Inconsistent transition types for '${baseName}': ` +
257
+ `first assignment produces ${type1Str}, but another produces ${type2Str}`,
258
+ transitions[i].location
259
+ );
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ // Back-propagate element types to iterable TypeVariables
266
+ // If we learned the element type from usage, constrain the iterable
267
+ for (const { iterableType, elemType } of forDrivers) {
268
+ const resolvedElem = elemType instanceof TypeVariable ? elemType.resolve() : elemType;
269
+ if (resolvedElem !== elemType && !(resolvedElem instanceof TypeVariable)) {
270
+ // Element type was resolved - unify iterable with appropriate container type
271
+ // Default to 1D array since we don't know the rank
272
+ unify(iterableType, new ArrayType(resolvedElem, 1));
273
+ }
274
+ }
275
+
276
+ // Result is a record of state variables
277
+ const resultFields = new Map();
278
+ for (const [name, type] of stateTypes) {
279
+ resultFields.set(name, type);
280
+ }
281
+
282
+ return this.setType(node, new RecordType(resultFields, false));
283
+ },
284
+
285
+ /**
286
+ * Detect if a condition is a discriminant check pattern.
287
+ * Patterns: x.field == "literal" or x.field != "literal"
288
+ * Returns { varName, fieldName, literal, isNegated } or null.
289
+ */
290
+ extractDiscriminantCheck(condition) {
291
+ if (!condition) return null;
292
+
293
+ // Check for binary comparison (== or !=)
294
+ if (condition.type !== "BinaryOp") return null;
295
+ if (condition.operator !== "==" && condition.operator !== "!=") return null;
296
+
297
+ const left = condition.left;
298
+ const right = condition.right;
299
+
300
+ // Left side should be member access (x.field)
301
+ if (left?.type !== "MemberAccess" || left.computed) return null;
302
+ if (left.object?.type !== "Identifier") return null;
303
+
304
+ // Right side should be a string literal
305
+ if (right?.type !== "Literal" || typeof right.value !== "string") return null;
306
+
307
+ return {
308
+ varName: left.object.name,
309
+ fieldName: left.property?.name,
310
+ literal: right.value,
311
+ isNegated: condition.operator === "!="
312
+ };
313
+ },
314
+
315
+ /**
316
+ * Check if a branch body is a simple return of a variable.
317
+ * Returns the variable name if so, null otherwise.
318
+ */
319
+ getSimpleReturnVar(body) {
320
+ if (!body || body.length !== 1) return null;
321
+ const stmt = body[0];
322
+
323
+ // Check for return statement
324
+ if (stmt.type === "ReturnStatement" && stmt.value?.type === "Identifier") {
325
+ return stmt.value.name;
326
+ }
327
+
328
+ // Check for direct identifier (implicit return in single-statement branch)
329
+ if (stmt.type === "Identifier") {
330
+ return stmt.name;
331
+ }
332
+
333
+ return null;
334
+ },
335
+
336
+ /**
337
+ * Visit a branch expression (if/elif/else)
338
+ * Validates that all branches return compatible types
339
+ */
340
+ visitBranchExpression(node, env) {
341
+ const branchResults = [];
342
+ const branchLabels = [];
343
+ let hasElseBranch = false;
344
+
345
+ // Detect discriminant guard pattern in first branch
346
+ // Pattern: if (x.field != "literal") { return x }
347
+ // This is a "type guard passthrough" - the branch returns the input unchanged
348
+ // when it's already the correct type, so we shouldn't unify its type with other branches
349
+ let guardPassthroughBranchIndex = -1;
350
+
351
+ if (node.paths.length >= 2 && node.paths[0].condition) {
352
+ const discriminant = this.extractDiscriminantCheck(node.paths[0].condition);
353
+ if (discriminant && discriminant.isNegated) {
354
+ // Check if branch body just returns the discriminated variable
355
+ const returnVar = this.getSimpleReturnVar(node.paths[0].body);
356
+ if (returnVar === discriminant.varName) {
357
+ guardPassthroughBranchIndex = 0;
358
+ }
359
+ }
360
+ }
361
+
362
+ for (let i = 0; i < node.paths.length; i++) {
363
+ const path = node.paths[i];
364
+ if (path.condition) {
365
+ this.visit(path.condition, env);
366
+ } else {
367
+ // Path without condition is the else branch
368
+ hasElseBranch = true;
369
+ }
370
+
371
+ const branchEnv = env.createChild();
372
+ let lastType = UNIT;
373
+
374
+ for (const stmt of path.body) {
375
+ // Always visit the statement to ensure proper type tracking
376
+ lastType = this.visit(stmt, branchEnv);
377
+ }
378
+
379
+ branchResults.push({
380
+ path,
381
+ type: lastType,
382
+ isIncomplete: this.isIncompleteType(lastType),
383
+ index: i
384
+ });
385
+
386
+ // Label for error messages
387
+ if (i === 0) {
388
+ branchLabels.push("'if'");
389
+ } else if (path.condition) {
390
+ branchLabels.push(`'elif' #${i}`);
391
+ } else {
392
+ branchLabels.push("'else'");
393
+ }
394
+ }
395
+
396
+ // Check for value-producing conditionals without else branch
397
+ if (node._isValueProducing && !hasElseBranch) {
398
+ this.addError(
399
+ `Conditional expression used as a value must have an 'else' branch. ` +
400
+ `Add 'else { ... }' to provide a value for all cases.`,
401
+ node.location
402
+ );
403
+ }
404
+
405
+ // Separate complete and incomplete types
406
+ // Exclude guard passthrough branch from unification - it returns the input unchanged
407
+ // so its type shouldn't constrain other branches' types
408
+ const complete = branchResults.filter(b => !b.isIncomplete && b.index !== guardPassthroughBranchIndex);
409
+ const incomplete = branchResults.filter(b => b.isIncomplete && b.index !== guardPassthroughBranchIndex);
410
+
411
+ // If there's only one branch (no else), this is a guard clause
412
+ // The if-expression itself returns UNIT; any return statements inside
413
+ // are handled at the function level
414
+ if (branchResults.length === 1) {
415
+ return this.setType(node, UNIT);
416
+ }
417
+
418
+ // If no branches at all, return UNIT
419
+ if (branchResults.length === 0) {
420
+ return this.setType(node, UNIT);
421
+ }
422
+
423
+ // If all branches are incomplete (type variables), unify them and return
424
+ // This handles polymorphic functions where parameter types are unknown
425
+ if (complete.length === 0 && incomplete.length > 0) {
426
+ let resultType = incomplete[0].type;
427
+ for (let i = 1; i < incomplete.length; i++) {
428
+ const result = unify(resultType, incomplete[i].type);
429
+ if (!result.success) {
430
+ const branchIdx = branchResults.indexOf(incomplete[i]);
431
+ const firstIdx = branchResults.indexOf(incomplete[0]);
432
+ this.addError(
433
+ `Branch type mismatch: ${branchLabels[firstIdx]} and ${branchLabels[branchIdx]} have incompatible types`,
434
+ node.location
435
+ );
436
+ }
437
+ if (resultType instanceof TypeVariable) {
438
+ resultType = resultType.resolve();
439
+ }
440
+ }
441
+ return this.setType(node, resultType);
442
+ }
443
+
444
+ // Find common type from complete branches
445
+ let resultType = complete[0].type;
446
+ for (let i = 1; i < complete.length; i++) {
447
+ const result = unify(resultType, complete[i].type);
448
+ if (!result.success) {
449
+ // Find branch index for this complete branch
450
+ const branchIdx = branchResults.indexOf(complete[i]);
451
+ const firstIdx = branchResults.indexOf(complete[0]);
452
+ const firstStr = formatType(this.toStructuredType(resultType));
453
+ const otherStr = formatType(this.toStructuredType(complete[i].type));
454
+ this.addError(
455
+ `Branch type mismatch: ${branchLabels[firstIdx]} returns ${firstStr}, ` +
456
+ `${branchLabels[branchIdx]} returns ${otherStr}`,
457
+ node.location
458
+ );
459
+ } else {
460
+ // Resolve if it's a TypeVariable
461
+ if (resultType instanceof TypeVariable) {
462
+ resultType = resultType.resolve();
463
+ }
464
+ // When promotion happened, use the wider type (e.g., complex over num)
465
+ if (canPromote(resultType, complete[i].type).can) {
466
+ resultType = complete[i].type;
467
+ }
468
+ }
469
+ }
470
+
471
+ // Back-propagate to incomplete branches
472
+ for (const { path, type } of incomplete) {
473
+ unify(type, resultType);
474
+ // Update the last statement's inferredType in the path
475
+ if (path.body.length > 0) {
476
+ const lastStmt = path.body[path.body.length - 1];
477
+ lastStmt.inferredType = resultType;
478
+ }
479
+ }
480
+
481
+ return this.setType(node, resultType);
482
+ },
483
+ };