Skip to content

Commit

Permalink
v1.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mandelsoft committed Dec 6, 2019
2 parents aac631c + b2e3a48 commit 5136c85
Show file tree
Hide file tree
Showing 21 changed files with 1,791 additions and 1,106 deletions.
10 changes: 9 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

113 changes: 109 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ Contents:
- [(( x509publickey(key) ))](#-x509publickeykey-)
- [(( x509cert(spec) ))](#-x509certspec-)
- [(( lambda |x|->x ":" port ))](#-lambda-x-x--port-)
- [Positional versus Named Argunments](#positional-versus-named-arguments)
- [Scopes and Lambda Expressions](#scopes-and-lambda-expressions)
- [Optional Parameters (( |x,y=2|-> x * y ))](#optional-parameters)
- [Variable Argument Lists (( |x,y...|-> x y ))](#variable-argument-lists)
Expand Down Expand Up @@ -3314,7 +3315,10 @@ cert:

## `(( lambda |x|->x ":" port ))`

Lambda expressions can be used to define additional anonymous functions. They can be assigned to yaml nodes as values and referenced with path expressions to call the function with approriate arguments in other dynaml expressions. For the final document they are mapped to string values.
Lambda expressions can be used to define additional anonymous functions. They
can be assigned to yaml nodes as values and referenced with path expressions
to call the function with approriate arguments in other dynaml expressions.
For the final document they are mapped to string values.

There are two forms of lambda expressions. While

Expand Down Expand Up @@ -3361,6 +3365,49 @@ for complex scoped and curried functions this is not possible.
Therefore function nodes should always be _temporary_ or _local_ to be available
during processing or merging, but being omitted for the final document.


### Positional versus Named Arguments

A typical function call uses positional arguments. Here the given arguments
satisfy the declared function parameters in the given order.
For lambda values it is also possible to use named arguments in the call
expression. Here an argument is assigned to a dedicated parameter as declared
by the lambda expression. The order of named arguments can be arbitrarily chosen.

e.g.:

```yaml
func: (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result: (( .func(c=1, b=2, a=1) ))
```

It is also posible to combine named with positional arguments. Hereby the
positional arguments must follow the named ones.

e.g.:

```yaml
func: (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result: (( .func(c=1, 1, 2) ))
```

The same argument MUST NOT be satified by both, a named and a positional
argument.

Instead of using the parameter name it is also possible to use the parameter
index, instead.

e.g.:

```yaml
func: (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result: (( .func(3=1, 1) ))
```

As such, this feature seems to be quite useless, but it shows its power if
combined with [optional parameters](#optional-parameters) or
[currying](#currying) as shown in the next paragraphs.

### Scopes and Lambda Expressions

A lambda expression might refer to absolute or relative nodes of the actual yaml document of the call. Relative references are evaluated in the context of the function call. Therefore
Expand Down Expand Up @@ -3464,9 +3511,28 @@ It is possible to default all parameters of a lambda expression. The function
can then be called without arguments. There might be no non-defaulted parameters
after a defaulted one.

A call may only omit arguments for optional parameters from right to left. If
there should be an explicit argument for the right most parameter, arguments for
all parameters must be specified. Cherry-picking is not possible.
A call with positional arguments may only omit arguments for optional parameters
from right to left. If there should be an explicit argument for the right most
parameter, arguments for all parameters must be specified or
[named arguments](#positional-versus-named-arguments) must be used.
Here the desired optional parameter can explicitly be set prior to the regular
positional arguments.

e.g.:

```yaml
func: (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
result: (( .func(c=3, 2) ))
```

evaluates `result` to

```yaml
result:
a: 2
b: 1
c: 3
```

The expression for the default does not need to be a constant value or even
expression, it might refer to other nodes in the yaml document. The default
Expand Down Expand Up @@ -3512,6 +3578,8 @@ If no argument is given for the _varargs_ parameter its value is the empty list.

The `...` operator can also be used for [inline list expansion](#inline-list-expansion).

If a vararg parameter should be set by a [named argument](#positional-versus-named-arguments)
its value must be a list.

### Currying

Expand Down Expand Up @@ -3565,6 +3633,43 @@ There are several builtin functions acting on unevaluated or unevaluatable
arguments, like [`defined`](#-definedfoobar-). For these functions currying is
not possible.

Using positional arguments currying is only possible from right to left.
But currying can also be done for [named arguments](#positional-versus-named-arguments).
Here any parameter combination, regardless of the position in the parameter
list, can be preset. The resulting function then has the unsatisfied parameters
in their original order. Switching the parameter order is not possible.

e.g.:

```yaml
func: (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
curry: (( .func(c=3, 2) ))
result: (( .curry(5) ))
```

evalutes `result` to

```yaml
result:
a: 2
b: 5
c: 3
```

The resulting function keeps the parameter `b`. Hereby the default value will
be kept. Therefore it can just be called without argument (`.curry()`), which
would produce

```yaml
result:
a: 2
b: 1
c: 3
```

**Attention**:

For compatibility reasons currying is also done, if a lambda function without
defaulted parameters is called with less arguments than declared parameters.

Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var cfgFile string
var rootCmd = &cobra.Command{
Use: "spiff",
Short: "YAML in-domain templating processor",
Version: "v1.4.0-beta-2",
Version: "v1.4.0",
}

// Execute adds all child commands to the root command and sets flags appropriately.
Expand Down
42 changes: 36 additions & 6 deletions dynaml/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dynaml

import (
"fmt"
"github.com/mandelsoft/spiff/yaml"
"strings"

"github.com/mandelsoft/spiff/debug"
Expand All @@ -15,6 +16,15 @@ func RegisterFunction(name string, f Function) {
functions[name] = f
}

type NameArgument struct {
Name string
Expression
}

func (a NameArgument) String() string {
return fmt.Sprintf("%s=%s", a.Name, a.Expression)
}

type CallExpr struct {
Function Expression
Arguments []Expression
Expand Down Expand Up @@ -83,16 +93,33 @@ func (e CallExpr) Evaluate(binding Binding, locally bool) (interface{}, Evaluati
return e, info, true
}

named := map[string]yaml.Node{}
found := -1
for i := range e.Arguments {
if n, ok := e.Arguments[i].(NameArgument); ok {
named[n.Name] = NewNode(values[i], binding)
found = i
} else {
break
}
}
if found >= 0 {
values = values[found+1:]
}

var result interface{}
var sub EvaluationInfo

if funcName != "" && len(named) > 0 {
return info.Error("no named arguments for builtin function (%s)", e.Function)
}
if funcName != "" && e.Curry {
params := []Parameter{Parameter{Name: "__args"}}
args := make([]Expression, len(values)+1)
for i, v := range values {
args[i] = ValueExpr{v}
}
args[len(values)] = VarArgsExpr{ReferenceExpr{[]string{"__args"}}}
args[len(values)] = ListExpansionExpr{ReferenceExpr{[]string{"__args"}}}
expr := CallExpr{
Function: ReferenceExpr{[]string{funcName}},
Arguments: args,
Expand All @@ -103,7 +130,7 @@ func (e CallExpr) Evaluate(binding Binding, locally bool) (interface{}, Evaluati
switch funcName {
case "":
debug.Debug("calling lambda function %#v\n", value)
resolved, result, sub, ok = value.(LambdaValue).Evaluate(false, e.Curry, true, values, binding, false)
resolved, result, sub, ok = value.(LambdaValue).Evaluate(false, e.Curry, true, named, values, binding, false)

case "static_ips":
result, sub, ok = func_static_ips(e.Arguments, binding)
Expand Down Expand Up @@ -289,9 +316,12 @@ func (e CallExpr) Evaluate(binding Binding, locally bool) (interface{}, Evaluati

func (e CallExpr) String() string {
args := make([]string, len(e.Arguments))
for i, e := range e.Arguments {
args[i] = fmt.Sprintf("%s", e)
for i, a := range e.Arguments {
args[i] = fmt.Sprintf("%s", a)
}

return fmt.Sprintf("%s(%s)", e.Function, strings.Join(args, ", "))
curry := ""
if e.Curry {
curry = "*"
}
return fmt.Sprintf("%s%s(%s)", e.Function, curry, strings.Join(args, ", "))
}
2 changes: 1 addition & 1 deletion dynaml/catch.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (e CatchExpr) Evaluate(binding Binding, locally bool) (interface{}, Evaluat
return info.Error("lambda expression for sync condition must take one or two arguments, found %d", len(lambda.lambda.Parameters))
}

resolved, mapped, info, ok := lambda.Evaluate(inline, false, false, inp, binding, false)
resolved, mapped, info, ok := lambda.Evaluate(inline, false, false, nil, inp, binding, false)
if !ok {
debug.Debug("catch lambda failed\n")
return nil, info, false
Expand Down
9 changes: 6 additions & 3 deletions dynaml/dynaml.peg
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ ChainedRef <- PathComponent FollowUpRef
ChainedDynRef <- '.'? '[' Expression ']'
Slice <- Range
Currying <- '*' ChainedCall
ChainedCall <- StartArguments ExpressionList? ')'
ChainedCall <- StartArguments NameArgumentList? ')'
StartArguments <- '(' ws
NameArgumentList <- (( NextNameArgument ( ',' NextNameArgument)* ) / NextExpression) ( ',' NextExpression)*
NextNameArgument <- ws Name ws '=' ws Expression ws

ExpressionList <- NextExpression ( ',' NextExpression)*
NextExpression <- Expression VarArgs?
VarArgs <- '...' ws
NextExpression <- Expression ListExpansion?
ListExpansion <- '...' ws

Projection <- '.'? ( '[*]' / Slice ) ProjectionValue ChainedQualifiedExpression*
ProjectionValue <- {}
Expand Down
Loading

0 comments on commit 5136c85

Please sign in to comment.