interfaces

package
v0.0.0-...-453cd44 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 18, 2024 License: GPL-3.0 Imports: 11 Imported by: 53

Documentation

Overview

Package interfaces contains the common interfaces used in the mcl language. This package has the common imports that most consumers use directly.

Index

Constants

View Source
const (
	// ModuleSep is the character used for the module scope separation. For
	// example when using `fmt.printf` or `math.sin` this is the char used.
	// It is also used for variable scope separation such as `$foo.bar.baz`.
	ModuleSep = "."

	// ClassSep is the character used for the class embedding separation.
	// For example when defining `class base:inner` this is the char used.
	ClassSep = ":"

	// VarPrefix is the prefix character that precedes the variables
	// identifier. For example, `$foo` or for a lambda, `$fn(42)`. It is
	// also used with `ModuleSep` for scoped variables like `$foo.bar.baz`.
	VarPrefix = "$"

	// BareSymbol is the character used primarily for imports to specify
	// that we want to import the entire contents and flatten them into our
	// current scope. It should probably be removed entirely to force
	// explicit imports.
	BareSymbol = "*"

	// PanicResKind is the kind string used for the panic resource.
	PanicResKind = "_panic"
)
View Source
const (
	// ErrTypeCurrentlyUnknown is returned from the Type() call on Expr if
	// unification didn't run successfully and the type isn't obvious yet.
	ErrTypeCurrentlyUnknown = Error("type is currently unknown")

	// ErrExpectedFileMissing is returned when a file that is used by an
	// import is missing. This might signal the downloader, or it might
	// signal a permanent error.
	ErrExpectedFileMissing = Error("file is currently missing")
)
View Source
const (
	// MetadataFilename is the filename for the metadata storage. This is
	// the ideal entry point for any running code.
	MetadataFilename = "metadata.yaml"

	// FileNameExtension is the filename extension used for languages files.
	FileNameExtension = "mcl" // alternate suggestions welcome!

	// DotFileNameExtension is the filename extension with a dot prefix.
	DotFileNameExtension = "." + FileNameExtension

	// MainFilename is the default filename for code to start running from.
	MainFilename = "main" + DotFileNameExtension

	// PathDirectory is the path directory name we search for modules in.
	PathDirectory = "path/"

	// FilesDirectory is the files directory name we include alongside
	// modules. It can store any useful files that we'd like.
	FilesDirectory = "files/"

	// ModuleDirectory is the default module directory name. It gets
	// appended to whatever the running prefix is or relative to the base
	// dir being used for deploys.
	ModuleDirectory = "modules/"
)

Variables

This section is empty.

Functions

func FindModulesPath

func FindModulesPath(metadata *Metadata, base, modules string) (string, error)

FindModulesPath returns an absolute path to the Path dir where modules can be found. This can vary, because the current metadata file might not specify a Path value, meaning we'd have to return the global modules path. Additionally, we can search upwards for a path if our metadata file allows this. It searches with respect to the calling base directory, and uses the ParentPathBlock field to determine if we're allowed to search upwards. It does logically without doing any filesystem operations.

func FindModulesPathList

func FindModulesPathList(metadata *Metadata, base, modules string) ([]string, error)

FindModulesPathList does what FindModulesPath does, except this function returns the entirely linear string of possible module locations until it gets to the root. This can be useful if you'd like to know which possible locations are valid, so that you can search through them to see if there is downloaded code available.

Types

type AnyInvariant

type AnyInvariant struct {
	Expr Expr
}

AnyInvariant is an invariant that symbolizes that the expression can be any type. It is sometimes used to ensure that an expr actually gets a solution type so that it is not left unreferenced, and as a result, unsolved. TODO: is there a better name than AnyInvariant

func (*AnyInvariant) ExprList

func (obj *AnyInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*AnyInvariant) Matches

func (obj *AnyInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*AnyInvariant) Possible

