freelang-v4 4.3.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 (261) hide show
  1. package/README.md +548 -0
  2. package/dist/ast.d.ts +367 -0
  3. package/dist/ast.js +4 -0
  4. package/dist/ast.js.map +1 -0
  5. package/dist/async-basic.test.d.ts +1 -0
  6. package/dist/async-basic.test.js +88 -0
  7. package/dist/async-basic.test.js.map +1 -0
  8. package/dist/async-jest.test.d.ts +1 -0
  9. package/dist/async-jest.test.js +99 -0
  10. package/dist/async-jest.test.js.map +1 -0
  11. package/dist/channel-jest.test.d.ts +1 -0
  12. package/dist/channel-jest.test.js +148 -0
  13. package/dist/channel-jest.test.js.map +1 -0
  14. package/dist/checker-jest.test.d.ts +1 -0
  15. package/dist/checker-jest.test.js +160 -0
  16. package/dist/checker-jest.test.js.map +1 -0
  17. package/dist/checker.d.ts +149 -0
  18. package/dist/checker.js +1565 -0
  19. package/dist/checker.js.map +1 -0
  20. package/dist/checker.test.d.ts +1 -0
  21. package/dist/checker.test.js +217 -0
  22. package/dist/checker.test.js.map +1 -0
  23. package/dist/compiler-jest.test.d.ts +1 -0
  24. package/dist/compiler-jest.test.js +233 -0
  25. package/dist/compiler-jest.test.js.map +1 -0
  26. package/dist/compiler.d.ts +127 -0
  27. package/dist/compiler.js +1588 -0
  28. package/dist/compiler.js.map +1 -0
  29. package/dist/compiler.test.d.ts +1 -0
  30. package/dist/compiler.test.js +313 -0
  31. package/dist/compiler.test.js.map +1 -0
  32. package/dist/db-100m-full.d.ts +5 -0
  33. package/dist/db-100m-full.js +78 -0
  34. package/dist/db-100m-full.js.map +1 -0
  35. package/dist/db-100m-no-index.d.ts +12 -0
  36. package/dist/db-100m-no-index.js +119 -0
  37. package/dist/db-100m-no-index.js.map +1 -0
  38. package/dist/db-100m-real.d.ts +5 -0
  39. package/dist/db-100m-real.js +131 -0
  40. package/dist/db-100m-real.js.map +1 -0
  41. package/dist/db-100m-streaming.d.ts +15 -0
  42. package/dist/db-100m-streaming.js +164 -0
  43. package/dist/db-100m-streaming.js.map +1 -0
  44. package/dist/db-100m-test.d.ts +5 -0
  45. package/dist/db-100m-test.js +111 -0
  46. package/dist/db-100m-test.js.map +1 -0
  47. package/dist/db-jest.test.d.ts +1 -0
  48. package/dist/db-jest.test.js +182 -0
  49. package/dist/db-jest.test.js.map +1 -0
  50. package/dist/db-runtime.d.ts +24 -0
  51. package/dist/db-runtime.js +204 -0
  52. package/dist/db-runtime.js.map +1 -0
  53. package/dist/db.d.ts +249 -0
  54. package/dist/db.js +593 -0
  55. package/dist/db.js.map +1 -0
  56. package/dist/file-io-jest.test.d.ts +1 -0
  57. package/dist/file-io-jest.test.js +225 -0
  58. package/dist/file-io-jest.test.js.map +1 -0
  59. package/dist/for-of-jest.test.d.ts +1 -0
  60. package/dist/for-of-jest.test.js +230 -0
  61. package/dist/for-of-jest.test.js.map +1 -0
  62. package/dist/for-of.test.d.ts +1 -0
  63. package/dist/for-of.test.js +305 -0
  64. package/dist/for-of.test.js.map +1 -0
  65. package/dist/function-literal-jest.test.d.ts +1 -0
  66. package/dist/function-literal-jest.test.js +180 -0
  67. package/dist/function-literal-jest.test.js.map +1 -0
  68. package/dist/function-literal.test.d.ts +1 -0
  69. package/dist/function-literal.test.js +245 -0
  70. package/dist/function-literal.test.js.map +1 -0
  71. package/dist/generics-jest.test.d.ts +1 -0
  72. package/dist/generics-jest.test.js +93 -0
  73. package/dist/generics-jest.test.js.map +1 -0
  74. package/dist/ir-gen.d.ts +15 -0
  75. package/dist/ir-gen.js +400 -0
  76. package/dist/ir-gen.js.map +1 -0
  77. package/dist/ir.d.ts +114 -0
  78. package/dist/ir.js +5 -0
  79. package/dist/ir.js.map +1 -0
  80. package/dist/lexer.d.ts +110 -0
  81. package/dist/lexer.js +467 -0
  82. package/dist/lexer.js.map +1 -0
  83. package/dist/lexer.test.d.ts +1 -0
  84. package/dist/lexer.test.js +426 -0
  85. package/dist/lexer.test.js.map +1 -0
  86. package/dist/main.d.ts +2 -0
  87. package/dist/main.js +241 -0
  88. package/dist/main.js.map +1 -0
  89. package/dist/module-jest.test.d.ts +1 -0
  90. package/dist/module-jest.test.js +123 -0
  91. package/dist/module-jest.test.js.map +1 -0
  92. package/dist/parser.d.ts +56 -0
  93. package/dist/parser.js +1060 -0
  94. package/dist/parser.js.map +1 -0
  95. package/dist/parser.test.d.ts +1 -0
  96. package/dist/parser.test.js +461 -0
  97. package/dist/parser.test.js.map +1 -0
  98. package/dist/pattern-matching-jest.test.d.ts +1 -0
  99. package/dist/pattern-matching-jest.test.js +158 -0
  100. package/dist/pattern-matching-jest.test.js.map +1 -0
  101. package/dist/pkg/init.d.ts +1 -0
  102. package/dist/pkg/init.js +118 -0
  103. package/dist/pkg/init.js.map +1 -0
  104. package/dist/pkg/install.d.ts +1 -0
  105. package/dist/pkg/install.js +77 -0
  106. package/dist/pkg/install.js.map +1 -0
  107. package/dist/pkg/registry.d.ts +23 -0
  108. package/dist/pkg/registry.js +106 -0
  109. package/dist/pkg/registry.js.map +1 -0
  110. package/dist/pkg/run.d.ts +1 -0
  111. package/dist/pkg/run.js +76 -0
  112. package/dist/pkg/run.js.map +1 -0
  113. package/dist/pkg/toml.d.ts +5 -0
  114. package/dist/pkg/toml.js +117 -0
  115. package/dist/pkg/toml.js.map +1 -0
  116. package/dist/repl.d.ts +15 -0
  117. package/dist/repl.js +197 -0
  118. package/dist/repl.js.map +1 -0
  119. package/dist/runtime/bytecode.d.ts +92 -0
  120. package/dist/runtime/bytecode.js +253 -0
  121. package/dist/runtime/bytecode.js.map +1 -0
  122. package/dist/runtime/value.d.ts +102 -0
  123. package/dist/runtime/value.js +302 -0
  124. package/dist/runtime/value.js.map +1 -0
  125. package/dist/runtime/vm.d.ts +65 -0
  126. package/dist/runtime/vm.js +293 -0
  127. package/dist/runtime/vm.js.map +1 -0
  128. package/dist/struct-instance-jest.test.d.ts +1 -0
  129. package/dist/struct-instance-jest.test.js +209 -0
  130. package/dist/struct-instance-jest.test.js.map +1 -0
  131. package/dist/struct-instance.test.d.ts +1 -0
  132. package/dist/struct-instance.test.js +291 -0
  133. package/dist/struct-instance.test.js.map +1 -0
  134. package/dist/struct-jest.test.d.ts +1 -0
  135. package/dist/struct-jest.test.js +176 -0
  136. package/dist/struct-jest.test.js.map +1 -0
  137. package/dist/struct.test.d.ts +1 -0
  138. package/dist/struct.test.js +231 -0
  139. package/dist/struct.test.js.map +1 -0
  140. package/dist/trait-jest.test.d.ts +1 -0
  141. package/dist/trait-jest.test.js +120 -0
  142. package/dist/trait-jest.test.js.map +1 -0
  143. package/dist/vm-jest.test.d.ts +1 -0
  144. package/dist/vm-jest.test.js +569 -0
  145. package/dist/vm-jest.test.js.map +1 -0
  146. package/dist/vm.d.ts +81 -0
  147. package/dist/vm.js +1956 -0
  148. package/dist/vm.js.map +1 -0
  149. package/dist/vm.test.d.ts +1 -0
  150. package/dist/vm.test.js +337 -0
  151. package/dist/vm.test.js.map +1 -0
  152. package/dist/web-repl/sandbox.d.ts +11 -0
  153. package/dist/web-repl/sandbox.js +76 -0
  154. package/dist/web-repl/sandbox.js.map +1 -0
  155. package/dist/web-repl/server.d.ts +1 -0
  156. package/dist/web-repl/server.js +111 -0
  157. package/dist/web-repl/server.js.map +1 -0
  158. package/dist/while-loop-jest.test.d.ts +1 -0
  159. package/dist/while-loop-jest.test.js +201 -0
  160. package/dist/while-loop-jest.test.js.map +1 -0
  161. package/dist/while-loop.test.d.ts +1 -0
  162. package/dist/while-loop.test.js +262 -0
  163. package/dist/while-loop.test.js.map +1 -0
  164. package/docs/EXPERIENCE.md +787 -0
  165. package/docs/README.md +175 -0
  166. package/docs/V1_V2_V3_ANALYSIS.md +107 -0
  167. package/docs/_config.yml +36 -0
  168. package/docs/api-reference.md +459 -0
  169. package/docs/architecture.md +470 -0
  170. package/docs/benchmarks.md +295 -0
  171. package/docs/comparison.md +454 -0
  172. package/docs/index.md +335 -0
  173. package/docs/language-completeness.md +228 -0
  174. package/docs/learning-guide.md +651 -0
  175. package/package.json +65 -0
  176. package/src/api/deploy_key.fl +294 -0
  177. package/src/api/issue.fl +302 -0
  178. package/src/api/org.fl +356 -0
  179. package/src/api/repo.fl +394 -0
  180. package/src/api/team.fl +299 -0
  181. package/src/api/user.fl +385 -0
  182. package/src/api/webhook.fl +273 -0
  183. package/src/ast.ts +158 -0
  184. package/src/async-basic.test.ts +94 -0
  185. package/src/async-jest.test.ts +107 -0
  186. package/src/channel-jest.test.ts +158 -0
  187. package/src/checker-jest.test.ts +189 -0
  188. package/src/checker.test.ts +279 -0
  189. package/src/checker.ts +1861 -0
  190. package/src/commands/analyze.fl +227 -0
  191. package/src/commands/auth.fl +315 -0
  192. package/src/commands/batch.fl +349 -0
  193. package/src/commands/config.fl +199 -0
  194. package/src/commands/deploy_key.fl +352 -0
  195. package/src/commands/issue.fl +275 -0
  196. package/src/commands/main.fl +492 -0
  197. package/src/commands/org.fl +425 -0
  198. package/src/commands/repo.fl +581 -0
  199. package/src/commands/team.fl +244 -0
  200. package/src/commands/user.fl +423 -0
  201. package/src/commands/webhook.fl +400 -0
  202. package/src/compiler-jest.test.ts +275 -0
  203. package/src/compiler.test.ts +375 -0
  204. package/src/compiler.ts +1770 -0
  205. package/src/config.fl +175 -0
  206. package/src/core/batch.fl +355 -0
  207. package/src/core/cache.fl +284 -0
  208. package/src/core/ensure.fl +324 -0
  209. package/src/db-100m-full.ts +96 -0
  210. package/src/db-100m-no-index.ts +133 -0
  211. package/src/db-100m-real.ts +152 -0
  212. package/src/db-100m-streaming.ts +154 -0
  213. package/src/db-100m-test.ts +136 -0
  214. package/src/db-jest.test.ts +161 -0
  215. package/src/db-runtime.ts +242 -0
  216. package/src/db.ts +676 -0
  217. package/src/errors.fl +134 -0
  218. package/src/for-of-jest.test.ts +246 -0
  219. package/src/for-of.test.ts +308 -0
  220. package/src/function-literal-jest.test.ts +193 -0
  221. package/src/function-literal.test.ts +248 -0
  222. package/src/generics-jest.test.ts +104 -0
  223. package/src/http/client.fl +327 -0
  224. package/src/ir-gen.ts +459 -0
  225. package/src/ir.ts +80 -0
  226. package/src/lexer.test.ts +499 -0
  227. package/src/lexer.ts +522 -0
  228. package/src/main.ts +223 -0
  229. package/src/models.fl +162 -0
  230. package/src/module-jest.test.ts +145 -0
  231. package/src/parser.test.ts +542 -0
  232. package/src/parser.ts +1211 -0
  233. package/src/pattern-matching-jest.test.ts +170 -0
  234. package/src/pkg/init.ts +91 -0
  235. package/src/pkg/install.ts +56 -0
  236. package/src/pkg/registry.ts +103 -0
  237. package/src/pkg/run.ts +49 -0
  238. package/src/pkg/toml.ts +129 -0
  239. package/src/repl.ts +190 -0
  240. package/src/runtime/bytecode.ts +291 -0
  241. package/src/runtime/value.ts +322 -0
  242. package/src/runtime/vm.ts +354 -0
  243. package/src/self-host/bootstrap.fl +68 -0
  244. package/src/self-host/interpreter.fl +361 -0
  245. package/src/self-host/lexer-simple.fl +22 -0
  246. package/src/self-host/lexer.fl +305 -0
  247. package/src/self-host/parser.fl +580 -0
  248. package/src/struct-instance-jest.test.ts +221 -0
  249. package/src/struct-instance.test.ts +293 -0
  250. package/src/struct-jest.test.ts +187 -0
  251. package/src/struct.test.ts +234 -0
  252. package/src/trait-jest.test.ts +136 -0
  253. package/src/vm-jest.test.ts +754 -0
  254. package/src/vm.ts +1976 -0
  255. package/src/web-repl/public/index.html +50 -0
  256. package/src/web-repl/public/main.js +105 -0
  257. package/src/web-repl/public/style.css +225 -0
  258. package/src/web-repl/sandbox.ts +88 -0
  259. package/src/web-repl/server.ts +97 -0
  260. package/src/while-loop-jest.test.ts +218 -0
  261. package/src/while-loop.test.ts +267 -0
