Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support protobuf NEW operators #119

Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 123 additions & 32 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,38 +133,41 @@ type Expr interface {
isExpr()
}

func (BinaryExpr) isExpr() {}
func (UnaryExpr) isExpr() {}
func (InExpr) isExpr() {}
func (IsNullExpr) isExpr() {}
func (IsBoolExpr) isExpr() {}
func (BetweenExpr) isExpr() {}
func (SelectorExpr) isExpr() {}
func (IndexExpr) isExpr() {}
func (CallExpr) isExpr() {}
func (CountStarExpr) isExpr() {}
func (CastExpr) isExpr() {}
func (ExtractExpr) isExpr() {}
func (CaseExpr) isExpr() {}
func (ParenExpr) isExpr() {}
func (ScalarSubQuery) isExpr() {}
func (ArraySubQuery) isExpr() {}
func (ExistsSubQuery) isExpr() {}
func (Param) isExpr() {}
func (Ident) isExpr() {}
func (Path) isExpr() {}
func (ArrayLiteral) isExpr() {}
func (StructLiteral) isExpr() {}
func (NullLiteral) isExpr() {}
func (BoolLiteral) isExpr() {}
func (IntLiteral) isExpr() {}
func (FloatLiteral) isExpr() {}
func (StringLiteral) isExpr() {}
func (BytesLiteral) isExpr() {}
func (DateLiteral) isExpr() {}
func (TimestampLiteral) isExpr() {}
func (NumericLiteral) isExpr() {}
func (JSONLiteral) isExpr() {}
func (BinaryExpr) isExpr() {}
func (UnaryExpr) isExpr() {}
func (InExpr) isExpr() {}
func (IsNullExpr) isExpr() {}
func (IsBoolExpr) isExpr() {}
func (BetweenExpr) isExpr() {}
func (SelectorExpr) isExpr() {}
func (IndexExpr) isExpr() {}
func (CallExpr) isExpr() {}
func (CountStarExpr) isExpr() {}
func (CastExpr) isExpr() {}
func (ExtractExpr) isExpr() {}
func (CaseExpr) isExpr() {}
func (ParenExpr) isExpr() {}
func (ScalarSubQuery) isExpr() {}
func (ArraySubQuery) isExpr() {}
func (ExistsSubQuery) isExpr() {}
func (Param) isExpr() {}
func (Ident) isExpr() {}
func (Path) isExpr() {}
func (ArrayLiteral) isExpr() {}
func (StructLiteral) isExpr() {}
func (NullLiteral) isExpr() {}
func (BoolLiteral) isExpr() {}
func (IntLiteral) isExpr() {}
func (FloatLiteral) isExpr() {}
func (StringLiteral) isExpr() {}
func (BytesLiteral) isExpr() {}
func (DateLiteral) isExpr() {}
func (TimestampLiteral) isExpr() {}
func (NumericLiteral) isExpr() {}
func (JSONLiteral) isExpr() {}
func (NewConstructor) isExpr() {}
func (BracedNewConstructor) isExpr() {}
func (BracedConstructor) isExpr() {}

