From 9e6d66ef4eefb830242078828fa86a0239e708e0 Mon Sep 17 00:00:00 2001 From: Ernest Micklei Date: Tue, 31 Jan 2017 23:02:06 +0100 Subject: [PATCH] work on other literals for option constant --- cmd/proto3fmt/formatter.go | 35 ++++++++++----------- enum.go | 10 ++---- enum_test.go | 2 +- field.go | 5 +-- field_test.go | 10 +++--- option.go | 50 ++++++++++++++++++++++++----- option_test.go | 64 +++++++++++++++++++++++++++----------- parser.go | 21 +++++++++++++ scanner.go | 25 +++++---------- scanner_test.go | 4 +-- syntax.go | 12 +++---- syntax_test.go | 2 +- token.go | 1 + 13 files changed, 151 insertions(+), 90 deletions(-) diff --git a/cmd/proto3fmt/formatter.go b/cmd/proto3fmt/formatter.go index 6d08ea6..7ae0314 100644 --- a/cmd/proto3fmt/formatter.go +++ b/cmd/proto3fmt/formatter.go @@ -29,7 +29,7 @@ func (f *formatter) VisitComment(c *proto3.Comment) { func (f *formatter) VisitEnum(e *proto3.Enum) { f.begin("enum") - fmt.Fprintf(f.w, "enum %s {\n", e.Name) + fmt.Fprintf(f.w, "enum %s {", e.Name) f.indentLevel++ for _, each := range e.Elements { each.Accept(f) @@ -50,20 +50,12 @@ func (f *formatter) VisitEnumField(e *proto3.EnumField) { } } -func (f *formatter) VisitField(f1 *proto3.Field) { - f.begin("field") - if f1.Repeated { - io.WriteString(f.w, "repeated ") - } - fmt.Fprintf(f.w, "%s %s = %d;\n", f1.Type, f1.Name, f1.Sequence) -} - func (f *formatter) VisitImport(i *proto3.Import) { f.begin("import") if len(i.Kind) > 0 { - fmt.Fprintf(f.w, "%s ", i.Kind) + fmt.Fprintf(f.w, "import %s ", i.Kind) } - fmt.Fprintf(f.w, "%q;\n", i.Filename) + fmt.Fprintf(f.w, "import %q;\n", i.Filename) } func (f *formatter) VisitMessage(m *proto3.Message) { @@ -92,11 +84,7 @@ func (f *formatter) VisitOption(o *proto3.Option) { io.WriteString(f.w, ")") } io.WriteString(f.w, " = ") - if len(o.String) > 0 { - fmt.Fprintf(f.w, "%q", o.String) - } else { - fmt.Fprintf(f.w, "%s", o.Identifier) - } + io.WriteString(f.w, o.Constant.String()) if o.IsEmbedded { io.WriteString(f.w, "];\n") } else { @@ -176,15 +164,24 @@ func (f *formatter) VisitRPC(r *proto3.RPC) { } func (f *formatter) VisitMapField(m *proto3.MapField) { - panic("VisitMapField") + f.begin("map") + fmt.Fprintf(f.w, "map<%s,%s> %s = %d;\n", m.KeyType, m.Type, m.Name, m.Sequence) +} + +func (f *formatter) VisitNormalField(f1 *proto3.NormalField) { + f.begin("field") + if f1.Repeated { + io.WriteString(f.w, "repeated ") + } + fmt.Fprintf(f.w, "%s %s = %d;\n", f1.Type, f1.Name, f1.Sequence) } // Utils func (f *formatter) begin(stmt string) { if f.lastStmt != stmt && len(f.lastStmt) > 0 { // not the first line - // add separator because stmt is changed, unless it was comment or a nested thingy - if !strings.Contains("comment message enum", f.lastStmt) { + // add separator because stmt is changed, unless it nested thingy + if !strings.Contains("comment", f.lastStmt) { io.WriteString(f.w, "\n") } } diff --git a/enum.go b/enum.go index ab462dc..b92f998 100644 --- a/enum.go +++ b/enum.go @@ -1,9 +1,6 @@ package proto3 -import ( - "fmt" - "strconv" -) +import "fmt" // Enum definition consists of a name and an enum body. type Enum struct { @@ -39,10 +36,9 @@ func (f *EnumField) parse(p *Parser) error { if tok != tEQUALS { return p.unexpected(lit, "=") } - is := p.s.scanIntegerString() - i, err := strconv.Atoi(is) + i, err := p.s.scanInteger() if err != nil { - return fmt.Errorf("found %q, expected integer", is) + return fmt.Errorf("found %q, expected integer", err) } f.Integer = i tok, lit = p.scanIgnoreWhitespace() diff --git a/enum_test.go b/enum_test.go index f2aa301..64966ac 100644 --- a/enum_test.go +++ b/enum_test.go @@ -33,7 +33,7 @@ enum EnumAllowingAlias { if got, want := ef3.ValueOption.Name, "custom_option"; got != want { t.Errorf("got [%v] want [%v]", got, want) } - if got, want := ef3.ValueOption.String, "hello world"; got != want { + if got, want := ef3.ValueOption.Constant.Source, "hello world"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } diff --git a/field.go b/field.go index a9217a0..fb234f5 100644 --- a/field.go +++ b/field.go @@ -1,7 +1,5 @@ package proto3 -import "strconv" - // Field is an abstract message field. type Field struct { Name string @@ -55,8 +53,7 @@ func parseFieldAfterType(f *Field, p *Parser) error { if tok != tEQUALS { return p.unexpected(lit, "=") } - lit = p.s.scanIntegerString() - i, err := strconv.Atoi(lit) + i, err := p.s.scanInteger() if err != nil { return p.unexpected(lit, "sequence number") } diff --git a/field_test.go b/field_test.go index 1896d7f..5e1cbc7 100644 --- a/field_test.go +++ b/field_test.go @@ -3,7 +3,7 @@ package proto3 import "testing" func TestField(t *testing.T) { - proto := `repeated foo.bar lots = 1 [option1=a, option2=b, option3="happy"];` + proto := `repeated foo.bar lots =1 [option1=a, option2=b, option3="happy"];` p := newParserOn(proto) f := newNormalField() err := f.parse(p) @@ -25,16 +25,16 @@ func TestField(t *testing.T) { if got, want := f.Options[0].Name, "option1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } - if got, want := f.Options[0].Identifier, "a"; got != want { + if got, want := f.Options[0].Constant.Source, "a"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[1].Name, "option2"; got != want { t.Errorf("got [%v] want [%v]", got, want) } - if got, want := f.Options[1].Identifier, "b"; got != want { + if got, want := f.Options[1].Constant.Source, "b"; got != want { t.Errorf("got [%v] want [%v]", got, want) } - if got, want := f.Options[2].String, "happy"; got != want { + if got, want := f.Options[2].Constant.Source, "happy"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } @@ -62,7 +62,7 @@ func TestFieldSimple(t *testing.T) { if got, want := f.Options[0].Name, "ctype"; got != want { t.Errorf("got [%v] want [%v]", got, want) } - if got, want := f.Options[0].Identifier, "STRING_PIECE"; got != want { + if got, want := f.Options[0].Constant.Source, "STRING_PIECE"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } diff --git a/option.go b/option.go index 38aec87..0799f02 100644 --- a/option.go +++ b/option.go @@ -5,8 +5,7 @@ import "fmt" // Option is a protoc compiler option type Option struct { Name string - String string // will be quoted - Identifier string // will not be quoted + Constant Literal IsEmbedded bool IsCustom bool // TODO needed? } @@ -49,19 +48,54 @@ func (o *Option) parse(p *Parser) error { if tok != tEQUALS { return p.unexpected(lit, "=") } - tok, lit = p.scanIgnoreWhitespace() + l := new(Literal) + if err := l.parse(p); err != nil { + return err + } + o.Constant = *l + return nil +} + +// Literal represents intLit,floatLit,strLit or boolLit +type Literal struct { + Source string + IsString bool +} + +// String returns the source (if quoted then use double quote). +func (l Literal) String() string { + if l.IsString { + return "\"" + l.Source + "\"" + } + return l.Source +} + +func (l *Literal) parse(p *Parser) error { + tok, lit := p.scanIgnoreWhitespace() // stringLiteral? if tok == tQUOTE { ident := p.s.scanUntil('"') if len(ident) == 0 { - return fmt.Errorf("unexpected end of quoted string") // TODO create constant for this + return p.unexpected(lit, "quoted string") + } + l.Source, l.IsString = ident, true + return nil + } + // stringLiteral? + if tok == tSINGLEQUOTE { + ident := p.s.scanUntil('\'') + if len(ident) == 0 { + return p.unexpected(lit, "single quoted string") } - o.String = ident + l.Source, l.IsString = ident, true return nil } - if tIDENT != tok { - return p.unexpected(lit, "constant") + // float, bool or intLit ? + if lit == "-" { // TODO token? + _, rem := p.s.scanIdent() + l.Source = "-" + rem + return nil } - o.Identifier = lit + l.Source = lit return nil } diff --git a/option_test.go b/option_test.go index d0c8523..4116947 100644 --- a/option_test.go +++ b/option_test.go @@ -3,23 +3,51 @@ package proto3 import "testing" func TestOption(t *testing.T) { - proto := `option (full.java_package) = "com.example.foo";` - p := newParserOn(proto) - pr, err := p.Parse() - if err != nil { - t.Fatal(err) - } - if got, want := len(pr.Elements), 1; got != want { - t.Errorf("got [%v] want [%v]", got, want) - } - o := pr.Elements[0].(*Option) - if got, want := o.Name, "full.java_package"; got != want { - t.Errorf("got [%v] want [%v]", got, want) - } - if got, want := o.String, "com.example.foo"; got != want { - t.Errorf("got [%v] want [%v]", got, want) - } - if got, want := o.IsEmbedded, false; got != want { - t.Errorf("got [%v] want [%v]", got, want) + for i, each := range []struct { + proto string + name string + strLit string + nonStrLit string + }{{ + `option (full.java_package) = "com.example.foo";`, + "full.java_package", + "com.example.foo", + "", + }, { + `option Bool = true;`, + "Bool", + "", + "true", + }, { + `option Float = -3.14E1;`, + "Float", + "", + "-3.14E1", + }} { + p := newParserOn(each.proto) + pr, err := p.Parse() + if err != nil { + t.Fatal(err) + } + if got, want := len(pr.Elements), 1; got != want { + t.Errorf("[%d] got [%v] want [%v]", i, got, want) + } + o := pr.Elements[0].(*Option) + if got, want := o.Name, each.name; got != want { + t.Errorf("[%d] got [%v] want [%v]", i, got, want) + } + if len(each.strLit) > 0 { + if got, want := o.Constant.Source, each.strLit; got != want { + t.Errorf("[%d] got [%v] want [%v]", i, got, want) + } + } + if len(each.nonStrLit) > 0 { + if got, want := o.Constant.Source, each.nonStrLit; got != want { + t.Errorf("[%d] got [%v] want [%v]", i, got, want) + } + } + if got, want := o.IsEmbedded, false; got != want { + t.Errorf("[%d] got [%v] want [%v]", i, got, want) + } } } diff --git a/parser.go b/parser.go index f813871..1bfa5ad 100644 --- a/parser.go +++ b/parser.go @@ -71,3 +71,24 @@ func (p *Parser) unexpected(found, expected string) error { } return fmt.Errorf("found %q on line %d, expected %s%s", found, p.s.line, expected, debug) } + +// read a single or double-quoted single-line string +// TODO used? +func (p *Parser) scanStringLiteral() (string, error) { + tok, lit := p.scanIgnoreWhitespace() + if tok == tQUOTE { + s := p.s.scanUntil('"') + if len(s) == 0 { + return "", p.unexpected(lit, "quoted string") + } + return s, nil + } + if tok == tSINGLEQUOTE { + s := p.s.scanUntil('\'') + if len(s) == 0 { + return "", p.unexpected(lit, "single quoted string") + } + return s, nil + } + return "", p.unexpected(lit, "single or double quoted string") +} diff --git a/scanner.go b/scanner.go index 9f27224..8068bbb 100644 --- a/scanner.go +++ b/scanner.go @@ -47,6 +47,8 @@ func (s *scanner) scan() (tok token, lit string) { return tEQUALS, string(ch) case '"': return tQUOTE, string(ch) + case '\'': + return tSINGLEQUOTE, string(ch) case '(': return tLEFTPAREN, string(ch) case ')': @@ -95,25 +97,12 @@ func (s *scanner) scanWhitespace() (tok token, lit string) { return tWS, buf.String() } -func (s *scanner) scanIntegerString() string { - s.scanWhitespace() - // Create a buffer. - var buf bytes.Buffer - - // Read every subsequent digit character into the buffer. - // Non-digit characters and EOF will cause the loop to exit. - // TODO handle sign correctly - for { - if ch := s.read(); ch == eof { - break - } else if !isDigit(ch) && ch != '-' { - s.unread(ch) - break - } else { - _, _ = buf.WriteRune(ch) - } +func (s *scanner) scanInteger() (int, error) { + var i int + if _, err := fmt.Fscanf(s.r, "%d", &i); err != nil { + return i, err } - return buf.String() + return i, nil } // scanIdent consumes the current rune and all contiguous ident runes. diff --git a/scanner_test.go b/scanner_test.go index 29e2a6f..3a4c32c 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -47,8 +47,8 @@ func TestScanSingleLineComment(t *testing.T) { func TestScanIntegerString(t *testing.T) { r := strings.NewReader("-1234;") s := newScanner(r) - if got, want := s.scanIntegerString(), "1234"; got != want { + i, _ := s.scanInteger() + if got, want := i, -1234; got != want { t.Errorf("got [%v] want [%v]", got, want) } - } diff --git a/syntax.go b/syntax.go index 937ac8d..d48c2e5 100644 --- a/syntax.go +++ b/syntax.go @@ -1,7 +1,5 @@ package proto3 -import "fmt" - // Syntax should have value "proto3" type Syntax struct { Value string @@ -16,15 +14,15 @@ func (s *Syntax) parse(p *Parser) error { if tok, lit := p.scanIgnoreWhitespace(); tok != tEQUALS { return p.unexpected(lit, "=") } - if tok, lit := p.scanIgnoreWhitespace(); tok != tQUOTE { - return fmt.Errorf("found %q, expected QUOTE", lit) + if tok, lit := p.scanIgnoreWhitespace(); tok != tQUOTE && tok != tSINGLEQUOTE { + return p.unexpected(lit, "\" or '") } tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { - return fmt.Errorf("found %q, expected string", lit) + return p.unexpected(lit, "proto3") } - if tok, lit := p.scanIgnoreWhitespace(); tok != tQUOTE { - return fmt.Errorf("found %q, expected QUOTE", lit) + if tok, lit := p.scanIgnoreWhitespace(); tok != tQUOTE && tok != tSINGLEQUOTE { + return p.unexpected(lit, "\" or '") } s.Value = lit return nil diff --git a/syntax_test.go b/syntax_test.go index ac76932..bdc0c05 100644 --- a/syntax_test.go +++ b/syntax_test.go @@ -23,7 +23,7 @@ func TestCommentAroundSyntax(t *testing.T) { proto := ` // comment1 // comment2 - syntax = "proto3"; // comment3 + syntax = 'proto3'; // comment3 // comment4 ` p := newParserOn(proto) diff --git a/token.go b/token.go index 5f64412..431698d 100644 --- a/token.go +++ b/token.go @@ -16,6 +16,7 @@ const ( tSEMICOLON // ; tEQUALS // = tQUOTE // " + tSINGLEQUOTE // ' tLEFTPAREN // ( tRIGHTPAREN // ) tLEFTCURLY // {