arc-lang 0.5.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/LICENSE +21 -0
- package/README.md +148 -0
- package/dist/ast.d.ts +298 -0
- package/dist/ast.js +2 -0
- package/dist/build.d.ts +7 -0
- package/dist/build.js +138 -0
- package/dist/codegen-js.d.ts +2 -0
- package/dist/codegen-js.js +168 -0
- package/dist/codegen.d.ts +2 -0
- package/dist/codegen.js +364 -0
- package/dist/errors.d.ts +52 -0
- package/dist/errors.js +229 -0
- package/dist/formatter.d.ts +5 -0
- package/dist/formatter.js +361 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +165 -0
- package/dist/interpreter.d.ts +39 -0
- package/dist/interpreter.js +668 -0
- package/dist/ir.d.ts +126 -0
- package/dist/ir.js +610 -0
- package/dist/lexer.d.ts +79 -0
- package/dist/lexer.js +335 -0
- package/dist/linter.d.ts +15 -0
- package/dist/linter.js +382 -0
- package/dist/lsp.d.ts +1 -0
- package/dist/lsp.js +253 -0
- package/dist/modules.d.ts +24 -0
- package/dist/modules.js +115 -0
- package/dist/optimizer.d.ts +17 -0
- package/dist/optimizer.js +481 -0
- package/dist/package-manager.d.ts +31 -0
- package/dist/package-manager.js +180 -0
- package/dist/parser.d.ts +42 -0
- package/dist/parser.js +779 -0
- package/dist/repl.d.ts +1 -0
- package/dist/repl.js +120 -0
- package/dist/security.d.ts +48 -0
- package/dist/security.js +198 -0
- package/dist/semantic.d.ts +7 -0
- package/dist/semantic.js +327 -0
- package/dist/typechecker.d.ts +7 -0
- package/dist/typechecker.js +132 -0
- package/dist/version.d.ts +26 -0
- package/dist/version.js +71 -0
- package/package.json +51 -0
package/dist/errors.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// Arc Language Error Reporting System - Rich, friendly error messages
|
|
2
|
+
// Error codes
|
|
3
|
+
export var ErrorCode;
|
|
4
|
+
(function (ErrorCode) {
|
|
5
|
+
// Parse errors (ARC001-ARC099)
|
|
6
|
+
ErrorCode["UNEXPECTED_TOKEN"] = "ARC001";
|
|
7
|
+
ErrorCode["MISSING_CLOSING_PAREN"] = "ARC002";
|
|
8
|
+
ErrorCode["MISSING_CLOSING_BRACKET"] = "ARC003";
|
|
9
|
+
ErrorCode["MISSING_CLOSING_BRACE"] = "ARC004";
|
|
10
|
+
ErrorCode["EXPECTED_EXPRESSION"] = "ARC005";
|
|
11
|
+
ErrorCode["INVALID_ASSIGNMENT"] = "ARC006";
|
|
12
|
+
ErrorCode["UNTERMINATED_STRING"] = "ARC007";
|
|
13
|
+
ErrorCode["INVALID_NUMBER"] = "ARC008";
|
|
14
|
+
// Type errors (ARC100-ARC199)
|
|
15
|
+
ErrorCode["TYPE_MISMATCH"] = "ARC100";
|
|
16
|
+
ErrorCode["INVALID_OPERATOR"] = "ARC101";
|
|
17
|
+
ErrorCode["NOT_CALLABLE"] = "ARC102";
|
|
18
|
+
ErrorCode["WRONG_ARITY"] = "ARC103";
|
|
19
|
+
// Runtime errors (ARC200-ARC299)
|
|
20
|
+
ErrorCode["UNDEFINED_VARIABLE"] = "ARC200";
|
|
21
|
+
ErrorCode["IMMUTABLE_REASSIGN"] = "ARC201";
|
|
22
|
+
ErrorCode["INDEX_OUT_OF_BOUNDS"] = "ARC202";
|
|
23
|
+
ErrorCode["DIVISION_BY_ZERO"] = "ARC203";
|
|
24
|
+
ErrorCode["NOT_ITERABLE"] = "ARC204";
|
|
25
|
+
ErrorCode["ASSERTION_FAILED"] = "ARC205";
|
|
26
|
+
ErrorCode["PROPERTY_ACCESS"] = "ARC206";
|
|
27
|
+
// Import errors (ARC300-ARC399)
|
|
28
|
+
ErrorCode["MODULE_NOT_FOUND"] = "ARC300";
|
|
29
|
+
ErrorCode["CIRCULAR_IMPORT"] = "ARC301";
|
|
30
|
+
// Security errors (ARC400-ARC499)
|
|
31
|
+
ErrorCode["SOURCE_TOO_LARGE"] = "ARC400";
|
|
32
|
+
ErrorCode["NESTING_TOO_DEEP"] = "ARC401";
|
|
33
|
+
ErrorCode["EXECUTION_LIMIT"] = "ARC402";
|
|
34
|
+
ErrorCode["RECURSION_LIMIT"] = "ARC403";
|
|
35
|
+
ErrorCode["TOOL_CALL_BLOCKED"] = "ARC404";
|
|
36
|
+
ErrorCode["IMPORT_BLOCKED"] = "ARC405";
|
|
37
|
+
ErrorCode["TIMEOUT"] = "ARC406";
|
|
38
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
39
|
+
// ANSI color codes
|
|
40
|
+
const COLORS = {
|
|
41
|
+
red: "\x1b[31m",
|
|
42
|
+
yellow: "\x1b[33m",
|
|
43
|
+
cyan: "\x1b[36m",
|
|
44
|
+
gray: "\x1b[90m",
|
|
45
|
+
bold: "\x1b[1m",
|
|
46
|
+
reset: "\x1b[0m",
|
|
47
|
+
underline: "\x1b[4m",
|
|
48
|
+
};
|
|
49
|
+
function noColor(s) {
|
|
50
|
+
return s.replace(/\x1b\[\d+m/g, "");
|
|
51
|
+
}
|
|
52
|
+
// Levenshtein distance for "did you mean?" suggestions
|
|
53
|
+
export function levenshtein(a, b) {
|
|
54
|
+
const m = a.length, n = b.length;
|
|
55
|
+
if (m === 0)
|
|
56
|
+
return n;
|
|
57
|
+
if (n === 0)
|
|
58
|
+
return m;
|
|
59
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
60
|
+
for (let i = 0; i <= m; i++)
|
|
61
|
+
dp[i][0] = i;
|
|
62
|
+
for (let j = 0; j <= n; j++)
|
|
63
|
+
dp[0][j] = j;
|
|
64
|
+
for (let i = 1; i <= m; i++) {
|
|
65
|
+
for (let j = 1; j <= n; j++) {
|
|
66
|
+
dp[i][j] = a[i - 1] === b[j - 1]
|
|
67
|
+
? dp[i - 1][j - 1]
|
|
68
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return dp[m][n];
|
|
72
|
+
}
|
|
73
|
+
// Find closest match from a list of candidates
|
|
74
|
+
export function findClosestMatch(name, candidates, maxDistance = 3) {
|
|
75
|
+
let best = null;
|
|
76
|
+
let bestDist = maxDistance + 1;
|
|
77
|
+
for (const c of candidates) {
|
|
78
|
+
const d = levenshtein(name, c);
|
|
79
|
+
if (d < bestDist) {
|
|
80
|
+
bestDist = d;
|
|
81
|
+
best = c;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return best;
|
|
85
|
+
}
|
|
86
|
+
// Format a rich error message with source snippet
|
|
87
|
+
export function formatError(error, useColor = true) {
|
|
88
|
+
const c = useColor ? COLORS : { red: "", yellow: "", cyan: "", gray: "", bold: "", reset: "", underline: "" };
|
|
89
|
+
const lines = [];
|
|
90
|
+
// Header: category[code]: message
|
|
91
|
+
lines.push(`${c.red}${c.bold}${error.category}[${error.code}]${c.reset}: ${c.bold}${error.message}${c.reset}`);
|
|
92
|
+
// Source snippet with error pointer
|
|
93
|
+
if (error.source && error.loc) {
|
|
94
|
+
const sourceLines = error.source.split("\n");
|
|
95
|
+
const lineIdx = error.loc.line - 1;
|
|
96
|
+
if (lineIdx >= 0 && lineIdx < sourceLines.length) {
|
|
97
|
+
const lineNum = error.loc.line;
|
|
98
|
+
const padding = String(lineNum).length;
|
|
99
|
+
// Line before (context)
|
|
100
|
+
if (lineIdx > 0) {
|
|
101
|
+
lines.push(`${c.gray}${String(lineNum - 1).padStart(padding)} │${c.reset} ${sourceLines[lineIdx - 1]}`);
|
|
102
|
+
}
|
|
103
|
+
// Error line
|
|
104
|
+
lines.push(`${c.cyan}${String(lineNum).padStart(padding)} │${c.reset} ${sourceLines[lineIdx]}`);
|
|
105
|
+
// Underline pointer
|
|
106
|
+
const col = Math.max(0, error.loc.col - 1);
|
|
107
|
+
const pointer = " ".repeat(padding) + " │ " + " ".repeat(col) + `${c.red}^^^${c.reset}`;
|
|
108
|
+
lines.push(pointer);
|
|
109
|
+
// Line after (context)
|
|
110
|
+
if (lineIdx + 1 < sourceLines.length) {
|
|
111
|
+
lines.push(`${c.gray}${String(lineNum + 1).padStart(padding)} │${c.reset} ${sourceLines[lineIdx + 1]}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Suggestion
|
|
116
|
+
if (error.suggestion) {
|
|
117
|
+
lines.push(`${c.yellow}hint${c.reset}: ${error.suggestion}`);
|
|
118
|
+
}
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
// Create specific error constructors
|
|
122
|
+
export function undefinedVariableError(name, candidates, loc, source) {
|
|
123
|
+
const suggestion = findClosestMatch(name, candidates);
|
|
124
|
+
return {
|
|
125
|
+
code: ErrorCode.UNDEFINED_VARIABLE,
|
|
126
|
+
category: "RuntimeError",
|
|
127
|
+
message: `Undefined variable '${name}'`,
|
|
128
|
+
loc,
|
|
129
|
+
source,
|
|
130
|
+
suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function parseError(message, loc, source, suggestion) {
|
|
134
|
+
// Auto-detect suggestion from message
|
|
135
|
+
if (!suggestion) {
|
|
136
|
+
if (message.includes("Expected RParen") || message.includes("Expected )")) {
|
|
137
|
+
suggestion = "Missing closing parenthesis ')'";
|
|
138
|
+
}
|
|
139
|
+
else if (message.includes("Expected RBracket") || message.includes("Expected ]")) {
|
|
140
|
+
suggestion = "Missing closing bracket ']'";
|
|
141
|
+
}
|
|
142
|
+
else if (message.includes("Expected RBrace") || message.includes("Expected }")) {
|
|
143
|
+
suggestion = "Missing closing brace '}'";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
code: ErrorCode.UNEXPECTED_TOKEN,
|
|
148
|
+
category: "ParseError",
|
|
149
|
+
message,
|
|
150
|
+
loc,
|
|
151
|
+
source,
|
|
152
|
+
suggestion,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export function typeError(message, loc, source) {
|
|
156
|
+
return {
|
|
157
|
+
code: ErrorCode.TYPE_MISMATCH,
|
|
158
|
+
category: "TypeError",
|
|
159
|
+
message,
|
|
160
|
+
loc,
|
|
161
|
+
source,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export function runtimeError(code, message, loc, source, suggestion) {
|
|
165
|
+
return {
|
|
166
|
+
code,
|
|
167
|
+
category: "RuntimeError",
|
|
168
|
+
message,
|
|
169
|
+
loc,
|
|
170
|
+
source,
|
|
171
|
+
suggestion,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
export function importError(message, loc, source) {
|
|
175
|
+
return {
|
|
176
|
+
code: ErrorCode.MODULE_NOT_FOUND,
|
|
177
|
+
category: "ImportError",
|
|
178
|
+
message,
|
|
179
|
+
loc,
|
|
180
|
+
source,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export function securityError(code, message) {
|
|
184
|
+
return {
|
|
185
|
+
code,
|
|
186
|
+
category: "SecurityError",
|
|
187
|
+
message,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// Pretty-print an error that was caught during execution
|
|
191
|
+
export function prettyPrintError(err, source, useColor = true) {
|
|
192
|
+
// Try to extract location from error message
|
|
193
|
+
const locMatch = err.message.match(/at line (\d+)(?:, col (\d+))?/);
|
|
194
|
+
const loc = locMatch ? { line: parseInt(locMatch[1]), col: parseInt(locMatch[2] || "1") } : undefined;
|
|
195
|
+
// Detect error category
|
|
196
|
+
let category = "RuntimeError";
|
|
197
|
+
let code = ErrorCode.UNDEFINED_VARIABLE;
|
|
198
|
+
let suggestion;
|
|
199
|
+
if (err.message.includes("Parse error")) {
|
|
200
|
+
category = "ParseError";
|
|
201
|
+
code = ErrorCode.UNEXPECTED_TOKEN;
|
|
202
|
+
}
|
|
203
|
+
else if (err.message.includes("Undefined variable")) {
|
|
204
|
+
code = ErrorCode.UNDEFINED_VARIABLE;
|
|
205
|
+
const nameMatch = err.message.match(/Undefined variable: (\w+)/);
|
|
206
|
+
if (nameMatch) {
|
|
207
|
+
suggestion = `Check that '${nameMatch[1]}' is defined before use`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (err.message.includes("Cannot reassign immutable")) {
|
|
211
|
+
code = ErrorCode.IMMUTABLE_REASSIGN;
|
|
212
|
+
suggestion = "Use 'let mut' to declare a mutable variable";
|
|
213
|
+
}
|
|
214
|
+
else if (err.message.includes("Not callable")) {
|
|
215
|
+
code = ErrorCode.NOT_CALLABLE;
|
|
216
|
+
}
|
|
217
|
+
else if (err.message.includes("SecurityError") || err.name === "SecurityError") {
|
|
218
|
+
category = "SecurityError";
|
|
219
|
+
code = ErrorCode.EXECUTION_LIMIT;
|
|
220
|
+
}
|
|
221
|
+
return formatError({ code, category, message: err.message, loc, source, suggestion }, useColor);
|
|
222
|
+
}
|
|
223
|
+
let prettyErrorsEnabled = true;
|
|
224
|
+
export function setPrettyErrors(enabled) {
|
|
225
|
+
prettyErrorsEnabled = enabled;
|
|
226
|
+
}
|
|
227
|
+
export function isPrettyErrorsEnabled() {
|
|
228
|
+
return prettyErrorsEnabled;
|
|
229
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// Arc Language Code Formatter
|
|
2
|
+
// Pretty-prints Arc source with consistent style
|
|
3
|
+
import { lex } from "./lexer.js";
|
|
4
|
+
import { parse } from "./parser.js";
|
|
5
|
+
const DEFAULT_OPTIONS = {
|
|
6
|
+
indentSize: 2,
|
|
7
|
+
maxLineLength: 100,
|
|
8
|
+
};
|
|
9
|
+
// Extract comments from source (lexer skips them)
|
|
10
|
+
function extractComments(source) {
|
|
11
|
+
const comments = [];
|
|
12
|
+
let line = 1, col = 1;
|
|
13
|
+
let i = 0;
|
|
14
|
+
while (i < source.length) {
|
|
15
|
+
if (source[i] === '"') {
|
|
16
|
+
i++;
|
|
17
|
+
col++;
|
|
18
|
+
while (i < source.length && source[i] !== '"') {
|
|
19
|
+
if (source[i] === '\\') {
|
|
20
|
+
i++;
|
|
21
|
+
col++;
|
|
22
|
+
}
|
|
23
|
+
if (source[i] === '\n') {
|
|
24
|
+
line++;
|
|
25
|
+
col = 1;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
col++;
|
|
29
|
+
}
|
|
30
|
+
i++;
|
|
31
|
+
}
|
|
32
|
+
if (i < source.length) {
|
|
33
|
+
i++;
|
|
34
|
+
col++;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (source[i] === '#') {
|
|
38
|
+
const startLine = line, startCol = col;
|
|
39
|
+
let text = '';
|
|
40
|
+
while (i < source.length && source[i] !== '\n') {
|
|
41
|
+
text += source[i];
|
|
42
|
+
i++;
|
|
43
|
+
col++;
|
|
44
|
+
}
|
|
45
|
+
comments.push({ text, line: startLine, col: startCol });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (source[i] === '\n') {
|
|
49
|
+
line++;
|
|
50
|
+
col = 1;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
col++;
|
|
54
|
+
}
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return comments;
|
|
59
|
+
}
|
|
60
|
+
// Map comments to the nearest following AST node line
|
|
61
|
+
function buildCommentMap(comments, stmts) {
|
|
62
|
+
// Map: stmtIndex -> comments that appear before it
|
|
63
|
+
const map = new Map();
|
|
64
|
+
if (comments.length === 0 || stmts.length === 0)
|
|
65
|
+
return map;
|
|
66
|
+
let ci = 0;
|
|
67
|
+
for (let si = 0; si < stmts.length; si++) {
|
|
68
|
+
const stmtLine = stmts[si].loc.line;
|
|
69
|
+
const before = [];
|
|
70
|
+
while (ci < comments.length && comments[ci].line <= stmtLine) {
|
|
71
|
+
before.push(comments[ci]);
|
|
72
|
+
ci++;
|
|
73
|
+
}
|
|
74
|
+
if (before.length > 0)
|
|
75
|
+
map.set(si, before);
|
|
76
|
+
}
|
|
77
|
+
// Trailing comments (after last stmt)
|
|
78
|
+
if (ci < comments.length) {
|
|
79
|
+
const trailing = [];
|
|
80
|
+
while (ci < comments.length) {
|
|
81
|
+
trailing.push(comments[ci]);
|
|
82
|
+
ci++;
|
|
83
|
+
}
|
|
84
|
+
map.set(stmts.length, trailing);
|
|
85
|
+
}
|
|
86
|
+
return map;
|
|
87
|
+
}
|
|
88
|
+
export function format(source, options) {
|
|
89
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
90
|
+
const comments = extractComments(source);
|
|
91
|
+
const tokens = lex(source);
|
|
92
|
+
const ast = parse(tokens);
|
|
93
|
+
const commentMap = buildCommentMap(comments, ast.stmts);
|
|
94
|
+
const lines = [];
|
|
95
|
+
function emit(line) { lines.push(line); }
|
|
96
|
+
function indent(depth) { return ' '.repeat(depth * opts.indentSize); }
|
|
97
|
+
function formatExpr(expr, depth) {
|
|
98
|
+
switch (expr.kind) {
|
|
99
|
+
case "IntLiteral": return String(expr.value);
|
|
100
|
+
case "FloatLiteral": return String(expr.value);
|
|
101
|
+
case "BoolLiteral": return expr.value ? "true" : "false";
|
|
102
|
+
case "NilLiteral": return "nil";
|
|
103
|
+
case "StringLiteral": return `"${escapeString(expr.value)}"`;
|
|
104
|
+
case "StringInterp": {
|
|
105
|
+
let s = '"';
|
|
106
|
+
for (const part of expr.parts) {
|
|
107
|
+
if (typeof part === "string")
|
|
108
|
+
s += escapeString(part);
|
|
109
|
+
else
|
|
110
|
+
s += `{${formatExpr(part, depth)}}`;
|
|
111
|
+
}
|
|
112
|
+
return s + '"';
|
|
113
|
+
}
|
|
114
|
+
case "Identifier": return expr.name;
|
|
115
|
+
case "BinaryExpr": {
|
|
116
|
+
const l = formatExpr(expr.left, depth);
|
|
117
|
+
const r = formatExpr(expr.right, depth);
|
|
118
|
+
return `${l} ${expr.op} ${r}`;
|
|
119
|
+
}
|
|
120
|
+
case "UnaryExpr": {
|
|
121
|
+
const operand = formatExpr(expr.operand, depth);
|
|
122
|
+
return expr.op === "not" ? `not ${operand}` : `${expr.op}${operand}`;
|
|
123
|
+
}
|
|
124
|
+
case "CallExpr": {
|
|
125
|
+
const callee = formatExpr(expr.callee, depth);
|
|
126
|
+
const args = expr.args.map(a => formatExpr(a, depth)).join(", ");
|
|
127
|
+
return `${callee}(${args})`;
|
|
128
|
+
}
|
|
129
|
+
case "MemberExpr":
|
|
130
|
+
return `${formatExpr(expr.object, depth)}.${expr.property}`;
|
|
131
|
+
case "IndexExpr":
|
|
132
|
+
return `${formatExpr(expr.object, depth)}[${formatExpr(expr.index, depth)}]`;
|
|
133
|
+
case "PipelineExpr": {
|
|
134
|
+
const l = formatExpr(expr.left, depth);
|
|
135
|
+
const r = formatExpr(expr.right, depth);
|
|
136
|
+
const single = `${l} |> ${r}`;
|
|
137
|
+
if (single.length + depth * opts.indentSize <= opts.maxLineLength) {
|
|
138
|
+
return single;
|
|
139
|
+
}
|
|
140
|
+
return `${l}\n${indent(depth + 1)}|> ${r}`;
|
|
141
|
+
}
|
|
142
|
+
case "IfExpr": {
|
|
143
|
+
const cond = formatExpr(expr.condition, depth);
|
|
144
|
+
const then = formatBlockExpr(expr.then, depth);
|
|
145
|
+
if (expr.else_) {
|
|
146
|
+
if (expr.else_.kind === "IfExpr") {
|
|
147
|
+
return `if ${cond} ${then} el ${formatExpr(expr.else_, depth)}`;
|
|
148
|
+
}
|
|
149
|
+
const el = formatBlockExpr(expr.else_, depth);
|
|
150
|
+
return `if ${cond} ${then} el ${el}`;
|
|
151
|
+
}
|
|
152
|
+
return `if ${cond} ${then}`;
|
|
153
|
+
}
|
|
154
|
+
case "MatchExpr": {
|
|
155
|
+
const subject = formatExpr(expr.subject, depth);
|
|
156
|
+
const armsStr = expr.arms.map(arm => {
|
|
157
|
+
const pat = formatPattern(arm.pattern);
|
|
158
|
+
const guard = arm.guard ? ` if ${formatExpr(arm.guard, depth + 1)}` : '';
|
|
159
|
+
const body = formatExpr(arm.body, depth + 1);
|
|
160
|
+
return `${indent(depth + 1)}${pat}${guard} => ${body}`;
|
|
161
|
+
}).join(',\n');
|
|
162
|
+
return `match ${subject} {\n${armsStr}\n${indent(depth)}}`;
|
|
163
|
+
}
|
|
164
|
+
case "LambdaExpr": {
|
|
165
|
+
const params = expr.params.length === 1
|
|
166
|
+
? expr.params[0]
|
|
167
|
+
: `(${expr.params.join(", ")})`;
|
|
168
|
+
return `${params} => ${formatExpr(expr.body, depth)}`;
|
|
169
|
+
}
|
|
170
|
+
case "ListLiteral": {
|
|
171
|
+
if (expr.elements.length === 0)
|
|
172
|
+
return "[]";
|
|
173
|
+
const elems = expr.elements.map(e => formatExpr(e, depth));
|
|
174
|
+
const single = `[${elems.join(", ")}]`;
|
|
175
|
+
if (single.length + depth * opts.indentSize <= opts.maxLineLength)
|
|
176
|
+
return single;
|
|
177
|
+
return `[\n${elems.map(e => `${indent(depth + 1)}${e}`).join(',\n')}\n${indent(depth)}]`;
|
|
178
|
+
}
|
|
179
|
+
case "MapLiteral": {
|
|
180
|
+
if (expr.entries.length === 0)
|
|
181
|
+
return "{}";
|
|
182
|
+
const entries = expr.entries.map(e => {
|
|
183
|
+
const key = typeof e.key === "string" ? e.key : formatExpr(e.key, depth + 1);
|
|
184
|
+
return `${key}: ${formatExpr(e.value, depth + 1)}`;
|
|
185
|
+
});
|
|
186
|
+
const single = `{ ${entries.join(", ")} }`;
|
|
187
|
+
if (single.length + depth * opts.indentSize <= opts.maxLineLength)
|
|
188
|
+
return single;
|
|
189
|
+
return `{\n${entries.map(e => `${indent(depth + 1)}${e}`).join(',\n')}\n${indent(depth)}}`;
|
|
190
|
+
}
|
|
191
|
+
case "ListComprehension": {
|
|
192
|
+
const ex = formatExpr(expr.expr, depth);
|
|
193
|
+
const iter = formatExpr(expr.iterable, depth);
|
|
194
|
+
const filter = expr.filter ? ` if ${formatExpr(expr.filter, depth)}` : '';
|
|
195
|
+
return `[${ex} for ${expr.variable} in ${iter}${filter}]`;
|
|
196
|
+
}
|
|
197
|
+
case "ToolCallExpr": {
|
|
198
|
+
const arg = formatExpr(expr.arg, depth);
|
|
199
|
+
const body = expr.body ? ` ${formatBlockExpr(expr.body, depth)}` : '';
|
|
200
|
+
return `@${expr.method} ${arg}${body}`;
|
|
201
|
+
}
|
|
202
|
+
case "RangeExpr":
|
|
203
|
+
return `${formatExpr(expr.start, depth)}..${formatExpr(expr.end, depth)}`;
|
|
204
|
+
case "BlockExpr":
|
|
205
|
+
return formatBlockInline(expr, depth);
|
|
206
|
+
case "AsyncExpr":
|
|
207
|
+
return `async ${formatBlockExpr(expr.body, depth)}`;
|
|
208
|
+
case "AwaitExpr":
|
|
209
|
+
return `await ${formatExpr(expr.expr, depth)}`;
|
|
210
|
+
case "FetchExpr": {
|
|
211
|
+
const targets = expr.targets.map(t => formatExpr(t, depth)).join(", ");
|
|
212
|
+
return `fetch [${targets}]`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function formatBlockExpr(expr, depth) {
|
|
217
|
+
if (expr.kind === "BlockExpr")
|
|
218
|
+
return formatBlockInline(expr, depth);
|
|
219
|
+
return formatExpr(expr, depth);
|
|
220
|
+
}
|
|
221
|
+
function formatBlockInline(block, depth) {
|
|
222
|
+
if (block.stmts.length === 0)
|
|
223
|
+
return "{}";
|
|
224
|
+
if (block.stmts.length === 1) {
|
|
225
|
+
const s = formatStmtStr(block.stmts[0], depth + 1);
|
|
226
|
+
const single = `{ ${s} }`;
|
|
227
|
+
if (single.length + depth * opts.indentSize <= opts.maxLineLength)
|
|
228
|
+
return single;
|
|
229
|
+
}
|
|
230
|
+
const body = block.stmts.map(s => `${indent(depth + 1)}${formatStmtStr(s, depth + 1)}`).join('\n');
|
|
231
|
+
return `{\n${body}\n${indent(depth)}}`;
|
|
232
|
+
}
|
|
233
|
+
function formatPattern(pat) {
|
|
234
|
+
switch (pat.kind) {
|
|
235
|
+
case "WildcardPattern": return "_";
|
|
236
|
+
case "LiteralPattern":
|
|
237
|
+
if (pat.value === null)
|
|
238
|
+
return "nil";
|
|
239
|
+
if (typeof pat.value === "string")
|
|
240
|
+
return `"${escapeString(pat.value)}"`;
|
|
241
|
+
return String(pat.value);
|
|
242
|
+
case "BindingPattern": return pat.name;
|
|
243
|
+
case "ArrayPattern": return `[${pat.elements.map(formatPattern).join(", ")}]`;
|
|
244
|
+
case "OrPattern": return pat.patterns.map(formatPattern).join(" | ");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function formatTypeExpr(t) {
|
|
248
|
+
switch (t.kind) {
|
|
249
|
+
case "NamedType": return t.name;
|
|
250
|
+
case "RecordType": {
|
|
251
|
+
const fields = t.fields.map(f => `${f.name}: ${formatTypeExpr(f.type)}`).join(", ");
|
|
252
|
+
return `{ ${fields} }`;
|
|
253
|
+
}
|
|
254
|
+
case "UnionType": return t.variants.map(formatTypeExpr).join(" | ");
|
|
255
|
+
case "FunctionType": {
|
|
256
|
+
const params = t.params.map(formatTypeExpr).join(", ");
|
|
257
|
+
return `(${params}) -> ${formatTypeExpr(t.ret)}`;
|
|
258
|
+
}
|
|
259
|
+
case "ConstrainedType":
|
|
260
|
+
return `${formatTypeExpr(t.base)} ${t.constraint} ${formatExpr(t.predicate, 0)}`;
|
|
261
|
+
case "EnumType":
|
|
262
|
+
return t.variants.map(v => {
|
|
263
|
+
if (v.params)
|
|
264
|
+
return `${v.name}(${v.params.map(formatTypeExpr).join(", ")})`;
|
|
265
|
+
return v.name;
|
|
266
|
+
}).join(" | ");
|
|
267
|
+
case "GenericType":
|
|
268
|
+
return `${t.name}<${t.params.map(formatTypeExpr).join(", ")}>`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function formatStmtStr(stmt, depth) {
|
|
272
|
+
switch (stmt.kind) {
|
|
273
|
+
case "LetStmt": {
|
|
274
|
+
const pub = stmt.pub ? "pub " : "";
|
|
275
|
+
const mut = stmt.mutable ? "mut " : "";
|
|
276
|
+
const name = typeof stmt.name === "string"
|
|
277
|
+
? stmt.name
|
|
278
|
+
: stmt.name.type === "object"
|
|
279
|
+
? `{ ${stmt.name.names.join(", ")} }`
|
|
280
|
+
: `[${stmt.name.names.join(", ")}]`;
|
|
281
|
+
return `${pub}let ${mut}${name} = ${formatExpr(stmt.value, depth)}`;
|
|
282
|
+
}
|
|
283
|
+
case "FnStmt": {
|
|
284
|
+
const pub = stmt.pub ? "pub " : "";
|
|
285
|
+
const async_ = stmt.isAsync ? "async " : "";
|
|
286
|
+
const params = stmt.params.join(", ");
|
|
287
|
+
if (stmt.body.kind === "BlockExpr") {
|
|
288
|
+
return `${pub}${async_}fn ${stmt.name}(${params}) ${formatBlockInline(stmt.body, depth)}`;
|
|
289
|
+
}
|
|
290
|
+
return `${pub}${async_}fn ${stmt.name}(${params}) => ${formatExpr(stmt.body, depth)}`;
|
|
291
|
+
}
|
|
292
|
+
case "ForStmt":
|
|
293
|
+
return `for ${stmt.variable} in ${formatExpr(stmt.iterable, depth)} ${formatBlockExpr(stmt.body, depth)}`;
|
|
294
|
+
case "DoStmt": {
|
|
295
|
+
const kw = stmt.isWhile ? "while" : "until";
|
|
296
|
+
return `do ${formatBlockExpr(stmt.body, depth)} ${kw} ${formatExpr(stmt.condition, depth)}`;
|
|
297
|
+
}
|
|
298
|
+
case "ExprStmt":
|
|
299
|
+
return formatExpr(stmt.expr, depth);
|
|
300
|
+
case "UseStmt": {
|
|
301
|
+
const path = stmt.path.join("/");
|
|
302
|
+
if (stmt.wildcard)
|
|
303
|
+
return `use ${path}: *`;
|
|
304
|
+
if (stmt.imports)
|
|
305
|
+
return `use ${path}: ${stmt.imports.join(", ")}`;
|
|
306
|
+
return `use ${path}`;
|
|
307
|
+
}
|
|
308
|
+
case "TypeStmt": {
|
|
309
|
+
const pub = stmt.pub ? "pub " : "";
|
|
310
|
+
return `${pub}type ${stmt.name} = ${formatTypeExpr(stmt.def)}`;
|
|
311
|
+
}
|
|
312
|
+
case "AssignStmt":
|
|
313
|
+
return `${stmt.target} = ${formatExpr(stmt.value, depth)}`;
|
|
314
|
+
case "MemberAssignStmt":
|
|
315
|
+
return `${formatExpr(stmt.object, depth)}.${stmt.property} = ${formatExpr(stmt.value, depth)}`;
|
|
316
|
+
case "IndexAssignStmt":
|
|
317
|
+
return `${formatExpr(stmt.object, depth)}[${formatExpr(stmt.index, depth)}] = ${formatExpr(stmt.value, depth)}`;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Emit top-level statements with comments and blank lines between declarations
|
|
321
|
+
let prevKind = '';
|
|
322
|
+
for (let i = 0; i < ast.stmts.length; i++) {
|
|
323
|
+
const stmtComments = commentMap.get(i);
|
|
324
|
+
if (stmtComments) {
|
|
325
|
+
for (const c of stmtComments) {
|
|
326
|
+
// If comment is on same line as previous stmt, it was inline — but we can't detect easily
|
|
327
|
+
// so emit as standalone line
|
|
328
|
+
emit(c.text);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const stmt = ast.stmts[i];
|
|
332
|
+
const isDecl = stmt.kind === "FnStmt" || stmt.kind === "TypeStmt";
|
|
333
|
+
const prevIsDecl = prevKind === "FnStmt" || prevKind === "TypeStmt";
|
|
334
|
+
// Blank line between top-level declarations
|
|
335
|
+
if (i > 0 && (isDecl || prevIsDecl)) {
|
|
336
|
+
// Only add if there isn't already a blank line from comments
|
|
337
|
+
if (!stmtComments || stmtComments.length === 0) {
|
|
338
|
+
emit('');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
emit(formatStmtStr(stmt, 0));
|
|
342
|
+
prevKind = stmt.kind;
|
|
343
|
+
}
|
|
344
|
+
// Trailing comments
|
|
345
|
+
const trailingComments = commentMap.get(ast.stmts.length);
|
|
346
|
+
if (trailingComments) {
|
|
347
|
+
for (const c of trailingComments)
|
|
348
|
+
emit(c.text);
|
|
349
|
+
}
|
|
350
|
+
// Normalize trailing newline
|
|
351
|
+
let result = lines.join('\n');
|
|
352
|
+
result = result.replace(/\n+$/, '') + '\n';
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
function escapeString(s) {
|
|
356
|
+
return s
|
|
357
|
+
.replace(/\\/g, '\\\\')
|
|
358
|
+
.replace(/"/g, '\\"')
|
|
359
|
+
.replace(/\n/g, '\\n')
|
|
360
|
+
.replace(/\t/g, '\\t');
|
|
361
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|