diff --git a/parser/parser.go b/parser/parser.go index 93c51e25..54f29460 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -46,6 +46,8 @@ func (p *parser) parseSequence(ctx *context) (*ast.SequenceNode, error) { node := ast.Sequence(ctx.currentToken(), true) node.SetPath(ctx.path) ctx.progress(1) // skip SequenceStart token + + isFirst := true for ctx.next() { tk := ctx.currentToken() if tk.Type == token.SequenceEndType { @@ -53,15 +55,27 @@ func (p *parser) parseSequence(ctx *context) (*ast.SequenceNode, error) { break } else if tk.Type == token.CollectEntryType { ctx.progress(1) - continue + } else if !isFirst { + return nil, errors.ErrSyntax("',' or ']' must be specified", tk) + } + + if tk := ctx.currentToken(); tk != nil && tk.Type == token.SequenceEndType { + // this case is here: "[ elem, ]". + // In this case, ignore the last element and break sequence parsing. + node.End = tk + break } - value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))), tk) + value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))), ctx.currentToken()) if err != nil { return nil, err } node.Values = append(node.Values, value) ctx.progress(1) + isFirst = false + } + if node.End == nil || node.End.Type != token.SequenceEndType { + return nil, errors.ErrSyntax("sequence end token ']' not found", node.Start) } return node, nil } @@ -227,7 +241,7 @@ func (p *parser) validateMapValue(ctx *context, key, value ast.Node) error { } ntk := ctx.nextToken() if ntk == nil || (ntk.Type != token.MappingValueType && ntk.Type != token.SequenceEntryType) { - return errors.ErrSyntax("could not found expected ':' token", value.GetToken()) + return errors.ErrSyntax("could not find expected ':' token", value.GetToken()) } return nil } @@ -272,8 +286,16 @@ func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) { ntk := ctx.nextNotCommentToken() antk := ctx.afterNextNotCommentToken() - for antk != nil && antk.Type == token.MappingValueType && - ntk.Position.Column == key.GetToken().Position.Column { + for ntk != nil && ntk.Position.Column == key.GetToken().Position.Column { + if ntk.Type == token.DocumentHeaderType || ntk.Type == token.DocumentEndType { + break + } + if antk == nil { + return nil, errors.ErrSyntax("required ':' and map value", ntk) + } + if antk.Type != token.MappingValueType { + return nil, errors.ErrSyntax("required ':' and map value", antk) + } ctx.progressIgnoreComment(1) value, err := p.parseToken(ctx, ctx.currentToken()) if err != nil { @@ -667,6 +689,10 @@ func (p *parser) createNodeFromToken(ctx *context, tk *token.Token) (ast.Node, e return p.parseMapping(ctx) case token.SequenceStartType: return p.parseSequence(ctx) + case token.SequenceEndType: + // SequenceEndType is always validated in parseSequence. + // Therefore, if this is found in other cases, it is treated as a syntax error. + return nil, errors.ErrSyntax("could not find '[' character corresponding to ']'", tk) case token.SequenceEntryType: return p.parseSequenceEntry(ctx) case token.AnchorType: diff --git a/parser/parser_test.go b/parser/parser_test.go index 5d378b9d..c6e33180 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -724,6 +724,56 @@ a: |invalidopt ^ 3 | foo`, }, + { + ` +a: 1 +b +`, + ` +[3:1] required ':' and map value + 2 | a: 1 +> 3 | b + ^ +`, + }, + { + ` +a: 1 +b +- c +`, + ` +[4:1] required ':' and map value + 2 | a: 1 + 3 | b +> 4 | - c + ^ +`, + }, + { + `a: [`, + ` +[1:4] sequence end token ']' not found +> 1 | a: [ + ^ +`, + }, + { + `a: ]`, + ` +[1:4] could not find '[' character corresponding to ']' +> 1 | a: ] + ^ +`, + }, + { + `a: [ [1] [2] [3] ]`, + ` +[1:10] ',' or ']' must be specified +> 1 | a: [ [1] [2] [3] ] + ^ +`, + }, } for _, test := range tests { t.Run(test.source, func(t *testing.T) { diff --git a/scanner/scanner.go b/scanner/scanner.go index 13295767..224d47b3 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -649,7 +649,7 @@ func (s *Scanner) scanFlowArrayStart(ctx *Context) bool { } func (s *Scanner) scanFlowArrayEnd(ctx *Context) bool { - if s.startedFlowSequenceNum <= 0 { + if ctx.existsBuffer() && s.startedFlowSequenceNum <= 0 { return false }