Skip to content

Commit

Permalink
Support Op and Sep Tokens in Expression Values (#3984)
Browse files Browse the repository at this point in the history
* Support Op and Sep Tokens in Expression Values
* Additional Test Cases
  • Loading branch information
skmcgrail authored Jul 1, 2021
1 parent 610918a commit f09a23b
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 52 deletions.
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

0 comments on commit f09a23b

Please sign in to comment.