Documentation ¶
Overview ¶
Package syntax implements the Reflow language.
It is a simple, type-safe, applicative domain specific language that compiles to Reflow's Flow IR.
A Reflow type is one of the following (where t1, t2, etc. are themselves types):
int // the type of arbitrary precision integers float // the type of arbitrary precision floats string // the type of (UTF-8 encoded) strings bool // the type of booleans file // the type of files dir // the type of directories (t1, t2, .., tn) // the type of the tuple consisting of types // t1, t2, t3... (id1, id2 t1, .., idn tn) // the type of the tuple (t1, t1, ..., tn), but // with optional labels (syntactic affordance) [t] // the type of list of type t [k:v] // the type of map with keys of type k // and values of type v {id1 t1, id2 t2} // the type of a struct with fields id1, id2 // of types t1, t2 respectively {id1, id2 t1, id3 t3} // the type of struct{id1 t1, id2 t1, id3 t3} // (syntactic affordance) func(t1, t2, ..., tn) tr // the type of a function with argument types // t1, t2, ..., tn, and return type tr func(a1, a2 t1, ..., an tn) tr // a function of type func(t1, t1, ..., tn) tr // with labels (syntactic affordance)
A Reflow expression is one of the following (where e1, e2, .. are themselves expressions, d1, d2, .. are declarations; t1, t2, .. are types):
123 // a literal (arbitrary precision) integer "abc" // a literal UTF-8 encoded string ident // an identifier [e1, e2, e3, ..] // a list of e1, e2, e3.. [e1, e2, ...e3] // list e1, e2, concatenated with list e3 [e1:e2, e3:e4, ..] // a map key e1 to value e2, etc. [e1:e2, e3:e4, ...e5] // as above, with map e5 appended (e1, e2, e3, ..) // a tuple of e1, e2, e3, .. {id1: e1, id2: e2, ..} // a struct with fields id1 with value e1, id2 with value e2, .. {id1, id2, ..} // a shorthand for {id1: id1, id2: id2} {d1; d2; ...; dn; e1} // a block of declarations usable by expression e1 func(id1, id2 t1, id3 t3) t4 => e1 // a function literal with arguments and return type; evaluates e1 func(id1, id2 t1, id3 t3) => e1 // a function literal with arguments, return type omitted exec(d1, d2, ..) t1 {{ template }} // an exec with declarations d1, d2, .., returning t1 with template // identifiers are valid declarations in this context; they are // deparsed as id := id. // takes an optional declaration nondeterministic bool, which tags // this exec as being non-deterministic. e1 <op> e2 // a binary op (||, &&, <, >, <=, >=, !=, ==, +, /, %, &, <<, >>) <op> e1 // unary expression (!) if e1 { d1; d2; ..; e2 } else { d3; d4; ..; e3 } // conditional expression (with declaration blocks) (e1) // parenthesized expression to control precedence int(e1) // builtin float to int type conversion float(e1) // builtin int to float type conversion len(e1) // builtin length operator zip(e1, e2) // builtin zip operator unzip(e1) // builtin unzip operator map(e1) // convert e1 to a map list(e1) // convert e1 to a list make(strlit, d1, ..., dn) // builtin make primitive. identifiers are valid declarations in // this context; they are deparsed as id := id. panic(e1) // terminate the program with error e1 [e1 | c1, c2,..., cn] // list comprehension: evaluate e1 in the environment provided by // the given clauses (see below) e1 ~> e2 // force evaluation of e1, ignore its result, then evaluate e2. flatten(e1) // flatten a list of lists into a single list trace(e1) // trace expression e1: evaluate it, print it to console, // and return it. Can be used for debugging. range(e1, e2) // produce a list of integers with the range of the two expressions.
A comprehension clause is one of the following:
pat <- e1 // bind a pattern to each of the list or map e1 if e1 // filter entries that do not meet the predicate e1
A Reflow declaration is one of the following:
val id = e1 or id := e1 // assign e1 to id val id t1 = e1 // assign e1 to id with type t1 type id t1 // declare id as a type alias to t1 func id(a1, a2 t1) r1 = e1 // sugar for id := func(a1, a2 t1) r1 => e1 func id(a1, a2 t1) = e1 // sugar for id := func(a1, a2 t1) => e1
Value declarations may be preceded by one of the following annotations, each of which takes a list of declarations.
@requires(...) // resource requirement annotation, // takes declarations mem int, // cpu int or cpu float, disk int, // cpufeatures[string, and wide // bool. They indicate resource // requirements for computing the // declaration; if wide is set to // true, then the resource // requirements have no // theoretical upper bound. Wide // is thus useful for // declarations whose // parallelization factor is not // known statically, for example // when processing sharded data.
Value declarations can take destructive pattern bindings, mimicking value constructors. Currently tuples and lists are supported. Patterns accept identifiers and "_" (ignore), but not yet literal values. Patterns follow the grammar:
_ [p1, p2, ..., pn] (p1, p2, ..., pn) {id1: p1, ..., idn: pn} {id1, ..., id3} // sugar for {id1: id1, ..., idn: idn}
For example, the declaration
val [(x1, y1), (x2, y2), _] = e1
pattern matches e1 to a list of length three, whose elements are 2-tuples. The first two tuples are bound; the third is ignored.
A Reflow module consists of, in order: an optional keyspace, a set of optional parameters, and a set of declarations.
keyspace "com.grail.WGSv1" // defines the keyspace to "com.grail.WGS1" param id1 = e1 // defines parameter id1 with default e1 param id2 string // defines parameter without default, of type t1 param ( // block version of parameters id3 int id4 stirng ) declarations // declarations as above
The Reflow language infers types, except for in function arguments and non-default params. Everywhere else, types can safely be omitted. When a type is supplied, it is used as an ascription: the expression is ascribed to the given type; the type checker fails if the expression's type is incompatible with its ascription.
Following Go, semicolons are inserted if a newline appears after the following tokens: identifier, string, integer, template, ')', '}', or ']'
All bytes in a line following the characters "//" are commentary. Comments directly preceding declarations, with no intervening blank line, will be included in `reflow doc` output.
Index ¶
- func Force(v values.T, t *types.T) values.T
- func Modules() (names []string)
- func ParseAndRegisterModule(name string, source io.Reader) error
- func RegisterModule(name string, m *ModuleImpl)
- func Stdlib() (*types.Env, *values.Env)
- type Bundle
- type CaseClause
- type ComprClause
- type ComprKind
- type Decl
- type DeclKind
- type Expr
- type ExprKind
- type FieldExpr
- type FuncMode
- type MatchKind
- type Matcher
- type Module
- type ModuleImpl
- func (m *ModuleImpl) Doc(ident string) string
- func (m *ModuleImpl) Eager() bool
- func (m *ModuleImpl) FlagEnv(flags *flag.FlagSet, venv *values.Env, tenv *types.Env) error
- func (m *ModuleImpl) Flags(sess *Session, env *values.Env) (*flag.FlagSet, error)
- func (m *ModuleImpl) Init(sess *Session, env *types.Env) error
- func (m *ModuleImpl) InjectArgs(sess *Session, args []string) error
- func (m *ModuleImpl) InjectedArgs() []string
- func (m *ModuleImpl) Make(sess *Session, params *values.Env) (values.T, error)
- func (m *ModuleImpl) Param(id string) (*types.T, bool)
- func (m *ModuleImpl) ParamErr(env *types.Env) error
- func (m *ModuleImpl) Params() []Param
- func (m *ModuleImpl) String() string
- func (m *ModuleImpl) Type(penv *types.Env) *types.T
- type Param
- type Parser
- type ParserMode
- type Pat
- func (p *Pat) BindTypes(env *types.Env, t *types.T, use types.Use) error
- func (p *Pat) BindValues(env *values.Env, v values.T) bool
- func (p *Pat) Debug() string
- func (p *Pat) Digest() digest.Digest
- func (p *Pat) Equal(q *Pat) bool
- func (p *Pat) FieldMap() map[string]*Pat
- func (p *Pat) Idents(ids []string) []string
- func (p *Pat) Matchers() []*Matcher
- func (p *Pat) Remove(idents interface{ ... }) (*Pat, []string)
- func (p *Pat) String() string
- type PatField
- type PatKind
- type Path
- type Session
- func (s *Session) Bundle() *Bundle
- func (s *Session) Images() []string
- func (s *Session) NWarn() int
- func (s *Session) Open(path string) (Module, error)
- func (s *Session) SeeImage(image string)
- func (s *Session) Warn(pos scanner.Position, v ...interface{})
- func (s *Session) Warnf(pos scanner.Position, format string, v ...interface{})
- type Sourcer
- type SystemFunc
- type Template
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Force ¶
Force produces a strict version of v. Force either returns an immediate value v, or else a *flow.Flow that will produce the immediate value.
func Modules ¶
func Modules() (names []string)
Modules returns the names of the available system modules.
func ParseAndRegisterModule ¶
ParseAndRegisterModule is like RegisterModule, but parses module from reflow source. This function has the advantage of being able to define module parameters.
func RegisterModule ¶
func RegisterModule(name string, m *ModuleImpl)
RegisterModule is a hook to register custom reflow intrinsics.
Types ¶
type Bundle ¶
type Bundle struct {
// contains filtered or unexported fields
}
Bundle represents a self-contained Reflow module. A bundle contains all necessary sources, arguments, and image references that are required to instantiate the module.
Bundle implements Sourcer.
func (*Bundle) Entrypoint ¶
Entrypoint returns the bundle's entrypoint: its source, command line arguments (which parameterize the module), or an error.
type CaseClause ¶
type CaseClause struct { // Position contains the source position of the clause. It is set by the // parser. scanner.Position // Comment is the commentary text that precedes this case, if any. Comment string // Pat is the pattern of this case. If the value of the switch matches // this pattern, the switch expression's value will be this cases's // expression's value. Pat *Pat // Expr is the expression of this case. If the value of the switch matches // this cases's pattern, the switch expression's value will be this // expression's value. Expr *Expr }
CaseClause is a single case within a switch expression.
func (*CaseClause) Equal ¶
func (c *CaseClause) Equal(d *CaseClause) bool
Equal tests whether case clause c is equivalent to case clause d.
func (*CaseClause) String ¶
func (c *CaseClause) String() string
String renders a tree-formatted version of c.
type ComprClause ¶
type ComprClause struct { // Kind is the clause's kind. Kind ComprKind // Pat is the clause's pattern (ComprEnum). Pat *Pat // Expr is the clause's expression (ComprEnum, ComprFilter). Expr *Expr }
A ComprClause is a single clause in a comprehension expression.
type Decl ¶
type Decl struct { // Position contains the source position of the node. // It is set by the parser. scanner.Position // Comment stores an optional comment attached to the declaration. Comment string // Kind is the Decl's op; see above. Kind DeclKind // Ident is the identifier in a DeclDeclare and DeclType. Ident string // Pat stores the pattern for this declaration. Pat *Pat // Expr stores the rvalue expression for this declaration. Expr *Expr // Type stores the type for DeclDeclare and DeclType. Type *types.T }
A Decl is a Reflow declaration.
type Expr ¶
type Expr struct { // Position contains the source position of the node. // It is set by the parser. scanner.Position // Comment is the commentary text that precedes this expression, // if any. Comment string // Kind is the expression's op; see above. Kind ExprKind // Cond is the condition expression in ExprCond. Cond *Expr // Left is the "left" operand for expressions. Left *Expr // Right is the "right" operand for expressions. Right *Expr // Op is the binary operation in ExprBinop, unary operation in // ExprUnop, and builtin in ExprBuiltin. Op string // Args holds function arguments in an ExprFunc. Args []*types.Field // List holds expressions for list literals. List []*Expr // Map holds expressions for map literals. Map map[*Expr]*Expr // Decls holds declarations for ExprBlock and ExprExec. Decls []*Decl // CaseClauses holds the case clauses for ExprSwitch. CaseClauses []*CaseClause // Fields holds field definitions (identifiers and expressions) // in ExprStruct, ExprTuple Fields []*FieldExpr // Ident stores the identifier for ExprIdent or tag for ExprVariant Ident string // Val stores constant values in ExprLit. Val values.T // Type holds the Type in ExprAscribe, ExprExec, and ExprLit. Type *types.T // Template is the exec template in ExprExec. Template *Template // NonDeterministic defines whether the exec in ExprExec is non-deterministic. NonDeterministic bool ComprExpr *Expr ComprClauses []*ComprClause // Env stores a value environment for ExprThunk. Env *values.Env // Pat stores the bind pattern in a comprehension. Pat *Pat // Module stores the module as opened during type checking. Module Module // contains filtered or unexported fields }
An Expr is a node in Reflow's expression AST.
func (*Expr) Abbrev ¶
Abbrev shows an "abbreviated" pretty-printed version of expression e. These are useful when showing expression values in documentary output; but they do not necessarily parse. Abbrev strips unnecessary parentheses from arithmetic expressions.
func (*Expr) Digest ¶
Digest computes an identifier that uniquely identifies what Expr e will compute with the given environment. i.e., it can be used as a key to identify the computation represented by Expr e and an environment.
On a semantic level, Digest is one-to-many: that is, there are many candidate digests for a given semantic computation. However, we go through some lengths to normalize, for example by using De Bruijn indices (levels) to remove dependence on concrete names. In the future, we could consider canonicalizing the expression tree as well (e.g., by exploiting commutativity, etc.)
type ExprKind ¶
type ExprKind int
ExprKind is the kind of an expression.
const ( // ExprError indicates an erroneous expression (e.g., through a parse error) ExprError ExprKind = iota // ExprIdent is an identifier reference. ExprIdent // ExprBinop is a binary operation. ExprBinop // ExprUnop is a unary operation. ExprUnop // ExprApply is function application. ExprApply // ExprLit is a literal value. ExprLit // ExprAscribe is an ascription (static type assertion). ExprAscribe // ExprBlock is a declaration-block. ExprBlock // ExprFunc is a function definition. ExprFunc // ExprTuple is a tuple literal. ExprTuple // ExprStruct is a struct literal. ExprStruct // ExprList is a list literal. ExprList // ExprMap is a map literal. ExprMap // ExprVariant is a variant construction expression. ExprVariant // ExprExec is an exec expression. ExprExec // ExprCond is a conditional expression. ExprCond // ExprSwitch is a switch expression. ExprSwitch // ExprDeref is a struct derefence expression. ExprDeref // ExprIndex is an indexing (map or list) expression. ExprIndex // ExprCompr is a comprehension expression. ExprCompr // ExprMake is a module instantiation expression. ExprMake // ExprBuiltin is a builtin expression (e.g., len, zip, unzip). ExprBuiltin // ExprRequires assigns resources to the underlying expression. // It also necessarily forces the value. ExprRequires // ExprThunk is a delayed evaluation (expression + environment). // These are never produced from parsing--they are used internally // by the evaluator. (But see note there.) ExprThunk )
type FuncMode ¶
type FuncMode int
FuncMode represents the behavior mode for SystemFuncs.
const ( // ModeDefault functions are passed arguments that have been evaluated. ModeDefault FuncMode = iota // ModeForced functions are passed arguments that have been evaluated and forced. ModeForced // ModeDirect functions are passed arguments that have not been evaluated (i.e. may be *flow.Flow). ModeDirect )
type MatchKind ¶
type MatchKind int
MatchKind is the kind of match performed by a Matcher.
const ( // MatchError is an erroneous matcher. MatchError MatchKind = iota // MatchValue matches a value. MatchValue // MatchTuple indexes a tuple. MatchTuple // MatchList indexes a list. MatchList // MatchListTail indexes the tail of a list. MatchListTail // MatchStruct indexes a struct. MatchStruct // MatchVariant matches a variant or, if the variant has an element, indexes // the element. MatchVariant )
type Matcher ¶
type Matcher struct { // Kind is the kind of matcher. Kind MatchKind // Ident is the identifier to which the value of this matcher's match should // be bound (MatchValue). If Ident == "", no identifier should be bound. Ident string // Index is the index of the match (MatchTuple, MatchList). Index int // Length is the required length of the containing tuple or list // (MatchTuple, MatchList). For MatchTuple, this is already validated by // the type-checker and only included here for convenience. Length int // AllowTail specifies whether the matcher allows the containing list to // have a tail (i.e. be longer than the pattern) (MatchList). AllowTail bool // Parent is this matcher's parent. Parent *Matcher // Field holds a struct field (MatchStruct). Field string // Tag is the variant tag required for a match. Tag string }
A Matcher binds individual pattern components (identifiers) in a pattern. Matchers form a tree; their interpretation (through method Match) performs value destructuring.
type Module ¶
type Module interface { // Make creates a new module instance in the provided session // with the provided parameters. Make(sess *Session, params *values.Env) (values.T, error) // ParamErr type-checks parameter types, returning an error on failure. ParamErr(env *types.Env) error // Flags returns the set of flags provided by this module. // Note that not all modules may have parameters that are supported // by the regular flag types. These return an error. Flags(sess *Session, env *values.Env) (*flag.FlagSet, error) // FlagEnv adds flags from the FlagSet to value environment env. // The FlagSet should be produced by Module.Flags. FlagEnv(flags *flag.FlagSet, env *values.Env, tenv *types.Env) error // Params returns the parameter descriptors for this module. Params() []Param // Doc returns the docstring for a toplevel identifier. Doc(string) string // Type returns the type of the module, given the types of the // parameters passed. Typechecking is done at the module's creation // time, but this creates predicated const types. The returned type // satisfies all predicates implied by the provided params, and thus // represents a unique type for an instantiation site. Type(params *types.Env) *types.T // Eager tells whether the module requires eager parameters. // When it does, all parameters are forced and fully evaluated // before instantiating a new module instance. Eager() bool // InjectArgs injects a set of command line flags to override module // parameters. InjectArgs(sess *Session, args []string) error // InjectedArgs returns the arguments that were injected into this module. InjectedArgs() []string }
Module abstracts a Reflow module, having the ability to type check parameters, inspect its type, and mint new instances.
func OpenBundleModule ¶
func OpenBundleModule(src io.ReaderAt, size int64, srcPath string, srcDigest digest.Digest) (Module, error)
OpenBundleModule opens a bundle archive saved by Bundle.Write and parses it into a Module. The arguments are: src: The source code (expected to be a bundle archive saved using Bundle.Write. size: Size of the source code bytes. srcPath: Path of the source which was used to fetch the source from the underlying Sourcer. srcDigest: Digest of the underlying source code OpenBundleModule uses a cache to retrieve known already parsed bundles (by digest).
type ModuleImpl ¶
type ModuleImpl struct { // Keyspace is the (optional) key space of this module. Keyspace *Expr // Reservation is the set of reservation declarations. Reservation []*Decl // ParamDecls is the set of declared parameters for this module. ParamDecls []*Decl // Decls is the set of declarations in this module. Decls []*Decl Docs map[string]string // contains filtered or unexported fields }
ModuleImpl defines a Reflow module comprising: a keyspace, a set of parameters, and a set of declarations.
func (*ModuleImpl) Doc ¶
func (m *ModuleImpl) Doc(ident string) string
Doc returns the documentation for the provided identifier.
func (*ModuleImpl) FlagEnv ¶
FlagEnv adds all flags from the FlagSet to value environment venv and type environment tenv. The FlagSet should be produced by (*Module).Flags.
func (*ModuleImpl) Flags ¶
Flags returns a FlagSet that captures the parameters of this module. This can be used to parameterize a module from the command line. The returned FlagSet uses parameter documentation as the help text.
func (*ModuleImpl) Init ¶
func (m *ModuleImpl) Init(sess *Session, env *types.Env) error
Init type checks this module and returns any type checking errors.
func (*ModuleImpl) InjectArgs ¶
func (m *ModuleImpl) InjectArgs(sess *Session, args []string) error
InjectArgs parameterizes the module with the provided flags. This is equivalent to either providing parameters or overriding their default values.
TODO(marius): this is really a hack; we should be serializing the environments directly instead.
func (*ModuleImpl) InjectedArgs ¶
func (m *ModuleImpl) InjectedArgs() []string
func (*ModuleImpl) Make ¶
Make creates a new instance of this module. ParamDecls contains the value environment storing parameter values.
func (*ModuleImpl) Param ¶
func (m *ModuleImpl) Param(id string) (*types.T, bool)
Param returns the type of the module parameter with identifier id, and whether it is mandatory.
func (*ModuleImpl) ParamErr ¶
func (m *ModuleImpl) ParamErr(env *types.Env) error
ParamErr type checks the type environment env against the parameters of this module. It returns any type checking errors (e.g., badly typed parameters, or missing ones).
func (*ModuleImpl) Params ¶
func (m *ModuleImpl) Params() []Param
Params returns the parameter metadata for this module.
func (*ModuleImpl) String ¶
func (m *ModuleImpl) String() string
String renders a tree-formatted version of m.
type Parser ¶
type Parser struct { // File is prefixed to parser error locations. File string // Body is the io.Reader that is parsed. Body io.Reader // Mode governs how the parser is started. See documentation above. // The fields Module, Decls, Expr, Type, and Pat are set depending on the // parser mode. Mode ParserMode // Module contains the parsed module (ParseModule). Module *ModuleImpl // Decls contains the parsed declarations (ParseDecls). Decls []*Decl // Expr contains the parsed expression (ParseExpr). Expr *Expr // Type contains the parsed type (ParseType). Type *types.T // Pat contains the parsed pattern (ParsePat). Pat *Pat // contains filtered or unexported fields }
Parser is a Reflow lexer. It composes an (internal) scanner to produce tokens for the YACC grammar. Parser inserts semicolons following the rules outlined in the package docs. Lex implements the (internal) yyParser.
type ParserMode ¶
type ParserMode int
ParserMode determines the lexer's entry behavior.
const ( // ParseModule parses a module. ParseModule ParserMode = iota // ParseDecls parses a set of declarations. ParseDecls // ParseExpr parses an expression. ParseExpr // ParseType parses a type. ParseType // ParsePat parses a pattern. ParsePat )
type Pat ¶
type Pat struct { scanner.Position Kind PatKind Ident string List []*Pat // Tail is the pattern to which to bind the tail of the list (PatList). If // nil, the pattern will only match lists of exactly the same length as the // pattern. Tail *Pat // Tag is the tag of the variant to match. Tag string // Elem is the pattern used to match the element of the variant, if it // it has one. Elem *Pat Fields []PatField }
A Pat stores a pattern tree used in destructuring operations. Patterns can bind type and value environments. They can also produce matchers that can be used to selectively match and bind identifiers.
func (*Pat) BindTypes ¶
BindTypes binds the pattern's identifier's types in the passed environment, given the type of binding value t.
func (*Pat) BindValues ¶
BindValues binds this pattern's values in the given value environment.
type PatKind ¶
type PatKind int
PatKind is the kind of pattern.
const ( // PatError is an erroneous pattern // (e.g., uninitialized or parse error). PatError PatKind = iota // PatIdent is an identifier pattern. PatIdent // PatTuple is a tuple pattern. PatTuple // PatList is a list pattern. PatList // PatStruct is a struct pattern. PatStruct // PatVariant is a variant pattern. PatVariant // PatIgnore is an ignore pattern. PatIgnore )
type Path ¶
type Path []*Matcher
Path represents a path to a value.
type Session ¶
type Session struct {
// Stdout and stderr is the writer to which standard output and error are written.
Stdout, Stderr io.Writer
// Stdwarn is the writer to which warnings are printed.
Stdwarn io.Writer
Types *types.Env
Values *values.Env
// contains filtered or unexported fields
}
A Session is a compiler session. It's responsible for opening, parsing and type checking modules.
func NewSession ¶
NewSession creates and initializes a session, reading source bytes from the provided sourcer.
If src is nil, the default Sourcer is selected.
func (*Session) Bundle ¶
Bundle creates a bundle that represents a self-contained Reflow module. Its entry point is the first module that was opened in the session.
func (*Session) Open ¶
Open parses and type checks, and then returns the module at the given path. If Source is set and if the given module path is present in it, then it reads the module source from it. It then returns the module and any associated error.
type Sourcer ¶
type Sourcer interface { // Source returns the source bytes for the provided path. Source(path string) ([]byte, digest.Digest, error) }
Sourcer is an interface that provides access to Reflow source files.
var Filesystem Sourcer = filesystem{}
Filesystem is a Sourcer that reads from the local file system.
type SystemFunc ¶
type SystemFunc struct { Module string Id string Doc string Type *types.T Mode FuncMode Do func(loc values.Location, args []values.T) (values.T, error) }
SystemFunc is a utility to define a reflow intrinsic.
func (SystemFunc) Decl ¶
func (s SystemFunc) Decl() *Decl
Decl returns the intrinsic as a reflow declaration.
func (SystemFunc) Digest ¶
func (s SystemFunc) Digest() digest.Digest
Digest computes the digest of the intrinsic.
type Template ¶
Template is an exec template and its interpolation arguments. The template is stored as a number of fragments interspersed by argument expressions to be rendered.
The following are guaranteed invariant:
len(Frags) > 0 len(Frags) == len(Args)+1
func (*Template) FormatString ¶
FormatString returns a format string that can be used to render the final template. It's provided for backwards compatibility only.