Skip to content

Commit 5bdf6a8

Browse files
authored
Improve the parser on several corner cases: (#138)
- Disallow use of function call in pipelines The command below now throws error: echo "test test" | replace(" ", "|") - Improve the error for invalid syntaxes (Closes #123) Signed-off-by: Tiago <[email protected]>
1 parent 338f96d commit 5bdf6a8

File tree

7 files changed

+135
-17
lines changed

7 files changed

+135
-17
lines changed

Diff for: cmd/nashfmt/main.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ func main() {
3636
file, err = os.Open(fname)
3737

3838
if err != nil {
39-
fmt.Fprintf(os.Stderr, "[ERROR] "+err.Error())
39+
fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
4040
os.Exit(1)
4141
}
4242

4343
content, err := ioutil.ReadAll(file)
4444

4545
if err != nil {
4646
file.Close()
47-
fmt.Fprintf(os.Stderr, "[ERROR] "+err.Error())
47+
fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
4848
os.Exit(1)
4949
}
5050

@@ -53,7 +53,7 @@ func main() {
5353
ast, err := parser.Parse()
5454

5555
if err != nil {
56-
fmt.Fprintf(os.Stderr, "[ERROR] "+err.Error())
56+
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
5757
file.Close()
5858
os.Exit(1)
5959
}
@@ -69,7 +69,7 @@ func main() {
6969
err = ioutil.WriteFile(fname, []byte(fmt.Sprintf("%s\n", ast.String())), 0666)
7070

7171
if err != nil {
72-
fmt.Fprintf(os.Stderr, "[ERROR] "+err.Error())
72+
fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
7373
return
7474
}
7575
}

Diff for: internal/sh/shell_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2041,7 +2041,7 @@ func TestExecuteMultilineCmdAssign(t *testing.T) {
20412041
}
20422042
}
20432043

2044-
func TestExecuteMuliReturnUnfinished(t *testing.T) {
2044+
func TestExecuteMultiReturnUnfinished(t *testing.T) {
20452045
shell, err := NewShell()
20462046

20472047
if err != nil {

Diff for: parser/parse.go

+36-11
Original file line numberDiff line numberDiff line change
@@ -208,18 +208,28 @@ func (p *Parser) parsePipe(first *ast.CommandNode) (ast.Node, error) {
208208
if n.IsMulti() {
209209
it = p.peek()
210210
if it.Type() != token.RParen {
211-
return nil, errors.NewUnfinishedCmdError(p.name, it)
211+
if it.Type() == token.EOF {
212+
return nil, errors.NewUnfinishedCmdError(p.name, it)
213+
}
214+
215+
return nil, newParserError(it, p.name, "Unexpected symbol '%s'", it)
212216
}
213217

214218
p.ignore()
215219
}
216220

217221
it = p.peek()
218222

219-
if it.Type() == token.Semicolon {
220-
p.ignore()
223+
if it.Type() == token.RBrace {
224+
return n, nil
221225
}
222226

227+
if it.Type() != token.Semicolon {
228+
return nil, newParserError(it, p.name, "Unexpected symbol %s", it)
229+
}
230+
231+
p.ignore()
232+
223233
return n, nil
224234
}
225235

@@ -234,7 +244,7 @@ func (p *Parser) parseCommand(it scanner.Token) (ast.Node, error) {
234244
}
235245

236246
if it.Type() != token.Ident && it.Type() != token.Arg {
237-
if isMulti {
247+
if isMulti && it.Type() == token.EOF {
238248
return nil, errors.NewUnfinishedCmdError(p.name, it)
239249
}
240250

@@ -249,6 +259,14 @@ cmdLoop:
249259

250260
switch typ := it.Type(); {
251261
case typ == token.RBrace:
262+
if p.openblocks > 0 {
263+
if p.insidePipe {
264+
p.insidePipe = false
265+
}
266+
267+
return n, nil
268+
}
269+
252270
break cmdLoop
253271
case isValidArgument(it):
254272
arg, err := p.getArgument(true, true)
@@ -288,26 +306,33 @@ cmdLoop:
288306
}
289307
}
290308

291-
if p.insidePipe {
292-
p.insidePipe = false
293-
}
294-
295309
it = p.peek()
296310

297311
if isMulti {
298312
if it.Type() != token.RParen {
299-
return nil, errors.NewUnfinishedCmdError(p.name, it)
313+
if it.Type() == token.EOF {
314+
return nil, errors.NewUnfinishedCmdError(p.name, it)
315+
}
316+
317+
return nil, newParserError(it, p.name, "Unexpected symbol '%s'", it)
300318
}
301319

302320
p.ignore()
303321

304322
it = p.peek()
305323
}
306324

307-
if it.Type() == token.Semicolon {
308-
p.ignore()
325+
if p.insidePipe {
326+
p.insidePipe = false
327+
return n, nil
328+
}
329+
330+
if it.Type() != token.Semicolon {
331+
return nil, newParserError(it, p.name, "Unexpected symbol '%s'", it)
309332
}
310333

334+
p.ignore()
335+
311336
return n, nil
312337
}
313338

Diff for: parser/parse_regression_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,20 @@ func TestParseIssue108(t *testing.T) {
244244

245245
parserTestTable("parse issue #108", `cat spec.ebnf | grep -i rfork`, expected, t, false)
246246
}
247+
248+
func TestParseIssue123(t *testing.T) {
249+
parser := NewParser("invalid cmd assignment", `IFS <= ("\n")`)
250+
251+
_, err := parser.Parse()
252+
253+
if err == nil {
254+
t.Errorf("Must fail...")
255+
return
256+
}
257+
258+
expected := "invalid cmd assignment:1:9: Unexpected token STRING. Expecting IDENT or ARG"
259+
if err.Error() != expected {
260+
t.Fatalf("Error string differs. Expecting '%s' but got '%s'",
261+
expected, err.Error())
262+
}
263+
}

Diff for: parser/parse_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,46 @@ func TestParseFnBasic(t *testing.T) {
947947
}`, expected, t, true)
948948
}
949949

950+
func TestParseInlineFnDecl(t *testing.T) {
951+
expected := ast.NewTree("fn")
952+
ln := ast.NewBlockNode(token.NewFileInfo(1, 0))
953+
954+
fn := ast.NewFnDeclNode(token.NewFileInfo(1, 0), "cd")
955+
tree := ast.NewTree("fn body")
956+
lnBody := ast.NewBlockNode(token.NewFileInfo(1, 0))
957+
echo := ast.NewCommandNode(token.NewFileInfo(1, 11), "echo", false)
958+
echo.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 16), "hello", true))
959+
lnBody.Push(echo)
960+
961+
tree.Root = lnBody
962+
fn.SetTree(tree)
963+
964+
// root
965+
ln.Push(fn)
966+
expected.Root = ln
967+
968+
parserTestTable("inline fn", `fn cd() { echo "hello" }`,
969+
expected, t, false)
970+
971+
test := ast.NewCommandNode(token.NewFileInfo(1, 26), "test", false)
972+
test.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 32), "-d", false))
973+
test.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 35), "/etc", false))
974+
975+
pipe := ast.NewPipeNode(token.NewFileInfo(1, 11), false)
976+
pipe.AddCmd(echo)
977+
pipe.AddCmd(test)
978+
lnBody = ast.NewBlockNode(token.NewFileInfo(1, 0))
979+
lnBody.Push(pipe)
980+
tree.Root = lnBody
981+
fn.SetTree(tree)
982+
ln = ast.NewBlockNode(token.NewFileInfo(1, 0))
983+
ln.Push(fn)
984+
expected.Root = ln
985+
986+
parserTestTable("inline fn", `fn cd() { echo "hello" | test -d /etc }`,
987+
expected, t, false)
988+
}
989+
950990
func TestParseBindFn(t *testing.T) {
951991
expected := ast.NewTree("bindfn")
952992
ln := ast.NewBlockNode(token.NewFileInfo(1, 0))
@@ -1216,3 +1256,15 @@ func TestMultiPipe(t *testing.T) {
12161256
awk "{print AAAAAAAAAAAAAAAAAAAAAA}"
12171257
)`, expected, t, true)
12181258
}
1259+
1260+
func TestFunctionPipes(t *testing.T) {
1261+
parser := NewParser("invalid pipe with functions",
1262+
`echo "some thing" | replace(" ", "|")`)
1263+
1264+
_, err := parser.Parse()
1265+
1266+
if err == nil {
1267+
t.Error("Must fail. Function must be bind'ed to command name to use in pipe.")
1268+
return
1269+
}
1270+
}

Diff for: scanner/lex.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,11 @@ func lexStart(l *Lexer) stateFn {
372372
l.emit(token.Arg)
373373
}
374374

375-
l.addSemicolon = true
375+
if next == eof && l.openParens > 0 {
376+
l.addSemicolon = false
377+
} else {
378+
l.addSemicolon = true
379+
}
376380

377381
return lexStart
378382
case isArgument(r):

Diff for: scanner/lex_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,26 @@ func TestLexerPipe(t *testing.T) {
484484
testTable("testPipe with redirection", `go tool vet -h > out.log | grep log`, expected, t)
485485
}
486486

487+
func TestPipeFunctions(t *testing.T) {
488+
expected := []Token{
489+
{typ: token.Ident, val: "echo"},
490+
{typ: token.String, val: "some thing"},
491+
{typ: token.Pipe, val: "|"},
492+
{typ: token.Ident, val: "replace"},
493+
{typ: token.LParen, val: "("},
494+
{typ: token.String, val: " "},
495+
{typ: token.Comma, val: ","},
496+
{typ: token.String, val: "|"},
497+
{typ: token.RParen, val: ")"},
498+
{typ: token.Semicolon, val: ";"},
499+
{typ: token.EOF},
500+
}
501+
502+
testTable("test pipe with function",
503+
`echo "some thing" | replace(" ", "|")`,
504+
expected, t)
505+
}
506+
487507
func TestLexerUnquoteArg(t *testing.T) {
488508
expected := []Token{
489509
{typ: token.Ident, val: "echo"},

0 commit comments

Comments
 (0)