Skip to content

Commit dee248c

Browse files
Merge pull request #259 from yassinebenaid/dev
support `unset` keyword
2 parents 75931ef + 79ff544 commit dee248c

File tree

10 files changed

+391
-0
lines changed

10 files changed

+391
-0
lines changed

analyser/analyser.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ func (a *analyser) analyseStatement(s ast.Statement) {
199199
a.analyseExpression(r.Dst)
200200
}
201201
}
202+
case ast.Unset:
203+
for _, name := range v.Names {
204+
a.analyseExpression(name)
205+
}
202206
default:
203207
a.report(Error{Msg: fmt.Sprintf("Unsupported statement type: %T", v)})
204208
}

ast/statement.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ type Return struct {
122122
BreakPoint int
123123
}
124124

125+
type Unset struct {
126+
Flag string
127+
Names []Expression
128+
}
129+
125130
type Continue struct {
126131
BreakPoint int
127132
Type BreakPointsType
@@ -229,6 +234,7 @@ func (Return) node() {}
229234
func (Wait) node() {}
230235
func (Embed) node() {}
231236
func (Defer) node() {}
237+
func (Unset) node() {}
232238

233239
// Expressions
234240
func (CommandSubstitution) expr() {}
@@ -258,3 +264,4 @@ func (Return) stmt() {}
258264
func (Wait) stmt() {}
259265
func (Embed) stmt() {}
260266
func (Defer) stmt() {}
267+
func (Unset) stmt() {}

generator/builtins.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ func (g *generator) handleExit(buf *InstructionBuffer, exit ast.Exit) {
2525
buf.add(ir.Exit{Code: g.handleExpression(buf, exit.Code)})
2626
}
2727

28+
func (g *generator) handleUnset(buf *InstructionBuffer, unset ast.Unset) {
29+
var names InstructionBuffer
30+
31+
for _, name := range unset.Names {
32+
names.add(g.handleExpression(buf, name))
33+
}
34+
35+
if unset.Flag == "" || unset.Flag == "-v" {
36+
buf.add(ir.Unset{Names: names, VarsOnly: unset.Flag == "-v"})
37+
} else {
38+
buf.add(ir.UnsetFunctions(names))
39+
}
40+
}
41+
2842
func (g *generator) handleReturn(buf *InstructionBuffer, b *ast.Return) {
2943
buf.add(ir.Set{Name: fmt.Sprintf("breakpoint%d", b.BreakPoint), Value: ir.Literal("true")})
3044
buf.add(ir.Set{Name: "shell.ExitCode", Value: ir.ParseInt{Value: g.handleExpression(buf, b.Code)}})

generator/generator.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ func (g *generator) generate(buf *InstructionBuffer, statement ast.Statement) {
8888
g.handleCase(buf, v)
8989
case ast.Exit:
9090
g.handleExit(buf, v)
91+
case ast.Unset:
92+
g.handleUnset(buf, v)
9193
default:
9294
panic(fmt.Sprintf("Unsupported statement: %T", v))
9395
}

generator/tests/19-unset.test

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#(TEST: can unset variables)
2+
3+
unset var
4+
5+
unset var $var var
6+
7+
unset -v var var
8+
9+
unset -f var var
10+
11+
#(RESULT)
12+
13+
package main
14+
15+
import "github.com/yassinebenaid/bunster/runtime"
16+
17+
func Main(shell *runtime.Shell, streamManager *runtime.StreamManager) {
18+
defer shell.Terminate(streamManager)
19+
shell.Unset(false, "var")
20+
shell.Unset(false, "var", shell.ReadVar("var"), "var")
21+
shell.Unset(true, "var", "var")
22+
shell.UnsetFunctions("var", "var")
23+
}
24+
25+
#(ENDTEST)
26+
27+
28+

ir/ir.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,31 @@ func (e Exit) togo() string {
178178
};`, e.Code.togo())
179179
}
180180

181+
type Unset struct {
182+
VarsOnly bool
183+
Names []Instruction
184+
}
185+
186+
func (e Unset) togo() string {
187+
var names []string
188+
for _, name := range e.Names {
189+
names = append(names, name.togo())
190+
}
191+
192+
return fmt.Sprintf(` shell.Unset(%t, %s);`, e.VarsOnly, strings.Join(names, ", "))
193+
}
194+
195+
type UnsetFunctions []Instruction
196+
197+
func (e UnsetFunctions) togo() string {
198+
var names []string
199+
for _, name := range e {
200+
names = append(names, name.togo())
201+
}
202+
203+
return fmt.Sprintf(` shell.UnsetFunctions( %s);`, strings.Join(names, ", "))
204+
}
205+
181206
type ReadVar string
182207

183208
func (rv ReadVar) togo() string {

parser/builtin_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,64 @@ var builtinTests = []testCase{
4646
Code: ast.Word("1"),
4747
},
4848
}},
49+
50+
{`unset var`, ast.Script{
51+
ast.Unset{
52+
Names: []ast.Expression{
53+
ast.Word("var"),
54+
},
55+
},
56+
}},
57+
{`unset var1 $var2 var3`, ast.Script{
58+
ast.Unset{
59+
Names: []ast.Expression{
60+
ast.Word("var1"),
61+
ast.Var("var2"),
62+
ast.Word("var3")},
63+
},
64+
}},
65+
{`unset var1 # comment`, ast.Script{
66+
ast.Unset{
67+
Names: []ast.Expression{
68+
ast.Word("var1"),
69+
},
70+
},
71+
}},
72+
{`unset var1 && unset var1`, ast.Script{
73+
ast.List{
74+
Left: ast.Unset{
75+
Names: []ast.Expression{
76+
ast.Word("var1"),
77+
},
78+
},
79+
Operator: "&&",
80+
Right: ast.Unset{
81+
Names: []ast.Expression{
82+
ast.Word("var1"),
83+
},
84+
},
85+
},
86+
}},
87+
{`
88+
unset var1
89+
unset var2
90+
`, ast.Script{
91+
ast.Unset{Names: []ast.Expression{ast.Word("var1")}},
92+
ast.Unset{Names: []ast.Expression{ast.Word("var2")}},
93+
}},
94+
{`
95+
unset -f var1
96+
unset -v var2
97+
`, ast.Script{
98+
ast.Unset{Flag: "-f", Names: []ast.Expression{ast.Word("var1")}},
99+
ast.Unset{Flag: "-v", Names: []ast.Expression{ast.Word("var2")}},
100+
}},
49101
}
50102

