Skip to content

Commit 8e5f541

Browse files
Merge pull request #60 from MarcellPerger1/add-list-literals
Add list literals, fix pow bug
2 parents 26f8b19 + 737de65 commit 8e5f541

File tree

12 files changed

+1248
-984
lines changed

12 files changed

+1248
-984
lines changed

main_example_1.st

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
global[] stack = list();
1+
global[] stack = [];
22
global COUNT_REFS = false;
33

44
// opcodes from python version 3.8:

parser/astgen/ast_node.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"AstNode", "AstProgramNode", "VarDeclScope", "VarType", "AstDeclNode",
1010
"AstRepeat", "AstIf", "AstWhile", "AstAssign", "AstAugAssign", "AstDefine",
1111
"AstNumber", "AstString", "AstAnyName", "AstIdent", "AstAttrName",
12-
"AstAttribute", "AstItem", "AstCall", "AstOp", "AstBinOp", "AstUnaryOp",
12+
"AstListLiteral", "AstAttribute", "AstItem", "AstCall", "AstOp", "AstBinOp",
13+
"AstUnaryOp",
1314
]
1415

1516

@@ -129,6 +130,12 @@ class AstAttrName(AstAnyName):
129130
name = 'attr'
130131

131132

133+
@dataclass
134+
class AstListLiteral(AstNode):
135+
name = 'list'
136+
items: list[AstNode]
137+
138+
132139
@dataclass
133140
class AstAttribute(AstNode):
134141
name = '.'

parser/astgen/astgen.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ def _walk_attr_name(self, attr_name: AttrNameNode) -> AstAttrName:
195195
return AstAttrName(
196196
attr_name.region, self.node_str(attr_name, intern=True))
197197

198+
@_register_autowalk_expr
199+
def _walk_list_literal(self, ls: ListNode):
200+
# For now it is UB to use this anywhere but in variable decls
201+
# (until proper syntax desugar-ing for literal arrays is done).
202+
# Anyway, for now the codegen/typechecker/desugar step will check this
203+
return AstListLiteral(ls.region, [self._walk_expr(i) for i in ls.items])
204+
198205
@_register_autowalk_expr
199206
def _walk_getattr(self, node: GetattrNode) -> AstAttribute:
200207
return AstAttribute(node.region, self._walk_expr(node.target),

parser/cst/cstgen.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,32 @@ def _parse_parens_or(self, idx: int) -> tuple[AnyNode, int]:
412412
idx = self._expect_cls_consume(
413413
idx, RParToken, f"Expected ')' at end of expr, got {self[idx].name}")
414414
return ParenNode(self.tok_region(start, idx), None, [inner]), idx
415+
elif isinstance(self[idx], LSqBracket):
416+
return self._parse_list_literal(idx)
415417
return self._parse_atom_or_autocat(idx)
416418

419+
def _parse_list_literal(self, idx: int) -> tuple[AnyNode, int]:
420+
start = idx
421+
assert self.matches(idx, LSqBracket)
422+
idx += 1
423+
if self.matches(idx, RSqBracket):
424+
idx += 1 # simple case, no args
425+
return ListNode(self.tok_region(start, idx)), idx
426+
arg1, idx = self._parse_expr(idx)
427+
args = [arg1]
428+
while not self.matches(idx, RSqBracket):
429+
if not self.matches(idx, CommaToken): # if not end, must be comma
430+
raise self.err(f"Expected ',' or ']' after item in list"
431+
f" literal, got {self[idx].name}", self[idx])
432+
idx += 1
433+
if self.matches(idx, RSqBracket): # trailing comma, no value
434+
break
435+
arg, idx = self._parse_expr(idx)
436+
args.append(arg)
437+
assert self.matches(idx, RSqBracket)
438+
idx += 1
439+
return ListNode(self.tok_region(start, idx), None, args), idx
440+
417441
def _parse_basic_item(self, idx: int):
418442
left, new_idx = self._parse_parens_or(idx)
419443
while idx != (idx := new_idx): # If progress made, set old to current and loop again
@@ -476,7 +500,8 @@ def _parse_pow_or(self, idx: int) -> tuple[AnyNode, int]:
476500
idx += 1
477501
# parse_unary_or -> parse_pow_or -> parse_unary_or -> parse_pow_or
478502
# This is right-recursion so is fine as progress will be made each call to get here
479-
return self._parse_unary_or(idx)
503+
right, idx = self._parse_unary_or(idx)
504+
return self.node_from_children(PowNode, [leftmost, right]), idx
480505

481506
def _parse_unary_or(self, idx: int) -> tuple[AnyNode, int]:
482507
unaries, idx = self._parse_unaries_into_tok_list(idx)

parser/cst/nodes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"AutocatNode", # autocat is sorta atom-ish (it is in AST but not in CST?)
1414
# Item chains
1515
"GetattrNode", "GetitemNode", "ParenNode", "CallNode", "CallArgs",
16+
"ListNode",
1617
# Operators
1718
"OperatorNode",
1819
"UnaryOpNode", "UPlusNode", "UMinusNode", "NotNode", # Unary operators
@@ -122,6 +123,14 @@ def contents(self):
122123
return self.children[0]
123124

124125

126+
class ListNode(NamedNodeCls):
127+
name = 'list' # List literal, varargs
128+
129+
@property
130+
def items(self):
131+
return self.children
132+
133+
125134
class CallNode(NamedSizedNodeCls):
126135
name = 'call'
127136
size = 2

test/common/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from parser.common.error import BaseParseError
1212
from parser.common.tree_print import tformat
1313
from parser.cst.base_node import Leaf, AnyNode, Node
14-
from parser.cst.cstgen import CstGen, CstParseError
14+
from parser.cst.cstgen import CstGen, LocatedCstError
1515
from parser.lexer import Tokenizer
1616
from parser.lexer.tokens import Token, OpToken
1717
from test.common.snapshottest import SnapshotTestCase
@@ -87,7 +87,7 @@ def assertValidParseCST(self, src: str):
8787

8888
def assertFailsGracefullyCST(self, src: str):
8989
t = CstGen(Tokenizer(src))
90-
with self.assertRaises(CstParseError) as ctx:
90+
with self.assertRaises(LocatedCstError) as ctx:
9191
t.parse()
9292
return ctx.exception
9393

test/test_astgen/.snapshots/test_astgen.txt

Lines changed: 69 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/test_astgen/test_astgen.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,12 @@ def test_decl(self):
5252
'global d = "STRING", e;\n'
5353
'let[] local_list=list(), other;\n'
5454
'global[] STACK;')
55+
56+
def test_list_literal_decl(self):
57+
self.assertAstMatchesSnapshot('let[] loc = [5, 6.,], b, c=[];\n'
58+
'global [] STACK = [foo(bar), 8];')
59+
60+
def test_list_literal_decl_paren(self):
61+
self.assertAstMatchesSnapshot('let[] a = ([1]);')
62+
# We only test for lists in variable decls (as them being allowed
63+
# elsewhere is UB for now).