// Arg represents argument of function call.
type Arg interface {
Expand Down Expand Up @@ -1381,6 +1384,94 @@ type JSONLiteral struct {
Value *StringLiteral
}

// ================================================================================
//
// NEW constructors
//
// ================================================================================

// BracedConstructorFieldValue represents value part of fields in BracedNewConstructor.
type BracedConstructorFieldValue interface {
Node
isBracedConstructorFieldValue()
}

func (*BracedConstructor) isBracedConstructorFieldValue() {}
func (*BracedConstructorFieldValueExpr) isBracedConstructorFieldValue() {}

// NewConstructorArg represents a single argument in NEW constructor.
//
// {{Expr | sql}} {{Alias | sqlOpt}}
type NewConstructorArg struct {
// pos = Expr.pos
// end = (Alias ?? Expr).end

Expr Expr
Alias *AsAlias // optional
}

// NewConstructor represents NEW operator which creates a protocol buffer using a parenthesized list of arguments.
//
// NEW {{.Type | sql}} ({{.Args | sqlJoin ", "}})
type NewConstructor struct {
// pos = New
// end = Rparen + 1

New token.Pos
Type *NamedType

Args []*NewConstructorArg

Rparen token.Pos
}

// BracedNewConstructor represents NEW operator which creates a protocol buffer using a map constructor.
//
// NEW {{.Type | sql}} {{"{"}}{{"}"}}
type BracedNewConstructor struct {
// pos = New
// end = Rbrace + 1

New token.Pos
Type *NamedType
Body *BracedConstructor
}

// BracedConstructor represents a single map constructor which is used in BracedNewConstructor.
// Actually, it is a top level Expr in syntax, but it is not permitted semantically in other place.
//
// {{"{"}}{{.Fields | sqlJoin ", "}}{{"}"}}
type BracedConstructor struct {
// pos = Lbrace
// end = Rbrace + 1

Lbrace, Rbrace token.Pos

Fields []*BracedConstructorField
}

// BracedConstructorField represents a single field in BracedConstructor.
//
// {{.Name | sql}} {{.Value | sql}}
type BracedConstructorField struct {
// pos = Name.pos
// end = Value.end

Name *Ident
Value BracedConstructorFieldValue
}

// BracedConstructorFieldValueExpr represents a field value node.
//
// : {{.Expr | sql}}
type BracedConstructorFieldValueExpr struct {
// pos = Colon
// end = Expr.end

Colon token.Pos
Expr Expr
}

// ================================================================================
//
// Type
Expand Down
26 changes: 26 additions & 0 deletions ast/pos.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,32 @@ func (t *NumericLiteral) End() token.Pos { return t.Value.End() }
func (t *JSONLiteral) Pos() token.Pos { return t.JSON }
func (t *JSONLiteral) End() token.Pos { return t.Value.End() }

// ================================================================================
//
// NEW constructors
//
// ================================================================================

func (n *NewConstructorArg) Pos() token.Pos { return n.Expr.Pos() }
func (n *NewConstructorArg) End() token.Pos {
return firstValidEnd(n.Alias, n.Expr)
}

func (n *NewConstructor) Pos() token.Pos { return n.New }
func (n *NewConstructor) End() token.Pos { return n.Rparen + 1 }

func (b *BracedNewConstructor) Pos() token.Pos { return b.New }
func (b *BracedNewConstructor) End() token.Pos { return b.Body.End() }

func (b *BracedConstructor) Pos() token.Pos { return b.Lbrace }
func (b *BracedConstructor) End() token.Pos { return b.Rbrace + 1 }

func (b *BracedConstructorField) Pos() token.Pos { return b.Name.Pos() }
func (b *BracedConstructorField) End() token.Pos { return b.Value.End() }

func (b *BracedConstructorFieldValueExpr) Pos() token.Pos { return b.Colon }
func (b *BracedConstructorFieldValueExpr) End() token.Pos { return b.Expr.End() }

// ================================================================================
//
// Type
Expand Down
33 changes: 33 additions & 0 deletions ast/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,39 @@ func (t *JSONLiteral) SQL() string {
return "JSON " + t.Value.SQL()
}

// ================================================================================
//
// NEW constructors
//
// ================================================================================

func (n *NewConstructorArg) SQL() string {
return n.Expr.SQL() + sqlOpt(" ", n.Alias, "")
}

func (n *NewConstructor) SQL() string {
return "NEW " + n.Type.SQL() + "(" + sqlJoin(n.Args, ", ") + ")"
}

func (b *BracedNewConstructor) SQL() string {
return "NEW " + b.Type.SQL() + " " + b.Body.SQL()
}

func (b *BracedConstructor) SQL() string {
return "{" + sqlJoin(b.Fields, ", ") + "}"
}

func (b *BracedConstructorField) SQL() string {
if _, ok := b.Value.(*BracedConstructor); ok {
// Name {...}
return b.Name.SQL() + " " + b.Value.SQL()
}
// Name: value
return b.Name.SQL() + b.Value.SQL()
}

func (b *BracedConstructorFieldValueExpr) SQL() string { return ": " + b.Expr.SQL() }

// ================================================================================
//
// Type
Expand Down
2 changes: 1 addition & 1 deletion lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (l *Lexer) consumeToken() {
}

switch l.peek(0) {
case '(', ')', '{', '}', ';', ',', '[', ']', '~', '*', '/', '&', '^', '+', '-':
case '(', ')', '{', '}', ';', ',', '[', ']', '~', '*', '/', '&', '^', '+', '-', ':':
l.Token.Kind = token.TokenKind([]byte{l.skip()})
return
case '.':
Expand Down
105 changes: 105 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,11 @@ func (p *Parser) parseLit() ast.Expr {
return p.parseSimpleArrayLiteral()
case "(":
return p.parseParenExpr()
case "NEW":
return p.parseNewConstructors()
// In parser level, it is a valid ast.Expr, but it is semantically valid only in ast.BracedConstructorFieldExpr.
case "{":
return p.parseBracedConstructor()
case token.TokenIdent:
id := p.Token
p.nextToken()
Expand Down Expand Up @@ -2008,6 +2013,106 @@ func (p *Parser) parseStructTypeFields() (fields []*ast.StructField, gt token.Po
return
}

func (p *Parser) parseNewConstructorArg() *ast.NewConstructorArg {
expr := p.parseExpr()
var alias *ast.AsAlias

apstndb marked this conversation as resolved.
Show resolved Hide resolved
// Whole "AS alias" is optional, but "AS" keyword can't be omitted.
if p.Token.Kind == "AS" {
alias = p.tryParseAsAlias()
}

return &ast.NewConstructorArg{
Expr: expr,
Alias: alias,
}
}
func (p *Parser) parseNewConstructor(newPos token.Pos, namedType *ast.NamedType) *ast.NewConstructor {
p.expect("(")

// Args can be empty like `NEW pkg.TypeName ()`.
var args []*ast.NewConstructorArg
if p.Token.Kind != ")" {
args = parseCommaSeparatedList(p, p.parseNewConstructorArg)
}

rparen := p.expect(")").Pos
return &ast.NewConstructor{
New: newPos,
Type: namedType,
Args: args,
Rparen: rparen,
}
}

func (p *Parser) parseBracedNewConstructorField() *ast.BracedConstructorField {
name := p.parseIdent()
var fieldValue ast.BracedConstructorFieldValue
switch p.Token.Kind {
case ":":
colon := p.expect(":").Pos
expr := p.parseExpr()
fieldValue = &ast.BracedConstructorFieldValueExpr{Colon: colon, Expr: expr}
case "{":
fieldValue = p.parseBracedConstructor()
}
return &ast.BracedConstructorField{Name: name, Value: fieldValue}
}

func (p *Parser) parseBracedConstructor() *ast.BracedConstructor {
lbrace := p.expect("{").Pos

// Braced constructor permits empty.
var fields []*ast.BracedConstructorField
for {
if p.Token.Kind == "}" {
break
}

if p.Token.Kind != token.TokenIdent {
p.panicfAtToken(&p.Token, "expect <ident>, but %v", p.Token.Kind)
}
fields = append(fields, p.parseBracedNewConstructorField())

// It is an optional comma.
if p.Token.Kind == "," {
p.nextToken()
}
}

rbrace := p.expect("}").Pos

return &ast.BracedConstructor{
Lbrace: lbrace,
Rbrace: rbrace,
Fields: fields,
}
}

func (p *Parser) parseBracedNewConstructor(newPos token.Pos, namedType *ast.NamedType) *ast.BracedNewConstructor {
body := p.parseBracedConstructor()
return &ast.BracedNewConstructor{
New: newPos,
Type: namedType,
Body: body,
}
}

func (p *Parser) parseNewConstructors() ast.Expr {
newPos := p.expect("NEW").Pos
namedType := p.parseNamedType()

switch p.Token.Kind {
case "(":
return p.parseNewConstructor(newPos, namedType)
case "{":
return p.parseBracedNewConstructor(newPos, namedType)
default:
p.panicfAtToken(&p.Token, `expect '{' or '(', but %v`, p.Token.Kind)
}
return nil
}

func (p *Parser) parseFieldType() *ast.StructField {
lexer := p.Lexer.Clone()
// Try to parse as "x INT64" case.
Expand Down
17 changes: 17 additions & 0 deletions testdata/input/expr/braced_new_constructor.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Example from https://cloud.google.com/spanner/docs/reference/standard-sql/operators#new_operator
NEW Universe {
name: "Sol"
closest_planets: ["Mercury", "Venus", "Earth" ]
star {
radius_miles: 432690
age: 4603000000
}
constellations: [{
name: "Libra"
index: 0
}, {
name: "Scorpio"
index: 1
}]
all_planets: (SELECT planets FROM SolTable)
}
1 change: 1 addition & 0 deletions testdata/input/expr/new_constructor.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEW googlesql.examples.music.Chart(key AS rank, name AS chart_name)
Loading