func (obj *AnyInvariant) Possible([]Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation always returns nil.

func (*AnyInvariant) String

func (obj *AnyInvariant) String() string

String returns a representation of this invariant.

type Arg

type Arg struct {
	Name string
	Type *types.Type // nil if unspecified (needs to be solved for)
}

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.

func (*Arg) String

func (obj *Arg) String() string

String returns a short representation of this arg.

type CallFuncArgsValueInvariant

type CallFuncArgsValueInvariant struct {
	// Expr represents the pointer to the ExprCall.
	Expr Expr

	// Func represents the pointer to the ExprFunc that ExprCall is using.
	Func Expr

	// Args represents the list of args that the ExprCall is using to call
	// the ExprFunc. A solver might speculatively call Value() on each of
	// these in the hopes of doing something useful if a value happens to be
	// known statically at compile time. One such solver that might do this
	// is the GeneratorInvariant inside of a difficult function like printf.
	Args []Expr
}

CallFuncArgsValueInvariant expresses that a func call is associated with a particular func, and that it is called with a specific list of args. Expr must match the function call expression, Func must match the actual function expression, and Args matches the args used in the call to run the func. TODO: should this be named FuncCallArgsValueInvariant or something different or not?

func (*CallFuncArgsValueInvariant) ExprList

func (obj *CallFuncArgsValueInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*CallFuncArgsValueInvariant) Matches

func (obj *CallFuncArgsValueInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*CallFuncArgsValueInvariant) Possible

func (obj *CallFuncArgsValueInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation is currently not implemented!

func (*CallFuncArgsValueInvariant) String

func (obj *CallFuncArgsValueInvariant) String() string

String returns a representation of this invariant.

type ConjunctionInvariant

type ConjunctionInvariant struct {
	Invariants []Invariant
}

ConjunctionInvariant represents a list of invariants which must all be true together. In other words, it's a grouping construct for a set of invariants.

func (*ConjunctionInvariant) ExprList

func (obj *ConjunctionInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*ConjunctionInvariant) Matches

func (obj *ConjunctionInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*ConjunctionInvariant) Possible

func (obj *ConjunctionInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation is currently not implemented!

func (*ConjunctionInvariant) String

func (obj *ConjunctionInvariant) String() string

String returns a representation of this invariant.

type Data

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, *Pos, *Data) (Expr, error)

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

Data provides some data to the node that could be useful during its lifetime.

type DataFunc

type DataFunc interface {
	Func // implement everything in Func but add the additional requirements

	// SetData is used by the language to pass our function some code-level
	// context.
	SetData(*FuncData)
}

DataFunc is a function that accepts some context from the AST and deploy before Init and runtime. If you don't wish to accept this data, then don't implement this method and you won't get any. This is mostly useful for special functions that are useful in core. TODO: This could be replaced if a func ever needs a SetScope method...

type DownloadInfo

type DownloadInfo struct {
	// Fs is the filesystem to use for downloading to.
	Fs engine.Fs

	// Noop specifies if we should actually download or just fake it. The
	// one problem is that if we *don't* download something, then we can't
	// follow it to see if there's anything else to download.
	Noop bool

	// Sema specifies the max number of simultaneous downloads to run.
	Sema int

	// Update specifies if we should try and update existing downloaded
	// artifacts.
	Update bool

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

DownloadInfo is the set of input values passed into the Init method of the Downloader interface, so that it can have some useful information to use.

type Downloader

type Downloader interface {
	// Init initializes the downloader with some core structures we'll need.
	Init(*DownloadInfo) error

	// Get runs a single download of an import and stores it on disk.
	Get(*ImportData, string) error
}

Downloader is the interface that must be fulfilled to download modules. TODO: this should probably be in a more central package like the top-level GAPI package, and not contain the lang specific *ImportData struct. Since we aren't working on a downloader for any other frontend at the moment, we'll keep it here, and keep it less generalized for now. If we *really* wanted to generalize it, Get would be implemented as part of the *ImportData struct and there would be an interface it helped fulfill for the Downloader GAPI.

type Edge

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?
}

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 EqualityInvariant

type EqualityInvariant struct {
	Expr1 Expr
	Expr2 Expr
}

EqualityInvariant is an invariant that symbolizes that the two expressions must have equivalent types. TODO: is there a better name than EqualityInvariant

func (*EqualityInvariant) ExprList

func (obj *EqualityInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*EqualityInvariant) Matches

func (obj *EqualityInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*EqualityInvariant) Possible

func (obj *EqualityInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one.

func (*EqualityInvariant) String

func (obj *EqualityInvariant) String() string

String returns a representation of this invariant.

type EqualityInvariantList

type EqualityInvariantList struct {
	Exprs []Expr
}

EqualityInvariantList is an invariant that symbolizes that all the expressions listed must have equivalent types.

func (*EqualityInvariantList) ExprList

func (obj *EqualityInvariantList) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*EqualityInvariantList) Matches

func (obj *EqualityInvariantList) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*EqualityInvariantList) Possible

func (obj *EqualityInvariantList) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one.

func (*EqualityInvariantList) String

func (obj *EqualityInvariantList) String() string

String returns a representation of this invariant.

type EqualityWrapCallInvariant

type EqualityWrapCallInvariant struct {
	Expr1     Expr
	Expr2Func Expr
}

EqualityWrapCallInvariant expresses that a call result that happened in Expr1 must match the type of the function result listed in Expr2. In this case, Expr2 will be a function expression, and the returned expression should match with the Expr1 expression, when comparing types. TODO: should this be named EqualityWrapFuncInvariant or not? TODO: should Expr1 and Expr2 be reversed???

func (*EqualityWrapCallInvariant) ExprList

func (obj *EqualityWrapCallInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*EqualityWrapCallInvariant) Matches

func (obj *EqualityWrapCallInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*EqualityWrapCallInvariant) Possible

func (obj *EqualityWrapCallInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation is currently not implemented!

func (*EqualityWrapCallInvariant) String

func (obj *EqualityWrapCallInvariant) String() string

String returns a representation of this invariant.

type EqualityWrapFuncInvariant

type EqualityWrapFuncInvariant struct {
	Expr1    Expr
	Expr2Map map[string]Expr
	Expr2Ord []string
	Expr2Out Expr
}

EqualityWrapFuncInvariant expresses that a func in Expr1 must have args that match the type of the expressions listed in Expr2Map and a return value that matches the type of the expression in Expr2Out. TODO: should this be named EqualityWrapCallInvariant or not?

func (*EqualityWrapFuncInvariant) ExprList

func (obj *EqualityWrapFuncInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*EqualityWrapFuncInvariant) Matches

func (obj *EqualityWrapFuncInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*EqualityWrapFuncInvariant) Possible

func (obj *EqualityWrapFuncInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation is currently not implemented!

func (*EqualityWrapFuncInvariant) String

func (obj *EqualityWrapFuncInvariant) String() string

String returns a representation of this invariant.

type EqualityWrapListInvariant

type EqualityWrapListInvariant struct {
	Expr1    Expr
	Expr2Val Expr
}

EqualityWrapListInvariant expresses that a list in Expr1 must have elements that have the same type as the expression in Expr2Val.

func (*EqualityWrapListInvariant) ExprList

func (obj *EqualityWrapListInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*EqualityWrapListInvariant) Matches

func (obj *EqualityWrapListInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*EqualityWrapListInvariant) Possible

func (obj *EqualityWrapListInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation is currently not implemented!

func (*EqualityWrapListInvariant) String

func (obj *EqualityWrapListInvariant) String() string

String returns a representation of this invariant.

type EqualityWrapMapInvariant

type EqualityWrapMapInvariant struct {
	Expr1    Expr
	Expr2Key Expr
	Expr2Val Expr
}

EqualityWrapMapInvariant expresses that a map in Expr1 must have keys that match the type of the expression in Expr2Key and values that match the type of the expression in Expr2Val.

func (*EqualityWrapMapInvariant) ExprList

func (obj *EqualityWrapMapInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*EqualityWrapMapInvariant) Matches

func (obj *EqualityWrapMapInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*EqualityWrapMapInvariant) Possible

func (obj *EqualityWrapMapInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation is currently not implemented!

func (*EqualityWrapMapInvariant) String

func (obj *EqualityWrapMapInvariant) String() string

String returns a representation of this invariant.

type EqualityWrapStructInvariant

type EqualityWrapStructInvariant struct {
	Expr1    Expr
	Expr2Map map[string]Expr
	Expr2Ord []string
}

EqualityWrapStructInvariant expresses that a struct in Expr1 must have fields that match the type of the expressions listed in Expr2Map.

func (*EqualityWrapStructInvariant) ExprList

func (obj *EqualityWrapStructInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*EqualityWrapStructInvariant) Matches

func (obj *EqualityWrapStructInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*EqualityWrapStructInvariant) Possible

func (obj *EqualityWrapStructInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation is currently not implemented!

func (*EqualityWrapStructInvariant) String

func (obj *EqualityWrapStructInvariant) String() string

String returns a representation of this invariant.

type EqualsInvariant

type EqualsInvariant struct {
	Expr Expr
	Type *types.Type
}

EqualsInvariant is an invariant that symbolizes that the expression has a known type. TODO: is there a better name than EqualsInvariant

func (*EqualsInvariant) ExprList

func (obj *EqualsInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*EqualsInvariant) Matches

func (obj *EqualsInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*EqualsInvariant) Possible

func (obj *EqualsInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one.

func (*EqualsInvariant) String

func (obj *EqualsInvariant) String() string

String returns a representation of this invariant.

type Error

type Error string

Error is a constant error type that implements error.

func (Error) Error

func (e Error) Error() string

Error fulfills the error interface of this type.

type ExclusiveInvariant

type ExclusiveInvariant struct {
	Invariants []Invariant
}

ExclusiveInvariant represents a list of invariants where one and *only* one should hold true. To combine multiple invariants in one of the list elements, you can group multiple invariants together using a ConjunctionInvariant. Do note that the solver might not verify that only one of the invariants in the list holds true, as it might choose to be lazy and pick the first solution found.

func (*ExclusiveInvariant) ExprList

func (obj *ExclusiveInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*ExclusiveInvariant) Matches

func (obj *ExclusiveInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors. Because this partial invariant requires only one to be true, it will mask children errors, since it's normal for only one to be consistent.

func (*ExclusiveInvariant) Possible

func (obj *ExclusiveInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one. This particular implementation is currently not implemented!

func (*ExclusiveInvariant) Simplify

func (obj *ExclusiveInvariant) Simplify(partials []Invariant) ([]Invariant, error)

Simplify attempts to reduce the exclusive invariant to eliminate any possibilities based on the list of known partials at this time. Hopefully, this will weed out some of the function polymorphism possibilities so that we can solve the problem without recursive, combinatorial permutation, which is very, very slow.

func (*ExclusiveInvariant) String

func (obj *ExclusiveInvariant) String() string

String returns a representation of this invariant.

type Expr

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)

	// Unify 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.
	Unify() ([]Invariant, 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 map[string]Func) (*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)
}

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 ExprAny

type ExprAny struct {
	V types.Value // stored value (set with SetValue)
	// contains filtered or unexported fields
}

ExprAny is a placeholder expression that is used for type unification hacks.

func (*ExprAny) Apply

func (obj *ExprAny) Apply(fn func(Node) error) error

Apply is a general purpose iterator method that operates on any AST node. It is not used as the primary AST traversal function because it is less readable and easy to reason about than manually implementing traversal for each node. Nevertheless, it is a useful facility for operations that might only apply to a select number of node types, since they won't need extra noop iterators...

func (*ExprAny) Copy

func (obj *ExprAny) Copy() (Expr, error)

Copy returns a light copy of this struct. Anything static will not be copied.

func (*ExprAny) Func

func (obj *ExprAny) Func() (Func, error)

Func returns the reactive stream of values that this expression produces.

func (*ExprAny) Graph

func (obj *ExprAny) Graph(env map[string]Func) (*pgraph.Graph, Func, error)

Graph returns the reactive function graph which is expressed by this node. It includes any vertices produced by this node, and the appropriate edges to any vertices that are produced by its children. Nodes which fulfill the Expr interface directly produce vertices (and possible children) where as nodes that fulfill the Stmt interface do not produces vertices, where as their children might. This returns a graph with a single vertex (itself) in it, and the edges from all of the child graphs to this.

func (*ExprAny) Init

func (obj *ExprAny) Init(*Data) error

Init initializes this branch of the AST, and returns an error if it fails to validate.

func (*ExprAny) Interpolate

func (obj *ExprAny) Interpolate() (Expr, error)

Interpolate returns a new node (aka a copy) once it has been expanded. This generally increases the size of the AST when it is used. It calls Interpolate on any child elements and builds the new node with those new node contents. Here it simply returns itself, as no interpolation is possible.

func (*ExprAny) Ordering

func (obj *ExprAny) Ordering(produces map[string]Node) (*pgraph.Graph, map[Node]string, 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.

func (*ExprAny) ScopeGraph

func (obj *ExprAny) ScopeGraph(g *pgraph.Graph)

ScopeGraph adds nodes and vertices to the supplied graph.

func (*ExprAny) SetScope

func (obj *ExprAny) SetScope(*Scope, map[string]Expr) error

SetScope does nothing for this struct, because it has no child nodes, and it does not need to know about the parent scope.

func (*ExprAny) SetType

func (obj *ExprAny) SetType(typ *types.Type) error

SetType is used to set the type of this expression once it is known. This usually happens during type unification, but it can also happen during parsing if a type is specified explicitly. Since types are static and don't change on expressions, if you attempt to set a different type than what has previously been set (when not initially known) this will error.

func (*ExprAny) SetValue

func (obj *ExprAny) SetValue(value types.Value) error

SetValue here is used to store a value for this expression node. This value is cached and can be retrieved by calling Value.

func (*ExprAny) String

func (obj *ExprAny) String() string

String returns a short representation of this expression.

func (*ExprAny) Type

func (obj *ExprAny) Type() (*types.Type, error)

Type returns the type of this expression.

func (*ExprAny) Unify

func (obj *ExprAny) Unify() ([]Invariant, error)

Unify returns the list of invariants that this node produces. It recursively calls Unify on any children elements that exist in the AST, and returns the collection to the caller.

func (*ExprAny) Value

func (obj *ExprAny) Value() (types.Value, error)

Value returns the value of this expression in our type system. This will usually only be valid once the engine has run and values have been produced. This might get called speculatively (early) during unification to learn more.

type Func

type Func interface {
	fmt.Stringer // so that this can be stored as a Vertex

	Validate() error // FIXME: this is only needed for PolyFunc. Get it moved and used!

	// Info returns some information about the function in question, which
	// includes the function signature. For a polymorphic function, this
	// might not be known until after Build was called. As a result, the
	// sig should be allowed to return a partial or variant type if it is
	// not known yet. This is because the Info method might be called
	// speculatively to aid in type unification.
	Info() *Info

	// Init passes some important values and references to the function.
	Init(*Init) error

	// Stream is the mainloop of the function. It reads and writes from
	// channels to return the changing values that this func has over time.
	// It should shutdown and cleanup when the input context is cancelled.
	// It must not exit before any goroutines it spawned have terminated.
	// It must close the Output chan if it's done sending new values out. It
	// must send at least one value, or return an error. It may also return
	// an error at anytime if it can't continue.
	Stream(context.Context) error
}

Func is the interface that any valid func must fulfill. It is very simple, but still event driven. Funcs should attempt to only send values when they have changed. TODO: should we support a static version of this interface for funcs that never change to avoid the overhead of the goroutine and channel listener?

type FuncData

type FuncData 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. This is a
	// copy of the value from the Expr and Stmt Data struct for Init.
	Base string
}

FuncData is some data that is passed into the function during compilation. It helps provide some context about the AST and the deploy for functions that might need it. TODO: Consider combining this with the existing Data struct or more of it... TODO: Do we want to add line/col/file values here, and generalize this?

type FuncEdge

type FuncEdge struct {
	Args []string // list of named args that this edge sends to
}

FuncEdge links an output vertex (value) to an input vertex with a named argument.

func (*FuncEdge) String

func (obj *FuncEdge) String() string

String displays the list of arguments this edge satisfies. It is a required property to be a valid pgraph.Edge.

type FuncSig

type FuncSig = func([]types.Value) (types.Value, error)

FuncSig is the simple signature that is used throughout our implementations.

type GeneratorInvariant

type GeneratorInvariant struct {
	// Func is a generator function that takes the state of the world, and
	// returns new invariants that should be added to this world view. The
	// state of the world includes both the currently unsolved invariants,
	// as well as the known solution map that has been solved so far. If
	// this returns nil, we add the invariants it returned and we remove it
	// from the list. If we error, it's because we don't have any new
	// information to provide at this time...
	Func func(invariants []Invariant, solved map[Expr]*types.Type) ([]Invariant, error)

	// Inactive specifies that we tried to run this, but it didn't help us
	// progress forwards. It can be reset if needed. It should only be set
	// or read by the solver itself.
	Inactive bool
}

GeneratorInvariant is an experimental type of new invariant. The idea is that this is a special invariant that the solver knows how to use; the solver runs all the easy bits first, and then passes the current solution state into the function, and in response, it runs some user-defined code and builds some new invariants that are added to the solver! This is not without caveats... This should only be used sparingly, and with care. It can suffer from the confluence problem, if the generator code that was provided is incorrect. What this means is that it could generate different results (and a different final solution) depending on the order in which it is called. Since this is undesirable, you must only use it for straight-forward situations. As an extreme example, if it generated different invariants depending on the time of day, this would be very problematic, and evil. Alternatively, it could be a pure function, but that returns wildly different results depending on what invariants were passed in. Use it wisely. It was added to make the printf function (which can have an infinite number of signatures) possible to express in terms of "normal" invariants. Lastly, if you wanted to use this to add-in partial progress, you could have it generate a list of invariants and include a new generator invariant in this list. Be sure to only do this if you are making progress on each invocation, and make sure to avoid infinite looping which isn't something we can currently detect or prevent. One special bit about generators and returning a partial: you must always return the minimum set of expressions that need to be solved in the first Unify() call that also returns the very first generator. This is because you must not rely on the generator to tell the solver about new expressions that it *also* wants solved. This is because after the initial (pre-generator-running) collection of the invariants, we need to be able to build a list of all the expressions that need to be solved for us to consider the problem "done". If a new expression only appeared after we ran a generator, then this would require our solver be far more complicated than it needs to be and currently is. Besides, there's no reason (that I know of at the moment) that needs this sort of invariant that only appears after the solver is running.

NOTE: We might *consider* an optimization where we return a different kind of error that represents a response of "impossible". This would mean that there is no way to reconcile the current world-view with what is know about things. However, it would be easier and better to just return your invariants and let the normal solver run its course, although future research might show that it could maybe help in some cases. XXX: solver question: Can our solver detect `expr1 == str` AND `expr1 == int` and fail the whole thing when we know of a case like this that is impossible?

func (*GeneratorInvariant) ExprList

func (obj *GeneratorInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*GeneratorInvariant) Matches

func (obj *GeneratorInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*GeneratorInvariant) Possible

func (obj *GeneratorInvariant) Possible(partials []Invariant) error

Possible is currently not implemented!

func (*GeneratorInvariant) String

func (obj *GeneratorInvariant) String() string

String returns a representation of this invariant.

type GraphAPI

type GraphAPI interface {
	AddVertex(Func) error
	AddEdge(Func, Func, *FuncEdge) error
	DeleteVertex(Func) error
	DeleteEdge(*FuncEdge) error

	//Adjacency() map[Func]map[Func]*FuncEdge
	HasVertex(Func) bool
	FindEdge(Func, Func) *FuncEdge
	LookupEdge(*FuncEdge) (Func, Func, bool)

	// Graph returns a copy of the current graph.
	Graph() *pgraph.Graph
}

GraphAPI is a subset of the available graph operations that are possible on a pgraph that is used for storing functions. The minimum subset are those which are needed for implementing the Txn interface.

type ImportData

type ImportData struct {
	// Name is the original input that produced this struct. It is stored
	// here so that you can parse it once and pass this struct around
	// without having to include a copy of the original data if needed.
	Name string

	// Alias is the name identifier that should be used for this import.
	Alias string

	// IsSystem specifies that this is a system import.
	IsSystem bool

	// IsLocal represents if a module is either local or a remote import.
	IsLocal bool

	// IsFile represents if we're referring to an individual file or not.
	IsFile bool

	// Path represents the relative path to the directory that this import
	// points to. Since it specifies a directory, it will end with a
	// trailing slash which makes detection more obvious for other helpers.
	// If this points to a local import, that directory is probably not
	// expected to contain a metadata file, and it will be a simple path
	// addition relative to the current file this import was parsed from. If
	// this is a remote import, then it's likely that the file will be found
	// in a more distinct path, such as a search path that contains the full
	// fqdn of the import.
	// TODO: should system imports put something here?
	Path string

	// URL is the path that a `git clone` operation should use as the URL.
	// If it is a local import, then this is the empty value.
	URL string
}

ImportData is the result of parsing a string import when it has not errored.

type Info

type Info struct {
	Pure bool        // is the function pure? (can it be memoized?)
	Memo bool        // should the function be memoized? (false if too much output)
	Slow bool        // is the function slow? (avoid speculative execution)
	Sig  *types.Type // the signature of the function, must be KindFunc
	Err  error       // is this a valid function, or was it created improperly?
}

Info is a static representation of some information about the function. It is used for static analysis and type checking. If you break this contract, you might cause a panic.

type Init

type Init struct {
	Hostname string // uuid for the host

	// Input is where a chan (stream) of values will get sent to this node.
	// The engine will close this `input` chan.
	Input chan types.Value

	// Output is the chan (stream) of values to get sent out from this node.
	// The Stream function must close this `output` chan.
	Output chan types.Value

	// Txn provides a transaction API that can be used to modify the
	// function graph while it is "running". This should not be used by most
	// nodes, and when it is used, it should be used carefully.
	Txn Txn

	Local *local.API
	World engine.World

	Debug bool
	Logf  func(format string, v ...interface{})
}

Init is the structure of values and references which is passed into all functions on initialization.

type Invariant

type Invariant interface {
	// TODO: should we add any other methods to this type?
	fmt.Stringer

	// ExprList returns the list of valid expressions in this invariant.
	ExprList() []Expr

	// Matches returns whether an invariant matches the existing solution.
	// If it is inconsistent, then it errors.
	Matches(solved map[Expr]*types.Type) (bool, error)

	// Possible returns an error if it is certain that it is NOT possible to
	// get a solution with this invariant and the set of partials. In
	// certain cases, it might not be able to determine that it's not
	// possible, while simultaneously not being able to guarantee a possible
	// solution either. In this situation, it should return nil, since this
	// is used as a filtering mechanism, and the nil result of possible is
	// preferred over eliminating a tricky, but possible one.
	Possible(partials []Invariant) error
}

Invariant represents a constraint that is described by the Expr's and Stmt's, and which is passed into the unification solver to describe what is known by the AST. XXX: add the extended methods into sub-interfaces since not each invariant uses them...

type Metadata

type Metadata struct {
	// Main is the path to the entry file where we start reading code.
	// Normally this is main.mcl or the value of the MainFilename constant.
	Main string `yaml:"main"`

	// Path is the relative path to the local module search path directory
	// that we should look in. This is similar to golang's vendor directory.
	// If a module wishes to include this directory, it's recommended that
	// it have the contained directory be a `git submodule` if possible.
	Path string `yaml:"path"`

	// Files is the location of the files/ directory which can contain some
	// useful additions that might get used in the modules. You can store
	// templates, or any other data that you'd like.
	// TODO: also allow storing files alongside the .mcl files in their dir!
	Files string `yaml:"files"`

	// License is the listed license of the module. Use the short names, eg:
	// LGPLv3+, or MIT.
	License string `yaml:"license"`

	// ParentPathBlock specifies whether we're allowed to search in parent
	// metadata file Path settings for modules. We always search in the
	// global path if we don't find others first. This setting defaults to
	// false, which is important because the downloader uses it to decide
	// where to put downloaded modules. It is similar to the equivalent of
	// a `require vendoring` flag in golang if such a thing existed. If a
	// module sets this to true, and specifies a Path value, then only that
	// path will be used as long as imports are present there. Otherwise it
	// will fall-back on the global modules directory. If a module sets this
	// to true, and does not specify a Path value, then the global modules
	// directory is automatically chosen for the import location for this
	// module. When this is set to true, in no scenario will an import come
	// from a directory other than the one specified here, or the global
	// modules directory. Module authors should use this sparingly when they
	// absolutely need a specific import vendored, otherwise they might
	// rouse the ire of module consumers. Keep in mind that you can specify
	// a Path directory, and include a git submodule in it, which will be
	// used by default, without specifying this option. In that scenario,
	// the consumer can decide to not recursively clone your submodule if
	// they wish to override it higher up in the module search locations.
	ParentPathBlock bool `yaml:"parentpathblock"`

	// Metadata stores a link to the parent metadata structure if it exists.
	Metadata *Metadata // this does *NOT* get a yaml struct tag
	// contains filtered or unexported fields
}

Metadata is a data structure representing the module metadata. Since it can get moved around to different filesystems, it should only contain relative paths.

func DefaultMetadata

func DefaultMetadata() *Metadata

DefaultMetadata returns the default metadata that is used for absent values.

func ParseMetadata

func ParseMetadata(reader io.Reader) (*Metadata, error)

ParseMetadata reads from some input and returns a *Metadata struct that contains plausible values to be used.

func (*Metadata) SetAbsSelfPath

func (obj *Metadata) SetAbsSelfPath(p string) error

SetAbsSelfPath sets the absolute directory path to this metadata file. This method is used on a built metadata file so that it can internally know where it is located.

func (*Metadata) ToBytes

func (obj *Metadata) ToBytes() ([]byte, error)

ToBytes marshals the struct into a byte array and returns it.

func (*Metadata) UnmarshalYAML

func (obj *Metadata) UnmarshalYAML(unmarshal func(interface{}) error) error

UnmarshalYAML is the standard unmarshal method for this struct.

type NamedArgsFunc

type NamedArgsFunc interface {
	Func // implement everything in Func but add the additional requirements

	// ArgGen implements the arg name generator function. By default, we use
	// the util.NumToAlpha function when this interface isn't implemented...
	ArgGen(int) (string, error)
}

NamedArgsFunc is a function that uses non-standard function arg names. If you don't implement this, then the argnames (if specified) must correspond to the a, b, c...z, aa, ab...az, ba...bz, and so on sequence.

type Node

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
}

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 OldPolyFunc

type OldPolyFunc interface {
	Func // implement everything in Func but add the additional requirements

	// Polymorphisms returns a list of possible function type signatures. It
	// takes as input a list of partial "hints" as to limit the number of
	// possible results it returns. These partial hints take the form of a
	// function type signature (with as many types in it specified and the
	// rest set to nil) and any known static values for the input args. If
	// the partial type is not nil, then the Ord parameter must be of the
	// correct arg length. If any types are specified, then the array must
	// be of that length as well, with the known ones filled in. Some
	// static polymorphic functions require a minimal amount of hinting or
	// they will be unable to return any possible result that is not
	// infinite in length. If you expect to need to return an infinite (or
	// very large) amount of results, then you should return an error
	// instead. The arg names in your returned func type signatures should
	// be in the standardized "a..b..c" format. Use util.NumToAlpha if you
	// want to convert easily.
	Polymorphisms(*types.Type, []types.Value) ([]*types.Type, error)

	// Build takes the known or unified type signature for this function and
	// finalizes this structure so that it is now determined, and ready to
	// function as a normal function would. (The normal methods in the Func
	// interface are all that should be needed or used after this point.)
	// Of note, the names of the specific input args shouldn't matter as
	// long as they are unique. Their position doesn't matter. This is so
	// that unification can use "arg0", "arg1", "argN"... if they can't be
	// determined statically. Build can transform them into it's desired
	// form, and must return the type (with the correct arg names) that it
	// will use. These are used when constructing the function graphs. This
	// means that when this is called from SetType, it can set the correct
	// type arg names, and this will also match what's in function Info().
	Build(*types.Type) (*types.Type, error)
}

OldPolyFunc is an interface for functions which are statically polymorphic. In other words, they are functions which before compile time are polymorphic, but after a successful compilation have a fixed static signature. This makes implementing what would appear to be generic or polymorphic instead something that is actually static and that still has the language safety properties.

type Output

type Output struct {
	Resources []engine.Res
	Edges     []*Edge
}

Output is a collection of data returned by a Stmt.

func EmptyOutput

func EmptyOutput() *Output

EmptyOutput returns the zero, empty value for the output, with all the internal lists initialized appropriately.

type PolyFunc

type PolyFunc interface {
	Func // implement everything in Func but add the additional requirements

	// Unify returns the list of invariants that this func produces. It is a
	// way for a polymorphic function to describe its type requirements. It
	// would be expected for this function to return at least one
	// ExclusiveInvariant or GeneratorInvariant, since these are two common
	// mechanisms for polymorphic functions to describe their constraints.
	// The important realization behind this method is that the collecting
	// of possible invariants, must happen *before* the solver runs so that
	// the solver can look at all the available logic *simultaneously* to
	// find a solution if we want to be able to reliably solve for things.
	// The input argument that it receives is the expression pointer that it
	// is unifying against-- in other words, the pointer is its own handle.
	// This is different than the `obj` reference of this function
	// implementation because _that_ handle is not the object/pointer in the
	// AST that we're discussing when performing type unification. Put
	// another way: the Expr input is the ExprFunc, not the ExprCall.
	Unify(Expr) ([]Invariant, error)

	// Build takes the known or unified type signature for this function and
	// finalizes this structure so that it is now determined, and ready to
	// function as a normal function would. (The normal methods in the Func
	// interface are all that should be needed or used after this point.)
	// Of note, the names of the specific input args shouldn't matter as
	// long as they are unique. Their position doesn't matter. This is so
	// that unification can use "arg0", "arg1", "argN"... if they can't be
	// determined statically. Build can transform them into it's desired
	// form, and must return the type (with the correct arg names) that it
	// will use. These are used when constructing the function graphs. This
	// means that when this is called from SetType, it can set the correct
	// type arg names, and this will also match what's in function Info().
	Build(*types.Type) (*types.Type, error)
}

PolyFunc is an interface for functions which are statically polymorphic. In other words, they are functions which before compile time are polymorphic, but after a successful compilation have a fixed static signature. This makes implementing what would appear to be generic or polymorphic instead something that is actually static and that still has the language safety properties. Our engine requires that by the end of compilation, everything is static. This is needed so that values can flow safely along the DAG that represents their execution. If the types could change, then we wouldn't be able to safely pass values around.

NOTE: This interface is similar to OldPolyFunc, except that it uses a Unify method that works differently than the original Polymorphisms method. This allows us to build invariants that are used directly by the type unification solver.

type Pos

type Pos struct {
	Line     int    // line number starting at 1
	Column   int    // column number starting at 1
	Filename string // optional source filename, if known
}

Pos represents a position in the code. This is used by the parser and string interpolation. TODO: consider expanding with range characteristics.

type Scope

type Scope struct {
	Variables map[string]Expr
	Functions map[string]Expr // the Expr will usually be an *ExprFunc (actually it's usually (or always) an *ExprSingleton, which wraps an *ExprFunc now)
	Classes   map[string]Stmt

	Chain []Node // chain of previously seen node's
}

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.

func EmptyScope

func EmptyScope() *Scope

EmptyScope returns the zero, empty value for the scope, with all the internal lists initialized appropriately.

func (*Scope) Copy

func (obj *Scope) Copy() *Scope

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 (*Scope) InitScope

func (obj *Scope) InitScope()

InitScope initializes any uninitialized part of the struct. It is safe to use on scopes with existing data.

func (*Scope) IsEmpty

func (obj *Scope) IsEmpty() bool

IsEmpty returns whether or not a scope is empty or not. FIXME: this doesn't currently consider Chain's... Should it?

func (*Scope) Merge

func (obj *Scope) Merge(scope *Scope) error

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?

type ScopeGrapher

type ScopeGrapher interface {
	Node

	// ScopeGraph adds nodes and vertices to the supplied graph.
	ScopeGraph(g *pgraph.Graph)
}

ScopeGrapher adds a method to turn an AST (Expr or Stmt) into a graph so that we can debug the SetScope compilation phase.

type SkipInvariant

type SkipInvariant struct {
	Expr Expr
}

SkipInvariant expresses that a particular expression does must not be part of the final solution, and should be skipped. It can be part of the solving process though.

func (*SkipInvariant) ExprList

func (obj *SkipInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant. It is not used for this invariant.

func (*SkipInvariant) Matches

func (obj *SkipInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches is not used for this invariant.

func (*SkipInvariant) Possible

func (obj *SkipInvariant) Possible(partials []Invariant) error

Possible is not used for this invariant.

func (*SkipInvariant) String

func (obj *SkipInvariant) String() string

String returns a representation of this invariant.

type Stmt

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

	// Unify 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.
	Unify() ([]Invariant, error)

	// Graph returns the reactive function graph expressed by this node.
	Graph() (*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)
}

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 Txn

type Txn interface {
	// AddVertex adds a vertex to the running graph. The operation will get
	// completed when Commit is run.
	AddVertex(Func) Txn

	// AddEdge adds an edge to the running graph. The operation will get
	// completed when Commit is run.
	AddEdge(Func, Func, *FuncEdge) Txn

	// DeleteVertex removes a vertex from the running graph. The operation
	// will get completed when Commit is run.
	DeleteVertex(Func) Txn

	// AddGraph adds a graph to the running graph. The operation will get
	// completed when Commit is run. This function panics if your graph
	// contains vertices that are not of type interfaces.Func or if your
	// edges are not of type *interfaces.FuncEdge.
	AddGraph(*pgraph.Graph) Txn

	// Commit runs the pending transaction.
	Commit() error

	// Clear erases any pending transactions that weren't committed yet.
	Clear()

	// Reverse runs the reverse commit of the last successful operation to
	// Commit. AddVertex is reversed by DeleteVertex, and vice-versa, and
	// the same for AddEdge and DeleteEdge. Keep in mind that if AddEdge is
	// called with either vertex not already part of the graph, it will
	// implicitly add them, but the Reverse operation will not necessarily
	// know that. As a result, it's recommended to not perform operations
	// that have implicit Adds or Deletes. Notwithstanding the above, the
	// initial Txn implementation can and does try to track these changes
	// so that it can correctly reverse them, but this is not guaranteed by
	// API, and it could contain bugs.
	Reverse() error

	// Erase removes the historical information that Reverse would run after
	// Commit.
	Erase()

	// Free releases the wait group that was used to lock around this Txn if
	// needed. It should get called when we're done with any Txn.
	Free()

	// Copy returns a new child Txn that has the same handles, but a
	// separate state. This allows you to do an Add*/Commit/Reverse that
	// isn't affected by a different user of this transaction.
	Copy() Txn

	// Graph returns a copy of the graph. It returns what has been already
	// committed.
	Graph() *pgraph.Graph
}

Txn is the interface that the engine graph API makes available so that functions can modify the function graph dynamically while it is "running". This could be implemented in one of two methods.

Method 1: Have a pair of graph Lock and Unlock methods. Queue up the work to do and when we "commit" the transaction, we're just queuing up the work to do and then we run it all surrounded by the lock.

Method 2: It's possible that we might eventually be able to actually modify the running graph without even causing it to pause at all. In this scenario, the "commit" would just directly perform those operations without even using the Lock and Unlock mutex operations. This is why we don't expose those in the API. It's also safer because someone can't forget to run Unlock which would block the whole code base.

type ValueInvariant

type ValueInvariant struct {
	Expr  Expr
	Value types.Value // pointer
}

ValueInvariant is an invariant that stores the value associated with an expr if it happens to be known statically at unification/compile time. This must only be used for static/pure values. For example, in `$x = 42`, we know that $x is 42. It's useful here because for `printf("hello %d times", 42)` we can get both the format string, and the other args as these new invariants, and we'd store those separately into this invariant, where they can eventually be passed into the generator invariant, where it can parse the format string and we'd be able to produce a precise type for the printf function, since it's nearly impossible to do otherwise since the number of possibilities is infinite! One special note: these values are typically not consumed, by the solver, because they need to be around for the generator invariant to use, so make sure your solver implementation can still terminate with unused invariants!

func (*ValueInvariant) ExprList

func (obj *ValueInvariant) ExprList() []Expr

ExprList returns the list of valid expressions in this invariant.

func (*ValueInvariant) Matches

func (obj *ValueInvariant) Matches(solved map[Expr]*types.Type) (bool, error)

Matches returns whether an invariant matches the existing solution. If it is inconsistent, then it errors.

func (*ValueInvariant) Possible

func (obj *ValueInvariant) Possible(partials []Invariant) error

Possible returns an error if it is certain that it is NOT possible to get a solution with this invariant and the set of partials. In certain cases, it might not be able to determine that it's not possible, while simultaneously not being able to guarantee a possible solution either. In this situation, it should return nil, since this is used as a filtering mechanism, and the nil result of possible is preferred over eliminating a tricky, but possible one.

func (*ValueInvariant) String

func (obj *ValueInvariant) String() string

String returns a representation of this invariant.

type Var

type Var interface {
	types.Value
}

Var is the interface that any valid stand-alone variable must fulfill. NOTE: It's just a simple Value from our types library at the moment.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL