test function bodies in parser

A naive test of a function with a body failed because we were
not actually looking for or accepting semicolons, but were generating
them on printing a thing back out. Changed to require semicolons
between statements. This logic may be wrong; for instance, if
you have a trailing semicolon on a body, the String() method won't
yield the original text exactly.
This commit is contained in:
Seebs 2023-04-07 16:23:14 -05:00
parent 6383a96ac5
commit 6787a8ec58
3 changed files with 63 additions and 3 deletions

View file

@ -198,6 +198,8 @@ func CloneStatement(stmt Statement) Statement {
return stmt.Clone()
case *ReleaseStatement:
return stmt.Clone()
case *ReturnStatement:
return stmt.Clone()
case *RollbackStatement:
return stmt.Clone()
case *SavepointStatement:
@ -3133,7 +3135,10 @@ func (s *CreateFunctionStatement) String() string {
buf.WriteString(" AS BEGIN")
for i := range s.Body {
fmt.Fprintf(&buf, " %s;", s.Body[i].String())
if i > 0 {
buf.WriteString(";")
}
fmt.Fprintf(&buf, " %s", s.Body[i].String())
}
buf.WriteString(" END")

View file

@ -1610,6 +1610,19 @@ func (p *Parser) parseCreateFunctionStatement(createPos Pos) (_ *CreateFunctionS
if p.peek() == END {
break
}
if p.peek() != SEMI {
return &stmt, p.errorExpected(p.pos, p.peek(), "semicolon or END")
}
// we don't have a good place to stash the semicolon's position,
// so just consume it.
_, _, _ = p.scan()
// Allow a trailing semicolon. Note that the trailing semicolon won't
// be reproduced by .String(); to fix this, we either have to modify
// every statement to track its own semicolon, or the body to track
// the positions of semicolons. Either of those might be reasonable.
if p.peek() == END {
break
}
}
if p.peek() != END {

View file

@ -472,7 +472,7 @@ func TestParser_ParseAlterStatement(t *testing.T) {
func TestParser_ParseFunctionStatement(t *testing.T) {
t.Run("CreateFunction", func(t *testing.T) {
AssertParseStatement(t, `CREATE FUNCTION IF NOT EXISTS func (@param1 int, @param2 string) returns int as begin end`, &parser.CreateFunctionStatement{
AssertParseStatement(t, `CREATE FUNCTION IF NOT EXISTS func (@param1 int, @param2 string) returns int as begin return 3 end`, &parser.CreateFunctionStatement{
Create: pos(0),
Function: pos(7),
If: pos(16),
@ -495,8 +495,50 @@ func TestParser_ParseFunctionStatement(t *testing.T) {
ReturnType: &parser.Type{Name: &parser.Ident{NamePos: pos(73), Name: "int"}},
As: pos(77),
Begin: pos(80),
End: pos(86),
Body: []parser.Statement{&parser.ReturnStatement{
Return: pos(86),
ReturnExpr: &parser.IntegerLit{ValuePos: pos(93), Value: "3"},
}},
End: pos(95),
})
// This example is probably invalid, but lets us verify that the semicolons between
// statements are working
AssertParseStatement(t, `CREATE FUNCTION IF NOT EXISTS func (@param1 int, @param2 string) returns int as begin return 3; return 4 end`, &parser.CreateFunctionStatement{
Create: pos(0),
Function: pos(7),
If: pos(16),
IfNot: pos(19),
IfNotExists: pos(23),
Name: &parser.Ident{NamePos: pos(30), Name: "func"},
Lparen: pos(35),
Parameters: []*parser.ParameterDefinition{
{
Name: &parser.Variable{Name: "@param1", NamePos: pos(36)},
Type: &parser.Type{Name: &parser.Ident{NamePos: pos(44), Name: "int"}},
},
{
Name: &parser.Variable{Name: "@param2", NamePos: pos(49)},
Type: &parser.Type{Name: &parser.Ident{NamePos: pos(57), Name: "string"}},
},
},
Rparen: pos(63),
Returns: pos(65),
ReturnType: &parser.Type{Name: &parser.Ident{NamePos: pos(73), Name: "int"}},
As: pos(77),
Begin: pos(80),
Body: []parser.Statement{
&parser.ReturnStatement{
Return: pos(86),
ReturnExpr: &parser.IntegerLit{ValuePos: pos(93), Value: "3"},
},
&parser.ReturnStatement{
Return: pos(96),
ReturnExpr: &parser.IntegerLit{ValuePos: pos(103), Value: "4"},
},
},
End: pos(105),
})
AssertParseStatementError(t, `CREATE FUNCTION IF NOT EXISTS func (@param1 int, @param2 string) returns int as begin return 3 return 4 end`, `1:96: expected semicolon or END, found 'RETURN'`)
// AssertParseStatement(t, `CREATE TRIGGER IF NOT EXISTS trig BEFORE INSERT ON tbl BEGIN DELETE FROM new; END`, &parser.CreateFunctionStatement{
// Create: pos(0),
// Function: pos(7),