Skip to content

Commit

Permalink
Read/parse errors now return locations
Browse files Browse the repository at this point in the history
  • Loading branch information
200sc committed Dec 22, 2020
1 parent a8dc37b commit b68cbf7
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 63 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ At `main/bebopfmt` there is a cli utility to format and rewrite bop files. It ta

The following is a list of known issues with the current version of the project, ordered by approximate priority for addressing them.

Tokenization and parsing errors do not currently report locations (line number, character position / column of the error's source).

Original bebop does not support one .bop file importing type definitions from another .bop file, and so neither does this, yet.

- This is nontrivial, and requires a lot of design toward the importing / packaging ecosystem.
Expand Down
47 changes: 26 additions & 21 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func ReadFile(r io.Reader) (File, error) {
continue
case tokenKindEnum:
if nextRecordOpCode != 0 {
return f, fmt.Errorf("enums may not have attached op codes")
return f, readError(tk, "enums may not have attached op codes")
}
en, err := readEnum(tr)
if err != nil {
Expand Down Expand Up @@ -79,7 +79,7 @@ func expectAnyOfNext(tr *tokenReader, kinds ...tokenKind) error {
return tr.Err()
}
if !next {
return fmt.Errorf("expected (%v), got no token", kinds)
return readError(tr.nextToken, "expected (%v), got no token", kinds)
}
tk := tr.Token()
found := false
Expand All @@ -89,7 +89,7 @@ func expectAnyOfNext(tr *tokenReader, kinds ...tokenKind) error {
}
}
if !found {
return fmt.Errorf("expected (%v) got %s", kinds, tk.kind)
return readError(tk, "expected (%v) got %s", kinds, tk.kind)
}
return nil
}
Expand All @@ -102,11 +102,11 @@ func expectNext(tr *tokenReader, kinds ...tokenKind) ([]token, error) {
return tokens, tr.Err()
}
if !next {
return tokens, fmt.Errorf("expected (%v), got no token", kinds)
return tokens, readError(tr.nextToken, "expected (%v), got no token", kinds)
}
tk := tr.Token()
if tk.kind != k {
return tokens, fmt.Errorf("expected (%v) got %s", k, tk.kind)
return tokens, readError(tk, "expected (%v) got %s", k, tk.kind)
}
tokens[i] = tk
}
Expand Down Expand Up @@ -135,7 +135,7 @@ func readEnum(tr *tokenReader) (Enum, error) {
nextIsDeprecated := false
for tr.Token().kind != tokenKindCloseCurly {
if !tr.Next() {
return en, fmt.Errorf("enum definition ended early")
return en, readError(tr.nextToken, "enum definition ended early")
}
tk := tr.Token()
switch tk.kind {
Expand All @@ -149,7 +149,7 @@ func readEnum(tr *tokenReader) (Enum, error) {
}
optInteger, err := strconv.ParseInt(string(toks[1].concrete), 10, 32)
if err != nil {
return en, err
return en, readError(toks[1], err.Error())
}
en.Options = append(en.Options, EnumOption{
Name: optName,
Expand All @@ -164,7 +164,7 @@ func readEnum(tr *tokenReader) (Enum, error) {

case tokenKindOpenSquare:
if nextIsDeprecated {
return en, fmt.Errorf("expected enum option following deprecated annotation")
return en, readError(tk, "expected enum option following deprecated annotation")
}
msg, err := readDeprecated(tr)
if err != nil {
Expand Down Expand Up @@ -227,7 +227,7 @@ func readStruct(tr *tokenReader) (Struct, error) {
nextIsDeprecated := false
for tr.Token().kind != tokenKindCloseCurly {
if !tr.Next() {
return st, fmt.Errorf("struct definition ended early")
return st, readError(tr.nextToken, "struct definition ended early")
}
tk := tr.Token()
switch tk.kind {
Expand Down Expand Up @@ -258,7 +258,7 @@ func readStruct(tr *tokenReader) (Struct, error) {
skipEndOfLineComments(tr)
case tokenKindOpenSquare:
if nextIsDeprecated {
return st, fmt.Errorf("expected field following deprecated annotation")
return st, readError(tk, "expected field following deprecated annotation")
}
msg, err := readDeprecated(tr)
if err != nil {
Expand Down Expand Up @@ -293,10 +293,10 @@ func readFieldType(tr *tokenReader) (FieldType, error) {
return ft, err
}
if keyType.Map != nil || keyType.Array != nil {
return ft, fmt.Errorf("map must begin with simple type")
return ft, readError(tk, "map must begin with simple type")
}
if !isPrimitiveType(keyType.Simple) {
return ft, fmt.Errorf("map must begin with simple type")
return ft, readError(tk, "map must begin with simple type")
}
if _, err := expectNext(tr, tokenKindComma); err != nil {
return ft, err
Expand Down Expand Up @@ -360,7 +360,7 @@ func readMessage(tr *tokenReader) (Message, error) {
nextIsDeprecated := false
for tr.Token().kind != tokenKindCloseCurly {
if !tr.Next() {
return msg, fmt.Errorf("message definition ended early")
return msg, readError(tr.nextToken, "message definition ended early")
}
tk := tr.Token()
switch tk.kind {
Expand All @@ -369,12 +369,14 @@ func readMessage(tr *tokenReader) (Message, error) {
case tokenKindInteger:
fdInteger, err := strconv.ParseInt(string(tr.Token().concrete), 10, 8)
if err != nil {
return msg, err
return msg, readError(tr.nextToken, err.Error())
}
if _, ok := msg.Fields[uint8(fdInteger)]; ok {
return msg, readError(tr.nextToken, "message has duplicate field index %d", fdInteger)
}
if _, err := expectNext(tr, tokenKindArrow); err != nil {
return msg, err
}

fdType, err := readFieldType(tr)
if err != nil {
return msg, err
Expand All @@ -384,9 +386,7 @@ func readMessage(tr *tokenReader) (Message, error) {
return msg, err
}
fdName := string(toks[0].concrete)
if _, ok := msg.Fields[uint8(fdInteger)]; ok {
return msg, fmt.Errorf("message has duplicate field index %d", fdInteger)
}

msg.Fields[uint8(fdInteger)] = Field{
Name: fdName,
FieldType: fdType,
Expand All @@ -401,7 +401,7 @@ func readMessage(tr *tokenReader) (Message, error) {
skipEndOfLineComments(tr)
case tokenKindOpenSquare:
if nextIsDeprecated {
return msg, fmt.Errorf("expected field following deprecated annotation")
return msg, readError(tk, "expected field following deprecated annotation")
}
dpMsg, err := readDeprecated(tr)
if err != nil {
Expand Down Expand Up @@ -432,13 +432,13 @@ func readOpCode(tr *tokenReader) (int32, error) {
content := string(tk.concrete)
opc, err := strconv.ParseInt(content, 0, 32)
if err != nil {
return 0, err
return 0, readError(tk, err.Error())
}
opCode = int32(opc)
} else if tk.kind == tokenKindStringLiteral {
tk.concrete = bytes.Trim(tk.concrete, "\"")
if len(tk.concrete) > 4 {
return 0, fmt.Errorf("opcode string %s exceeds 4 ascii characters", string(tk.concrete))
return 0, readError(tk, "opcode string %s exceeds 4 ascii characters", string(tk.concrete))
}
opCode = bytesToOpCode(tk.concrete)
}
Expand Down Expand Up @@ -466,3 +466,8 @@ func bytesToOpCode(data []byte) int32 {
}
return opCode
}

func readError(tk token, format string, args ...interface{}) error {
format = fmt.Sprintf("[%d:%d] ", tk.loc.line, tk.loc.lineChar) + format
return fmt.Errorf(format, args...)
}
80 changes: 40 additions & 40 deletions read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1068,46 +1068,46 @@ func TestReadFileError(t *testing.T) {
errMessage string
}
tcs := []testCase{
{file: "invalid_enum_with_op_code", errMessage: "enums may not have attached op codes"},
{file: "invalid_op_code_1", errMessage: "expected (OpCode) got Close Square"},
{file: "invalid_op_code_2", errMessage: "expected (OpCode) got Ident"},
{file: "invalid_op_code_3", errMessage: "opcode string 12345 exceeds 4 ascii characters"},
{file: "invalid_op_code_4", errMessage: "expected (Open Paren) got Open Square"},
{file: "invalid_op_code_5", errMessage: "strconv.ParseInt: parsing \"1111111111111111111111111111111111111111111111111111111111111111111111111\": value out of range"},
{file: "invalid_op_code_6", errMessage: "expected (Close Paren) got Close Square"},
{file: "invalid_op_code_7", errMessage: "expected (Close Square) got Equals"},
{file: "invalid_op_code_8", errMessage: "expected ([Integer String Literal]) got Ident"},
{file: "invalid_enum_bad_deprecated", errMessage: "expected (String Literal) got Equals"},
{file: "invalid_enum_double_deprecated", errMessage: "expected enum option following deprecated annotation"},
{file: "invalid_enum_hex_int", errMessage: "strconv.ParseInt: parsing \"0x1\": invalid syntax"},
{file: "invalid_enum_no_close", errMessage: "enum definition ended early"},
{file: "invalid_enum_no_curly", errMessage: "expected (Open Curly) got Newline"},
{file: "invalid_enum_no_eq", errMessage: "expected (Equals) got Integer"},
{file: "invalid_enum_no_int", errMessage: "expected (Integer) got Semicolon"},
{file: "invalid_enum_no_name", errMessage: "expected (Ident) got Open Curly"},
{file: "invalid_enum_no_semi", errMessage: "expected (Semicolon) got Newline"},
{file: "invalid_struct_bad_deprecated", errMessage: "expected (String Literal) got Ident"},
{file: "invalid_struct_bad_type", errMessage: "expected ([Ident Array Map]) got Open Square"},
{file: "invalid_struct_double_deprecated", errMessage: "expected field following deprecated annotation"},
{file: "invalid_struct_no_close", errMessage: "struct definition ended early"},
{file: "invalid_struct_no_curly", errMessage: "expected (Open Curly) got Newline"},
{file: "invalid_struct_no_field_name", errMessage: "expected (Ident) got Semicolon"},
{file: "invalid_struct_no_name", errMessage: "expected (Ident) got Open Curly"},
{file: "invalid_struct_no_semi", errMessage: "expected (Semicolon) got Newline"},
{file: "invalid_message_bad_deprecated", errMessage: "expected (String Literal) got Arrow"},
{file: "invalid_message_bad_type", errMessage: "expected ([Ident Array Map]) got Open Square"},
{file: "invalid_message_double_deprecated", errMessage: "expected field following deprecated annotation"},
{file: "invalid_message_hex_int", errMessage: "strconv.ParseInt: parsing \"0x1\": invalid syntax"},
{file: "invalid_message_no_arrow", errMessage: "expected (Arrow) got Ident"},
{file: "invalid_message_no_close", errMessage: "message definition ended early"},
{file: "invalid_message_no_curly", errMessage: "expected (Open Curly) got Newline"},
{file: "invalid_message_no_field_name", errMessage: "expected (Ident) got Semicolon"},
{file: "invalid_message_no_name", errMessage: "expected (Ident) got Open Curly"},
{file: "invalid_message_no_semi", errMessage: "expected (Semicolon) got Newline"},
{file: "invalid_enum_reserved", errMessage: "expected (Ident) got Struct"},
{file: "invalid_struct_reserved", errMessage: "expected (Ident) got Array"},
{file: "invalid_message_reserved", errMessage: "expected (Ident) got Map"},
{file: "invalid_message_duplicate_index", errMessage: "message has duplicate field index 1"},
{file: "invalid_enum_with_op_code", errMessage: "[1:5] enums may not have attached op codes"},
{file: "invalid_op_code_1", errMessage: "[0:2] expected (OpCode) got Close Square"},
{file: "invalid_op_code_2", errMessage: "[0:7] expected (OpCode) got Ident"},
{file: "invalid_op_code_3", errMessage: "[0:16] opcode string 12345 exceeds 4 ascii characters"},
{file: "invalid_op_code_4", errMessage: "[0:9] expected (Open Paren) got Open Square"},
{file: "invalid_op_code_5", errMessage: "[0:82] strconv.ParseInt: parsing \"1111111111111111111111111111111111111111111111111111111111111111111111111\": value out of range"},
{file: "invalid_op_code_6", errMessage: "[0:16] expected (Close Paren) got Close Square"},
{file: "invalid_op_code_7", errMessage: "[0:17] expected (Close Square) got Equals"},
{file: "invalid_op_code_8", errMessage: "[0:15] expected ([Integer String Literal]) got Ident"},
{file: "invalid_enum_bad_deprecated", errMessage: "[1:18] expected (String Literal) got Equals"},
{file: "invalid_enum_double_deprecated", errMessage: "[2:5] expected enum option following deprecated annotation"},
{file: "invalid_enum_hex_int", errMessage: "[1:14] strconv.ParseInt: parsing \"0x1\": invalid syntax"},
{file: "invalid_enum_no_close", errMessage: "[2:0] enum definition ended early"},
{file: "invalid_enum_no_curly", errMessage: "[1:0] expected (Open Curly) got Newline"},
{file: "invalid_enum_no_eq", errMessage: "[1:10] expected (Equals) got Integer"},
{file: "invalid_enum_no_int", errMessage: "[1:11] expected (Integer) got Semicolon"},
{file: "invalid_enum_no_name", errMessage: "[0:7] expected (Ident) got Open Curly"},
{file: "invalid_enum_no_semi", errMessage: "[2:0] expected (Semicolon) got Newline"},
{file: "invalid_struct_bad_deprecated", errMessage: "[1:22] expected (String Literal) got Ident"},
{file: "invalid_struct_bad_type", errMessage: "[1:10] expected ([Ident Array Map]) got Open Square"},
{file: "invalid_struct_double_deprecated", errMessage: "[2:5] expected field following deprecated annotation"},
{file: "invalid_struct_no_close", errMessage: "[1:16] struct definition ended early"},
{file: "invalid_struct_no_curly", errMessage: "[1:0] expected (Open Curly) got Newline"},
{file: "invalid_struct_no_field_name", errMessage: "[1:11] expected (Ident) got Semicolon"},
{file: "invalid_struct_no_name", errMessage: "[0:9] expected (Ident) got Open Curly"},
{file: "invalid_struct_no_semi", errMessage: "[2:0] expected (Semicolon) got Newline"},
{file: "invalid_message_bad_deprecated", errMessage: "[1:19] expected (String Literal) got Arrow"},
{file: "invalid_message_bad_type", errMessage: "[1:15] expected ([Ident Array Map]) got Open Square"},
{file: "invalid_message_double_deprecated", errMessage: "[2:5] expected field following deprecated annotation"},
{file: "invalid_message_hex_int", errMessage: "[1:7] strconv.ParseInt: parsing \"0x1\": invalid syntax"},
{file: "invalid_message_no_arrow", errMessage: "[1:12] expected (Arrow) got Ident"},
{file: "invalid_message_no_close", errMessage: "[1:21] message definition ended early"},
{file: "invalid_message_no_curly", errMessage: "[1:0] expected (Open Curly) got Newline"},
{file: "invalid_message_no_field_name", errMessage: "[1:16] expected (Ident) got Semicolon"},
{file: "invalid_message_no_name", errMessage: "[0:10] expected (Ident) got Open Curly"},
{file: "invalid_message_no_semi", errMessage: "[2:0] expected (Semicolon) got Newline"},
{file: "invalid_enum_reserved", errMessage: "[0:13] expected (Ident) got Struct"},
{file: "invalid_struct_reserved", errMessage: "[0:14] expected (Ident) got Array"},
{file: "invalid_message_reserved", errMessage: "[0:13] expected (Ident) got Map"},
{file: "invalid_message_duplicate_index", errMessage: "[2:2] message has duplicate field index 1"},
}
for _, tc := range tcs {
tc := tc
Expand Down
1 change: 1 addition & 0 deletions token.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bebop
type token struct {
kind tokenKind
concrete []byte
loc location
}

type tokenKind uint8
Expand Down
2 changes: 2 additions & 0 deletions tokenize.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type nextResp struct {
func (tr *tokenReader) setNextToken(tk token) {
tr.lastToken = tr.nextToken
tr.nextToken = tk
tr.nextToken.loc = tr.loc
}

func (tr *tokenReader) readByte() (byte, error) {
Expand Down Expand Up @@ -350,6 +351,7 @@ func (tr *tokenReader) Token() token {
injectedToken := token{
kind: tokenKindSemicolon,
concrete: []byte{';'},
loc: tr.loc,
}
tr.keepNextToken = true
tr.lastToken = injectedToken
Expand Down

0 comments on commit b68cbf7

Please sign in to comment.