Skip to content

Commit 1fd38a2

Browse files
Merge pull request #256 from yassinebenaid/dev
allow use of `break` and `continue` in nested scopes
2 parents 6579447 + 18da02b commit 1fd38a2

File tree

24 files changed

+4428
-1529
lines changed

24 files changed

+4428
-1529
lines changed

README.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,28 @@
1414

1515
A shell compiler that converts shell scripts into secure, portable and static binaries. Unlike other tools (ie. [shc](https://github.com/neurobin/shc)), Bunster does not just wrap your script within a binary. It literally compiles them to standalone shell-independent programs.
1616

17-
Technically speaking, **Bunster** in fact is a `shell-to-Go` [Transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) that generates [Go](https://go.dev) source out of your scripts. Then, optionally uses the [Go Toolchain](https://go.dev/dl) to compile the code to an executable.
17+
Under the hood, **Bunster** transpiles shell scripts into [Go](https://go.dev) code. Then uses the [Go Toolchain](https://go.dev/dl) to compile the code to an executable.
1818

19-
**Bunster** aims to be compatible with `bash` as a starting move. You should expect your `bash` scripts to just work with bunster. Additional shells will be supported as soon as we release v1.
19+
**Bunster** aims to be compatible with `bash` as a starting move. Expecting that most `bash` scripts will just work with bunster. Additional shells will be supported as soon as we release v1.
2020

2121
> [!WARNING]
2222
> This project is in its early stages of development. [Only a subset of features are supported so far](https://bunster.netlify.app/features/simple-commands).
2323
24-
## Vision
24+
## Features
2525

26-
Bunster has a vision to make shell scripts feel like any modern programming language. With as many features as we could, without any bloating. anything that
27-
makes you feel happy when writing shell scripts. a feeling that shells usually don't provide, a feeling that languages like Go give you, we aim to:
26+
In addition to being compatible with bash. bunster offers a lot of additional features that empower its uniqueness:
2827

29-
- Improve error handling and messages, we want to help everyone write error-aware scripts. And when they fail, we want to give them clear, concise error messages.
30-
- Introduce a module system that allows you to publish and consume scripts as libraries, with a builtin package manager.
31-
- Add first-class support for a wide collection of builtin commands that just work out of the box. You don't need external programs to use them.
32-
- Add first-class support for `.env` files. Allowing you to load variables from `.env`.
33-
- Support static asset embedding. This feature allows you to embed a file's content to a variable at build time. ([Go has one already](https://pkg.go.dev/embed))
34-
- Support different shells and POSIX.
28+
- **Static binaries**: scripts compiled with bunster are not just wrappers around your script, nor do they rely on any external shells on your system.
29+
30+
- **Modular**: unlike traditional shells scripts that are written in a single file. bunster offers a module system that allows you to distribute code across as many files as needed. [learn more](https://bunster.netlify.app/workspace/modules)
31+
32+
- **Package Manager**: bunster has a buitlin package manager that makes it easy to publish and consume modules as libraries. [learn more](https://bunster.netlify.app/workspace/modules)
33+
34+
- **Native `.env` files support**: `.env` files are nativily supported in bunster. Allowing you to load variables from `.env` files at runtime. [learn more](https://bunster.netlify.app/features/environment-files)
35+
36+
- **Static assets embedding**: bunster allows you to embed files and directories within your compiled program at compile time. And use them as if they were normal files in the system at runtime. [learn more](https://bunster.netlify.app/features/embedding)
37+
38+
- **Static analysis**: bunster statically analizes your scripts and reports potential bugs at compile time. (_wip_)
3539

3640
## Get Started
3741

analyser/analyser.go

Lines changed: 17 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ func Analyse(s ast.Script, main bool) error {
3333
}
3434

3535
type analyser struct {
36-
script ast.Script
37-
errors []error
38-
stack []ast.Statement
36+
script ast.Script
37+
errors []error
38+
stack []ast.Statement
39+
breakpoints int
3940
}
4041

4142
func (a *analyser) analyse(main bool) {
@@ -73,7 +74,7 @@ func (a *analyser) analyseStatement(s ast.Statement) {
7374
case ast.List:
7475
a.analyseStatement(v.Left)
7576
a.analyseStatement(v.Right)
76-
case ast.If:
77+
case *ast.If:
7778
for _, s := range v.Head {
7879
a.analyseStatement(s)
7980
}
@@ -145,50 +146,16 @@ func (a *analyser) analyseStatement(s ast.Statement) {
145146
a.analyseExpression(pa.Value)
146147
}
147148
}
148-
case ast.Loop:
149-
for _, s := range v.Head {
150-
a.analyseStatement(s)
151-
}
152-
for _, s := range v.Body {
153-
a.analyseStatement(s)
154-
}
155-
for _, r := range v.Redirections {
156-
if r.Dst != nil {
157-
a.analyseExpression(r.Dst)
158-
}
159-
}
160-
case ast.Break:
161-
var withinLoop bool
162-
loop:
163-
for i := len(a.stack) - 1; i >= 0; i-- {
164-
switch a.stack[i].(type) {
165-
case ast.Loop, ast.RangeLoop, ast.For:
166-
withinLoop = true
167-
break loop
168-
case ast.List, ast.Break:
169-
default:
170-
a.report(Error{Msg: "the `break` keyword cannot be used here"})
171-
}
172-
}
173-
if !withinLoop {
174-
a.report(Error{Msg: "the `break` keyword cannot be used here"})
175-
}
176-
case ast.Continue:
177-
var withinLoop bool
178-
loop2:
179-
for i := len(a.stack) - 1; i >= 0; i-- {
180-
switch a.stack[i].(type) {
181-
case ast.Loop, ast.RangeLoop, ast.For:
182-
withinLoop = true
183-
break loop2
184-
case ast.List, ast.Continue:
185-
default:
186-
a.report(Error{Msg: "the `continue` keyword cannot be used here"})
187-
}
188-
}
189-
if !withinLoop {
190-
a.report(Error{Msg: "the `continue` keyword cannot be used here"})
191-
}
149+
case *ast.For:
150+
a.analyseFor(v)
151+
case *ast.RangeLoop:
152+
a.analyseRangeLoop(v)
153+
case *ast.Loop:
154+
a.analyseLoop(v)
155+
case *ast.Break:
156+
a.analyseBreak(v)
157+
case *ast.Continue:
158+
a.analyseContinue(v)
192159
case ast.Pipeline:
193160
for _, cmd := range v {
194161
a.analyseStatement(cmd.Command)
@@ -208,36 +175,7 @@ func (a *analyser) analyseStatement(s ast.Statement) {
208175
a.analyseExpression(r.Dst)
209176
}
210177
}
211-
case ast.RangeLoop:
212-
for _, expr := range v.Operands {
213-
a.analyseExpression(expr)
214-
}
215-
for _, s := range v.Body {
216-
a.analyseStatement(s)
217-
}
218-
for _, r := range v.Redirections {
219-
if r.Dst != nil {
220-
a.analyseExpression(r.Dst)
221-
}
222-
}
223-
case ast.For:
224-
for _, expr := range v.Head.Init {
225-
a.analyseArithmeticExpression(expr)
226-
}
227-
for _, expr := range v.Head.Test {
228-
a.analyseArithmeticExpression(expr)
229-
}
230-
for _, expr := range v.Head.Update {
231-
a.analyseArithmeticExpression(expr)
232-
}
233-
for _, s := range v.Body {
234-
a.analyseStatement(s)
235-
}
236-
for _, r := range v.Redirections {
237-
if r.Dst != nil {
238-
a.analyseExpression(r.Dst)
239-
}
240-
}
178+
241179
case ast.Test:
242180
a.analyseExpression(v.Expr)
243181

@@ -267,7 +205,7 @@ func (a *analyser) analyseStatement(s ast.Statement) {
267205
a.analyseExpression(r.Dst)
268206
}
269207
}
270-
case ast.Case:
208+
case *ast.Case:
271209
a.analyseExpression(v.Word)
272210

273211
for _, _case := range v.Cases {

analyser/loops.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package analyser
2+
3+
import (
4+
"github.com/yassinebenaid/bunster/ast"
5+
)
6+
7+
func (a *analyser) analyseFor(loop *ast.For) {
8+
for _, expr := range loop.Head.Init {
9+
a.analyseArithmeticExpression(expr)
10+
}
11+
for _, expr := range loop.Head.Test {
12+
a.analyseArithmeticExpression(expr)
13+
}
14+
for _, expr := range loop.Head.Update {
15+
a.analyseArithmeticExpression(expr)
16+
}
17+
for _, s := range loop.Body {
18+
a.analyseStatement(s)
19+
}
20+
for _, r := range loop.Redirections {
21+
if r.Dst != nil {
22+
a.analyseExpression(r.Dst)
23+
}
24+
}
25+
}
26+
27+
func (a *analyser) analyseRangeLoop(loop *ast.RangeLoop) {
28+
for _, expr := range loop.Operands {
29+
a.analyseExpression(expr)
30+
}
31+
for _, s := range loop.Body {
32+
a.analyseStatement(s)
33+
}
34+
for _, r := range loop.Redirections {
35+
if r.Dst != nil {
36+
a.analyseExpression(r.Dst)
37+
}
38+
}
39+
}
40+
41+
func (a *analyser) analyseLoop(loop *ast.Loop) {
42+
for _, s := range loop.Head {
43+
a.analyseStatement(s)
44+
}
45+
for _, s := range loop.Body {
46+
a.analyseStatement(s)
47+
}
48+
for _, r := range loop.Redirections {
49+
if r.Dst != nil {
50+
a.analyseExpression(r.Dst)
51+
}
52+
}
53+
}
54+
55+
func (a *analyser) analyseBreak(b *ast.Break) {
56+
a.breakpoints++
57+
b.BreakPoint = a.breakpoints
58+
59+
var withinLoop bool
60+
var last int
61+
62+
loop:
63+
for i := len(a.stack) - 1; i >= 0; i-- {
64+
switch v := a.stack[i].(type) {
65+
case *ast.Loop:
66+
v.BreakPoints.Add(a.breakpoints, ast.DECLARE)
67+
withinLoop = true
68+
break loop
69+
case *ast.RangeLoop:
70+
v.BreakPoints.Add(a.breakpoints, ast.DECLARE)
71+
withinLoop = true
72+
break loop
73+
case *ast.For:
74+
v.BreakPoints.Add(a.breakpoints, ast.DECLARE)
75+
withinLoop = true
76+
break loop
77+
case *ast.If:
78+
v.BreakPoints.Add(a.breakpoints, ast.RETURN)
79+
case *ast.Case:
80+
v.BreakPoints.Add(a.breakpoints, ast.RETURN)
81+
case *ast.Break:
82+
v.Type = ast.RETURN
83+
case ast.List:
84+
default:
85+
break loop
86+
}
87+
last = i
88+
}
89+
90+
if !withinLoop {
91+
a.report(Error{Msg: "the `break` keyword cannot be used here"})
92+
return
93+
}
94+
95+
switch v := a.stack[last].(type) {
96+
case *ast.If:
97+
v.BreakPoints.Add(a.breakpoints, ast.BREAK)
98+
case *ast.Case:
99+
v.BreakPoints.Add(a.breakpoints, ast.BREAK)
100+
case *ast.Break, ast.List:
101+
b.Type = ast.CONTINUE
102+
}
103+
}
104+
105+
func (a *analyser) analyseContinue(b *ast.Continue) {
106+
a.breakpoints++
107+
b.BreakPoint = a.breakpoints
108+
109+
var withinLoop bool
110+
var last int
111+
112+
loop:
113+
for i := len(a.stack) - 1; i >= 0; i-- {
114+
switch v := a.stack[i].(type) {
115+
case *ast.Loop:
116+
v.BreakPoints.Add(a.breakpoints, ast.DECLARE)
117+
withinLoop = true
118+
break loop
119+
case *ast.RangeLoop:
120+
v.BreakPoints.Add(a.breakpoints, ast.DECLARE)
121+
withinLoop = true
122+
break loop
123+
case *ast.For:
124+
v.BreakPoints.Add(a.breakpoints, ast.DECLARE)
125+
withinLoop = true
126+
break loop
127+
case *ast.If:
128+
v.BreakPoints.Add(a.breakpoints, ast.RETURN)
129+
case *ast.Case:
130+
v.BreakPoints.Add(a.breakpoints, ast.RETURN)
131+
case *ast.Continue:
132+
v.Type = ast.RETURN
133+
case ast.List:
134+
default:
135+
break loop
136+
}
137+
last = i
138+
}
139+
140+
if !withinLoop {
141+
a.report(Error{Msg: "the `continue` keyword cannot be used here"})
142+
return
143+
}
144+
145+
switch v := a.stack[last].(type) {
146+
case *ast.If:
147+
v.BreakPoints.Add(a.breakpoints, ast.CONTINUE)
148+
case *ast.Case:
149+
v.BreakPoints.Add(a.breakpoints, ast.CONTINUE)
150+
case *ast.Continue, ast.List:
151+
b.Type = ast.CONTINUE
152+
}
153+
}

0 commit comments

Comments
 (0)