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 Op and Sep Tokens in Expression Values #3984

Merged
merged 2 commits into from
Jul 1, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
### SDK Features
* `internal/ini`: The ini parser has been updated to support `[`, `]`, `:`, and `=` being present in section key values. ([#3958](https://github.com/aws/aws-sdk-go/issues/3958))

### SDK Enhancements

Expand Down
33 changes: 23 additions & 10 deletions internal/ini/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,30 @@
// }
//
// Below is the BNF that describes this parser
// Grammar:
// stmt -> value stmt'
// stmt' -> epsilon | op stmt
// value -> number | string | boolean | quoted_string
// Grammar:
// stmt -> section | stmt'
// stmt' -> epsilon | expr
// expr -> value (stmt)* | equal_expr (stmt)*
// equal_expr -> value ( ':' | '=' ) equal_expr'
// equal_expr' -> number | string | quoted_string
// quoted_string -> " quoted_string'
// quoted_string' -> string quoted_string_end
// quoted_string_end -> "
//
// section -> [ section'
// section' -> value section_close
// section_close -> ]
// section -> [ section'
// section' -> section_value section_close
// section_value -> number | string_subset | boolean | quoted_string_subset
// quoted_string_subset -> " quoted_string_subset'
// quoted_string_subset' -> string_subset quoted_string_end
// quoted_string_subset -> "
// section_close -> ]
//
// SkipState will skip (NL WS)+
// value -> number | string_subset | boolean
// string -> ? UTF-8 Code-Points except '\n' (U+000A) and '\r\n' (U+000D U+000A) ?
// string_subset -> ? Code-points excepted by <string> grammar except ':' (U+003A), '=' (U+003D), '[' (U+005B), and ']' (U+005D) ?
//
// comment -> # comment' | ; comment'
// comment' -> epsilon | value
// SkipState will skip (NL WS)+
//
// comment -> # comment' | ; comment'
// comment' -> epsilon | value
package ini
51 changes: 22 additions & 29 deletions internal/ini/ini_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import (
"io"
)

// ParseState represents the current state of the parser.
type ParseState uint

// State enums for the parse table
const (
InvalidState = iota
InvalidState ParseState = iota
// stmt -> value stmt'
StatementState
// stmt' -> MarkComplete | op stmt
Expand Down Expand Up @@ -36,24 +39,24 @@ const (
)

// parseTable is a state machine to dictate the grammar above.
var parseTable = map[ASTKind]map[TokenType]int{
ASTKindStart: map[TokenType]int{
var parseTable = map[ASTKind]map[TokenType]ParseState{
ASTKindStart: {
TokenLit: StatementState,
TokenSep: OpenScopeState,
TokenWS: SkipTokenState,
TokenNL: SkipTokenState,
TokenComment: CommentState,
TokenNone: TerminalState,
},
ASTKindCommentStatement: map[TokenType]int{
ASTKindCommentStatement: {
TokenLit: StatementState,
TokenSep: OpenScopeState,
TokenWS: SkipTokenState,
TokenNL: SkipTokenState,
TokenComment: CommentState,
TokenNone: MarkCompleteState,
},
ASTKindExpr: map[TokenType]int{
ASTKindExpr: {
TokenOp: StatementPrimeState,
TokenLit: ValueState,
TokenSep: OpenScopeState,
Expand All @@ -62,46 +65,48 @@ var parseTable = map[ASTKind]map[TokenType]int{
TokenComment: CommentState,
TokenNone: MarkCompleteState,
},
ASTKindEqualExpr: map[TokenType]int{
TokenLit: ValueState,
TokenWS: SkipTokenState,
TokenNL: SkipState,
TokenNone: SkipState,
ASTKindEqualExpr: {
TokenLit: ValueState,
TokenSep: ValueState,
TokenOp: ValueState,
TokenWS: SkipTokenState,
TokenNL: SkipState,
TokenNone: SkipState,
},
ASTKindStatement: map[TokenType]int{
ASTKindStatement: {
TokenLit: SectionState,
TokenSep: CloseScopeState,
TokenWS: SkipTokenState,
TokenNL: SkipTokenState,
TokenComment: CommentState,
TokenNone: MarkCompleteState,
},
ASTKindExprStatement: map[TokenType]int{
ASTKindExprStatement: {
TokenLit: ValueState,
TokenSep: OpenScopeState,
TokenSep: ValueState,
TokenOp: ValueState,
TokenWS: ValueState,
TokenNL: MarkCompleteState,
TokenComment: CommentState,
TokenNone: TerminalState,
TokenComma: SkipState,
},
ASTKindSectionStatement: map[TokenType]int{
ASTKindSectionStatement: {
TokenLit: SectionState,
TokenOp: SectionState,
TokenSep: CloseScopeState,
TokenWS: SectionState,
TokenNL: SkipTokenState,
},
ASTKindCompletedSectionStatement: map[TokenType]int{
ASTKindCompletedSectionStatement: {
TokenWS: SkipTokenState,
TokenNL: SkipTokenState,
TokenLit: StatementState,
TokenSep: OpenScopeState,
TokenComment: CommentState,
TokenNone: MarkCompleteState,
},
ASTKindSkipStatement: map[TokenType]int{
ASTKindSkipStatement: {
TokenLit: StatementState,
TokenSep: OpenScopeState,
TokenWS: SkipTokenState,
Expand Down Expand Up @@ -205,18 +210,6 @@ loop:
case ValueState:
// ValueState requires the previous state to either be an equal expression
// or an expression statement.
//
// This grammar occurs when the RHS is a number, word, or quoted string.
// equal_expr -> lit op equal_expr'
// equal_expr' -> number | string | quoted_string
// quoted_string -> " quoted_string'
// quoted_string' -> string quoted_string_end
// quoted_string_end -> "
//
// otherwise
// expr_stmt -> equal_expr (expr_stmt')*
// expr_stmt' -> ws S | op S | MarkComplete
// S -> equal_expr' expr_stmt'
switch k.Kind {
case ASTKindEqualExpr:
// assigning a value to some key
Expand All @@ -243,7 +236,7 @@ loop:
}

children[len(children)-1] = rhs
k.SetChildren(children)
root.SetChildren(children)

stack.Push(k)
}
Expand Down
73 changes: 61 additions & 12 deletions internal/ini/ini_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ func TestParser(t *testing.T) {
outputID, _, _ := newLitToken([]rune("output"))
outputLit, _, _ := newLitToken([]rune("json"))

sepInValueID, _, _ := newLitToken([]rune("sepInValue"))
sepInValueLit := newToken(TokenOp, []rune("=:[foo]]bar["), StringType)

equalOp, _, _ := newOpToken([]rune("= 1234"))
equalColonOp, _, _ := newOpToken([]rune(": 1234"))
numLit, _, _ := newLitToken([]rune("1234"))
Expand Down Expand Up @@ -53,6 +56,9 @@ func TestParser(t *testing.T) {
outputEQExpr := newEqualExpr(newExpression(outputID), equalOp)
outputEQExpr.AppendChild(newExpression(outputLit))

sepInValueExpr := newEqualExpr(newExpression(sepInValueID), equalOp)
sepInValueExpr.AppendChild(newExpression(sepInValueLit))

cases := []struct {
name string
r io.Reader
Expand All @@ -67,24 +73,48 @@ func TestParser(t *testing.T) {
},
},
{
name: "0==0",
r: bytes.NewBuffer([]byte(`0==0`)),
expectedError: true,
name: "0==0",
r: bytes.NewBuffer([]byte(`0==0`)),
expectedStack: []AST{
func() AST {
equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalOp)
equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune("=0"), StringType)))
return newExprStatement(equalExpr)
}(),
},
},
{
name: "0=:0",
r: bytes.NewBuffer([]byte(`0=:0`)),
expectedError: true,
name: "0=:0",
r: bytes.NewBuffer([]byte(`0=:0`)),
expectedStack: []AST{
func() AST {
equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalOp)
equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune(":0"), StringType)))
return newExprStatement(equalExpr)
}(),
},
},
{
name: "0:=0",
r: bytes.NewBuffer([]byte(`0:=0`)),
expectedError: true,
name: "0:=0",
r: bytes.NewBuffer([]byte(`0:=0`)),
expectedStack: []AST{
func() AST {
equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalColonOp)
equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune("=0"), StringType)))
return newExprStatement(equalExpr)
}(),
},
},
{
name: "0::0",
r: bytes.NewBuffer([]byte(`0::0`)),
expectedError: true,
name: "0::0",
r: bytes.NewBuffer([]byte(`0::0`)),
expectedStack: []AST{
func() AST {
equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalColonOp)
equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune(":0"), StringType)))
return newExprStatement(equalExpr)
}(),
},
},
{
name: "section with variable",
Expand Down Expand Up @@ -302,6 +332,25 @@ s3 =`)),
newExprStatement(noQuotesRegionEQRegion),
},
},
{
name: "token seperators [ and ] in values",
r: bytes.NewBuffer([]byte(
`[default]
sepInValue = =:[foo]]bar[
output = json
[assumerole]
sepInValue==:[foo]]bar[
output = json
`)),
expectedStack: []AST{
newCompletedSectionStatement(defaultProfileStmt),
newExprStatement(sepInValueExpr),
newExprStatement(outputEQExpr),
newCompletedSectionStatement(assumeProfileStmt),
newExprStatement(sepInValueExpr),
newExprStatement(outputEQExpr),
},
},
}

for i, c := range cases {
Expand Down
1 change: 1 addition & 0 deletions internal/ini/testdata/invalid/bad_section_name
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[ :=foo ]
1 change: 1 addition & 0 deletions internal/ini/testdata/invalid/bad_syntax_2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[ foo ]]
2 changes: 2 additions & 0 deletions internal/ini/testdata/invalid/invalid_keys
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[assumerole]
key[id] = value
30 changes: 30 additions & 0 deletions internal/ini/testdata/valid/op_sep_in_values
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[case1]
sepInValue = =:[foo]]bar[
key:= value1

[case2]
sepInValue==:[foo]]bar[
key = value2

[case3]
sepInValue = []
key== value3

[case4]
sepInValue = [value] x=a
key:=value4

[case5]
key : value5

[case6]
s3 =
[nested6]
key = valuen6
key :=value6

[case7]
s3 =
key :value7
[sub7]
key ==values7
32 changes: 32 additions & 0 deletions internal/ini/testdata/valid/op_sep_in_values_expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"case1": {
"sepInValue": "=:[foo]]bar[",
"key": "= value1"
},
"case2": {
"sepInValue": "=:[foo]]bar[",
"key": "value2"
},
"case3": {
"sepInValue": "[]",
"key": "= value3"
},
"case4": {
"sepInValue": "[value] x=a",
"key": "=value4"
},
"case5": {
"key": "value5"
},
"case6": {
"s3": "",
"key": "=value6"
},
"case7": {
"s3": "",
"key": "value7"
},
"sub7": {
"key": "=values7"
}
}
5 changes: 4 additions & 1 deletion internal/ini/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ func (v *DefaultVisitor) VisitExpr(expr AST) error {

rhs := children[1]

if rhs.Root.Type() != TokenLit {
// The right-hand value side the equality expression is allowed to contain '[', ']', ':', '=' in the values.
// If the token is not either a literal or one of the token types that identifies those four additional
// tokens then error.
if !(rhs.Root.Type() == TokenLit || rhs.Root.Type() == TokenOp || rhs.Root.Type() == TokenSep) {
return NewParseError("unexpected token type")
}

Expand Down
12 changes: 12 additions & 0 deletions internal/ini/walker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ func TestInvalidDataFiles(t *testing.T) {
path: "./testdata/invalid/bad_syntax_1",
expectedParseError: true,
},
{
path: "./testdata/invalid/bad_syntax_2",
expectedParseError: true,
},
{
path: "./testdata/invalid/incomplete_section_profile",
expectedParseError: true,
Expand All @@ -113,6 +117,14 @@ func TestInvalidDataFiles(t *testing.T) {
path: "./testdata/invalid/syntax_error_comment",
expectedParseError: true,
},
{
path: "./testdata/invalid/invalid_keys",
expectedParseError: true,
},
{
path: "./testdata/invalid/bad_section_name",
expectedParseError: true,
},
}

for i, c := range cases {
Expand Down