hython-lang 2.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.
- hython/__init__.py +15 -0
- hython/__main__.py +3 -0
- hython/bytecode.py +368 -0
- hython/cli.py +449 -0
- hython/compiler.py +870 -0
- hython/compiler_manager.py +227 -0
- hython/diagnostics.py +158 -0
- hython/environment.py +48 -0
- hython/exe_builder.py +330 -0
- hython/frontend.py +870 -0
- hython/hir.py +93 -0
- hython/importer.py +76 -0
- hython/package_manager.py +343 -0
- hython/phonetics.py +49 -0
- hython/runtime.py +110 -0
- hython/runtime_manager.py +83 -0
- hython/translator.py +244 -0
- hython/updater.py +93 -0
- hython/vm.py +2409 -0
- hython/vocabulary.py +132 -0
- hython_lang-2.0.0.dist-info/METADATA +327 -0
- hython_lang-2.0.0.dist-info/RECORD +26 -0
- hython_lang-2.0.0.dist-info/WHEEL +5 -0
- hython_lang-2.0.0.dist-info/entry_points.txt +2 -0
- hython_lang-2.0.0.dist-info/licenses/LICENSE +22 -0
- hython_lang-2.0.0.dist-info/top_level.txt +1 -0
hython/frontend.py
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
"""Hython-owned AST and parser for the native compiler path."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
import io
|
|
5
|
+
import token as token_types
|
|
6
|
+
import tokenize
|
|
7
|
+
from .package_manager import load_dictionaries
|
|
8
|
+
from .translator import normalize_literal_prefixes
|
|
9
|
+
from .vocabulary import HYTHON_TO_PYTHON
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Node:
|
|
13
|
+
kind: str
|
|
14
|
+
value: object = None
|
|
15
|
+
children: list["Node"] = field(default_factory=list)
|
|
16
|
+
line: int = 0
|
|
17
|
+
grouped: bool = False
|
|
18
|
+
|
|
19
|
+
class ParseError(SyntaxError): pass
|
|
20
|
+
|
|
21
|
+
def lex(source: str) -> list[tuple[int,str,int,int,int]]:
|
|
22
|
+
source = normalize_literal_prefixes(source)
|
|
23
|
+
names = load_dictionaries() | HYTHON_TO_PYTHON
|
|
24
|
+
result=[]; line_offsets=[]; offset=0
|
|
25
|
+
for source_line in source.splitlines(keepends=True): line_offsets.append(offset); offset+=len(source_line)
|
|
26
|
+
line_offsets.append(offset)
|
|
27
|
+
def absolute(position):
|
|
28
|
+
row,column=position
|
|
29
|
+
return line_offsets[min(row-1,len(line_offsets)-1)]+column
|
|
30
|
+
try: stream=list(tokenize.generate_tokens(io.StringIO(source).readline))
|
|
31
|
+
except Exception as exc: raise ParseError(str(exc)) from exc
|
|
32
|
+
aliases={stream[index+1].string for index,item in enumerate(stream[:-1]) if item.type==tokenize.NAME and item.string in ("as","애즈") and stream[index+1].type==tokenize.NAME}
|
|
33
|
+
try:
|
|
34
|
+
for index,item in enumerate(stream):
|
|
35
|
+
if item.type in (tokenize.ENCODING, tokenize.COMMENT, tokenize.NL): continue
|
|
36
|
+
alias_reference=item.string in aliases and (index==0 or stream[index-1].string!=".")
|
|
37
|
+
text=names.get(item.string,item.string) if item.type==tokenize.NAME and not alias_reference else item.string
|
|
38
|
+
result.append((item.type,text,item.start[0],absolute(item.start),absolute(item.end)))
|
|
39
|
+
except (tokenize.TokenError,IndentationError) as exc: raise ParseError(str(exc)) from exc
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
class Parser:
|
|
43
|
+
def __init__(self, source: str, filename: str = "<하이썬>"):
|
|
44
|
+
self.source=source; self.filename=filename; self.tokens=lex(source); self.pos=0; self.future_features=set()
|
|
45
|
+
for index,token in enumerate(self.tokens):
|
|
46
|
+
if token[1]=="barry_as_FLUFL" and index>0 and self.tokens[index-1][1] in ("import","(",","):
|
|
47
|
+
statement_start=max((i for i in range(index) if self.tokens[i][0]==tokenize.NEWLINE),default=-1)+1
|
|
48
|
+
words=[item[1] for item in self.tokens[statement_start:index]]
|
|
49
|
+
if len(words)>=3 and words[0:3]==["from","__future__","import"]: self.future_features.add("barry_as_FLUFL")
|
|
50
|
+
def current(self): return self.tokens[self.pos] if self.pos<len(self.tokens) else (tokenize.ENDMARKER,"",0,len(self.source),len(self.source))
|
|
51
|
+
def text(self): return self.current()[1]
|
|
52
|
+
def take(self, text=None):
|
|
53
|
+
token=self.current()
|
|
54
|
+
if text is not None and token[1]!=text: raise ParseError(f"'{text}' 필요 (줄 {token[2]})")
|
|
55
|
+
self.pos+=1; return token
|
|
56
|
+
@staticmethod
|
|
57
|
+
def require_grouped_namedexpr(node:Node,line:int,context:str):
|
|
58
|
+
items=node.children if node.kind=="tuple" else [node]
|
|
59
|
+
if any(item.kind=="namedexpr" and not item.grouped for item in items): raise ParseError(f"{context}의 명명식에는 괄호가 필요합니다 (줄 {line})")
|
|
60
|
+
@staticmethod
|
|
61
|
+
def reject_namedexpr(node:Node,line:int,context:str):
|
|
62
|
+
if Parser._contains_kind(node,"namedexpr"): raise ParseError(f"{context}에는 명명식을 사용할 수 없습니다 (줄 {line})")
|
|
63
|
+
@staticmethod
|
|
64
|
+
def validate_unpack_target(node:Node,line:int,star_allowed:bool=False):
|
|
65
|
+
if node.kind=="starred":
|
|
66
|
+
if not star_allowed: raise ParseError(f"별표 대입 대상은 튜플이나 리스트 안에 있어야 합니다 (줄 {line})")
|
|
67
|
+
Parser.validate_unpack_target(node.children[0],line,False); return
|
|
68
|
+
if node.kind in ("tuple","list"):
|
|
69
|
+
if sum(child.kind=="starred" for child in node.children)>1: raise ParseError(f"구조 분해 별표 대상은 한 수준에 하나만 허용됩니다 (줄 {line})")
|
|
70
|
+
for child in node.children: Parser.validate_unpack_target(child,line,True)
|
|
71
|
+
return
|
|
72
|
+
if node.kind not in ("name","attribute","subscript"): raise ParseError(f"잘못된 대입 대상 (줄 {line})")
|
|
73
|
+
@staticmethod
|
|
74
|
+
def validate_delete_target(node:Node,line:int):
|
|
75
|
+
if node.kind in ("tuple","list"):
|
|
76
|
+
for child in node.children: Parser.validate_delete_target(child,line)
|
|
77
|
+
return
|
|
78
|
+
if node.kind not in ("name","attribute","subscript"): raise ParseError(f"잘못된 델 대상 (줄 {line})")
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _contains_kind(node:Node,kind:str) -> bool:
|
|
81
|
+
if node.kind in ("lambda","def","class"): return False
|
|
82
|
+
return node.kind==kind or any(Parser._contains_kind(child,kind) for child in node.children)
|
|
83
|
+
def skip_newlines(self):
|
|
84
|
+
while self.current()[0] in (tokenize.NEWLINE,): self.pos+=1
|
|
85
|
+
def parse(self):
|
|
86
|
+
body=[]; self.skip_newlines()
|
|
87
|
+
while self.current()[0]!=tokenize.ENDMARKER:
|
|
88
|
+
body.append(self.statement()); self.skip_newlines()
|
|
89
|
+
return Node("module",children=body)
|
|
90
|
+
def suite(self):
|
|
91
|
+
self.take(":")
|
|
92
|
+
if self.current()[0]!=tokenize.NEWLINE:
|
|
93
|
+
if self.text() in (";","@","def","async","class","if","while","try","for","with","match"):
|
|
94
|
+
raise ParseError(f"한 줄 suite에는 단순 문장만 사용할 수 있습니다 (줄 {self.current()[2]})")
|
|
95
|
+
line=self.current()[2]; body=[self.statement()]
|
|
96
|
+
while self.current()[0] not in (tokenize.NEWLINE,tokenize.ENDMARKER) and self.current()[2]==line: body.append(self.statement())
|
|
97
|
+
return body
|
|
98
|
+
self.take_newline(); self.expect_type(tokenize.INDENT)
|
|
99
|
+
body=[]; self.skip_newlines()
|
|
100
|
+
while self.current()[0] not in (tokenize.DEDENT,tokenize.ENDMARKER):
|
|
101
|
+
body.append(self.statement()); self.skip_newlines()
|
|
102
|
+
self.expect_type(tokenize.DEDENT); return body
|
|
103
|
+
def take_newline(self):
|
|
104
|
+
if self.text()==";": self.pos+=1; return
|
|
105
|
+
if self.current()[0]!=tokenize.NEWLINE: raise ParseError(f"줄 끝 필요 (줄 {self.current()[2]})")
|
|
106
|
+
self.pos+=1
|
|
107
|
+
def expect_type(self, kind):
|
|
108
|
+
if self.current()[0]!=kind: raise ParseError(f"잘못된 들여쓰기 (줄 {self.current()[2]})")
|
|
109
|
+
self.pos+=1
|
|
110
|
+
def has_suite_colon(self):
|
|
111
|
+
depth=0
|
|
112
|
+
for token in self.tokens[self.pos:]:
|
|
113
|
+
if token[0] in (tokenize.NEWLINE,tokenize.ENDMARKER): return False
|
|
114
|
+
if token[1] in ("(","[","{"): depth+=1
|
|
115
|
+
elif token[1] in (")","]","}"): depth=max(0,depth-1)
|
|
116
|
+
elif token[1]==":" and depth==0: return True
|
|
117
|
+
return False
|
|
118
|
+
def dotted_name(self,allow_relative=False):
|
|
119
|
+
dots=0
|
|
120
|
+
if allow_relative:
|
|
121
|
+
while self.text() in (".","..."):
|
|
122
|
+
marker=self.take()[1]; dots+=len(marker)
|
|
123
|
+
if dots and self.text()=="import": return "."*dots
|
|
124
|
+
parts=[self.take()[1]]
|
|
125
|
+
while self.text()==".": self.take("."); parts.append(self.take()[1])
|
|
126
|
+
return "."*dots+".".join(parts)
|
|
127
|
+
def statement(self):
|
|
128
|
+
line=self.current()[2]; word=self.text()
|
|
129
|
+
if word==";": raise ParseError(f"세미콜론 앞에는 단순 문장이 필요합니다 (줄 {line})")
|
|
130
|
+
if word in ("elif","else","except","finally"):
|
|
131
|
+
raise ParseError(f"앞선 복합문에 연결되지 않은 {word} 절입니다 (줄 {line})")
|
|
132
|
+
if word=="@":
|
|
133
|
+
decorators=[]
|
|
134
|
+
while self.text()=="@":
|
|
135
|
+
self.take("@"); decorators.append(self.expression()); self.take_newline()
|
|
136
|
+
target=self.statement()
|
|
137
|
+
if target.kind not in ("def","class"): raise ParseError(f"데코레이터 뒤에는 함수나 클래스가 필요합니다 (줄 {line})")
|
|
138
|
+
return Node("decorated",target.value,[*decorators,target],line)
|
|
139
|
+
if word=="import":
|
|
140
|
+
self.take(); imports=[]
|
|
141
|
+
while True:
|
|
142
|
+
if self.current()[0]!=tokenize.NAME: raise ParseError(f"import에는 모듈 이름이 필요합니다 (줄 {line})")
|
|
143
|
+
module=self.dotted_name(); alias=module.split(".")[0]
|
|
144
|
+
if self.text()=="as": self.take(); alias=self.take()[1]
|
|
145
|
+
imports.append((module,alias))
|
|
146
|
+
if self.text()!=",": break
|
|
147
|
+
self.take(",")
|
|
148
|
+
self.take_newline(); return Node("imports",imports,line=line)
|
|
149
|
+
if word=="from":
|
|
150
|
+
self.take(); module=self.dotted_name(True); self.take("import"); names=[]
|
|
151
|
+
parenthesized=self.text()=="("
|
|
152
|
+
if parenthesized: self.take("(")
|
|
153
|
+
while True:
|
|
154
|
+
if self.text()=="*":
|
|
155
|
+
if parenthesized: raise ParseError(f"별표 import는 괄호 안에 둘 수 없습니다 (줄 {line})")
|
|
156
|
+
self.take("*"); names.append(("*","*"))
|
|
157
|
+
if self.current()[0]!=tokenize.NEWLINE: raise ParseError(f"별표 import는 별칭이나 다른 이름과 섞을 수 없습니다 (줄 {line})")
|
|
158
|
+
break
|
|
159
|
+
name=self.take()[1]; alias=name
|
|
160
|
+
if self.text()=="as": self.take(); alias=self.take()[1]
|
|
161
|
+
names.append((name,alias))
|
|
162
|
+
if self.text()!=",": break
|
|
163
|
+
self.take(",")
|
|
164
|
+
if parenthesized and self.text()==")": break
|
|
165
|
+
if parenthesized: self.take(")")
|
|
166
|
+
self.take_newline(); return Node("fromimport",(module,names),line=line)
|
|
167
|
+
if word=="global":
|
|
168
|
+
self.take(); names=[]
|
|
169
|
+
while True:
|
|
170
|
+
names.append(self.take()[1])
|
|
171
|
+
if self.text()!=",": break
|
|
172
|
+
self.take(",")
|
|
173
|
+
self.take_newline(); return Node("global",names,line=line)
|
|
174
|
+
if word=="nonlocal":
|
|
175
|
+
self.take(); names=[]
|
|
176
|
+
while True:
|
|
177
|
+
names.append(self.take()[1])
|
|
178
|
+
if self.text()!=",": break
|
|
179
|
+
self.take(",")
|
|
180
|
+
self.take_newline(); return Node("nonlocal",names,line=line)
|
|
181
|
+
if word=="del":
|
|
182
|
+
self.take(); targets=[]
|
|
183
|
+
while True:
|
|
184
|
+
target=self.expression()
|
|
185
|
+
self.validate_delete_target(target,line)
|
|
186
|
+
targets.append(target)
|
|
187
|
+
if self.text()!=",": break
|
|
188
|
+
self.take(",")
|
|
189
|
+
if self.current()[0]==tokenize.NEWLINE: break
|
|
190
|
+
self.take_newline(); return Node("dels",children=targets,line=line)
|
|
191
|
+
if word=="assert":
|
|
192
|
+
self.take(); condition=self.expression(); message=Node("constant",None,line=line)
|
|
193
|
+
self.require_grouped_namedexpr(condition,line,"assert")
|
|
194
|
+
if self.text()==",": self.take(","); message=self.expression()
|
|
195
|
+
self.take_newline(); return Node("assert",children=[condition,message],line=line)
|
|
196
|
+
if word=="type" and self.pos+1<len(self.tokens) and self.tokens[self.pos+1][0]==tokenize.NAME:
|
|
197
|
+
self.take(); name=self.take()[1]; parameters=self.parse_type_parameters()
|
|
198
|
+
self.take("="); start=self.pos; value=self.expression(); self.reject_namedexpr(value,line,"타입 별칭"); value_text="".join(token[1] for token in self.tokens[start:self.pos]); self.take_newline()
|
|
199
|
+
return Node("type_alias",(name,parameters,value_text),[value],line)
|
|
200
|
+
if word=="def": self.take(); return self.function_statement(line,False)
|
|
201
|
+
if word=="async":
|
|
202
|
+
self.take()
|
|
203
|
+
if self.text()=="def": self.take("def"); return self.function_statement(line,True)
|
|
204
|
+
if self.text()=="for":
|
|
205
|
+
self.take("for"); target=self.loop_target(); self.take("in"); iterable=self.for_iterable_list()
|
|
206
|
+
self.require_grouped_namedexpr(iterable,line,"async for iterable")
|
|
207
|
+
body=self.suite(); other=[]
|
|
208
|
+
if self.text()=="else": self.take(); other=self.suite()
|
|
209
|
+
return Node("async_for",target,[iterable,Node("body",children=body),Node("body",children=other)],line)
|
|
210
|
+
if self.text()=="with":
|
|
211
|
+
self.take("with"); managers=self.parse_managers(line)
|
|
212
|
+
return Node("async_with",None,[Node("managers",children=managers),Node("body",children=self.suite())],line)
|
|
213
|
+
raise ParseError(f"어싱크 뒤에는 데프, 포 또는 위드가 필요합니다 (줄 {line})")
|
|
214
|
+
if word=="class":
|
|
215
|
+
self.take(); name=self.take()[1]; type_parameters=self.parse_type_parameters(); bases=[]; keywords=[]; arguments=[]; saw_keyword=False; saw_kwstar=False
|
|
216
|
+
if self.text()=="(":
|
|
217
|
+
self.take("(")
|
|
218
|
+
if self.text()!=")":
|
|
219
|
+
while True:
|
|
220
|
+
if self.text()=="*":
|
|
221
|
+
if saw_kwstar: raise ParseError(f"클래스 ** 키워드 unpack 뒤에는 * base를 둘 수 없습니다 (줄 {line})")
|
|
222
|
+
self.take("*"); value=self.expression(); bases.append(Node("starred",children=[value],line=line)); arguments.append(("star",None,value))
|
|
223
|
+
elif self.text()=="**":
|
|
224
|
+
self.take("**"); value=self.expression(); keywords.append((None,value)); arguments.append(("kwstar",None,value)); saw_keyword=True; saw_kwstar=True
|
|
225
|
+
elif self.current()[0]==tokenize.NAME and self.pos+1<len(self.tokens) and self.tokens[self.pos+1][1]=="=":
|
|
226
|
+
keyword=self.take()[1]
|
|
227
|
+
if any(existing==keyword for existing,_ in keywords): raise ParseError(f"클래스 키워드 인자 중복: {keyword} (줄 {line})")
|
|
228
|
+
self.take("="); value=self.expression(); keywords.append((keyword,value)); arguments.append(("keyword",keyword,value)); saw_keyword=True
|
|
229
|
+
else:
|
|
230
|
+
if saw_keyword: raise ParseError(f"클래스 키워드 인자 뒤에는 일반 base를 둘 수 없습니다 (줄 {line})")
|
|
231
|
+
value=self.expression(); bases.append(value); arguments.append(("base",None,value))
|
|
232
|
+
if self.text()!=",": break
|
|
233
|
+
self.take(",")
|
|
234
|
+
if self.text()==")": break
|
|
235
|
+
self.take(")")
|
|
236
|
+
return Node("class",(name,bases,type_parameters,keywords,arguments),self.suite(),line)
|
|
237
|
+
if word=="if":
|
|
238
|
+
self.take(); return self._if_statement(line)
|
|
239
|
+
if word=="while":
|
|
240
|
+
self.take(); test=self.expression(); body=self.suite(); other=[]
|
|
241
|
+
if self.text()=="else": self.take(); other=self.suite()
|
|
242
|
+
return Node("while",None,[test,Node("body",children=body),Node("body",children=other)],line)
|
|
243
|
+
if word=="try":
|
|
244
|
+
self.take(); body=self.suite(); handlers=[]; otherwise=[]; final=[]
|
|
245
|
+
while self.text()=="except":
|
|
246
|
+
handler_line=self.current()[2]; self.take(); is_star=False
|
|
247
|
+
if self.text()=="*": self.take("*"); is_star=True
|
|
248
|
+
type_expression=None; alias=None
|
|
249
|
+
if self.text()!=":":
|
|
250
|
+
types=[self.expression()]
|
|
251
|
+
while self.text()==",": self.take(","); types.append(self.expression())
|
|
252
|
+
type_expression=Node("tuple",children=types,line=handler_line) if len(types)>1 else types[0]
|
|
253
|
+
if self.text()=="as":
|
|
254
|
+
if len(types)>1: raise ParseError(f"괄호 없는 다중 예외 타입에는 as를 사용할 수 없습니다 (줄 {handler_line})")
|
|
255
|
+
self.take(); alias=self.take()[1]
|
|
256
|
+
elif is_star: raise ParseError(f"익셉트*에는 예외 타입이 필요합니다 (줄 {handler_line})")
|
|
257
|
+
handlers.append(Node("except",(type_expression,alias,is_star),self.suite(),handler_line))
|
|
258
|
+
if self.text()=="else":
|
|
259
|
+
if not handlers: raise ParseError(f"익셉트 없는 트라이에는 엘스를 사용할 수 없습니다 (줄 {line})")
|
|
260
|
+
self.take(); otherwise=self.suite()
|
|
261
|
+
if self.text()=="finally": self.take(); final=self.suite()
|
|
262
|
+
if not handlers and not final: raise ParseError(f"트라이는 익셉트 또는 파이널리가 필요합니다 (줄 {line})")
|
|
263
|
+
if any(handler.value[0] is None for handler in handlers[:-1]): raise ParseError(f"타입 없는 익셉트는 마지막이어야 합니다 (줄 {line})")
|
|
264
|
+
if handlers and any(item.value[2] for item in handlers) and not all(item.value[2] for item in handlers): raise ParseError(f"익셉트와 익셉트*는 같은 트라이에서 섞을 수 없습니다 (줄 {line})")
|
|
265
|
+
return Node("try",None,[Node("body",children=body),Node("handlers",children=handlers),Node("body",children=otherwise),Node("body",children=final)],line)
|
|
266
|
+
if word=="with":
|
|
267
|
+
self.take(); managers=self.parse_managers(line)
|
|
268
|
+
return Node("with",None,[Node("managers",children=managers),Node("body",children=self.suite())],line)
|
|
269
|
+
if word=="match" and self.pos+1<len(self.tokens) and self.tokens[self.pos+1][1] not in ("=",":",".",":=","+=","-=","*=","/=","|=","&=","^=") and self.has_suite_colon():
|
|
270
|
+
self.take(); subject=self.for_iterable_list(); self.take(":"); self.take_newline(); self.expect_type(tokenize.INDENT)
|
|
271
|
+
cases=[]; self.skip_newlines()
|
|
272
|
+
while self.text()=="case":
|
|
273
|
+
case_line=self.current()[2]; self.take(); pattern=self.pattern(True); guard=None
|
|
274
|
+
if self.text()=="if": self.take(); guard=self.expression()
|
|
275
|
+
body=self.suite(); cases.append(Node("case",guard,[pattern,Node("body",children=body)],case_line)); self.skip_newlines()
|
|
276
|
+
self.expect_type(tokenize.DEDENT)
|
|
277
|
+
if not cases: raise ParseError(f"매치에는 케이스가 필요합니다 (줄 {line})")
|
|
278
|
+
return Node("match",children=[subject,*cases],line=line)
|
|
279
|
+
if word=="for":
|
|
280
|
+
self.take(); target=self.loop_target(); self.take("in"); iterable=self.for_iterable_list()
|
|
281
|
+
self.require_grouped_namedexpr(iterable,line,"for iterable")
|
|
282
|
+
body=self.suite(); other=[]
|
|
283
|
+
if self.text()=="else": self.take(); other=self.suite()
|
|
284
|
+
return Node("for",target,[iterable,Node("body",children=body),Node("body",children=other)],line)
|
|
285
|
+
if word=="return":
|
|
286
|
+
self.take(); has_value=self.current()[0]!=tokenize.NEWLINE
|
|
287
|
+
value=self.expression_list() if has_value else Node("constant",None,line=line)
|
|
288
|
+
if has_value: self.require_grouped_namedexpr(value,line,"return")
|
|
289
|
+
self.take_newline(); return Node("return",has_value,children=[value],line=line)
|
|
290
|
+
if word=="yield":
|
|
291
|
+
self.take(); delegated=False
|
|
292
|
+
if self.text()=="from": self.take(); delegated=True
|
|
293
|
+
if delegated and self.current()[0]==tokenize.NEWLINE: raise ParseError(f"yield from에는 피연산자가 필요합니다 (줄 {line})")
|
|
294
|
+
value=Node("constant",None,line=line) if self.current()[0]==tokenize.NEWLINE else self.expression() if delegated else self.expression_list()
|
|
295
|
+
self.require_grouped_namedexpr(value,line,"yield")
|
|
296
|
+
self.take_newline(); return Node("yield_from" if delegated else "yield",children=[value],line=line)
|
|
297
|
+
if word=="raise":
|
|
298
|
+
self.take()
|
|
299
|
+
if self.current()[0]==tokenize.NEWLINE:
|
|
300
|
+
self.take_newline(); return Node("reraise",line=line)
|
|
301
|
+
value=self.expression(); cause=None
|
|
302
|
+
self.require_grouped_namedexpr(value,line,"raise")
|
|
303
|
+
if self.text()=="from": self.take(); cause=self.expression()
|
|
304
|
+
if cause is not None: self.require_grouped_namedexpr(cause,line,"raise from")
|
|
305
|
+
self.take_newline(); return Node("raise",children=[value,*([cause] if cause else [])],line=line)
|
|
306
|
+
if word in ("break","continue"):
|
|
307
|
+
self.take(); self.take_newline(); return Node(word,line=line)
|
|
308
|
+
if word=="pass": self.take(); self.take_newline(); return Node("pass",line=line)
|
|
309
|
+
expression=self.expression()
|
|
310
|
+
if self.text()==":" and expression.kind in ("name","attribute","subscript"):
|
|
311
|
+
self.take(":"); annotation_start=self.pos; annotation=self.expression(1); annotation_text="".join(token[1] for token in self.tokens[annotation_start:self.pos]); value=None
|
|
312
|
+
self.reject_namedexpr(annotation,line,"주석")
|
|
313
|
+
if self.text()=="=": self.take("="); value=self.expression_list()
|
|
314
|
+
if value is not None: self.require_grouped_namedexpr(value,line,"주석 대입")
|
|
315
|
+
self.take_newline(); return Node("annassign",annotation_text,[expression,annotation,*([value] if value else [])],line)
|
|
316
|
+
if self.text()==",":
|
|
317
|
+
values=[expression]
|
|
318
|
+
while self.text()==",":
|
|
319
|
+
self.take(",")
|
|
320
|
+
if self.text()=="=": break
|
|
321
|
+
values.append(self.expression())
|
|
322
|
+
expression=Node("tuple",children=values,line=line)
|
|
323
|
+
if self.text() in ("+=","-=","*=","/=","//=","%=","**=","|=","&=","^=","<<=",">>=","@="):
|
|
324
|
+
if expression.kind not in ("name","attribute","subscript"): raise ParseError(f"잘못된 복합 대입 대상 (줄 {line})")
|
|
325
|
+
op=self.take()[1][:-1]; value=self.expression_list(); self.take_newline()
|
|
326
|
+
self.require_grouped_namedexpr(value,line,"복합 대입")
|
|
327
|
+
return Node("augassign",op,[expression,value],line)
|
|
328
|
+
if self.text()=="=":
|
|
329
|
+
if expression.kind not in ("name","attribute","subscript","tuple","list"): raise ParseError(f"잘못된 대입 대상 (줄 {line})")
|
|
330
|
+
targets=[expression]; self.take("="); value=self.expression_list()
|
|
331
|
+
self.require_grouped_namedexpr(value,line,"대입")
|
|
332
|
+
while self.text()=="=":
|
|
333
|
+
if value.kind not in ("name","attribute","subscript"): raise ParseError(f"잘못된 연쇄 대입 대상 (줄 {line})")
|
|
334
|
+
targets.append(value); self.take("="); value=self.expression_list()
|
|
335
|
+
self.take_newline()
|
|
336
|
+
return Node("assign_chain",children=[*targets,value],line=line) if len(targets)>1 else Node("assign",children=[expression,value],line=line)
|
|
337
|
+
self.require_grouped_namedexpr(expression,line,"표현식 문장")
|
|
338
|
+
self.take_newline(); return Node("expr",children=[expression],line=line)
|
|
339
|
+
|
|
340
|
+
def expression_list(self):
|
|
341
|
+
line=self.current()[2]; values=[self.expression()]; had_comma=False
|
|
342
|
+
while self.text()==",":
|
|
343
|
+
self.take(","); had_comma=True
|
|
344
|
+
if self.current()[0]==tokenize.NEWLINE or self.text() in (")","]","}",":"): break
|
|
345
|
+
values.append(self.expression())
|
|
346
|
+
return Node("tuple",children=values,line=line) if had_comma else values[0]
|
|
347
|
+
|
|
348
|
+
def for_iterable_list(self):
|
|
349
|
+
line=self.current()[2]; values=[self.expression()]; had_comma=False
|
|
350
|
+
while self.text()==",":
|
|
351
|
+
self.take(","); had_comma=True
|
|
352
|
+
if self.text()==":": break
|
|
353
|
+
values.append(self.expression())
|
|
354
|
+
return Node("tuple",children=values,line=line) if had_comma else values[0]
|
|
355
|
+
|
|
356
|
+
def parse_managers(self,line):
|
|
357
|
+
managers=[]; parenthesized=self.text()=="("
|
|
358
|
+
if parenthesized: self.take("(")
|
|
359
|
+
if parenthesized and self.text()==")": self.take(")"); return managers
|
|
360
|
+
while True:
|
|
361
|
+
expression=self.expression(); target=None
|
|
362
|
+
if not parenthesized: self.require_grouped_namedexpr(expression,line,"with")
|
|
363
|
+
if self.text()=="as": self.take(); target=self.expression(); self.validate_unpack_target(target,line)
|
|
364
|
+
managers.append(Node("manager",None,[expression] if target is None else [expression,target],line))
|
|
365
|
+
if self.text()!=",": break
|
|
366
|
+
self.take(",")
|
|
367
|
+
if parenthesized and self.text()==")": break
|
|
368
|
+
if parenthesized: self.take(")")
|
|
369
|
+
return managers
|
|
370
|
+
|
|
371
|
+
def _if_statement(self,line):
|
|
372
|
+
test=self.expression(); body=self.suite(); other=[]
|
|
373
|
+
if self.text()=="elif":
|
|
374
|
+
self.take(); other=[self._if_statement(self.current()[2])]
|
|
375
|
+
elif self.text()=="else":
|
|
376
|
+
self.take(); other=self.suite()
|
|
377
|
+
return Node("if",None,[test,Node("body",children=body),Node("body",children=other)],line)
|
|
378
|
+
|
|
379
|
+
def function_statement(self,line,is_async):
|
|
380
|
+
name=self.take()[1]; type_parameters=self.parse_type_parameters(); self.take("("); params=[]; posonly=[]; kwonly=[]; defaults={}; annotations={}; saw_default=False; vararg=None; kwarg=None; keyword_mode=False
|
|
381
|
+
if self.text()!=")":
|
|
382
|
+
while True:
|
|
383
|
+
if self.text()=="*":
|
|
384
|
+
self.take("*"); keyword_mode=True
|
|
385
|
+
if self.text() not in (",",")"):
|
|
386
|
+
vararg=self.take()[1]
|
|
387
|
+
if self.text()==":":
|
|
388
|
+
self.take(":"); start=self.pos; annotation=self.expression(1); self.reject_namedexpr(annotation,line,"함수 주석"); annotations[vararg]=(annotation,"".join(token[1] for token in self.tokens[start:self.pos]))
|
|
389
|
+
if self.text()==",":
|
|
390
|
+
self.take(",")
|
|
391
|
+
if self.text()==")": break
|
|
392
|
+
continue
|
|
393
|
+
break
|
|
394
|
+
if self.text()=="**":
|
|
395
|
+
self.take("**"); kwarg=self.take()[1]
|
|
396
|
+
if self.text()==":":
|
|
397
|
+
self.take(":"); start=self.pos; annotation=self.expression(1); self.reject_namedexpr(annotation,line,"함수 주석"); annotations[kwarg]=(annotation,"".join(token[1] for token in self.tokens[start:self.pos]))
|
|
398
|
+
if self.text()==",": self.take(",")
|
|
399
|
+
break
|
|
400
|
+
if self.text()=="/":
|
|
401
|
+
if not params or posonly: raise ParseError(f"잘못된 위치 전용 인자 구분자 (줄 {line})")
|
|
402
|
+
self.take("/"); posonly=list(params)
|
|
403
|
+
if self.text()==",":
|
|
404
|
+
self.take(",")
|
|
405
|
+
if self.text()==")": break
|
|
406
|
+
continue
|
|
407
|
+
break
|
|
408
|
+
parameter=self.take()[1]; (kwonly if keyword_mode else params).append(parameter)
|
|
409
|
+
if self.text()==":":
|
|
410
|
+
self.take(":"); start=self.pos; annotation=self.expression(1); self.reject_namedexpr(annotation,line,"함수 주석"); annotations[parameter]=(annotation,"".join(token[1] for token in self.tokens[start:self.pos]))
|
|
411
|
+
if self.text()=="=":
|
|
412
|
+
self.take("="); default=self.expression(); self.require_grouped_namedexpr(default,line,"함수 기본값"); defaults[parameter]=default; saw_default=True
|
|
413
|
+
elif saw_default and not keyword_mode: raise ParseError(f"기본값 없는 인자가 기본값 인자 뒤에 있습니다 (줄 {line})")
|
|
414
|
+
if self.text()!=",": break
|
|
415
|
+
self.take(",")
|
|
416
|
+
if self.text()==")": break
|
|
417
|
+
self.take(")")
|
|
418
|
+
all_parameters=[*params,*kwonly,*([vararg] if vararg else []),*([kwarg] if kwarg else [])]
|
|
419
|
+
duplicate=next((name for name in all_parameters if all_parameters.count(name)>1),None)
|
|
420
|
+
if duplicate is not None: raise ParseError(f"매개변수 이름 중복: {duplicate} (줄 {line})")
|
|
421
|
+
if keyword_mode and vararg is None and not kwonly: raise ParseError(f"단독 * 뒤에는 키워드 전용 매개변수가 필요합니다 (줄 {line})")
|
|
422
|
+
if self.text()=="->":
|
|
423
|
+
self.take("->"); start=self.pos; annotation=self.expression(1); self.reject_namedexpr(annotation,line,"반환 주석"); annotations["return"]=(annotation,"".join(token[1] for token in self.tokens[start:self.pos]))
|
|
424
|
+
return Node("def",(name,params,defaults,vararg,kwarg,kwonly,is_async,posonly,annotations,type_parameters),self.suite(),line)
|
|
425
|
+
|
|
426
|
+
def parse_type_parameters(self):
|
|
427
|
+
parameters=[]
|
|
428
|
+
if self.text()!="[": return parameters
|
|
429
|
+
self.take("[")
|
|
430
|
+
if self.text()=="]": raise ParseError("타입 매개변수 목록은 비어 있을 수 없습니다")
|
|
431
|
+
while self.text()!="]":
|
|
432
|
+
kind="typevar"
|
|
433
|
+
if self.text()=="*": self.take("*"); kind="typevartuple"
|
|
434
|
+
elif self.text()=="**": self.take("**"); kind="paramspec"
|
|
435
|
+
name=self.take()[1]; bound=None; default=None; bound_text=None; default_text=None
|
|
436
|
+
if self.text()==":":
|
|
437
|
+
if kind!="typevar": raise ParseError(f"{name} 타입 매개변수 종류에는 바운드를 사용할 수 없습니다")
|
|
438
|
+
self.take(":"); start=self.pos; bound=self.expression(1); self.reject_namedexpr(bound,bound.line,"타입 매개변수 바운드"); bound_text="".join(token[1] for token in self.tokens[start:self.pos])
|
|
439
|
+
if self.text()=="=":
|
|
440
|
+
self.take("="); start=self.pos; default=self.expression(1); self.reject_namedexpr(default,default.line,"타입 매개변수 기본값"); default_text="".join(token[1] for token in self.tokens[start:self.pos])
|
|
441
|
+
parameters.append({"kind":kind,"name":name,"bound":bound,"default":default,"bound_text":bound_text,"default_text":default_text})
|
|
442
|
+
if self.text()!=",": break
|
|
443
|
+
self.take(",")
|
|
444
|
+
self.take("]")
|
|
445
|
+
names=[item["name"] for item in parameters]
|
|
446
|
+
duplicate=next((name for name in names if names.count(name)>1),None)
|
|
447
|
+
if duplicate is not None: raise ParseError(f"타입 매개변수 이름 중복: {duplicate}")
|
|
448
|
+
saw_default=False
|
|
449
|
+
for item in parameters:
|
|
450
|
+
if item["default"] is not None: saw_default=True
|
|
451
|
+
elif saw_default: raise ParseError("기본값 없는 타입 매개변수가 기본값 매개변수 뒤에 있습니다")
|
|
452
|
+
return parameters
|
|
453
|
+
|
|
454
|
+
def pattern(self,allow_open_sequence=False):
|
|
455
|
+
line=self.current()[2]; patterns=[self.single_pattern()]
|
|
456
|
+
while self.text()=="|": self.take("|"); patterns.append(self.single_pattern())
|
|
457
|
+
result=Node("pattern_or",children=patterns,line=line) if len(patterns)>1 else patterns[0]
|
|
458
|
+
if self.text()=="as": self.take("as"); result=Node("pattern_as",self.take()[1],[result],line)
|
|
459
|
+
if allow_open_sequence and self.text()==",":
|
|
460
|
+
items=[result]
|
|
461
|
+
while self.text()==",":
|
|
462
|
+
self.take(",")
|
|
463
|
+
if self.text() in (":","if"): break
|
|
464
|
+
if self.text()=="*": self.take("*"); items.append(Node("pattern_star",self.take()[1],line=line))
|
|
465
|
+
else: items.append(self.pattern())
|
|
466
|
+
result=Node("pattern_sequence",children=items,line=line)
|
|
467
|
+
return result
|
|
468
|
+
|
|
469
|
+
def single_pattern(self):
|
|
470
|
+
line=self.current()[2]; word=self.text()
|
|
471
|
+
if word=="_": self.take(); return Node("pattern_wildcard",line=line)
|
|
472
|
+
if word in ("[","("):
|
|
473
|
+
opening=word; closing="]" if word=="[" else ")"; self.take(); items=[]; had_comma=False
|
|
474
|
+
if self.text()!=closing:
|
|
475
|
+
while True:
|
|
476
|
+
if self.text()=="*": self.take("*"); items.append(Node("pattern_star",self.take()[1],line=line))
|
|
477
|
+
else: items.append(self.pattern())
|
|
478
|
+
if self.text()!=",": break
|
|
479
|
+
self.take(","); had_comma=True
|
|
480
|
+
if self.text()==closing: break
|
|
481
|
+
self.take(closing)
|
|
482
|
+
if opening=="(" and len(items)==1 and not had_comma: return items[0]
|
|
483
|
+
return Node("pattern_sequence",children=items,line=line)
|
|
484
|
+
if word=="{":
|
|
485
|
+
self.take("{"); pairs=[]; rest=None
|
|
486
|
+
if self.text()!="}":
|
|
487
|
+
while True:
|
|
488
|
+
if self.text()=="**":
|
|
489
|
+
if rest is not None: raise ParseError(f"매핑 패턴의 ** 캡처는 하나만 허용됩니다 (줄 {line})")
|
|
490
|
+
self.take("**"); rest=self.take()[1]
|
|
491
|
+
if rest=="_": raise ParseError(f"매핑 패턴의 **_는 허용되지 않습니다 (줄 {line})")
|
|
492
|
+
if self.text()==",": self.take(",")
|
|
493
|
+
if self.text()!="}": raise ParseError(f"매핑 패턴의 ** 캡처는 마지막이어야 합니다 (줄 {line})")
|
|
494
|
+
break
|
|
495
|
+
else:
|
|
496
|
+
key=self.expression(7); self.take(":"); pairs.append(Node("pattern_pair",children=[key,self.pattern()],line=line))
|
|
497
|
+
if self.text()!=",": break
|
|
498
|
+
self.take(",")
|
|
499
|
+
if self.text()=="}": break
|
|
500
|
+
self.take("}"); return Node("pattern_mapping",rest,pairs,line)
|
|
501
|
+
if self.current()[0] in (tokenize.NUMBER,tokenize.STRING) or word in ("-","+") or word in ("True","False","None"):
|
|
502
|
+
value=self.expression(7)
|
|
503
|
+
def fold_numeric(node):
|
|
504
|
+
if node.kind=="constant": return node.value
|
|
505
|
+
if node.kind=="unary" and node.value in ("-","+"): return -fold_numeric(node.children[0]) if node.value=="-" else +fold_numeric(node.children[0])
|
|
506
|
+
if node.kind=="binary" and node.value in ("+","-"):
|
|
507
|
+
left,right=map(fold_numeric,node.children); return left+right if node.value=="+" else left-right
|
|
508
|
+
raise ValueError
|
|
509
|
+
if value.kind!="constant":
|
|
510
|
+
try: value=Node("constant",fold_numeric(value),line=line)
|
|
511
|
+
except (TypeError,ValueError): pass
|
|
512
|
+
return Node("pattern_literal",children=[value],line=line)
|
|
513
|
+
parts=[self.take()[1]]
|
|
514
|
+
while self.text()==".": self.take("."); parts.append(self.take()[1])
|
|
515
|
+
name=".".join(parts)
|
|
516
|
+
if self.text()=="(":
|
|
517
|
+
self.take("("); positional=[]; keywords=[]; saw_keyword=False
|
|
518
|
+
if self.text()!=")":
|
|
519
|
+
while True:
|
|
520
|
+
if self.current()[0]==tokenize.NAME and self.pos+1<len(self.tokens) and self.tokens[self.pos+1][1]=="=":
|
|
521
|
+
attribute=self.take()[1]; self.take("="); keywords.append(Node("pattern_keyword",attribute,[self.pattern()],line)); saw_keyword=True
|
|
522
|
+
else:
|
|
523
|
+
if saw_keyword: raise ParseError(f"클래스 키워드 패턴 뒤에는 위치 패턴을 둘 수 없습니다 (줄 {line})")
|
|
524
|
+
positional.append(self.pattern())
|
|
525
|
+
if self.text()!=",": break
|
|
526
|
+
self.take(",")
|
|
527
|
+
self.take(")"); return Node("pattern_class",name,[*positional,*keywords],line)
|
|
528
|
+
if len(parts)>1: return Node("pattern_value",name,line=line)
|
|
529
|
+
return Node("pattern_capture",name,line=line)
|
|
530
|
+
|
|
531
|
+
PRECEDENCE={"or":1,"and":2,"|":3,"^":4,"&":5,"==":6,"!=":6,"<>":6,"<":6,"<=":6,">":6,">=":6,"in":6,"is":6,"not in":6,"is not":6,
|
|
532
|
+
"<<":7,">>":7,"+":8,"-":8,"*":9,"/":9,"//":9,"%":9,"@":9,"**":10}
|
|
533
|
+
def expression(self, minimum=0, atom_only=False):
|
|
534
|
+
line=self.current()[2]; word=self.text()
|
|
535
|
+
if word=="not":
|
|
536
|
+
self.take(); left=Node("unary",word,[self.expression(3)],line)
|
|
537
|
+
elif word in ("-","+","~"):
|
|
538
|
+
self.take(); left=Node("unary",word,[self.expression(10)],line)
|
|
539
|
+
elif word=="await":
|
|
540
|
+
self.take(); operand=self.expression(11)
|
|
541
|
+
if operand.kind in ("unary","await","yield_expr","yield_from_expr","lambda","starred","binary","compare_chain","conditional","namedexpr") and not operand.grouped:
|
|
542
|
+
raise ParseError(f"await 뒤에는 primary 표현식 또는 괄호식이 필요합니다 (줄 {line})")
|
|
543
|
+
left=Node("await",children=[operand],line=line)
|
|
544
|
+
elif word=="yield":
|
|
545
|
+
self.take(); delegated=False
|
|
546
|
+
if self.text()=="from": self.take(); delegated=True
|
|
547
|
+
if delegated and self.text() in (")","]",",",":"): raise ParseError(f"yield from에는 피연산자가 필요합니다 (줄 {line})")
|
|
548
|
+
value=Node("constant",None,line=line) if self.text() in (")","]",",",":") else self.expression(1)
|
|
549
|
+
left=Node("yield_from_expr" if delegated else "yield_expr",children=[value],line=line)
|
|
550
|
+
elif word=="*":
|
|
551
|
+
self.take("*"); left=Node("starred",children=[self.expression(10)],line=line)
|
|
552
|
+
elif word=="lambda":
|
|
553
|
+
self.take(); params=[]; posonly=[]; kwonly=[]; defaults={}; vararg=None; kwarg=None; keyword_mode=False; saw_default=False
|
|
554
|
+
if self.text()!=":":
|
|
555
|
+
while True:
|
|
556
|
+
if self.text()=="*":
|
|
557
|
+
self.take("*"); keyword_mode=True
|
|
558
|
+
if self.text() not in (",",":"): vararg=self.take()[1]
|
|
559
|
+
if self.text()==",":
|
|
560
|
+
self.take(",")
|
|
561
|
+
if self.text()==":": break
|
|
562
|
+
continue
|
|
563
|
+
break
|
|
564
|
+
if self.text()=="**":
|
|
565
|
+
self.take("**"); kwarg=self.take()[1]
|
|
566
|
+
if self.text()==",": self.take(",")
|
|
567
|
+
break
|
|
568
|
+
if self.text()=="/":
|
|
569
|
+
if not params or posonly: raise ParseError(f"잘못된 위치 전용 인자 구분자 (줄 {line})")
|
|
570
|
+
self.take("/"); posonly=list(params)
|
|
571
|
+
if self.text()==",":
|
|
572
|
+
self.take(",")
|
|
573
|
+
if self.text()==":": break
|
|
574
|
+
continue
|
|
575
|
+
break
|
|
576
|
+
parameter=self.take()[1]; (kwonly if keyword_mode else params).append(parameter)
|
|
577
|
+
if self.text()=="=":
|
|
578
|
+
self.take("="); default=self.expression(1); self.require_grouped_namedexpr(default,line,"lambda 기본값"); defaults[parameter]=default; saw_default=True
|
|
579
|
+
elif saw_default and not keyword_mode: raise ParseError(f"기본값 없는 인자가 기본값 인자 뒤에 있습니다 (줄 {line})")
|
|
580
|
+
if self.text()!=",": break
|
|
581
|
+
self.take(",")
|
|
582
|
+
if self.text()==":": break
|
|
583
|
+
all_parameters=[*params,*kwonly,*([vararg] if vararg else []),*([kwarg] if kwarg else [])]
|
|
584
|
+
duplicate=next((name for name in all_parameters if all_parameters.count(name)>1),None)
|
|
585
|
+
if duplicate is not None: raise ParseError(f"매개변수 이름 중복: {duplicate} (줄 {line})")
|
|
586
|
+
if keyword_mode and vararg is None and not kwonly: raise ParseError(f"단독 * 뒤에는 키워드 전용 매개변수가 필요합니다 (줄 {line})")
|
|
587
|
+
self.take(":"); signature={"params":params,"posonly":posonly,"kwonly":kwonly,"defaults":defaults,"vararg":vararg,"kwarg":kwarg}
|
|
588
|
+
body=self.expression(1); self.require_grouped_namedexpr(body,line,"lambda 본문"); left=Node("lambda",signature,[body],line)
|
|
589
|
+
elif word=="(":
|
|
590
|
+
self.take()
|
|
591
|
+
if self.text()==")": self.take(")"); left=Node("tuple",children=[],line=line)
|
|
592
|
+
else:
|
|
593
|
+
first=self.expression()
|
|
594
|
+
first.grouped=True
|
|
595
|
+
if self.text() in ("for","async"):
|
|
596
|
+
left=self.comprehension("generatorexpr",first,line,")")
|
|
597
|
+
return self.postfix_and_binary(left,minimum)
|
|
598
|
+
elif self.text()==",":
|
|
599
|
+
values=[first]
|
|
600
|
+
while self.text()==",":
|
|
601
|
+
self.take(",")
|
|
602
|
+
if self.text()==")": break
|
|
603
|
+
values.append(self.expression())
|
|
604
|
+
self.take(")"); left=Node("tuple",children=values,line=line)
|
|
605
|
+
else: self.take(")"); left=first
|
|
606
|
+
elif self.current()[0] in (getattr(tokenize,"FSTRING_START",-1),getattr(tokenize,"TSTRING_START",-4)):
|
|
607
|
+
template=self.current()[0]==getattr(tokenize,"TSTRING_START",-4)
|
|
608
|
+
middle_type=getattr(tokenize,"TSTRING_MIDDLE",-5) if template else getattr(tokenize,"FSTRING_MIDDLE",-3)
|
|
609
|
+
end_type=getattr(tokenize,"TSTRING_END",-6) if template else getattr(tokenize,"FSTRING_END",-2)
|
|
610
|
+
self.take(); parts=[]
|
|
611
|
+
while self.current()[0] != end_type:
|
|
612
|
+
if self.current()[0] == middle_type:
|
|
613
|
+
parts.append(Node("constant",self.take()[1],line=line))
|
|
614
|
+
elif self.text()=="{":
|
|
615
|
+
opening=self.take("{"); expression_start=self.pos; value=self.expression()
|
|
616
|
+
expression_text=self.source[opening[4]:self.tokens[self.pos-1][4]]
|
|
617
|
+
conversion=None; spec=[]; debug_prefix=None; has_spec=False
|
|
618
|
+
if self.text()=="=":
|
|
619
|
+
self.take("=")
|
|
620
|
+
debug_prefix=self.source[opening[4]:self.current()[3]]
|
|
621
|
+
if self.text()=="!":
|
|
622
|
+
self.take("!"); conversion=self.take()[1]
|
|
623
|
+
if conversion not in ("s","r","a"): raise ParseError(f"잘못된 문자열 변환 '!{conversion}' (줄 {line})")
|
|
624
|
+
if self.text()==":":
|
|
625
|
+
self.take(":"); has_spec=True
|
|
626
|
+
while self.text()!="}":
|
|
627
|
+
if self.current()[0] == middle_type: spec.append(Node("constant",self.take()[1],line=line))
|
|
628
|
+
elif self.text()=="{":
|
|
629
|
+
nested_opening=self.take("{"); nested=self.expression(); nested_conversion=None; nested_prefix=None; nested_spec=[]; nested_has_spec=False
|
|
630
|
+
if self.text()=="=":
|
|
631
|
+
self.take("="); nested_prefix=self.source[nested_opening[4]:self.current()[3]]
|
|
632
|
+
if self.text()=="!":
|
|
633
|
+
self.take("!"); nested_conversion=self.take()[1]
|
|
634
|
+
if nested_conversion not in ("s","r","a"): raise ParseError(f"잘못된 문자열 변환 '!{nested_conversion}' (줄 {line})")
|
|
635
|
+
if self.text()==":":
|
|
636
|
+
self.take(":"); nested_has_spec=True
|
|
637
|
+
while self.text()!="}":
|
|
638
|
+
if self.current()[0] == middle_type: nested_spec.append(Node("constant",self.take()[1],line=line))
|
|
639
|
+
elif self.text()=="{":
|
|
640
|
+
deep_opening=self.take("{"); deep=self.expression(); deep_conversion=None; deep_prefix=None; deep_spec=[]; deep_has_spec=False
|
|
641
|
+
if self.text()=="=":
|
|
642
|
+
self.take("="); deep_prefix=self.source[deep_opening[4]:self.current()[3]]
|
|
643
|
+
if self.text()=="!":
|
|
644
|
+
self.take("!"); deep_conversion=self.take()[1]
|
|
645
|
+
if deep_conversion not in ("s","r","a"): raise ParseError(f"잘못된 문자열 변환 '!{deep_conversion}' (줄 {line})")
|
|
646
|
+
if self.text()==":":
|
|
647
|
+
self.take(":"); deep_has_spec=True
|
|
648
|
+
while self.text()!="}":
|
|
649
|
+
if self.current()[0] == middle_type: deep_spec.append(Node("constant",self.take()[1],line=line))
|
|
650
|
+
else: raise ParseError(f"형식 지정자 표현식이 너무 깊게 중첩되었습니다 (줄 {line})")
|
|
651
|
+
if deep_prefix is not None:
|
|
652
|
+
nested_spec.append(Node("constant",deep_prefix,line=line))
|
|
653
|
+
if deep_conversion is None and not deep_has_spec: deep_conversion="r"
|
|
654
|
+
self.take("}")
|
|
655
|
+
deep_children=[deep,*([Node("fstring",children=deep_spec,line=line)] if deep_has_spec else [])]
|
|
656
|
+
nested_spec.append(Node("format",deep_conversion,deep_children,line))
|
|
657
|
+
else: raise ParseError(f"형식 지정자 표현식이 너무 깊게 중첩되었습니다 (줄 {line})")
|
|
658
|
+
if nested_prefix is not None:
|
|
659
|
+
spec.append(Node("constant",nested_prefix,line=line))
|
|
660
|
+
if nested_conversion is None and not nested_has_spec: nested_conversion="r"
|
|
661
|
+
self.take("}")
|
|
662
|
+
nested_children=[nested,*([Node("fstring",children=nested_spec,line=line)] if nested_has_spec else [])]
|
|
663
|
+
spec.append(Node("format",nested_conversion,nested_children,line))
|
|
664
|
+
else: raise ParseError(f"지원하지 않는 f-string 형식 지정자 (줄 {line})")
|
|
665
|
+
if debug_prefix is not None:
|
|
666
|
+
parts.append(Node("constant",debug_prefix,line=line))
|
|
667
|
+
if conversion is None and not has_spec: conversion="r"
|
|
668
|
+
children=[value,*([Node("fstring",children=spec,line=line)] if has_spec else [])]
|
|
669
|
+
parts.append(Node("interpolation",{"conversion":conversion,"expression":expression_text},children,line) if template else Node("format",conversion,children,line)); self.take("}")
|
|
670
|
+
else: raise ParseError(f"지원하지 않는 f-string 형식 (줄 {line})")
|
|
671
|
+
self.take(); left=Node("template" if template else "fstring",children=parts,line=line)
|
|
672
|
+
elif word=="...":
|
|
673
|
+
self.take(); left=Node("constant",Ellipsis,line=line)
|
|
674
|
+
elif self.current()[0] in (tokenize.NUMBER,tokenize.STRING):
|
|
675
|
+
raw=self.take()[1]
|
|
676
|
+
import ast
|
|
677
|
+
value=ast.literal_eval(ast.parse(raw,filename=self.filename,mode="eval").body)
|
|
678
|
+
while self.current()[0]==tokenize.STRING:
|
|
679
|
+
adjacent_raw=self.take()[1]
|
|
680
|
+
adjacent=ast.literal_eval(ast.parse(adjacent_raw,filename=self.filename,mode="eval").body)
|
|
681
|
+
if type(value) is not type(adjacent) or not isinstance(value,(str,bytes)): raise ParseError(f"문자열과 바이트 리터럴은 이어 붙일 수 없습니다 (줄 {line})")
|
|
682
|
+
value+=adjacent
|
|
683
|
+
left=Node("constant",value,line=line)
|
|
684
|
+
elif word in ("True","False","None"):
|
|
685
|
+
self.take(); left=Node("constant",{"True":True,"False":False,"None":None}[word],line=line)
|
|
686
|
+
elif word=="[":
|
|
687
|
+
self.take(); values=[]
|
|
688
|
+
if self.text()!="]":
|
|
689
|
+
first=self.expression()
|
|
690
|
+
if self.text() in ("for","async"):
|
|
691
|
+
left=self.comprehension("listcomp",first,line,"]")
|
|
692
|
+
return self.postfix_and_binary(left,minimum)
|
|
693
|
+
values.append(first)
|
|
694
|
+
while self.text()==",":
|
|
695
|
+
self.take(",")
|
|
696
|
+
if self.text()=="]": break
|
|
697
|
+
values.append(self.expression())
|
|
698
|
+
self.take("]"); left=Node("list",children=values,line=line)
|
|
699
|
+
elif word=="{":
|
|
700
|
+
self.take(); pairs=[]
|
|
701
|
+
if self.text()!="}":
|
|
702
|
+
if self.text()=="**":
|
|
703
|
+
while True:
|
|
704
|
+
if self.text()=="**":
|
|
705
|
+
self.take("**"); unpack=self.expression(); self.require_grouped_namedexpr(unpack,line,"dict unpack"); pairs.append(Node("dict_unpack",children=[unpack],line=line))
|
|
706
|
+
else:
|
|
707
|
+
key=self.expression(); self.take(":"); value=self.expression()
|
|
708
|
+
self.require_grouped_namedexpr(key,line,"dict key"); self.require_grouped_namedexpr(value,line,"dict value")
|
|
709
|
+
pairs.append(Node("pair",children=[key,value],line=line))
|
|
710
|
+
if self.text()!=",": break
|
|
711
|
+
self.take(",")
|
|
712
|
+
if self.text()=="}": break
|
|
713
|
+
self.take("}"); left=Node("dict",children=pairs,line=line)
|
|
714
|
+
return self.postfix_and_binary(left,minimum)
|
|
715
|
+
first=self.expression()
|
|
716
|
+
if self.text()==":":
|
|
717
|
+
self.take(":"); value=self.expression()
|
|
718
|
+
self.require_grouped_namedexpr(first,line,"dict key"); self.require_grouped_namedexpr(value,line,"dict value")
|
|
719
|
+
if self.text() in ("for","async"):
|
|
720
|
+
pair=Node("pair",children=[first,value],line=line)
|
|
721
|
+
left=self.comprehension("dictcomp",pair,line,"}")
|
|
722
|
+
return self.postfix_and_binary(left,minimum)
|
|
723
|
+
pairs.append(Node("pair",children=[first,value],line=line))
|
|
724
|
+
while self.text()==",":
|
|
725
|
+
self.take(",")
|
|
726
|
+
if self.text()=="}": break
|
|
727
|
+
if self.text()=="**":
|
|
728
|
+
self.take("**"); unpack=self.expression(); self.require_grouped_namedexpr(unpack,line,"dict unpack"); pairs.append(Node("dict_unpack",children=[unpack],line=line))
|
|
729
|
+
else:
|
|
730
|
+
key=self.expression(); self.take(":"); value=self.expression()
|
|
731
|
+
self.require_grouped_namedexpr(key,line,"dict key"); self.require_grouped_namedexpr(value,line,"dict value")
|
|
732
|
+
pairs.append(Node("pair",children=[key,value],line=line))
|
|
733
|
+
self.take("}"); left=Node("dict",children=pairs,line=line)
|
|
734
|
+
else:
|
|
735
|
+
values=[first]
|
|
736
|
+
if self.text() in ("for","async"):
|
|
737
|
+
left=self.comprehension("setcomp",first,line,"}")
|
|
738
|
+
return self.postfix_and_binary(left,minimum)
|
|
739
|
+
while self.text()==",":
|
|
740
|
+
self.take(",")
|
|
741
|
+
if self.text()=="}": break
|
|
742
|
+
values.append(self.expression())
|
|
743
|
+
self.take("}"); left=Node("set",children=values,line=line)
|
|
744
|
+
else: self.take("}"); left=Node("dict",children=[],line=line)
|
|
745
|
+
else:
|
|
746
|
+
left=Node("name",self.take()[1],line=line)
|
|
747
|
+
string_starts=(tokenize.STRING,getattr(tokenize,"FSTRING_START",-1),getattr(tokenize,"TSTRING_START",-4))
|
|
748
|
+
def string_node(node): return node.kind in ("fstring","template") or (node.kind=="constant" and isinstance(node.value,(str,bytes)))
|
|
749
|
+
while string_node(left) and self.current()[0] in string_starts:
|
|
750
|
+
right=self.expression(100,True)
|
|
751
|
+
if not string_node(right): raise ParseError(f"잘못된 인접 문자열 리터럴 (줄 {line})")
|
|
752
|
+
if "template" in (left.kind,right.kind):
|
|
753
|
+
if left.kind!="template" or right.kind!="template": raise ParseError(f"t-string은 다른 문자열 리터럴과 섞을 수 없습니다 (줄 {line})")
|
|
754
|
+
left=Node("template",children=[*left.children,*right.children],line=line)
|
|
755
|
+
continue
|
|
756
|
+
if (left.kind=="constant" and isinstance(left.value,bytes)) or (right.kind=="constant" and isinstance(right.value,bytes)):
|
|
757
|
+
if left.kind!="constant" or right.kind!="constant" or not isinstance(left.value,bytes) or not isinstance(right.value,bytes): raise ParseError(f"바이트와 문자열 리터럴은 섞을 수 없습니다 (줄 {line})")
|
|
758
|
+
left=Node("constant",left.value+right.value,line=line); continue
|
|
759
|
+
left_parts=left.children if left.kind=="fstring" else [Node("constant",left.value,line=line)]
|
|
760
|
+
right_parts=right.children if right.kind=="fstring" else [Node("constant",right.value,line=line)]
|
|
761
|
+
left=Node("fstring",children=[*left_parts,*right_parts],line=line)
|
|
762
|
+
if atom_only: return left
|
|
763
|
+
return self.postfix_and_binary(left,minimum)
|
|
764
|
+
|
|
765
|
+
def comprehension(self,kind,element,line,closing):
|
|
766
|
+
clauses=[]
|
|
767
|
+
while self.text() in ("for","async"):
|
|
768
|
+
is_async=self.text()=="async"
|
|
769
|
+
if is_async: self.take("async")
|
|
770
|
+
self.take("for"); target=self.loop_target(); self.take("in"); iterable=self.expression(1)
|
|
771
|
+
filters=[]
|
|
772
|
+
while self.text()=="if": self.take(); filters.append(self.expression(1))
|
|
773
|
+
clauses.append(Node("comp_clause",(target,is_async),[iterable,*filters],line))
|
|
774
|
+
self.take(closing)
|
|
775
|
+
return Node(kind,None,[element,*clauses],line)
|
|
776
|
+
|
|
777
|
+
def loop_target(self):
|
|
778
|
+
line=self.current()[2]; first=self.expression(7); values=[first]; had_comma=False
|
|
779
|
+
while self.text()==",":
|
|
780
|
+
self.take(","); had_comma=True
|
|
781
|
+
if self.text()=="in": break
|
|
782
|
+
values.append(self.expression(7))
|
|
783
|
+
target=Node("tuple",children=values,line=line) if had_comma else first
|
|
784
|
+
if target.kind not in ("name","tuple","list","starred","attribute","subscript"): raise ParseError(f"잘못된 반복 대입 대상 (줄 {line})")
|
|
785
|
+
self.validate_unpack_target(target,line)
|
|
786
|
+
return target
|
|
787
|
+
|
|
788
|
+
def subscript_item(self,line):
|
|
789
|
+
start=Node("constant",None,line=line) if self.text()==":" else self.expression()
|
|
790
|
+
if self.text()!=":": return start
|
|
791
|
+
if start.kind=="starred": raise ParseError(f"slice 경계에는 별표 표현식을 사용할 수 없습니다 (줄 {line})")
|
|
792
|
+
self.take(":")
|
|
793
|
+
stop=Node("constant",None,line=line) if self.text() in (":",",","]") else self.expression()
|
|
794
|
+
if stop.kind=="starred": raise ParseError(f"slice 경계에는 별표 표현식을 사용할 수 없습니다 (줄 {line})")
|
|
795
|
+
step=Node("constant",None,line=line)
|
|
796
|
+
if self.text()==":":
|
|
797
|
+
self.take(":"); step=Node("constant",None,line=line) if self.text() in (",","]") else self.expression()
|
|
798
|
+
if step.kind=="starred": raise ParseError(f"slice 경계에는 별표 표현식을 사용할 수 없습니다 (줄 {line})")
|
|
799
|
+
return Node("slice",children=[start,stop,step],line=line)
|
|
800
|
+
|
|
801
|
+
def postfix_and_binary(self,left,minimum):
|
|
802
|
+
line=left.line
|
|
803
|
+
while self.text() in ("(", ".", "["):
|
|
804
|
+
if self.text()=="(":
|
|
805
|
+
self.take(); args=[]; descriptors=[]; closed=False; saw_keyword=False; saw_kwstar=False
|
|
806
|
+
if self.text()!=")":
|
|
807
|
+
while True:
|
|
808
|
+
if self.current()[0]==tokenize.NAME and self.pos+1<len(self.tokens) and self.tokens[self.pos+1][1]=="=":
|
|
809
|
+
keyword=self.take()[1]
|
|
810
|
+
if any(item[0]=="keyword" and item[1]==keyword for item in descriptors): raise ParseError(f"키워드 인자 중복: {keyword} (줄 {line})")
|
|
811
|
+
self.take("="); value=self.expression(); self.require_grouped_namedexpr(value,line,"키워드 인자"); args.append(value); descriptors.append(["keyword",keyword]); saw_keyword=True
|
|
812
|
+
elif self.text()=="*":
|
|
813
|
+
if saw_kwstar: raise ParseError(f"** 키워드 unpack 뒤에는 * 위치 unpack을 둘 수 없습니다 (줄 {line})")
|
|
814
|
+
self.take("*"); args.append(self.expression()); descriptors.append(["star",None])
|
|
815
|
+
elif self.text()=="**":
|
|
816
|
+
self.take("**"); args.append(self.expression()); descriptors.append(["kwstar",None]); saw_keyword=True; saw_kwstar=True
|
|
817
|
+
else:
|
|
818
|
+
if saw_keyword: raise ParseError(f"키워드 인자 뒤에는 일반 위치 인자를 둘 수 없습니다 (줄 {line})")
|
|
819
|
+
argument=self.expression()
|
|
820
|
+
if self.text() in ("for","async"):
|
|
821
|
+
argument=self.comprehension("generatorexpr",argument,line,")"); closed=True
|
|
822
|
+
args.append(argument); descriptors.append(["positional",None])
|
|
823
|
+
if closed: break
|
|
824
|
+
if self.text()!=",": break
|
|
825
|
+
self.take(",")
|
|
826
|
+
if self.text()==")": break
|
|
827
|
+
if not closed: self.take(")")
|
|
828
|
+
left=Node("call",descriptors,[left,*args],line=line)
|
|
829
|
+
elif self.text()==".":
|
|
830
|
+
self.take("."); left=Node("attribute",self.take()[1],[left],line)
|
|
831
|
+
else:
|
|
832
|
+
self.take("[")
|
|
833
|
+
if self.text() in ("]",","): raise ParseError(f"첨자에는 하나 이상의 항목이 필요합니다 (줄 {line})")
|
|
834
|
+
values=[self.subscript_item(line)]; had_comma=False
|
|
835
|
+
while self.text()==",":
|
|
836
|
+
self.take(","); had_comma=True
|
|
837
|
+
if self.text()=="]": break
|
|
838
|
+
values.append(self.subscript_item(line))
|
|
839
|
+
index=Node("tuple",children=values,line=line) if had_comma or values[0].kind=="starred" else values[0]
|
|
840
|
+
self.take("]")
|
|
841
|
+
left=Node("subscript",children=[left,index],line=line)
|
|
842
|
+
while True:
|
|
843
|
+
compound=None
|
|
844
|
+
if self.text()=="is" and self.pos+1<len(self.tokens) and self.tokens[self.pos+1][1]=="not": compound="is not"
|
|
845
|
+
if self.text()=="not" and self.pos+1<len(self.tokens) and self.tokens[self.pos+1][1]=="in": compound="not in"
|
|
846
|
+
candidate=compound or self.text()
|
|
847
|
+
if candidate not in self.PRECEDENCE or self.PRECEDENCE[candidate]<minimum: break
|
|
848
|
+
if candidate=="!=" and "barry_as_FLUFL" in self.future_features: raise ParseError(f"barry_as_FLUFL에서는 != 대신 <>를 사용해야 합니다 (줄 {line})")
|
|
849
|
+
if candidate=="<>" and "barry_as_FLUFL" not in self.future_features: break
|
|
850
|
+
if compound: self.take(); self.take(); op=compound
|
|
851
|
+
else: op=self.take()[1]
|
|
852
|
+
if op=="<>": op="!="
|
|
853
|
+
precedence=self.PRECEDENCE[op]
|
|
854
|
+
right=self.expression(precedence+(0 if op=="**" else 1))
|
|
855
|
+
comparisons={"==","!=","<","<=",">",">=","in","is","not in","is not"}
|
|
856
|
+
if op in comparisons:
|
|
857
|
+
if left.kind=="compare_chain": left.value.append(op); left.children.append(right)
|
|
858
|
+
else: left=Node("compare_chain",[op],[left,right],line)
|
|
859
|
+
else: left=Node("binary",op,[left,right],line)
|
|
860
|
+
if left.kind=="compare_chain" and len(left.value)==1:
|
|
861
|
+
left=Node("binary",left.value[0],left.children,line)
|
|
862
|
+
if minimum<=0 and self.text()==":=":
|
|
863
|
+
if left.kind!="name": raise ParseError(f"바다코끼리 대입 대상은 이름이어야 합니다 (줄 {line})")
|
|
864
|
+
self.take(":="); left=Node("namedexpr",left.value,[self.expression()],line)
|
|
865
|
+
if minimum<=0 and self.text()=="if":
|
|
866
|
+
self.take(); condition=self.expression(); self.take("else"); otherwise=self.expression()
|
|
867
|
+
left=Node("conditional",children=[condition,left,otherwise],line=line)
|
|
868
|
+
return left
|
|
869
|
+
|
|
870
|
+
def parse(source: str, filename: str = "<하이썬>") -> Node: return Parser(source,filename).parse()
|