PyUMSN 1.0.0__py3-none-any.whl

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.
pyumsn/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ """PyUMSN — 엄슨(UMSN) 프로그래밍 언어.
2
+
3
+ >>> import pyumsn
4
+ >>> pyumsn.umsn_to_py('엄!..하"안녕 엄슨"..다')
5
+ 'print("안녕 엄슨")'
6
+ >>> pyumsn.py_to_umsn('print("안녕 엄슨")')
7
+ '엄!..하"안녕 엄슨"..다'
8
+ """
9
+
10
+ __version__ = "1.0.0"
11
+
12
+ from .errors import UmsnEncodingError, UmsnError, UmsnSyntaxError # noqa: E402
13
+ from .sourceio import read_source, write_source # noqa: E402
14
+ from .translator import (check_umsn, highlight_spans, py_to_umsn, # noqa: E402
15
+ tokenize_umsn, transliterate, umsn_to_py, untransliterate)
16
+ from .vocab import all_words, category_of # noqa: E402
17
+
18
+ __all__ = [
19
+ "__version__", "UmsnError", "UmsnSyntaxError", "UmsnEncodingError",
20
+ "umsn_to_py", "py_to_umsn", "check_umsn", "highlight_spans", "tokenize_umsn",
21
+ "transliterate", "untransliterate", "read_source", "write_source",
22
+ "all_words", "category_of", "run_file", "install_import_hook",
23
+ ]
24
+
25
+
26
+ def run_file(path, args=(), keep=False):
27
+ """엄슨 파일을 실행하고 종료 코드를 돌려준다."""
28
+ from .runner import run_file as _run
29
+ return _run(path, list(args), keep=keep)
30
+
31
+
32
+ def install_import_hook():
33
+ """``import`` 로 ``.umsn`` 모듈을 불러올 수 있게 한다."""
34
+ from .importer import install
35
+ install()
pyumsn/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
pyumsn/cli.py ADDED
@@ -0,0 +1,258 @@
1
+ """``pyumsn`` 명령줄 도구."""
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from . import __version__, vocab
9
+ from .errors import UmsnEncodingError, UmsnError, UmsnSyntaxError
10
+ from .sourceio import read_source, write_source
11
+ from .translator import check_umsn, py_to_umsn, transliterate, umsn_to_py, untransliterate
12
+
13
+ IDE_PATH = Path(__file__).resolve().parent / "ide" / "umsn_ide.umsn"
14
+
15
+
16
+ def _setup_stdio():
17
+ for stream in (sys.stdout, sys.stderr):
18
+ try:
19
+ stream.reconfigure(encoding="utf-8")
20
+ except (AttributeError, ValueError, OSError):
21
+ pass
22
+
23
+
24
+ def _say(text, err=False):
25
+ stream = sys.stderr if err else sys.stdout
26
+ stream.write(text + "\n")
27
+ stream.flush()
28
+
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # 변환 (topy / toumsn)
32
+ # ---------------------------------------------------------------------------
33
+ def _convert_one(src_path, out_path, direction, keep_symbols):
34
+ text = read_source(src_path)
35
+ if direction == "topy":
36
+ result = umsn_to_py(text, filename=str(src_path))
37
+ else:
38
+ result = py_to_umsn(text, filename=str(src_path), keep_symbols=keep_symbols)
39
+ if out_path == "-":
40
+ sys.stdout.write(result)
41
+ sys.stdout.flush()
42
+ return
43
+ out_path = Path(out_path)
44
+ out_path.parent.mkdir(parents=True, exist_ok=True)
45
+ write_source(out_path, result)
46
+ _say("만들었슨: %s → %s" % (src_path, out_path), err=True)
47
+
48
+
49
+ def _convert(args, direction):
50
+ src_suffix, dst_suffix = (".umsn", ".py") if direction == "topy" else (".py", ".umsn")
51
+ source = Path(args.source)
52
+ if not source.exists():
53
+ _say("엄슨 오류! 파일이나 폴더가 없슨: %s" % source, err=True)
54
+ return 1
55
+ keep = getattr(args, "keep_symbols", False)
56
+ failed = 0
57
+ if source.is_dir():
58
+ if args.output == "-":
59
+ _say("엄슨 오류! 폴더는 표준 출력(-)으로 변환할 수 없슨.", err=True)
60
+ return 1
61
+ out_root = Path(args.output) if args.output else source
62
+ files = sorted(p for p in source.rglob("*" + src_suffix) if p.is_file())
63
+ if not files:
64
+ _say("변환할 %s 파일이 없슨: %s" % (src_suffix, source), err=True)
65
+ for path in files:
66
+ target = out_root / path.relative_to(source).with_suffix(dst_suffix)
67
+ try:
68
+ _convert_one(path, target, direction, keep)
69
+ except UmsnError as exc:
70
+ failed += 1
71
+ _report_error(exc)
72
+ return 1 if failed else 0
73
+ out = args.output or str(source.with_suffix(dst_suffix))
74
+ try:
75
+ _convert_one(source, out, direction, keep)
76
+ except UmsnError as exc:
77
+ _report_error(exc)
78
+ return 1
79
+ return 0
80
+
81
+
82
+ def _report_error(exc):
83
+ if isinstance(exc, UmsnSyntaxError):
84
+ _say(exc.report(), err=True)
85
+ else:
86
+ _say("엄슨 오류! %s" % exc, err=True)
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # 명령
91
+ # ---------------------------------------------------------------------------
92
+ def cmd_run(args):
93
+ from .runner import run_file
94
+ try:
95
+ return run_file(args.file, args.args, keep=args.keep, show_py=args.show_py)
96
+ except FileNotFoundError:
97
+ _say("엄슨 오류! 파일이 없슨: %s" % args.file, err=True)
98
+ return 1
99
+ except UmsnError as exc:
100
+ _report_error(exc)
101
+ return 1
102
+
103
+
104
+ def cmd_topy(args):
105
+ return _convert(args, "topy")
106
+
107
+
108
+ def cmd_toumsn(args):
109
+ return _convert(args, "toumsn")
110
+
111
+
112
+ def cmd_check(args):
113
+ bad = 0
114
+ for name in args.files:
115
+ try:
116
+ source = read_source(name)
117
+ except (OSError, UmsnEncodingError) as exc:
118
+ _say("엄슨 오류! %s" % exc, err=True)
119
+ bad += 1
120
+ continue
121
+ problems = check_umsn(source)
122
+ if problems:
123
+ bad += 1
124
+ _say(UmsnSyntaxError(problems, filename=name, source=source).report(), err=True)
125
+ else:
126
+ _say("문제 없슨! %s" % name)
127
+ return 1 if bad else 0
128
+
129
+
130
+ def cmd_ide(args):
131
+ from .runner import run_file
132
+ extra = [os.path.abspath(args.file)] if args.file else []
133
+ try:
134
+ return run_file(IDE_PATH, extra)
135
+ except UmsnError as exc:
136
+ _report_error(exc)
137
+ return 1
138
+
139
+
140
+ def cmd_repl(args):
141
+ from .repl import main
142
+ return main()
143
+
144
+
145
+ def _markdown_table():
146
+ lines = ["# 엄슨(UMSN) 단어장", "",
147
+ "`pyumsn words --markdown` 으로 자동 생성한 문서입니다.", ""]
148
+ groups = [("예약어", vocab.KEYWORDS), ("내장 함수·상수·예외", vocab.BUILTINS),
149
+ ("관례·특수 이름", vocab.SPECIAL_NAMES), ("메소드·자주 쓰는 인자", vocab.METHODS)]
150
+ groups += [("라이브러리 — " + title, table) for title, table in vocab.LIBRARY_GROUPS.items()]
151
+ for title, table in groups:
152
+ lines += ["## " + title, "", "| 엄슨 | 파이썬 |", "|---|---|"]
153
+ lines += ["| `%s` | `%s` |" % (um, py) for py, um in table.items()]
154
+ lines.append("")
155
+ lines += ["## 기호 (문장부호·연산자)", "", "| 엄슨 | 파이썬 |", "|---|---|"]
156
+ lines += ["| `%s` | `%s` |" % (um, py.replace("|", "\\|")) for py, um in vocab.SYMBOL2UMSN.items()]
157
+ lines += ["", "`.` (속성 접근)과 `...` 은 그대로 씁니다.", ""]
158
+ lines += ["## 문자열 접두사", "", "| 엄슨 | 파이썬 |", "|---|---|"]
159
+ lines += ["| `%s\"...\"` | `%s\"...\"` |" % (um, py) for py, um in vocab.STRING_PREFIX_CHARS.items()]
160
+ lines += ["", "대문자 접두사는 앞에 `대` 를 붙입니다 (`대형\"...\"` = `F\"...\"`). 조합 가능: `형날`=`fr`, `날바`=`rb`.", ""]
161
+ lines += ["## 음역 (사전에 없는 영어 이름)", "",
162
+ "표지 `%s` 뒤에 알파벳을 한 글자씩 한글 음절로 적습니다. 대문자는 앞에 `대`, 숫자와 `_` 는 그대로."
163
+ % vocab.TRANSLIT_MARK, "", "| 알파벳 | 한글 |", "|---|---|"]
164
+ lines += ["| `%s` | `%s` |" % (a, h) for a, h in vocab.TRANSLIT_LETTERS.items()]
165
+ lines += ["", "예: `polyfit` → `%s`, `DataFrame` 이 사전에 없다면 `%s`" %
166
+ (transliterate("polyfit"), transliterate("DataFrame")), ""]
167
+ return "\n".join(lines)
168
+
169
+
170
+ def cmd_words(args):
171
+ if args.translit:
172
+ for name in args.translit:
173
+ if name in vocab.PY2UMSN:
174
+ _say("%s → %s (사전 단어)" % (name, vocab.PY2UMSN[name]))
175
+ else:
176
+ try:
177
+ _say("%s → %s" % (name, transliterate(name)))
178
+ except ValueError as exc:
179
+ _say("엄슨 오류! %s" % exc, err=True)
180
+ return 1
181
+ return 0
182
+ if args.untranslit:
183
+ for word in args.untranslit:
184
+ py = vocab.UMSN2PY.get(word) or vocab.UMSN2SYMBOL.get(word) or untransliterate(word)
185
+ _say("%s → %s" % (word, py if py else "(엄슨 단어/음역이 아닙니다)"))
186
+ return 0
187
+ if args.markdown:
188
+ sys.stdout.write(_markdown_table())
189
+ return 0
190
+ query = (args.search or "").lower()
191
+ for title, um, py in vocab.all_words():
192
+ if query and query not in um.lower() and query not in py.lower() and query not in title.lower():
193
+ continue
194
+ _say("%-18s %-22s %s" % (um, py, title))
195
+ return 0
196
+
197
+
198
+ def build_parser():
199
+ parser = argparse.ArgumentParser(
200
+ prog="pyumsn",
201
+ description="엄슨(UMSN) 프로그래밍 언어 도구 — 엄슨 ↔ 파이썬 변환, 실행, IDE",
202
+ epilog="예) pyumsn run 안녕.umsn | pyumsn topy 안녕.umsn | pyumsn toumsn hello.py | pyumsn ide")
203
+ parser.add_argument("--version", "-V", action="version", version="PyUMSN %s" % __version__)
204
+ sub = parser.add_subparsers(dest="command", metavar="명령")
205
+
206
+ p = sub.add_parser("run", help="엄슨 파일 실행 (임시 파이썬 파일을 만들어 실행)")
207
+ p.add_argument("file", help=".umsn 파일")
208
+ p.add_argument("args", nargs=argparse.REMAINDER, help="프로그램에 넘길 인자")
209
+ p.add_argument("--keep", action="store_true", help="임시 파이썬 파일을 지우지 않음")
210
+ p.add_argument("--show-py", action="store_true", help="변환된 파이썬 코드를 먼저 보여줌")
211
+ p.set_defaults(func=cmd_run)
212
+
213
+ p = sub.add_parser("topy", help="엄슨 → 파이썬 변환 (파일 또는 폴더)")
214
+ p.add_argument("source", help=".umsn 파일 또는 폴더")
215
+ p.add_argument("-o", "--output", help="출력 파일/폴더 ('-' 는 화면)")
216
+ p.set_defaults(func=cmd_topy)
217
+
218
+ p = sub.add_parser("toumsn", help="파이썬 → 엄슨 변환 (파일 또는 폴더)")
219
+ p.add_argument("source", help=".py 파일 또는 폴더")
220
+ p.add_argument("-o", "--output", help="출력 파일/폴더 ('-' 는 화면)")
221
+ p.add_argument("--keep-symbols", action="store_true", help="괄호·연산자 기호는 ASCII 그대로 둠")
222
+ p.set_defaults(func=cmd_toumsn)
223
+
224
+ p = sub.add_parser("check", help="엄슨 파일 검사 (영어 이름 등)")
225
+ p.add_argument("files", nargs="+", help=".umsn 파일")
226
+ p.set_defaults(func=cmd_check)
227
+
228
+ p = sub.add_parser("ide", help="UMSN-IDE 실행")
229
+ p.add_argument("file", nargs="?", help="열 파일")
230
+ p.set_defaults(func=cmd_ide)
231
+
232
+ p = sub.add_parser("repl", help="엄슨 대화형 셸")
233
+ p.set_defaults(func=cmd_repl)
234
+
235
+ p = sub.add_parser("words", help="엄슨 단어장 보기 / 음역")
236
+ p.add_argument("--search", "-s", help="찾을 말 (엄슨 또는 파이썬)")
237
+ p.add_argument("--markdown", action="store_true", help="마크다운 표로 출력")
238
+ p.add_argument("--translit", nargs="+", metavar="영어이름", help="영어 이름 → 엄슨 음역")
239
+ p.add_argument("--untranslit", nargs="+", metavar="엄슨단어", help="엄슨 단어/음역 → 파이썬")
240
+ p.set_defaults(func=cmd_words)
241
+ return parser
242
+
243
+
244
+ def main(argv=None):
245
+ _setup_stdio()
246
+ argv = sys.argv[1:] if argv is None else list(argv)
247
+ if argv and argv[0].lower().endswith(".umsn"):
248
+ argv = ["run"] + argv
249
+ parser = build_parser()
250
+ args = parser.parse_args(argv)
251
+ if not getattr(args, "func", None):
252
+ parser.print_help()
253
+ return 0
254
+ return args.func(args)
255
+
256
+
257
+ if __name__ == "__main__":
258
+ sys.exit(main())
pyumsn/errors.py ADDED
@@ -0,0 +1,75 @@
1
+ """엄슨 오류 클래스."""
2
+
3
+
4
+ class UmsnError(Exception):
5
+ """PyUMSN 의 모든 오류의 부모."""
6
+
7
+
8
+ class UmsnEncodingError(UmsnError):
9
+ """UTF-8 이 아닌 소스 파일."""
10
+
11
+
12
+ class UmsnSyntaxError(UmsnError):
13
+ """엄슨 문법 오류 (영어 이름, 알 수 없는 기호어, 닫히지 않은 문자열 등).
14
+
15
+ ``problems`` 에는 ``(줄번호, 칸번호, 길이, 메시지)`` 목록이 들어 있고,
16
+ 대표 오류는 그중 첫 번째이다. 줄번호는 1부터, 칸번호는 0부터 센다.
17
+ """
18
+
19
+ def __init__(self, problems, filename=None, source=None, incomplete=False):
20
+ if isinstance(problems, str):
21
+ problems = [(None, None, 0, problems)]
22
+ self.problems = list(problems)
23
+ self.filename = filename
24
+ self.source = source
25
+ self.incomplete = incomplete
26
+ first = self.problems[0]
27
+ self.lineno, self.col, self.length, self.msg = first
28
+ super().__init__(self._format())
29
+
30
+ def line_text(self, lineno):
31
+ if self.source is None or lineno is None:
32
+ return None
33
+ lines = self.source.splitlines()
34
+ if 1 <= lineno <= len(lines):
35
+ return lines[lineno - 1]
36
+ return None
37
+
38
+ def _where(self, lineno, col):
39
+ parts = []
40
+ if self.filename:
41
+ parts.append(str(self.filename))
42
+ if lineno is not None:
43
+ parts.append("%d줄 %d칸" % (lineno, (col or 0) + 1))
44
+ return " ".join(parts)
45
+
46
+ def _format(self):
47
+ where = self._where(self.lineno, self.col)
48
+ text = "엄슨 문법 오류"
49
+ if where:
50
+ text += " (%s)" % where
51
+ text += ": " + self.msg
52
+ if len(self.problems) > 1:
53
+ text += " (외 %d개)" % (len(self.problems) - 1)
54
+ return text
55
+
56
+ def report(self):
57
+ """모든 문제를 사람이 읽기 좋게 여러 줄로 만든다."""
58
+ out = []
59
+ for lineno, col, length, msg in self.problems:
60
+ where = self._where(lineno, col)
61
+ out.append("엄슨 문법 오류%s: %s" % (" (%s)" % where if where else "", msg))
62
+ line = self.line_text(lineno)
63
+ if line is not None:
64
+ col = col or 0
65
+ out.append(" " + line.replace("\t", " "))
66
+ pad = _display_width(line[:col].replace("\t", " "))
67
+ mark = max(1, _display_width(line[col:col + (length or 1)]))
68
+ out.append(" " + " " * pad + "^" * mark)
69
+ return "\n".join(out)
70
+
71
+
72
+ def _display_width(text):
73
+ """터미널 표시 폭 (한글 등 전각 문자는 2칸)."""
74
+ import unicodedata
75
+ return sum(2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1 for ch in text)