Documentation ¶
Index ¶
- Constants
- func FindModulesPath(metadata *Metadata, base, modules string) (string, error)
- func FindModulesPathList(metadata *Metadata, base, modules string) ([]string, error)
- type Data
- type DataFunc
- type DownloadInfo
- type Downloader
- type Edge
- type Error
- type Expr
- type Func
- type FuncData
- type ImportData
- type Info
- type Init
- type Invariant
- type Metadata
- type NamedArgsFunc
- type Node
- type Output
- type PolyFunc
- type Scope
- type Stmt
- type Var
Constants ¶
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. ModuleSep = "." // VarPrefix is the prefix character that precedes the variables // identifer. For example, `$foo` or for a lambda, `$fn(42)`. VarPrefix = "$" )
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") )
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 ¶
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 ¶
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 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 // 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 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) 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. Graph() (*pgraph.Graph, error) // Func returns a function that represents this reactively. Func() (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 { 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(*Init) error Stream() error Close() 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 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 //Noop bool Input chan types.Value // Engine will close `input` chan Output chan types.Value // Stream must close `output` chan // TODO: should we pass in a *Scope here for functions like template() ? 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.
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 ¶
ParseMetadata reads from some input and returns a *Metadata struct that contains plausible values to be used.
func (*Metadata) SetAbsSelfPath ¶
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) UnmarshalYAML ¶
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 Output ¶
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 // 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 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.) Build(*types.Type) error // then, you can get argNames from Info() }
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.
type Scope ¶
type Scope struct { Variables map[string]Expr Functions map[string]Expr // the Expr will usually be an *ExprFunc Classes map[string]Stmt // TODO: It is easier to shift a list, but let's use a map for Indexes // for now in case we ever need holes... Indexes map[int][]Expr // TODO: use [][]Expr instead? 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 ¶
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 ¶
IsEmpty returns whether or not a scope is empty or not. FIXME: this doesn't currently consider Chain's... Should it?
func (*Scope) MaxIndexes ¶
MaxIndexes returns the maximum index of Indexes stored in the scope. If it is empty then -1 is returned.
func (*Scope) Merge ¶
Merge takes an existing scope and merges a scope on top of it. If any elements had to be overwritten, then the error result will contain some info. Even if this errors, the scope will have been merged successfully. The merge runs in a deterministic order so that errors will be consistent. Use Copy if you don't want to change this destructively. FIXME: this doesn't currently merge Chain's... Should it?
func (*Scope) PullIndexes ¶
PullIndexes takes a list of expressions from the zeroth index in Indexes and then pulls everyone over by one. The returned value is only valid if one was found at the zeroth index. The returned boolean will be true if it exists.
func (*Scope) PushIndexes ¶
PushIndexes adds a list of expressions at the zeroth index in Indexes after firsh pushing everyone else over by one. If you pass in nil input this may panic!
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. Output() (*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.)