Skip to content

Commit

Permalink
🐛 Fix variable declaration parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
ChmielewskiKamil committed Sep 7, 2024
1 parent 047a162 commit f03c460
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 56 deletions.
51 changes: 32 additions & 19 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ type (
Function Expression // Function being called
Args []Expression // Comma-separated list of arguments
}

ElementaryTypeExpression struct {
Pos token.Pos // position of the type keyword e.g. `a` in "address"
Kind token.Token // type of the literal e.g. token.ADDRESS, token.UINT_256, token.BOOL
// Used in expressions e.g. `return uint256(a + b)`. Then it
// contains the expression a + b
Value Expression
}
)

// Start() and End() implementations for Expression type Nodes
Expand Down Expand Up @@ -120,17 +128,25 @@ func (x *CallExpression) End() token.Pos {
}
return x.Pos + 2 // length of "()"
}
func (x *ElementaryTypeExpression) Start() token.Pos { return x.Pos }
func (x *ElementaryTypeExpression) End() token.Pos {
if x.Value != nil {
return x.Value.End()
}
return x.Pos + token.Pos(len(x.Kind.Literal))
}

// expressionNode() implementations to ensure that only expressions can be
// assigned to an Expression. This is useful if by mistake we try to use
// a Statement in a place where an Expression should be used instead.

func (*Identifier) expressionNode() {}
func (*NumberLiteral) expressionNode() {}
func (*BooleanLiteral) expressionNode() {}
func (*PrefixExpression) expressionNode() {}
func (*InfixExpression) expressionNode() {}
func (*CallExpression) expressionNode() {}
func (*Identifier) expressionNode() {}
func (*NumberLiteral) expressionNode() {}
func (*BooleanLiteral) expressionNode() {}
func (*PrefixExpression) expressionNode() {}
func (*InfixExpression) expressionNode() {}
func (*CallExpression) expressionNode() {}
func (*ElementaryTypeExpression) expressionNode() {}

// String() implementations for Expressions

Expand Down Expand Up @@ -174,6 +190,16 @@ func (x *CallExpression) String() string {
out.WriteString(")")
return out.String()
}
func (x *ElementaryTypeExpression) String() string {
var out bytes.Buffer
out.WriteString(x.Kind.Literal)
if x.Value != nil {
out.WriteString("(")
out.WriteString(x.Value.String())
out.WriteString(")")
}
return out.String()
}

