| // 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) |
| } |