interfaces

package
v0.0.0-...-43bd847 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2024 License: GPL-3.0 Imports: 11 Imported by: 66

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.

func StructToCallableArgs

func StructToCallableArgs(st types.Value) ([]types.Value, error)

StructToCallableArgs transforms the single value, graph representation of the callable values into a linear, standard args list.

Types

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 BuildableFunc

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

	// 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().
	// This can also be used as a "check" method to make sure that the
	// unification result for this function is one of the valid
	// possibilities. This can happen if the specified unification variables
	// do not guarantee a valid type. (For example: the sig for the len()
	// function is `func(?1) int`, but we can't build the function if ?1 is
	// an int or a float. That is checked during Build.
	Build(*types.Type) (*types.Type, error)
}

BuildableFunc is an interface for functions which need a Build or Check step. These functions need that method called after type unification to either tell them the precise type, and/or Check if it's a valid solution. These functions are usually polymorphic before compile time. After a successful compilation, every function include these, must 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 doesn't require any Infer/Check methods because simple polymorphism can be achieved by having a type signature that contains unification variables. Variants that require fancier extensions can implement the InferableFunc interface as well.

type CallableFunc

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

	// Call this function with the input args and return the value if it is
	// possible to do so at this time. To transform from the single value,
	// graph representation of the callable values into a linear, standard
	// args list for use here, you can use the StructToCallableArgs
	// function.
	Call(ctx context.Context, args []types.Value) (types.Value, error)
}

CallableFunc is a function that can be called statically if we want to do it speculatively or from a resource.

type CopyableFunc

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

	// Copy is used because we sometimes copy the ExprFunc with its Copy
	// method because we're using the same ExprFunc in two places, and it
	// might have a different type and type unification needs to solve for
	// it in more than one way. It also turns out that some functions such
	// as the struct lookup function store information that they learned
	// during `FuncInfer`, and as a result, if we re-build this, then we
	// lose that information and the function can then fail during `Build`.
	// As a result, those functions can implement a `Copy` method which we
	// will use instead, so they can preserve any internal state that they
	// would like to keep.
	Copy() Func
}

CopyableFunc is an interface which extends the base Func interface with the ability to let our compiler know how to copy a Func if that func deems it's needed to be able to do so.

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

	// 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 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 Func

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

	// Validate ensures that our struct implementing this function was built
	// correctly.
	Validate() error

	// 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 type that includes unification
	// variables if it is not known yet. This is because the Info method
	// might be called speculatively to aid in type unification elsewhere.
	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(context.Context, []types.Value) (types.Value, error)

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

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 InferableFunc

type InferableFunc interface {
	BuildableFunc // includes Build and the base Func stuff...

	// FuncInfer returns the type and the list of invariants that this func
	// produces. That type may include unification variables. This is a
	// fancy way for a polymorphic function to describe its type
	// requirements. It uses compile-time information to help it build the
	// correct signature and constraints. This compile time information is
	// passed into this method as a list of partial "hints" that take the
	// form of a (possible partial) 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 of partial values 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 unambiguous result. Remember that your result
	// can include unification variables, but it should not be a standalone
	// ?1 variable. It should at the minimum be of the form `func(?1) ?2`.
	// Since this is almost always called by an ExprCall when building
	// invariants for type unification, we'll know the precise number of
	// args the function is being called with, so you can use this
	// information to more correctly discern the correct function you want
	// to build. The arg names in your returned func type signatures can be
	// in the standardized "a..b..c" format. Use util.NumToAlpha if you want
	// to convert easily. These arg names will be replaced by the correct
	// ones during the Build step. All of these features and limitations are
	// this way so that we can use the standard Union-Fund type unification
	// algorithm which runs fairly quickly.
	// TODO: Do we ever need to return any invariants?
	FuncInfer(partialType *types.Type, partialValues []types.Value) (*types.Type, []*UnificationInvariant, error)
}

InferableFunc is an interface which extends the BuildableFunc interface by adding a new function that can give the user more control over how function inference runs. This allows the user to return more precise information for type unification from compile-time information, than would otherwise be possible.

NOTE: This is the third iteration of this interface which is now incredibly well-polished.

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 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. XXX: I expect that we can get rid of this since type unification doesn't care what the arguments are named, and at the end, we get them from Info or Build.

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

	// 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.
	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 UnificationInvariant

type UnificationInvariant struct {
	// Expr is the expression we are determining the type for. This improves
	// our error messages.
	Expr Expr

	// Expect is one of the two types to unify.
	Expect *types.Type

	// Actual is one of the two types to unify.
	Actual *types.Type
}

UnificationInvariant is the only type of invariant that we currently support. It always lets you specify an `Expr` so that we know what we're referring to. It always lets you specify two types which must get unified for a successful solution. Those two types are symmetrical in that it doesn't matter which is used where, it only affects how we print out error messages.

func GenericCheck

func GenericCheck(obj Expr, typ *types.Type) ([]*UnificationInvariant, error)

GenericCheck is the generic implementation of the Check Expr interface call. It 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.

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