Skip to content

Commit ea465ed

Browse files
committed
Fix subshell counter
1 parent f66d048 commit ea465ed

File tree

2 files changed

+57
-187
lines changed

2 files changed

+57
-187
lines changed

SOLUTION/solution.py

Lines changed: 35 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -87,104 +87,45 @@ def step3_unparse(ast):
8787

8888
##
8989
## Step 4:
90-
## Use our simple feature counter to count different shell scripts.
91-
##
92-
## Find a (POSIX) shell script you use frequently (or pull one from GitHub) and see what features it uses.
93-
##
94-
95-
96-
def feature_counter():
97-
features = [
98-
"background",
99-
"subshell",
100-
"home_tilde",
101-
"$((arithmetic))",
102-
"eval",
103-
"alias",
104-
"while",
105-
"for",
106-
"case",
107-
"if",
108-
"and",
109-
"or",
110-
"negate",
111-
"heredoc_redir",
112-
"dup_redir",
113-
"file_redir",
114-
"$(substitution)",
115-
"function",
116-
"assignment",
117-
"variable_use",
118-
"pipeline",
119-
"command",
120-
]
121-
feature_counts = {name: 0 for name in features}
122-
123-
def count_features(node):
124-
match node:
125-
case AST.BackgroundNode():
126-
feature_counts["background"] += 1
127-
case AST.PipeNode():
128-
feature_counts["pipeline"] += 1
129-
if node.is_background:
130-
feature_counts["background"] += 1
131-
case AST.SubshellNode():
132-
feature_counts["subshell"] += 1
133-
case AST.TArgChar():
134-
feature_counts["home_tilde"] += 1
135-
case AST.AArgChar():
136-
feature_counts["$((arithmetic))"] += 1
137-
case AST.BArgChar():
138-
feature_counts["$(substitution)"] += 1
139-
case AST.VArgChar():
140-
feature_counts["variable_use"] += 1
141-
case AST.AssignNode():
142-
feature_counts["assignment"] += 1
143-
case AST.DefunNode():
144-
feature_counts["function"] += 1
145-
case AST.WhileNode():
146-
feature_counts["while"] += 1
147-
case AST.ForNode():
148-
feature_counts["for"] += 1
149-
case AST.CaseNode():
150-
feature_counts["case"] += 1
151-
case AST.IfNode():
152-
feature_counts["if"] += 1
153-
case AST.AndNode():
154-
feature_counts["and"] += 1
155-
case AST.OrNode():
156-
feature_counts["or"] += 1
157-
case AST.NotNode():
158-
feature_counts["negate"] += 1
159-
case AST.HeredocRedirNode():
160-
feature_counts["heredoc_redir"] += 1
161-
case AST.DupRedirNode():
162-
feature_counts["dup_redir"] += 1
163-
case AST.FileRedirNode():
164-
feature_counts["file_redir"] += 1
165-
case AST.CommandNode():
166-
feature_counts["command"] += 1
167-
if node.arguments:
168-
cmd_name = AST.string_of_arg(node.arguments[0])
169-
# NB this is conservative---we're detecting static uses of these constructs
170-
if cmd_name == "eval":
171-
feature_counts["eval"] += 1
172-
elif cmd_name == "alias":
173-
feature_counts["alias"] += 1
174-
case _:
175-
pass
176-
return node
177-
178-
return (count_features, feature_counts)
179-
90+
## Create a simple analysis that returns the number of subshells a script will create:
91+
## Four ways to create a subshell:
92+
## - Asynchronous commands `&`
93+
## - Pipes `|`
94+
## - Subshells `(...)`
95+
## - Command Substitution `$(...)`
96+
##
97+
## Find a (POSIX) shell script you use frequently (or pull one from binpash/koala) and see how many it creates.
98+
##
99+
100+
class Counter:
101+
def __init__(self):
102+
self.cnt = 0
103+
104+
def add(self, n):
105+
self.cnt += n
106+
107+
def get(self):
108+
return self.cnt
180109

181110
def step4_subshells(ast):
182111
show_step("4: counting shell features")
183112

184-
(counter, counts) = feature_counter()
185-
walk_ast(ast, visit=counter) # REPLACE # FILL IN HERE WITH CALL to `walk_ast` with the `counter` as `visit`
186-
for (feature, count) in (counts.items()): # REPLACE # FILL IN HERE WITH LOOP to print out the features (HINT: use the `dict.items` method)
187-
print(f"- {feature}: {count}") # REMOVE
113+
subshells = Counter()
114+
def count_features(node): # REMOVE
115+
match node: # REMOVE
116+
case AST.BackgroundNode(): # REMOVE
117+
subshells.add(1) # REMOVE
118+
case AST.PipeNode(): # REMOVE
119+
subshells.add(len(node.items)) # REMOVE
120+
case AST.SubshellNode(): # REMOVE
121+
subshells.add(1) # REMOVE
122+
case AST.BArgChar(): # REMOVE
123+
subshells.add(1) # REMOVE
124+
case _: # REMOVE
125+
pass # REMOVE
126+
return node # REMOVE
127+
walk_ast(ast, visit=count_features) # REPLACE # FILL IN HERE WITH CALL to `walk_ast` that counts subshells
128+
print("Number of subshells in script:", subshells.get())
188129

