Initial stubs of the public API
This commit is contained in:
parent
bde4c176b9
commit
b9183e85e4
63
zcl/diagnostic.go
Normal file
63
zcl/diagnostic.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package zcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiagnosticSeverity represents the severity of a diagnostic.
|
||||||
|
type DiagnosticSeverity int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DiagInvalid is the invalid zero value of DiagnosticSeverity
|
||||||
|
DiagInvalid DiagnosticSeverity = iota
|
||||||
|
|
||||||
|
// DiagError indicates that the problem reported by a diagnostic prevents
|
||||||
|
// further progress in parsing and/or evaluating the subject.
|
||||||
|
DiagError
|
||||||
|
|
||||||
|
// DiagWarning indicates that the problem reported by a diagnostic warrants
|
||||||
|
// user attention but does not prevent further progress. It is most
|
||||||
|
// commonly used for showing deprecation notices.
|
||||||
|
DiagWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
// Diagnostic represents information to be presented to a user about an
|
||||||
|
// error or anomoly in parsing or evaluating configuration.
|
||||||
|
type Diagnostic struct {
|
||||||
|
Severity DiagnosticSeverity
|
||||||
|
|
||||||
|
// Summary and detail contain the English-language description of the
|
||||||
|
// problem. Summary is a terse description of the general problem and
|
||||||
|
// detail is a more elaborate, often-multi-sentence description of
|
||||||
|
// the probem and what might be done to solve it.
|
||||||
|
Summary string
|
||||||
|
Detail string
|
||||||
|
Subject *Range
|
||||||
|
Context *Range
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagnostics is a list of Diagnostic instances.
|
||||||
|
type Diagnostics []Diagnostic
|
||||||
|
|
||||||
|
// error implementation, so that diagnostics can be returned via APIs
|
||||||
|
// that normally deal in vanilla Go errors.
|
||||||
|
//
|
||||||
|
// This presents only minimal context about the error, for compatibility
|
||||||
|
// with usual expectations about how errors will present as strings.
|
||||||
|
func (d *Diagnostic) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", d.Subject.Start, d.Summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// error implementation, so that sets of diagnostics can be returned via
|
||||||
|
// APIs that normally deal in vanilla Go errors.
|
||||||
|
func (d Diagnostics) Error() string {
|
||||||
|
count := len(d)
|
||||||
|
switch {
|
||||||
|
case count == 0:
|
||||||
|
return "no diagnostics"
|
||||||
|
case count == 1:
|
||||||
|
return d[0].Error()
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%s, and %d other diagnostic(s)", d[0].Error(), count-1)
|
||||||
|
}
|
||||||
|
}
|
1
zcl/doc.go
Normal file
1
zcl/doc.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package zcl
|
88
zcl/pos.go
Normal file
88
zcl/pos.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package zcl
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Pos represents a single position in a source file, by addressing the
|
||||||
|
// start byte of a unicode character encoded in UTF-8.
|
||||||
|
//
|
||||||
|
// Pos is generally used only in the context of a Range, which then defines
|
||||||
|
// which source file the position is within.
|
||||||
|
type Pos struct {
|
||||||
|
// Line is the source code line where this position points. Lines are
|
||||||
|
// counted starting at 1 and incremented for each newline character
|
||||||
|
// encountered.
|
||||||
|
Line int
|
||||||
|
|
||||||
|
// Column is the source code column where this position points, in
|
||||||
|
// unicode characters, with counting starting at 1.
|
||||||
|
//
|
||||||
|
// Column counts characters as they appear visually, so for example a
|
||||||
|
// latin letter with a combining diacritic mark counts as one character.
|
||||||
|
// This is intended for rendering visual markers against source code in
|
||||||
|
// contexts where these diacritics would be rendered in a single character
|
||||||
|
// cell. Technically speaking, Column is counting grapheme clusters as
|
||||||
|
// used in unicode normalization.
|
||||||
|
Column int
|
||||||
|
|
||||||
|
// Byte is the byte offset into the file where the indicated character
|
||||||
|
// begins. This is a zero-based offset to the first byte of the first
|
||||||
|
// UTF-8 codepoint sequence in the character, and thus gives a position
|
||||||
|
// that can be resolved _without_ awareness of Unicode characters.
|
||||||
|
Byte int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range represents a span of characters between two positions in a source
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// This struct is usually used by value in types that represent AST nodes,
|
||||||
|
// but by pointer in types that refer to the positions of other objects,
|
||||||
|
// such as in diagnostics.
|
||||||
|
type Range struct {
|
||||||
|
// Filename is the name of the file into which this range's positions
|
||||||
|
// point.
|
||||||
|
Filename string
|
||||||
|
|
||||||
|
// Start and End represent the bounds of this range. Start is inclusive
|
||||||
|
// and End is exclusive.
|
||||||
|
Start, End Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeBetween returns a new range that spans from the beginning of the
|
||||||
|
// start range to the end of the end range.
|
||||||
|
//
|
||||||
|
// The result is meaningless if the two ranges do not belong to the same
|
||||||
|
// source file or if the end range appears before the start range.
|
||||||
|
func RangeBetween(start, end Range) Range {
|
||||||
|
return Range{
|
||||||
|
Filename: start.Filename,
|
||||||
|
Start: start.Start,
|
||||||
|
End: end.End,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsOffset returns true if and only if the given byte offset is within
|
||||||
|
// the receiving Range.
|
||||||
|
func (r *Range) ContainsOffset(offset int) bool {
|
||||||
|
return offset >= r.Start.Byte && offset < r.End.Byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a compact string representation of the receiver.
|
||||||
|
// Callers should generally prefer to present a range more visually,
|
||||||
|
// e.g. via markers directly on the relevant portion of source code.
|
||||||
|
func (r *Range) String() string {
|
||||||
|
if r.Start.Line == r.End.Line {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s:%d,%d-%d",
|
||||||
|
r.Filename,
|
||||||
|
r.Start.Line, r.Start.Column,
|
||||||
|
r.End.Column,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s:%d,%d-%d,%d",
|
||||||
|
r.Filename,
|
||||||
|
r.Start.Line, r.Start.Column,
|
||||||
|
r.End.Line, r.End.Column,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
15
zcl/schema.go
Normal file
15
zcl/schema.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package zcl
|
||||||
|
|
||||||
|
// ElementHeaderSchema represents the shape of an element header, and is
|
||||||
|
// used for matching elements within bodies.
|
||||||
|
type ElementHeaderSchema struct {
|
||||||
|
Name string
|
||||||
|
LabelNames []string
|
||||||
|
Single bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodySchema represents the desired shallow structure of a body.
|
||||||
|
type BodySchema struct {
|
||||||
|
Attributes []string
|
||||||
|
Elements []ElementHeaderSchema
|
||||||
|
}
|
95
zcl/structure.go
Normal file
95
zcl/structure.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package zcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/apparentlymart/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File is the top-level node that results from parsing a ZCL file.
|
||||||
|
type File struct {
|
||||||
|
Body Body
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element represents a nested block within a Body.
|
||||||
|
type Element struct {
|
||||||
|
Type string
|
||||||
|
Labels []string
|
||||||
|
Body Body
|
||||||
|
|
||||||
|
DefRange Range // Range that can be considered the "definition" for seeking in an editor
|
||||||
|
TypeRange Range // Range for the element type declaration specifically.
|
||||||
|
LabelRanges []Range // Ranges for the label values specifically.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elements is a sequence of Element.
|
||||||
|
type Elements []*Element
|
||||||
|
|
||||||
|
// Body is a container for attributes and elements. It serves as the primary
|
||||||
|
// unit of heirarchical structure within configuration.
|
||||||
|
//
|
||||||
|
// The content of a body cannot be meaningfully intepreted without a schema,
|
||||||
|
// so Body represents the raw body content and has methods that allow the
|
||||||
|
// content to be extracted in terms of a given schema.
|
||||||
|
type Body interface {
|
||||||
|
// Content verifies that the entire body content conforms to the given
|
||||||
|
// schema and then returns it, and/or returns diagnostics. The returned
|
||||||
|
// body content is valid if non-nil, regardless of whether Diagnostics
|
||||||
|
// are provided, but diagnostics should still be eventually shown to
|
||||||
|
// the user.
|
||||||
|
Content(schema *BodySchema) (*BodyContent, Diagnostics)
|
||||||
|
|
||||||
|
// PartialContent is like Content except that it permits the configuration
|
||||||
|
// to contain additional elements or attributes not specified in the
|
||||||
|
// schema. If any are present, the returned Body is non-nil and contains
|
||||||
|
// the remaining items from the body that were not selected by the schema.
|
||||||
|
PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyContent is the result of applying a BodySchema to a Body.
|
||||||
|
type BodyContent struct {
|
||||||
|
Attributes map[string]Attribute
|
||||||
|
Elements Elements
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attribute represents an attribute from within a body.
|
||||||
|
type Attribute struct {
|
||||||
|
Name string
|
||||||
|
Expr Expression
|
||||||
|
|
||||||
|
Range Range
|
||||||
|
NameRange Range
|
||||||
|
ExprRange Range
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expression is a literal value or an expression provided in the
|
||||||
|
// configuration, which can be evaluated within a scope to produce a value.
|
||||||
|
type Expression interface {
|
||||||
|
LiteralValue() cty.Value
|
||||||
|
// TODO: evaluation of non-literal expressions
|
||||||
|
}
|
||||||
|
|
||||||
|
// OfType filters the receiving element sequence by element type name,
|
||||||
|
// returning a new element sequence including only the elements of the
|
||||||
|
// requested type.
|
||||||
|
func (els Elements) OfType(typeName string) Elements {
|
||||||
|
ret := make(Elements, 0)
|
||||||
|
for _, el := range els {
|
||||||
|
if el.Type == typeName {
|
||||||
|
ret = append(ret, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByType transforms the receiving elements sequence into a map from type
|
||||||
|
// name to element sequences of only that type.
|
||||||
|
func (els Elements) ByType() map[string]Elements {
|
||||||
|
ret := make(map[string]Elements)
|
||||||
|
for _, el := range els {
|
||||||
|
ty := el.Type
|
||||||
|
if ret[ty] == nil {
|
||||||
|
ret[ty] = make(Elements, 0, 1)
|
||||||
|
}
|
||||||
|
ret[ty] = append(ret[ty], el)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user