Skip to content

Commit

Permalink
Support Op and Sep Tokens in Expression Values (#1324)
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 431a408 commit eb286a1
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 33 deletions.
8 changes: 8 additions & 0 deletions .changelog/5082ddd069fe4504932a1a45b3eff82a.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "5082ddd0-69fe-4504-932a-1a45b3eff82a",
"type": "feature",
"description": "Support for `:`, `=`, `[`, `]` being present in expression values.",
"modules": [
"internal/ini"
]
}
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
25 changes: 9 additions & 16 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,7 +39,7 @@ const (
)

// parseTable is a state machine to dictate the grammar above.
var parseTable = map[ASTKind]map[TokenType]int{
var parseTable = map[ASTKind]map[TokenType]ParseState{
ASTKindStart: {
TokenLit: StatementState,
TokenSep: OpenScopeState,
Expand Down Expand Up @@ -64,6 +67,8 @@ var parseTable = map[ASTKind]map[TokenType]int{
},
ASTKindEqualExpr: {
TokenLit: ValueState,
TokenSep: ValueState,
TokenOp: ValueState,
TokenWS: SkipTokenState,
TokenNL: SkipState,
},
Expand All @@ -77,7 +82,7 @@ var parseTable = map[ASTKind]map[TokenType]int{
},
ASTKindExprStatement: {
TokenLit: ValueState,
TokenSep: OpenScopeState,
TokenSep: ValueState,
TokenOp: ValueState,
TokenWS: ValueState,
TokenNL: MarkCompleteState,
Expand Down Expand Up @@ -204,18 +209,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 @@ -242,7 +235,7 @@ loop:
}

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

stack.Push(k)
}
Expand Down
61 changes: 55 additions & 6 deletions internal/ini/ini_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,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 @@ -51,6 +54,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 @@ -65,24 +71,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,
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,
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,
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 @@ -287,6 +317,25 @@ output = json
newExprStatement(outputEQExpr),
},
},
{
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 @@ -63,7 +63,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 @@ -106,6 +106,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 @@ -114,6 +118,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 eb286a1

Please sign in to comment.