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/importer.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""``.umsn`` 모듈 불러오기 (import hook).
|
|
2
|
+
|
|
3
|
+
``엄슨가져와 내모듈`` 은 ``sys.path`` 에서 ``내모듈.umsn`` (또는 ``내모듈/__init__.umsn``) 을 찾는다.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import importlib.abc
|
|
7
|
+
import importlib.util
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from .sourceio import read_source
|
|
12
|
+
from .translator import umsn_to_py
|
|
13
|
+
|
|
14
|
+
SUFFIX = ".umsn"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UmsnLoader(importlib.abc.Loader):
|
|
18
|
+
def __init__(self, path):
|
|
19
|
+
self.path = path
|
|
20
|
+
|
|
21
|
+
def create_module(self, spec):
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
def exec_module(self, module):
|
|
25
|
+
source = read_source(self.path)
|
|
26
|
+
py = umsn_to_py(source, filename=self.path)
|
|
27
|
+
try:
|
|
28
|
+
compile(py, "<엄슨 %s>" % self.path, "exec")
|
|
29
|
+
except SyntaxError as exc:
|
|
30
|
+
raise syntax_error_to_umsn(exc, source, self.path) from None
|
|
31
|
+
code = compile(py, self.path, "exec")
|
|
32
|
+
module.__file__ = self.path
|
|
33
|
+
exec(code, module.__dict__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class UmsnFinder(importlib.abc.MetaPathFinder):
|
|
37
|
+
def find_spec(self, fullname, path, target=None):
|
|
38
|
+
name = fullname.rpartition(".")[2]
|
|
39
|
+
search = sys.path if path is None else path
|
|
40
|
+
for entry in list(search):
|
|
41
|
+
if not isinstance(entry, str):
|
|
42
|
+
continue
|
|
43
|
+
base = entry or os.getcwd()
|
|
44
|
+
pkg_init = os.path.join(base, name, "__init__" + SUFFIX)
|
|
45
|
+
if os.path.isfile(pkg_init):
|
|
46
|
+
return importlib.util.spec_from_file_location(
|
|
47
|
+
fullname, pkg_init, loader=UmsnLoader(pkg_init),
|
|
48
|
+
submodule_search_locations=[os.path.join(base, name)])
|
|
49
|
+
candidate = os.path.join(base, name + SUFFIX)
|
|
50
|
+
if os.path.isfile(candidate):
|
|
51
|
+
return importlib.util.spec_from_file_location(
|
|
52
|
+
fullname, candidate, loader=UmsnLoader(candidate))
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def syntax_error_to_umsn(exc, umsn_source, filename):
|
|
57
|
+
"""파이썬 SyntaxError 를 엄슨 줄·칸 기준의 UmsnSyntaxError 로."""
|
|
58
|
+
from .errors import UmsnSyntaxError
|
|
59
|
+
from .translator import PositionMap
|
|
60
|
+
col = None
|
|
61
|
+
if exc.lineno and exc.offset:
|
|
62
|
+
col = PositionMap(umsn_source).umsn_col(exc.lineno, exc.offset - 1)
|
|
63
|
+
return UmsnSyntaxError([(exc.lineno, col, 1, "파이썬 문법 오류: %s" % exc.msg)],
|
|
64
|
+
filename=filename, source=umsn_source)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def install():
|
|
68
|
+
"""import hook 을 한 번만 설치한다."""
|
|
69
|
+
if not any(isinstance(f, UmsnFinder) for f in sys.meta_path):
|
|
70
|
+
sys.meta_path.append(UmsnFinder())
|
pyumsn/launcher.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""하위 프로세스 진입점.
|
|
2
|
+
|
|
3
|
+
python -X utf8 -m pyumsn.launcher <임시.py> <원본.umsn> [인자...]
|
|
4
|
+
|
|
5
|
+
임시 파이썬 파일을 원본 ``.umsn`` 파일 이름으로 컴파일해서 실행한다.
|
|
6
|
+
변환 전후 줄 번호가 같으므로 오류 트레이스백이 엄슨 소스 줄을 그대로 보여준다.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import builtins
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import traceback
|
|
13
|
+
import types
|
|
14
|
+
|
|
15
|
+
_SYNTAX_HINTS = [
|
|
16
|
+
("expected ':'", "'..한' (:) 이 빠졌슨"),
|
|
17
|
+
("invalid syntax", "문법이 잘못됐슨"),
|
|
18
|
+
("unexpected indent", "들여쓰기가 이상하슨"),
|
|
19
|
+
("expected an indented block", "들여쓴 블록이 필요하슨"),
|
|
20
|
+
("unindent does not match", "내어쓰기 위치가 맞지 않슨"),
|
|
21
|
+
("was never closed", "괄호가 닫히지 않았슨"),
|
|
22
|
+
("unmatched", "짝이 없는 괄호가 있슨"),
|
|
23
|
+
("unterminated string", "문자열이 닫히지 않았슨"),
|
|
24
|
+
("cannot assign", "여기에는 값을 넣을 수 없슨"),
|
|
25
|
+
("Perhaps you forgot a comma", "'..고' (,) 를 빠뜨린 것 같슨"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _setup_stdio():
|
|
30
|
+
for stream in (sys.stdin, sys.stdout, sys.stderr):
|
|
31
|
+
try:
|
|
32
|
+
stream.reconfigure(encoding="utf-8")
|
|
33
|
+
except (AttributeError, ValueError, OSError):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _korean_name(exc_type):
|
|
38
|
+
from . import vocab
|
|
39
|
+
from .translator import transliterate
|
|
40
|
+
name = exc_type.__name__
|
|
41
|
+
if name in vocab.PY2UMSN:
|
|
42
|
+
return vocab.PY2UMSN[name]
|
|
43
|
+
try:
|
|
44
|
+
return transliterate(name)
|
|
45
|
+
except ValueError:
|
|
46
|
+
return name
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
_MAPS = {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _position_map(filename):
|
|
53
|
+
"""엄슨 파일의 PositionMap (없거나 엄슨 파일이 아니면 None)."""
|
|
54
|
+
if not filename.lower().endswith(".umsn"):
|
|
55
|
+
return None
|
|
56
|
+
if filename not in _MAPS:
|
|
57
|
+
try:
|
|
58
|
+
from .sourceio import read_source
|
|
59
|
+
from .translator import PositionMap
|
|
60
|
+
_MAPS[filename] = PositionMap(read_source(filename))
|
|
61
|
+
except Exception:
|
|
62
|
+
_MAPS[filename] = None
|
|
63
|
+
return _MAPS[filename]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _width(text):
|
|
67
|
+
import unicodedata
|
|
68
|
+
return sum(2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1 for ch in text)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _caret_line(line, start, end):
|
|
72
|
+
stripped = line.lstrip()
|
|
73
|
+
indent = len(line) - len(stripped)
|
|
74
|
+
if start is None or end is None or end <= start or start < indent:
|
|
75
|
+
return None
|
|
76
|
+
if start == indent and end >= len(line.rstrip()):
|
|
77
|
+
return None
|
|
78
|
+
return " " + " " * _width(line[indent:start]) + "^" * max(1, _width(line[start:end]))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _byte_to_char(line, col):
|
|
82
|
+
if col is None:
|
|
83
|
+
return None
|
|
84
|
+
return len(line.encode("utf-8")[:col].decode("utf-8", "replace"))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _format_frame(fs):
|
|
88
|
+
import linecache
|
|
89
|
+
out = [' File "%s", line %s, in %s\n' % (fs.filename, fs.lineno, fs.name)]
|
|
90
|
+
line = linecache.getline(fs.filename, fs.lineno).rstrip("\r\n")
|
|
91
|
+
if not line.strip():
|
|
92
|
+
return out
|
|
93
|
+
out.append(" %s\n" % line.strip())
|
|
94
|
+
colno = getattr(fs, "colno", None)
|
|
95
|
+
end_colno = getattr(fs, "end_colno", None)
|
|
96
|
+
if colno is None or end_colno is None or getattr(fs, "end_lineno", fs.lineno) != fs.lineno:
|
|
97
|
+
return out
|
|
98
|
+
pmap = _position_map(fs.filename)
|
|
99
|
+
if pmap is not None:
|
|
100
|
+
py_line = pmap.py_line(fs.lineno)
|
|
101
|
+
start = pmap.umsn_col(fs.lineno, _byte_to_char(py_line, colno))
|
|
102
|
+
end = pmap.umsn_col(fs.lineno, _byte_to_char(py_line, end_colno), end=True)
|
|
103
|
+
else:
|
|
104
|
+
start, end = _byte_to_char(line, colno), _byte_to_char(line, end_colno)
|
|
105
|
+
caret = _caret_line(line, start, end)
|
|
106
|
+
if caret:
|
|
107
|
+
out.append(caret + "\n")
|
|
108
|
+
return out
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _report_syntax(exc, umsn_path):
|
|
112
|
+
hint = None
|
|
113
|
+
for key, text in _SYNTAX_HINTS:
|
|
114
|
+
if key in (exc.msg or ""):
|
|
115
|
+
hint = text
|
|
116
|
+
break
|
|
117
|
+
col = None
|
|
118
|
+
line = None
|
|
119
|
+
pmap = _position_map(umsn_path)
|
|
120
|
+
if pmap is not None and exc.lineno:
|
|
121
|
+
lines = pmap.umsn_src.splitlines()
|
|
122
|
+
if 1 <= exc.lineno <= len(lines):
|
|
123
|
+
line = lines[exc.lineno - 1]
|
|
124
|
+
if exc.offset:
|
|
125
|
+
col = pmap.umsn_col(exc.lineno, exc.offset - 1)
|
|
126
|
+
where = "%s %s줄" % (umsn_path, exc.lineno)
|
|
127
|
+
if col is not None:
|
|
128
|
+
where += " %d칸" % (col + 1)
|
|
129
|
+
sys.stderr.write("엄슨 오류! 문법 오류 (%s): %s\n" % (where, hint or exc.msg))
|
|
130
|
+
if hint:
|
|
131
|
+
sys.stderr.write(" (파이썬: %s)\n" % exc.msg)
|
|
132
|
+
if line is not None:
|
|
133
|
+
sys.stderr.write(" %s\n" % line)
|
|
134
|
+
if col is not None:
|
|
135
|
+
sys.stderr.write(" %s^\n" % (" " * _width(line[:col])))
|
|
136
|
+
sys.stderr.flush()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _report(exc, _seen=None, outer=True):
|
|
140
|
+
_seen = _seen if _seen is not None else set()
|
|
141
|
+
_seen.add(id(exc))
|
|
142
|
+
if exc.__cause__ is not None and id(exc.__cause__) not in _seen:
|
|
143
|
+
_report(exc.__cause__, _seen, False)
|
|
144
|
+
sys.stderr.write("\n위 예외가 아래 예외의 직접적인 원인이었슨:\n\n")
|
|
145
|
+
elif (exc.__context__ is not None and not exc.__suppress_context__
|
|
146
|
+
and id(exc.__context__) not in _seen):
|
|
147
|
+
_report(exc.__context__, _seen, False)
|
|
148
|
+
sys.stderr.write("\n위 예외를 처리하는 동안 또 다른 예외가 일어났슨:\n\n")
|
|
149
|
+
tb = exc.__traceback__
|
|
150
|
+
here = os.path.abspath(__file__)
|
|
151
|
+
while tb is not None and os.path.abspath(tb.tb_frame.f_code.co_filename) == here:
|
|
152
|
+
tb = tb.tb_next
|
|
153
|
+
sys.stderr.write("엄슨 추적 기록 (가장 최근 호출이 마지막):\n")
|
|
154
|
+
for fs in traceback.extract_tb(tb):
|
|
155
|
+
for piece in _format_frame(fs):
|
|
156
|
+
sys.stderr.write(piece)
|
|
157
|
+
for line in traceback.format_exception_only(type(exc), exc):
|
|
158
|
+
sys.stderr.write(line)
|
|
159
|
+
if outer:
|
|
160
|
+
sys.stderr.write("엄슨 오류! %s (%s): %s\n" % (_korean_name(type(exc)), type(exc).__name__, exc))
|
|
161
|
+
sys.stderr.flush()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main(argv=None):
|
|
165
|
+
argv = sys.argv[1:] if argv is None else list(argv)
|
|
166
|
+
if len(argv) < 2:
|
|
167
|
+
sys.stderr.write("사용법: python -m pyumsn.launcher <임시.py> <원본.umsn> [인자...]\n")
|
|
168
|
+
return 2
|
|
169
|
+
_setup_stdio()
|
|
170
|
+
from .importer import install
|
|
171
|
+
from .sourceio import read_source
|
|
172
|
+
|
|
173
|
+
py_path, umsn_path = argv[0], os.path.abspath(argv[1])
|
|
174
|
+
source = read_source(py_path)
|
|
175
|
+
if not os.environ.pop("PYUMSN_KEEP_TEMP", None):
|
|
176
|
+
try:
|
|
177
|
+
os.remove(py_path) # 이미 읽었으니 임시 파일은 바로 지운다
|
|
178
|
+
except OSError:
|
|
179
|
+
pass
|
|
180
|
+
sys.argv = [umsn_path] + argv[2:]
|
|
181
|
+
if sys.path and sys.path[0] in ("", os.getcwd()):
|
|
182
|
+
sys.path[0] = os.path.dirname(umsn_path)
|
|
183
|
+
else:
|
|
184
|
+
sys.path.insert(0, os.path.dirname(umsn_path))
|
|
185
|
+
install()
|
|
186
|
+
|
|
187
|
+
# 문법 검사는 임시 .py 이름으로 한다: 실제 .umsn 파일 이름을 주면 파이썬이
|
|
188
|
+
# 오류 칸 번호를 (파이썬 코드가 아닌) 엄슨 줄 기준으로 다시 계산해 버린다.
|
|
189
|
+
try:
|
|
190
|
+
compile(source, py_path, "exec")
|
|
191
|
+
except SyntaxError as exc:
|
|
192
|
+
_report_syntax(exc, umsn_path)
|
|
193
|
+
return 1
|
|
194
|
+
code = compile(source, umsn_path, "exec")
|
|
195
|
+
|
|
196
|
+
keep_alive = sys.modules.get("__main__") # noqa: F841 (실행 중인 모듈 유지)
|
|
197
|
+
module = types.ModuleType("__main__")
|
|
198
|
+
module.__file__ = umsn_path
|
|
199
|
+
module.__builtins__ = builtins
|
|
200
|
+
sys.modules["__main__"] = module
|
|
201
|
+
try:
|
|
202
|
+
exec(code, module.__dict__)
|
|
203
|
+
except SystemExit:
|
|
204
|
+
raise
|
|
205
|
+
except KeyboardInterrupt:
|
|
206
|
+
sys.stderr.write("\n엄슨 중단! (Ctrl+C)\n")
|
|
207
|
+
return 130
|
|
208
|
+
except BaseException as exc: # noqa: B902
|
|
209
|
+
from .errors import UmsnError, UmsnSyntaxError
|
|
210
|
+
if isinstance(exc, UmsnSyntaxError):
|
|
211
|
+
sys.stderr.write(exc.report() + "\n")
|
|
212
|
+
elif isinstance(exc, UmsnError):
|
|
213
|
+
sys.stderr.write("엄슨 오류! %s\n" % exc)
|
|
214
|
+
else:
|
|
215
|
+
_report(exc)
|
|
216
|
+
return 1
|
|
217
|
+
finally:
|
|
218
|
+
sys.stdout.flush()
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
if __name__ == "__main__":
|
|
223
|
+
sys.exit(main())
|