189130
##
190131
## Step 5:

src/solution.py

Lines changed: 22 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -87,103 +87,32 @@ def step3_unparse(ast):
8787

8888
##
8989
## Step 4:
90-
## Use our simple feature counter to count different shell scripts.
91-
##
92-
## Find a (POSIX) shell script you use frequently (or pull one from GitHub) and see what features it uses.
93-
##
94-
95-
96-
def feature_counter():
97-
features = [
98-
"background",
99-
"subshell",
100-
"home_tilde",
101-
"$((arithmetic))",
102-
"eval",
103-
"alias",
104-
"while",
105-
"for",
106-
"case",
107-
"if",
108-
"and",
109-
"or",
110-
"negate",
111-
"heredoc_redir",
112-
"dup_redir",
113-
"file_redir",
114-
"$(substitution)",
115-
"function",
116-
"assignment",
117-
"variable_use",
118-
"pipeline",
119-
"command",
120-
]
121-
feature_counts = {name: 0 for name in features}
122-
123-
def count_features(node):
124-
match node:
125-
case AST.BackgroundNode():
126-
feature_counts["background"] += 1
127-
case AST.PipeNode():
128-
feature_counts["pipeline"] += 1
129-
if node.is_background:
130-
feature_counts["background"] += 1
131-
case AST.SubshellNode():
132-
feature_counts["subshell"] += 1
133-
case AST.TArgChar():
134-
feature_counts["home_tilde"] += 1
135-
case AST.AArgChar():
136-
feature_counts["$((arithmetic))"] += 1
137-
case AST.BArgChar():
138-
feature_counts["$(substitution)"] += 1
139-
case AST.VArgChar():
140-
feature_counts["variable_use"] += 1
141-
case AST.AssignNode():
142-
feature_counts["assignment"] += 1
143-
case AST.DefunNode():
144-
feature_counts["function"] += 1
145-
case AST.WhileNode():
146-
feature_counts["while"] += 1
147-
case AST.ForNode():
148-
feature_counts["for"] += 1
149-
case AST.CaseNode():
150-
feature_counts["case"] += 1
151-
case AST.IfNode():
152-
feature_counts["if"] += 1
153-
case AST.AndNode():
154-
feature_counts["and"] += 1
155-
case AST.OrNode():
156-
feature_counts["or"] += 1
157-
case AST.NotNode():
158-
feature_counts["negate"] += 1
159-
case AST.HeredocRedirNode():
160-
feature_counts["heredoc_redir"] += 1
161-
case AST.DupRedirNode():
162-
feature_counts["dup_redir"] += 1
163-
case AST.FileRedirNode():
164-
feature_counts["file_redir"] += 1
165-
case AST.CommandNode():
166-
feature_counts["command"] += 1
167-
if node.arguments:
168-
cmd_name = AST.string_of_arg(node.arguments[0])
169-
# NB this is conservative---we're detecting static uses of these constructs
170-
if cmd_name == "eval":
171-
feature_counts["eval"] += 1
172-
elif cmd_name == "alias":
173-
feature_counts["alias"] += 1
174-
case _:
175-
pass
176-
return node
177-
178-
return (count_features, feature_counts)
179-
90+
## Create a simple analysis that returns the number of subshells a script will create:
91+
## Four ways to create a subshell:
92+
## - Asynchronous commands `&`
93+
## - Pipes `|`
94+
## - Subshells `(...)`
95+
## - Command Substitution `$(...)`
96+
##
97+
## Find a (POSIX) shell script you use frequently (or pull one from binpash/koala) and see how many it creates.
98+
##
99+
100+
class Counter:
101+
def __init__(self):
102+
self.cnt = 0
103+
104+
def add(self, n):
105+
self.cnt += n
106+
107+
def get(self):
108+
return self.cnt
180109

181110
def step4_subshells(ast):
182111
show_step("4: counting shell features")
183112

184-
(counter, counts) = feature_counter()
185-
# FILL IN HERE WITH CALL to `walk_ast` with the `counter` as `visit`
186-
# FILL IN HERE WITH LOOP to print out the features (HINT: use the `dict.items` method)
113+
subshells = Counter()
114+
# FILL IN HERE WITH CALL to `walk_ast` that counts subshells
115+
print("Number of subshells in script:", subshells.get())
187116

188117
##
189118
## Step 5:

0 commit comments

Comments
 (0)