/*~*~*~*~*~*~*~*~*~*~*~*~*~* Types ~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*/
// Type nodes are constrains on expressions. They define the kinds of values
Expand All @@ -189,17 +215,8 @@ func (x *CallExpression) String() string {
type ElementaryType struct {
Pos token.Pos // position of the type keyword e.g. `a` in "address"
Kind token.Token // type of the literal e.g. token.ADDRESS, token.UINT_256, token.BOOL
// nil when used as declaration e.g. `address a;`
// BUT also used in expressions e.g. `return uint256(a + b)`. Then it
// contains the expression a + b
Value Expression
}

// WARNING ElementaryType implements both Type and Expression interfaces.
// It can be used as a type e.g. in variable declaration `uint256 x;` OR
// as an expression in return statement `return uint256(a + b);`
func (x *ElementaryType) expressionNode() {}

// FunctionType represents a Solidity's function type. NOT TO BE CONFUSED WITH
// FUNCTION DECLARATION. FunctionType is a weird thing that no one uses (lol) e.g.
// ```solidity
Expand Down Expand Up @@ -263,10 +280,6 @@ func (*ElementaryType) typeNode() {}
func (x *ElementaryType) String() string {
var out bytes.Buffer
out.WriteString(x.Kind.Literal)
if x.Value != nil {
out.WriteString(" ")
out.WriteString(x.Value.String())
}
return out.String()
}

Expand Down
2 changes: 1 addition & 1 deletion parser/exprparsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ func (p *Parser) parseElementaryTypeExpression() ast.Expression {
defer un(trace("parseElementaryType"))
}

et := &ast.ElementaryType{
et := &ast.ElementaryTypeExpression{
Pos: p.currTkn.Pos,
Kind: token.Token{
Type: p.currTkn.Type,
Expand Down
83 changes: 47 additions & 36 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,17 +182,19 @@ func (p *Parser) parseFunctionDeclaration() *ast.FunctionDeclaration {
// - function types
// - arrays of other types
// - mappings (?)

if !token.IsElementaryType(p.peekTkn.Type) {
p.errors.Add(p.peekTkn.Pos, "Fn param: expected elementary type, got: "+p.peekTkn.Literal)
return nil
}
p.nextToken() // Move to the type name.

prm.Type = &ast.ElementaryType{
Pos: p.currTkn.Pos,
Kind: p.currTkn,
Value: nil, // Here it's a type, not an expression.
Pos: p.currTkn.Pos,
Kind: token.Token{
Type: p.currTkn.Type,
Literal: p.currTkn.Literal,
Pos: p.currTkn.Pos,
},
}

if token.IsDataLocation(p.peekTkn.Type) {
Expand Down Expand Up @@ -259,9 +261,12 @@ func (p *Parser) parseStateVariableDeclaration() *ast.StateVariableDeclaration {

// We are sitting on the variable type e.g. address or uint256
decl.Type = &ast.ElementaryType{
Pos: p.currTkn.Pos,
Kind: p.currTkn,
Value: nil,
Pos: p.currTkn.Pos,
Kind: token.Token{
Type: p.currTkn.Type,
Literal: p.currTkn.Literal,
Pos: p.currTkn.Pos,
},
}

p.nextToken()
Expand Down Expand Up @@ -418,40 +423,46 @@ func (p *Parser) parseVariableDeclarationStatement() *ast.VariableDeclarationSta
Literal: p.currTkn.Literal,
Pos: p.currTkn.Pos,
},
Value: nil,
}

p.nextToken()
if !p.expectPeek(token.IDENTIFIER) {
return nil
}

for {
switch tkType := p.currTkn.Type; {
default:
p.errors.Add(p.currTkn.Pos, "Unexpected token: "+p.currTkn.Literal)
return nil
case tkType == token.IDENTIFIER:
vdStmt.Name = &ast.Identifier{
Pos: p.currTkn.Pos,
Value: p.currTkn.Literal,
}
p.nextToken()
case token.IsDataLocation(tkType):
switch tkType {
case token.STORAGE:
vdStmt.DataLocation = ast.Storage
case token.MEMORY:
vdStmt.DataLocation = ast.Memory
case token.CALLDATA:
vdStmt.DataLocation = ast.Calldata
}
p.nextToken()
case tkType == token.ASSIGN:
p.nextToken()
vdStmt.Value = p.parseExpression(LOWEST)
p.nextToken()
case tkType == token.SEMICOLON:
return vdStmt
vdStmt.Name = &ast.Identifier{
Pos: p.currTkn.Pos,
Value: p.currTkn.Literal,
}

if token.IsDataLocation(p.peekTkn.Type) {
p.nextToken()
switch p.currTkn.Type {
case token.STORAGE:
vdStmt.DataLocation = ast.Storage
case token.MEMORY:
vdStmt.DataLocation = ast.Memory
case token.CALLDATA:
vdStmt.DataLocation = ast.Calldata
}
}

if p.peekTknIs(token.SEMICOLON) {
p.nextToken()
return vdStmt
}

if !p.expectPeek(token.ASSIGN) {
return nil
}

p.nextToken() // Move past the ASSIGN token
vdStmt.Value = p.parseExpression(LOWEST)

if p.peekTknIs(token.SEMICOLON) {
p.nextToken()
}

return vdStmt
}

func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
Expand Down

0 comments on commit f03c460

Please sign in to comment.