This ports things to the new textarea. We need to plumb through things a lot more, especially the string interpolation math to get the right offsets everywhere, but that's coming.
627 lines
23 KiB
Go
627 lines
23 KiB
Go
// Mgmt
|
|
// Copyright (C) James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// Additional permission under GNU GPL version 3 section 7
|
|
//
|
|
// If you modify this program, or any covered work, by linking or combining it
|
|
// with embedded mcl code and modules (and that the embedded mcl code and
|
|
// modules which link with this program, contain a copy of their source code in
|
|
// the authoritative form) containing parts covered by the terms of any other
|
|
// license, the licensors of this program grant you additional permission to
|
|
// convey the resulting work. Furthermore, the licensors of this program grant
|
|
// the original author, James Shubin, additional permission to update this
|
|
// additional permission if he deems it necessary to achieve the goals of this
|
|
// additional permission.
|
|
|
|
package interfaces
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/purpleidea/mgmt/engine"
|
|
"github.com/purpleidea/mgmt/lang/types"
|
|
"github.com/purpleidea/mgmt/pgraph"
|
|
"github.com/purpleidea/mgmt/util/errwrap"
|
|
)
|
|
|
|
// Node represents either a Stmt or an Expr. It contains the minimum set of
|
|
// methods that they must both implement. In practice it is not used especially
|
|
// often since we usually know which kind of node we want.
|
|
type Node interface {
|
|
//fmt.Stringer // already provided by pgraph.Vertex
|
|
pgraph.Vertex // must implement this since we store these in our graphs
|
|
|
|
// Apply is a general purpose iterator method that operates on any node.
|
|
Apply(fn func(Node) error) error
|
|
|
|
//Parent() Node // TODO: should we implement this?
|
|
}
|
|
|
|
// Stmt represents a statement node in the language. A stmt could be a resource,
|
|
// a `bind` statement, or even an `if` statement. (Different from an `if`
|
|
// expression.)
|
|
type Stmt interface {
|
|
Node
|
|
|
|
// Init initializes the populated node and does some basic validation.
|
|
Init(*Data) error
|
|
|
|
// Interpolate returns an expanded form of the AST as a new AST. It does
|
|
// a recursive interpolate (copy) of all members in the AST.
|
|
Interpolate() (Stmt, error) // return expanded form of AST as a new AST
|
|
|
|
// Copy returns a light copy of the struct. Anything static will not be
|
|
// copied. For a full recursive copy consider using Interpolate instead.
|
|
// TODO: do we need an error in the signature?
|
|
Copy() (Stmt, error)
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the
|
|
// data flow. This can be used in SetScope so that it knows the correct
|
|
// order to run it in.
|
|
Ordering(map[string]Node) (*pgraph.Graph, map[Node]string, error)
|
|
|
|
// SetScope sets the scope here and propagates it downwards.
|
|
SetScope(*Scope) error
|
|
|
|
// TypeCheck returns the list of invariants that this node produces. It
|
|
// does so recursively on any children elements that exist in the AST,
|
|
// and returns the collection to the caller. It calls TypeCheck for
|
|
// child statements, and Infer/Check for child expressions.
|
|
TypeCheck() ([]*UnificationInvariant, error)
|
|
|
|
// Graph returns the reactive function graph expressed by this node. It
|
|
// takes in the environment of any functions in scope. Intended to be
|
|
// called only once, creates and returns the graph and also stores it to
|
|
// be used in .Output if needed.
|
|
Graph(env *Env) (*pgraph.Graph, error)
|
|
|
|
// Output returns the output that this "program" produces. This output
|
|
// is what is used to build the output graph. It requires the input
|
|
// table of values that are used to populate each function.
|
|
Output(map[Func]types.Value) (*Output, error)
|
|
}
|
|
|
|
// Expr represents an expression in the language. Expr implementations must have
|
|
// their method receivers implemented as pointer receivers so that they can be
|
|
// easily copied and moved around. Expr also implements pgraph.Vertex so that
|
|
// these can be stored as pointers in our graph data structure.
|
|
type Expr interface {
|
|
Node
|
|
|
|
// Init initializes the populated node and does some basic validation.
|
|
Init(*Data) error
|
|
|
|
// Interpolate returns an expanded form of the AST as a new AST. It does
|
|
// a recursive interpolate (copy) of all members in the AST. For a light
|
|
// copy use Copy.
|
|
Interpolate() (Expr, error)
|
|
|
|
// Copy returns a light copy of the struct. Anything static will not be
|
|
// copied. For a full recursive copy consider using Interpolate instead.
|
|
// TODO: do we need an error in the signature?
|
|
Copy() (Expr, error)
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the
|
|
// data flow. This can be used in SetScope so that it knows the correct
|
|
// order to run it in.
|
|
Ordering(map[string]Node) (*pgraph.Graph, map[Node]string, error)
|
|
|
|
// SetScope sets the scope here and propagates it downwards.
|
|
SetScope(*Scope, map[string]Expr) error
|
|
|
|
// SetType sets the type definitively, and errors if it is incompatible.
|
|
SetType(*types.Type) error
|
|
|
|
// Type returns the type of this expression. It may speculate if it can
|
|
// determine it statically. This errors if it is not yet known.
|
|
Type() (*types.Type, error)
|
|
|
|
// Infer returns the type of itself and a collection of invariants. The
|
|
// returned type may contain unification variables. It collects the
|
|
// invariants by calling Check on its children expressions. In making
|
|
// those calls, it passes in the known type for that child to get it to
|
|
// "Check" it. When the type is not known, it should create a new
|
|
// unification variable to pass in to the child Check calls. Infer
|
|
// usually only calls Check on things inside of it, and often does not
|
|
// call another Infer.
|
|
Infer() (*types.Type, []*UnificationInvariant, error)
|
|
|
|
// Check is checking that the input type is equal to the object that
|
|
// Check is running on. In doing so, it adds any invariants that are
|
|
// necessary. Check must always call Infer to produce the invariant. The
|
|
// implementation can be generic for all expressions.
|
|
Check(typ *types.Type) ([]*UnificationInvariant, error)
|
|
|
|
// Graph returns the reactive function graph expressed by this node. It
|
|
// takes in the environment of any functions in scope. It also returns
|
|
// the function for this node.
|
|
Graph(env *Env) (*pgraph.Graph, Func, error)
|
|
|
|
// SetValue stores the result of the last computation of this expression
|
|
// node.
|
|
SetValue(types.Value) error
|
|
|
|
// Value returns the value of this expression in our type system.
|
|
Value() (types.Value, error)
|
|
}
|
|
|
|
// ScopeGrapher adds a method to turn an AST (Expr or Stmt) into a graph so that
|
|
// we can debug the SetScope compilation phase.
|
|
type ScopeGrapher interface {
|
|
Node
|
|
|
|
// ScopeGraph adds nodes and vertices to the supplied graph.
|
|
ScopeGraph(g *pgraph.Graph)
|
|
}
|
|
|
|
// Data provides some data to the node that could be useful during its lifetime.
|
|
type Data struct {
|
|
// Fs represents a handle to the filesystem that we're running on. This
|
|
// is necessary for opening files if needed by import statements. The
|
|
// file() paths used to get templates or other files from our deploys
|
|
// come from here, this is *not* used to interact with the host file
|
|
// system to manage file resources or other aspects.
|
|
Fs engine.Fs
|
|
|
|
// FsURI is the fs URI of the active filesystem. This is useful to pass
|
|
// to the engine.World API for further consumption.
|
|
FsURI string
|
|
|
|
// Base directory (absolute path) that the running code is in. If an
|
|
// import is found, that's a recursive addition, and naturally for that
|
|
// run, this value would be different in the recursion.
|
|
Base string
|
|
|
|
// Files is a list of absolute paths seen so far. This includes all
|
|
// previously seen paths, where as the former Offsets parameter did not.
|
|
Files []string
|
|
|
|
// Imports stores a graph inside a vertex so we have a current cursor.
|
|
// This means that as we recurse through our import graph (hopefully a
|
|
// DAG) we can know what the parent vertex in our graph is to edge to.
|
|
// If we ever can't topologically sort it, then it has an import loop.
|
|
Imports *pgraph.SelfVertex
|
|
|
|
// Metadata is the metadata structure associated with the given parsing.
|
|
// It can be present, which is often the case when importing a module,
|
|
// or it can be nil, which is often the case when parsing a single file.
|
|
// When imports are nested (eg: an imported module imports another one)
|
|
// the metadata structure can recursively point to an earlier structure.
|
|
Metadata *Metadata
|
|
|
|
// Modules is an absolute path to a modules directory on the current Fs.
|
|
// It is the directory to use to look for remote modules if we haven't
|
|
// specified an alternative with the metadata Path field. This is
|
|
// usually initialized with the global modules path that can come from
|
|
// the cli or an environment variable, but this only occurs for the
|
|
// initial download/get operation, and obviously not once we're running
|
|
// a deploy, since by then everything in here would have been copied to
|
|
// the runtime fs.
|
|
Modules string
|
|
|
|
// Downloader is the interface that must be fulfilled to download
|
|
// modules. If a missing import is found, and this is not nil, then it
|
|
// will be run once in an attempt to get the missing module before it
|
|
// fails outright. In practice, it is recommended to separate this
|
|
// download phase in a separate step from the production running and
|
|
// deploys, however that is not blocked at the level of this interface.
|
|
Downloader Downloader
|
|
|
|
// LexParser is a function that needs to get passed in to run the lexer
|
|
// and parser to build the initial AST. This is passed in this way to
|
|
// avoid dependency cycles.
|
|
LexParser func(io.Reader) (Stmt, error)
|
|
|
|
// StrInterpolater is a function that needs to get passed in to run the
|
|
// string interpolation. This is passed in this way to avoid dependency
|
|
// cycles.
|
|
StrInterpolater func(string, *Textarea, *Data) (Expr, error)
|
|
|
|
// SourceFinder is a function that returns the contents of a source file
|
|
// when requested by filename. This data is used to annotate error
|
|
// messages with some context from the source, and as a result is
|
|
// optional. This function is passed in this way so that the different
|
|
// consumers of this can use different methods to find the source. The
|
|
// three main users are: (1) normal GAPI CLI, before the bundle is
|
|
// created, (2) the main bundled execution, and (3) the tests.
|
|
SourceFinder SourceFinderFunc
|
|
|
|
//World engine.World // TODO: do we need this?
|
|
|
|
// Prefix provides a unique path prefix that we can namespace in. It is
|
|
// currently shared identically across the whole AST. Nodes should be
|
|
// careful to not write on top of other nodes data.
|
|
Prefix string
|
|
|
|
// Debug represents if we're running in debug mode or not.
|
|
Debug bool
|
|
|
|
// Logf is a logger which should be used.
|
|
Logf func(format string, v ...interface{})
|
|
}
|
|
|
|
// AbsFilename returns the absolute filename path to the code this Data struct
|
|
// is running. This is used to pull out a filename for error messages.
|
|
func (obj *Data) AbsFilename() string {
|
|
// TODO: is this correct? Do we want to check if Metadata is nil?
|
|
if obj == nil || obj.Metadata == nil { // for tests
|
|
return ""
|
|
}
|
|
return obj.Base + obj.Metadata.Main
|
|
}
|
|
|
|
// Scope represents a mapping between a variables identifier and the
|
|
// corresponding expression it is bound to. Local scopes in this language exist
|
|
// and are formed by nesting within if statements. Child scopes can shadow
|
|
// variables in parent scopes, which is another way of saying they can redefine
|
|
// previously used variables as long as the new binding happens within a child
|
|
// scope. This is useful so that someone in the top scope can't prevent a child
|
|
// module from ever using that variable name again. It might be worth revisiting
|
|
// this point in the future if we find it adds even greater code safety. Please
|
|
// report any bugs you have written that would have been prevented by this. This
|
|
// also contains the currently available functions. They function similarly to
|
|
// the variables, and you can add new ones with a function statement definition.
|
|
// An interesting note about these is that they exist in a distinct namespace
|
|
// from the variables, which could actually contain lambda functions.
|
|
type Scope struct {
|
|
// Variables maps the scope of name to Expr.
|
|
Variables map[string]Expr
|
|
|
|
// Functions is the scope of functions.
|
|
//
|
|
// The Expr will usually be an *ExprFunc. (Actually it's usually or
|
|
// always an *ExprSingleton, which wraps an *ExprFunc now.)
|
|
Functions map[string]Expr
|
|
|
|
// Classes map the name of the class to the class.
|
|
Classes map[string]Stmt
|
|
|
|
// Iterated is a flag that is true if this scope is inside of a for
|
|
// loop.
|
|
Iterated bool
|
|
|
|
Chain []Node // chain of previously seen node's
|
|
}
|
|
|
|
// EmptyScope returns the zero, empty value for the scope, with all the internal
|
|
// maps and lists initialized appropriately.
|
|
func EmptyScope() *Scope {
|
|
return &Scope{
|
|
Variables: make(map[string]Expr),
|
|
Functions: make(map[string]Expr),
|
|
Classes: make(map[string]Stmt),
|
|
Iterated: false,
|
|
Chain: []Node{},
|
|
}
|
|
}
|
|
|
|
// Copy makes a copy of the Scope struct. This ensures that if the internal map
|
|
// is changed, it doesn't affect other copies of the Scope. It does *not* copy
|
|
// or change the Expr pointers contained within, since these are references, and
|
|
// we need those to be consistently pointing to the same things after copying.
|
|
func (obj *Scope) Copy() *Scope {
|
|
if obj == nil { // allow copying nil scopes
|
|
return EmptyScope()
|
|
}
|
|
|
|
variables := make(map[string]Expr)
|
|
functions := make(map[string]Expr)
|
|
classes := make(map[string]Stmt)
|
|
iterated := obj.Iterated
|
|
chain := []Node{}
|
|
|
|
for k, v := range obj.Variables { // copy
|
|
variables[k] = v // we don't copy the expr's!
|
|
}
|
|
for k, v := range obj.Functions { // copy
|
|
functions[k] = v // we don't copy the generator func's
|
|
}
|
|
for k, v := range obj.Classes { // copy
|
|
classes[k] = v // we don't copy the StmtClass!
|
|
}
|
|
for _, x := range obj.Chain { // copy
|
|
chain = append(chain, x) // we don't copy the Stmt pointer!
|
|
}
|
|
|
|
return &Scope{
|
|
Variables: variables,
|
|
Functions: functions,
|
|
Classes: classes,
|
|
Iterated: iterated,
|
|
Chain: chain,
|
|
}
|
|
}
|
|
|
|
// Merge takes an existing scope and merges a scope on top of it. If any
|
|
// elements had to be overwritten, then the error result will contain some info.
|
|
// Even if this errors, the scope will have been merged successfully. The merge
|
|
// runs in a deterministic order so that errors will be consistent. Use Copy if
|
|
// you don't want to change this destructively.
|
|
// FIXME: this doesn't currently merge Chain's... Should it?
|
|
func (obj *Scope) Merge(scope *Scope) error {
|
|
var err error
|
|
// collect names so we can iterate in a deterministic order
|
|
namedVariables := []string{}
|
|
namedFunctions := []string{}
|
|
namedClasses := []string{}
|
|
for name := range scope.Variables {
|
|
namedVariables = append(namedVariables, name)
|
|
}
|
|
for name := range scope.Functions {
|
|
namedFunctions = append(namedFunctions, name)
|
|
}
|
|
for name := range scope.Classes {
|
|
namedClasses = append(namedClasses, name)
|
|
}
|
|
sort.Strings(namedVariables)
|
|
sort.Strings(namedFunctions)
|
|
sort.Strings(namedClasses)
|
|
|
|
for _, name := range namedVariables {
|
|
if _, exists := obj.Variables[name]; exists {
|
|
e := fmt.Errorf("variable `%s` was overwritten", name)
|
|
err = errwrap.Append(err, e)
|
|
}
|
|
obj.Variables[name] = scope.Variables[name]
|
|
}
|
|
for _, name := range namedFunctions {
|
|
if _, exists := obj.Functions[name]; exists {
|
|
e := fmt.Errorf("function `%s` was overwritten", name)
|
|
err = errwrap.Append(err, e)
|
|
}
|
|
obj.Functions[name] = scope.Functions[name]
|
|
}
|
|
for _, name := range namedClasses {
|
|
if _, exists := obj.Classes[name]; exists {
|
|
e := fmt.Errorf("class `%s` was overwritten", name)
|
|
err = errwrap.Append(err, e)
|
|
}
|
|
obj.Classes[name] = scope.Classes[name]
|
|
}
|
|
|
|
if scope.Iterated { // XXX: how should we merge this?
|
|
obj.Iterated = scope.Iterated
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// IsEmpty returns whether or not a scope is empty or not.
|
|
// FIXME: this doesn't currently consider Chain's... Should it?
|
|
func (obj *Scope) IsEmpty() bool {
|
|
//if obj == nil { // TODO: add me if this turns out to be useful
|
|
// return true
|
|
//}
|
|
if len(obj.Variables) > 0 {
|
|
return false
|
|
}
|
|
if len(obj.Functions) > 0 {
|
|
return false
|
|
}
|
|
if len(obj.Classes) > 0 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Env is an environment which contains the relevant mappings. This is used at
|
|
// the Graph(...) stage of the compiler. It does not contain classes.
|
|
type Env struct {
|
|
// Variables map and Expr to a *FuncSingleton which deduplicates the
|
|
// use of a function.
|
|
Variables map[Expr]*FuncSingleton
|
|
|
|
// Functions contains the captured environment, because when we're
|
|
// recursing into a StmtFunc which is defined inside a for loop, we can
|
|
// use that to get the right Env.Variables map. As for the function
|
|
// itself, it's the same in each loop iteration, therefore, we find it
|
|
// in obj.expr of ExprCall. (Functions map[string]*Env) But actually,
|
|
// our new version is now this:
|
|
Functions map[Expr]*Env
|
|
}
|
|
|
|
// EmptyEnv returns the zero, empty value for the env, with all the internal
|
|
// maps initialized appropriately.
|
|
func EmptyEnv() *Env {
|
|
return &Env{
|
|
Variables: make(map[Expr]*FuncSingleton),
|
|
Functions: make(map[Expr]*Env),
|
|
}
|
|
}
|
|
|
|
// Copy makes a copy of the Env struct. This ensures that if the internal maps
|
|
// are changed, it doesn't affect other copies of the Env. It does *not* copy or
|
|
// change the pointers contained within, since these are references, and we need
|
|
// those to be consistently pointing to the same things after copying.
|
|
func (obj *Env) Copy() *Env {
|
|
if obj == nil { // allow copying nil envs
|
|
return EmptyEnv()
|
|
}
|
|
|
|
variables := make(map[Expr]*FuncSingleton)
|
|
functions := make(map[Expr]*Env)
|
|
|
|
for k, v := range obj.Variables { // copy
|
|
variables[k] = v // we don't copy the func's!
|
|
}
|
|
for k, v := range obj.Functions { // copy
|
|
functions[k] = v // we don't copy the generator func's
|
|
}
|
|
|
|
return &Env{
|
|
Variables: variables,
|
|
Functions: functions,
|
|
}
|
|
}
|
|
|
|
// FuncSingleton is a singleton system for storing a singleton func and its
|
|
// corresponding graph. You must pass in a `MakeFunc` builder method to generate
|
|
// these. The graph which is returned from this must contain that Func as a
|
|
// node.
|
|
type FuncSingleton struct {
|
|
// MakeFunc builds and returns a Func and a graph that it must be
|
|
// contained within.
|
|
// XXX: Add Txn as an input arg?
|
|
MakeFunc func() (*pgraph.Graph, Func, error)
|
|
|
|
g *pgraph.Graph
|
|
f Func
|
|
}
|
|
|
|
// GraphFunc returns the previously saved graph and func if they exist. If they
|
|
// do not, then it calls the MakeFunc method to get them, and saves a copy for
|
|
// next time.
|
|
// XXX: Add Txn as an input arg?
|
|
func (obj *FuncSingleton) GraphFunc() (*pgraph.Graph, Func, error) {
|
|
// If obj.f already exists, just use that.
|
|
if obj.f != nil { // && obj.g != nil
|
|
return obj.g, obj.f, nil
|
|
}
|
|
|
|
var err error
|
|
obj.g, obj.f, err = obj.MakeFunc() // XXX: Add Txn as an input arg?
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if obj.g == nil {
|
|
return nil, nil, fmt.Errorf("unexpected nil graph")
|
|
}
|
|
if obj.f == nil {
|
|
return nil, nil, fmt.Errorf("unexpected nil function")
|
|
}
|
|
return obj.g, obj.f, nil
|
|
}
|
|
|
|
// ValueEnv is a future potential argument to the Value() method on Expr.
|
|
// XXX: Consider using this if we want to improve graph shape even more.
|
|
type ValueEnv struct {
|
|
Variables map[Expr]types.Value
|
|
}
|
|
|
|
// EmptyValueEnv returns the zero, empty value for the value env, with all the
|
|
// internal maps initialized appropriately.
|
|
func EmptyValueEnv() *ValueEnv {
|
|
return &ValueEnv{
|
|
Variables: make(map[Expr]types.Value),
|
|
}
|
|
}
|
|
|
|
// Copy makes a copy of the ValueEnv struct. This ensures that if the internal
|
|
// maps are changed, it doesn't affect other copies of the ValueEnv. It does
|
|
// *not* copy or change the pointers contained within, since these are
|
|
// references, and we need those to be consistently pointing to the same things
|
|
// after copying.
|
|
func (obj *ValueEnv) Copy() *ValueEnv {
|
|
if obj == nil { // allow copying nil envs
|
|
return EmptyValueEnv()
|
|
}
|
|
|
|
variables := make(map[Expr]types.Value)
|
|
|
|
for k, v := range obj.Variables { // copy
|
|
variables[k] = v // we don't copy the values!
|
|
}
|
|
|
|
return &ValueEnv{
|
|
Variables: variables,
|
|
}
|
|
}
|
|
|
|
// Arg represents a name identifier for a func or class argument declaration and
|
|
// is sometimes accompanied by a type. This does not satisfy the Expr interface.
|
|
type Arg struct {
|
|
Name string
|
|
Type *types.Type // nil if unspecified (needs to be solved for)
|
|
}
|
|
|
|
// String returns a short representation of this arg.
|
|
func (obj *Arg) String() string {
|
|
s := obj.Name
|
|
if obj.Type != nil {
|
|
s += fmt.Sprintf(" %s", obj.Type.String())
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Edge is the data structure representing a compiled edge that is used in the
|
|
// lang to express a dependency between two resources and optionally send/recv.
|
|
type Edge struct {
|
|
Kind1 string // kind of resource
|
|
Name1 string // name of resource
|
|
Send string // name of field used for send/recv (optional)
|
|
|
|
Kind2 string // kind of resource
|
|
Name2 string // name of resource
|
|
Recv string // name of field used for send/recv (optional)
|
|
|
|
Notify bool // is there a notification being sent?
|
|
}
|
|
|
|
// Output is a collection of data returned by a Stmt.
|
|
type Output struct { // returned by Stmt
|
|
Resources []engine.Res
|
|
Edges []*Edge
|
|
//Exported []*Exports // TODO: add exported resources
|
|
}
|
|
|
|
// EmptyOutput returns the zero, empty value for the output, with all the
|
|
// internal lists initialized appropriately.
|
|
func EmptyOutput() *Output {
|
|
return &Output{
|
|
Resources: []engine.Res{},
|
|
Edges: []*Edge{},
|
|
}
|
|
}
|
|
|
|
// PositionableNode is the interface implemented by AST nodes that store their
|
|
// code position. It is implemented by node types that embed Textarea.
|
|
type PositionableNode interface {
|
|
// IsSet returns if the position was already set with Locate already.
|
|
IsSet() bool
|
|
|
|
// Locate sets the position in zero-based (start line, start column, end
|
|
// line, end column) format.
|
|
Locate(int, int, int, int)
|
|
|
|
// Pos returns the zero-based start line and then start column position.
|
|
Pos() (int, int)
|
|
|
|
// End returns the zero-based end line and then end column position.
|
|
End() (int, int)
|
|
|
|
// String returns a friendly representation of the positions.
|
|
String() string
|
|
}
|
|
|
|
// TextDisplayer is a graph node that is aware of its position in the source
|
|
// code, and can emit a textual representation of that part of the source.
|
|
type TextDisplayer interface {
|
|
// Byline returns a simple version of the error location.
|
|
Byline() string
|
|
|
|
// HighlightText returns a textual representation of this definition
|
|
// for this node in source.
|
|
HighlightText() string
|
|
}
|
|
|
|
// SourceFinderFunc is the function signature used to return the contents of a
|
|
// source file when requested by filename. This data is used to annotate error
|
|
// messages with some context from the source, and as a result is optional.
|
|
type SourceFinderFunc = func(string) ([]byte, error)
|