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
package/src/parser.ts ADDED
@@ -0,0 +1,1211 @@
1
+ // FreeLang v4 — Parser (SPEC_05 구현)
2
+ // RD(문) + Pratt(식) 하이브리드
3
+
4
+ import { Token, TokenType } from "./lexer";
5
+ import {
6
+ Program, Stmt, Expr, TypeAnnotation, Pattern, MatchArm, Param, StructField, FnParam, ImportDecl, ExportDecl, ImportItem,
7
+ } from "./ast";
8
+
9
+ // ============================================================
10
+ // ParseError
11
+ // ============================================================
12
+
13
+ export type ParseError = {
14
+ message: string;
15
+ line: number;
16
+ col: number;
17
+ };
18
+
19
+ // ============================================================
20
+ // Binding Power (SPEC_05 Q4)
21
+ // ============================================================
22
+
23
+ const BP_ASSIGN = 10;
24
+ const BP_OR = 20;
25
+ const BP_AND = 30;
26
+ const BP_EQUALITY = 40;
27
+ const BP_COMPARISON = 50;
28
+ const BP_ADDITIVE = 60;
29
+ const BP_MULTIPLICATIVE = 70;
30
+ const BP_UNARY = 90;
31
+ const BP_POSTFIX = 100;
32
+
33
+ function infixBP(type: TokenType): number {
34
+ switch (type) {
35
+ // EQ는 Pratt에서 처리하지 않음 → ExprStmt에서 할당으로 처리
36
+ case TokenType.OR: return BP_OR;
37
+ case TokenType.AND: return BP_AND;
38
+ case TokenType.EQEQ:
39
+ case TokenType.NEQ: return BP_EQUALITY;
40
+ case TokenType.LT:
41
+ case TokenType.GT:
42
+ case TokenType.LTEQ:
43
+ case TokenType.GTEQ: return BP_COMPARISON;
44
+ case TokenType.PLUS:
45
+ case TokenType.MINUS: return BP_ADDITIVE;
46
+ case TokenType.STAR:
47
+ case TokenType.SLASH:
48
+ case TokenType.PERCENT: return BP_MULTIPLICATIVE;
49
+ case TokenType.LARROW: return BP_ASSIGN; // 채널 송신
50
+ case TokenType.LPAREN:
51
+ case TokenType.LBRACKET:
52
+ case TokenType.DOT:
53
+ case TokenType.QUESTION: return BP_POSTFIX;
54
+ default: return 0;
55
+ }
56
+ }
57
+
58
+ // ============================================================
59
+ // Parser
60
+ // ============================================================
61
+
62
+ export class Parser {
63
+ private tokens: Token[];
64
+ private pos: number = 0;
65
+ private errors: ParseError[] = [];
66
+
67
+ constructor(tokens: Token[]) {
68
+ this.tokens = tokens;
69
+ }
70
+
71
+ parse(): { program: Program; errors: ParseError[] } {
72
+ const stmts: Stmt[] = [];
73
+ while (!this.isAtEnd()) {
74
+ try {
75
+ stmts.push(this.parseStmt());
76
+ } catch (e) {
77
+ // 에러 복구: 다음 문 시작까지 건너뜀
78
+ this.synchronize();
79
+ }
80
+ }
81
+ return { program: { stmts }, errors: this.errors };
82
+ }
83
+
84
+ // ============================================================
85
+ // 문 파싱 — Recursive Descent (SPEC_05 Q1)
86
+ // ============================================================
87
+
88
+ private parseStmt(): Stmt {
89
+ const tok = this.peek();
90
+
91
+ switch (tok.type) {
92
+ case TokenType.IMPORT:
93
+ return this.parseImportStmt();
94
+ case TokenType.EXPORT:
95
+ return this.parseExportStmt();
96
+ case TokenType.VAR:
97
+ case TokenType.LET:
98
+ case TokenType.CONST:
99
+ return this.parseVarDecl();
100
+ case TokenType.ASYNC:
101
+ case TokenType.FN:
102
+ return this.parseFnDecl();
103
+ case TokenType.STRUCT:
104
+ return this.parseStructDecl();
105
+ case TokenType.TRAIT:
106
+ return this.parseTraitDecl();
107
+ case TokenType.IMPL:
108
+ return this.parseImplDecl();
109
+ case TokenType.IF:
110
+ return this.parseIfStmt();
111
+ case TokenType.MATCH:
112
+ return this.parseMatchStmt();
113
+ case TokenType.FOR:
114
+ return this.parseForStmt();
115
+ case TokenType.WHILE:
116
+ return this.parseWhileStmt();
117
+ case TokenType.BREAK:
118
+ return this.parseBreakStmt();
119
+ case TokenType.CONTINUE:
120
+ return this.parseContinueStmt();
121
+ case TokenType.SPAWN:
122
+ return this.parseSpawnStmt();
123
+ case TokenType.RETURN:
124
+ return this.parseReturnStmt();
125
+ default:
126
+ return this.parseExprStmt();
127
+ }
128
+ }
129
+
130
+ // var/let/const 선언
131
+ private parseVarDecl(): Stmt {
132
+ const kw = this.advance(); // var/let/const
133
+ const mutable = kw.type === TokenType.VAR;
134
+ const name = this.expectIdent("variable name");
135
+ let type: TypeAnnotation | null = null;
136
+
137
+ if (this.check(TokenType.COLON)) {
138
+ this.advance(); // :
139
+ type = this.parseType();
140
+ }
141
+
142
+ this.expect(TokenType.EQ, "expected '=' in variable declaration");
143
+ const init = this.parseExpr(0);
144
+ this.match(TokenType.SEMICOLON); // optional semicolon
145
+
146
+ return { kind: "var_decl", name, mutable, type, init, line: kw.line, col: kw.col };
147
+ }
148
+
149
+ // fn 선언 (async fn 지원)
150
+ private parseFnDecl(): Stmt {
151
+ let isAsync = false;
152
+ let kw = this.peek();
153
+
154
+ // async 키워드 확인
155
+ if (this.match(TokenType.ASYNC)) {
156
+ isAsync = true;
157
+ }
158
+
159
+ kw = this.advance(); // fn
160
+ const name = this.expectIdent("function name");
161
+
162
+ // Generic 타입 파라미터: fn foo<T, K>(...) [STEP B-1]
163
+ const typeParams: string[] = [];
164
+ if (this.match(TokenType.LT)) {
165
+ do {
166
+ typeParams.push(this.expectIdent("type parameter"));
167
+ } while (this.match(TokenType.COMMA));
168
+ this.expect(TokenType.GT, "expected '>' after type parameters");
169
+ }
170
+
171
+ this.expect(TokenType.LPAREN, "expected '(' after function name");
172
+
173
+ const params: Param[] = [];
174
+ if (!this.check(TokenType.RPAREN)) {
175
+ do {
176
+ const pName = this.expectIdent("parameter name");
177
+ this.expect(TokenType.COLON, "expected ':' after parameter name");
178
+ const pType = this.parseType();
179
+ params.push({ name: pName, type: pType });
180
+ } while (this.match(TokenType.COMMA));
181
+ }
182
+ this.expect(TokenType.RPAREN, "expected ')' after parameters");
183
+
184
+ // 반환 타입 (필수 — SPEC_06: 함수 시그니처 명시)
185
+ this.expect(TokenType.RARROW, "expected '->' for return type");
186
+ const returnType = this.parseType();
187
+
188
+ this.expect(TokenType.LBRACE, "expected '{' for function body");
189
+ const body = this.parseBlock();
190
+
191
+ return { kind: "fn_decl", name, isAsync, typeParams, params, returnType, body, line: kw.line, col: kw.col };
192
+ }
193
+
194
+ // struct 선언
195
+ private parseStructDecl(): Stmt {
196
+ const kw = this.advance(); // struct
197
+ const name = this.expectIdent("struct name");
198
+
199
+ // Generic 타입 파라미터: struct Box<T> [STEP B-2]
200
+ const typeParams: string[] = [];
201
+ if (this.match(TokenType.LT)) {
202
+ do {
203
+ typeParams.push(this.expectIdent("type parameter"));
204
+ } while (this.match(TokenType.COMMA));
205
+ this.expect(TokenType.GT, "expected '>' after type parameters");
206
+ }
207
+
208
+ this.expect(TokenType.LBRACE, "expected '{' after struct name");
209
+
210
+ const fields: StructField[] = [];
211
+ if (!this.check(TokenType.RBRACE)) {
212
+ do {
213
+ if (this.check(TokenType.RBRACE)) break; // Allow trailing comma
214
+ const fieldName = this.expectIdent("field name");
215
+ this.expect(TokenType.COLON, "expected ':' after field name");
216
+ const fieldType = this.parseType();
217
+ fields.push({ name: fieldName, type: fieldType });
218
+ } while (this.match(TokenType.COMMA));
219
+ }
220
+
221
+ this.expect(TokenType.RBRACE, "expected '}' to close struct");
222
+
223
+ return { kind: "struct_decl", name, typeParams, fields, line: kw.line, col: kw.col };
224
+ }
225
+
226
+ // trait 선언
227
+ private parseTraitDecl(): Stmt {
228
+ const kw = this.advance(); // trait
229
+ const name = this.expectIdent("trait name");
230
+
231
+ // Generic 타입 파라미터
232
+ const typeParams: string[] = [];
233
+ if (this.match(TokenType.LT)) {
234
+ do {
235
+ typeParams.push(this.expectIdent("type parameter"));
236
+ } while (this.match(TokenType.COMMA));
237
+ this.expect(TokenType.GT, "expected '>' after type parameters");
238
+ }
239
+
240
+ this.expect(TokenType.LBRACE, "expected '{' after trait name");
241
+
242
+ const methods: any[] = [];
243
+ if (!this.check(TokenType.RBRACE)) {
244
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
245
+ const mLine = this.peek().line;
246
+ const mCol = this.peek().col;
247
+ this.expect(TokenType.FN, "expected 'fn' in trait method");
248
+ const methodName = this.expectIdent("method name");
249
+
250
+ this.expect(TokenType.LPAREN, "expected '(' after method name");
251
+ const params: Param[] = [];
252
+ if (!this.check(TokenType.RPAREN)) {
253
+ do {
254
+ const pName = this.expectIdent("parameter name");
255
+ // self는 타입 명시 없음
256
+ if (pName === "self") {
257
+ if (this.check(TokenType.COMMA)) {
258
+ this.advance(); // consume comma
259
+ params.push({ name: pName, type: { kind: "self_type" } });
260
+ continue;
261
+ } else if (this.check(TokenType.RPAREN)) {
262
+ params.push({ name: pName, type: { kind: "self_type" } });
263
+ break;
264
+ }
265
+ }
266
+ // 일반 파라미터
267
+ this.expect(TokenType.COLON, "expected ':' after parameter name");
268
+ const pType = this.parseType();
269
+ params.push({ name: pName, type: pType });
270
+ } while (this.match(TokenType.COMMA));
271
+ }
272
+ this.expect(TokenType.RPAREN, "expected ')' after method parameters");
273
+
274
+ this.expect(TokenType.RARROW, "expected '->' for return type");
275
+ const returnType = this.parseType();
276
+ this.match(TokenType.SEMICOLON);
277
+
278
+ methods.push({ name: methodName, params, returnType, line: mLine, col: mCol });
279
+ }
280
+ }
281
+
282
+ this.expect(TokenType.RBRACE, "expected '}' to close trait");
283
+
284
+ return { kind: "trait_decl", name, typeParams, methods, line: kw.line, col: kw.col };
285
+ }
286
+
287
+ // impl 선언
288
+ private parseImplDecl(): Stmt {
289
+ const kw = this.advance(); // impl
290
+
291
+ // Generic 타입 파라미터
292
+ const typeParams: string[] = [];
293
+ if (this.match(TokenType.LT)) {
294
+ do {
295
+ typeParams.push(this.expectIdent("type parameter"));
296
+ } while (this.match(TokenType.COMMA));
297
+ this.expect(TokenType.GT, "expected '>' after type parameters");
298
+ }
299
+
300
+ // Trait name: impl [Drawable] for Circle
301
+ let trait: string | null = null;
302
+ let forType: TypeAnnotation;
303
+
304
+ // Check if it's "impl Trait for Type" or just "impl Type"
305
+ const savedPos = this.pos;
306
+ if (this.peek().type === TokenType.IDENT) {
307
+ const firstIdent = this.peek().lexeme;
308
+ this.advance(); // consume identifier
309
+
310
+ if (this.match(TokenType.FOR)) {
311
+ // It was "impl Trait for Type"
312
+ trait = firstIdent;
313
+ forType = this.parseType();
314
+ } else {
315
+ // It was "impl Type" (inherent impl) — reset and parse as type
316
+ this.pos = savedPos;
317
+ forType = this.parseType();
318
+ }
319
+ } else {
320
+ forType = this.parseType();
321
+ }
322
+
323
+ this.expect(TokenType.LBRACE, "expected '{' for impl body");
324
+
325
+ const methods: any[] = [];
326
+ if (!this.check(TokenType.RBRACE)) {
327
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
328
+ const mLine = this.peek().line;
329
+ const mCol = this.peek().col;
330
+ this.expect(TokenType.FN, "expected 'fn' in impl method");
331
+ const methodName = this.expectIdent("method name");
332
+
333
+ this.expect(TokenType.LPAREN, "expected '(' after method name");
334
+ const params: Param[] = [];
335
+ if (!this.check(TokenType.RPAREN)) {
336
+ do {
337
+ const pName = this.expectIdent("parameter name");
338
+ // self는 타입 명시 없음
339
+ if (pName === "self") {
340
+ if (this.check(TokenType.COMMA)) {
341
+ this.advance(); // consume comma
342
+ params.push({ name: pName, type: { kind: "self_type" } });
343
+ continue;
344
+ } else if (this.check(TokenType.RPAREN)) {
345
+ params.push({ name: pName, type: { kind: "self_type" } });
346
+ break;
347
+ }
348
+ }
349
+ // 일반 파라미터
350
+ this.expect(TokenType.COLON, "expected ':' after parameter name");
351
+ const pType = this.parseType();
352
+ params.push({ name: pName, type: pType });
353
+ } while (this.match(TokenType.COMMA));
354
+ }
355
+ this.expect(TokenType.RPAREN, "expected ')' after method parameters");
356
+
357
+ this.expect(TokenType.RARROW, "expected '->' for return type");
358
+ const returnType = this.parseType();
359
+
360
+ this.expect(TokenType.LBRACE, "expected '{' for method body");
361
+ const body = this.parseBlock();
362
+
363
+ methods.push({ name: methodName, params, returnType, body, line: mLine, col: mCol });
364
+ }
365
+ }
366
+
367
+ this.expect(TokenType.RBRACE, "expected '}' to close impl");
368
+
369
+ return { kind: "impl_decl", trait, forType, typeParams, methods, line: kw.line, col: kw.col };
370
+ }
371
+
372
+ // if 문 (문 위치)
373
+ private parseIfStmt(): Stmt {
374
+ const kw = this.advance(); // if
375
+ const condition = this.parseExpr(0);
376
+ this.expect(TokenType.LBRACE, "expected '{' after if condition");
377
+ const then = this.parseBlock();
378
+
379
+ let else_: Stmt[] | null = null;
380
+ if (this.match(TokenType.ELSE)) {
381
+ if (this.check(TokenType.IF)) {
382
+ // else if 체인
383
+ else_ = [this.parseIfStmt()];
384
+ } else {
385
+ this.expect(TokenType.LBRACE, "expected '{' after else");
386
+ else_ = this.parseBlock();
387
+ }
388
+ }
389
+
390
+ return { kind: "if_stmt", condition, then, else_, line: kw.line, col: kw.col };
391
+ }
392
+
393
+ // match 문
394
+ private parseMatchStmt(): Stmt {
395
+ const kw = this.advance(); // match
396
+ const subject = this.parseExpr(0);
397
+ this.expect(TokenType.LBRACE, "expected '{' after match expression");
398
+ const arms = this.parseMatchArms();
399
+ this.expect(TokenType.RBRACE, "expected '}' to close match");
400
+
401
+ return { kind: "match_stmt", subject, arms, line: kw.line, col: kw.col };
402
+ }
403
+
404
+ // for 문
405
+ private parseForStmt(): Stmt {
406
+ const kw = this.advance(); // for
407
+ const variable = this.expectIdent("loop variable");
408
+
409
+ // for...in or for...of
410
+ const loopType = this.peek().type;
411
+ if (loopType === TokenType.IN) {
412
+ this.advance(); // in
413
+ const iterable = this.parseExpr(0);
414
+ this.expect(TokenType.LBRACE, "expected '{' after for...in");
415
+ const body = this.parseBlock();
416
+ return { kind: "for_stmt", variable, iterable, body, line: kw.line, col: kw.col };
417
+ } else if (loopType === TokenType.OF) {
418
+ this.advance(); // of
419
+ const iterable = this.parseExpr(0);
420
+ this.expect(TokenType.LBRACE, "expected '{' after for...of");
421
+ const body = this.parseBlock();
422
+ return { kind: "for_of_stmt", variable, iterable, body, line: kw.line, col: kw.col };
423
+ } else {
424
+ this.error("expected 'in' or 'of' after loop variable", this.peek());
425
+ return { kind: "for_stmt", variable, iterable: { kind: "ident", name: "", line: kw.line, col: kw.col }, body: [], line: kw.line, col: kw.col };
426
+ }
427
+ }
428
+
429
+ // while 문
430
+ private parseWhileStmt(): Stmt {
431
+ const kw = this.advance(); // while
432
+ const condition = this.parseExpr(0);
433
+ this.expect(TokenType.LBRACE, "expected '{' after while condition");
434
+ const body = this.parseBlock();
435
+
436
+ return { kind: "while_stmt", condition, body, line: kw.line, col: kw.col };
437
+ }
438
+
439
+ // break 문
440
+ private parseBreakStmt(): Stmt {
441
+ const kw = this.advance(); // break
442
+ this.match(TokenType.SEMICOLON); // optional semicolon
443
+ return { kind: "break_stmt", line: kw.line, col: kw.col };
444
+ }
445
+
446
+ // continue 문
447
+ private parseContinueStmt(): Stmt {
448
+ const kw = this.advance(); // continue
449
+ this.match(TokenType.SEMICOLON); // optional semicolon
450
+ return { kind: "continue_stmt", line: kw.line, col: kw.col };
451
+ }
452
+
453
+ // spawn 문
454
+ private parseSpawnStmt(): Stmt {
455
+ const kw = this.advance(); // spawn
456
+ this.expect(TokenType.LBRACE, "expected '{' after spawn");
457
+ const body = this.parseBlock();
458
+
459
+ return { kind: "spawn_stmt", body, line: kw.line, col: kw.col };
460
+ }
461
+
462
+ // import 문 파싱
463
+ private parseImportStmt(): ImportDecl {
464
+ const kw = this.advance(); // import
465
+ const items: ImportItem[] = [];
466
+ let default_ = false;
467
+
468
+ if (this.check(TokenType.LBRACE)) {
469
+ this.advance(); // {
470
+ if (!this.check(TokenType.RBRACE)) {
471
+ do {
472
+ const name = this.expectIdent("imported name");
473
+ let alias: string | undefined;
474
+ if (this.match(TokenType.AS)) {
475
+ alias = this.expectIdent("alias");
476
+ }
477
+ items.push({ name, alias });
478
+ } while (this.match(TokenType.COMMA));
479
+ }
480
+ this.expect(TokenType.RBRACE, "expected '}' after import items");
481
+ } else {
482
+ const name = this.expectIdent("module name");
483
+ items.push({ name });
484
+ default_ = true;
485
+ }
486
+
487
+ this.expect(TokenType.FROM, "expected 'from' in import statement");
488
+ const sourceToken = this.peek();
489
+ if (sourceToken.type !== TokenType.STRING_LIT) {
490
+ this.error("expected string literal for module source", sourceToken);
491
+ throw new Error("expected module path");
492
+ }
493
+ const source = this.advance().lexeme;
494
+ this.match(TokenType.SEMICOLON);
495
+
496
+ return { kind: "import_decl", source, items, default: default_, line: kw.line, col: kw.col };
497
+ }
498
+
499
+ // export 문 파싱
500
+ private parseExportStmt(): ExportDecl {
501
+ const kw = this.advance(); // export
502
+
503
+ if (this.check(TokenType.FN) || this.check(TokenType.STRUCT)) {
504
+ const target = this.parseStmt();
505
+ return { kind: "export_decl", target, line: kw.line, col: kw.col };
506
+ }
507
+
508
+ if (this.check(TokenType.LBRACE)) {
509
+ this.advance(); // {
510
+ const names: string[] = [];
511
+ if (!this.check(TokenType.RBRACE)) {
512
+ do {
513
+ names.push(this.expectIdent("export name"));
514
+ } while (this.match(TokenType.COMMA));
515
+ }
516
+ this.expect(TokenType.RBRACE, "expected '}' after export names");
517
+ this.match(TokenType.SEMICOLON);
518
+ return { kind: "export_decl", target: names, line: kw.line, col: kw.col };
519
+ }
520
+
521
+ this.error("expected 'fn', 'struct', or '{' after 'export'", kw);
522
+ throw new Error("invalid export syntax");
523
+ }
524
+
525
+ // return 문
526
+ private parseReturnStmt(): Stmt {
527
+ const kw = this.advance(); // return
528
+
529
+ // return 뒤에 식이 있는지 확인
530
+ let value: Expr | null = null;
531
+ if (!this.check(TokenType.RBRACE) && !this.isAtEnd() && !this.isStmtStart()) {
532
+ value = this.parseExpr(0);
533
+ }
534
+ this.match(TokenType.SEMICOLON); // optional semicolon
535
+
536
+ return { kind: "return_stmt", value, line: kw.line, col: kw.col };
537
+ }
538
+
539
+ // 식 문 (ExprStmt)
540
+ private parseExprStmt(): Stmt {
541
+ const tok = this.peek();
542
+ const expr = this.parseExpr(0);
543
+
544
+ // 할당 처리: expr = value
545
+ if (this.check(TokenType.EQ)) {
546
+ const eq = this.advance();
547
+ const value = this.parseExpr(0);
548
+ this.match(TokenType.SEMICOLON); // optional semicolon
549
+ return {
550
+ kind: "expr_stmt",
551
+ expr: { kind: "assign", target: expr, value, line: eq.line, col: eq.col },
552
+ line: tok.line,
553
+ col: tok.col,
554
+ };
555
+ }
556
+
557
+ this.match(TokenType.SEMICOLON); // optional semicolon
558
+ return { kind: "expr_stmt", expr, line: tok.line, col: tok.col };
559
+ }
560
+
561
+ // ============================================================
562
+ // 블록 파싱 ({ 이미 소비됨, } 소비함 )
563
+ // ============================================================
564
+
565
+ private parseBlock(): Stmt[] {
566
+ const stmts: Stmt[] = [];
567
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
568
+ try {
569
+ stmts.push(this.parseStmt());
570
+ } catch {
571
+ this.synchronize();
572
+ }
573
+ }
574
+ this.expect(TokenType.RBRACE, "expected '}'");
575
+ return stmts;
576
+ }
577
+
578
+ // ============================================================
579
+ // 식 파싱 — Pratt Parser (SPEC_05 Q3, Q4)
580
+ // ============================================================
581
+
582
+ private parseExpr(minBP: number): Expr {
583
+ let left = this.nud();
584
+
585
+ while (!this.isAtEnd()) {
586
+ const tok = this.peek();
587
+
588
+ // 구조체 리터럴: ident { field: value, ... }
589
+ // Lookahead: { 다음이 ident: 패턴인지 확인 (while/if 블록과 구분)
590
+ if (tok.type === TokenType.LBRACE && left.kind === "ident") {
591
+ // Lookahead: 다음 토큰이 ident이고, 그 다음이 :인지 확인
592
+ if (this.pos + 1 < this.tokens.length &&
593
+ this.tokens[this.pos + 1].type === TokenType.IDENT &&
594
+ this.pos + 2 < this.tokens.length &&
595
+ this.tokens[this.pos + 2].type === TokenType.COLON) {
596
+ this.advance(); // {
597
+ const fields: { name: string; value: Expr }[] = [];
598
+ if (!this.check(TokenType.RBRACE)) {
599
+ do {
600
+ const name = this.expectIdent("field name");
601
+ this.expect(TokenType.COLON, "expected ':' after field name");
602
+ const value = this.parseExpr(0);
603
+ fields.push({ name, value });
604
+ } while (this.match(TokenType.COMMA));
605
+ }
606
+ this.expect(TokenType.RBRACE, "expected '}'");
607
+ left = { kind: "struct_lit", structName: left.name, fields, line: left.line, col: left.col };
608
+ continue;
609
+ }
610
+ }
611
+
612
+ const bp = infixBP(tok.type);
613
+ if (bp <= minBP) break;
614
+
615
+ left = this.led(left, bp);
616
+ }
617
+
618
+ return left;
619
+ }
620
+
621
+ // nud — prefix 위치
622
+ private nud(): Expr {
623
+ const tok = this.peek();
624
+
625
+ // 정수 리터럴
626
+ if (tok.type === TokenType.INT_LIT) {
627
+ this.advance();
628
+ const raw = tok.lexeme.replace(/_/g, "");
629
+ return { kind: "int_lit", value: parseInt(raw, 10), line: tok.line, col: tok.col };
630
+ }
631
+
632
+ // 부동소수점 리터럴
633
+ if (tok.type === TokenType.FLOAT_LIT) {
634
+ this.advance();
635
+ const raw = tok.lexeme.replace(/_/g, "");
636
+ return { kind: "float_lit", value: parseFloat(raw), line: tok.line, col: tok.col };
637
+ }
638
+
639
+ // 문자열 리터럴
640
+ if (tok.type === TokenType.STRING_LIT) {
641
+ this.advance();
642
+ return { kind: "string_lit", value: tok.lexeme, line: tok.line, col: tok.col };
643
+ }
644
+
645
+ // 불리언 리터럴
646
+ if (tok.type === TokenType.TRUE) {
647
+ this.advance();
648
+ return { kind: "bool_lit", value: true, line: tok.line, col: tok.col };
649
+ }
650
+ if (tok.type === TokenType.FALSE) {
651
+ this.advance();
652
+ return { kind: "bool_lit", value: false, line: tok.line, col: tok.col };
653
+ }
654
+
655
+ // 식별자 또는 channel<T>
656
+ if (tok.type === TokenType.IDENT || tok.type === TokenType.TYPE_CHANNEL) {
657
+ const identTok = this.advance();
658
+ const name = identTok.lexeme;
659
+
660
+ // channel<T> 특별 처리
661
+ if (name === "channel" && this.check(TokenType.LT)) {
662
+ this.advance(); // <
663
+ const element = this.parseType();
664
+ this.expect(TokenType.GT, "expected '>' after channel type");
665
+ this.expect(TokenType.LPAREN, "expected '()' after channel<T>");
666
+ this.expect(TokenType.RPAREN, "expected ')'");
667
+ return { kind: "chan_new", element, line: identTok.line, col: identTok.col };
668
+ }
669
+
670
+ return { kind: "ident", name, line: identTok.line, col: identTok.col };
671
+ }
672
+
673
+ // 단항 연산자: - !
674
+ if (tok.type === TokenType.MINUS || tok.type === TokenType.NOT) {
675
+ this.advance();
676
+ const operand = this.parseExpr(BP_UNARY);
677
+ return { kind: "unary", op: tok.lexeme, operand, line: tok.line, col: tok.col };
678
+ }
679
+
680
+ // await 연산자
681
+ if (tok.type === TokenType.AWAIT) {
682
+ this.advance();
683
+ const expr = this.parseExpr(BP_UNARY);
684
+ return { kind: "await", expr, line: tok.line, col: tok.col };
685
+ }
686
+
687
+ // 괄호 그룹: ( expr )
688
+ if (tok.type === TokenType.LPAREN) {
689
+ this.advance();
690
+ const expr = this.parseExpr(0);
691
+ this.expect(TokenType.RPAREN, "expected ')'");
692
+ return expr;
693
+ }
694
+
695
+ // 배열 리터럴: [ elem, ... ]
696
+ if (tok.type === TokenType.LBRACKET) {
697
+ return this.parseArrayLit();
698
+ }
699
+
700
+ // if 식 (식 위치)
701
+ if (tok.type === TokenType.IF) {
702
+ return this.parseIfExpr();
703
+ }
704
+
705
+ // match 식 (식 위치)
706
+ if (tok.type === TokenType.MATCH) {
707
+ return this.parseMatchExpr();
708
+ }
709
+
710
+ // 함수 리터럴 (람다): fn(x: i32) -> i32 { x + 1 }
711
+ if (tok.type === TokenType.FN) {
712
+ return this.parseFnLit();
713
+ }
714
+
715
+ // 채널 수신: <- chan
716
+ if (tok.type === TokenType.LARROW) {
717
+ this.advance();
718
+ const chan = this.parseExpr(BP_UNARY);
719
+ return { kind: "chan_recv", chan, line: tok.line, col: tok.col };
720
+ }
721
+
722
+ this.error(`unexpected token: ${tok.lexeme}`, tok);
723
+ this.advance();
724
+ return { kind: "ident", name: "__error__", line: tok.line, col: tok.col };
725
+ }
726
+
727
+ // led — infix/postfix 위치
728
+ private led(left: Expr, bp: number): Expr {
729
+ const tok = this.peek();
730
+
731
+ // 함수 호출: expr(args) 형태만 지원 (generic call은 제한)
732
+ // 제네릭 호출은 <T> 타입 인자를 파싱하기 위해 복잡한 lookahead가 필요하므로,
733
+ // 여기서는 skip하고 나중에 추가 개선
734
+
735
+ // 이항 연산자
736
+ if (
737
+ tok.type === TokenType.PLUS || tok.type === TokenType.MINUS ||
738
+ tok.type === TokenType.STAR || tok.type === TokenType.SLASH ||
739
+ tok.type === TokenType.PERCENT ||
740
+ tok.type === TokenType.EQEQ || tok.type === TokenType.NEQ ||
741
+ tok.type === TokenType.LT || tok.type === TokenType.GT ||
742
+ tok.type === TokenType.LTEQ || tok.type === TokenType.GTEQ ||
743
+ tok.type === TokenType.AND || tok.type === TokenType.OR
744
+ ) {
745
+ this.advance();
746
+ const right = this.parseExpr(bp); // left-associative
747
+ return { kind: "binary", op: tok.lexeme, left, right, line: tok.line, col: tok.col };
748
+ }
749
+
750
+ if (tok.type === TokenType.LPAREN) {
751
+ this.advance();
752
+ const args: Expr[] = [];
753
+ if (!this.check(TokenType.RPAREN)) {
754
+ do {
755
+ args.push(this.parseExpr(0));
756
+ } while (this.match(TokenType.COMMA));
757
+ }
758
+ this.expect(TokenType.RPAREN, "expected ')' after arguments");
759
+ return { kind: "call", callee: left, args, line: tok.line, col: tok.col };
760
+ }
761
+
762
+ // 인덱스: expr[index]
763
+ if (tok.type === TokenType.LBRACKET) {
764
+ this.advance();
765
+ const index = this.parseExpr(0);
766
+ this.expect(TokenType.RBRACKET, "expected ']'");
767
+ return { kind: "index", object: left, index, line: tok.line, col: tok.col };
768
+ }
769
+
770
+ // 필드 접근: expr.field
771
+ if (tok.type === TokenType.DOT) {
772
+ this.advance();
773
+ const field = this.expectIdent("field name");
774
+ return { kind: "field_access", object: left, field, line: tok.line, col: tok.col };
775
+ }
776
+
777
+ // try 연산자: expr?
778
+ if (tok.type === TokenType.QUESTION) {
779
+ this.advance();
780
+ return { kind: "try", operand: left, line: tok.line, col: tok.col };
781
+ }
782
+
783
+ // 채널 송신: chan <- value
784
+ if (tok.type === TokenType.LARROW) {
785
+ this.advance();
786
+ const value = this.parseExpr(BP_ASSIGN);
787
+ return { kind: "chan_send", chan: left, value, line: tok.line, col: tok.col };
788
+ }
789
+
790
+ // 할당은 ExprStmt에서 처리하므로 여기선 패스
791
+ // EQ가 여기 오면 식 끝으로 처리
792
+ this.error(`unexpected operator: ${tok.lexeme}`, tok);
793
+ this.advance();
794
+ return left;
795
+ }
796
+
797
+ // ============================================================
798
+ // 복합 식 파싱
799
+ // ============================================================
800
+
801
+ // 배열 리터럴: [a, b, c]
802
+ private parseArrayLit(): Expr {
803
+ const tok = this.advance(); // [
804
+ const elements: Expr[] = [];
805
+ if (!this.check(TokenType.RBRACKET)) {
806
+ do {
807
+ elements.push(this.parseExpr(0));
808
+ } while (this.match(TokenType.COMMA));
809
+ }
810
+ this.expect(TokenType.RBRACKET, "expected ']'");
811
+ return { kind: "array_lit", elements, line: tok.line, col: tok.col };
812
+ }
813
+
814
+ // 구조체 리터럴: { name: expr, ... }
815
+ // if 식 (식 위치, else 필수 — SPEC_06)
816
+ private parseIfExpr(): Expr {
817
+ const tok = this.advance(); // if
818
+ const condition = this.parseExpr(0);
819
+ this.expect(TokenType.LBRACE, "expected '{' after if condition");
820
+ const then = this.parseBlockExprs();
821
+ this.expect(TokenType.ELSE, "if expression requires else branch");
822
+ this.expect(TokenType.LBRACE, "expected '{' after else");
823
+ const else_ = this.parseBlockExprs();
824
+ return { kind: "if_expr", condition, then, else_, line: tok.line, col: tok.col };
825
+ }
826
+
827
+ // match 식 (식 위치)
828
+ private parseMatchExpr(): Expr {
829
+ const tok = this.advance(); // match
830
+ const subject = this.parseExpr(0);
831
+ this.expect(TokenType.LBRACE, "expected '{' after match expression");
832
+ const arms = this.parseMatchArms();
833
+ this.expect(TokenType.RBRACE, "expected '}' to close match");
834
+ return { kind: "match_expr", subject, arms, line: tok.line, col: tok.col };
835
+ }
836
+
837
+ // 블록 내 식 목록 (if/match 식의 body) → } 소비
838
+ private parseBlockExprs(): Expr[] {
839
+ const exprs: Expr[] = [];
840
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
841
+ exprs.push(this.parseExpr(0));
842
+ }
843
+ this.expect(TokenType.RBRACE, "expected '}'");
844
+ return exprs;
845
+ }
846
+
847
+ // ============================================================
848
+ // match arms
849
+ // ============================================================
850
+
851
+ private parseMatchArms(): MatchArm[] {
852
+ const arms: MatchArm[] = [];
853
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
854
+ const pattern = this.parsePattern();
855
+
856
+ // Guard 절 파싱: if 조건
857
+ let guard: Expr | undefined = undefined;
858
+ if (this.match(TokenType.IF)) {
859
+ guard = this.parseExpr(0);
860
+ }
861
+
862
+ this.expect(TokenType.ARROW, "expected '=>' after pattern");
863
+
864
+ let body: Expr;
865
+ if (this.check(TokenType.LBRACE)) {
866
+ // 블록 body
867
+ const bTok = this.advance(); // {
868
+ const stmts: Stmt[] = [];
869
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
870
+ stmts.push(this.parseStmt());
871
+ }
872
+ this.expect(TokenType.RBRACE, "expected '}'");
873
+ // 블록의 마지막 문이 ExprStmt면 그 식이 반환값
874
+ const lastExpr = stmts.length > 0 && stmts[stmts.length - 1].kind === "expr_stmt"
875
+ ? (stmts[stmts.length - 1] as any).expr
876
+ : null;
877
+ body = { kind: "block_expr", stmts: stmts.slice(0, lastExpr ? -1 : stmts.length), expr: lastExpr, line: bTok.line, col: bTok.col };
878
+ } else {
879
+ body = this.parseExpr(0);
880
+ }
881
+
882
+ this.match(TokenType.COMMA); // trailing comma optional
883
+ arms.push({ pattern, guard, body });
884
+ }
885
+ return arms;
886
+ }
887
+
888
+ // 함수 리터럴: fn(x: i32, y: i32) -> i32 { x + y }
889
+ private parseFnLit(): Expr {
890
+ const kw = this.advance(); // fn
891
+ this.expect(TokenType.LPAREN, "expected '(' after fn");
892
+
893
+ const params: FnParam[] = [];
894
+ if (!this.check(TokenType.RPAREN)) {
895
+ do {
896
+ const name = this.expectIdent("parameter name");
897
+ let type: TypeAnnotation | undefined = undefined;
898
+ if (this.match(TokenType.COLON)) {
899
+ type = this.parseType();
900
+ }
901
+ params.push({ name, type });
902
+ } while (this.match(TokenType.COMMA));
903
+ }
904
+ this.expect(TokenType.RPAREN, "expected ')' after parameters");
905
+
906
+ let returnType: TypeAnnotation | undefined = undefined;
907
+ if (this.match(TokenType.RARROW)) {
908
+ returnType = this.parseType();
909
+ }
910
+
911
+ // body
912
+ let body: Expr;
913
+ if (this.check(TokenType.LBRACE)) {
914
+ const bTok = this.advance(); // {
915
+ const stmts: Stmt[] = [];
916
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
917
+ stmts.push(this.parseStmt());
918
+ }
919
+ this.expect(TokenType.RBRACE, "expected '}'");
920
+ const lastExpr = stmts.length > 0 && stmts[stmts.length - 1].kind === "expr_stmt"
921
+ ? (stmts[stmts.length - 1] as any).expr
922
+ : null;
923
+ body = { kind: "block_expr", stmts: stmts.slice(0, lastExpr ? -1 : stmts.length), expr: lastExpr, line: bTok.line, col: bTok.col };
924
+ } else {
925
+ body = this.parseExpr(0);
926
+ }
927
+
928
+ return { kind: "fn_lit", params, returnType, body, line: kw.line, col: kw.col };
929
+ }
930
+
931
+ // ============================================================
932
+ // 패턴 파싱 (SPEC_05 Q8)
933
+ // ============================================================
934
+
935
+ private parsePattern(): Pattern {
936
+ const tok = this.peek();
937
+
938
+ // _ (wildcard)
939
+ if (tok.type === TokenType.IDENT && tok.lexeme === "_") {
940
+ this.advance();
941
+ return { kind: "wildcard" };
942
+ }
943
+
944
+ // Ok(p), Err(p), Some(p), None
945
+ if (tok.type === TokenType.IDENT) {
946
+ if (tok.lexeme === "Ok" || tok.lexeme === "Err" || tok.lexeme === "Some") {
947
+ this.advance();
948
+ this.expect(TokenType.LPAREN, `expected '(' after ${tok.lexeme}`);
949
+ const inner = this.parsePattern();
950
+ this.expect(TokenType.RPAREN, "expected ')'");
951
+ if (tok.lexeme === "Ok") return { kind: "ok", inner };
952
+ if (tok.lexeme === "Err") return { kind: "err", inner };
953
+ return { kind: "some", inner };
954
+ }
955
+ if (tok.lexeme === "None") {
956
+ this.advance();
957
+ return { kind: "none" };
958
+ }
959
+
960
+ // 구조 분해: Point { x, y }, Point { x as px, y as py }, Point { name, .. }
961
+ const ident = tok.lexeme;
962
+ this.advance();
963
+
964
+ if (this.check(TokenType.LBRACE)) {
965
+ this.advance(); // {
966
+ const fields: { name: string; pattern: Pattern; alias?: string }[] = [];
967
+ let rest = false;
968
+
969
+ // 구조체 필드 파싱 (빈 중괄호도 허용)
970
+ if (!this.check(TokenType.RBRACE)) {
971
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
972
+ if (this.match(TokenType.DOTDOT)) {
973
+ rest = true;
974
+ break;
975
+ }
976
+
977
+ const fieldName = this.expectIdent("field name");
978
+ let fieldPattern: Pattern;
979
+ let alias: string | undefined = undefined;
980
+
981
+ if (this.match(TokenType.AS)) {
982
+ alias = this.expectIdent("alias name");
983
+ fieldPattern = { kind: "ident", name: alias };
984
+ } else {
985
+ fieldPattern = { kind: "ident", name: fieldName };
986
+ }
987
+
988
+ fields.push({ name: fieldName, pattern: fieldPattern, alias });
989
+
990
+ if (!this.match(TokenType.COMMA)) break;
991
+ }
992
+ }
993
+
994
+ this.expect(TokenType.RBRACE, "expected '}'");
995
+ return { kind: "struct", name: ident, fields, rest };
996
+ }
997
+
998
+ // 일반 식별자 바인딩
999
+ return { kind: "ident", name: ident };
1000
+ }
1001
+
1002
+ // 배열 분해: [a, b, c], [x, .., y]
1003
+ if (tok.type === TokenType.LBRACKET) {
1004
+ this.advance(); // [
1005
+ const elements: Pattern[] = [];
1006
+ let rest = false;
1007
+ let restIndex: number | undefined = undefined;
1008
+
1009
+ while (!this.check(TokenType.RBRACKET) && !this.isAtEnd()) {
1010
+ if (this.check(TokenType.DOTDOT)) {
1011
+ this.advance(); // ..
1012
+ rest = true;
1013
+ restIndex = elements.length;
1014
+ if (this.match(TokenType.COMMA)) {
1015
+ // [x, .., y] 형태: 나머지 후 계속
1016
+ while (!this.check(TokenType.RBRACKET) && !this.isAtEnd()) {
1017
+ elements.push(this.parsePattern());
1018
+ if (!this.match(TokenType.COMMA)) break;
1019
+ }
1020
+ }
1021
+ break;
1022
+ }
1023
+
1024
+ elements.push(this.parsePattern());
1025
+
1026
+ if (!this.match(TokenType.COMMA)) break;
1027
+ }
1028
+
1029
+ this.expect(TokenType.RBRACKET, "expected ']'");
1030
+ return { kind: "array", elements, rest, restIndex };
1031
+ }
1032
+
1033
+ // 리터럴 패턴
1034
+ if (tok.type === TokenType.INT_LIT || tok.type === TokenType.FLOAT_LIT ||
1035
+ tok.type === TokenType.STRING_LIT || tok.type === TokenType.TRUE ||
1036
+ tok.type === TokenType.FALSE) {
1037
+ const expr = this.nud();
1038
+ return { kind: "literal", value: expr };
1039
+ }
1040
+
1041
+ // 단항 마이너스 (음수 리터럴 패턴)
1042
+ if (tok.type === TokenType.MINUS) {
1043
+ const expr = this.nud(); // unary minus
1044
+ return { kind: "literal", value: expr };
1045
+ }
1046
+
1047
+ this.error("expected pattern", tok);
1048
+ this.advance();
1049
+ return { kind: "wildcard" };
1050
+ }
1051
+
1052
+ // ============================================================
1053
+ // 타입 파싱
1054
+ // ============================================================
1055
+
1056
+ private parseType(): TypeAnnotation {
1057
+ const tok = this.peek();
1058
+
1059
+ // fn(T1, T2) -> R 함수 타입
1060
+ if (tok.type === TokenType.FN) {
1061
+ this.advance(); // fn
1062
+ this.expect(TokenType.LPAREN, "expected '(' after fn");
1063
+
1064
+ const params: TypeAnnotation[] = [];
1065
+ if (!this.check(TokenType.RPAREN)) {
1066
+ do {
1067
+ params.push(this.parseType());
1068
+ } while (this.match(TokenType.COMMA));
1069
+ }
1070
+
1071
+ this.expect(TokenType.RPAREN, "expected ')' after fn params");
1072
+ this.expect(TokenType.RARROW, "expected '->' in fn type");
1073
+ const returnType = this.parseType();
1074
+
1075
+ return { kind: "fn", params, returnType };
1076
+ }
1077
+
1078
+ switch (tok.type) {
1079
+ case TokenType.TYPE_I32: this.advance(); return { kind: "i32" };
1080
+ case TokenType.TYPE_I64: this.advance(); return { kind: "i64" };
1081
+ case TokenType.TYPE_F64: this.advance(); return { kind: "f64" };
1082
+ case TokenType.TYPE_BOOL: this.advance(); return { kind: "bool" };
1083
+ case TokenType.TYPE_STRING: this.advance(); return { kind: "string" };
1084
+ case TokenType.TYPE_VOID: this.advance(); return { kind: "void" };
1085
+ default:
1086
+ break;
1087
+ }
1088
+
1089
+ // [T] → 배열
1090
+ if (tok.type === TokenType.LBRACKET) {
1091
+ this.advance();
1092
+ const element = this.parseType();
1093
+ this.expect(TokenType.RBRACKET, "expected ']' for array type");
1094
+ return { kind: "array", element };
1095
+ }
1096
+
1097
+ // channel<T>
1098
+ if (tok.type === TokenType.TYPE_CHANNEL) {
1099
+ this.advance();
1100
+ this.expect(TokenType.LT, "expected '<' after channel");
1101
+ const element = this.parseType();
1102
+ this.expect(TokenType.GT, "expected '>' for channel type");
1103
+ return { kind: "channel", element };
1104
+ }
1105
+
1106
+ // IDENT: 타입 파라미터(T, K, V) 또는 Generic/Struct 타입 [STEP B-3]
1107
+ if (tok.type === TokenType.IDENT) {
1108
+ const name = tok.lexeme;
1109
+ this.advance();
1110
+
1111
+ // 단일 대문자 → type_param (T, K, V)
1112
+ if (name.length === 1 && name >= 'A' && name <= 'Z') {
1113
+ return { kind: "type_param", name };
1114
+ }
1115
+
1116
+ // Option<T>, Result<T,E>, Generic 타입, 커스텀 struct
1117
+ if (this.check(TokenType.LT)) {
1118
+ this.advance(); // LT 소비
1119
+ const typeArgs: TypeAnnotation[] = [];
1120
+ do {
1121
+ typeArgs.push(this.parseType());
1122
+ } while (this.match(TokenType.COMMA));
1123
+ this.expect(TokenType.GT, "expected '>' after type arguments");
1124
+
1125
+ // 내장 제네릭 타입 매핑
1126
+ if (name === "Option") return { kind: "option", element: typeArgs[0] };
1127
+ if (name === "Result") return { kind: "result", ok: typeArgs[0], err: typeArgs[1] };
1128
+ if (name === "Promise") return { kind: "promise", element: typeArgs[0] };
1129
+ return { kind: "generic_ref", name, typeArgs };
1130
+ }
1131
+
1132
+ return { kind: "struct_ref", name };
1133
+ }
1134
+
1135
+ this.error(`expected type, got ${tok.lexeme}`, tok);
1136
+ this.advance();
1137
+ return { kind: "i32" }; // fallback
1138
+ }
1139
+
1140
+ // ============================================================
1141
+ // 유틸리티
1142
+ // ============================================================
1143
+
1144
+ private peek(): Token {
1145
+ return this.tokens[this.pos];
1146
+ }
1147
+
1148
+ private advance(): Token {
1149
+ const tok = this.tokens[this.pos];
1150
+ if (!this.isAtEnd()) this.pos++;
1151
+ return tok;
1152
+ }
1153
+
1154
+ private check(type: TokenType): boolean {
1155
+ return this.peek().type === type;
1156
+ }
1157
+
1158
+ private match(type: TokenType): boolean {
1159
+ if (this.check(type)) {
1160
+ this.advance();
1161
+ return true;
1162
+ }
1163
+ return false;
1164
+ }
1165
+
1166
+ private expect(type: TokenType, message: string): Token {
1167
+ if (this.check(type)) {
1168
+ return this.advance();
1169
+ }
1170
+ const tok = this.peek();
1171
+ this.error(`${message} (got ${tok.type}: "${tok.lexeme}")`, tok);
1172
+ throw new Error(message);
1173
+ }
1174
+
1175
+ private expectIdent(context: string): string {
1176
+ const tok = this.peek();
1177
+ if (tok.type === TokenType.IDENT) {
1178
+ this.advance();
1179
+ return tok.lexeme;
1180
+ }
1181
+ this.error(`expected ${context} (got ${tok.type}: "${tok.lexeme}")`, tok);
1182
+ throw new Error(`expected ${context}`);
1183
+ }
1184
+
1185
+ private isAtEnd(): boolean {
1186
+ return this.peek().type === TokenType.EOF;
1187
+ }
1188
+
1189
+ private isStmtStart(): boolean {
1190
+ const t = this.peek().type;
1191
+ return t === TokenType.VAR || t === TokenType.LET || t === TokenType.CONST ||
1192
+ t === TokenType.FN || t === TokenType.STRUCT || t === TokenType.IF || t === TokenType.MATCH ||
1193
+ t === TokenType.FOR || t === TokenType.WHILE || t === TokenType.BREAK || t === TokenType.CONTINUE ||
1194
+ t === TokenType.SPAWN || t === TokenType.RETURN || t === TokenType.IMPORT || t === TokenType.EXPORT;
1195
+ }
1196
+
1197
+ private error(message: string, tok: Token): void {
1198
+ this.errors.push({ message, line: tok.line, col: tok.col });
1199
+ }
1200
+
1201
+ private synchronize(): void {
1202
+ while (!this.isAtEnd()) {
1203
+ if (this.isStmtStart()) return;
1204
+ if (this.check(TokenType.RBRACE)) {
1205
+ this.advance();
1206
+ return;
1207
+ }
1208
+ this.advance();
1209
+ }
1210
+ }
1211
+ }