skyforge-lang 1.0.0__tar.gz

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.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: skyforge-lang
3
+ Version: 1.0.0
4
+ Summary: SkyForge - русскоязычный язык программирования
5
+ Author: SkyForge
6
+ License: MIT
7
+ Keywords: language,russian,interpreter,skyforge
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Natural Language :: Russian
10
+ Classifier: Topic :: Software Development :: Interpreters
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+
14
+ # SkyForge
15
+
16
+ Русскоязычный язык программирования.
17
+
18
+ ## Установка (dev)
19
+
20
+ pip install -e .
21
+
22
+ ## Запуск
23
+
24
+ SkyForge GAZUY examples/test.skf
25
+ SkyForge check examples/test.skf
@@ -0,0 +1,12 @@
1
+ # SkyForge
2
+
3
+ Русскоязычный язык программирования.
4
+
5
+ ## Установка (dev)
6
+
7
+ pip install -e .
8
+
9
+ ## Запуск
10
+
11
+ SkyForge GAZUY examples/test.skf
12
+ SkyForge check examples/test.skf
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "skyforge-lang" # было "skyforge"
7
+ version = "1.0.0"
8
+ description = "SkyForge - русскоязычный язык программирования"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "SkyForge" }]
13
+ keywords = ["language", "russian", "interpreter", "skyforge"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Natural Language :: Russian",
17
+ "Topic :: Software Development :: Interpreters",
18
+ ]
19
+
20
+ [project.scripts]
21
+ SkyForge = "skyforge.cli:main"
22
+ skyforge = "skyforge.cli:main"
23
+
24
+ [tool.setuptools]
25
+ packages = ["skyforge"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ from .core import (
2
+ Lexer, Parser, Interpreter,
3
+ run_source, run_file,
4
+ Token, Program, Environment, Function,
5
+ )
6
+
7
+ __version__ = "1.0.0"
8
+ __all__ = [
9
+ "Lexer", "Parser", "Interpreter",
10
+ "run_source", "run_file",
11
+ "Token", "Program", "Environment", "Function",
12
+ "__version__",
13
+ ]
@@ -0,0 +1,442 @@
1
+ # skyforge/cli.py
2
+ # SkyForge — командный интерфейс
3
+ # Команды: SkyForge GAZUY file.skf | SkyForge check file.skf | SkyForge gui
4
+
5
+ import os
6
+ import sys
7
+ import argparse
8
+ import signal
9
+ import traceback
10
+
11
+ from .core import Lexer, Parser, Interpreter
12
+ from . import errors as E
13
+
14
+
15
+ BANNER = r"""
16
+ ███████╗██╗ ██╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ███████╗
17
+ ██╔════╝██║ ██╔╝╚██╗ ██╔╝██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝
18
+ ███████╗█████╔╝ ╚████╔╝ █████╗ ██║ ██║██████╔╝██║ ███╗█████╗
19
+ ╚════██║██╔═██╗ ╚██╔╝ ██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝
20
+ ███████║██║ ██╗ ██║ ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗
21
+ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
22
+ русскоязычный язык программирования
23
+ """
24
+
25
+
26
+ # =========================================================
27
+ # GAZUY — запуск .skf-файла
28
+ # =========================================================
29
+
30
+ def cmd_gazuy(args) -> int:
31
+ import time as _time
32
+
33
+ path = args.file
34
+ if not os.path.isfile(path):
35
+ print(f"❌ Файл не найден: {path}", file=sys.stderr)
36
+ return 1
37
+
38
+ if args.port is not None:
39
+ os.environ["PORT"] = str(args.port)
40
+ if args.host is not None:
41
+ os.environ["SKYFORGE_HOST"] = args.host
42
+
43
+ os.environ.setdefault("PYTHONIOENCODING", "utf-8")
44
+ os.environ.setdefault("PYTHONUTF8", "1")
45
+
46
+ print(BANNER, flush=True)
47
+ print(f"🚀 GAZUY! Файл: {path}", flush=True)
48
+ if os.environ.get("PORT"):
49
+ print(f" PORT = {os.environ['PORT']}", flush=True)
50
+ if os.environ.get("SKYFORGE_HOST"):
51
+ print(f" HOST = {os.environ['SKYFORGE_HOST']}", flush=True)
52
+ print("-" * 66, flush=True)
53
+
54
+ try:
55
+ with open(path, encoding="utf-8") as f:
56
+ source = f.read()
57
+ except Exception as e:
58
+ print(f"❌ Не могу прочитать файл: {e}", file=sys.stderr)
59
+ return 1
60
+
61
+ try:
62
+ tokens = Lexer(source).tokenize()
63
+ program = Parser(tokens).parse()
64
+ except SyntaxError as e:
65
+ print(f"❌ Синтаксическая ошибка: {e}", file=sys.stderr)
66
+ return 2
67
+ except Exception as e:
68
+ print(f"❌ Ошибка разбора: {e}", file=sys.stderr)
69
+ traceback.print_exc()
70
+ return 2
71
+
72
+ if args.check_only:
73
+ print("✅ Синтаксис в порядке.", flush=True)
74
+ return 0
75
+
76
+ interp = Interpreter()
77
+ interp.current_file = os.path.abspath(path)
78
+
79
+ # SIGTERM — для контейнеров (Render/Docker).
80
+ # SIGINT НЕ перехватываем — пусть Python бросит KeyboardInterrupt,
81
+ # это самый надёжный способ остановки.
82
+ def _on_sigterm(signum, _frame):
83
+ print(f"\n⚠️ SIGTERM — останавливаю...", flush=True)
84
+ try:
85
+ interp._web_server_stop()
86
+ except Exception:
87
+ pass
88
+ # даём shutdown время
89
+ for _ in range(20):
90
+ if not interp.has_active_servers():
91
+ break
92
+ _time.sleep(0.1)
93
+ os._exit(0)
94
+
95
+ try:
96
+ signal.signal(signal.SIGTERM, _on_sigterm)
97
+ except (ValueError, AttributeError):
98
+ pass
99
+
100
+ try:
101
+ interp.run(program)
102
+ except SystemExit:
103
+ raise
104
+ except KeyboardInterrupt:
105
+ print("\n⏹️ Прервано пользователем.", flush=True)
106
+ try:
107
+ interp._web_server_stop()
108
+ except Exception:
109
+ pass
110
+ return 0
111
+ except Exception:
112
+ print("❌ Ошибка выполнения:", file=sys.stderr)
113
+ traceback.print_exc()
114
+ return 3
115
+
116
+ if interp.has_active_servers():
117
+ print("🟢 Сервер(ы) работают. Ctrl+C для остановки.\n", flush=True)
118
+ interp._web_server_wait()
119
+
120
+ else:
121
+ print("\n✅ Скрипт завершён.", flush=True)
122
+
123
+ return 0
124
+
125
+
126
+ # =========================================================
127
+ # check — только проверка синтаксиса
128
+ # =========================================================
129
+
130
+ def cmd_test(args) -> int:
131
+ """Запуск тестов из .skf файла."""
132
+ path = args.file
133
+ if not os.path.isfile(path):
134
+ print(f"❌ Файл не найден: {path}", file=sys.stderr)
135
+ return 1
136
+
137
+ try:
138
+ with open(path, encoding="utf-8") as f:
139
+ source = f.read()
140
+ except Exception as e:
141
+ print(f"❌ Не могу прочитать: {e}", file=sys.stderr)
142
+ return 1
143
+
144
+ try:
145
+ tokens = Lexer(source).tokenize()
146
+ program = Parser(tokens).parse()
147
+ except SyntaxError as e:
148
+ print(E.format_syntax_error(e, source=source, filename=path), file=sys.stderr)
149
+ return 2
150
+
151
+ interp = Interpreter()
152
+ interp.current_file = os.path.abspath(path)
153
+
154
+ try:
155
+ interp.run(program)
156
+ except SystemExit:
157
+ raise
158
+ except Exception as err:
159
+ print(E.format_runtime_error(err, filename=path), file=sys.stderr)
160
+ return 3
161
+
162
+ # Запускаем накопленные тесты
163
+ runner = interp.globals.vars.get("запустить_тесты")
164
+ if runner is None:
165
+ print("⚠️ Не найдено функции 'запустить_тесты'. Добавь её в файл.")
166
+ return 1
167
+
168
+ ok = interp.call_function(runner, [])
169
+ return 0 if ok else 1
170
+
171
+
172
+ def cmd_web(args) -> int:
173
+ """Запуск веб-приложения SkyForge."""
174
+ path = args.file
175
+ if not os.path.isfile(path):
176
+ print(f"❌ Файл не найден: {path}", file=sys.stderr)
177
+ return 1
178
+
179
+ if args.port is not None:
180
+ os.environ["PORT"] = str(args.port)
181
+ if args.host is not None:
182
+ os.environ["SKYFORGE_HOST"] = args.host
183
+
184
+ print(BANNER, flush=True)
185
+ print(f"🌐 WEB! Файл: {path}", flush=True)
186
+ if os.environ.get("PORT"):
187
+ print(f" PORT = {os.environ['PORT']}", flush=True)
188
+ if os.environ.get("SKYFORGE_HOST"):
189
+ print(f" HOST = {os.environ['SKYFORGE_HOST']}", flush=True)
190
+ print("-" * 66, flush=True)
191
+
192
+ try:
193
+ with open(path, encoding="utf-8") as f:
194
+ source = f.read()
195
+ except Exception as e:
196
+ print(f"❌ Не могу прочитать файл: {e}", file=sys.stderr)
197
+ return 1
198
+
199
+ try:
200
+ tokens = Lexer(source).tokenize()
201
+ program = Parser(tokens).parse()
202
+ except SyntaxError as e:
203
+ print(E.format_syntax_error(e, source=source, filename=path), file=sys.stderr)
204
+ return 2
205
+
206
+ interp = Interpreter()
207
+ interp.current_file = os.path.abspath(path)
208
+ try:
209
+ interp.run(program)
210
+ except KeyboardInterrupt:
211
+ print("\n⏹ Прервано.", flush=True)
212
+ return 0
213
+ except SystemExit:
214
+ raise
215
+ except Exception as err:
216
+ print(E.format_runtime_error(err, filename=path), file=sys.stderr)
217
+ return 3
218
+ return 0
219
+
220
+
221
+ def cmd_repl(args) -> int:
222
+ from .repl import main as repl_main
223
+ return repl_main() or 0
224
+
225
+
226
+ def cmd_check(args) -> int:
227
+ path = args.file
228
+ if not os.path.isfile(path):
229
+ print(f"❌ Файл не найден: {path}", file=sys.stderr)
230
+ return 1
231
+
232
+ try:
233
+ with open(path, encoding="utf-8") as f:
234
+ src = f.read()
235
+ except Exception as e:
236
+ print(f"❌ Не могу прочитать файл: {e}", file=sys.stderr)
237
+ return 1
238
+
239
+ try:
240
+ tokens = Lexer(src).tokenize()
241
+ Parser(tokens).parse()
242
+ print(f"✅ {path}: синтаксис в порядке")
243
+ return 0
244
+ except SyntaxError as e:
245
+ print(E.format_syntax_error(e, source=src, filename=path), file=sys.stderr)
246
+ return 2
247
+ except Exception as e:
248
+ print(E.format_runtime_error(e, filename=path), file=sys.stderr)
249
+ return 1
250
+
251
+
252
+ # =========================================================
253
+ # run — запуск без баннера (короткий вывод)
254
+ # =========================================================
255
+
256
+ def cmd_run(args) -> int:
257
+ path = args.file
258
+ if not os.path.isfile(path):
259
+ print(f"❌ Файл не найден: {path}", file=sys.stderr)
260
+ return 1
261
+
262
+ if args.port is not None:
263
+ os.environ["PORT"] = str(args.port)
264
+ if args.host is not None:
265
+ os.environ["SKYFORGE_HOST"] = args.host
266
+
267
+ try:
268
+ with open(path, encoding="utf-8") as f:
269
+ src = f.read()
270
+ except Exception as e:
271
+ print(f"❌ Не могу прочитать файл: {e}", file=sys.stderr)
272
+ return 1
273
+
274
+ try:
275
+ tokens = Lexer(src).tokenize()
276
+ program = Parser(tokens).parse()
277
+ except SyntaxError as e:
278
+ print(f"❌ Синтаксическая ошибка: {e}", file=sys.stderr)
279
+ return 2
280
+
281
+ interp = Interpreter()
282
+
283
+ def _shutdown(signum, _frame):
284
+ try:
285
+ interp._web_server_stop()
286
+ except Exception:
287
+ pass
288
+ sys.exit(0)
289
+
290
+ try:
291
+ signal.signal(signal.SIGTERM, _shutdown)
292
+ signal.signal(signal.SIGINT, _shutdown)
293
+ except (ValueError, AttributeError):
294
+ pass
295
+
296
+ try:
297
+ interp.run(program)
298
+ except SystemExit:
299
+ raise
300
+ except Exception:
301
+ traceback.print_exc()
302
+ return 3
303
+
304
+ if interp.has_active_servers():
305
+ try:
306
+ interp._web_server_wait()
307
+ except KeyboardInterrupt:
308
+ interp._web_server_stop()
309
+
310
+ return 0
311
+
312
+
313
+ # =========================================================
314
+ # gui — опциональный графический лаунчер
315
+ # =========================================================
316
+
317
+ def cmd_gui(args) -> int:
318
+ try:
319
+ from .launcher import main as gui_main
320
+ except ImportError as e:
321
+ print(f"❌ GUI недоступен: {e}", file=sys.stderr)
322
+ print(" Убедитесь, что файл skyforge/launcher.py присутствует.", file=sys.stderr)
323
+ return 1
324
+
325
+ # Подменяем argv, чтобы launcher.main() увидел файл
326
+ argv_backup = sys.argv[:]
327
+ try:
328
+ sys.argv = ["SkyForge-gui"] + ([args.file] if args.file else [])
329
+ gui_main()
330
+ finally:
331
+ sys.argv = argv_backup
332
+ return 0
333
+
334
+
335
+ # =========================================================
336
+ # version / info
337
+ # =========================================================
338
+
339
+ def cmd_version(args) -> int:
340
+ from . import __version__
341
+ print(f"SkyForge {__version__}")
342
+ print(f"Python: {sys.version.split()[0]}")
343
+ print(f"Платформа: {sys.platform}")
344
+ print(f"Файл пакета: {os.path.dirname(os.path.abspath(__file__))}")
345
+ return 0
346
+
347
+
348
+ # =========================================================
349
+ # Парсер аргументов
350
+ # =========================================================
351
+
352
+ def build_parser() -> argparse.ArgumentParser:
353
+ p = argparse.ArgumentParser(
354
+ prog="SkyForge",
355
+ description="SkyForge — русскоязычный язык программирования",
356
+ formatter_class=argparse.RawDescriptionHelpFormatter,
357
+ epilog=(
358
+ "Примеры:\n"
359
+ " SkyForge GAZUY app.skf # запустить файл\n"
360
+ " SkyForge GAZUY app.skf --port 9000 # свой порт\n"
361
+ " SkyForge check app.skf # проверить синтаксис\n"
362
+ " SkyForge gui app.skf # открыть GUI\n"
363
+ ),
364
+ )
365
+ p.add_argument("--version", action="version", version="SkyForge 1.0.0")
366
+
367
+ sub = p.add_subparsers(dest="cmd", metavar="КОМАНДА")
368
+
369
+ # --- GAZUY ---
370
+ g = sub.add_parser(
371
+ "GAZUY",
372
+ aliases=["gazuy", "ГАЗУЙ", "газуй"],
373
+ help="Запустить .skf-файл (двигатель деплоя)",
374
+ )
375
+ g.add_argument("file", help="Путь к .skf-файлу")
376
+ g.add_argument("--port", type=int, default=None, help="Переопределить $PORT")
377
+ g.add_argument("--host", default=None, help="Переопределить $SKYFORGE_HOST")
378
+ g.add_argument("--check", dest="check_only", action="store_true",
379
+ help="Только проверить синтаксис, не запускать")
380
+ g.set_defaults(func=cmd_gazuy)
381
+
382
+ # --- check ---
383
+ c = sub.add_parser("check", help="Проверить синтаксис .skf-файла")
384
+ c.add_argument("file", help="Путь к .skf-файлу")
385
+ c.set_defaults(func=cmd_check)
386
+
387
+ # --- run (без баннера) ---
388
+ r = sub.add_parser("run", help="Запустить .skf-файл без баннера")
389
+ r.add_argument("file", help="Путь к .skf-файлу")
390
+ r.add_argument("--port", type=int, default=None, help="Переопределить $PORT")
391
+ r.add_argument("--host", default=None, help="Переопределить $SKYFORGE_HOST")
392
+ r.set_defaults(func=cmd_run)
393
+
394
+ # --- test ---
395
+ tp = sub.add_parser("test", help="Запустить тесты из .skf файла")
396
+ tp.add_argument("file", help="Путь к файлу с тестами")
397
+ tp.set_defaults(func=cmd_test)
398
+
399
+ # --- web ---
400
+ wp = sub.add_parser("web", help="Запустить веб-приложение SkyForge")
401
+ wp.add_argument("file", help="Путь к .skf файлу")
402
+ wp.add_argument("--port", type=int, default=None, help="Порт (или $PORT)")
403
+ wp.add_argument("--host", default=None, help="Хост (или $SKYFORGE_HOST)")
404
+ wp.set_defaults(func=cmd_web)
405
+
406
+ # --- repl ---
407
+ rp = sub.add_parser("repl", help="Интерактивная консоль SkyForge")
408
+ rp.set_defaults(func=cmd_repl)
409
+
410
+ # --- gui ---
411
+ u = sub.add_parser("gui", help="Открыть графический лаунчер")
412
+ u.add_argument("file", nargs="?", default=None, help="Файл для автозагрузки")
413
+ u.set_defaults(func=cmd_gui)
414
+
415
+ # --- version ---
416
+ v = sub.add_parser("version", help="Показать версию и окружение")
417
+ v.set_defaults(func=cmd_version)
418
+
419
+ return p
420
+
421
+
422
+ # =========================================================
423
+ # main
424
+ # =========================================================
425
+
426
+ def main(argv=None) -> int:
427
+ parser = build_parser()
428
+ args = parser.parse_args(argv)
429
+
430
+ if not args.cmd:
431
+ parser.print_help()
432
+ return 0
433
+
434
+ try:
435
+ return args.func(args) or 0
436
+ except KeyboardInterrupt:
437
+ print("\n⚠️ Прервано.", file=sys.stderr)
438
+ return 130
439
+
440
+
441
+ if __name__ == "__main__":
442
+ sys.exit(main())