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/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,5 @@
1
+ export interface FormatOptions {
2
+ indentSize: number;
3
+ maxLineLength: number;
4
+ }
5
+ export declare function format(source: string, options?: Partial<FormatOptions>): string;
@@ -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
+ }
@@ -0,0 +1 @@
1
+ export {};