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 +35 -0
- pyumsn/__main__.py +5 -0
- pyumsn/cli.py +258 -0
- pyumsn/errors.py +75 -0
- pyumsn/ide/umsn_ide.umsn +1269 -0
- pyumsn/importer.py +70 -0
- pyumsn/launcher.py +223 -0
- pyumsn/lexer.py +422 -0
- pyumsn/repl.py +32 -0
- pyumsn/runner.py +118 -0
- pyumsn/sourceio.py +53 -0
- pyumsn/translator.py +393 -0
- pyumsn/vocab.py +949 -0
- pyumsn-1.0.0.dist-info/METADATA +278 -0
- pyumsn-1.0.0.dist-info/RECORD +19 -0
- pyumsn-1.0.0.dist-info/WHEEL +5 -0
- pyumsn-1.0.0.dist-info/entry_points.txt +2 -0
- pyumsn-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyumsn-1.0.0.dist-info/top_level.txt +1 -0
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
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)
|