Initial stubs of the public API

This commit is contained in:
Martin Atkins 2017-05-13 17:44:11 -07:00
parent bde4c176b9
commit b9183e85e4
5 changed files with 262 additions and 0 deletions

63
zcl/diagnostic.go Normal file
View 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
View File

@ -0,0 +1 @@
package zcl

88
zcl/pos.go Normal file
View 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
View 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
View 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
}