Documentation ¶
Overview ¶
Package wdte implements the WDTE scripting language.
WDTE is an embeddable, functionalish scripting language with a primary goal of simplicity of use from the embedding side, which is what this package provides.
Quick Language Overview ¶
In order to understand how this package works, an overview of the language itself is first necessary. WDTE is functional-ish, with some emphasis on the "-ish". Although it generally follows a functional design, it is not purely functional. In WDTE, everything is a function in that everything can be "called", optionally with arguments. Value types return themselves, however, allowing them to be passed around.
WDTE contains a construct called a "compound" which is similar to a function body in most languages. It is surrounded by parentheses and contains a semicolon separated list of expressions, with the last semicolon being optional. The top-level of a WDTE script is a compound without the parentheses. When a compound is executed, each expression is evaluated in turn. If any yield an error, that error is immediately returned from the entire compound. If not, the result of the last expression is returned.
Example 1
# Declare a function called print3 that takes no arguments. let print3 => ( let x => 3; io.writeln io.stdout x; );
There are very few functions built-in in WDTE, but the standard library, found in the std directory and its subdirectories, contains a number of useful functions and definitions. For example, the stream module contains iterator functionality, which provides a means of looping over expressions, something which is otherwise not possible.
Example 2
# Import 'stream' and 'array' and assign them to s and a, # respectively. Note that an import is a compile-time operation, # unlike normal functions. As such, it must be passed a string # literal, not a variable. let s => import 'stream'; let a => import 'arrays'; # Create a function called flatten that takes one argument, # array. let flatten array => # Create a new stream that iterates over array. a.stream array # Create a stream from the previous one that performs a flat # map operation. The (@ name arg => ...) syntax is a lambda # declaration. -> s.flatMap (@ f v => v { # If the current element of the stream, v, is an array, # recursively flatten it into the stream. reflect 'Array' => a.stream v -> s.flatMap f; }) # Collect the previous stream into an array. -> s.collect ;
This example also demonstrates "chains" and "switches", some features that seem complicated at first but quickly become second nature so with some practice.
A chain is a series of expressions separated by either the chain operator, "->", the ignored chain operator, "--", or the error chain operator, "-|". Each piece of the chain is executed in turn, and the output of the previous section is passed as an argument to the output of the current section. In other words, in the previous example, the chain's execution matches the following pseudocode
r1 = a.stream(array) r2 = s.flatMap(<lambda>) r1 = r2(r1) r2 = s.collect return r2(r1)
A chain with a use of "--" operates in much the same way, but the output of the piece of the chain immediately following the operator is ignored, meaning that it doesn't affect the remainder of the chain.
The "-|" chain operator is used for error handling. During the evaluation of a chain, if no errors have occurred, chain segements using "-|" are ignored completely. Unlike with "--", they are completely not executed. If, however, an error occurs, all chain segments that don't use "-|" are ignored instead. If a "-|" segment exists in the chain after the location that the error occurred, then that segment is executed next, following which normal execution continues, unless that segment itself returned an error. If no "-|" segment exists, the error is returned from the entire chain.
Chains can also have "slots" assigned to each piece. This is an identifier immediately following the expression of a piece of chain. This identifier is inserted into the scope for the remainder of the chain, allowing manual access to earlier sections of the chain. For example
let io => import 'io'; let file => import 'io/file'; let readFile path => file.open path : f -> io.string -- io.close f ;
The aforementioned switch expression is the only conditional provided by WDTE. It looks like an expression followed by a semicolon separated series of cases in squiggly braces. A case is two expressions separated by the assignment operator, "=>". The original expression is first evaluated, following which each case's left-hand side is evaluated and the result of the original expression's evaluation is passed to it. If and only if this call results in the boolean value true, the right-hand side of that case is returned. If no cases match, the original expression is returned. For example,
func arg1 { lhs1 arg2 => rhs1 arg3; lhs2 arg4 => rhs2 arg6; }
This is analogous to the following pseudocode
check = func(arg1) if lhs := lhs1(arg2); lhs(check) { return rhs1(arg3) } if lhs := lhs2(arg4); lhs(check) { return rhs2(arg6) } return check
A few more minor points exist as well:
Array literals are a semicolon list of expression surrounded by square brackets. Like in compounds and switches, the last semicolon is optional. Identifier parsing rules are very loose; essentially, anything that isn't ambiguous with an existing keyword, operator, or other syntactic construct is allowed. All strings are essentially heredocs, allowing newlines like they're any other character. There's no difference between single-quoted and double-quoted strings. There are no boolean literals, but the standard library provides true and false functions that are essentially the same thing.
Embedding ¶
As previously mentioned, everything in WDTE is a function. In Go terms, everything in WDTE implements the Func type defined in this package. This includes syntactic constructs as well, such as compounds, switches, and chains.
When a script is parsed by one of the parsing functions in this package, it is translated into a recursive series of Func implementations. The specific types that it is translated to are all defined in and exported by this package. For example, the top-level of a script, being itself a compound, results in the instantiation of a Compound.
What this means in terms of embedding is that the only thing required for interaction between Go and WDTE is an interoperative layer of Func implementations. As a functional language, WDTE is stateless; there is no global interpreter state to keep track of at all. Systems for tracking interpreter state, should they be required, are provided by the repl package.
When a Func is called, it is passed a Frame. A Frame keeps track of anything the function needs that isn't directly an argument to the function. This includes the scope in which the Func call should be evaluated. For example, the expression
func arg1 arg2
translates to an instance of the FuncCall implementation of Func. When the FuncCall is "called", it must be given a scope which contains, at a minimum, "func", "arg1", and "arg2", or the call will fail with an error. It is through this mechanism that new functions can be provided to WDTE. A custom scope can be created with new implementations of Func inserted into it. If this scope is inserted into a Frame which is then passed to a call of, for example, the top-level compound created by parsing a script, they will be available during the evaluation.
Example:
const src = ` let io => import 'io'; io.writeln io.stdout example; ` c, _ := wdte.Parse(strings.NewReader(src), std.Import) scope := std.Scope.Add("example", wdte.String("This is an example.")) r := c.Call(std.F().WithScope(scope)) if err, ok := r.(error); ok { log.Fatalln(err) }
This will print "This is an example." to stdout.
For convenience, a simple function wrapper around the single method required by Func is provided in the form of GoFunc. GoFunc provides a number of extra features, such as automatically converting panics into errors, but for the most part is just a simple wrapper around manual implementations of Func. If more automatic behavior is required, possibly at the cost of some runtime performance, functions for automatically wrapping Go functions are provided in the wdteutil package.
Example ¶
package main import ( "fmt" "os" "strings" "github.com/DeedleFake/wdte" "github.com/DeedleFake/wdte/wdteutil" ) const src = ` let i => import 'some/import/path/or/another'; i.print 3; + 5 2 -> i.print; 7 -> + 5 -> i.print; ` func im(from string) (*wdte.Scope, error) { return wdte.S().Map(map[wdte.ID]wdte.Func{ "print": wdteutil.Func("print", func(v interface{}) interface{} { fmt.Println(v) return v }), }), nil } func Sum(frame wdte.Frame, args ...wdte.Func) wdte.Func { frame = frame.Sub("+") if len(args) < 2 { return wdteutil.SaveArgs(wdte.GoFunc(Sum), args...) } var sum wdte.Number for _, arg := range args { sum += arg.(wdte.Number) } return sum } func main() { m, err := wdte.Parse(strings.NewReader(src), wdte.ImportFunc(im), nil) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing script: %v\n", err) os.Exit(1) } scope := wdte.S().Add("+", wdte.GoFunc(Sum)) r := m.Call(wdte.F().WithScope(scope)) if err, ok := r.(error); ok { fmt.Fprintf(os.Stderr, "Error running script: %v\n", err) os.Exit(1) } }
Output:
Index ¶
- Constants
- func Reflect(f Func, name string) bool
- type Array
- type Assigner
- type Atter
- type Bool
- type Chain
- type ChainPiece
- type Collector
- type Comparer
- type Composite
- type Compound
- type Error
- type Frame
- type Func
- type FuncCall
- type GoFunc
- type ID
- type ImportFunc
- type Importer
- type Lambda
- type Lenner
- type LetAssigner
- type Modifier
- type Number
- type PatternAssigner
- type Reflector
- type Scope
- func (s *Scope) Add(id ID, val Func) *Scope
- func (s *Scope) At(i Func) (Func, error)
- func (s *Scope) Call(frame Frame, args ...Func) Func
- func (s *Scope) Custom(getFunc func(ID) Func, known func(map[ID]struct{})) *Scope
- func (s *Scope) Get(id ID) Func
- func (s *Scope) Known() []ID
- func (s *Scope) Map(vars map[ID]Func) *Scope
- func (s *Scope) Parent() *Scope
- func (s *Scope) Reflect(name string) bool
- func (s *Scope) Set(k, v Func) (Func, error)
- func (s *Scope) String() string
- func (s *Scope) Sub(sub *Scope) *Scope
- type Setter
- type SimpleAssigner
- type String
- type Sub
- type Switch
- type Var
Examples ¶
Constants ¶
const ( NormalChain = 0 IgnoredChain = 1 << (iota - 1) ErrorChain )
Variables ¶
This section is empty.
Functions ¶
func Reflect ¶ added in v0.4.4
Reflect checks if a Func can be considered to be of a given type. If v implements Reflector, v.Reflect(name) is used to check for compatability. If not, a simple string comparison is done against whatever Go's reflect package claims the short name of the underlying type to be.
Types ¶
type Array ¶
type Array []Func
An Array represents a WDTE array type. It's similar to a Compound, but when evaluated, it returns itself with its own members replaced with their own evaluations. This allows it to be passed around as a value in the same way as strings and numbers.
type Assigner ¶ added in v0.5.2
type Assigner interface { // Assign produces a subscope from an existing frame, scope, and // function, returning both the new subscope and a function. The // returned function may or may not be related to the original // function, but should be in most cases. // // In the event of an error, the returned scope should be nil to // indicate that the error was not simply stored in the scope, as // that is valid behavior. Assign(frame Frame, scope *Scope, val Func) (*Scope, Func) // IDs returns the list of IDs associated with the Assigner. This is // generally the IDs that will be added to a scope via the Assign // method. IDs() []ID }
An Assigner places items into a scope. How exactly iy does this differs, but the general idea is to produce a subscope from a combination of frame, an existing scope, and a function.
type Bool ¶
type Bool bool
Bool is a boolean. Like other primitive types, it simply returns itself when called.
type ChainPiece ¶ added in v0.5.3
A ChainPiece is, as you can probably guess from the name, a piece of a Chain. It stores the underlying expression as well as some extra information necessary for properly evaluating the Chain.
func (ChainPiece) String ¶ added in v0.5.5
func (p ChainPiece) String() string
type Collector ¶ added in v0.10.0
type Collector struct {
Compound Compound
}
Collector wraps a compound, causing it to return its collected scope instead of the last result. If any expression in the compound returns an error, however, then that error is returned instead.
type Comparer ¶
type Comparer interface { // Compare returns two values. The meaning of the first is dependent // upon the second. If the second is true, then the first indicates // ordering via the standard negative, positive, and zero results to // indicate less than, greater than, and equal, respectively. If the // second is false, then the first indicates only equality, with // zero still meaning equal, but other values simply meaning unequal. Compare(other Func) (int, bool) }
A Comparer is a Func that is able to be compared to other functions.
type Composite ¶ added in v0.12.0
type Composite []Func
Composite represents a composite function. When called, it calls its components in reverse order on their previous results. In other words,
Composite{func1, func2, func3}.Call(frame, arg1, arg2)
is the equivalent of
func1 (func2 (func3 arg1 arg2))
type Compound ¶
type Compound []Func
A Compound represents a compound expression. Calling it calls each of the expressions in the compound, returning the value of the last one. If the compound is empty, nil is returned.
If an element of a compound is an Assigner, it is used to build a new subscope under which the remainder of the elements of the compound will be evaluated. If the element is the last element of the compound, the Func returned by its assignment is returned from the whole compound.
func FromAST ¶
FromAST translates an AST into a top-level compound. im is used to handle import statements. If im is nil, a no-op importer is used.
func Parse ¶
Parse parses an AST from r and then translates it into a top-level compound. im is used to handle import statements. If im is nil, a no-op importer is used. In most cases, std.Import is a good default.
type Error ¶
type Error struct { // Err is the error that generated the Error. In a lot of cases, // this is just a simple error message. Err error // Frame is the frame of the function that the error was first // generated in. Frame Frame }
An Error is returned by any of the built-in functions when they run into an error.
type Frame ¶
type Frame struct {
// contains filtered or unexported fields
}
A Frame tracks information about the current function call, such as the scope that the function is being executed in and debugging info.
func F ¶
func F() Frame
F returns a top-level frame. This can be used by Go code calling WDTE functions directly if another frame is not available.
In many cases, it may be preferable to use std.F() instead.
func (Frame) ID ¶
ID returns the ID of the frame. This is generally the function that created the frame.
func (Frame) Parent ¶
Parent returns the frame that this frame was created from, or a blank frame if there was none.
func (Frame) Sub ¶
Sub returns a new child frame of f with the given ID and the same scope as f.
Under most circumstances, a GoFunc should call this before calling any WDTE functions, as it is useful for debugging. For example:
func Example(frame wdte.Frame, args ...wdte.Func) wdte.Func { frame = frame.Sub("example") ... }
func (Frame) WithContext ¶
WithContext returns a copy of f with the given context.
type Func ¶
type Func interface { // Call calls the function with the given arguments, returning its // return value. frame represents the current call frame, which // tracks scope as well as debugging info. Call(frame Frame, args ...Func) Func }
Func is the base type through which all data is handled by WDTE. It represents everything that can be passed around in the language. This includes functions, of course, expressions, strings, numbers, Go functions, and anything else the client wants to pass into WDTE.
type FuncCall ¶ added in v0.2.1
A FuncCall is an unevaluated function call. This is usually the right-hand side of a function declaration, but could also be any of various pieces of switches, compounds, or arrays.
type GoFunc ¶
A GoFunc is an implementation of Func that calls a Go function. This is the easiest way to implement lower-level systems for WDTE scripts to make use of.
For example, to implement a simple, non-type-safe addition function:
GoFunc(func(frame wdte.Frame, args ...wdte.Func) wdte.Func { frame = frame.Sub("+") var sum wdte.Number for _, arg := range(args) { sum += arg.Call(frame).(wdte.Number) } return sum })
If placed into a scope with the ID "+", this function can then be called from WDTE as follows:
- 3 6 9
As shown, it is recommended that arguments be passed the given frame when evaluating them. Failing to do so without knowing what you're doing can cause unexpected behavior, including sending the evaluation system into infinite loops or causing panics.
In the event that a GoFunc panics with an error value, it will be automatically caught and converted into an Error, which will then be returned.
type ImportFunc ¶
ImportFunc is a wrapper around simple functions to allow them to be used as Importers.
type Importer ¶
An Importer creates scopes from strings. When parsing a WDTE script, an importer is used to import scopes into namespaces.
When the WDTE import expression
import 'example'
is parsed, the associated Importer will be invoked as follows:
im.Import("example")
type Lambda ¶
A Lambda is a closure. When called, it calls its inner expression with itself and its own arguments placed into the scope. In other words, given the lambda
(@ ex x y => + x y)
it will create a new subscope containing itself under the ID "ex", and its first and second arguments under the IDs "x" and "y", respectively. It will then evaluate `+ x y` in that new scope.
type Lenner ¶
type Lenner interface {
Len() int
}
A Lenner is a Func that has a length, such as arrays and strings.
type LetAssigner ¶ added in v0.10.1
A LetAssigner assigns a pre-defined expression using an Assigner. Unlike other Assigners, it completely ignores the val argument of its Assign method.
func (LetAssigner) Call ¶ added in v0.10.1
func (a LetAssigner) Call(frame Frame, args ...Func) Func
func (LetAssigner) String ¶ added in v0.10.1
func (a LetAssigner) String() string
type Modifier ¶ added in v0.12.0
Modifier applies modifications to a function before passing it its own arguments by calling Mods and passing it Func.
type Number ¶
type Number float64
A Number is a number, as parsed from a number literal. That's about it. Like everything else, it's a function. It simply returns itself when called.
type PatternAssigner ¶ added in v0.10.1
type PatternAssigner []Assigner
PatternAssigner assigns variables to the corresponding indices of an Atter under the assumption that the Atter uses integer indices. For example, given
PatternAssigner{ "a", "b", "c" }
assiging with a value of
wdte.Array{wdte.Number(1), wdte.Number(5), wdte.Number(3)}
will result in a subscope with
a = 1 b = 5 c = 3
func (PatternAssigner) IDs ¶ added in v0.10.1
func (a PatternAssigner) IDs() []ID
func (PatternAssigner) String ¶ added in v0.10.1
func (a PatternAssigner) String() string
type Reflector ¶ added in v0.4.2
A Reflector is a Func that can determine if it can be treated as the named type or not. For example,
s := wdte.String("example") return s.Reflect("string")
returns true.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope is a tiered storage space for local variables. This includes function parameters and chain slots. A nil *Scope is equivalent to a blank, top-level scope.
func (*Scope) Custom ¶
Custom returns a new subscope that uses the given lookup function to retrieve values. If getFunc returns nil, the parent of s will be searched. known is an optional function which adds all variables known to this layer of the scope into the map that it is passed as keys.
func (*Scope) Get ¶
Get returns the value of the variable with the given ID. If the variable doesn't exist in either the current scope or any of its parent scopes, nil is returned.
func (*Scope) Map ¶
Map returns a subscope that includes the given mapping of variable names to functions. Note that no copy is made of vars, so changing the map after passing it to this method may result in undefined behavior.
type Setter ¶ added in v0.8.0
A Setter is a Func that can produce a new Func from itself with a key-value mapping applied in some way. For example, a scope can produce a subscope with a new variable added to it, or an array can produce a new array with an index modified.
type SimpleAssigner ¶ added in v0.10.1
type SimpleAssigner ID
SimpleAssigner is an Assigner that assigns a single variable to a value.
func (SimpleAssigner) IDs ¶ added in v0.10.1
func (a SimpleAssigner) IDs() []ID
func (SimpleAssigner) String ¶ added in v0.10.1
func (a SimpleAssigner) String() string
type String ¶
type String string
A String is a string, as parsed from a string literal. That's about it. Like everything else, it's a function. It simply returns itself when called.
type Sub ¶
type Sub []Func
A Sub is a function that is in a subscope. This is most commonly an imported function.
type Switch ¶
type Switch struct { // Check is the condition at the front of the switch. Check Func // Cases is the switch's cases. Each contains two functions. The // first index is the left-hand side, while the second is the // right-hand side. When the switch is evaluated, the cases are run // in order. If any matches, the right-hand side is evaluated and // its return value is returned. Cases [][2]Func }
Switch represents a switch expression.
Directories ¶
Path | Synopsis |
---|---|
Package ast provides the parser for WDTE.
|
Package ast provides the parser for WDTE. |
cmd
|
|
Package repl provides a layer intended to help with the development of a read-eval-print loop.
|
Package repl provides a layer intended to help with the development of a read-eval-print loop. |
Package scanner provides a scanner for WDTE tokens.
|
Package scanner provides a scanner for WDTE tokens. |
Package std provides a number of basic WDTE functions.
|
Package std provides a number of basic WDTE functions. |
all
Package all is a convenience package that imports the entire standard library, thus registering it with std.Import.
|
Package all is a convenience package that imports the entire standard library, thus registering it with std.Import. |
arrays
Package arrays contains functions for manipulating arrays.
|
Package arrays contains functions for manipulating arrays. |
io
Package io contains WDTE functions for dealing with files and other types of data streams.
|
Package io contains WDTE functions for dealing with files and other types of data streams. |
io/file
Package file provides functions for dealing with files.
|
Package file provides functions for dealing with files. |
math
Package math contains wdte.Funcs for performing mathematical operations.
|
Package math contains wdte.Funcs for performing mathematical operations. |
rand
Package rand provides functions for generating and dealing with random numbers.
|
Package rand provides functions for generating and dealing with random numbers. |
stream
Package stream provides WDTE functions for manipulating streams of data.
|
Package stream provides WDTE functions for manipulating streams of data. |
strings
Package strings contains functions for dealing with strings.
|
Package strings contains functions for dealing with strings. |
Package wdteutil provides higher-level automatic wrappers and convenience functions for Go/WDTE interoperability.
|
Package wdteutil provides higher-level automatic wrappers and convenience functions for Go/WDTE interoperability. |