@@ -0,0 +1,1770 @@
1
+ // FreeLang v4 — Bytecode Compiler (SPEC_02 구현)
2
+ // AST → Bytecode
3
+
4
+ import { Program, Stmt, Expr, Pattern, MatchArm, Param, TypeAnnotation, ImportDecl, ExportDecl } from "./ast";
5
+ import { IrValue, IrInst, IrFunction, IrProgram } from "./ir";
6
+
7
+ // ============================================================
8
+ // Opcodes
9
+ // ============================================================
10
+
11
+ export enum Op {
12
+ // 상수 로드
13
+ PUSH_I32 = 0x01,
14
+ PUSH_F64 = 0x02,
15
+ PUSH_STR = 0x03,
16
+ PUSH_TRUE = 0x04,
17
+ PUSH_FALSE = 0x05,
18
+ PUSH_VOID = 0x06,
19
+ PUSH_NONE = 0x07,
20
+ POP = 0x08,
21
+
22
+ // 산술 (i32)
23
+ ADD_I32 = 0x10,
24
+ SUB_I32 = 0x11,
25
+ MUL_I32 = 0x12,
26
+ DIV_I32 = 0x13,
27
+ MOD_I32 = 0x14,
28
+ NEG_I32 = 0x15,
29
+
30
+ // 산술 (f64)
31
+ ADD_F64 = 0x18,
32
+ SUB_F64 = 0x19,
33
+ MUL_F64 = 0x1A,
34
+ DIV_F64 = 0x1B,
35
+ MOD_F64 = 0x1C,
36
+ NEG_F64 = 0x1D,
37
+
38
+ // 비교
39
+ EQ = 0x20,
40
+ NEQ = 0x21,
41
+ LT = 0x22,
42
+ GT = 0x23,
43
+ LTEQ = 0x24,
44
+ GTEQ = 0x25,
45
+
46
+ // 논리
47
+ AND = 0x28,
48
+ OR = 0x29,
49
+ NOT = 0x2A,
50
+
51
+ // 문자열
52
+ STR_CONCAT = 0x2E,
53
+
54
+ // 변수
55
+ LOAD_LOCAL = 0x30,
56
+ STORE_LOCAL = 0x31,
57
+ LOAD_GLOBAL = 0x32,
58
+ STORE_GLOBAL = 0x33,
59
+
60
+ // 제어
61
+ JUMP = 0x40,
62
+ JUMP_IF_FALSE = 0x41,
63
+ RETURN = 0x42,
64
+ HALT = 0x43,
65
+
66
+ // 함수
67
+ CALL = 0x50,
68
+ CALL_BUILTIN = 0x51,
69
+
70
+ // 배열
71
+ ARRAY_NEW = 0x60,
72
+ ARRAY_GET = 0x61,
73
+ ARRAY_SET = 0x62,
74
+
75
+ // 구조체
76
+ STRUCT_NEW = 0x68,
77
+ STRUCT_GET = 0x69,
78
+ STRUCT_SET = 0x6A,
79
+
80
+ // Option/Result
81
+ WRAP_OK = 0x70,
82
+ WRAP_ERR = 0x71,
83
+ WRAP_SOME = 0x72,
84
+ UNWRAP = 0x73,
85
+ IS_OK = 0x74,
86
+ IS_ERR = 0x75,
87
+ IS_SOME = 0x76,
88
+ IS_NONE = 0x77,
89
+
90
+ // Actor/Channel
91
+ SPAWN = 0x80,
92
+ CHAN_NEW = 0x81,
93
+ CHAN_SEND = 0x82,
94
+ CHAN_RECV = 0x83,
95
+
96
+ // 디버그
97
+ DUP = 0xF0,
98
+ }
99
+
100
+ // ============================================================
101
+ // Chunk — 바이트코드 청크
102
+ // ============================================================
103
+
104
+ export type FuncInfo = {
105
+ name: string;
106
+ arity: number;
107
+ offset: number; // bytecode 시작 위치
108
+ };
109
+
110
+ export class Chunk {
111
+ code: number[] = [];
112
+ constants: any[] = [];
113
+ functions: FuncInfo[] = [];
114
+ lines: number[] = []; // 각 바이트코드의 소스 줄
115
+
116
+ emit(op: Op, line: number): void {
117
+ this.code.push(op);
118
+ this.lines.push(line);
119
+ }
120
+
121
+ emitByte(b: number, line: number): void {
122
+ this.code.push(b & 0xFF);
123
+ this.lines.push(line);
124
+ }
125
+
126
+ emitI32(val: number, line: number): void {
127
+ // 4바이트 little-endian
128
+ this.code.push(val & 0xFF);
129
+ this.code.push((val >> 8) & 0xFF);
130
+ this.code.push((val >> 16) & 0xFF);
131
+ this.code.push((val >> 24) & 0xFF);
132
+ for (let i = 0; i < 4; i++) this.lines.push(line);
133
+ }
134
+
135
+ emitF64(val: number, line: number): void {
136
+ const buf = new ArrayBuffer(8);
137
+ new Float64Array(buf)[0] = val;
138
+ const bytes = new Uint8Array(buf);
139
+ for (let i = 0; i < 8; i++) {
140
+ this.code.push(bytes[i]);
141
+ this.lines.push(line);
142
+ }
143
+ }
144
+
145
+ addConstant(val: any): number {
146
+ const idx = this.constants.length;
147
+ this.constants.push(val);
148
+ return idx;
149
+ }
150
+
151
+ // 패치: 나중에 오프셋 채우기
152
+ currentOffset(): number {
153
+ return this.code.length;
154
+ }
155
+
156
+ patchI32(offset: number, val: number): void {
157
+ this.code[offset] = val & 0xFF;
158
+ this.code[offset + 1] = (val >> 8) & 0xFF;
159
+ this.code[offset + 2] = (val >> 16) & 0xFF;
160
+ this.code[offset + 3] = (val >> 24) & 0xFF;
161
+ }
162
+ }
163
+
164
+ // ============================================================
165
+ // Compiler — 스코프 내 변수 슬롯 관리
166
+ // ============================================================
167
+
168
+ type LocalVar = {
169
+ name: string;
170
+ slot: number;
171
+ depth: number;
172
+ };
173
+
174
+ // ============================================================
175
+ // Compiler
176
+ // ============================================================
177
+
178
+ type LoopLabel = {
179
+ loopStart: number; // 루프 시작 (조건 계산)
180
+ breakPatches: number[]; // break JUMP placeholder 위치들
181
+ continuePatches: number[]; // continue JUMP placeholder 위치들 (for 루프에서만 필요)
182
+ };
183
+
184
+ export class Compiler {
185
+ private chunk: Chunk = new Chunk();
186
+ private locals: LocalVar[] = [];
187
+ private scopeDepth: number = 0;
188
+ private nextSlot: number = 0;
189
+ private functionBodies: Map<string, Stmt & { kind: "fn_decl" }> = new Map();
190
+ private currentLoopLabels: LoopLabel[] = [];
191
+
192
+ compile(program: Program): Chunk {
193
+ // Pass 1: 함수 등록
194
+ for (const stmt of program.stmts) {
195
+ if (stmt.kind === "fn_decl") {
196
+ this.functionBodies.set(stmt.name, stmt);
197
+ this.chunk.functions.push({
198
+ name: stmt.name,
199
+ arity: stmt.params.length,
200
+ offset: -1, // 나중에 패치
201
+ });
202
+ }
203
+ }
204
+
205
+ // Pass 2: 최상위 코드 컴파일
206
+ for (const stmt of program.stmts) {
207
+ if (stmt.kind !== "fn_decl") {
208
+ this.compileStmt(stmt);
209
+ }
210
+ }
211
+ this.chunk.emit(Op.HALT, 0);
212
+
213
+ // Pass 3: 함수 본문 컴파일
214
+ for (const [name, stmt] of this.functionBodies) {
215
+ this.compileFnBody(name, stmt);
216
+ }
217
+
218
+ return this.chunk;
219
+ }
220
+
221
+ // ============================================================
222
+ // IR → Bytecode (새 파이프라인)
223
+ // ============================================================
224
+
225
+ compileIR(ir: IrProgram): Chunk {
226
+ // Pass 1: 함수 등록
227
+ for (const fn of ir.functions) {
228
+ this.chunk.functions.push({
229
+ name: fn.name,
230
+ arity: fn.params.length,
231
+ offset: -1,
232
+ });
233
+ }
234
+
235
+ // Pass 2: main 코드 컴파일
236
+ for (const inst of ir.main) {
237
+ this.compileIrInst(inst);
238
+ }
239
+ this.chunk.emit(Op.HALT, 0);
240
+
241
+ // Pass 3: 함수 본문 컴파일
242
+ for (const fn of ir.functions) {
243
+ this.compileIrFunction(fn);
244
+ }
245
+
246
+ return this.chunk;
247
+ }
248
+
249
+ private irLabelOffsets: Map<string, number> = new Map();
250
+ private irLabelPatches: Map<string, number[]> = new Map();
251
+
252
+ private recordLabel(label: string): void {
253
+ this.irLabelOffsets.set(label, this.chunk.currentOffset());
254
+ }
255
+
256
+ private patchLabel(label: string): void {
257
+ const targets = this.irLabelPatches.get(label) || [];
258
+ const offset = this.irLabelOffsets.get(label);
259
+ if (offset !== undefined) {
260
+ for (const patchOffset of targets) {
261
+ this.chunk.patchI32(patchOffset, offset);
262
+ }
263
+ this.irLabelPatches.delete(label);
264
+ }
265
+ }
266
+
267
+ private emitJumpPlaceholder(label: string): number {
268
+ const offset = this.chunk.currentOffset();
269
+ this.chunk.emitI32(0, 0); // placeholder
270
+ const targets = this.irLabelPatches.get(label) || [];
271
+ targets.push(offset);
272
+ this.irLabelPatches.set(label, targets);
273
+ return offset;
274
+ }
275
+
276
+ private pushIrValue(val: IrValue): void {
277
+ switch (val.kind) {
278
+ case "const_i32":
279
+ this.chunk.emit(Op.PUSH_I32, 0);
280
+ this.chunk.emitI32(val.val, 0);
281
+ break;
282
+ case "const_f64":
283
+ this.chunk.emit(Op.PUSH_F64, 0);
284
+ this.chunk.emitF64(val.val, 0);
285
+ break;
286
+ case "const_str":
287
+ this.chunk.emit(Op.PUSH_STR, 0);
288
+ this.chunk.emitI32(this.chunk.addConstant(val.val), 0);
289
+ break;
290
+ case "const_bool":
291
+ if (val.val) {
292
+ this.chunk.emit(Op.PUSH_TRUE, 0);
293
+ } else {
294
+ this.chunk.emit(Op.PUSH_FALSE, 0);
295
+ }
296
+ break;
297
+ case "local":
298
+ // 로컬 변수는 LOAD_LOCAL로 로드
299
+ const slot = this.resolveLocal(val.name);
300
+ if (slot >= 0) {
301
+ this.chunk.emit(Op.LOAD_LOCAL, 0);
302
+ this.chunk.emitI32(slot, 0);
303
+ }
304
+ break;
305
+ case "global":
306
+ // 글로벌 변수는 LOAD_GLOBAL로 로드
307
+ this.chunk.emit(Op.LOAD_GLOBAL, 0);
308
+ this.chunk.emitI32(this.chunk.addConstant(val.name), 0);
309
+ break;
310
+ case "temp":
311
+ // temp는 로컬로 취급 (slot 할당 필요)
312
+ const tslot = this.resolveLocal(val.name);
313
+ if (tslot >= 0) {
314
+ this.chunk.emit(Op.LOAD_LOCAL, 0);
315
+ this.chunk.emitI32(tslot, 0);
316
+ }
317
+ break;
318
+ }
319
+ }
320
+
321
+ private compileIrInst(inst: IrInst): void {
322
+ switch (inst.kind) {
323
+ case "assign": {
324
+ this.pushIrValue(inst.src);
325
+ const slot = this.resolveLocal(inst.dest);
326
+ if (slot >= 0) {
327
+ this.chunk.emit(Op.STORE_LOCAL, 0);
328
+ this.chunk.emitI32(slot, 0);
329
+ } else {
330
+ // 새 로컬 변수 선언
331
+ const newSlot = this.declareLocal(inst.dest);
332
+ this.chunk.emit(Op.STORE_LOCAL, 0);
333
+ this.chunk.emitI32(newSlot, 0);
334
+ }
335
+ break;
336
+ }
337
+
338
+ case "binop": {
339
+ this.pushIrValue(inst.left);
340
+ this.pushIrValue(inst.right);
341
+
342
+ // 연산자에 따른 Op 선택 (타입은 런타임에 결정됨)
343
+ switch (inst.op) {
344
+ case "+":
345
+ this.chunk.emit(Op.ADD_I32, 0); // 런타임에 타입 체크
346
+ break;
347
+ case "-":
348
+ this.chunk.emit(Op.SUB_I32, 0);
349
+ break;
350
+ case "*":
351
+ this.chunk.emit(Op.MUL_I32, 0);
352
+ break;
353
+ case "/":
354
+ this.chunk.emit(Op.DIV_I32, 0);
355
+ break;
356
+ case "%":
357
+ this.chunk.emit(Op.MOD_I32, 0);
358
+ break;
359
+ case "==":
360
+ this.chunk.emit(Op.EQ, 0);
361
+ break;
362
+ case "!=":
363
+ this.chunk.emit(Op.NEQ, 0);
364
+ break;
365
+ case "<":
366
+ this.chunk.emit(Op.LT, 0);
367
+ break;
368
+ case ">":
369
+ this.chunk.emit(Op.GT, 0);
370
+ break;
371
+ case "<=":
372
+ this.chunk.emit(Op.LTEQ, 0);
373
+ break;
374
+ case ">=":
375
+ this.chunk.emit(Op.GTEQ, 0);
376
+ break;
377
+ case "&&":
378
+ this.chunk.emit(Op.AND, 0);
379
+ break;
380
+ case "||":
381
+ this.chunk.emit(Op.OR, 0);
382
+ break;
383
+ default:
384
+ break;
385
+ }
386
+
387
+ // 결과를 dest에 저장
388
+ const slot = this.resolveLocal(inst.dest);
389
+ if (slot >= 0) {
390
+ this.chunk.emit(Op.STORE_LOCAL, 0);
391
+ this.chunk.emitI32(slot, 0);
392
+ } else {
393
+ const newSlot = this.declareLocal(inst.dest);
394
+ this.chunk.emit(Op.STORE_LOCAL, 0);
395
+ this.chunk.emitI32(newSlot, 0);
396
+ }
397
+ break;
398
+ }
399
+
400
+ case "unop": {
401
+ this.pushIrValue(inst.src);
402
+ switch (inst.op) {
403
+ case "-":
404
+ this.chunk.emit(Op.NEG_I32, 0);
405
+ break;
406
+ case "!":
407
+ this.chunk.emit(Op.NOT, 0);
408
+ break;
409
+ default:
410
+ break;
411
+ }
412
+ const slot = this.resolveLocal(inst.dest);
413
+ if (slot >= 0) {
414
+ this.chunk.emit(Op.STORE_LOCAL, 0);
415
+ this.chunk.emitI32(slot, 0);
416
+ } else {
417
+ const newSlot = this.declareLocal(inst.dest);
418
+ this.chunk.emit(Op.STORE_LOCAL, 0);
419
+ this.chunk.emitI32(newSlot, 0);
420
+ }
421
+ break;
422
+ }
423
+
424
+ case "label": {
425
+ this.recordLabel(inst.name);
426
+ break;
427
+ }
428
+
429
+ case "jump": {
430
+ this.chunk.emit(Op.JUMP, 0);
431
+ this.emitJumpPlaceholder(inst.target);
432
+ break;
433
+ }
434
+
435
+ case "jump_if_false": {
436
+ this.pushIrValue(inst.cond);
437
+ this.chunk.emit(Op.JUMP_IF_FALSE, 0);
438
+ this.emitJumpPlaceholder(inst.target);
439
+ break;
440
+ }
441
+
442
+ case "call": {
443
+ // 인수 푸시
444
+ for (const arg of inst.args) {
445
+ this.pushIrValue(arg);
446
+ }
447
+ // 함수 호출
448
+ this.chunk.emit(Op.CALL, 0);
449
+ this.chunk.emitI32(this.chunk.addConstant(inst.fn), 0);
450
+ this.chunk.emitByte(inst.args.length, 0);
451
+
452
+ // 결과 저장
453
+ if (inst.dest) {
454
+ const slot = this.resolveLocal(inst.dest);
455
+ if (slot >= 0) {
456
+ this.chunk.emit(Op.STORE_LOCAL, 0);
457
+ this.chunk.emitI32(slot, 0);
458
+ } else {
459
+ const newSlot = this.declareLocal(inst.dest);
460
+ this.chunk.emit(Op.STORE_LOCAL, 0);
461
+ this.chunk.emitI32(newSlot, 0);
462
+ }
463
+ }
464
+ break;
465
+ }
466
+
467
+ case "call_builtin": {
468
+ // 인수 푸시
469
+ for (const arg of inst.args) {
470
+ this.pushIrValue(arg);
471
+ }
472
+ // 내장 함수 호출
473
+ this.chunk.emit(Op.CALL_BUILTIN, 0);
474
+ this.chunk.emitI32(this.chunk.addConstant(inst.name), 0);
475
+ this.chunk.emitByte(inst.args.length, 0);
476
+
477
+ // 결과 저장
478
+ if (inst.dest) {
479
+ const slot = this.resolveLocal(inst.dest);
480
+ if (slot >= 0) {
481
+ this.chunk.emit(Op.STORE_LOCAL, 0);
482
+ this.chunk.emitI32(slot, 0);
483
+ } else {
484
+ const newSlot = this.declareLocal(inst.dest);
485
+ this.chunk.emit(Op.STORE_LOCAL, 0);
486
+ this.chunk.emitI32(newSlot, 0);
487
+ }
488
+ }
489
+ break;
490
+ }
491
+
492
+ case "return": {
493
+ if (inst.value) {
494
+ this.pushIrValue(inst.value);
495
+ } else {
496
+ this.chunk.emit(Op.PUSH_VOID, 0);
497
+ }
498
+ this.chunk.emit(Op.RETURN, 0);
499
+ break;
500
+ }
501
+
502
+ case "array_new": {
503
+ for (const elem of inst.elements) {
504
+ this.pushIrValue(elem);
505
+ }
506
+ this.chunk.emit(Op.ARRAY_NEW, 0);
507
+ this.chunk.emitI32(inst.elements.length, 0);
508
+
509
+ const slot = this.resolveLocal(inst.dest);
510
+ if (slot >= 0) {
511
+ this.chunk.emit(Op.STORE_LOCAL, 0);
512
+ this.chunk.emitI32(slot, 0);
513
+ } else {
514
+ const newSlot = this.declareLocal(inst.dest);
515
+ this.chunk.emit(Op.STORE_LOCAL, 0);
516
+ this.chunk.emitI32(newSlot, 0);
517
+ }
518
+ break;
519
+ }
520
+
521
+ case "array_get": {
522
+ this.pushIrValue(inst.arr);
523
+ this.pushIrValue(inst.idx);
524
+ this.chunk.emit(Op.ARRAY_GET, 0);
525
+
526
+ const slot = this.resolveLocal(inst.dest);
527
+ if (slot >= 0) {
528
+ this.chunk.emit(Op.STORE_LOCAL, 0);
529
+ this.chunk.emitI32(slot, 0);
530
+ } else {
531
+ const newSlot = this.declareLocal(inst.dest);
532
+ this.chunk.emit(Op.STORE_LOCAL, 0);
533
+ this.chunk.emitI32(newSlot, 0);
534
+ }
535
+ break;
536
+ }
537
+
538
+ case "array_set": {
539
+ this.pushIrValue(inst.arr);
540
+ this.pushIrValue(inst.idx);
541
+ this.pushIrValue(inst.value);
542
+ this.chunk.emit(Op.ARRAY_SET, 0);
543
+ break;
544
+ }
545
+
546
+ case "struct_new": {
547
+ // 각 필드마다: PUSH_STR(fieldName) + value
548
+ for (const field of inst.fields) {
549
+ this.chunk.emit(Op.PUSH_STR, 0);
550
+ this.chunk.emitI32(this.chunk.addConstant(field.name), 0);
551
+ this.pushIrValue(field.value);
552
+ }
553
+ this.chunk.emit(Op.STRUCT_NEW, 0);
554
+ this.chunk.emitI32(inst.fields.length, 0);
555
+
556
+ const slot = this.resolveLocal(inst.dest);
557
+ if (slot >= 0) {
558
+ this.chunk.emit(Op.STORE_LOCAL, 0);
559
+ this.chunk.emitI32(slot, 0);
560
+ } else {
561
+ const newSlot = this.declareLocal(inst.dest);
562
+ this.chunk.emit(Op.STORE_LOCAL, 0);
563
+ this.chunk.emitI32(newSlot, 0);
564
+ }
565
+ break;
566
+ }
567
+
568
+ case "struct_get": {
569
+ this.pushIrValue(inst.obj);
570
+ this.chunk.emit(Op.STRUCT_GET, 0);
571
+ this.chunk.emitI32(this.chunk.addConstant(inst.field), 0);
572
+
573
+ const slot = this.resolveLocal(inst.dest);
574
+ if (slot >= 0) {
575
+ this.chunk.emit(Op.STORE_LOCAL, 0);
576
+ this.chunk.emitI32(slot, 0);
577
+ } else {
578
+ const newSlot = this.declareLocal(inst.dest);
579
+ this.chunk.emit(Op.STORE_LOCAL, 0);
580
+ this.chunk.emitI32(newSlot, 0);
581
+ }
582
+ break;
583
+ }
584
+
585
+ case "struct_set": {
586
+ this.pushIrValue(inst.obj);
587
+ this.pushIrValue(inst.value);
588
+ this.chunk.emit(Op.STRUCT_SET, 0);
589
+ this.chunk.emitI32(this.chunk.addConstant(inst.field), 0);
590
+ break;
591
+ }
592
+
593
+ case "wrap_ok": {
594
+ this.pushIrValue(inst.value);
595
+ this.chunk.emit(Op.WRAP_OK, 0);
596
+ const slot = this.resolveLocal(inst.dest);
597
+ if (slot >= 0) {
598
+ this.chunk.emit(Op.STORE_LOCAL, 0);
599
+ this.chunk.emitI32(slot, 0);
600
+ } else {
601
+ const newSlot = this.declareLocal(inst.dest);
602
+ this.chunk.emit(Op.STORE_LOCAL, 0);
603
+ this.chunk.emitI32(newSlot, 0);
604
+ }
605
+ break;
606
+ }
607
+
608
+ case "wrap_err": {
609
+ this.pushIrValue(inst.value);
610
+ this.chunk.emit(Op.WRAP_ERR, 0);
611
+ const slot = this.resolveLocal(inst.dest);
612
+ if (slot >= 0) {
613
+ this.chunk.emit(Op.STORE_LOCAL, 0);
614
+ this.chunk.emitI32(slot, 0);
615
+ } else {
616
+ const newSlot = this.declareLocal(inst.dest);
617
+ this.chunk.emit(Op.STORE_LOCAL, 0);
618
+ this.chunk.emitI32(newSlot, 0);
619
+ }
620
+ break;
621
+ }
622
+
623
+ case "unwrap": {
624
+ this.pushIrValue(inst.value);
625
+ this.chunk.emit(Op.UNWRAP, 0);
626
+ const slot = this.resolveLocal(inst.dest);
627
+ if (slot >= 0) {
628
+ this.chunk.emit(Op.STORE_LOCAL, 0);
629
+ this.chunk.emitI32(slot, 0);
630
+ } else {
631
+ const newSlot = this.declareLocal(inst.dest);
632
+ this.chunk.emit(Op.STORE_LOCAL, 0);
633
+ this.chunk.emitI32(newSlot, 0);
634
+ }
635
+ break;
636
+ }
637
+ }
638
+ }
639
+
640
+ private compileIrFunction(fn: IrFunction): void {
641
+ const fnInfo = this.chunk.functions.find((f) => f.name === fn.name);
642
+ if (fnInfo) fnInfo.offset = this.chunk.currentOffset();
643
+
644
+ const prevLocals = this.locals;
645
+ const prevSlot = this.nextSlot;
646
+ const prevDepth = this.scopeDepth;
647
+
648
+ this.locals = [];
649
+ this.nextSlot = 0;
650
+ this.scopeDepth = 0;
651
+ this.irLabelOffsets.clear();
652
+ this.irLabelPatches.clear();
653
+
654
+ // 매개변수 선언
655
+ for (const param of fn.params) {
656
+ this.declareLocal(param);
657
+ }
658
+
659
+ // 함수 본문 컴파일
660
+ for (const inst of fn.insts) {
661
+ this.compileIrInst(inst);
662
+ }
663
+
664
+ // 명시적 return이 없으면 void return 추가
665
+ const lastInst = fn.insts[fn.insts.length - 1];
666
+ if (!lastInst || lastInst.kind !== "return") {
667
+ this.chunk.emit(Op.PUSH_VOID, 0);
668
+ this.chunk.emit(Op.RETURN, 0);
669
+ }
670
+
671
+ this.locals = prevLocals;
672
+ this.nextSlot = prevSlot;
673
+ this.scopeDepth = prevDepth;
674
+ }
675
+
676
+ // ============================================================
677
+ // 문 컴파일
678
+ // ============================================================
679
+
680
+ private compileStmt(stmt: Stmt): void {
681
+ switch (stmt.kind) {
682
+ case "var_decl": return this.compileVarDecl(stmt);
683
+ case "fn_decl": return; // Pass 3에서 처리
684
+ case "if_stmt": return this.compileIfStmt(stmt);
685
+ case "match_stmt": return this.compileMatchStmt(stmt);
686
+ case "for_stmt": return this.compileForStmt(stmt);
687
+ case "for_of_stmt": return this.compileForOfStmt(stmt);
688
+ case "while_stmt": return this.compileWhileStmt(stmt);
689
+ case "break_stmt": return this.compileBreakStmt(stmt);
690
+ case "continue_stmt": return this.compileContinueStmt(stmt);
691
+ case "struct_decl": return; // 타입 선언, 런타임 불필요
692
+ case "spawn_stmt": return this.compileSpawnStmt(stmt);
693
+ case "return_stmt": return this.compileReturnStmt(stmt);
694
+ case "expr_stmt": return this.compileExprStmt(stmt);
695
+ case "import_decl": return; // 모듈 로드는 별도 처리
696
+ case "export_decl": return; // export는 컴파일 시점에 무시
697
+ }
698
+ }
699
+
700
+ private compileVarDecl(stmt: Stmt & { kind: "var_decl" }): void {
701
+ this.compileExpr(stmt.init);
702
+ const slot = this.declareLocal(stmt.name);
703
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
704
+ this.chunk.emitI32(slot, stmt.line);
705
+ }
706
+
707
+ private compileFnBody(name: string, stmt: Stmt & { kind: "fn_decl" }): void {
708
+ // 함수 오프셋 기록
709
+ const fnInfo = this.chunk.functions.find((f) => f.name === name);
710
+ if (fnInfo) fnInfo.offset = this.chunk.currentOffset();
711
+
712
+ // 스코프 + 매개변수
713
+ const prevLocals = this.locals;
714
+ const prevSlot = this.nextSlot;
715
+ const prevDepth = this.scopeDepth;
716
+
717
+ this.locals = [];
718
+ this.nextSlot = 0;
719
+ this.scopeDepth = 0;
720
+
721
+ for (const p of stmt.params) {
722
+ this.declareLocal(p.name);
723
+ }
724
+
725
+ // 본문: 마지막 statement가 expr_stmt/match_stmt이면 값을 반환
726
+ let lastStmtIsExpr = false;
727
+ for (let i = 0; i < stmt.body.length; i++) {
728
+ const s = stmt.body[i];
729
+ const isLast = i === stmt.body.length - 1;
730
+
731
+ if (isLast && s.kind === "expr_stmt") {
732
+ // 마지막이 expression statement → expression 값을 스택에 남김
733
+ this.compileExpr(s.expr);
734
+ lastStmtIsExpr = true;
735
+ } else if (isLast && s.kind === "match_stmt") {
736
+ // 마지막이 match statement → match 값을 반환하도록 컴파일
737
+ this.compileMatchStmtAsExpr(s as Stmt & { kind: "match_stmt" });
738
+ lastStmtIsExpr = true;
739
+ } else {
740
+ this.compileStmt(s);
741
+ }
742
+ }
743
+
744
+ // void 함수는 암시적 return
745
+ if (!lastStmtIsExpr) {
746
+ this.chunk.emit(Op.PUSH_VOID, stmt.line);
747
+ }
748
+ this.chunk.emit(Op.RETURN, stmt.line);
749
+
750
+ this.locals = prevLocals;
751
+ this.nextSlot = prevSlot;
752
+ this.scopeDepth = prevDepth;
753
+ }
754
+
755
+ private compileIfStmt(stmt: Stmt & { kind: "if_stmt" }): void {
756
+ this.compileExpr(stmt.condition);
757
+
758
+ // JUMP_IF_FALSE → else or end
759
+ this.chunk.emit(Op.JUMP_IF_FALSE, stmt.line);
760
+ const elseJump = this.chunk.currentOffset();
761
+ this.chunk.emitI32(0, stmt.line); // 패치 대상
762
+
763
+ // then
764
+ this.beginScope();
765
+ for (const s of stmt.then) this.compileStmt(s);
766
+ this.endScope(stmt.line);
767
+
768
+ if (stmt.else_) {
769
+ // JUMP → end (then 끝에서)
770
+ this.chunk.emit(Op.JUMP, stmt.line);
771
+ const endJump = this.chunk.currentOffset();
772
+ this.chunk.emitI32(0, stmt.line);
773
+
774
+ // else 시작 (패치)
775
+ this.chunk.patchI32(elseJump, this.chunk.currentOffset());
776
+
777
+ this.beginScope();
778
+ for (const s of stmt.else_) this.compileStmt(s);
779
+ this.endScope(stmt.line);
780
+
781
+ // end (패치)
782
+ this.chunk.patchI32(endJump, this.chunk.currentOffset());
783
+ } else {
784
+ this.chunk.patchI32(elseJump, this.chunk.currentOffset());
785
+ }
786
+ }
787
+
788
+ private compileMatchStmt(stmt: Stmt & { kind: "match_stmt" }): void {
789
+ this.compileExpr(stmt.subject);
790
+
791
+ // subject를 로컬 슬롯에 저장 (struct/array 분해 바인딩용)
792
+ const subjectSlot = this.declareLocal("__match_subject__");
793
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
794
+ this.chunk.emitI32(subjectSlot, stmt.line);
795
+
796
+ const endJumps: number[] = [];
797
+
798
+ for (const arm of stmt.arms) {
799
+ // subject를 스택에 로드해서 테스트
800
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
801
+ this.chunk.emitI32(subjectSlot, stmt.line);
802
+
803
+ // 패턴 매칭 코드
804
+ this.compilePatternTest(arm.pattern, stmt.line);
805
+
806
+ // JUMP_IF_FALSE → 다음 arm
807
+ this.chunk.emit(Op.JUMP_IF_FALSE, stmt.line);
808
+ const nextArm = this.chunk.currentOffset();
809
+ this.chunk.emitI32(0, stmt.line);
810
+
811
+ // Pattern 매칭 성공 — scope 시작 및 변수 바인딩
812
+ this.beginScope();
813
+ this.compilePatternBind(arm.pattern, stmt.line, subjectSlot);
814
+
815
+ // Guard 절이 있으면 추가 조건 검사 (pattern bind 후)
816
+ let guardJump: number | null = null;
817
+ if (arm.guard) {
818
+ this.compileExpr(arm.guard);
819
+ this.chunk.emit(Op.JUMP_IF_FALSE, stmt.line);
820
+ guardJump = this.chunk.currentOffset();
821
+ this.chunk.emitI32(0, stmt.line);
822
+ }
823
+
824
+ // body
825
+ this.compileExpr(arm.body);
826
+ this.chunk.emit(Op.POP, stmt.line); // match stmt → 값 버림
827
+ this.endScope(stmt.line);
828
+
829
+ // JUMP → end
830
+ this.chunk.emit(Op.JUMP, stmt.line);
831
+ endJumps.push(this.chunk.currentOffset());
832
+ this.chunk.emitI32(0, stmt.line);
833
+
834
+ // 다음 arm (패치) - pattern fail과 guard fail 모두 이 위치로
835
+ this.chunk.patchI32(nextArm, this.chunk.currentOffset());
836
+ if (guardJump !== null) {
837
+ this.chunk.patchI32(guardJump, this.chunk.currentOffset());
838
+ }
839
+ }
840
+
841
+ // end 패치
842
+ for (const j of endJumps) {
843
+ this.chunk.patchI32(j, this.chunk.currentOffset());
844
+ }
845
+ }
846
+
847
+ private compileMatchStmtAsExpr(stmt: Stmt & { kind: "match_stmt" }): void {
848
+ // match_stmt를 expression처럼 컴파일 (값을 반환)
849
+ this.compileExpr(stmt.subject);
850
+
851
+ const subjectSlot = this.declareLocal("__match_subject__");
852
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
853
+ this.chunk.emitI32(subjectSlot, stmt.line);
854
+
855
+ const endJumps: number[] = [];
856
+
857
+ for (const arm of stmt.arms) {
858
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
859
+ this.chunk.emitI32(subjectSlot, stmt.line);
860
+ this.compilePatternTest(arm.pattern, stmt.line);
861
+ this.chunk.emit(Op.JUMP_IF_FALSE, stmt.line);
862
+ const nextArm = this.chunk.currentOffset();
863
+ this.chunk.emitI32(0, stmt.line);
864
+
865
+ // Pattern 매칭 성공 — scope 시작 및 변수 바인딩
866
+ this.beginScope();
867
+ this.compilePatternBind(arm.pattern, stmt.line, subjectSlot);
868
+
869
+ // Guard 절이 있으면 추가 조건 검사 (pattern bind 후)
870
+ let guardJump: number | null = null;
871
+ if (arm.guard) {
872
+ this.compileExpr(arm.guard);
873
+ this.chunk.emit(Op.JUMP_IF_FALSE, stmt.line);
874
+ guardJump = this.chunk.currentOffset();
875
+ this.chunk.emitI32(0, stmt.line);
876
+ }
877
+
878
+ // body 실행
879
+ this.compileExpr(arm.body); // 값을 반환 (POP 없음)
880
+ this.endScope(stmt.line);
881
+
882
+ this.chunk.emit(Op.JUMP, stmt.line);
883
+ endJumps.push(this.chunk.currentOffset());
884
+ this.chunk.emitI32(0, stmt.line);
885
+
886
+ // 다음 arm (패치) - pattern fail과 guard fail 모두 이 위치로
887
+ this.chunk.patchI32(nextArm, this.chunk.currentOffset());
888
+ if (guardJump !== null) {
889
+ this.chunk.patchI32(guardJump, this.chunk.currentOffset());
890
+ }
891
+ }
892
+
893
+ this.chunk.emit(Op.PUSH_VOID, stmt.line);
894
+ const afterMatchLabel = this.chunk.currentOffset();
895
+ for (const j of endJumps) {
896
+ this.chunk.patchI32(j, afterMatchLabel);
897
+ }
898
+ }
899
+
900
+ private compileForStmt(stmt: Stmt & { kind: "for_stmt" }): void {
901
+ // iterable 계산
902
+ this.compileExpr(stmt.iterable);
903
+ const arrSlot = this.declareLocal("__arr__");
904
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
905
+ this.chunk.emitI32(arrSlot, stmt.line);
906
+
907
+ // 인덱스 = 0
908
+ this.chunk.emit(Op.PUSH_I32, stmt.line);
909
+ this.chunk.emitI32(0, stmt.line);
910
+ const idxSlot = this.declareLocal("__idx__");
911
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
912
+ this.chunk.emitI32(idxSlot, stmt.line);
913
+
914
+ // 루프 변수
915
+ this.chunk.emit(Op.PUSH_VOID, stmt.line);
916
+ const itemSlot = this.declareLocal(stmt.variable);
917
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
918
+ this.chunk.emitI32(itemSlot, stmt.line);
919
+
920
+ const loopStart = this.chunk.currentOffset();
921
+
922
+ // 루프 레이블 푸시
923
+ const loopLabel: LoopLabel = { loopStart, breakPatches: [], continuePatches: [] };
924
+ this.currentLoopLabels.push(loopLabel);
925
+
926
+ // 조건: idx < length(arr)
927
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
928
+ this.chunk.emitI32(idxSlot, stmt.line);
929
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
930
+ this.chunk.emitI32(arrSlot, stmt.line);
931
+ this.chunk.emit(Op.CALL_BUILTIN, stmt.line);
932
+ this.chunk.emitI32(this.chunk.addConstant("length"), stmt.line);
933
+ this.chunk.emitByte(1, stmt.line); // 1 arg
934
+ this.chunk.emit(Op.LT, stmt.line);
935
+ this.chunk.emit(Op.JUMP_IF_FALSE, stmt.line);
936
+ const exitJump = this.chunk.currentOffset();
937
+ this.chunk.emitI32(0, stmt.line);
938
+
939
+ // item = arr[idx]
940
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
941
+ this.chunk.emitI32(arrSlot, stmt.line);
942
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
943
+ this.chunk.emitI32(idxSlot, stmt.line);
944
+ this.chunk.emit(Op.ARRAY_GET, stmt.line);
945
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
946
+ this.chunk.emitI32(itemSlot, stmt.line);
947
+
948
+ // body
949
+ this.beginScope();
950
+ for (const s of stmt.body) this.compileStmt(s);
951
+ this.endScope(stmt.line);
952
+
953
+ // idx = idx + 1 (continueTarget 설정)
954
+ const continueTarget = this.chunk.currentOffset();
955
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
956
+ this.chunk.emitI32(idxSlot, stmt.line);
957
+ this.chunk.emit(Op.PUSH_I32, stmt.line);
958
+ this.chunk.emitI32(1, stmt.line);
959
+ this.chunk.emit(Op.ADD_I32, stmt.line);
960
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
961
+ this.chunk.emitI32(idxSlot, stmt.line);
962
+
963
+ // JUMP → loopStart
964
+ this.chunk.emit(Op.JUMP, stmt.line);
965
+ this.chunk.emitI32(loopStart, stmt.line);
966
+
967
+ // exit 주소 결정 및 패치
968
+ const exitTarget = this.chunk.currentOffset();
969
+ this.chunk.patchI32(exitJump, exitTarget);
970
+
971
+ // break 문 모두 패치
972
+ for (const breakPatch of loopLabel.breakPatches) {
973
+ this.chunk.patchI32(breakPatch, exitTarget);
974
+ }
975
+
976
+ // continue 문 모두 패치 (for는 idx 증가로)
977
+ for (const continuePatch of loopLabel.continuePatches) {
978
+ this.chunk.patchI32(continuePatch, continueTarget);
979
+ }
980
+
981
+ // 루프 레이블 팝
982
+ this.currentLoopLabels.pop();
983
+ }
984
+
985
+ private compileForOfStmt(stmt: Stmt & { kind: "for_of_stmt" }): void {
986
+ // for_of_stmt는 for_stmt와 동일 (변수 선언 방식만 다름)
987
+ this.compileExpr(stmt.iterable);
988
+ const arrSlot = this.declareLocal("__arr__");
989
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
990
+ this.chunk.emitI32(arrSlot, stmt.line);
991
+
992
+ this.chunk.emit(Op.PUSH_I32, stmt.line);
993
+ this.chunk.emitI32(0, stmt.line);
994
+ const idxSlot = this.declareLocal("__idx__");
995
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
996
+ this.chunk.emitI32(idxSlot, stmt.line);
997
+
998
+ this.chunk.emit(Op.PUSH_VOID, stmt.line);
999
+ const itemSlot = this.declareLocal(stmt.variable);
1000
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
1001
+ this.chunk.emitI32(itemSlot, stmt.line);
1002
+
1003
+ const loopStart = this.chunk.currentOffset();
1004
+
1005
+ // 루프 레이블 푸시
1006
+ const loopLabel: LoopLabel = { loopStart, breakPatches: [], continuePatches: [] };
1007
+ this.currentLoopLabels.push(loopLabel);
1008
+
1009
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
1010
+ this.chunk.emitI32(idxSlot, stmt.line);
1011
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
1012
+ this.chunk.emitI32(arrSlot, stmt.line);
1013
+ this.chunk.emit(Op.CALL_BUILTIN, stmt.line);
1014
+ this.chunk.emitI32(this.chunk.addConstant("length"), stmt.line);
1015
+ this.chunk.emitByte(1, stmt.line);
1016
+ this.chunk.emit(Op.LT, stmt.line);
1017
+ this.chunk.emit(Op.JUMP_IF_FALSE, stmt.line);
1018
+ const exitJump = this.chunk.currentOffset();
1019
+ this.chunk.emitI32(0, stmt.line);
1020
+
1021
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
1022
+ this.chunk.emitI32(arrSlot, stmt.line);
1023
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
1024
+ this.chunk.emitI32(idxSlot, stmt.line);
1025
+ this.chunk.emit(Op.ARRAY_GET, stmt.line);
1026
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
1027
+ this.chunk.emitI32(itemSlot, stmt.line);
1028
+
1029
+ this.beginScope();
1030
+ for (const s of stmt.body) this.compileStmt(s);
1031
+ this.endScope(stmt.line);
1032
+
1033
+ // idx = idx + 1 (continueTarget 설정)
1034
+ const continueTarget = this.chunk.currentOffset();
1035
+ this.chunk.emit(Op.LOAD_LOCAL, stmt.line);
1036
+ this.chunk.emitI32(idxSlot, stmt.line);
1037
+ this.chunk.emit(Op.PUSH_I32, stmt.line);
1038
+ this.chunk.emitI32(1, stmt.line);
1039
+ this.chunk.emit(Op.ADD_I32, stmt.line);
1040
+ this.chunk.emit(Op.STORE_LOCAL, stmt.line);
1041
+ this.chunk.emitI32(idxSlot, stmt.line);
1042
+
1043
+ this.chunk.emit(Op.JUMP, stmt.line);
1044
+ this.chunk.emitI32(loopStart, stmt.line);
1045
+
1046
+ // exit 주소 결정 및 패치
1047
+ const exitTarget = this.chunk.currentOffset();
1048
+ this.chunk.patchI32(exitJump, exitTarget);
1049
+
1050
+ // break 문 모두 패치
1051
+ for (const breakPatch of loopLabel.breakPatches) {
1052
+ this.chunk.patchI32(breakPatch, exitTarget);
1053
+ }
1054
+
1055
+ // continue 문 모두 패치 (for는 idx 증가로)
1056
+ for (const continuePatch of loopLabel.continuePatches) {
1057
+ this.chunk.patchI32(continuePatch, continueTarget);
1058
+ }
1059
+
1060
+ // 루프 레이블 팝
1061
+ this.currentLoopLabels.pop();
1062
+ }
1063
+
1064
+ private compileWhileStmt(stmt: Stmt & { kind: "while_stmt" }): void {
1065
+ const loopStart = this.chunk.currentOffset();
1066
+
1067
+ // 루프 레이블 푸시
1068
+ const loopLabel: LoopLabel = { loopStart, breakPatches: [], continuePatches: [] };
1069
+ this.currentLoopLabels.push(loopLabel);
1070
+
1071
+ // 조건 계산
1072
+ this.compileExpr(stmt.condition);
1073
+ this.chunk.emit(Op.JUMP_IF_FALSE, stmt.line);
1074
+ const exitJump = this.chunk.currentOffset();
1075
+ this.chunk.emitI32(0, stmt.line); // 패치 예정
1076
+
1077
+ // body
1078
+ this.beginScope();
1079
+ for (const s of stmt.body) this.compileStmt(s);
1080
+ this.endScope(stmt.line);
1081
+
1082
+ // JUMP → loopStart
1083
+ this.chunk.emit(Op.JUMP, stmt.line);
1084
+ this.chunk.emitI32(loopStart, stmt.line);
1085
+
1086
+ // exit 주소 결정 및 패치
1087
+ const exitTarget = this.chunk.currentOffset();
1088
+ this.chunk.patchI32(exitJump, exitTarget);
1089
+
1090
+ // break 문 모두 패치
1091
+ for (const breakPatch of loopLabel.breakPatches) {
1092
+ this.chunk.patchI32(breakPatch, exitTarget);
1093
+ }
1094
+
1095
+ // continue 문 모두 패치 (while은 loopStart로)
1096
+ for (const continuePatch of loopLabel.continuePatches) {
1097
+ this.chunk.patchI32(continuePatch, loopStart);
1098
+ }
1099
+
1100
+ // 루프 레이블 팝
1101
+ this.currentLoopLabels.pop();
1102
+ }
1103
+
1104
+ private compileSpawnStmt(stmt: Stmt & { kind: "spawn_stmt" }): void {
1105
+ // SPAWN: 본문의 시작 오프셋을 인자로
1106
+ this.chunk.emit(Op.SPAWN, stmt.line);
1107
+ const bodyJump = this.chunk.currentOffset();
1108
+ this.chunk.emitI32(0, stmt.line); // 패치
1109
+
1110
+ // main은 spawn 다음으로 점프
1111
+ this.chunk.emit(Op.JUMP, stmt.line);
1112
+ const skipJump = this.chunk.currentOffset();
1113
+ this.chunk.emitI32(0, stmt.line);
1114
+
1115
+ // spawn body 시작 (패치)
1116
+ this.chunk.patchI32(bodyJump, this.chunk.currentOffset());
1117
+ for (const s of stmt.body) this.compileStmt(s);
1118
+ this.chunk.emit(Op.HALT, stmt.line);
1119
+
1120
+ // skip end (패치)
1121
+ this.chunk.patchI32(skipJump, this.chunk.currentOffset());
1122
+ }
1123
+
1124
+ private compileReturnStmt(stmt: Stmt & { kind: "return_stmt" }): void {
1125
+ if (stmt.value) {
1126
+ this.compileExpr(stmt.value);
1127
+ } else {
1128
+ this.chunk.emit(Op.PUSH_VOID, stmt.line);
1129
+ }
1130
+ this.chunk.emit(Op.RETURN, stmt.line);
1131
+ }
1132
+
1133
+ private compileBreakStmt(stmt: Stmt & { kind: "break_stmt" }): void {
1134
+ if (this.currentLoopLabels.length === 0) {
1135
+ throw new Error("break 문이 루프 밖에 있습니다");
1136
+ }
1137
+ const loopLabel = this.currentLoopLabels[this.currentLoopLabels.length - 1];
1138
+ this.chunk.emit(Op.JUMP, stmt.line);
1139
+ const breakPatch = this.chunk.currentOffset();
1140
+ this.chunk.emitI32(0, stmt.line); // placeholder
1141
+ loopLabel.breakPatches.push(breakPatch);
1142
+ }
1143
+
1144
+ private compileContinueStmt(stmt: Stmt & { kind: "continue_stmt" }): void {
1145
+ if (this.currentLoopLabels.length === 0) {
1146
+ throw new Error("continue 문이 루프 밖에 있습니다");
1147
+ }
1148
+ const loopLabel = this.currentLoopLabels[this.currentLoopLabels.length - 1];
1149
+ this.chunk.emit(Op.JUMP, stmt.line);
1150
+ const continuePatch = this.chunk.currentOffset();
1151
+ this.chunk.emitI32(0, stmt.line); // placeholder
1152
+ loopLabel.continuePatches.push(continuePatch);
1153
+ }
1154
+
1155
+ private compileExprStmt(stmt: Stmt & { kind: "expr_stmt" }): void {
1156
+ this.compileExpr(stmt.expr);
1157
+ // 할당은 이미 STORE_LOCAL을 emit하므로 POP 불필요
1158
+ if (stmt.expr.kind !== "assign") {
1159
+ this.chunk.emit(Op.POP, stmt.line);
1160
+ }
1161
+ }
1162
+
1163
+ // ============================================================
1164
+ // 식 컴파일
1165
+ // ============================================================
1166
+
1167
+ private compileExpr(expr: Expr): void {
1168
+ switch (expr.kind) {
1169
+ case "int_lit":
1170
+ this.chunk.emit(Op.PUSH_I32, expr.line);
1171
+ this.chunk.emitI32(expr.value, expr.line);
1172
+ break;
1173
+
1174
+ case "float_lit":
1175
+ this.chunk.emit(Op.PUSH_F64, expr.line);
1176
+ this.chunk.emitF64(expr.value, expr.line);
1177
+ break;
1178
+
1179
+ case "string_lit": {
1180
+ const idx = this.chunk.addConstant(expr.value);
1181
+ this.chunk.emit(Op.PUSH_STR, expr.line);
1182
+ this.chunk.emitI32(idx, expr.line);
1183
+ break;
1184
+ }
1185
+
1186
+ case "bool_lit":
1187
+ this.chunk.emit(expr.value ? Op.PUSH_TRUE : Op.PUSH_FALSE, expr.line);
1188
+ break;
1189
+
1190
+ case "ident":
1191
+ this.compileIdent(expr);
1192
+ break;
1193
+
1194
+ case "binary":
1195
+ this.compileBinary(expr);
1196
+ break;
1197
+
1198
+ case "unary":
1199
+ this.compileUnary(expr);
1200
+ break;
1201
+
1202
+ case "await": {
1203
+ // Promise 객체를 스택에 push
1204
+ this.compileExpr(expr.expr);
1205
+
1206
+ // Promise._value 필드 추출
1207
+ this.chunk.emit(Op.STRUCT_GET, expr.line);
1208
+ this.chunk.emitI32(
1209
+ this.chunk.addConstant("_value"),
1210
+ expr.line
1211
+ );
1212
+
1213
+ // 이제 스택에 추출된 값이 있음
1214
+ break;
1215
+ }
1216
+
1217
+ case "call":
1218
+ this.compileCall(expr);
1219
+ break;
1220
+
1221
+ case "index":
1222
+ this.compileExpr(expr.object);
1223
+ this.compileExpr(expr.index);
1224
+ this.chunk.emit(Op.ARRAY_GET, expr.line);
1225
+ break;
1226
+
1227
+ case "field_access":
1228
+ this.compileExpr(expr.object);
1229
+ this.chunk.emit(Op.STRUCT_GET, expr.line);
1230
+ this.chunk.emitI32(this.chunk.addConstant(expr.field), expr.line);
1231
+ break;
1232
+
1233
+ case "assign":
1234
+ this.compileAssign(expr);
1235
+ break;
1236
+
1237
+ case "try":
1238
+ this.compileExpr(expr.operand);
1239
+ this.chunk.emit(Op.UNWRAP, expr.line);
1240
+ break;
1241
+
1242
+ case "if_expr":
1243
+ this.compileIfExpr(expr);
1244
+ break;
1245
+
1246
+ case "match_expr":
1247
+ this.compileMatchExpr(expr);
1248
+ break;
1249
+
1250
+ case "array_lit":
1251
+ for (const el of expr.elements) this.compileExpr(el);
1252
+ this.chunk.emit(Op.ARRAY_NEW, expr.line);
1253
+ this.chunk.emitI32(expr.elements.length, expr.line);
1254
+ break;
1255
+
1256
+ case "struct_lit":
1257
+ for (const f of expr.fields) {
1258
+ this.chunk.emit(Op.PUSH_STR, expr.line);
1259
+ this.chunk.emitI32(this.chunk.addConstant(f.name), expr.line);
1260
+ this.compileExpr(f.value);
1261
+ }
1262
+ this.chunk.emit(Op.STRUCT_NEW, expr.line);
1263
+ this.chunk.emitI32(expr.fields.length, expr.line);
1264
+ break;
1265
+
1266
+ case "block_expr":
1267
+ this.beginScope();
1268
+ for (const s of expr.stmts) this.compileStmt(s);
1269
+ if (expr.expr) this.compileExpr(expr.expr);
1270
+ else this.chunk.emit(Op.PUSH_VOID, expr.line);
1271
+ this.endScope(expr.line);
1272
+ break;
1273
+
1274
+ case "chan_new":
1275
+ this.chunk.emit(Op.CHAN_NEW, expr.line);
1276
+ break;
1277
+
1278
+ case "chan_send":
1279
+ this.compileExpr(expr.chan);
1280
+ this.compileExpr(expr.value);
1281
+ this.chunk.emit(Op.CHAN_SEND, expr.line);
1282
+ break;
1283
+
1284
+ case "chan_recv":
1285
+ this.compileExpr(expr.chan);
1286
+ this.chunk.emit(Op.CHAN_RECV, expr.line);
1287
+ break;
1288
+ }
1289
+ }
1290
+
1291
+ private compileIdent(expr: Expr & { kind: "ident" }): void {
1292
+ const local = this.resolveLocal(expr.name);
1293
+ if (local !== -1) {
1294
+ this.chunk.emit(Op.LOAD_LOCAL, expr.line);
1295
+ this.chunk.emitI32(local, expr.line);
1296
+ } else {
1297
+ // global 또는 함수 이름 — 함수 참조로 처리
1298
+ this.chunk.emit(Op.LOAD_GLOBAL, expr.line);
1299
+ this.chunk.emitI32(this.chunk.addConstant(expr.name), expr.line);
1300
+ }
1301
+ }
1302
+
1303
+ private compileBinary(expr: Expr & { kind: "binary" }): void {
1304
+ // 문자열 + 문자열
1305
+ if (expr.op === "+") {
1306
+ this.compileExpr(expr.left);
1307
+ this.compileExpr(expr.right);
1308
+ // VM이 런타임에 타입 보고 ADD_I32/ADD_F64/STR_CONCAT 결정
1309
+ // 여기서는 일반 ADD로 emit
1310
+ this.chunk.emit(Op.ADD_I32, expr.line);
1311
+ return;
1312
+ }
1313
+
1314
+ this.compileExpr(expr.left);
1315
+ this.compileExpr(expr.right);
1316
+
1317
+ switch (expr.op) {
1318
+ case "-": this.chunk.emit(Op.SUB_I32, expr.line); break;
1319
+ case "*": this.chunk.emit(Op.MUL_I32, expr.line); break;
1320
+ case "/": this.chunk.emit(Op.DIV_I32, expr.line); break;
1321
+ case "%": this.chunk.emit(Op.MOD_I32, expr.line); break;
1322
+ case "==": this.chunk.emit(Op.EQ, expr.line); break;
1323
+ case "!=": this.chunk.emit(Op.NEQ, expr.line); break;
1324
+ case "<": this.chunk.emit(Op.LT, expr.line); break;
1325
+ case ">": this.chunk.emit(Op.GT, expr.line); break;
1326
+ case "<=": this.chunk.emit(Op.LTEQ, expr.line); break;
1327
+ case ">=": this.chunk.emit(Op.GTEQ, expr.line); break;
1328
+ case "&&": this.chunk.emit(Op.AND, expr.line); break;
1329
+ case "||": this.chunk.emit(Op.OR, expr.line); break;
1330
+ }
1331
+ }
1332
+
1333
+ private compileUnary(expr: Expr & { kind: "unary" }): void {
1334
+ this.compileExpr(expr.operand);
1335
+ if (expr.op === "-") this.chunk.emit(Op.NEG_I32, expr.line);
1336
+ if (expr.op === "!") this.chunk.emit(Op.NOT, expr.line);
1337
+ }
1338
+
1339
+ private compileCall(expr: Expr & { kind: "call" }): void {
1340
+ // 내장 함수
1341
+ if (expr.callee.kind === "ident") {
1342
+ const name = expr.callee.name;
1343
+ const builtins = [
1344
+ "println", "print", "read_line", "read_file", "write_file",
1345
+ "i32", "i64", "f64", "str",
1346
+ "push", "pop", "slice", "clone", "length",
1347
+ "char_at", "char_code", "chr", "contains", "split", "trim", "to_upper", "to_lower",
1348
+ "abs", "min", "max", "pow", "sqrt",
1349
+ "range", "channel", "panic", "typeof", "assert",
1350
+ // Bitwise Operations (5) - A-1
1351
+ "bitand", "bitor", "bitxor", "shl", "shr",
1352
+ // Phase 7: 20 Core Libraries
1353
+ // Cryptography & Encoding (6)
1354
+ "md5", "sha256", "sha512", "base64_encode", "base64_decode", "hmac",
1355
+ // JSON (4)
1356
+ "json_parse", "json_stringify", "json_validate", "json_pretty",
1357
+ // Advanced Strings (3)
1358
+ "starts_with", "ends_with", "replace",
1359
+ // Advanced Arrays (3)
1360
+ "reverse", "sort", "unique",
1361
+ // Math (2)
1362
+ "gcd", "lcm",
1363
+ // Utils (2)
1364
+ "uuid", "timestamp",
1365
+ // Channel (2)
1366
+ "send", "recv",
1367
+ // Environment (1)
1368
+ "env",
1369
+ // HTTP Client (4) - Phase 2
1370
+ "http_get", "http_post", "http_post_json", "fetch",
1371
+ // Database SQLite (7) + PostgreSQL (7) + MySQL (7) - Phase 3
1372
+ "sqlite_open", "sqlite_query", "sqlite_execute", "sqlite_close",
1373
+ "sqlite_begin", "sqlite_commit", "sqlite_rollback",
1374
+ "pg_connect", "pg_query", "pg_execute", "pg_close",
1375
+ "pg_begin", "pg_commit", "pg_rollback",
1376
+ "mysql_connect", "mysql_query", "mysql_execute", "mysql_close",
1377
+ "mysql_begin", "mysql_commit", "mysql_rollback",
1378
+ // v4.3 Extensions
1379
+ // Math (7) - B-1
1380
+ "floor", "ceil", "round", "random", "sin", "cos", "log",
1381
+ // String (3) - B-2
1382
+ "index_of", "pad_left", "pad_right",
1383
+ // Regex (3) - B-3
1384
+ "regex_match", "regex_find_all", "regex_replace",
1385
+ // CSV (2) - B-4
1386
+ "csv_parse", "csv_stringify",
1387
+ // DateTime (3) - B-5
1388
+ "now", "format_date", "parse_date",
1389
+ // YAML (2) - v4.3 Extension
1390
+ "yaml_parse", "yaml_stringify",
1391
+ // HTTP Server (3) - gogs-server 지원
1392
+ "http_server_create", "http_server_accept", "http_server_respond",
1393
+ // External Commands (1)
1394
+ "exec_command",
1395
+ ];
1396
+
1397
+ if (builtins.includes(name)) {
1398
+ for (const arg of expr.args) this.compileExpr(arg);
1399
+ this.chunk.emit(Op.CALL_BUILTIN, expr.line);
1400
+ this.chunk.emitI32(this.chunk.addConstant(name), expr.line);
1401
+ this.chunk.emitByte(expr.args.length, expr.line);
1402
+ return;
1403
+ }
1404
+
1405
+ // 사용자 함수
1406
+ const fnIdx = this.chunk.functions.findIndex((f) => f.name === name);
1407
+ if (fnIdx !== -1) {
1408
+ for (const arg of expr.args) this.compileExpr(arg);
1409
+ this.chunk.emit(Op.CALL, expr.line);
1410
+ this.chunk.emitI32(fnIdx, expr.line);
1411
+ this.chunk.emitByte(expr.args.length, expr.line);
1412
+ return;
1413
+ }
1414
+ }
1415
+
1416
+ // 메서드 호출: obj.method(args)
1417
+ if (expr.callee.kind === "field_access") {
1418
+ this.compileExpr(expr.callee.object);
1419
+ for (const arg of expr.args) this.compileExpr(arg);
1420
+ this.chunk.emit(Op.CALL_BUILTIN, expr.line);
1421
+ this.chunk.emitI32(this.chunk.addConstant(expr.callee.field), expr.line);
1422
+ this.chunk.emitByte(expr.args.length + 1, expr.line); // +1 for self
1423
+ return;
1424
+ }
1425
+
1426
+ // fallback: 동적 호출
1427
+ this.compileExpr(expr.callee);
1428
+ for (const arg of expr.args) this.compileExpr(arg);
1429
+ this.chunk.emit(Op.CALL, expr.line);
1430
+ this.chunk.emitI32(-1, expr.line);
1431
+ this.chunk.emitByte(expr.args.length, expr.line);
1432
+ }
1433
+
1434
+ private compileAssign(expr: Expr & { kind: "assign" }): void {
1435
+ if (expr.target.kind === "ident") {
1436
+ this.compileExpr(expr.value);
1437
+ const slot = this.resolveLocal(expr.target.name);
1438
+ if (slot !== -1) {
1439
+ this.chunk.emit(Op.STORE_LOCAL, expr.line);
1440
+ this.chunk.emitI32(slot, expr.line);
1441
+ } else {
1442
+ this.chunk.emit(Op.STORE_GLOBAL, expr.line);
1443
+ this.chunk.emitI32(this.chunk.addConstant(expr.target.name), expr.line);
1444
+ }
1445
+ } else if (expr.target.kind === "index") {
1446
+ // Stack order: array, index, value
1447
+ // VM ARRAY_SET pops: val=pop(), idx=pop(), arr=pop()
1448
+ this.compileExpr(expr.target.object);
1449
+ this.compileExpr(expr.target.index);
1450
+ this.compileExpr(expr.value);
1451
+ this.chunk.emit(Op.ARRAY_SET, expr.line);
1452
+ }
1453
+ }
1454
+
1455
+ private compileIfExpr(expr: Expr & { kind: "if_expr" }): void {
1456
+ this.compileExpr(expr.condition);
1457
+ this.chunk.emit(Op.JUMP_IF_FALSE, expr.line);
1458
+ const elseJump = this.chunk.currentOffset();
1459
+ this.chunk.emitI32(0, expr.line);
1460
+
1461
+ // then — 마지막 식이 값
1462
+ for (const e of expr.then) this.compileExpr(e);
1463
+
1464
+ this.chunk.emit(Op.JUMP, expr.line);
1465
+ const endJump = this.chunk.currentOffset();
1466
+ this.chunk.emitI32(0, expr.line);
1467
+
1468
+ this.chunk.patchI32(elseJump, this.chunk.currentOffset());
1469
+
1470
+ // else — 마지막 식이 값
1471
+ for (const e of expr.else_) this.compileExpr(e);
1472
+
1473
+ this.chunk.patchI32(endJump, this.chunk.currentOffset());
1474
+ }
1475
+
1476
+ private compileMatchExpr(expr: Expr & { kind: "match_expr" }): void {
1477
+ this.compileExpr(expr.subject);
1478
+
1479
+ // subject를 로컬 슬롯에 저장 (struct/array 분해 바인딩용)
1480
+ const subjectSlot = this.declareLocal("__match_subject__");
1481
+ this.chunk.emit(Op.STORE_LOCAL, expr.line);
1482
+ this.chunk.emitI32(subjectSlot, expr.line);
1483
+
1484
+ const endJumps: number[] = [];
1485
+
1486
+ for (const arm of expr.arms) {
1487
+ // subject를 스택에 로드해서 테스트
1488
+ this.chunk.emit(Op.LOAD_LOCAL, expr.line);
1489
+ this.chunk.emitI32(subjectSlot, expr.line);
1490
+
1491
+ this.compilePatternTest(arm.pattern, expr.line);
1492
+ this.chunk.emit(Op.JUMP_IF_FALSE, expr.line);
1493
+ const nextArm = this.chunk.currentOffset();
1494
+ this.chunk.emitI32(0, expr.line);
1495
+
1496
+ // Pattern 매칭 성공 — scope 시작 및 변수 바인딩
1497
+ this.beginScope();
1498
+ this.compilePatternBind(arm.pattern, expr.line, subjectSlot);
1499
+
1500
+ // Guard 절이 있으면 추가 조건 검사
1501
+ let guardJump: number | null = null;
1502
+ if (arm.guard) {
1503
+ this.compileExpr(arm.guard);
1504
+ this.chunk.emit(Op.JUMP_IF_FALSE, expr.line);
1505
+ guardJump = this.chunk.currentOffset();
1506
+ this.chunk.emitI32(0, expr.line);
1507
+ }
1508
+
1509
+ // arm body의 값이 스택에 남음
1510
+ this.compileExpr(arm.body);
1511
+ this.endScope(expr.line); // scope 종료
1512
+
1513
+ this.chunk.emit(Op.JUMP, expr.line);
1514
+ endJumps.push(this.chunk.currentOffset());
1515
+ this.chunk.emitI32(0, expr.line);
1516
+
1517
+ // Guard 실패 시 처리 - guard fail 점프를 다음 arm으로 설정
1518
+ this.chunk.patchI32(nextArm, this.chunk.currentOffset());
1519
+ if (guardJump !== null) {
1520
+ // guard fail 시에도 같은 위치(nextArm)로 점프하도록 설정
1521
+ this.chunk.patchI32(guardJump, this.chunk.currentOffset());
1522
+ }
1523
+ }
1524
+
1525
+ // fallthrough (아무 arm도 매칭 안 됨) - void
1526
+ this.chunk.emit(Op.PUSH_VOID, expr.line);
1527
+
1528
+ // arm이 성공했으면 여기(PUSH_VOID 이후)로 점프
1529
+ const afterMatchLabel = this.chunk.currentOffset();
1530
+
1531
+ for (const j of endJumps) {
1532
+ this.chunk.patchI32(j, afterMatchLabel);
1533
+ }
1534
+ }
1535
+
1536
+ // ============================================================
1537
+ // 패턴 컴파일
1538
+ // ============================================================
1539
+
1540
+ private compilePatternTest(pattern: Pattern, line: number): void {
1541
+ switch (pattern.kind) {
1542
+ case "wildcard":
1543
+ case "ident":
1544
+ // 항상 매칭
1545
+ this.chunk.emit(Op.PUSH_TRUE, line);
1546
+ break;
1547
+ case "literal":
1548
+ this.compileExpr(pattern.value);
1549
+ this.chunk.emit(Op.EQ, line);
1550
+ break;
1551
+ case "none":
1552
+ this.chunk.emit(Op.IS_NONE, line);
1553
+ break;
1554
+ case "some":
1555
+ this.chunk.emit(Op.IS_SOME, line);
1556
+ break;
1557
+ case "ok":
1558
+ this.chunk.emit(Op.IS_OK, line);
1559
+ break;
1560
+ case "err":
1561
+ this.chunk.emit(Op.IS_ERR, line);
1562
+ break;
1563
+ case "struct":
1564
+ // 구조체 분해: 이름으로 타입 확인
1565
+ this.chunk.emit(Op.POP, line); // 일단 subject 제거
1566
+ this.chunk.emit(Op.PUSH_TRUE, line);
1567
+ break;
1568
+ case "array":
1569
+ // 배열 분해: 배열 타입 확인
1570
+ this.chunk.emit(Op.POP, line); // 일단 subject 제거
1571
+ this.chunk.emit(Op.PUSH_TRUE, line);
1572
+ break;
1573
+ case "tuple":
1574
+ // 튜플은 아직 미지원
1575
+ this.chunk.emit(Op.POP, line);
1576
+ this.chunk.emit(Op.PUSH_FALSE, line);
1577
+ break;
1578
+ }
1579
+ }
1580
+
1581
+ private compilePatternBind(pattern: Pattern, line: number, subjectSlot?: number): void {
1582
+ // 패턴에서 바인딩 변수 생성
1583
+ switch (pattern.kind) {
1584
+ case "ident": {
1585
+ // pattern test 후 스택에 subject가 있음 — 직접 저장
1586
+ const slot = this.declareLocal(pattern.name);
1587
+ this.chunk.emit(Op.STORE_LOCAL, line);
1588
+ this.chunk.emitI32(slot, line);
1589
+ break;
1590
+ }
1591
+ case "struct": {
1592
+ // 구조체 필드 분해 바인딩
1593
+ // Point { x, y } 패턴에서 각 필드에 대해:
1594
+ // LOAD_LOCAL(subjectSlot) → STRUCT_GET(field) → STORE_LOCAL(field_slot)
1595
+ for (const field of pattern.fields) {
1596
+ // subject 로드
1597
+ this.chunk.emit(Op.LOAD_LOCAL, line);
1598
+ this.chunk.emitI32(subjectSlot!, line);
1599
+
1600
+ // 필드 접근
1601
+ this.chunk.emit(Op.STRUCT_GET, line);
1602
+ this.chunk.emitI32(this.chunk.addConstant(field.name), line);
1603
+
1604
+ // 필드 패턴에 바인딩
1605
+ if (field.pattern.kind === "ident") {
1606
+ // 간단한 ident 바인딩
1607
+ const fieldSlot = this.declareLocal(field.pattern.name);
1608
+ this.chunk.emit(Op.STORE_LOCAL, line);
1609
+ this.chunk.emitI32(fieldSlot, line);
1610
+ } else {
1611
+ // 복잡한 패턴 (nested struct, array, etc)
1612
+ const fieldTempSlot = this.declareLocal(`__field_${field.name}__`);
1613
+ this.chunk.emit(Op.STORE_LOCAL, line);
1614
+ this.chunk.emitI32(fieldTempSlot, line);
1615
+ this.compilePatternBind(field.pattern, line, fieldTempSlot);
1616
+ }
1617
+ }
1618
+ break;
1619
+ }
1620
+ case "array": {
1621
+ // 배열 요소 분해 바인딩
1622
+ // [a, b, c] 패턴에서 각 요소에 대해:
1623
+ // LOAD_LOCAL(subjectSlot) → PUSH_I32(index) → ARRAY_GET → STORE_LOCAL(elem_slot)
1624
+ // [a, .., b] rest 패턴의 경우: 뒤쪽 요소는 배열 길이를 동적으로 계산해서 인덱싱
1625
+
1626
+ const restIndex = pattern.restIndex ?? pattern.elements.length;
1627
+ const afterRestCount = pattern.elements.length - restIndex;
1628
+ let arrayLenSlot: number | null = null;
1629
+
1630
+ // rest 패턴이 있고 뒤에 요소가 있으면 배열 길이를 미리 저장
1631
+ if (pattern.rest && afterRestCount > 0) {
1632
+ this.chunk.emit(Op.LOAD_LOCAL, line);
1633
+ this.chunk.emitI32(subjectSlot!, line);
1634
+ // length() 내장 함수 호출
1635
+ this.chunk.emit(Op.CALL_BUILTIN, line);
1636
+ this.chunk.emitI32(this.chunk.addConstant("length"), line);
1637
+ this.chunk.emitByte(1, line); // argCount = 1
1638
+ arrayLenSlot = this.declareLocal("__array_len__");
1639
+ this.chunk.emit(Op.STORE_LOCAL, line);
1640
+ this.chunk.emitI32(arrayLenSlot, line);
1641
+ }
1642
+
1643
+ for (let i = 0; i < pattern.elements.length; i++) {
1644
+ const elem = pattern.elements[i];
1645
+
1646
+ // 실제 배열 인덱스 결정
1647
+ const isAfterRest = i >= restIndex;
1648
+
1649
+ // subject 로드
1650
+ this.chunk.emit(Op.LOAD_LOCAL, line);
1651
+ this.chunk.emitI32(subjectSlot!, line);
1652
+
1653
+ // 인덱스 계산 및 로드
1654
+ if (isAfterRest && arrayLenSlot !== null) {
1655
+ // rest 뒤: len - (afterRestCount - (i - restIndex))
1656
+ // [a, .., b, c], restIndex=1, len=5 → i=1: 5-(2-0)=3, i=2: 5-(2-1)=4
1657
+ const offsetFromEnd = afterRestCount - (i - restIndex);
1658
+ this.chunk.emit(Op.LOAD_LOCAL, line);
1659
+ this.chunk.emitI32(arrayLenSlot, line);
1660
+ this.chunk.emit(Op.PUSH_I32, line);
1661
+ this.chunk.emitI32(offsetFromEnd, line);
1662
+ this.chunk.emit(Op.SUB_I32, line);
1663
+ } else {
1664
+ // rest 앞: 순서대로 0, 1, 2, ...
1665
+ this.chunk.emit(Op.PUSH_I32, line);
1666
+ this.chunk.emitI32(i, line);
1667
+ }
1668
+
1669
+ // 배열 요소 접근
1670
+ this.chunk.emit(Op.ARRAY_GET, line);
1671
+
1672
+ // 요소 패턴에 바인딩
1673
+ if (elem.kind === "ident") {
1674
+ // 간단한 ident 바인딩
1675
+ const elemSlot = this.declareLocal(elem.name);
1676
+ this.chunk.emit(Op.STORE_LOCAL, line);
1677
+ this.chunk.emitI32(elemSlot, line);
1678
+ } else if (elem.kind === "wildcard") {
1679
+ // wildcard는 버림
1680
+ this.chunk.emit(Op.POP, line);
1681
+ } else {
1682
+ // 복잡한 패턴 (nested struct, array, etc)
1683
+ const elemTempSlot = this.declareLocal(`__elem_${i}__`);
1684
+ this.chunk.emit(Op.STORE_LOCAL, line);
1685
+ this.chunk.emitI32(elemTempSlot, line);
1686
+ this.compilePatternBind(elem, line, elemTempSlot);
1687
+ }
1688
+ }
1689
+ break;
1690
+ }
1691
+ case "some":
1692
+ case "ok": {
1693
+ // Option/Result의 inner 패턴 처리
1694
+ if (pattern.inner && subjectSlot !== undefined) {
1695
+ // LOAD_LOCAL(subjectSlot) → UNWRAP → 임시 슬롯에 저장
1696
+ this.chunk.emit(Op.LOAD_LOCAL, line);
1697
+ this.chunk.emitI32(subjectSlot, line);
1698
+ this.chunk.emit(Op.UNWRAP, line);
1699
+
1700
+ if (pattern.inner.kind === "ident") {
1701
+ const innerSlot = this.declareLocal(pattern.inner.name);
1702
+ this.chunk.emit(Op.STORE_LOCAL, line);
1703
+ this.chunk.emitI32(innerSlot, line);
1704
+ } else {
1705
+ const innerTempSlot = this.declareLocal(`__unwrapped__`);
1706
+ this.chunk.emit(Op.STORE_LOCAL, line);
1707
+ this.chunk.emitI32(innerTempSlot, line);
1708
+ this.compilePatternBind(pattern.inner, line, innerTempSlot);
1709
+ }
1710
+ }
1711
+ break;
1712
+ }
1713
+ case "err": {
1714
+ // Err의 inner 패턴 처리 (same as Ok)
1715
+ if (pattern.inner && subjectSlot !== undefined) {
1716
+ this.chunk.emit(Op.LOAD_LOCAL, line);
1717
+ this.chunk.emitI32(subjectSlot, line);
1718
+ this.chunk.emit(Op.UNWRAP, line);
1719
+
1720
+ if (pattern.inner.kind === "ident") {
1721
+ const innerSlot = this.declareLocal(pattern.inner.name);
1722
+ this.chunk.emit(Op.STORE_LOCAL, line);
1723
+ this.chunk.emitI32(innerSlot, line);
1724
+ } else {
1725
+ const innerTempSlot = this.declareLocal(`__unwrapped__`);
1726
+ this.chunk.emit(Op.STORE_LOCAL, line);
1727
+ this.chunk.emitI32(innerTempSlot, line);
1728
+ this.compilePatternBind(pattern.inner, line, innerTempSlot);
1729
+ }
1730
+ }
1731
+ break;
1732
+ }
1733
+ case "wildcard":
1734
+ case "literal":
1735
+ case "none":
1736
+ case "tuple":
1737
+ // 이들은 바인딩이 필요 없음
1738
+ break;
1739
+ }
1740
+ }
1741
+
1742
+ // ============================================================
1743
+ // 스코프 관리
1744
+ // ============================================================
1745
+
1746
+ private beginScope(): void {
1747
+ this.scopeDepth++;
1748
+ }
1749
+
1750
+ private endScope(line: number): void {
1751
+ while (this.locals.length > 0 && this.locals[this.locals.length - 1].depth === this.scopeDepth) {
1752
+ this.locals.pop();
1753
+ this.nextSlot--;
1754
+ }
1755
+ this.scopeDepth--;
1756
+ }
1757
+
1758
+ private declareLocal(name: string): number {
1759
+ const slot = this.nextSlot++;
1760
+ this.locals.push({ name, slot, depth: this.scopeDepth });
1761
+ return slot;
1762
+ }
1763
+
1764
+ private resolveLocal(name: string): number {
1765
+ for (let i = this.locals.length - 1; i >= 0; i--) {
1766
+ if (this.locals[i].name === name) return this.locals[i].slot;
1767
+ }
1768
+ return -1;
1769
+ }
1770
+ }