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