diff --git a/zcl/json/parser.go b/zcl/json/parser.go index 7e13674..ef35985 100644 --- a/zcl/json/parser.go +++ b/zcl/json/parser.go @@ -58,6 +58,15 @@ func parseValue(p *peeker) (node, zcl.Diagnostics) { Subject: &tok.Range, }, } + case tokenEOF: + return nil, zcl.Diagnostics{ + { + Severity: zcl.DiagError, + Summary: "Missing value", + Detail: "The JSON data ends prematurely.", + Subject: &tok.Range, + }, + } default: return nil, zcl.Diagnostics{ { @@ -132,7 +141,7 @@ Token: valNode, valDiags := parseValue(p) diags = diags.Extend(valDiags) - if keyNode == nil { + if valNode == nil { return nil, diags } @@ -157,14 +166,14 @@ Token: switch p.Peek().Type { case tokenComma: - p.Read() + comma := p.Read() if p.Peek().Type == tokenBraceC { // Special error message for this common mistake return nil, diags.Append(&zcl.Diagnostic{ Severity: zcl.DiagError, Summary: "Trailing comma in object", Detail: "JSON does not permit a trailing comma after the final attribute in an object.", - Subject: &colon.Range, + Subject: &comma.Range, }) } continue Token @@ -189,7 +198,7 @@ Token: Severity: zcl.DiagError, Summary: "Missing attribute seperator comma", Detail: "A comma must appear between each attribute declaration in an object.", - Subject: &colon.Range, + Subject: p.Peek().Range.Ptr(), }) } @@ -204,7 +213,78 @@ Token: } func parseArray(p *peeker) (node, zcl.Diagnostics) { - return nil, nil + var diags zcl.Diagnostics + + open := p.Read() + vals := []node{} + +Token: + for { + if p.Peek().Type == tokenBrackC { + break Token + } + + valNode, valDiags := parseValue(p) + diags = diags.Extend(valDiags) + if valNode == nil { + return nil, diags + } + + vals = append(vals, valNode) + + switch p.Peek().Type { + case tokenComma: + comma := p.Read() + if p.Peek().Type == tokenBrackC { + // Special error message for this common mistake + return nil, diags.Append(&zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Trailing comma in array", + Detail: "JSON does not permit a trailing comma after the final attribute in an array.", + Subject: &comma.Range, + }) + } + continue Token + case tokenColon: + return nil, diags.Append(&zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Invalid array value", + Detail: "A colon is not used to introduce values in a JSON array.", + Subject: p.Peek().Range.Ptr(), + }) + case tokenEOF: + return nil, diags.Append(&zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Unclosed object", + Detail: "No closing bracket was found for this JSON array.", + Subject: &open.Range, + }) + case tokenBraceC: + return nil, diags.Append(&zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Mismatched brackets", + Detail: "A JSON array must be closed with a bracket, not a brace.", + Subject: p.Peek().Range.Ptr(), + }) + case tokenBrackC: + break Token + default: + return nil, diags.Append(&zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Missing attribute seperator comma", + Detail: "A comma must appear between each value in an array.", + Subject: p.Peek().Range.Ptr(), + }) + } + + } + + close := p.Read() + return &arrayVal{ + Values: vals, + SrcRange: zcl.RangeBetween(open.Range, close.Range), + OpenRange: open.Range, + }, diags } func parseNumber(p *peeker) (node, zcl.Diagnostics) { diff --git a/zcl/json/parser_test.go b/zcl/json/parser_test.go index 78021db..a5ba789 100644 --- a/zcl/json/parser_test.go +++ b/zcl/json/parser_test.go @@ -314,6 +314,136 @@ func TestParse(t *testing.T) { nil, 1, }, + { + `[]`, + &arrayVal{ + Values: []node{}, + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 3, Byte: 2}, + }, + OpenRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + }, + }, + 0, + }, + { + `[true]`, + &arrayVal{ + Values: []node{ + &booleanVal{ + Value: true, + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + End: zcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + }, + }, + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + OpenRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + }, + }, + 0, + }, + { + `[true, false]`, + &arrayVal{ + Values: []node{ + &booleanVal{ + Value: true, + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + End: zcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + }, + &booleanVal{ + Value: false, + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: zcl.Pos{Line: 1, Column: 13, Byte: 12}, + }, + }, + }, + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + OpenRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + }, + }, + 0, + }, + { + `[[]]`, + &arrayVal{ + Values: []node{ + &arrayVal{ + Values: []node{}, + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + End: zcl.Pos{Line: 1, Column: 4, Byte: 3}, + }, + OpenRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + End: zcl.Pos{Line: 1, Column: 3, Byte: 2}, + }, + }, + }, + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 5, Byte: 4}, + }, + OpenRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + }, + }, + 0, + }, + { + `[`, + nil, + 1, + }, + { + `[true`, + nil, + 1, + }, + { + `]`, + nil, + 1, + }, + { + `[true,]`, + nil, + 1, + }, + { + `[[],]`, + nil, + 1, + }, + { + `["hello":true]`, + nil, + 1, + }, + { + `[true}`, + nil, + 1, + }, } for _, test := range tests {