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