Example #1
0
func (p *parser) readMessageContents(msg *ast.Message) *parseError {
	// Parse message fields and other things inside a message.
	var oneof *ast.Oneof // set while inside a oneof
	for !p.done {
		tok := p.next()
		if tok.err != nil {
			return tok.err
		}
		switch tok.value {
		case "extend":
			// extension
			p.back()
			ext := new(ast.Extension)
			msg.Extensions = append(msg.Extensions, ext)
			if err := p.readExtension(ext); err != nil {
				return err
			}
			ext.Up = msg
		case "oneof":
			// oneof
			if oneof != nil {
				return p.errorf("nested oneof not permitted")
			}
			oneof = new(ast.Oneof)
			msg.Oneofs = append(msg.Oneofs, oneof)
			oneof.Position = p.cur.astPosition()

			tok := p.next()
			if tok.err != nil {
				return tok.err
			}
			oneof.Name = tok.value // TODO: validate
			oneof.Up = msg

			if err := p.readToken("{"); err != nil {
				return err
			}
		case "message":
			// nested message
			p.back()
			nmsg := new(ast.Message)
			msg.Messages = append(msg.Messages, nmsg)
			if err := p.readMessage(nmsg); err != nil {
				return err
			}
			nmsg.Up = msg
		case "enum":
			// nested enum
			p.back()
			ne := new(ast.Enum)
			msg.Enums = append(msg.Enums, ne)
			if err := p.readEnum(ne); err != nil {
				return err
			}
			ne.Up = msg
		case "extensions":
			// extension range
			p.back()
			r, err := p.readExtensionRange()
			if err != nil {
				return err
			}
			msg.ExtensionRanges = append(msg.ExtensionRanges, r...)
		default:
			// field; this token is required/optional/repeated,
			// a primitive type, or a named type.
			p.back()
			field := new(ast.Field)
			msg.Fields = append(msg.Fields, field)
			field.Oneof = oneof
			field.Up = msg // p.readField uses this
			if err := p.readField(field); err != nil {
				return err
			}
		case "}":
			if oneof != nil {
				// end of oneof
				oneof = nil
				continue
			}
			// end of message
			p.back()
			return nil
		}
	}
	return p.errorf("unexpected EOF while parsing message")
}
Example #2
0
func (p *parser) readFile(f *ast.File) *parseError {
	// Parse top-level things.
	for !p.done {
		tok := p.next()
		if tok.err == eof {
			break
		} else if tok.err != nil {
			return tok.err
		}
		// TODO: enforce ordering? package, imports, remainder
		switch tok.value {
		case "package":
			if f.Package != nil {
				return p.errorf("duplicate package statement")
			}
			var pkg string
			for {
				tok := p.next()
				if tok.err != nil {
					return tok.err
				}
				if tok.value == ";" {
					break
				}
				if tok.value == "." {
					// okay if we already have at least one package component,
					// and didn't just read a dot.
					if pkg == "" || strings.HasSuffix(pkg, ".") {
						return p.errorf(`got ".", want package name`)
					}
				} else {
					// okay if we don't have a package component,
					// or just read a dot.
					if pkg != "" && !strings.HasSuffix(pkg, ".") {
						return p.errorf(`got %q, want "." or ";"`, tok.value)
					}
					// TODO: validate more
				}
				pkg += tok.value
			}
			f.Package = strings.Split(pkg, ".")
		case "option":
			tok := p.next()
			if tok.err != nil {
				return tok.err
			}
			key := tok.value
			if err := p.readToken("="); err != nil {
				return err
			}
			tok = p.next()
			if tok.err != nil {
				return tok.err
			}
			value := tok.value
			if err := p.readToken(";"); err != nil {
				return err
			}
			f.Options = append(f.Options, [2]string{key, value})
		case "syntax":
			if f.Syntax != "" {
				return p.errorf("duplicate syntax statement")
			}
			if err := p.readToken("="); err != nil {
				return err
			}
			tok, err := p.readString()
			if err != nil {
				return err
			}
			switch s := tok.unquoted; s {
			case "proto2", "proto3":
				f.Syntax = s
			default:
				return p.errorf("invalid syntax value %q", s)
			}
			if err := p.readToken(";"); err != nil {
				return err
			}
		case "import":
			if err := p.readToken("public"); err == nil {
				f.PublicImports = append(f.PublicImports, len(f.Imports))
			} else {
				p.back()
			}
			tok, err := p.readString()
			if err != nil {
				return err
			}
			f.Imports = append(f.Imports, tok.unquoted)
			if err := p.readToken(";"); err != nil {
				return err
			}
		case "message":
			p.back()
			msg := new(ast.Message)
			f.Messages = append(f.Messages, msg)
			if err := p.readMessage(msg); err != nil {
				return err
			}
			msg.Up = f
		case "enum":
			p.back()
			enum := new(ast.Enum)
			f.Enums = append(f.Enums, enum)
			if err := p.readEnum(enum); err != nil {
				return err
			}
			enum.Up = f
		case "service":
			p.back()
			srv := new(ast.Service)
			f.Services = append(f.Services, srv)
			if err := p.readService(srv); err != nil {
				return err
			}
			srv.Up = f
		case "extend":
			p.back()
			ext := new(ast.Extension)
			f.Extensions = append(f.Extensions, ext)
			if err := p.readExtension(ext); err != nil {
				return err
			}
			ext.Up = f
		default:
			return p.errorf("unknown top-level thing %q", tok.value)
		}
	}

	// Handle comments.
	for len(p.comments) > 0 {
		n := 1
		for ; n < len(p.comments); n++ {
			if p.comments[n].line != p.comments[n-1].line+1 {
				break
			}
		}
		c := &ast.Comment{
			Start: ast.Position{
				Line:   p.comments[0].line,
				Offset: p.comments[0].offset,
			},
			End: ast.Position{
				Line:   p.comments[n-1].line,
				Offset: p.comments[n-1].offset,
			},
		}
		for _, comm := range p.comments[:n] {
			c.Text = append(c.Text, comm.text)
		}
		p.comments = p.comments[n:]

		// Strip common whitespace prefix and any whitespace suffix.
		// TODO: this is a bodgy implementation of Longest Common Prefix,
		// and also doesn't do tabs vs. spaces well.
		var prefix string
		for i, line := range c.Text {
			line = strings.TrimRightFunc(line, unicode.IsSpace)
			c.Text[i] = line
			trim := len(line) - len(strings.TrimLeftFunc(line, unicode.IsSpace))
			if i == 0 {
				prefix = line[:trim]
			} else {
				// Check how much of prefix is in common.
				for !strings.HasPrefix(line, prefix) {
					prefix = prefix[:len(prefix)-1]
				}
			}
			if prefix == "" {
				break
			}
		}
		if prefix != "" {
			for i, line := range c.Text {
				c.Text[i] = strings.TrimPrefix(line, prefix)
			}
		}

		f.Comments = append(f.Comments, c)
	}
	// No need to sort comments; they are already in source order.

	return nil
}