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/config.fl ADDED
@@ -0,0 +1,175 @@
1
+ // FreeLang v4.2 — gogs-cli 설정 관리
2
+ // ~/.gogs/config.yaml 읽고 쓰기
3
+
4
+ struct Config {
5
+ default_host: str
6
+ hosts: [HostConfig]
7
+ }
8
+
9
+ struct HostConfig {
10
+ name: str
11
+ url: str
12
+ token: str
13
+ }
14
+
15
+ // 설정 파일 경로
16
+ fn config_dir() -> str {
17
+ home_dir() + "/.gogs"
18
+ }
19
+
20
+ fn config_file() -> str {
21
+ config_dir() + "/config.yaml"
22
+ }
23
+
24
+ // 홈 디렉토리
25
+ fn home_dir() -> str {
26
+ // TODO: 환경 변수 $HOME 읽기
27
+ "/root"
28
+ }
29
+
30
+ /// 설정 로드
31
+ async fn load_config() -> Config {
32
+ var path = config_file()
33
+
34
+ // 파일 존재 확인
35
+ if !exists(path) {
36
+ println("Config file not found: " + path)
37
+ println("Please run: gogs auth login --host URL --token TOKEN")
38
+
39
+ // 기본 설정 반환
40
+ return Config {
41
+ default_host: "",
42
+ hosts: []
43
+ }
44
+ }
45
+
46
+ // 파일 읽기
47
+ var content = read_file(path)
48
+
49
+ // YAML 파싱 (yaml.fl 사용 필요)
50
+ // var yaml = parse_yaml(content)
51
+ // ... 파싱 로직
52
+
53
+ // 임시: 빈 설정 반환
54
+ Config {
55
+ default_host: "",
56
+ hosts: []
57
+ }
58
+ }
59
+
60
+ /// 설정 저장
61
+ async fn save_config(cfg: Config) -> bool {
62
+ var path = config_file()
63
+ var dir = config_dir()
64
+
65
+ // 디렉토리 생성 (존재하지 않으면)
66
+ // TODO: mkdir 구현
67
+
68
+ // YAML 직렬화
69
+ var content = config_to_yaml(cfg)
70
+
71
+ // 파일 쓰기
72
+ write_file(path, content)
73
+
74
+ // 파일 권한 설정 (600)
75
+ // TODO: chmod 구현
76
+
77
+ true
78
+ }
79
+
80
+ /// Config를 YAML로 변환
81
+ fn config_to_yaml(cfg: Config) -> str {
82
+ var result = "default_host: " + cfg.default_host + "\n"
83
+ result = result + "hosts:\n"
84
+
85
+ var i: i32 = 0
86
+ while i < length(cfg.hosts) {
87
+ var host = cfg.hosts[i]
88
+ result = result + " - name: " + host.name + "\n"
89
+ result = result + " url: " + host.url + "\n"
90
+ result = result + " token: " + host.token + "\n"
91
+ i = i + 1
92
+ }
93
+
94
+ result
95
+ }
96
+
97
+ /// 호스트 추가/업데이트
98
+ async fn add_host(name: str, url: str, token: str) -> Config {
99
+ var cfg = await load_config()
100
+
101
+ // 이미 존재하는 호스트 찾기
102
+ var found = false
103
+ var i: i32 = 0
104
+ while i < length(cfg.hosts) {
105
+ if cfg.hosts[i].name == name {
106
+ cfg.hosts[i].url = url
107
+ cfg.hosts[i].token = token
108
+ found = true
109
+ break
110
+ }
111
+ i = i + 1
112
+ }
113
+
114
+ // 새 호스트 추가
115
+ if !found {
116
+ var host = HostConfig { name: name, url: url, token: token }
117
+ cfg.hosts.push(host)
118
+ }
119
+
120
+ // 기본 호스트 설정
121
+ if cfg.default_host == "" {
122
+ cfg.default_host = name
123
+ }
124
+
125
+ // 저장
126
+ await save_config(cfg)
127
+
128
+ cfg
129
+ }
130
+
131
+ /// 기본 호스트 조회
132
+ async fn get_default_host() -> HostConfig {
133
+ var cfg = await load_config()
134
+
135
+ if cfg.default_host == "" {
136
+ return HostConfig { name: "", url: "", token: "" }
137
+ }
138
+
139
+ // 호스트 찾기
140
+ var i: i32 = 0
141
+ while i < length(cfg.hosts) {
142
+ if cfg.hosts[i].name == cfg.default_host {
143
+ return cfg.hosts[i]
144
+ }
145
+ i = i + 1
146
+ }
147
+
148
+ HostConfig { name: "", url: "", token: "" }
149
+ }
150
+
151
+ /// 호스트 조회 (이름으로)
152
+ async fn get_host(name: str) -> HostConfig {
153
+ var cfg = await load_config()
154
+
155
+ var i: i32 = 0
156
+ while i < length(cfg.hosts) {
157
+ if cfg.hosts[i].name == name {
158
+ return cfg.hosts[i]
159
+ }
160
+ i = i + 1
161
+ }
162
+
163
+ HostConfig { name: "", url: "", token: "" }
164
+ }
165
+
166
+ /// 모든 호스트 목록
167
+ async fn list_hosts() -> [HostConfig] {
168
+ var cfg = await load_config()
169
+ cfg.hosts
170
+ }
171
+
172
+ // 파일 I/O 헬퍼 (FreeLang 내장)
173
+ // - exists(path: str) -> bool
174
+ // - read_file(path: str) -> str
175
+ // - write_file(path: str, content: str) -> void
@@ -0,0 +1,355 @@
1
+ // FreeLang v4.2 — 배치 처리 엔진
2
+ // 대규모 작업을 병렬로 처리하고 결과를 수집
3
+
4
+ /// 배치 작업 결과
5
+ struct BatchJobResult {
6
+ job_id: str
7
+ success: bool
8
+ result: str
9
+ error: str
10
+ duration_ms: i32
11
+ }
12
+
13
+ /// 배치 엔진 설정
14
+ struct BatchConfig {
15
+ workers: i32 // 병렬 워커 수 (기본 4)
16
+ max_retries: i32 // 최대 재시도 (기본 3)
17
+ backoff_factor: f64 // exponential backoff (기본 2.0)
18
+ timeout_ms: i32 // 작업당 타임아웃 (기본 30000ms)
19
+ }
20
+
21
+ /// 배치 엔진 상태
22
+ struct BatchEngine {
23
+ config: BatchConfig
24
+ total_jobs: i32
25
+ completed: i32
26
+ succeeded: i32
27
+ failed: i32
28
+ }
29
+
30
+ /// 배치 처리 결과 요약
31
+ struct BatchSummary {
32
+ total: i32
33
+ succeeded: i32
34
+ failed: i32
35
+ success_rate: f64
36
+ }
37
+
38
+ /// 배치 엔진 생성
39
+ fn new_batch_engine(workers: i32) -> BatchEngine {
40
+ var config = BatchConfig {
41
+ workers: if workers > 0 { workers } else { 4 },
42
+ max_retries: 3,
43
+ backoff_factor: 2.0,
44
+ timeout_ms: 30000
45
+ }
46
+
47
+ BatchEngine {
48
+ config: config,
49
+ total_jobs: 0,
50
+ completed: 0,
51
+ succeeded: 0,
52
+ failed: 0
53
+ }
54
+ }
55
+
56
+ // ==================== 주요 배치 작업 ====================
57
+
58
+ /// 저장소 배치 생성
59
+ async fn batch_create_repos(
60
+ client: HttpClient,
61
+ repos: [RepositorySpec],
62
+ workers: i32
63
+ ) -> [BatchJobResult] {
64
+ var results: [BatchJobResult] = []
65
+ var engine = new_batch_engine(workers)
66
+
67
+ println("Creating " + str(length(repos)) + " repositories...")
68
+ println("Workers: " + str(workers))
69
+
70
+ var i: i32 = 0
71
+ while i < length(repos) {
72
+ var repo = repos[i]
73
+
74
+ // 작업 생성 및 실행
75
+ var result = await execute_with_retry(
76
+ client,
77
+ repo.owner + "/" + repo.name,
78
+ async fn() -> str {
79
+ var resp = await http_post_json(
80
+ client,
81
+ "/api/v1/user/repos",
82
+ json_stringify(repo)
83
+ )
84
+ if is_error(resp.status) {
85
+ return "ERROR"
86
+ }
87
+ resp.body
88
+ },
89
+ engine.config.max_retries,
90
+ engine.config.backoff_factor
91
+ )
92
+
93
+ results.push(result)
94
+
95
+ // 진행 상황 출력
96
+ if result.success {
97
+ println("✓ " + result.job_id)
98
+ engine.succeeded = engine.succeeded + 1
99
+ } else {
100
+ println("✗ " + result.job_id + ": " + result.error)
101
+ engine.failed = engine.failed + 1
102
+ }
103
+
104
+ engine.completed = engine.completed + 1
105
+ i = i + 1
106
+ }
107
+
108
+ engine.total_jobs = length(repos)
109
+ print_summary(engine)
110
+
111
+ results
112
+ }
113
+
114
+ /// 저장소 배치 ensure (상태 동기화)
115
+ async fn batch_ensure_repos(
116
+ client: HttpClient,
117
+ repos: [RepositorySpec],
118
+ workers: i32
119
+ ) -> [BatchJobResult] {
120
+ var results: [BatchJobResult] = []
121
+ var engine = new_batch_engine(workers)
122
+
123
+ println("Ensuring " + str(length(repos)) + " repositories...")
124
+ println("Workers: " + str(workers))
125
+
126
+ var i: i32 = 0
127
+ while i < length(repos) {
128
+ var repo = repos[i]
129
+
130
+ // ensure 작업 실행
131
+ var result = await execute_with_retry(
132
+ client,
133
+ repo.owner + "/" + repo.name,
134
+ async fn() -> str {
135
+ var action = await ensure_repo(
136
+ client,
137
+ repo.owner,
138
+ repo.name,
139
+ repo.private,
140
+ repo.description
141
+ )
142
+
143
+ match action {
144
+ EnsureAction::Created => "created",
145
+ EnsureAction::Updated(_) => "updated",
146
+ EnsureAction::Unchanged => "unchanged",
147
+ EnsureAction::Error(msg) => "ERROR: " + msg,
148
+ }
149
+ },
150
+ engine.config.max_retries,
151
+ engine.config.backoff_factor
152
+ )
153
+
154
+ results.push(result)
155
+
156
+ if result.success {
157
+ println("✓ " + result.job_id + ": " + result.result)
158
+ engine.succeeded = engine.succeeded + 1
159
+ } else {
160
+ println("✗ " + result.job_id + ": " + result.error)
161
+ engine.failed = engine.failed + 1
162
+ }
163
+
164
+ engine.completed = engine.completed + 1
165
+ i = i + 1
166
+ }
167
+
168
+ engine.total_jobs = length(repos)
169
+ print_summary(engine)
170
+
171
+ results
172
+ }
173
+
174
+ /// 저장소 배치 삭제
175
+ async fn batch_delete_repos(
176
+ client: HttpClient,
177
+ repos: [str],
178
+ workers: i32,
179
+ force: bool
180
+ ) -> [BatchJobResult] {
181
+ var results: [BatchJobResult] = []
182
+ var engine = new_batch_engine(workers)
183
+
184
+ if !force {
185
+ println("Warning: This will delete " + str(length(repos)) + " repositories!")
186
+ println("Use --force to confirm")
187
+ return results
188
+ }
189
+
190
+ println("Deleting " + str(length(repos)) + " repositories...")
191
+
192
+ var i: i32 = 0
193
+ while i < length(repos) {
194
+ var repo_path = repos[i] // "owner/repo"
195
+
196
+ var result = await execute_with_retry(
197
+ client,
198
+ repo_path,
199
+ async fn() -> str {
200
+ var resp = await http_delete(client, "/api/v1/repos/" + repo_path)
201
+ if is_error(resp.status) {
202
+ return "ERROR"
203
+ }
204
+ "deleted"
205
+ },
206
+ engine.config.max_retries,
207
+ engine.config.backoff_factor
208
+ )
209
+
210
+ results.push(result)
211
+
212
+ if result.success {
213
+ println("✓ " + result.job_id + " deleted")
214
+ engine.succeeded = engine.succeeded + 1
215
+ } else {
216
+ println("✗ " + result.job_id + " failed")
217
+ engine.failed = engine.failed + 1
218
+ }
219
+
220
+ engine.completed = engine.completed + 1
221
+ i = i + 1
222
+ }
223
+
224
+ engine.total_jobs = length(repos)
225
+ print_summary(engine)
226
+
227
+ results
228
+ }
229
+
230
+ // ==================== 재시도 로직 ====================
231
+
232
+ /// 재시도 로직과 함께 작업 실행
233
+ async fn execute_with_retry(
234
+ client: HttpClient,
235
+ job_id: str,
236
+ exec: async fn() -> str,
237
+ max_retries: i32,
238
+ backoff_factor: f64
239
+ ) -> BatchJobResult {
240
+ var retries: i32 = 0
241
+ var backoff_ms: i32 = 100
242
+
243
+ loop {
244
+ // 작업 실행
245
+ var start = now_ms()
246
+ var result = await exec()
247
+ var duration = now_ms() - start
248
+
249
+ if result != "ERROR" {
250
+ return BatchJobResult {
251
+ job_id: job_id,
252
+ success: true,
253
+ result: result,
254
+ error: "",
255
+ duration_ms: duration
256
+ }
257
+ }
258
+
259
+ // 재시도 확인
260
+ if retries >= max_retries {
261
+ return BatchJobResult {
262
+ job_id: job_id,
263
+ success: false,
264
+ result: "",
265
+ error: "Max retries exceeded",
266
+ duration_ms: duration
267
+ }
268
+ }
269
+
270
+ // exponential backoff
271
+ retries = retries + 1
272
+ backoff_ms = int(float(backoff_ms) * backoff_factor)
273
+
274
+ println(" Retry " + str(retries) + "/" + str(max_retries) + " for " + job_id + " (wait " + str(backoff_ms) + "ms)")
275
+ sleep(backoff_ms)
276
+ }
277
+ }
278
+
279
+ // ==================== 통계 ====================
280
+
281
+ /// 배치 처리 요약 출력
282
+ fn print_summary(engine: BatchEngine) {
283
+ println("")
284
+ println("========== Batch Summary ==========")
285
+ println("Total: " + str(engine.total_jobs))
286
+ println("Succeeded: " + str(engine.succeeded))
287
+ println("Failed: " + str(engine.failed))
288
+
289
+ if engine.total_jobs > 0 {
290
+ var rate = float(engine.succeeded) / float(engine.total_jobs) * 100.0
291
+ println("Success: " + str(rate) + "%")
292
+ }
293
+ println("===================================")
294
+ println("")
295
+ }
296
+
297
+ /// 배치 요약 계산
298
+ fn get_summary(results: [BatchJobResult]) -> BatchSummary {
299
+ var succeeded: i32 = 0
300
+ var failed: i32 = 0
301
+ var i: i32 = 0
302
+
303
+ while i < length(results) {
304
+ if results[i].success {
305
+ succeeded = succeeded + 1
306
+ } else {
307
+ failed = failed + 1
308
+ }
309
+ i = i + 1
310
+ }
311
+
312
+ var rate = 0.0
313
+ if length(results) > 0 {
314
+ rate = float(succeeded) / float(length(results)) * 100.0
315
+ }
316
+
317
+ BatchSummary {
318
+ total: length(results),
319
+ succeeded: succeeded,
320
+ failed: failed,
321
+ success_rate: rate
322
+ }
323
+ }
324
+
325
+ // ==================== 설정 (RepositorySpec) ====================
326
+
327
+ /// 저장소 스펙 정의
328
+ struct RepositorySpec {
329
+ owner: str
330
+ name: str
331
+ description: str
332
+ private: bool
333
+ }
334
+
335
+ // ==================== 헬퍼 함수 ====================
336
+
337
+ /// 현재 시간 (밀리초)
338
+ fn now_ms() -> i32 {
339
+ // 프로덕션: system clock 호출
340
+ 0 // placeholder
341
+ }
342
+
343
+ /// 현재 시간 문자열
344
+ fn now_str() -> str {
345
+ // 프로덕션: timestamp 포매팅
346
+ "" // placeholder
347
+ }
348
+
349
+ // 필요한 함수들 (다른 모듈):
350
+ // - http_post_json, http_delete
351
+ // - is_error, response_to_error
352
+ // - ensure_repo
353
+ // - json_stringify
354
+ // - float, int (타입 변환)
355
+ // - sleep (FreeLang 내장)