Skip to content

Commit 05f1134

Browse files
Merge pull request #52 from MarcellPerger1/add-list-decl
Add list declarations
2 parents 12e11ac + af1f6d4 commit 05f1134

File tree

12 files changed

+565
-191
lines changed

12 files changed

+565
-191
lines changed

detect_unused.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vulture ./parser ./main.py ./fuzz.py ./test --ignore-decorators="@_register_autowalk_expr*" --exclude="parser/astgen/ast_print.py"

parser/astgen/ast_node.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
from ..common import HasRegion, StrRegion
77

88
__all__ = [
9-
"AstNode", "AstProgramNode", "VarDeclType", "AstDeclNode", "AstRepeat",
10-
"AstIf", "AstWhile", "AstAssign", "AstAugAssign", "AstDefine", "AstNumber",
11-
"AstString", "AstAnyName", "AstIdent", "AstAttrName", "AstAttribute",
12-
"AstItem", "AstCall", "AstOp", "AstBinOp", "AstUnaryOp",
9+
"AstNode", "AstProgramNode", "VarDeclScope", "VarType", "AstDeclNode",
10+
"AstRepeat", "AstIf", "AstWhile", "AstAssign", "AstAugAssign", "AstDefine",
11+
"AstNumber", "AstString", "AstAnyName", "AstIdent", "AstAttrName",
12+
"AstAttribute", "AstItem", "AstCall", "AstOp", "AstBinOp", "AstUnaryOp",
1313
]
1414

1515

@@ -27,15 +27,21 @@ class AstProgramNode(AstNode):
2727

2828

2929
# region ---- <Statements> ----
30-
class VarDeclType(Enum):
30+
class VarDeclScope(Enum):
3131
LET = 'let'
3232
GLOBAL = 'global'
3333

3434

35+
class VarType(Enum):
36+
VARIABLE = 'variable'
37+
LIST = 'list'
38+
39+
3540
@dataclass
3641
class AstDeclNode(AstNode):
3742
name = 'var_decl'
38-
type: VarDeclType
43+
scope: VarDeclScope
44+
type: VarType
3945
decls: list[tuple[AstIdent, AstNode | None]]
4046

4147

parser/astgen/astgen.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
WhileBlock,
2727
RepeatBlock,
2828
DefineNode,
29-
LetNode,
30-
GlobalNode,
29+
DeclNode,
3130
AssignOpNode,
3231
)
3332

@@ -101,10 +100,8 @@ def _walk_program(self, root: ProgramNode):
101100
def _walk_smt(self, smt: AnyNode) -> list[AstNode]:
102101
if isinstance(smt, NopNode):
103102
return []
104-
elif isinstance(smt, LetNode):
105-
return self._walk_var_decl(smt, VarDeclType.LET)
106-
elif isinstance(smt, GlobalNode):
107-
return self._walk_var_decl(smt, VarDeclType.GLOBAL)
103+
elif isinstance(smt, DeclNode):
104+
return self._walk_var_decl(smt)
108105
elif isinstance(smt, RepeatBlock):
109106
return [AstRepeat(smt.region, self._walk_expr(smt.count),
110107
self._walk_block(smt.block))]
@@ -136,11 +133,15 @@ def _walk_smt(self, smt: AnyNode) -> list[AstNode]:
136133
f"expressions have no side-effect so are not allowed at "
137134
f"the root level.", smt.region)
138135

139-
def _walk_var_decl(self, smt: LetNode | GlobalNode, decl_tp: VarDeclType):
136+
def _walk_var_decl(self, smt: DeclNode):
140137
decls = [(self._walk_ident(d.ident),
141138
None if d.value is None else self._walk_expr(d.value))
142-
for d in smt.decls]
143-
return [AstDeclNode(smt.region, decl_tp, decls)]
139+
for d in smt.decl_list.decls]
140+
scope = (VarDeclScope.LET if isinstance(smt.decl_scope, DeclScope_Let)
141+
else VarDeclScope.GLOBAL)
142+
tp = (VarType.LIST if isinstance(smt.decl_type, DeclType_List)
143+
else VarType.VARIABLE)
144+
return [AstDeclNode(smt.region, scope, tp, decls)]
144145

