arc-lang 0.6.7 → 0.6.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.d.ts +6 -1
- package/dist/formatter.js +15 -1
- package/dist/interpreter.js +18 -1
- package/dist/linter.js +27 -2
- package/dist/modules.js +1 -1
- package/dist/parser.d.ts +1 -0
- package/dist/parser.js +17 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/ast.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export interface Loc {
|
|
|
2
2
|
line: number;
|
|
3
3
|
col: number;
|
|
4
4
|
}
|
|
5
|
-
export type Expr = IntLiteral | FloatLiteral | BoolLiteral | NilLiteral | StringLiteral | StringInterp | Identifier | BinaryExpr | UnaryExpr | CallExpr | MemberExpr | IndexExpr | PipelineExpr | IfExpr | MatchExpr | LambdaExpr | ListLiteral | MapLiteral | ListComprehension | ToolCallExpr | RangeExpr | BlockExpr | AsyncExpr | AwaitExpr | FetchExpr | SpreadExpr | OptionalMemberExpr | TryExpr | TryCatchExpr;
|
|
5
|
+
export type Expr = IntLiteral | FloatLiteral | BoolLiteral | NilLiteral | StringLiteral | StringInterp | Identifier | BinaryExpr | UnaryExpr | CallExpr | MemberExpr | IndexExpr | PipelineExpr | IfExpr | MatchExpr | LambdaExpr | ListLiteral | MapLiteral | ListComprehension | ToolCallExpr | RangeExpr | BlockExpr | AsyncExpr | AwaitExpr | FetchExpr | SpreadExpr | OptionalMemberExpr | TryExpr | TryCatchExpr | GroupExpr;
|
|
6
6
|
export interface IntLiteral {
|
|
7
7
|
kind: "IntLiteral";
|
|
8
8
|
value: number;
|
|
@@ -177,6 +177,11 @@ export interface TryCatchExpr {
|
|
|
177
177
|
catchBody: Expr;
|
|
178
178
|
loc: Loc;
|
|
179
179
|
}
|
|
180
|
+
export interface GroupExpr {
|
|
181
|
+
kind: "GroupExpr";
|
|
182
|
+
expr: Expr;
|
|
183
|
+
loc: Loc;
|
|
184
|
+
}
|
|
180
185
|
export type Pattern = WildcardPattern | LiteralPattern | BindingPattern | ArrayPattern | OrPattern | ConstructorPattern;
|
|
181
186
|
export interface WildcardPattern {
|
|
182
187
|
kind: "WildcardPattern";
|
package/dist/formatter.js
CHANGED
|
@@ -35,6 +35,16 @@ function extractComments(source) {
|
|
|
35
35
|
col++;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
else if (source[i] === '/' && i + 1 < source.length && source[i + 1] === '/') {
|
|
39
|
+
const startLine = line, startCol = col;
|
|
40
|
+
let text = '';
|
|
41
|
+
while (i < source.length && source[i] !== '\n') {
|
|
42
|
+
text += source[i];
|
|
43
|
+
i++;
|
|
44
|
+
col++;
|
|
45
|
+
}
|
|
46
|
+
comments.push({ text, line: startLine, col: startCol });
|
|
47
|
+
}
|
|
38
48
|
else if (source[i] === '#') {
|
|
39
49
|
const startLine = line, startCol = col;
|
|
40
50
|
let text = '';
|
|
@@ -98,7 +108,10 @@ export function format(source, options) {
|
|
|
98
108
|
function formatExpr(expr, depth) {
|
|
99
109
|
switch (expr.kind) {
|
|
100
110
|
case "IntLiteral": return String(expr.value);
|
|
101
|
-
case "FloatLiteral":
|
|
111
|
+
case "FloatLiteral": {
|
|
112
|
+
const s = String(expr.value);
|
|
113
|
+
return s.includes('.') ? s : s + '.0';
|
|
114
|
+
}
|
|
102
115
|
case "BoolLiteral": return expr.value ? "true" : "false";
|
|
103
116
|
case "NilLiteral": return "nil";
|
|
104
117
|
case "StringLiteral": {
|
|
@@ -244,6 +257,7 @@ export function format(source, options) {
|
|
|
244
257
|
const targets = expr.targets.map(t => formatExpr(t, depth)).join(", ");
|
|
245
258
|
return `fetch [${targets}]`;
|
|
246
259
|
}
|
|
260
|
+
case "GroupExpr": return `(${formatExpr(expr.expr, depth)})`;
|
|
247
261
|
case "SpreadExpr": return `...${formatExpr(expr.expr, depth)}`;
|
|
248
262
|
case "OptionalMemberExpr": return `${formatExpr(expr.object, depth)}?.${expr.property}`;
|
|
249
263
|
case "TryExpr": return `${formatExpr(expr.expr, depth)}?`;
|
package/dist/interpreter.js
CHANGED
|
@@ -20,6 +20,13 @@ class Env {
|
|
|
20
20
|
if (this.parent)
|
|
21
21
|
return this.parent.get(name);
|
|
22
22
|
// Collect all known variable names for "did you mean?" suggestion
|
|
23
|
+
if (name === "mut" || name === "var") {
|
|
24
|
+
throw new ArcRuntimeError(`Undefined variable: ${name}`, {
|
|
25
|
+
code: ErrorCode.UNDEFINED_VARIABLE,
|
|
26
|
+
category: "RuntimeError",
|
|
27
|
+
suggestion: "Did you mean 'let mut' to declare a mutable variable?",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
23
30
|
const candidates = this.allNames();
|
|
24
31
|
const closest = findClosestMatch(name, candidates);
|
|
25
32
|
throw new ArcRuntimeError(`Undefined variable: ${name}`, {
|
|
@@ -2681,7 +2688,15 @@ function evalExpr(expr, env) {
|
|
|
2681
2688
|
if (obj === null)
|
|
2682
2689
|
return null;
|
|
2683
2690
|
if (obj && typeof obj === "object" && "__map" in obj) {
|
|
2684
|
-
|
|
2691
|
+
const mapObj = obj;
|
|
2692
|
+
const val = mapObj.entries.get(expr.property);
|
|
2693
|
+
if (val === undefined && "__module" in obj) {
|
|
2694
|
+
const modName = obj.__module;
|
|
2695
|
+
const candidates = [...mapObj.entries.keys()];
|
|
2696
|
+
const closest = findClosestMatch(expr.property, candidates);
|
|
2697
|
+
throw new ArcRuntimeError(`Module '${modName}' has no member '${expr.property}'${closest ? `. Did you mean '${closest}'?` : ""}`, { code: ErrorCode.PROPERTY_ACCESS, loc: expr.loc });
|
|
2698
|
+
}
|
|
2699
|
+
return val ?? null;
|
|
2685
2700
|
}
|
|
2686
2701
|
// Teaching error messages for common method-style access
|
|
2687
2702
|
const prop = expr.property;
|
|
@@ -2956,6 +2971,8 @@ function evalExpr(expr, env) {
|
|
|
2956
2971
|
}
|
|
2957
2972
|
return result;
|
|
2958
2973
|
}
|
|
2974
|
+
case "GroupExpr":
|
|
2975
|
+
return evalExpr(expr.expr, env);
|
|
2959
2976
|
default:
|
|
2960
2977
|
throw new Error(`Unknown expression kind: ${expr.kind}`);
|
|
2961
2978
|
}
|
package/dist/linter.js
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
// Checks for common code quality issues
|
|
3
3
|
import { lex } from "./lexer.js";
|
|
4
4
|
import { parse } from "./parser.js";
|
|
5
|
+
/** Walk an expression to find the root variable name (e.g. `a[b].c` → "a") */
|
|
6
|
+
function getRootVariable(expr) {
|
|
7
|
+
if (expr.kind === "Identifier")
|
|
8
|
+
return expr.name;
|
|
9
|
+
if (expr.kind === "MemberExpr" || expr.kind === "OptionalMemberExpr")
|
|
10
|
+
return getRootVariable(expr.object);
|
|
11
|
+
if (expr.kind === "IndexExpr")
|
|
12
|
+
return getRootVariable(expr.object);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
5
15
|
const DEFAULT_OPTIONS = {
|
|
6
16
|
maxLineLength: 100,
|
|
7
17
|
file: "<stdin>",
|
|
@@ -212,6 +222,9 @@ export function lint(source, options) {
|
|
|
212
222
|
for (const t of expr.targets)
|
|
213
223
|
analyzeExpr(t, scope);
|
|
214
224
|
break;
|
|
225
|
+
case "GroupExpr":
|
|
226
|
+
analyzeExpr(expr.expr, scope);
|
|
227
|
+
break;
|
|
215
228
|
}
|
|
216
229
|
}
|
|
217
230
|
function analyzePattern(pat, scope) {
|
|
@@ -399,15 +412,27 @@ export function lint(source, options) {
|
|
|
399
412
|
scope.markUsed(stmt.target);
|
|
400
413
|
analyzeExpr(stmt.value, scope);
|
|
401
414
|
break;
|
|
402
|
-
case "MemberAssignStmt":
|
|
415
|
+
case "MemberAssignStmt": {
|
|
403
416
|
analyzeExpr(stmt.object, scope);
|
|
404
417
|
analyzeExpr(stmt.value, scope);
|
|
418
|
+
const memberRoot = getRootVariable(stmt.object);
|
|
419
|
+
if (memberRoot) {
|
|
420
|
+
scope.markMutated(memberRoot);
|
|
421
|
+
scope.markUsed(memberRoot);
|
|
422
|
+
}
|
|
405
423
|
break;
|
|
406
|
-
|
|
424
|
+
}
|
|
425
|
+
case "IndexAssignStmt": {
|
|
407
426
|
analyzeExpr(stmt.object, scope);
|
|
408
427
|
analyzeExpr(stmt.index, scope);
|
|
409
428
|
analyzeExpr(stmt.value, scope);
|
|
429
|
+
const indexRoot = getRootVariable(stmt.object);
|
|
430
|
+
if (indexRoot) {
|
|
431
|
+
scope.markMutated(indexRoot);
|
|
432
|
+
scope.markUsed(indexRoot);
|
|
433
|
+
}
|
|
410
434
|
break;
|
|
435
|
+
}
|
|
411
436
|
}
|
|
412
437
|
}
|
|
413
438
|
const globalScope = new LintScope();
|
package/dist/modules.js
CHANGED
|
@@ -124,7 +124,7 @@ export function handleUse(stmt, env, currentFile) {
|
|
|
124
124
|
for (const [name, value] of Object.entries(exports)) {
|
|
125
125
|
entries.set(name, value);
|
|
126
126
|
}
|
|
127
|
-
env.set(nsName, { __map: true, entries });
|
|
127
|
+
env.set(nsName, { __map: true, __module: nsName, entries });
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
130
|
* Create a UseHandler bound to a specific file path.
|
package/dist/parser.d.ts
CHANGED
package/dist/parser.js
CHANGED
|
@@ -68,6 +68,10 @@ export class Parser {
|
|
|
68
68
|
case TokenType.Type: return this.parseType();
|
|
69
69
|
case TokenType.Ret: return this.parseRet();
|
|
70
70
|
case TokenType.Try: return this.parseTryStmtOrExpr();
|
|
71
|
+
case TokenType.Mut: {
|
|
72
|
+
const loc = this.loc();
|
|
73
|
+
throw new ParseError(`'mut' is not a statement. Did you mean 'let mut' to declare a mutable variable?`, loc);
|
|
74
|
+
}
|
|
71
75
|
default: {
|
|
72
76
|
const exprLoc = this.loc();
|
|
73
77
|
const expr = this.parseExpr();
|
|
@@ -696,7 +700,7 @@ export class Parser {
|
|
|
696
700
|
this.pos = saved;
|
|
697
701
|
const expr = this.parseExpr();
|
|
698
702
|
this.expect(TokenType.RParen);
|
|
699
|
-
return expr;
|
|
703
|
+
return { kind: "GroupExpr", expr, loc };
|
|
700
704
|
}
|
|
701
705
|
// List literal or comprehension
|
|
702
706
|
if (t.type === TokenType.LBracket) {
|
|
@@ -856,9 +860,13 @@ export class Parser {
|
|
|
856
860
|
this.expect(TokenType.RBracket);
|
|
857
861
|
return { kind: "ListLiteral", elements, loc };
|
|
858
862
|
}
|
|
863
|
+
isIdentOrKeyword(t) {
|
|
864
|
+
const tt = t ?? this.peek().type;
|
|
865
|
+
return tt === TokenType.Ident || tt === TokenType.Match || tt === TokenType.Fn || tt === TokenType.Let || tt === TokenType.If || tt === TokenType.For || tt === TokenType.In || tt === TokenType.Do || tt === TokenType.While || tt === TokenType.Until || tt === TokenType.Use || tt === TokenType.Pub || tt === TokenType.Type || tt === TokenType.Ret || tt === TokenType.Where || tt === TokenType.Matching || tt === TokenType.Fetch || tt === TokenType.Async || tt === TokenType.Await || tt === TokenType.Try || tt === TokenType.Catch || tt === TokenType.Mut || tt === TokenType.True || tt === TokenType.False || tt === TokenType.NilKw || tt === TokenType.And || tt === TokenType.Or || tt === TokenType.Not || tt === TokenType.Break || tt === TokenType.Continue || tt === TokenType.El;
|
|
866
|
+
}
|
|
859
867
|
isMapStart() {
|
|
860
868
|
// Check if current position (after {) looks like a map entry
|
|
861
|
-
if (this.
|
|
869
|
+
if (this.isIdentOrKeyword() && this.tokens[this.pos + 1]?.type === TokenType.Colon)
|
|
862
870
|
return true;
|
|
863
871
|
if (this.at(TokenType.String) && this.tokens[this.pos + 1]?.type === TokenType.Colon)
|
|
864
872
|
return true;
|
|
@@ -912,7 +920,13 @@ export class Parser {
|
|
|
912
920
|
const value = this.parseExpr();
|
|
913
921
|
return { key, value };
|
|
914
922
|
}
|
|
915
|
-
// Ident key: name: value
|
|
923
|
+
// Ident or keyword key: name: value
|
|
924
|
+
if (this.isIdentOrKeyword()) {
|
|
925
|
+
const key = this.advance().value;
|
|
926
|
+
this.expect(TokenType.Colon);
|
|
927
|
+
const value = this.parseExpr();
|
|
928
|
+
return { key, value };
|
|
929
|
+
}
|
|
916
930
|
const key = this.expect(TokenType.Ident).value;
|
|
917
931
|
this.expect(TokenType.Colon);
|
|
918
932
|
const value = this.parseExpr();
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED