blob: bdb71bb7d4d9a1a0a4fc456b4279c7d646ad8943 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package parse
import (
"fmt"
"path"
"strconv"
"strings"
"v.io/x/ref/lib/vdl/vdlutil"
)
// Pos captures positional information during parsing.
type Pos struct {
Line int // Line number, starting at 1
Col int // Column number (character count), starting at 1
}
// StringPos holds a string and a Pos.
type StringPos struct {
String string
Pos Pos
}
// Returns true iff this Pos has been initialized. The zero Pos is invalid.
func (p Pos) IsValid() bool {
return p.Line > 0 && p.Col > 0
}
func (p Pos) String() string {
if !p.IsValid() {
return "[no pos]"
}
return fmt.Sprintf("%v:%v", p.Line, p.Col)
}
// InferPackageName returns the package name from a group of files. Every file
// must specify the same package name, otherwise an error is reported in errs.
func InferPackageName(files []*File, errs *vdlutil.Errors) (pkgName string) {
var firstFile string
for _, f := range files {
switch {
case pkgName == "":
firstFile = f.BaseName
pkgName = f.PackageDef.Name
case pkgName != f.PackageDef.Name:
errs.Errorf("Files in the same directory must be in the same package; %v has package %v, but %v has package %v", firstFile, pkgName, f.BaseName, f.PackageDef.Name)
}
}
return
}
// Representation of the components of an vdl file. These data types represent
// the parse tree generated by the parse.
// File represents a parsed vdl file.
type File struct {
BaseName string // Base name of the vdl file, e.g. "foo.vdl"
Doc string // Top-level file documentation
PackageDef NamePos // Name, position and docs of the "package" clause
Imports []*Import // Imports listed in this file
ErrorDefs []*ErrorDef // Errors defined in this file
TypeDefs []*TypeDef // Types defined in this file
ConstDefs []*ConstDef // Consts defined in this file
Interfaces []*Interface // Interfaces defined in this file
}
// Config represents a parsed config file. Config files use a similar syntax as
// vdl files, with similar concepts.
type Config struct {
FileName string // Config file name, e.g. "a/b/foo.config"
Doc string // Top-level config file documentation
ConfigDef NamePos // Name, position and docs of the "config" clause
Imports []*Import // Imports listed in this file.
Config ConstExpr // Const expression exported from this config.
ConstDefs []*ConstDef // Consts defined in this file.
}
// AddImports adds the path imports that don't already exist to c.
func (c *Config) AddImports(path ...string) {
for _, p := range path {
if !c.HasImport(p) {
c.Imports = append(c.Imports, &Import{Path: p})
}
}
}
// HasImport returns true iff path exists in c.Imports.
func (c *Config) HasImport(path string) bool {
for _, imp := range c.Imports {
if imp.Path == path {
return true
}
}
return false
}
// Import represents an import definition, which is used to import other
// packages into an vdl file. An example of the syntax in the vdl file:
// import foo "some/package/path"
type Import struct {
NamePos // e.g. foo (from above), or typically empty
Path string // e.g. "some/package/path" (from above)
}
// LocalName returns the name used locally within the File to refer to the
// imported package.
func (i *Import) LocalName() string {
if i.Name != "" {
return i.Name
}
return path.Base(i.Path)
}
// ErrorDef represents an error definition.
type ErrorDef struct {
NamePos // error name, pos and doc
Params []*Field // list of positional parameters
Actions []StringPos // list of action code identifiers
Formats []LangFmt // list of language / format pairs
}
// LangFmt represents a language / format string pair.
type LangFmt struct {
Lang StringPos // IETF language tag
Fmt StringPos // i18n format string in the given language
}
// Pos returns the position of the LangFmt.
func (x LangFmt) Pos() Pos {
if x.Lang.Pos.IsValid() {
return x.Lang.Pos
}
return x.Fmt.Pos
}
// Interface represents a set of embedded interfaces and methods.
type Interface struct {
NamePos // interface name, pos and doc
Embeds []*NamePos // names of embedded interfaces
Methods []*Method // list of methods
}
// Method represents a method in an interface.
type Method struct {
NamePos // method name, pos and doc
InArgs []*Field // list of positional in-args
OutArgs []*Field // list of positional out-args
InStream Type // in-stream type, may be nil
OutStream Type // out-stream type, may be nil
Tags []ConstExpr // list of method tags
}
// Field represents fields in structs as well as method arguments.
type Field struct {
NamePos // field name, pos and doc
Type Type // field type, never nil
}
// NamePos represents a name, its associated position and documentation.
type NamePos struct {
Name string
Pos Pos // position of first character in name
Doc string // docs that occur before the item
DocSuffix string // docs that occur on the same line after the item
}
func (x *File) String() string { return fmt.Sprintf("%+v", *x) }
func (x *Import) String() string { return fmt.Sprintf("%+v", *x) }
func (x *ErrorDef) String() string { return fmt.Sprintf("%+v", *x) }
func (x *Interface) String() string { return fmt.Sprintf("%+v", *x) }
func (x *Method) String() string { return fmt.Sprintf("%+v", *x) }
func (x *Field) String() string { return fmt.Sprintf("%+v", *x) }
func (x *NamePos) String() string { return fmt.Sprintf("%+v", *x) }
// QuoteStripDoc takes a Doc string, which includes comment markers /**/ and
// double-slash, and returns a raw-quoted string.
//
// TODO(toddw): This should remove comment markers. This is non-trivial, since
// we should handle removing leading whitespace "rectangles", and might want to
// retain inline /**/ or adjacent /**/ on the same line. For now we just leave
// them in the output.
func QuoteStripDoc(doc string) string {
trimmed := strings.Trim(doc, "\n")
if strconv.CanBackquote(doc) {
return "`" + trimmed + "`"
}
return strconv.Quote(trimmed)
}