51103
var builtinsErrorHandlingCases = []errorHandlingTestCase{
52104
{`exit <foo`, "syntax error: unexpected token `<`. (line: 1, column: 6)"},
53105
{`return <foo`, "syntax error: unexpected token `<`. (line: 1, column: 8)"},
106+
{`unset`, "syntax error: unexpected token `end of file`. (line: 1, column: 6)"},
107+
{`unset -`, "syntax error: expected a valid flag character after `-`, found `end of file`. (line: 1, column: 8)"},
108+
{`unset -k`, "syntax error: expected a valid flag character after `-`, found `k`. (line: 1, column: 8)"},
54109
}

parser/builtins.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ func (p *parser) getBuiltinParser() func() ast.Statement {
2727
return p.parseLocal
2828
case token.EXPORT:
2929
return p.parseExport
30+
case token.UNSET:
31+
return p.parseUnset
3032
case token.AT:
3133
if p.next.Type != token.EMBED {
3234
return nil
@@ -341,6 +343,57 @@ func (p *parser) parseExport() ast.Statement {
341343
return assignements
342344
}
343345

346+
func (p *parser) parseUnset() ast.Statement {
347+
p.proceed()
348+
349+
if p.curr.Type == token.BLANK {
350+
p.proceed()
351+
}
352+
353+
var flag string
354+
355+
if p.curr.Type == token.MINUS {
356+
p.proceed()
357+
if p.curr.Type == token.WORD && (p.curr.Literal == "f" || p.curr.Literal == "v") {
358+
flag = "-" + p.curr.Literal
359+
p.proceed()
360+
} else {
361+
p.error("expected a valid flag character after `-`, found `%v`", p.curr)
362+
}
363+
}
364+
365+
if p.curr.Type == token.BLANK {
366+
p.proceed()
367+
}
368+
369+
var names []ast.Expression
370+
371+
for !p.isControlToken() && p.curr.Type != token.EOF {
372+
name := p.parseExpression()
373+
names = append(names, name)
374+
375+
if p.curr.Type == token.BLANK {
376+
p.proceed()
377+
}
378+
379+
if p.curr.Type == token.HASH {
380+
for p.curr.Type != token.NEWLINE && p.curr.Type != token.EOF {
381+
p.proceed()
382+
}
383+
}
384+
}
385+
386+
if names == nil || (!p.isControlToken() && p.curr.Type != token.EOF) {
387+
p.error("unexpected token `%s`", p.curr)
388+
return nil
389+
}
390+
391+
return ast.Unset{
392+
Flag: flag,
393+
Names: names,
394+
}
395+
}
396+
344397
func (p *parser) parseEmbedDirective() ast.Statement {
345398
p.proceed()
346399
p.proceed()

runtime/shell.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,30 @@ func (shell *Shell) MarkVarAsExported(name string) {
154154
shell.exportedVars.set(name, struct{}{})
155155
}
156156

157+
func (shell *Shell) Unset(vars_only bool, names ...string) {
158+
for _, name := range names {
159+
if shell.localVars.forget(name) ||
160+
shell.vars.forget(name) ||
161+
shell.env.forget(name) {
162+
continue
163+
}
164+
165+
if !vars_only && shell.functions.forget(name) {
166+
continue
167+
}
168+
169+
if shell.parent != nil {
170+
shell.parent.Unset(vars_only, name)
171+
}
172+
}
173+
}
174+
175+
func (shell *Shell) UnsetFunctions(names ...string) {
176+
for _, name := range names {
177+
shell.functions.forget(name)
178+
}
179+
}
180+
157181
func (shell *Shell) ReadSpecialVar(name string) string {
158182
switch name {
159183
case "0":
@@ -348,6 +372,17 @@ func (r *repository[T]) set(key string, value T) {
348372
r.data[key] = value
349373
}
350374

375+
func (r *repository[T]) forget(key string) bool {
376+
r.mx.Lock()
377+
defer r.mx.Unlock()
378+
_, ok := r.data[key]
379+
if !ok {
380+
return false
381+
}
382+
delete(r.data, key)
383+
return true
384+
}
385+
351386
func (r *repository[T]) clone() *repository[T] {
352387
var repo = newRepository[T]()
353388
for key, value := range r.data {

0 commit comments

Comments
 (0)