arc-lang 0.5.2 → 0.5.3

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 CHANGED
@@ -177,7 +177,7 @@ export interface OrPattern {
177
177
  patterns: Pattern[];
178
178
  loc: Loc;
179
179
  }
180
- export type Stmt = LetStmt | FnStmt | ForStmt | DoStmt | ExprStmt | UseStmt | TypeStmt | AssignStmt | MemberAssignStmt | IndexAssignStmt;
180
+ export type Stmt = LetStmt | FnStmt | ForStmt | DoStmt | ExprStmt | UseStmt | TypeStmt | AssignStmt | MemberAssignStmt | IndexAssignStmt | RetStmt;
181
181
  export interface AssignStmt {
182
182
  kind: "AssignStmt";
183
183
  target: string;
@@ -198,6 +198,11 @@ export interface IndexAssignStmt {
198
198
  value: Expr;
199
199
  loc: Loc;
200
200
  }
201
+ export interface RetStmt {
202
+ kind: "RetStmt";
203
+ value?: Expr;
204
+ loc: Loc;
205
+ }
201
206
  export interface LetStmt {
202
207
  kind: "LetStmt";
203
208
  name: string | DestructureTarget;
package/dist/build.js CHANGED
@@ -127,8 +127,8 @@ export function newProject(name, parentDir) {
127
127
  "dev-dependencies": {},
128
128
  };
129
129
  writeFileSync(resolve(projectDir, "arc.toml"), serializeArcToml(toml));
130
- writeFileSync(resolve(projectDir, "src", "main.arc"), `fn main() do\n let msg = "Hello from ${name}!"\nend\n`);
131
- writeFileSync(resolve(projectDir, "tests", "main.test.arc"), `fn test_main() do\n let x = 1 + 1\nend\n`);
130
+ writeFileSync(resolve(projectDir, "src", "main.arc"), `fn main() {\n let msg = "Hello from ${name}!"\n print(msg)\n}\n`);
131
+ writeFileSync(resolve(projectDir, "tests", "main.test.arc"), `fn test_main() {\n let x = 1 + 1\n print(x)\n}\n`);
132
132
  writeFileSync(resolve(projectDir, "README.md"), `# ${name}\n\nAn Arc project.\n\n## Getting Started\n\n\`\`\`bash\narc build\narc run\n\`\`\`\n`);
133
133
  console.log(`Created project '${name}'`);
134
134
  console.log(` ${name}/arc.toml`);
@@ -74,9 +74,71 @@ function emitFunction(fn, baseIndent = "") {
74
74
  }
75
75
  function emitBlock(block, indent) {
76
76
  const lines = [];
77
- for (const instr of block.instrs) {
78
- lines.push(indent + emitInstr(instr));
77
+ const instrs = block.instrs;
78
+ const sections = [];
79
+ let currentSection = { label: "__start", instrs: [] };
80
+ sections.push(currentSection);
81
+ for (const instr of instrs) {
82
+ if (instr.op === "label") {
83
+ currentSection = { label: instr.name, instrs: [] };
84
+ sections.push(currentSection);
85
+ }
86
+ else {
87
+ currentSection.instrs.push(instr);
88
+ }
79
89
  }
90
+ // If no labels exist, emit linearly (simple case)
91
+ if (sections.length === 1) {
92
+ for (const instr of instrs) {
93
+ lines.push(indent + emitInstr(instr));
94
+ }
95
+ return lines;
96
+ }
97
+ // Use a state-machine approach with a __pc variable and a while/switch loop
98
+ // to properly handle branches and jumps
99
+ const labelToIdx = new Map();
100
+ for (let i = 0; i < sections.length; i++) {
101
+ labelToIdx.set(sections[i].label, i);
102
+ }
103
+ lines.push(`${indent}var __pc = 0;`);
104
+ lines.push(`${indent}__control: while (true) {`);
105
+ lines.push(`${indent} switch (__pc) {`);
106
+ for (let i = 0; i < sections.length; i++) {
107
+ const section = sections[i];
108
+ lines.push(`${indent} case ${i}: /* ${section.label} */`);
109
+ for (const instr of section.instrs) {
110
+ if (instr.op === "jump") {
111
+ const target = labelToIdx.get(instr.target);
112
+ if (target !== undefined) {
113
+ lines.push(`${indent} __pc = ${target}; continue __control;`);
114
+ }
115
+ }
116
+ else if (instr.op === "branch") {
117
+ const ifTrue = labelToIdx.get(instr.ifTrue);
118
+ const ifFalse = labelToIdx.get(instr.ifFalse);
119
+ lines.push(`${indent} if (${S(instr.cond)}) { __pc = ${ifTrue}; } else { __pc = ${ifFalse}; } continue __control;`);
120
+ }
121
+ else if (instr.op === "ret") {
122
+ lines.push(`${indent} ` + emitInstr(instr));
123
+ }
124
+ else {
125
+ lines.push(`${indent} ` + emitInstr(instr));
126
+ }
127
+ }
128
+ // Fall through to next section if no jump/branch/ret at end
129
+ const lastInstr = section.instrs[section.instrs.length - 1];
130
+ if (!lastInstr || (lastInstr.op !== "jump" && lastInstr.op !== "branch" && lastInstr.op !== "ret")) {
131
+ if (i + 1 < sections.length) {
132
+ lines.push(`${indent} __pc = ${i + 1}; continue __control;`);
133
+ }
134
+ else {
135
+ lines.push(`${indent} break __control;`);
136
+ }
137
+ }
138
+ }
139
+ lines.push(`${indent} default: break __control;`);
140
+ lines.push(`${indent} }`);
141
+ lines.push(`${indent}}`);
80
142
  return lines;
81
143
  }
82
144
  function emitInstr(instr) {
package/dist/formatter.js CHANGED
@@ -141,15 +141,39 @@ export function format(source, options) {
141
141
  }
142
142
  case "IfExpr": {
143
143
  const cond = formatExpr(expr.condition, depth);
144
- const then = formatBlockExpr(expr.then, depth);
144
+ const thenInline = formatBlockExpr(expr.then, depth);
145
145
  if (expr.else_) {
146
+ let elInline;
146
147
  if (expr.else_.kind === "IfExpr") {
147
- return `if ${cond} ${then} el ${formatExpr(expr.else_, depth)}`;
148
+ elInline = formatExpr(expr.else_, depth);
148
149
  }
149
- const el = formatBlockExpr(expr.else_, depth);
150
- return `if ${cond} ${then} el ${el}`;
150
+ else {
151
+ elInline = formatBlockExpr(expr.else_, depth);
152
+ }
153
+ const single = `if ${cond} ${thenInline} el ${elInline}`;
154
+ if (single.length + depth * opts.indentSize <= opts.maxLineLength) {
155
+ return single;
156
+ }
157
+ // Force multi-line blocks when single-line is too long
158
+ const thenMulti = expr.then.kind === "BlockExpr"
159
+ ? formatBlockMultiline(expr.then, depth)
160
+ : thenInline;
161
+ if (expr.else_.kind === "IfExpr") {
162
+ return `if ${cond} ${thenMulti} el ${formatExpr(expr.else_, depth)}`;
163
+ }
164
+ const elMulti = expr.else_.kind === "BlockExpr"
165
+ ? formatBlockMultiline(expr.else_, depth)
166
+ : elInline;
167
+ return `if ${cond} ${thenMulti} el ${elMulti}`;
168
+ }
169
+ const single = `if ${cond} ${thenInline}`;
170
+ if (single.length + depth * opts.indentSize <= opts.maxLineLength) {
171
+ return single;
151
172
  }
152
- return `if ${cond} ${then}`;
173
+ const thenMulti = expr.then.kind === "BlockExpr"
174
+ ? formatBlockMultiline(expr.then, depth)
175
+ : thenInline;
176
+ return `if ${cond} ${thenMulti}`;
153
177
  }
154
178
  case "MatchExpr": {
155
179
  const subject = formatExpr(expr.subject, depth);
@@ -218,6 +242,12 @@ export function format(source, options) {
218
242
  return formatBlockInline(expr, depth);
219
243
  return formatExpr(expr, depth);
220
244
  }
245
+ function formatBlockMultiline(block, depth) {
246
+ if (block.stmts.length === 0)
247
+ return "{}";
248
+ const body = block.stmts.map(s => `${indent(depth + 1)}${formatStmtStr(s, depth + 1)}`).join('\n');
249
+ return `{\n${body}\n${indent(depth)}}`;
250
+ }
221
251
  function formatBlockInline(block, depth) {
222
252
  if (block.stmts.length === 0)
223
253
  return "{}";
@@ -315,6 +345,8 @@ export function format(source, options) {
315
345
  return `${formatExpr(stmt.object, depth)}.${stmt.property} = ${formatExpr(stmt.value, depth)}`;
316
346
  case "IndexAssignStmt":
317
347
  return `${formatExpr(stmt.object, depth)}[${formatExpr(stmt.index, depth)}] = ${formatExpr(stmt.value, depth)}`;
348
+ default:
349
+ return `/* unknown stmt: ${stmt.kind} */`;
318
350
  }
319
351
  }
320
352
  // Emit top-level statements with comments and blank lines between declarations
@@ -243,6 +243,13 @@ function makePrelude(env) {
243
243
  env.set(name, fn);
244
244
  }
245
245
  }
246
+ // Return signal — thrown to unwind the stack on `ret`
247
+ class ReturnSignal {
248
+ value;
249
+ constructor(value) {
250
+ this.value = value;
251
+ }
252
+ }
246
253
  // Evaluate expression in tail position — returns TCOSignal for self-recursive tail calls
247
254
  function evalExprTCO(expr, env, fnName) {
248
255
  // Only handle tail-position expressions specially
@@ -317,8 +324,16 @@ function evalExpr(expr, env) {
317
324
  case "+": return left + right;
318
325
  case "-": return left - right;
319
326
  case "*": return left * right;
320
- case "/": return left / right;
321
- case "%": return left % right;
327
+ case "/": {
328
+ if (right === 0)
329
+ throw new Error(`Division by zero at line ${expr.loc.line}`);
330
+ return left / right;
331
+ }
332
+ case "%": {
333
+ if (right === 0)
334
+ throw new Error(`Modulo by zero at line ${expr.loc.line}`);
335
+ return left % right;
336
+ }
322
337
  case "**": return Math.pow(left, right);
323
338
  case "==": return left === right;
324
339
  case "!=": return left !== right;
@@ -353,18 +368,28 @@ function evalExpr(expr, env) {
353
368
  let fn = callee;
354
369
  // Tail call optimization loop: if the function body resolves to
355
370
  // a tail call back to itself, reuse the frame instead of recursing
356
- tailLoop: while (true) {
357
- const fnEnv = new Env(fn.closure);
358
- fn.params.forEach((p, i) => fnEnv.set(p, args[i] ?? null));
359
- const bodyResult = evalExprTCO(fn.body, fnEnv, fn.name);
360
- if (bodyResult && typeof bodyResult === "object" && "__tco" in bodyResult) {
361
- const tco = bodyResult;
362
- args = tco.args;
363
- // fn stays the same — it's a self-recursive tail call
364
- continue tailLoop;
371
+ try {
372
+ tailLoop: while (true) {
373
+ const fnEnv = new Env(fn.closure);
374
+ fn.params.forEach((p, i) => fnEnv.set(p, args[i] ?? null));
375
+ const bodyResult = evalExprTCO(fn.body, fnEnv, fn.name);
376
+ if (bodyResult && typeof bodyResult === "object" && "__tco" in bodyResult) {
377
+ const tco = bodyResult;
378
+ args = tco.args;
379
+ // fn stays the same — it's a self-recursive tail call
380
+ continue tailLoop;
381
+ }
382
+ result = bodyResult;
383
+ break;
384
+ }
385
+ }
386
+ catch (e) {
387
+ if (e instanceof ReturnSignal) {
388
+ result = e.value;
389
+ }
390
+ else {
391
+ throw e;
365
392
  }
366
- result = bodyResult;
367
- break;
368
393
  }
369
394
  }
370
395
  else {
@@ -615,6 +640,10 @@ function evalStmt(stmt, env) {
615
640
  }
616
641
  throw new Error(`Cannot assign index on ${toStr(obj)}`);
617
642
  }
643
+ case "RetStmt": {
644
+ const value = stmt.value ? evalExpr(stmt.value, env) : null;
645
+ throw new ReturnSignal(value);
646
+ }
618
647
  case "ExprStmt": return evalExpr(stmt.expr, env);
619
648
  case "UseStmt": {
620
649
  // Module imports handled by interpretWithFile; no-op if no file context
package/dist/lexer.js CHANGED
@@ -135,6 +135,10 @@ export function lex(source) {
135
135
  const parts = [];
136
136
  let hasInterp = false;
137
137
  while (i < source.length && peek() !== '"') {
138
+ if (peek() === "\n") {
139
+ // Unterminated string - newline before closing quote
140
+ throw new Error(`Unterminated string literal at line ${sl}, col ${sc}`);
141
+ }
138
142
  if (peek() === "{") {
139
143
  hasInterp = true;
140
144
  if (str.length > 0 || parts.length === 0) {
@@ -162,6 +166,9 @@ export function lex(source) {
162
166
  }
163
167
  if (peek() === "\\") {
164
168
  advance();
169
+ if (i >= source.length) {
170
+ throw new Error(`Unterminated string literal (escape at end of file) at line ${sl}, col ${sc}`);
171
+ }
165
172
  const esc = advance();
166
173
  if (esc === "n")
167
174
  str += "\n";
@@ -177,6 +184,9 @@ export function lex(source) {
177
184
  }
178
185
  str += advance();
179
186
  }
187
+ if (i >= source.length) {
188
+ throw new Error(`Unterminated string literal at line ${sl}, col ${sc}`);
189
+ }
180
190
  if (peek() === '"')
181
191
  advance(); // skip closing quote
182
192
  if (hasInterp) {
package/dist/parser.d.ts CHANGED
@@ -24,6 +24,7 @@ export declare class Parser {
24
24
  private parseDo;
25
25
  private parseUse;
26
26
  private parseType;
27
+ private parseRet;
27
28
  private parseTypeExpr;
28
29
  private parseTypeAtom;
29
30
  private parseBlock;
package/dist/parser.js CHANGED
@@ -55,6 +55,7 @@ export class Parser {
55
55
  case TokenType.Do: return this.parseDo();
56
56
  case TokenType.Use: return this.parseUse();
57
57
  case TokenType.Type: return this.parseType();
58
+ case TokenType.Ret: return this.parseRet();
58
59
  default: {
59
60
  const exprLoc = this.loc();
60
61
  const expr = this.parseExpr();
@@ -226,6 +227,16 @@ export class Parser {
226
227
  const def = this.parseTypeExpr();
227
228
  return { kind: "TypeStmt", name, pub, def, loc };
228
229
  }
230
+ parseRet() {
231
+ const loc = this.loc();
232
+ this.expect(TokenType.Ret);
233
+ let value;
234
+ // If the next token could start an expression, parse it
235
+ if (!this.at(TokenType.EOF) && !this.at(TokenType.RBrace) && !this.at(TokenType.Semicolon)) {
236
+ value = this.parseExpr();
237
+ }
238
+ return { kind: "RetStmt", value, loc };
239
+ }
229
240
  parseTypeExpr() {
230
241
  let typeExpr = this.parseTypeAtom();
231
242
  // Check for enum/union: Type | Type
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arc-lang",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Arc ⚡ — A programming language designed by AI agents, for AI agents. 27-63% fewer tokens than JavaScript.",
5
5
  "type": "module",
6
6
  "bin": {