145146
def _walk_assign_left(self, lhs: AnyNode) -> AstNode:
146147
if isinstance(lhs, IdentNode):

parser/cst/named_node.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import TYPE_CHECKING, overload, Sequence, cast, Literal
55

66
from .base_node import Leaf, AnyNode, Node
7-
from ..common import StrRegion
7+
from ..common import StrRegion, HasRegion
88

99
if TYPE_CHECKING:
1010
from ..tokens import Token
@@ -26,7 +26,7 @@ def __post_init__(self):
2626

2727
# noinspection PyMethodOverriding
2828
@classmethod
29-
def of(cls, token: Token, parent: Node | None = None):
29+
def of(cls, token: HasRegion, parent: Node | None = None):
3030
return cls(token.region, parent)
3131

3232

@@ -49,7 +49,7 @@ def __init__(self, region: StrRegion, parent: Node | None = None,
4949

5050
# noinspection PyMethodOverriding
5151
@classmethod
52-
def of(cls, token: Token, parent: Node | None = None):
52+
def of(cls, token: HasRegion, parent: Node | None = None):
5353
return cls(token.region, parent)
5454

5555

@@ -70,7 +70,7 @@ def __post_init__(self):
7070

7171
# noinspection PyMethodOverriding
7272
@classmethod
73-
def of(cls, token: Token, children: list[AnyNode] | None = None,
73+
def of(cls, token: HasRegion, children: list[AnyNode] | None = None,
7474
parent: Node | None = None):
7575
return cls(token.region, parent, children or [])
7676

parser/cst/nodes.py

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,38 @@
66
from .named_node import (NamedLeafCls, NamedNodeCls, NamedSizedNodeCls,
77
register_corresponding_token)
88

9-
__all__ = [
10-
"ProgramNode", "NumberNode", "StringNode", "AnyNameLeaf", "IdentNode",
11-
"AttrNameNode", "AutocatNode", "GetattrNode", "GetitemNode", "ParenNode",
12-
"CallNode", "CallArgs", "OperatorNode", "UnaryOpNode", "UPlusNode",
13-
"UMinusNode", "NotNode", "BinOpNode", "AddNode", "SubNode", "MulNode",
14-
"DivNode", "ModNode", "PowNode", "ConcatNode", "AndNode", "OrNode",
9+
__all__ = [ # Keep these sorted by category
10+
"ProgramNode", "AnyNullNode",
11+
# Atoms
12+
"NumberNode", "StringNode", "AnyNameLeaf", "IdentNode", "AttrNameNode",
13+
"AutocatNode", # autocat is sorta atom-ish (it is in AST but not in CST?)
14+
# Item chains
15+
"GetattrNode", "GetitemNode", "ParenNode", "CallNode", "CallArgs",
16+
# Operators
17+
"OperatorNode",
18+
"UnaryOpNode", "UPlusNode", "UMinusNode", "NotNode", # Unary operators
19+
# Binary operators
20+
"BinOpNode", "AddNode", "SubNode", "MulNode", "DivNode", "ModNode",
21+
"PowNode", "ConcatNode", "AndNode", "OrNode",
22+
# Comparisons
1523
"ComparisonNode", "EqNode", "NeqNode", "LtNode", "LeNode", "GtNode",
16-
"GeNode", "NopNode", "BlockNode", "ConditionalBlock", "IfBlock",
17-
"ElseIfBlock", "ElseBlock", "NullElseBlock", "WhileBlock", "RepeatBlock",
18-
"DefineNode", "ArgsDeclNode", "ArgDeclNode", "DeclItemNode", "LetNode",
19-
"GlobalNode", "AssignOpNode", "AssignNode", "AddEqNode", "SubEqNode",
20-
"MulEqNode", "DivEqNode", "ModEqNode", "PowEqNode", "ConcatEqNode",
21-
"AndEqNode", "OrEqNode",
24+
"GeNode",
25+
26+
"NopNode",
27+
# Blocks
28+
"BlockNode", "WhileBlock", "RepeatBlock",
29+
# If blocks & related infrastructure
30+
"ConditionalBlock", "IfBlock", "ElseIfBlock", "ElseBlock", "NullElseBlock",
31+
# Function Definitions
32+
"DefineNode", "ArgsDeclNode", "ArgDeclNode",
33+
# Variable declarations & related infrastructure
34+
"DeclNode", "DeclItemsList", "DeclItemNode",
35+
"DeclScopeNode", "DeclScope_Let", "DeclScope_Global",
36+
"DeclTypeNode", "DeclType_Variable", "DeclType_List",
37+
# Assignment (regular and augmented)
38+
"AssignOpNode", "AssignNode",
39+
"AddEqNode", "SubEqNode", "MulEqNode", "DivEqNode", "ModEqNode",
40+
"PowEqNode", "ConcatEqNode", "AndEqNode", "OrEqNode",
2241
]
2342

2443

@@ -30,6 +49,11 @@ def statements(self):
3049
return self.children
3150

3251

52+
class AnyNullNode(NamedLeafCls):
53+
"""For nodes whose presence denotes an absence of something in the syntax"""
54+
size = 0 # Name should be set by subclasses
55+
56+
3357
# region ---- Expressions ----
3458
@register_corresponding_token
3559
class NumberNode(NamedLeafCls):
@@ -305,9 +329,8 @@ def block(self):
305329
return checked_cast(BlockNode, self.children[0])
306330

307331

308-
class NullElseBlock(NamedLeafCls):
332+
class NullElseBlock(AnyNullNode):
309333
name = 'else_null'
310-
size = 0
311334

312335

313336
class WhileBlock(NamedSizedNodeCls):
@@ -375,6 +398,7 @@ def ident(self):
375398
# endregion
376399

377400

401+
# region ---- Variable Decls ----
378402
class DeclItemNode(NamedNodeCls):
379403
name = 'decl_item' # 1 or 2 (name and optional value)
380404

@@ -389,20 +413,58 @@ def value(self):
389413
return self.children[1]
390414

391415

392-
class LetNode(NamedNodeCls):
393-
name = 'let_decl' # Varargs
416+
class DeclScopeNode(AnyNullNode):
417+
pass
418+
419+
420+
# noinspection PyPep8Naming
421+
class DeclScope_Let(DeclScopeNode):
422+
name = 'scope__let'
423+
424+
425+
# noinspection PyPep8Naming
426+
class DeclScope_Global(DeclScopeNode):
427+
name = 'scope__global'
428+
429+
430+
class DeclTypeNode(AnyNullNode):
431+
pass
432+
433+
434+
# noinspection PyPep8Naming
435+
class DeclType_Variable(DeclTypeNode):
436+
name = 'decl_type__variable'
437+
438+
439+
# noinspection PyPep8Naming
440+
class DeclType_List(DeclTypeNode):
441+
name = 'decl_type__list'
442+
443+
444+
class DeclNode(NamedSizedNodeCls):
445+
name = 'var_decl'
446+
size = 3 # scope, type (value/list), decl_list
394447

395448
@property
396-
def decls(self):
397-
return cast(list[DeclItemNode], self.children)
449+
def decl_scope(self):
450+
return checked_cast(DeclScopeNode, self.children[0])
398451

452+
@property
453+
def decl_type(self):
454+
return checked_cast(DeclTypeNode, self.children[1])
455+
456+
@property
457+
def decl_list(self):
458+
return cast(DeclItemsList, self.children[2])
399459

400-
class GlobalNode(NamedNodeCls):
401-
name = 'global_decl' # Varargs
460+
461+
class DeclItemsList(NamedNodeCls):
462+
name = 'decl_list'
402463

403464
@property
404465
def decls(self):
405466
return cast(list[DeclItemNode], self.children)
467+
# endregion
406468

407469

408470
# region ---- Assignment-ops ----

parser/cst/treegen.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,8 @@ def _parse_smt(self, idx: int) -> tuple[AnyNode, int]:
100100
smt, idx = self._parse_while(idx)
101101
elif self.matches(idx, KwdM('repeat')):
102102
smt, idx = self._parse_repeat(idx)
103-
elif self.matches(idx, (KwdM('global'), IdentNameToken)):
104-
smt, idx = self._parse_global(idx)
105-
elif self.matches(idx, (KwdM('let'), IdentNameToken)):
106-
smt, idx = self._parse_let(idx)
103+
elif self.matches(idx, KwdM('let')) or self.matches(idx, KwdM('global')):
104+
smt, idx = self._parse_decl(idx) # Could be faster ^^
107105
elif self.matches(idx, SemicolonToken):
108106
smt = NopNode(self.tok_region(idx, idx + 1))
109107
idx += 1
@@ -132,37 +130,43 @@ def _parse_expr_or_assign(self, idx: int) -> tuple[AnyNode, int]:
132130
raise self.err(f"Expected semicolon at end of expr, "
133131
f"got {self[idx].name}", self[idx])
134132

135-
def _parse_let(self, start: int) -> tuple[AnyNode, int]:
136-
idx = start
137-
assert self.matches(idx, KwdM('let'))
138-
idx += 1
139-
items, idx = self._parse_decl_item_list(idx)
133+
def _parse_decl(self, idx: int): # e.g. global [] foo=bar, ...;
134+
scope, idx = self._parse_decl_scope(idx) # ~~~~~^ ^^ ~~^~~~~~~~~~
135+
tp_node, idx = self._parse_decl_sqb_or(idx) # ~/ |
136+
decl_items, idx = self._parse_decl_item_list(idx) # ~~~/
140137
if not self.matches(idx, SemicolonToken):
141138
raise self.err(f"Expected ';' or ',' after decl_item,"
142139
f" got {self[idx].name}", self[idx])
143140
idx += 1
144-
return LetNode(self.tok_region(start, idx), None, items), idx
141+
return self.node_from_children(DeclNode, [scope, tp_node, decl_items]), idx
142+
143+
def _parse_decl_scope(self, idx: int) -> tuple[AnyNode, int]:
144+
if self.matches(idx, KwdM('let')):
145+
return DeclScope_Let.of(self[idx]), idx + 1
146+
if self.matches(idx, KwdM('global')):
147+
return DeclScope_Global.of(self[idx]), idx + 1
148+
assert 0, "Unknown decl scope"
149+
150+
def _parse_decl_sqb_or(self, idx: int) -> tuple[AnyNode, int]:
151+
if not isinstance(self[idx], LSqBracket):
152+
loc = self[idx - 1].region.end # Have to give region, so say char after 'let'
153+
return DeclType_Variable(StrRegion(loc, loc)), idx
154+
sqb_start = idx
155+
idx += 1
156+
if not isinstance(self[idx], RSqBracket):
157+
raise self.err("Expected ']' after '[' in list decl (eg. 'let[] a;')", self[idx])
158+
idx += 1
159+
return DeclType_List(self.tok_region(sqb_start, idx)), idx
145160

146-
def _parse_decl_item_list(self, idx):
161+
def _parse_decl_item_list(self, idx: int) -> tuple[AnyNode, int]:
147162
items = []
148163
while True:
149-
glob, idx = self._parse_decl_item(idx)
150-
items.append(glob)
164+
item, idx = self._parse_decl_item(idx)
165+
items.append(item)
151166
if not self.matches(idx, CommaToken):
152167
break
153168
idx += 1
154-
return items, idx
155-
156-
def _parse_global(self, start: int) -> tuple[AnyNode, int]:
157-
idx = start
158-
assert self.matches(idx, KwdM('global'))
159-
idx += 1
160-
items, idx = self._parse_decl_item_list(idx)
161-
if not self.matches(idx, SemicolonToken):
162-
raise self.err(f"Expected ';' or ',' after decl_item,"
163-
f" got {self[idx].name}", self[idx])
164-
idx += 1
165-
return GlobalNode(self.tok_region(start, idx), None, items), idx
169+
return self.node_from_children(DeclItemsList, items), idx
166170

167171
def _parse_decl_item(self, start: int) -> tuple[AnyNode, int]:
168172
idx = start

0 commit comments

Comments
 (0)