test/test_cstgen/.snapshots/test_cstgen.txt

Lines changed: 115 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/test_cstgen/test_cstgen.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ def test_autocat(self):
1414
def test_mod_supported(self):
1515
self.assertCstMatchesSnapshot('c=a%b;')
1616

17+
def test_pow(self):
18+
self.assertCstMatchesSnapshot('a**b;')
19+
1720

1821
class TestItemChain(CommonTestCase):
1922
def test_item_chain(self):
@@ -24,11 +27,15 @@ def test_fn_call_in_lvalue(self):
2427

2528
def test_empty_sqb_error(self):
2629
with self.assertRaises(LocatedCstError) as err:
27-
CstGen(Tokenizer('v=a[]+b')).parse()
30+
CstGen(Tokenizer('v=a[]+b;')).parse()
2831
exc = err.exception
2932
self.assertBetweenIncl(3, 4, exc.region.start)
3033
self.assertEqual(4, exc.region.end - 1)
3134

35+
def test_empty_getitem_comma_error(self):
36+
exc = self.assertFailsGracefullyCST('v=a[1,]+b;')
37+
self.assertEqual(StrRegion(5, 6), exc.region)
38+
3239
def test_getattr__issue_09(self):
3340
t = Tokenizer('fn(call_arg).a;').tokenize()
3441
node = CstGen(t).parse()
@@ -50,6 +57,23 @@ def test_getitem__issue_09(self):
5057
t = Tokenizer('"a string"["key_" .. 3];').tokenize()
5158
node = CstGen(t).parse()
5259
self.assertMatchesSnapshot(node, 'after_string')
60+
61+
def test_list_literal_empty(self):
62+
self.assertCstMatchesSnapshot('let a = [];\n'
63+
'join([], a, [].length);')
64+
self.assertFailsGracefullyCST('let b = [,];')
65+
66+
def test_list_literal_single_item(self):
67+
self.assertCstMatchesSnapshot('["a"].method([-2**--6,]);')
68+
69+
def test_list_literal_multi_item(self):
70+
self.assertCstMatchesSnapshot(
71+
'global[] STACK = ["int", 0];\n'
72+
'(STACK + ["float", .2,]).extend([6.6, 1, fn()[0], 2][-1]);\n'
73+
'v+=[1,2](3);')
74+
e = self.assertFailsGracefullyCST('[1;')
75+
self.assertEqual(StrRegion(2, 3), e.region)
76+
self.assertContains(e.msg.lower(), "after item in list")
5377
# endregion </Test Expressions>
5478

5579

0 commit comments

Comments
 (0)