lang: Add function values and lambdas

This adds a giant missing piece of the language: proper function values!
It is lovely to now understand why early programming language designers
didn't implement these, but a joy to now reap the benefits of them. In
adding these, many other changes had to be made to get them to "fit"
correctly. This improved the code and fixed a number of bugs.
Unfortunately this touched many areas of the code, and since I was
learning how to do all of this for the first time, I've squashed most of
my work into a single commit. Some more information:

* This adds over 70 new tests to verify the new functionality.

* Functions, global variables, and classes can all be implemented
natively in mcl and built into core packages.

* A new compiler step called "Ordering" was added. It is called by the
SetScope step, and determines statement ordering and shadowing
precedence formally. It helped remove at least one bug and provided the
additional analysis required to properly capture variables when
implementing function generators and closures.

* The type unification code was improved to handle the new cases.

* Light copying of Node's allowed our function graphs to be more optimal
and share common vertices and edges. For example, if two different
closures capture a variable $x, they'll both use the same copy when
running the function, since the compiler can prove if they're identical.

* Some areas still need improvements, but this is ready for mainstream
testing and use!
This commit is contained in:
James Shubin
2019-06-04 21:51:21 -04:00
parent 4f1c463bdd
commit f53376cea1
189 changed files with 7170 additions and 849 deletions

View File

@@ -21,6 +21,7 @@ SHELL = /usr/bin/env bash
# a large amount of output from this `find`, can cause `make` to be much slower! # a large amount of output from this `find`, can cause `make` to be much slower!
GO_FILES := $(shell find * -name '*.go' -not -path 'old/*' -not -path 'tmp/*') GO_FILES := $(shell find * -name '*.go' -not -path 'old/*' -not -path 'tmp/*')
MCL_FILES := $(shell find lang/funcs/ -name '*.mcl' -not -path 'old/*' -not -path 'tmp/*')
SVERSION := $(or $(SVERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --dirty --always)) SVERSION := $(or $(SVERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --dirty --always))
VERSION := $(or $(VERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --abbrev=0)) VERSION := $(or $(VERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --abbrev=0))
@@ -131,7 +132,7 @@ lang: ## generates the lexer/parser for the language frontend
$(PROGRAM): build/mgmt-${GOHOSTOS}-${GOHOSTARCH} ## build an mgmt binary for current host os/arch $(PROGRAM): build/mgmt-${GOHOSTOS}-${GOHOSTARCH} ## build an mgmt binary for current host os/arch
cp -a $< $@ cp -a $< $@
$(PROGRAM).static: $(GO_FILES) $(PROGRAM).static: $(GO_FILES) $(MCL_FILES)
@echo "Building: $(PROGRAM).static, version: $(SVERSION)..." @echo "Building: $(PROGRAM).static, version: $(SVERSION)..."
go generate go generate
go build -a -installsuffix cgo -tags netgo -ldflags '-extldflags "-static" -X main.program=$(PROGRAM) -X main.version=$(SVERSION) -s -w' -o $(PROGRAM).static $(BUILD_FLAGS); go build -a -installsuffix cgo -tags netgo -ldflags '-extldflags "-static" -X main.program=$(PROGRAM) -X main.version=$(SVERSION) -s -w' -o $(PROGRAM).static $(BUILD_FLAGS);
@@ -146,7 +147,7 @@ build-debug: $(PROGRAM)
# extract os and arch from target pattern # extract os and arch from target pattern
GOOS=$(firstword $(subst -, ,$*)) GOOS=$(firstword $(subst -, ,$*))
GOARCH=$(lastword $(subst -, ,$*)) GOARCH=$(lastword $(subst -, ,$*))
build/mgmt-%: $(GO_FILES) | bindata lang funcgen build/mgmt-%: $(GO_FILES) $(MCL_FILES) | bindata lang funcgen
@echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..." @echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..."
@# reassigning GOOS and GOARCH to make build command copy/pastable @# reassigning GOOS and GOARCH to make build command copy/pastable
@# go 1.10+ requires specifying the package for ldflags @# go 1.10+ requires specifying the package for ldflags

View File

@@ -511,6 +511,9 @@ without making any changes. The `ExprVar` node naturally consumes scope's and
the `StmtProg` node cleverly passes the scope through in the order expected for the `StmtProg` node cleverly passes the scope through in the order expected for
the out-of-order bind logic to work. the out-of-order bind logic to work.
This step typically calls the ordering algorithm to determine the correct order
of statements in a program.
#### Type unification #### Type unification
Each expression must have a known type. The unpleasant option is to force the Each expression must have a known type. The unpleasant option is to force the

View File

@@ -33,6 +33,7 @@ build: $(GENERATED)
# add more input files as dependencies at the end here... # add more input files as dependencies at the end here...
$(GENERATED): $(MCL_FILES) $(GENERATED): $(MCL_FILES)
@echo "Generating: native mcl..."
@# go-bindata --pkg bindata -o <OUTPUT> <INPUT> @# go-bindata --pkg bindata -o <OUTPUT> <INPUT>
go-bindata --pkg bindata -o ./$@ $^ go-bindata --pkg bindata -o ./$@ $^
@# gofmt the output file @# gofmt the output file

View File

@@ -48,6 +48,15 @@ type ContainsPolyFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *ContainsPolyFunc) ArgGen(index int) (string, error) {
seq := []string{"needle", "haystack"}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Polymorphisms returns the list of possible function signatures available for // Polymorphisms returns the list of possible function signatures available for
// this static polymorphic function. It relies on type and value hints to limit // this static polymorphic function. It relies on type and value hints to limit
// the number of returned possibilities. // the number of returned possibilities.
@@ -155,11 +164,15 @@ func (obj *ContainsPolyFunc) Validate() error {
// Info returns some static info about itself. Build must be called before this // Info returns some static info about itself. Build must be called before this
// will return correct data. // will return correct data.
func (obj *ContainsPolyFunc) Info() *interfaces.Info { func (obj *ContainsPolyFunc) Info() *interfaces.Info {
typ := types.NewType(fmt.Sprintf("func(needle %s, haystack []%s) bool", obj.Type.String(), obj.Type.String())) var sig *types.Type
if obj.Type != nil { // don't panic if called speculatively
s := obj.Type.String()
sig = types.NewType(fmt.Sprintf("func(needle %s, haystack []%s) bool", s, s))
}
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: false,
Sig: typ, // func kind Sig: sig, // func kind
Err: obj.Validate(), Err: obj.Validate(),
} }
} }

View File

@@ -0,0 +1,21 @@
# Mgmt
# Copyright (C) 2013-2019+ James Shubin and the project contributors
# Written by James Shubin <james@shubin.ca> and the project contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
# This is a native (mcl) function.
func nativeanswer() {
42
}

View File

@@ -0,0 +1,38 @@
// Mgmt
// Copyright (C) 2013-2019+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package coreexample
import (
"github.com/purpleidea/mgmt/lang/funcs/simple"
"github.com/purpleidea/mgmt/lang/types"
)
func init() {
simple.ModuleRegister(ModuleName, "plus", &types.FuncValue{
T: types.NewType("func(y str, z str) str"),
V: Plus,
})
}
// Plus returns y + z.
func Plus(input []types.Value) (types.Value, error) {
y, z := input[0].Str(), input[1].Str()
return &types.StrValue{
V: y + z,
}, nil
}

View File

@@ -0,0 +1,31 @@
# Mgmt
# Copyright (C) 2013-2019+ James Shubin and the project contributors
# Written by James Shubin <james@shubin.ca> and the project contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
# This is a native (mcl) function.
func test1() {
42
}
# This is a native (mcl) global variable.
$test1 = 13
# This is a native (mcl) class.
class test1 {
print "inside" {
msg => "wow, cool",
}
}

View File

@@ -50,6 +50,15 @@ type VUMeterFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *VUMeterFunc) ArgGen(index int) (string, error) {
seq := []string{"symbol", "multiplier", "peak"}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Validate makes sure we've built our struct properly. It is usually unused for // Validate makes sure we've built our struct properly. It is usually unused for
// normal functions that users can use directly. // normal functions that users can use directly.
func (obj *VUMeterFunc) Validate() error { func (obj *VUMeterFunc) Validate() error {

View File

@@ -33,9 +33,7 @@ func init() {
} }
const ( const (
// XXX: does this need to be `a` ? -- for now yes, fix this compiler bug formatArgName = "format" // name of the first arg
//formatArgName = "format" // name of the first arg
formatArgName = "a" // name of the first arg
) )
// PrintfFunc is a static polymorphic function that compiles a format string and // PrintfFunc is a static polymorphic function that compiles a format string and
@@ -58,6 +56,14 @@ type PrintfFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *PrintfFunc) ArgGen(index int) (string, error) {
if index == 0 {
return formatArgName, nil
}
return util.NumToAlpha(index - 1), nil
}
// Polymorphisms returns the possible type signature for this function. In this // Polymorphisms returns the possible type signature for this function. In this
// case, since the number of arguments can be infinite, it returns the final // case, since the number of arguments can be infinite, it returns the final
// precise type if it can be gleamed from the format argument. If it cannot, it // precise type if it can be gleamed from the format argument. If it cannot, it
@@ -108,9 +114,9 @@ func (obj *PrintfFunc) Polymorphisms(partialType *types.Type, partialValues []ty
typ.Ord = append(typ.Ord, formatArgName) typ.Ord = append(typ.Ord, formatArgName)
for i, x := range typList { for i, x := range typList {
name := util.NumToAlpha(i + 1) // +1 to skip the format arg name := util.NumToAlpha(i) // start with a...
if name == formatArgName { if name == formatArgName {
return nil, fmt.Errorf("could not build function with %d args", i+1) return nil, fmt.Errorf("could not build function with %d args", i+1) // +1 for format arg
} }
// if we also had even more partial type information, check it! // if we also had even more partial type information, check it!

View File

@@ -49,6 +49,15 @@ type ReadFileFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *ReadFileFunc) ArgGen(index int) (string, error) {
seq := []string{"filename"}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Validate makes sure we've built our struct properly. It is usually unused for // Validate makes sure we've built our struct properly. It is usually unused for
// normal functions that users can use directly. // normal functions that users can use directly.
func (obj *ReadFileFunc) Validate() error { func (obj *ReadFileFunc) Validate() error {

View File

@@ -52,6 +52,15 @@ type Random1Func struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *Random1Func) ArgGen(index int) (string, error) {
seq := []string{"length"}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Validate makes sure we've built our struct properly. It is usually unused for // Validate makes sure we've built our struct properly. It is usually unused for
// normal functions that users can use directly. // normal functions that users can use directly.
func (obj *Random1Func) Validate() error { func (obj *Random1Func) Validate() error {

View File

@@ -41,8 +41,14 @@ func init() {
funcs.Register("template", func() interfaces.Func { return &TemplateFunc{} }) funcs.Register("template", func() interfaces.Func { return &TemplateFunc{} })
} }
// TemplateName is the name of our template as required by the template library. const (
const TemplateName = "template" // TemplateName is the name of our template as required by the template
// library.
TemplateName = "template"
argNameTemplate = "template"
argNameVars = "vars"
)
// TemplateFunc is a static polymorphic function that compiles a template and // TemplateFunc is a static polymorphic function that compiles a template and
// returns the output as a string. It bases its output on the values passed in // returns the output as a string. It bases its output on the values passed in
@@ -62,6 +68,15 @@ type TemplateFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *TemplateFunc) ArgGen(index int) (string, error) {
seq := []string{argNameTemplate, argNameVars}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Polymorphisms returns the possible type signatures for this template. In this // Polymorphisms returns the possible type signatures for this template. In this
// case, since the second argument can be an infinite number of values, it // case, since the second argument can be an infinite number of values, it
// instead returns either the final precise type (if it can be gleamed from the // instead returns either the final precise type (if it can be gleamed from the
@@ -72,7 +87,8 @@ type TemplateFunc struct {
// XXX: is there a better API than returning a buried `variant` type? // XXX: is there a better API than returning a buried `variant` type?
func (obj *TemplateFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) { func (obj *TemplateFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) {
// TODO: return `variant` as second arg for now -- maybe there's a better way? // TODO: return `variant` as second arg for now -- maybe there's a better way?
variant := []*types.Type{types.NewType("func(a str, b variant) str")} str := fmt.Sprintf("func(%s str, %s variant) str", argNameTemplate, argNameVars)
variant := []*types.Type{types.NewType(str)}
if partialType == nil { if partialType == nil {
return variant, nil return variant, nil
@@ -150,10 +166,15 @@ func (obj *TemplateFunc) Validate() error {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *TemplateFunc) Info() *interfaces.Info { func (obj *TemplateFunc) Info() *interfaces.Info {
var sig *types.Type
if obj.Type != nil { // don't panic if called speculatively
str := fmt.Sprintf("func(%s str, %s %s) str", argNameTemplate, argNameVars, obj.Type.String())
sig = types.NewType(str)
}
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: false,
Sig: types.NewType(fmt.Sprintf("func(template str, vars %s) str", obj.Type.String())), Sig: sig,
Err: obj.Validate(), Err: obj.Validate(),
} }
} }
@@ -293,8 +314,8 @@ func (obj *TemplateFunc) Stream() error {
} }
obj.last = input // store for next obj.last = input // store for next
tmpl := input.Struct()["template"].Str() tmpl := input.Struct()[argNameTemplate].Str()
vars := input.Struct()["vars"] vars := input.Struct()[argNameVars]
result, err := obj.run(tmpl, vars) result, err := obj.run(tmpl, vars)
if err != nil { if err != nil {

View File

@@ -46,6 +46,15 @@ type ExchangeFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *ExchangeFunc) ArgGen(index int) (string, error) {
seq := []string{"namespace", "value"}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Validate makes sure we've built our struct properly. It is usually unused for // Validate makes sure we've built our struct properly. It is usually unused for
// normal functions that users can use directly. // normal functions that users can use directly.
func (obj *ExchangeFunc) Validate() error { func (obj *ExchangeFunc) Validate() error {

View File

@@ -46,6 +46,15 @@ type KVLookupFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *KVLookupFunc) ArgGen(index int) (string, error) {
seq := []string{"namespace"}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Validate makes sure we've built our struct properly. It is usually unused for // Validate makes sure we've built our struct properly. It is usually unused for
// normal functions that users can use directly. // normal functions that users can use directly.
func (obj *KVLookupFunc) Validate() error { func (obj *KVLookupFunc) Validate() error {

View File

@@ -76,6 +76,15 @@ func (obj *SchedulePolyFunc) validOpts() map[string]*types.Type {
} }
} }
// ArgGen returns the Nth arg name for this function.
func (obj *SchedulePolyFunc) ArgGen(index int) (string, error) {
seq := []string{"namespace", "opts"} // 2nd arg is optional
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Polymorphisms returns the list of possible function signatures available for // Polymorphisms returns the list of possible function signatures available for
// this static polymorphic function. It relies on type and value hints to limit // this static polymorphic function. It relies on type and value hints to limit
// the number of returned possibilities. // the number of returned possibilities.

View File

@@ -277,6 +277,10 @@ Loop:
if !ok { if !ok {
break Loop break Loop
} }
if err == nil {
// programming error
err = fmt.Errorf("error was missing")
}
e := errwrap.Wrapf(err, "problem streaming func") e := errwrap.Wrapf(err, "problem streaming func")
reterr = errwrap.Append(reterr, e) reterr = errwrap.Append(reterr, e)
} }
@@ -287,5 +291,13 @@ Loop:
return nil, errwrap.Wrapf(err, "problem closing func") return nil, errwrap.Wrapf(err, "problem closing func")
} }
if result == nil && reterr == nil {
// programming error
// XXX: i think this can happen when we exit without error, but
// before we send one output message... not sure how this happens
// XXX: iow, we never send on output, and errch closes...
// XXX: this could happen if we send zero input args, and Stream exits without error
return nil, fmt.Errorf("function exited with nil result and nil error")
}
return result, reterr return result, reterr
} }

View File

@@ -57,6 +57,15 @@ type HistoryFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *HistoryFunc) ArgGen(index int) (string, error) {
seq := []string{"value", "index"}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Polymorphisms returns the possible type signature for this function. In this // Polymorphisms returns the possible type signature for this function. In this
// case, since the number of possible types for the first arg can be infinite, // case, since the number of possible types for the first arg can be infinite,
// it returns the final precise type only if it can be gleamed statically. If // it returns the final precise type only if it can be gleamed statically. If
@@ -149,10 +158,15 @@ func (obj *HistoryFunc) Validate() error {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *HistoryFunc) Info() *interfaces.Info { func (obj *HistoryFunc) Info() *interfaces.Info {
var sig *types.Type
if obj.Type != nil { // don't panic if called speculatively
s := obj.Type.String()
sig = types.NewType(fmt.Sprintf("func(value %s, index int) %s", s, s))
}
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
Sig: types.NewType(fmt.Sprintf("func(value %s, index int) %s", obj.Type.String(), obj.Type.String())), Sig: sig,
Err: obj.Validate(), Err: obj.Validate(),
} }
} }

View File

@@ -30,6 +30,10 @@ const (
// starts with an underscore so that it cannot be used from the lexer. // starts with an underscore so that it cannot be used from the lexer.
// XXX: change to _maplookup and add syntax in the lexer/parser // XXX: change to _maplookup and add syntax in the lexer/parser
MapLookupFuncName = "maplookup" MapLookupFuncName = "maplookup"
argNameMap = "map"
argNameKey = "key"
argNameDef = "default"
) )
func init() { func init() {
@@ -48,6 +52,15 @@ type MapLookupPolyFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *MapLookupPolyFunc) ArgGen(index int) (string, error) {
seq := []string{argNameMap, argNameKey, argNameDef}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Polymorphisms returns the list of possible function signatures available for // Polymorphisms returns the list of possible function signatures available for
// this static polymorphic function. It relies on type and value hints to limit // this static polymorphic function. It relies on type and value hints to limit
// the number of returned possibilities. // the number of returned possibilities.
@@ -116,20 +129,20 @@ func (obj *MapLookupPolyFunc) Polymorphisms(partialType *types.Type, partialValu
typFunc := &types.Type{ typFunc := &types.Type{
Kind: types.KindFunc, // function type Kind: types.KindFunc, // function type
Map: make(map[string]*types.Type), Map: make(map[string]*types.Type),
Ord: []string{"map", "key", "default"}, Ord: []string{argNameMap, argNameKey, argNameDef},
Out: nil, Out: nil,
} }
typFunc.Map["map"] = typ typFunc.Map[argNameMap] = typ
typFunc.Map["key"] = typ.Key typFunc.Map[argNameKey] = typ.Key
typFunc.Map["default"] = typ.Val typFunc.Map[argNameDef] = typ.Val
typFunc.Out = typ.Val typFunc.Out = typ.Val
// TODO: don't include partial internal func map's for now, allow in future? // TODO: don't include partial internal func map's for now, allow in future?
if typ.Key == nil || typ.Val == nil { if typ.Key == nil || typ.Val == nil {
typFunc.Map = make(map[string]*types.Type) // erase partial typFunc.Map = make(map[string]*types.Type) // erase partial
typFunc.Map["map"] = types.TypeVariant typFunc.Map[argNameMap] = types.TypeVariant
typFunc.Map["key"] = types.TypeVariant typFunc.Map[argNameKey] = types.TypeVariant
typFunc.Map["default"] = types.TypeVariant typFunc.Map[argNameDef] = types.TypeVariant
} }
if typ.Val == nil { if typ.Val == nil {
typFunc.Out = types.TypeVariant typFunc.Out = types.TypeVariant
@@ -211,7 +224,13 @@ func (obj *MapLookupPolyFunc) Validate() error {
// Info returns some static info about itself. Build must be called before this // Info returns some static info about itself. Build must be called before this
// will return correct data. // will return correct data.
func (obj *MapLookupPolyFunc) Info() *interfaces.Info { func (obj *MapLookupPolyFunc) Info() *interfaces.Info {
typ := types.NewType(fmt.Sprintf("func(map %s, key %s, default %s) %s", obj.Type.String(), obj.Type.Key.String(), obj.Type.Val.String(), obj.Type.Val.String())) var typ *types.Type
if obj.Type != nil { // don't panic if called speculatively
// TODO: can obj.Type.Key or obj.Type.Val be nil (a partial) ?
k := obj.Type.Key.String()
v := obj.Type.Val.String()
typ = types.NewType(fmt.Sprintf("func(map %s, key %s, default %s) %s", obj.Type.String(), k, v, v))
}
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: false,
@@ -245,9 +264,9 @@ func (obj *MapLookupPolyFunc) Stream() error {
} }
obj.last = input // store for next obj.last = input // store for next
m := (input.Struct()["map"]).(*types.MapValue) m := (input.Struct()[argNameMap]).(*types.MapValue)
key := input.Struct()["key"] key := input.Struct()[argNameKey]
def := input.Struct()["default"] def := input.Struct()[argNameDef]
var result types.Value var result types.Value
val, exists := m.Lookup(key) val, exists := m.Lookup(key)

View File

@@ -33,8 +33,9 @@ const (
// starts with an underscore so that it cannot be used from the lexer. // starts with an underscore so that it cannot be used from the lexer.
OperatorFuncName = "_operator" OperatorFuncName = "_operator"
// operatorArgName is the edge and arg name used for the function's operator. // operatorArgName is the edge and arg name used for the function's
operatorArgName = "x" // something short and arbitrary // operator.
operatorArgName = "op" // something short and arbitrary
) )
func init() { func init() {
@@ -356,6 +357,7 @@ func RegisterOperator(operator string, fn *types.FuncValue) {
panic(fmt.Sprintf("can't use `%s` as an argName for operator `%s` with type `%+v`", x, operator, fn.T)) panic(fmt.Sprintf("can't use `%s` as an argName for operator `%s` with type `%+v`", x, operator, fn.T))
} }
// yes this limits the arg max to 24 (`x`) including operator // yes this limits the arg max to 24 (`x`) including operator
// if the operator is `x`...
if s := util.NumToAlpha(i); x != s { if s := util.NumToAlpha(i); x != s {
panic(fmt.Sprintf("arg for operator `%s` (index `%d`) should be named `%s`, not `%s`", operator, i, s, x)) panic(fmt.Sprintf("arg for operator `%s` (index `%d`) should be named `%s`, not `%s`", operator, i, s, x))
} }
@@ -387,8 +389,7 @@ func LookupOperator(operator string, size int) ([]*types.Type, error) {
} }
for _, fn := range fns { for _, fn := range fns {
typ := addOperatorArg(fn.T) // add in the `operatorArgName` arg typ := addOperatorArg(fn.T) // add in the `operatorArgName` arg
typ = unlabelOperatorArgNames(typ) // label in standard a..b..c
if size >= 0 && len(typ.Ord) != size { if size >= 0 && len(typ.Ord) != size {
continue continue
@@ -414,7 +415,7 @@ type OperatorPolyFunc struct {
// argNames returns the maximum list of possible argNames. This can be truncated // argNames returns the maximum list of possible argNames. This can be truncated
// if needed. The first arg name is the operator. // if needed. The first arg name is the operator.
func (obj *OperatorPolyFunc) argNames() []string { func (obj *OperatorPolyFunc) argNames() ([]string, error) {
// we could just do this statically, but i did it dynamically so that I // we could just do this statically, but i did it dynamically so that I
// wouldn't ever have to remember to update this list... // wouldn't ever have to remember to update this list...
max := 0 max := 0
@@ -434,12 +435,12 @@ func (obj *OperatorPolyFunc) argNames() []string {
for i := 0; i < max; i++ { for i := 0; i < max; i++ {
s := util.NumToAlpha(i) s := util.NumToAlpha(i)
if s == operatorArgName { if s == operatorArgName {
panic(fmt.Sprintf("can't use `%s` as arg name", operatorArgName)) return nil, fmt.Errorf("can't use `%s` as arg name", operatorArgName)
} }
args = append(args, s) args = append(args, s)
} }
return args return args, nil
} }
// findFunc tries to find the first available registered operator function that // findFunc tries to find the first available registered operator function that
@@ -458,6 +459,18 @@ func (obj *OperatorPolyFunc) findFunc(operator string) *types.FuncValue {
return nil return nil
} }
// ArgGen returns the Nth arg name for this function.
func (obj *OperatorPolyFunc) ArgGen(index int) (string, error) {
seq, err := obj.argNames()
if err != nil {
return "", err
}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Polymorphisms returns the list of possible function signatures available for // Polymorphisms returns the list of possible function signatures available for
// this static polymorphic function. It relies on type and value hints to limit // this static polymorphic function. It relies on type and value hints to limit
// the number of returned possibilities. // the number of returned possibilities.
@@ -507,11 +520,9 @@ func (obj *OperatorPolyFunc) Polymorphisms(partialType *types.Type, partialValue
// specific statically typed version. It is usually run after Unify completes, // specific statically typed version. It is usually run after Unify completes,
// and must be run before Info() and any of the other Func interface methods are // and must be run before Info() and any of the other Func interface methods are
// used. This function is idempotent, as long as the arg isn't changed between // used. This function is idempotent, as long as the arg isn't changed between
// runs. It typically re-labels the input arg names to match what is actually // runs.
// used.
func (obj *OperatorPolyFunc) Build(typ *types.Type) error { func (obj *OperatorPolyFunc) Build(typ *types.Type) error {
// typ is the KindFunc signature we're trying to build... // typ is the KindFunc signature we're trying to build...
if len(typ.Ord) < 1 { if len(typ.Ord) < 1 {
return fmt.Errorf("the operator function needs at least 1 arg") return fmt.Errorf("the operator function needs at least 1 arg")
} }
@@ -519,11 +530,7 @@ func (obj *OperatorPolyFunc) Build(typ *types.Type) error {
return fmt.Errorf("return type of function must be specified") return fmt.Errorf("return type of function must be specified")
} }
t, err := obj.relabelOperatorArgNames(typ) obj.Type = typ // func type
if err != nil {
return fmt.Errorf("could not build function from type: %+v", typ)
}
obj.Type = t // func type
return nil return nil
} }
@@ -635,59 +642,6 @@ func (obj *OperatorPolyFunc) Close() error {
return nil return nil
} }
// relabelOperatorArgNames relabels the input type of kind func with arg names
// that match the expected ones for this operator (which are all standardized).
func (obj *OperatorPolyFunc) relabelOperatorArgNames(typ *types.Type) (*types.Type, error) {
if typ == nil {
return nil, fmt.Errorf("cannot re-label missing type")
}
if typ.Kind != types.KindFunc {
return nil, fmt.Errorf("specified type must be a func kind")
}
argNames := obj.argNames() // correct arg names...
if l := len(argNames); len(typ.Ord) > l {
return nil, fmt.Errorf("did not expect more than %d args", l)
}
m := make(map[string]*types.Type)
ord := []string{}
for pos, x := range typ.Ord { // function args in order
name := argNames[pos] // new arg name
m[name] = typ.Map[x] // n-th type stored with new arg name
ord = append(ord, name)
}
return &types.Type{
Kind: types.KindFunc,
Map: m,
Ord: ord,
Out: typ.Out,
}, nil
}
// unlabelOperatorArgNames unlabels the input type of kind func with arg names
// that match the default ones for all functions (which are all standardized).
func unlabelOperatorArgNames(typ *types.Type) *types.Type {
if typ == nil {
return nil
}
m := make(map[string]*types.Type)
ord := []string{}
for pos, x := range typ.Ord { // function args in order
name := util.NumToAlpha(pos) // default (unspecified) naming
m[name] = typ.Map[x] // n-th type stored with new arg name
ord = append(ord, name)
}
return &types.Type{
Kind: types.KindFunc,
Map: m,
Ord: ord,
Out: typ.Out,
}
}
// removeOperatorArg returns a copy of the input KindFunc type, without the // removeOperatorArg returns a copy of the input KindFunc type, without the
// operator arg which specifies which operator we're using. It *is* idempotent. // operator arg which specifies which operator we're using. It *is* idempotent.
func removeOperatorArg(typ *types.Type) *types.Type { func removeOperatorArg(typ *types.Type) *types.Type {

View File

@@ -26,6 +26,13 @@ import (
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
) )
const (
// DirectInterface specifies whether we should use the direct function
// API or not. If we don't use it, then these simple functions are
// wrapped with the struct below.
DirectInterface = false // XXX: fix any bugs and set to true!
)
// RegisteredFuncs maps a function name to the corresponding static, pure func. // RegisteredFuncs maps a function name to the corresponding static, pure func.
var RegisteredFuncs = make(map[string]*types.FuncValue) // must initialize var RegisteredFuncs = make(map[string]*types.FuncValue) // must initialize
@@ -38,7 +45,7 @@ func Register(name string, fn *types.FuncValue) {
RegisteredFuncs[name] = fn // store a copy for ourselves RegisteredFuncs[name] = fn // store a copy for ourselves
// register a copy in the main function database // register a copy in the main function database
funcs.Register(name, func() interfaces.Func { return &simpleFunc{Fn: fn} }) funcs.Register(name, func() interfaces.Func { return &WrappedFunc{Fn: fn} })
} }
// ModuleRegister is exactly like Register, except that it registers within a // ModuleRegister is exactly like Register, except that it registers within a
@@ -47,9 +54,9 @@ func ModuleRegister(module, name string, fn *types.FuncValue) {
Register(module+funcs.ModuleSep+name, fn) Register(module+funcs.ModuleSep+name, fn)
} }
// simpleFunc is a scaffolding function struct which fulfills the boiler-plate // WrappedFunc is a scaffolding function struct which fulfills the boiler-plate
// for the function API, but that can run a very simple, static, pure function. // for the function API, but that can run a very simple, static, pure function.
type simpleFunc struct { type WrappedFunc struct {
Fn *types.FuncValue Fn *types.FuncValue
init *interfaces.Init init *interfaces.Init
@@ -60,9 +67,22 @@ type simpleFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *WrappedFunc) ArgGen(index int) (string, error) {
typ := obj.Fn.Type()
if typ.Kind != types.KindFunc {
return "", fmt.Errorf("expected %s, got %s", types.KindFunc, typ.Kind)
}
seq := typ.Ord
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Validate makes sure we've built our struct properly. It is usually unused for // Validate makes sure we've built our struct properly. It is usually unused for
// normal functions that users can use directly. // normal functions that users can use directly.
func (obj *simpleFunc) Validate() error { func (obj *WrappedFunc) Validate() error {
if obj.Fn == nil { // build must be run first if obj.Fn == nil { // build must be run first
return fmt.Errorf("type is still unspecified") return fmt.Errorf("type is still unspecified")
} }
@@ -70,7 +90,7 @@ func (obj *simpleFunc) Validate() error {
} }
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *simpleFunc) Info() *interfaces.Info { func (obj *WrappedFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, // TODO: should this be something we specify here? Memo: false, // TODO: should this be something we specify here?
@@ -80,14 +100,14 @@ func (obj *simpleFunc) Info() *interfaces.Info {
} }
// Init runs some startup code for this function. // Init runs some startup code for this function.
func (obj *simpleFunc) Init(init *interfaces.Init) error { func (obj *WrappedFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init
obj.closeChan = make(chan struct{}) obj.closeChan = make(chan struct{})
return nil return nil
} }
// Stream returns the changing values that this func has over time. // Stream returns the changing values that this func has over time.
func (obj *simpleFunc) Stream() error { func (obj *WrappedFunc) Stream() error {
defer close(obj.init.Output) // the sender closes defer close(obj.init.Output) // the sender closes
for { for {
select { select {
@@ -141,7 +161,7 @@ func (obj *simpleFunc) Stream() error {
} }
// Close runs some shutdown code for this function and turns off the stream. // Close runs some shutdown code for this function and turns off the stream.
func (obj *simpleFunc) Close() error { func (obj *WrappedFunc) Close() error {
close(obj.closeChan) close(obj.closeChan)
return nil return nil
} }

View File

@@ -27,6 +27,13 @@ import (
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
) )
const (
// DirectInterface specifies whether we should use the direct function
// API or not. If we don't use it, then these simple functions are
// wrapped with the struct below.
DirectInterface = false // XXX: fix any bugs and set to true!
)
// RegisteredFuncs maps a function name to the corresponding static, pure funcs. // RegisteredFuncs maps a function name to the corresponding static, pure funcs.
var RegisteredFuncs = make(map[string][]*types.FuncValue) // must initialize var RegisteredFuncs = make(map[string][]*types.FuncValue) // must initialize
@@ -60,10 +67,15 @@ func Register(name string, fns []*types.FuncValue) {
panic(fmt.Sprintf("polyfunc %s has a duplicate implementation: %+v", name, err)) panic(fmt.Sprintf("polyfunc %s has a duplicate implementation: %+v", name, err))
} }
_, err := consistentArgs(fns)
if err != nil {
panic(fmt.Sprintf("polyfunc %s has inconsistent arg names: %+v", name, err))
}
RegisteredFuncs[name] = fns // store a copy for ourselves RegisteredFuncs[name] = fns // store a copy for ourselves
// register a copy in the main function database // register a copy in the main function database
funcs.Register(name, func() interfaces.Func { return &simplePolyFunc{Fns: fns} }) funcs.Register(name, func() interfaces.Func { return &WrappedFunc{Fns: fns} })
} }
// ModuleRegister is exactly like Register, except that it registers within a // ModuleRegister is exactly like Register, except that it registers within a
@@ -72,10 +84,38 @@ func ModuleRegister(module, name string, fns []*types.FuncValue) {
Register(module+funcs.ModuleSep+name, fns) Register(module+funcs.ModuleSep+name, fns)
} }
// simplePolyFunc is a scaffolding function struct which fulfills the // consistentArgs returns the list of arg names across all the functions or
// boiler-plate for the function API, but that can run a very simple, static, // errors if one consistent list could not be found.
// pure, polymorphic function. func consistentArgs(fns []*types.FuncValue) ([]string, error) {
type simplePolyFunc struct { if len(fns) == 0 {
return nil, fmt.Errorf("no functions specified for simple polyfunc")
}
seq := []string{}
for _, x := range fns {
typ := x.Type()
if typ.Kind != types.KindFunc {
return nil, fmt.Errorf("expected %s, got %s", types.KindFunc, typ.Kind)
}
ord := typ.Ord
// check
l := len(seq)
if m := len(ord); m < l {
l = m // min
}
for i := 0; i < l; i++ { // check shorter list
if seq[i] != ord[i] {
return nil, fmt.Errorf("arg name at index %d differs (%s != %s)", i, seq[i], ord[i])
}
}
seq = ord // keep longer version!
}
return seq, nil
}
// WrappedFunc is a scaffolding function struct which fulfills the boiler-plate
// for the function API, but that can run a very simple, static, pure,
// polymorphic function.
type WrappedFunc struct {
Fns []*types.FuncValue // list of possible functions Fns []*types.FuncValue // list of possible functions
fn *types.FuncValue // the concrete version of our chosen function fn *types.FuncValue // the concrete version of our chosen function
@@ -88,10 +128,22 @@ type simplePolyFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *WrappedFunc) ArgGen(index int) (string, error) {
seq, err := consistentArgs(obj.Fns)
if err != nil {
return "", err
}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Polymorphisms returns the list of possible function signatures available for // Polymorphisms returns the list of possible function signatures available for
// this static polymorphic function. It relies on type and value hints to limit // this static polymorphic function. It relies on type and value hints to limit
// the number of returned possibilities. // the number of returned possibilities.
func (obj *simplePolyFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) { func (obj *WrappedFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) {
if len(obj.Fns) == 0 { if len(obj.Fns) == 0 {
return nil, fmt.Errorf("no matching signatures for simple polyfunc") return nil, fmt.Errorf("no matching signatures for simple polyfunc")
} }
@@ -101,7 +153,7 @@ func (obj *simplePolyFunc) Polymorphisms(partialType *types.Type, partialValues
for _, f := range obj.Fns { for _, f := range obj.Fns {
// TODO: if status is "both", should we skip as too difficult? // TODO: if status is "both", should we skip as too difficult?
_, err := f.T.ComplexCmp(partialType) _, err := f.T.ComplexCmp(partialType)
// XXX: can an f.T with a variant compare with a partial ? // can an f.T with a variant compare with a partial ? (yes)
if err != nil { if err != nil {
continue continue
} }
@@ -115,58 +167,28 @@ func (obj *simplePolyFunc) Polymorphisms(partialType *types.Type, partialValues
// specific statically typed version. It is usually run after Unify completes, // specific statically typed version. It is usually run after Unify completes,
// and must be run before Info() and any of the other Func interface methods are // and must be run before Info() and any of the other Func interface methods are
// used. // used.
func (obj *simplePolyFunc) Build(typ *types.Type) error { func (obj *WrappedFunc) Build(typ *types.Type) error {
// typ is the KindFunc signature we're trying to build... // typ is the KindFunc signature we're trying to build...
if typ.Out == nil {
return fmt.Errorf("return type of function must be specified")
}
// find typ in obj.Fns index, err := langutil.FnMatch(typ, obj.Fns)
for ix, f := range obj.Fns { if err != nil {
if f.T.HasVariant() { return err
continue // match these if no direct matches exist
}
// FIXME: can we replace this by the complex matcher down below?
if f.T.Cmp(typ) == nil {
obj.buildFunction(typ, ix) // found match at this index
return nil
}
} }
obj.buildFunction(typ, index) // found match at this index
// match concrete type against our list that might contain a variant return nil
var found bool
for ix, f := range obj.Fns {
_, err := typ.ComplexCmp(f.T)
if err != nil {
continue
}
if found { // already found one...
// TODO: we *could* check that the previous duplicate is
// equivalent, but in this case, it is really a bug that
// the function author had by allowing ambiguity in this
return fmt.Errorf("duplicate match found for build type: %+v", typ)
}
found = true
obj.buildFunction(typ, ix) // found match at this index
}
// ensure there's only one match...
if found {
return nil // w00t!
}
return fmt.Errorf("unable to find a compatible function for type: %+v", typ)
} }
// buildFunction builds our concrete static function, from the potentially // buildFunction builds our concrete static function, from the potentially
// abstract, possibly variant containing list of functions. // abstract, possibly variant containing list of functions.
func (obj *simplePolyFunc) buildFunction(typ *types.Type, ix int) { func (obj *WrappedFunc) buildFunction(typ *types.Type, ix int) {
obj.fn = obj.Fns[ix].Copy().(*types.FuncValue) obj.fn = obj.Fns[ix].Copy().(*types.FuncValue)
obj.fn.T = typ.Copy() // overwrites any contained "variant" type obj.fn.T = typ.Copy() // overwrites any contained "variant" type
} }
// Validate makes sure we've built our struct properly. It is usually unused for // Validate makes sure we've built our struct properly. It is usually unused for
// normal functions that users can use directly. // normal functions that users can use directly.
func (obj *simplePolyFunc) Validate() error { func (obj *WrappedFunc) Validate() error {
if len(obj.Fns) == 0 { if len(obj.Fns) == 0 {
return fmt.Errorf("missing list of functions") return fmt.Errorf("missing list of functions")
} }
@@ -195,24 +217,28 @@ func (obj *simplePolyFunc) Validate() error {
} }
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *simplePolyFunc) Info() *interfaces.Info { func (obj *WrappedFunc) Info() *interfaces.Info {
var sig *types.Type
if obj.fn != nil { // don't panic if called speculatively
sig = obj.fn.Type()
}
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, // TODO: should this be something we specify here? Memo: false, // TODO: should this be something we specify here?
Sig: obj.fn.Type(), Sig: sig,
Err: obj.Validate(), Err: obj.Validate(),
} }
} }
// Init runs some startup code for this function. // Init runs some startup code for this function.
func (obj *simplePolyFunc) Init(init *interfaces.Init) error { func (obj *WrappedFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init
obj.closeChan = make(chan struct{}) obj.closeChan = make(chan struct{})
return nil return nil
} }
// Stream returns the changing values that this func has over time. // Stream returns the changing values that this func has over time.
func (obj *simplePolyFunc) Stream() error { func (obj *WrappedFunc) Stream() error {
defer close(obj.init.Output) // the sender closes defer close(obj.init.Output) // the sender closes
for { for {
select { select {
@@ -275,7 +301,7 @@ func (obj *simplePolyFunc) Stream() error {
} }
// Close runs some shutdown code for this function and turns off the stream. // Close runs some shutdown code for this function and turns off the stream.
func (obj *simplePolyFunc) Close() error { func (obj *WrappedFunc) Close() error {
close(obj.closeChan) close(obj.closeChan)
return nil return nil
} }

View File

@@ -50,6 +50,15 @@ type StructLookupPolyFunc struct {
closeChan chan struct{} closeChan chan struct{}
} }
// ArgGen returns the Nth arg name for this function.
func (obj *StructLookupPolyFunc) ArgGen(index int) (string, error) {
seq := []string{"struct", "field"}
if l := len(seq); index >= l {
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
}
return seq[index], nil
}
// Polymorphisms returns the list of possible function signatures available for // Polymorphisms returns the list of possible function signatures available for
// this static polymorphic function. It relies on type and value hints to limit // this static polymorphic function. It relies on type and value hints to limit
// the number of returned possibilities. // the number of returned possibilities.
@@ -200,11 +209,15 @@ func (obj *StructLookupPolyFunc) Validate() error {
// Info returns some static info about itself. Build must be called before this // Info returns some static info about itself. Build must be called before this
// will return correct data. // will return correct data.
func (obj *StructLookupPolyFunc) Info() *interfaces.Info { func (obj *StructLookupPolyFunc) Info() *interfaces.Info {
typ := types.NewType(fmt.Sprintf("func(struct %s, field str) %s", obj.Type.String(), obj.Out.String())) var sig *types.Type
if obj.Type != nil { // don't panic if called speculatively
// TODO: can obj.Out be nil (a partial) ?
sig = types.NewType(fmt.Sprintf("func(struct %s, field str) %s", obj.Type.String(), obj.Out.String()))
}
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: false,
Sig: typ, // func kind Sig: sig, // func kind
Err: obj.Validate(), Err: obj.Validate(),
} }
} }

177
lang/funcs/structs/call.go Normal file
View File

@@ -0,0 +1,177 @@
// Mgmt
// Copyright (C) 2013-2019+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package structs
import (
"fmt"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util/errwrap"
)
// CallFunc is a function that takes in a function and all the args, and passes
// through the results of running the function call.
type CallFunc struct {
Type *types.Type // this is the type of the var's value that we hold
FuncType *types.Type
Edge string // name of the edge used (typically starts with: `call:`)
//Func interfaces.Func // this isn't actually used in the Stream :/
//Fn *types.FuncValue // pass in the actual function instead of Edge
// Indexed specifies that args are accessed by index instead of name.
// This is currently unused.
Indexed bool
init *interfaces.Init
last types.Value // last value received to use for diff
result types.Value // last calculated output
closeChan chan struct{}
}
// Validate makes sure we've built our struct properly.
func (obj *CallFunc) Validate() error {
if obj.Type == nil {
return fmt.Errorf("must specify a type")
}
if obj.FuncType == nil {
return fmt.Errorf("must specify a func type")
}
// TODO: maybe we can remove this if we use this for core functions...
if obj.Edge == "" {
return fmt.Errorf("must specify an edge name")
}
typ := obj.FuncType
// we only care about the output type of calling our func
if err := obj.Type.Cmp(typ.Out); err != nil {
return errwrap.Wrapf(err, "call expr type must match func out type")
}
return nil
}
// Info returns some static info about itself.
func (obj *CallFunc) Info() *interfaces.Info {
typ := &types.Type{
Kind: types.KindFunc, // function type
Map: make(map[string]*types.Type),
Ord: []string{},
Out: obj.Type, // this is the output type for the expression
}
sig := obj.FuncType
if obj.Edge != "" {
typ.Map[obj.Edge] = sig // we get a function in
typ.Ord = append(typ.Ord, obj.Edge)
}
// add any incoming args
for _, key := range sig.Ord { // sig.Out, not sig!
typ.Map[key] = sig.Map[key]
typ.Ord = append(typ.Ord, key)
}
return &interfaces.Info{
Pure: true,
Memo: false, // TODO: ???
Sig: typ,
Err: obj.Validate(),
}
}
// Init runs some startup code for this composite function.
func (obj *CallFunc) Init(init *interfaces.Init) error {
obj.init = init
obj.closeChan = make(chan struct{})
return nil
}
// Stream takes an input struct in the format as described in the Func and Graph
// methods of the Expr, and returns the actual expected value as a stream based
// on the changing inputs to that value.
func (obj *CallFunc) Stream() error {
defer close(obj.init.Output) // the sender closes
for {
select {
case input, ok := <-obj.init.Input:
if !ok {
return nil // can't output any more
}
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
// return errwrap.Wrapf(err, "wrong function input")
//}
if obj.last != nil && input.Cmp(obj.last) == nil {
continue // value didn't change, skip it
}
obj.last = input // store for next
st := input.(*types.StructValue) // must be!
// get the function
fn, exists := st.Lookup(obj.Edge)
if !exists {
return fmt.Errorf("missing expected input argument `%s`", obj.Edge)
}
// get the arguments to call the function
args := []types.Value{}
typ := obj.FuncType
for ix, key := range typ.Ord { // sig!
if obj.Indexed {
key = fmt.Sprintf("%d", ix)
}
value, exists := st.Lookup(key)
// TODO: replace with:
//value, exists := st.Lookup(fmt.Sprintf("arg:%s", key))
if !exists {
return fmt.Errorf("missing expected input argument `%s`", key)
}
args = append(args, value)
}
// actually call it
result, err := fn.(*types.FuncValue).Call(args)
if err != nil {
return errwrap.Wrapf(err, "error calling function")
}
// skip sending an update...
if obj.result != nil && result.Cmp(obj.result) == nil {
continue // result didn't change
}
obj.result = result // store new result
case <-obj.closeChan:
return nil
}
select {
case obj.init.Output <- obj.result: // send
// pass
case <-obj.closeChan:
return nil
}
}
}
// Close runs some shutdown code for this function and turns off the stream.
func (obj *CallFunc) Close() error {
close(obj.closeChan)
return nil
}

View File

@@ -0,0 +1,200 @@
// Mgmt
// Copyright (C) 2013-2019+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package structs
import (
"fmt"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util/errwrap"
)
// FunctionFunc is a function that passes through the function body it receives.
type FunctionFunc struct {
Type *types.Type // this is the type of the function that we hold
Edge string // name of the edge used (typically "body")
Func interfaces.Func
Fn *types.FuncValue
init *interfaces.Init
last types.Value // last value received to use for diff
result types.Value // last calculated output
closeChan chan struct{}
}
// fn returns the function that wraps the Func interface if that API is used.
func (obj *FunctionFunc) fn() (*types.FuncValue, error) {
fn := func(args []types.Value) (types.Value, error) {
// FIXME: can we run a recursive engine
// to support running non-pure functions?
if !obj.Func.Info().Pure {
return nil, fmt.Errorf("only pure functions can be used by value")
}
// XXX: this might not be needed anymore...
return funcs.PureFuncExec(obj.Func, args)
}
result := types.NewFunc(obj.Type) // new func
if err := result.Set(fn); err != nil {
return nil, errwrap.Wrapf(err, "can't build func from built-in")
}
return result, nil
}
// Validate makes sure we've built our struct properly.
func (obj *FunctionFunc) Validate() error {
if obj.Type == nil {
return fmt.Errorf("must specify a type")
}
if obj.Type.Kind != types.KindFunc {
return fmt.Errorf("can't use type `%s`", obj.Type.String())
}
if obj.Edge == "" && obj.Func == nil && obj.Fn == nil {
return fmt.Errorf("must specify an Edge, Func, or Fn")
}
if obj.Fn != nil && obj.Fn.Type() != obj.Type {
return fmt.Errorf("type of Fn did not match input Type")
}
return nil
}
// Info returns some static info about itself.
func (obj *FunctionFunc) Info() *interfaces.Info {
typ := &types.Type{
Kind: types.KindFunc, // function type
Map: make(map[string]*types.Type),
Ord: []string{},
Out: obj.Type, // after the function is called it's this...
}
// type of body is what we'd get by running the function (what's inside)
if obj.Edge != "" {
typ.Map[obj.Edge] = obj.Type.Out
typ.Ord = append(typ.Ord, obj.Edge)
}
pure := true // assume true
if obj.Func != nil {
pure = obj.Func.Info().Pure
}
return &interfaces.Info{
Pure: pure, // TODO: can we guarantee this?
Memo: false, // TODO: ???
Sig: typ,
Err: obj.Validate(),
}
}
// Init runs some startup code for this composite function.
func (obj *FunctionFunc) Init(init *interfaces.Init) error {
obj.init = init
obj.closeChan = make(chan struct{})
return nil
}
// Stream takes an input struct in the format as described in the Func and Graph
// methods of the Expr, and returns the actual expected value as a stream based
// on the changing inputs to that value.
func (obj *FunctionFunc) Stream() error {
defer close(obj.init.Output) // the sender closes
for {
select {
case input, ok := <-obj.init.Input:
if !ok {
if obj.Edge != "" { // then it's not a built-in
return nil // can't output any more
}
var result *types.FuncValue
if obj.Fn != nil {
result = obj.Fn
} else {
var err error
result, err = obj.fn()
if err != nil {
return err
}
}
// if we never had input args, send the function
select {
case obj.init.Output <- result: // send
// pass
case <-obj.closeChan:
return nil
}
return nil
}
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
// return errwrap.Wrapf(err, "wrong function input")
//}
if obj.last != nil && input.Cmp(obj.last) == nil {
continue // value didn't change, skip it
}
obj.last = input // store for next
var result types.Value
st := input.(*types.StructValue) // must be!
value, exists := st.Lookup(obj.Edge) // single argName
if !exists {
return fmt.Errorf("missing expected input argument `%s`", obj.Edge)
}
result = obj.Type.New() // new func
fn := func([]types.Value) (types.Value, error) {
return value, nil
}
if err := result.(*types.FuncValue).Set(fn); err != nil {
return errwrap.Wrapf(err, "can't build func with body")
}
// skip sending an update...
if obj.result != nil && result.Cmp(obj.result) == nil {
continue // result didn't change
}
obj.result = result // store new result
case <-obj.closeChan:
return nil
}
select {
case obj.init.Output <- obj.result: // send
// pass
case <-obj.closeChan:
return nil
}
}
}
// Close runs some shutdown code for this function and turns off the stream.
func (obj *FunctionFunc) Close() error {
close(obj.closeChan)
return nil
}

View File

@@ -22,15 +22,15 @@ import (
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types" "github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util/errwrap" //"github.com/purpleidea/mgmt/util/errwrap"
) )
// VarFunc is a function that passes through a function that came from a bind // VarFunc is a function that passes through a function that came from a bind
// lookup. It exists so that the reactive function engine type checks correctly. // lookup. It exists so that the reactive function engine type checks correctly.
type VarFunc struct { type VarFunc struct {
Type *types.Type // this is the type of the var's value that we hold Type *types.Type // this is the type of the var's value that we hold
Func interfaces.Func Edge string // name of the edge used
Edge string // name of the edge used //Func interfaces.Func // this isn't actually used in the Stream :/
init *interfaces.Init init *interfaces.Init
last types.Value // last value received to use for diff last types.Value // last value received to use for diff
@@ -44,22 +44,9 @@ func (obj *VarFunc) Validate() error {
if obj.Type == nil { if obj.Type == nil {
return fmt.Errorf("must specify a type") return fmt.Errorf("must specify a type")
} }
if obj.Func == nil {
return fmt.Errorf("must specify a func")
}
if obj.Edge == "" { if obj.Edge == "" {
return fmt.Errorf("must specify an edge name") return fmt.Errorf("must specify an edge name")
} }
// we're supposed to call Validate() before we ever call Info()
if err := obj.Func.Validate(); err != nil {
return errwrap.Wrapf(err, "var func did not validate")
}
typ := obj.Func.Info().Sig
if err := obj.Type.Cmp(typ.Out); err != nil {
return errwrap.Wrapf(err, "var expr type must match func out type")
}
return nil return nil
} }

View File

@@ -24,7 +24,6 @@ import (
"sync" "sync"
"github.com/purpleidea/mgmt/gapi" "github.com/purpleidea/mgmt/gapi"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/unification" "github.com/purpleidea/mgmt/lang/unification"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
@@ -271,7 +270,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
"hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used "hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used
}, },
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: funcs.LookupPrefix(""), Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
logf("building scope...") logf("building scope...")

View File

@@ -31,7 +31,12 @@ import (
// methods that they must both implement. In practice it is not used especially // methods that they must both implement. In practice it is not used especially
// often since we usually know which kind of node we want. // often since we usually know which kind of node we want.
type Node interface { 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 Apply(fn func(Node) error) error
//Parent() Node // TODO: should we implement this? //Parent() Node // TODO: should we implement this?
} }
@@ -40,12 +45,37 @@ type Node interface {
// expression.) // expression.)
type Stmt interface { type Stmt interface {
Node Node
fmt.Stringer // String() string
Init(*Data) error // initialize the populated node and validate // Init initializes the populated node and does some basic validation.
Interpolate() (Stmt, error) // return expanded form of AST as a new AST Init(*Data) error
SetScope(*Scope) error // set the scope here and propagate it downwards
Unify() ([]Invariant, error) // TODO: is this named correctly? // 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) 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) Output() (*Output, error)
} }
@@ -55,17 +85,51 @@ type Stmt interface {
// these can be stored as pointers in our graph data structure. // these can be stored as pointers in our graph data structure.
type Expr interface { type Expr interface {
Node Node
//fmt.Stringer // already provided by pgraph.Vertex
pgraph.Vertex // must implement this since we store these in our graphs // Init initializes the populated node and does some basic validation.
Init(*Data) error // initialize the populated node and validate Init(*Data) error
Interpolate() (Expr, error) // return expanded form of AST as a new AST
SetScope(*Scope) error // set the scope here and propagate it downwards // Interpolate returns an expanded form of the AST as a new AST. It does
SetType(*types.Type) error // sets the type definitively, errors if incompatible // 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) Type() (*types.Type, error)
Unify() ([]Invariant, error) // TODO: is this named correctly?
// 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) Graph() (*pgraph.Graph, error)
Func() (Func, error) // a function that represents this reactively
// 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 SetValue(types.Value) error
// Value returns the value of this expression in our type system.
Value() (types.Value, error) Value() (types.Value, error)
} }
@@ -147,10 +211,13 @@ type Data struct {
// from the variables, which could actually contain lambda functions. // from the variables, which could actually contain lambda functions.
type Scope struct { type Scope struct {
Variables map[string]Expr Variables map[string]Expr
Functions map[string]func() Func Functions map[string]Expr // the Expr will usually be an *ExprFunc
Classes map[string]Stmt 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 []Stmt // chain of previously seen stmt's Chain []Node // chain of previously seen node's
} }
// EmptyScope returns the zero, empty value for the scope, with all the internal // EmptyScope returns the zero, empty value for the scope, with all the internal
@@ -158,9 +225,30 @@ type Scope struct {
func EmptyScope() *Scope { func EmptyScope() *Scope {
return &Scope{ return &Scope{
Variables: make(map[string]Expr), Variables: make(map[string]Expr),
Functions: make(map[string]func() Func), Functions: make(map[string]Expr),
Classes: make(map[string]Stmt), Classes: make(map[string]Stmt),
Chain: []Stmt{}, Indexes: make(map[int][]Expr),
Chain: []Node{},
}
}
// InitScope initializes any uninitialized part of the struct. It is safe to use
// on scopes with existing data.
func (obj *Scope) InitScope() {
if obj.Variables == nil {
obj.Variables = make(map[string]Expr)
}
if obj.Functions == nil {
obj.Functions = make(map[string]Expr)
}
if obj.Classes == nil {
obj.Classes = make(map[string]Stmt)
}
if obj.Indexes == nil {
obj.Indexes = make(map[int][]Expr)
}
if obj.Chain == nil {
obj.Chain = []Node{}
} }
} }
@@ -170,10 +258,12 @@ func EmptyScope() *Scope {
// we need those to be consistently pointing to the same things after copying. // we need those to be consistently pointing to the same things after copying.
func (obj *Scope) Copy() *Scope { func (obj *Scope) Copy() *Scope {
variables := make(map[string]Expr) variables := make(map[string]Expr)
functions := make(map[string]func() Func) functions := make(map[string]Expr)
classes := make(map[string]Stmt) classes := make(map[string]Stmt)
chain := []Stmt{} indexes := make(map[int][]Expr)
chain := []Node{}
if obj != nil { // allow copying nil scopes if obj != nil { // allow copying nil scopes
obj.InitScope() // safety
for k, v := range obj.Variables { // copy for k, v := range obj.Variables { // copy
variables[k] = v // we don't copy the expr's! variables[k] = v // we don't copy the expr's!
} }
@@ -183,6 +273,13 @@ func (obj *Scope) Copy() *Scope {
for k, v := range obj.Classes { // copy for k, v := range obj.Classes { // copy
classes[k] = v // we don't copy the StmtClass! classes[k] = v // we don't copy the StmtClass!
} }
for k, v := range obj.Indexes { // copy
ixs := []Expr{}
for _, x := range v {
ixs = append(ixs, x) // we don't copy the expr's!
}
indexes[k] = ixs
}
for _, x := range obj.Chain { // copy for _, x := range obj.Chain { // copy
chain = append(chain, x) // we don't copy the Stmt pointer! chain = append(chain, x) // we don't copy the Stmt pointer!
} }
@@ -191,6 +288,7 @@ func (obj *Scope) Copy() *Scope {
Variables: variables, Variables: variables,
Functions: functions, Functions: functions,
Classes: classes, Classes: classes,
Indexes: indexes,
Chain: chain, Chain: chain,
} }
} }
@@ -220,6 +318,8 @@ func (obj *Scope) Merge(scope *Scope) error {
sort.Strings(namedFunctions) sort.Strings(namedFunctions)
sort.Strings(namedClasses) sort.Strings(namedClasses)
obj.InitScope() // safety
for _, name := range namedVariables { for _, name := range namedVariables {
if _, exists := obj.Variables[name]; exists { if _, exists := obj.Variables[name]; exists {
e := fmt.Errorf("variable `%s` was overwritten", name) e := fmt.Errorf("variable `%s` was overwritten", name)
@@ -242,6 +342,9 @@ func (obj *Scope) Merge(scope *Scope) error {
obj.Classes[name] = scope.Classes[name] obj.Classes[name] = scope.Classes[name]
} }
// FIXME: should we merge or overwrite? (I think this isn't even used)
obj.Indexes = scope.Indexes // overwrite without error
return err return err
} }
@@ -257,12 +360,80 @@ func (obj *Scope) IsEmpty() bool {
if len(obj.Functions) > 0 { if len(obj.Functions) > 0 {
return false return false
} }
if len(obj.Indexes) > 0 { // FIXME: should we check each one? (unused?)
return false
}
if len(obj.Classes) > 0 { if len(obj.Classes) > 0 {
return false return false
} }
return true return true
} }
// MaxIndexes returns the maximum index of Indexes stored in the scope. If it is
// empty then -1 is returned.
func (obj *Scope) MaxIndexes() int {
obj.InitScope() // safety
max := -1
for k := range obj.Indexes {
if k > max {
max = k
}
}
return max
}
// 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!
func (obj *Scope) PushIndexes(exprs []Expr) {
if exprs == nil {
// TODO: is this the right thing to do?
panic("unexpected nil input")
}
obj.InitScope() // safety
max := obj.MaxIndexes()
for i := max; i >= 0; i-- { // reverse order
indexes, exists := obj.Indexes[i]
if !exists {
continue
}
delete(obj.Indexes, i)
obj.Indexes[i+1] = indexes // push it
}
if obj.Indexes == nil { // in case we weren't initialized yet
obj.Indexes = make(map[int][]Expr)
}
obj.Indexes[0] = exprs // usually the list of Args in ExprCall
}
// 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 (obj *Scope) PullIndexes() ([]Expr, bool) {
obj.InitScope() // safety
if obj.Indexes == nil { // in case we weren't initialized yet
obj.Indexes = make(map[int][]Expr)
}
indexes, exists := obj.Indexes[0] // save for later
max := obj.MaxIndexes()
for i := 0; i <= max; i++ {
ixs, exists := obj.Indexes[i]
if !exists {
continue
}
delete(obj.Indexes, i)
if i == 0 { // zero falls off
continue
}
obj.Indexes[i-1] = ixs
}
return indexes, exists
}
// Edge is the data structure representing a compiled edge that is used in the // 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. // lang to express a dependency between two resources and optionally send/recv.
type Edge struct { type Edge struct {

View File

@@ -21,4 +21,8 @@ const (
// ModuleSep is the character used for the module scope separation. For // ModuleSep is the character used for the module scope separation. For
// example when using `fmt.printf` or `math.sin` this is the char used. // example when using `fmt.printf` or `math.sin` this is the char used.
ModuleSep = "." ModuleSep = "."
// VarPrefix is the prefix character that precedes the variables
// identifer. For example, `$foo` or for a lambda, `$fn(42)`.
VarPrefix = "$"
) )

View File

@@ -53,6 +53,13 @@ type Init struct {
// never change to avoid the overhead of the goroutine and channel listener? // never change to avoid the overhead of the goroutine and channel listener?
type Func interface { type Func interface {
Validate() error // FIXME: this is only needed for PolyFunc. Get it moved and used! 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 Info() *Info
Init(*Init) error Init(*Init) error
Stream() error Stream() error
@@ -99,5 +106,5 @@ type NamedArgsFunc interface {
// ArgGen implements the arg name generator function. By default, we use // ArgGen implements the arg name generator function. By default, we use
// the util.NumToAlpha function when this interface isn't implemented... // the util.NumToAlpha function when this interface isn't implemented...
ArgGen(int) string ArgGen(int) (string, error)
} }

View File

@@ -36,4 +36,13 @@ type Invariant interface {
// Matches returns whether an invariant matches the existing solution. // Matches returns whether an invariant matches the existing solution.
// If it is inconsistent, then it errors. // If it is inconsistent, then it errors.
Matches(solved map[Expr]*types.Type) (bool, error) 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
} }

View File

@@ -77,7 +77,7 @@ func TestAstFunc0(t *testing.T) {
"answer": &ExprInt{V: 42}, "answer": &ExprInt{V: 42},
}, },
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: funcs.LookupPrefix(""), Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
type test struct { // an individual test type test struct { // an individual test
@@ -111,10 +111,11 @@ func TestAstFunc0(t *testing.T) {
} }
{ {
graph, _ := pgraph.NewGraph("g") graph, _ := pgraph.NewGraph("g")
v1, v2 := vtex("int(42)"), vtex("var(x)") // empty graph at the moment, because they're all unused!
e1 := edge("var:x") //v1, v2 := vtex("int(42)"), vtex("var(x)")
graph.AddVertex(&v1, &v2) //e1 := edge("var:x")
graph.AddEdge(&v1, &v2, &e1) //graph.AddVertex(&v1, &v2)
//graph.AddEdge(&v1, &v2, &e1)
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "two vars", name: "two vars",
code: ` code: `
@@ -181,7 +182,7 @@ func TestAstFunc0(t *testing.T) {
graph, _ := pgraph.NewGraph("g") graph, _ := pgraph.NewGraph("g")
v1, v2, v3, v4, v5 := vtex(`str("t")`), vtex(`str("+")`), vtex("int(42)"), vtex("int(13)"), vtex(fmt.Sprintf(`call:%s(str("+"), int(42), int(13))`, operatorFuncName)) v1, v2, v3, v4, v5 := vtex(`str("t")`), vtex(`str("+")`), vtex("int(42)"), vtex("int(13)"), vtex(fmt.Sprintf(`call:%s(str("+"), int(42), int(13))`, operatorFuncName))
graph.AddVertex(&v1, &v2, &v3, &v4, &v5) graph.AddVertex(&v1, &v2, &v3, &v4, &v5)
e1, e2, e3 := edge("x"), edge("a"), edge("b") e1, e2, e3 := edge("op"), edge("a"), edge("b")
graph.AddEdge(&v2, &v5, &e1) graph.AddEdge(&v2, &v5, &e1)
graph.AddEdge(&v3, &v5, &e2) graph.AddEdge(&v3, &v5, &e2)
graph.AddEdge(&v4, &v5, &e3) graph.AddEdge(&v4, &v5, &e3)
@@ -205,12 +206,12 @@ func TestAstFunc0(t *testing.T) {
v8 := vtex(fmt.Sprintf(`call:%s(str("-"), call:%s(str("+"), int(42), int(13)), int(99))`, operatorFuncName, operatorFuncName)) v8 := vtex(fmt.Sprintf(`call:%s(str("-"), call:%s(str("+"), int(42), int(13)), int(99))`, operatorFuncName, operatorFuncName))
graph.AddVertex(&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8) graph.AddVertex(&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8)
e1, e2, e3 := edge("x"), edge("a"), edge("b") e1, e2, e3 := edge("op"), edge("a"), edge("b")
graph.AddEdge(&v3, &v7, &e1) graph.AddEdge(&v3, &v7, &e1)
graph.AddEdge(&v4, &v7, &e2) graph.AddEdge(&v4, &v7, &e2)
graph.AddEdge(&v5, &v7, &e3) graph.AddEdge(&v5, &v7, &e3)
e4, e5, e6 := edge("x"), edge("a"), edge("b") e4, e5, e6 := edge("op"), edge("a"), edge("b")
graph.AddEdge(&v2, &v8, &e4) graph.AddEdge(&v2, &v8, &e4)
graph.AddEdge(&v7, &v8, &e5) graph.AddEdge(&v7, &v8, &e5)
graph.AddEdge(&v6, &v8, &e6) graph.AddEdge(&v6, &v8, &e6)
@@ -233,7 +234,7 @@ func TestAstFunc0(t *testing.T) {
v5, v6 := vtex("var(i)"), vtex("var(x)") v5, v6 := vtex("var(i)"), vtex("var(x)")
v7, v8 := vtex(`str("+")`), vtex(fmt.Sprintf(`call:%s(str("+"), int(42), var(i))`, operatorFuncName)) v7, v8 := vtex(`str("+")`), vtex(fmt.Sprintf(`call:%s(str("+"), int(42), var(i))`, operatorFuncName))
e1, e2, e3, e4, e5 := edge("x"), edge("a"), edge("b"), edge("var:i"), edge("var:x") e1, e2, e3, e4, e5 := edge("op"), edge("a"), edge("b"), edge("var:i"), edge("var:x")
graph.AddVertex(&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8) graph.AddVertex(&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8)
graph.AddEdge(&v3, &v5, &e4) graph.AddEdge(&v3, &v5, &e4)
@@ -288,7 +289,8 @@ func TestAstFunc0(t *testing.T) {
v1, v2, v3 := vtex(`str("hello")`), vtex(`str("world")`), vtex("bool(true)") v1, v2, v3 := vtex(`str("hello")`), vtex(`str("world")`), vtex("bool(true)")
v4, v5 := vtex("var(x)"), vtex(`str("t")`) v4, v5 := vtex("var(x)"), vtex(`str("t")`)
graph.AddVertex(&v1, &v2, &v3, &v4, &v5) graph.AddVertex(&v1, &v3, &v4, &v5)
_ = v2 // v2 is not used because it's shadowed!
e1 := edge("var:x") e1 := edge("var:x")
// only one edge! (cool) // only one edge! (cool)
graph.AddEdge(&v1, &v4, &e1) graph.AddEdge(&v1, &v4, &e1)
@@ -314,7 +316,8 @@ func TestAstFunc0(t *testing.T) {
v1, v2, v3 := vtex(`str("hello")`), vtex(`str("world")`), vtex("bool(true)") v1, v2, v3 := vtex(`str("hello")`), vtex(`str("world")`), vtex("bool(true)")
v4, v5 := vtex("var(x)"), vtex(`str("t")`) v4, v5 := vtex("var(x)"), vtex(`str("t")`)
graph.AddVertex(&v1, &v2, &v3, &v4, &v5) graph.AddVertex(&v2, &v3, &v4, &v5)
_ = v1 // v1 is not used because it's shadowed!
e1 := edge("var:x") e1 := edge("var:x")
// only one edge! (cool) // only one edge! (cool)
graph.AddEdge(&v2, &v4, &e1) graph.AddEdge(&v2, &v4, &e1)
@@ -552,7 +555,7 @@ func TestAstFunc1(t *testing.T) {
"hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used "hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used
}, },
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: funcs.LookupPrefix(""), Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
type errs struct { type errs struct {
@@ -890,7 +893,22 @@ func TestAstFunc1(t *testing.T) {
return return
} }
t.Logf("test #%d: graph: %+v", index, graph) t.Logf("test #%d: graph: %s", index, graph)
for i, v := range graph.Vertices() {
t.Logf("test #%d: vertex(%d): %+v", index, i, v)
}
for v1 := range graph.Adjacency() {
for v2, e := range graph.Adjacency()[v1] {
t.Logf("test #%d: edge(%+v): %+v -> %+v", index, e, v1, v2)
}
}
t.Logf("test #%d: Running graphviz...", index)
if err := graph.ExecGraphviz("dot", "/tmp/graphviz.dot", ""); err != nil {
t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: writing graph failed: %+v", index, err)
return
}
str := strings.Trim(graph.Sprint(), "\n") // text format of graph str := strings.Trim(graph.Sprint(), "\n") // text format of graph
if expstr == magicEmpty { if expstr == magicEmpty {
expstr = "" expstr = ""
@@ -954,7 +972,7 @@ func TestAstFunc2(t *testing.T) {
"hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used "hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used
}, },
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: funcs.LookupPrefix(""), Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
type errs struct { type errs struct {
@@ -1307,6 +1325,22 @@ func TestAstFunc2(t *testing.T) {
return return
} }
t.Logf("test #%d: graph: %s", index, graph)
for i, v := range graph.Vertices() {
t.Logf("test #%d: vertex(%d): %+v", index, i, v)
}
for v1 := range graph.Adjacency() {
for v2, e := range graph.Adjacency()[v1] {
t.Logf("test #%d: edge(%+v): %+v -> %+v", index, e, v1, v2)
}
}
t.Logf("test #%d: Running graphviz...", index)
if err := graph.ExecGraphviz("dot", "/tmp/graphviz.dot", ""); err != nil {
t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: writing graph failed: %+v", index, err)
return
}
// run the function engine once to get some real output // run the function engine once to get some real output
funcs := &funcs.Engine{ funcs := &funcs.Engine{
Graph: graph, // not the same as the output graph! Graph: graph, // not the same as the output graph!
@@ -1342,6 +1376,7 @@ func TestAstFunc2(t *testing.T) {
t.Errorf("test #%d: run error with func engine: %+v", index, err) t.Errorf("test #%d: run error with func engine: %+v", index, err)
return return
} }
// TODO: cleanup before we print any test failures...
defer funcs.Close() // cleanup defer funcs.Close() // cleanup
// wait for some activity // wait for some activity

View File

@@ -0,0 +1,38 @@
Edge: bool(false) -> call:funcgen(bool(false)) # b
Edge: bool(false) -> var(b) # var:b
Edge: bool(true) -> call:funcgen(bool(true)) # b
Edge: bool(true) -> var(b) # var:b
Edge: call:fn1() -> var(out1) # var:out1
Edge: call:fn2() -> var(out2) # var:out2
Edge: call:funcgen(bool(false)) -> call:fn2() # call:fn2
Edge: call:funcgen(bool(true)) -> call:fn1() # call:fn1
Edge: func() { str("hello") } -> if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } # a
Edge: func() { str("hello") } -> if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } # a
Edge: func() { str("world") } -> if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } # b
Edge: func() { str("world") } -> if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } # b
Edge: func(b) { if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } } -> call:funcgen(bool(false)) # call:funcgen
Edge: func(b) { if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } } -> call:funcgen(bool(true)) # call:funcgen
Edge: if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } -> func(b) { if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } } # body
Edge: if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } -> func(b) { if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } } # body
Edge: str("hello") -> func() { str("hello") } # body
Edge: str("world") -> func() { str("world") } # body
Edge: var(b) -> if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } # c
Edge: var(b) -> if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } # c
Vertex: bool(false)
Vertex: bool(true)
Vertex: call:fn1()
Vertex: call:fn2()
Vertex: call:funcgen(bool(false))
Vertex: call:funcgen(bool(true))
Vertex: func() { str("hello") }
Vertex: func() { str("world") }
Vertex: func(b) { if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } }
Vertex: func(b) { if( var(b) ) { func() { str("hello") } } else { func() { str("world") } } }
Vertex: if( var(b) ) { func() { str("hello") } } else { func() { str("world") } }
Vertex: if( var(b) ) { func() { str("hello") } } else { func() { str("world") } }
Vertex: str("hello")
Vertex: str("world")
Vertex: var(b)
Vertex: var(b)
Vertex: var(out1)
Vertex: var(out2)

View File

@@ -0,0 +1,21 @@
# this can return changing functions, and could be optimized, too
func funcgen($b) {
if $b {
func() {
"hello"
}
} else {
func() {
"world"
}
}
}
$fn1 = funcgen(true)
$fn2 = funcgen(false)
$out1 = $fn1()
$out2 = $fn2()
test $out1 {}
test $out2 {}

View File

@@ -0,0 +1,17 @@
Edge: call:len(var(b)) -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # a
Edge: int(-37) -> list(int(13), int(42), int(0), int(-37)) # 3
Edge: int(0) -> list(int(13), int(42), int(0), int(-37)) # 2
Edge: int(13) -> list(int(13), int(42), int(0), int(-37)) # 0
Edge: int(42) -> list(int(13), int(42), int(0), int(-37)) # 1
Edge: list(int(13), int(42), int(0), int(-37)) -> var(b) # var:b
Edge: str("len is: %d") -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # format
Edge: var(b) -> call:len(var(b)) # 0
Vertex: call:fmt.printf(str("len is: %d"), call:len(var(b)))
Vertex: call:len(var(b))
Vertex: int(-37)
Vertex: int(0)
Vertex: int(13)
Vertex: int(42)
Vertex: list(int(13), int(42), int(0), int(-37))
Vertex: str("len is: %d")
Vertex: var(b)

View File

@@ -0,0 +1,6 @@
import "fmt"
include c1([13, 42, 0, -37,])
class c1($b) {
test fmt.printf("len is: %d", len($b)) {} # len is 4
}

View File

@@ -0,0 +1,69 @@
Edge: call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) -> var(inside) # var:inside
Edge: call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) -> var(inside) # var:inside
Edge: call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) -> var(inside) # var:inside
Edge: call:_operator(str("+"), var(some_value1), var(some_value2)) -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # a
Edge: call:_operator(str("+"), var(some_value1), var(some_value2)) -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # a
Edge: call:_operator(str("+"), var(some_value1), var(some_value2)) -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # a
Edge: int(1) -> var(num) # var:num
Edge: int(13) -> var(some_value2) # var:some_value2
Edge: int(13) -> var(some_value2) # var:some_value2
Edge: int(13) -> var(some_value2) # var:some_value2
Edge: int(2) -> var(num) # var:num
Edge: int(3) -> var(num) # var:num
Edge: int(4) -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # b
Edge: int(4) -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # b
Edge: int(4) -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # b
Edge: int(42) -> var(some_value1) # var:some_value1
Edge: int(42) -> var(some_value1) # var:some_value1
Edge: int(42) -> var(some_value1) # var:some_value1
Edge: str("+") -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # op
Edge: str("+") -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # op
Edge: str("+") -> call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4)) # op
Edge: str("+") -> call:_operator(str("+"), var(some_value1), var(some_value2)) # op
Edge: str("+") -> call:_operator(str("+"), var(some_value1), var(some_value2)) # op
Edge: str("+") -> call:_operator(str("+"), var(some_value1), var(some_value2)) # op
Edge: str("test-%d-%d") -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # format
Edge: str("test-%d-%d") -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # format
Edge: str("test-%d-%d") -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # format
Edge: var(inside) -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # b
Edge: var(inside) -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # b
Edge: var(inside) -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # b
Edge: var(num) -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # a
Edge: var(num) -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # a
Edge: var(num) -> call:fmt.printf(str("test-%d-%d"), var(num), var(inside)) # a
Edge: var(some_value1) -> call:_operator(str("+"), var(some_value1), var(some_value2)) # a
Edge: var(some_value1) -> call:_operator(str("+"), var(some_value1), var(some_value2)) # a
Edge: var(some_value1) -> call:_operator(str("+"), var(some_value1), var(some_value2)) # a
Edge: var(some_value2) -> call:_operator(str("+"), var(some_value1), var(some_value2)) # b
Edge: var(some_value2) -> call:_operator(str("+"), var(some_value1), var(some_value2)) # b
Edge: var(some_value2) -> call:_operator(str("+"), var(some_value1), var(some_value2)) # b
Vertex: call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4))
Vertex: call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4))
Vertex: call:_operator(str("+"), call:_operator(str("+"), var(some_value1), var(some_value2)), int(4))
Vertex: call:_operator(str("+"), var(some_value1), var(some_value2))
Vertex: call:_operator(str("+"), var(some_value1), var(some_value2))
Vertex: call:_operator(str("+"), var(some_value1), var(some_value2))
Vertex: call:fmt.printf(str("test-%d-%d"), var(num), var(inside))
Vertex: call:fmt.printf(str("test-%d-%d"), var(num), var(inside))
Vertex: call:fmt.printf(str("test-%d-%d"), var(num), var(inside))
Vertex: int(1)
Vertex: int(13)
Vertex: int(2)
Vertex: int(3)
Vertex: int(4)
Vertex: int(42)
Vertex: str("+")
Vertex: str("+")
Vertex: str("test-%d-%d")
Vertex: var(inside)
Vertex: var(inside)
Vertex: var(inside)
Vertex: var(num)
Vertex: var(num)
Vertex: var(num)
Vertex: var(some_value1)
Vertex: var(some_value1)
Vertex: var(some_value1)
Vertex: var(some_value2)
Vertex: var(some_value2)
Vertex: var(some_value2)

View File

@@ -0,0 +1,15 @@
import "fmt"
# this value should only be built once
$some_value1 = 42 # or something more complex like the output of a slow function...
class foo($num) {
# we should have a different `$inside` value for each use of this class
$inside = $some_value1 + $some_value2 + 4
test fmt.printf("test-%d-%d", $num, $inside) {} # some resource
}
$some_value2 = 13 # check that non-ordering works too!
# We *don't* unnecessarily copy `4` on each include, because it's static!
include foo(1)
include foo(2)
include foo(3)

View File

@@ -1,4 +1,4 @@
Edge: str("hello world") -> call:fmt.printf(str("hello world")) # a Edge: str("hello world") -> call:fmt.printf(str("hello world")) # format
Vertex: call:fmt.printf(str("hello world")) Vertex: call:fmt.printf(str("hello world"))
Vertex: str("/tmp/foo") Vertex: str("/tmp/foo")
Vertex: str("/tmp/foo") Vertex: str("/tmp/foo")

View File

@@ -0,0 +1,30 @@
Edge: call:_operator(str("+"), str("hello"), var(x)) -> func(x) { call:_operator(str("+"), str("hello"), var(x)) } # body
Edge: call:_operator(str("+"), str("hello"), var(x)) -> func(x) { call:_operator(str("+"), str("hello"), var(x)) } # body
Edge: call:prefixer(str("a")) -> var(out1) # var:out1
Edge: call:prefixer(str("b")) -> var(out2) # var:out2
Edge: func(x) { call:_operator(str("+"), str("hello"), var(x)) } -> call:prefixer(str("a")) # call:prefixer
Edge: func(x) { call:_operator(str("+"), str("hello"), var(x)) } -> call:prefixer(str("b")) # call:prefixer
Edge: str("+") -> call:_operator(str("+"), str("hello"), var(x)) # op
Edge: str("+") -> call:_operator(str("+"), str("hello"), var(x)) # op
Edge: str("a") -> call:prefixer(str("a")) # x
Edge: str("a") -> var(x) # var:x
Edge: str("b") -> call:prefixer(str("b")) # x
Edge: str("b") -> var(x) # var:x
Edge: str("hello") -> call:_operator(str("+"), str("hello"), var(x)) # a
Edge: str("hello") -> call:_operator(str("+"), str("hello"), var(x)) # a
Edge: var(x) -> call:_operator(str("+"), str("hello"), var(x)) # b
Edge: var(x) -> call:_operator(str("+"), str("hello"), var(x)) # b
Vertex: call:_operator(str("+"), str("hello"), var(x))
Vertex: call:_operator(str("+"), str("hello"), var(x))
Vertex: call:prefixer(str("a"))
Vertex: call:prefixer(str("b"))
Vertex: func(x) { call:_operator(str("+"), str("hello"), var(x)) }
Vertex: func(x) { call:_operator(str("+"), str("hello"), var(x)) }
Vertex: str("+")
Vertex: str("a")
Vertex: str("b")
Vertex: str("hello")
Vertex: var(out1)
Vertex: var(out2)
Vertex: var(x)
Vertex: var(x)

View File

@@ -0,0 +1,10 @@
# this should be a function as a value, iow a lambda
$prefixer = func($x) {
"hello" + $x # i'd only ever expect one "hello" string in the graph
}
$out1 = $prefixer("a")
$out2 = $prefixer("b")
test $out1 {} # helloa
test $out2 {} # hellob

View File

@@ -1,12 +1 @@
Edge: list(str("hey")) -> var(names) # var:names # err: err3: only recursive solutions left
Edge: str("hello") -> list(str("hello"), str("world")) # 0
Edge: str("hey") -> list(str("hey")) # 0
Edge: str("world") -> list(str("hello"), str("world")) # 1
Vertex: list()
Vertex: list(str("hello"), str("world"))
Vertex: list(str("hey"))
Vertex: str("hello")
Vertex: str("hey")
Vertex: str("name")
Vertex: str("world")
Vertex: var(names)

View File

@@ -1,13 +1,5 @@
# this is an empty list of test resources, iow test resources # this is an empty list of test resources, iow test resources
# this must pass type unification # this must pass type unification
# this can only currently pass if we allow recursive unification solving
# if we do, then the function graph is: `Vertex: list()` otherwise it's an error
test [] {} test [] {}
# single resource
test "name" {}
# single resource, defined by list variable
$names = ["hey",]
test $names {}
# multiples resources, defined by list
test ["hello", "world",] {}

View File

@@ -0,0 +1,11 @@
Edge: list(str("hey")) -> var(names) # var:names
Edge: str("hello") -> list(str("hello"), str("world")) # 0
Edge: str("hey") -> list(str("hey")) # 0
Edge: str("world") -> list(str("hello"), str("world")) # 1
Vertex: list(str("hello"), str("world"))
Vertex: list(str("hey"))
Vertex: str("hello")
Vertex: str("hey")
Vertex: str("name")
Vertex: str("world")
Vertex: var(names)

View File

@@ -0,0 +1,9 @@
# single resource
test "name" {}
# single resource, defined by list variable
$names = ["hey",]
test $names {}
# multiples resources, defined by list
test ["hello", "world",] {}

View File

@@ -1,6 +1,6 @@
Edge: str("hello: %s") -> call:fmt.printf(str("hello: %s"), var(s)) # a Edge: str("hello: %s") -> call:fmt.printf(str("hello: %s"), var(s)) # format
Edge: str("world") -> var(s) # var:s Edge: str("world") -> var(s) # var:s
Edge: var(s) -> call:fmt.printf(str("hello: %s"), var(s)) # b Edge: var(s) -> call:fmt.printf(str("hello: %s"), var(s)) # a
Vertex: call:fmt.printf(str("hello: %s"), var(s)) Vertex: call:fmt.printf(str("hello: %s"), var(s))
Vertex: str("greeting") Vertex: str("greeting")
Vertex: str("hello: %s") Vertex: str("hello: %s")

View File

@@ -1,9 +1,9 @@
Edge: call:os.is_debian() -> if(call:os.is_debian()) # c Edge: call:os.is_debian() -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # c
Edge: if(call:os.is_debian()) -> var(aaa) # var:aaa Edge: if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } -> var(aaa) # var:aaa
Edge: str("bbb") -> if(call:os.is_debian()) # a Edge: str("bbb") -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # a
Edge: str("ccc") -> if(call:os.is_debian()) # b Edge: str("ccc") -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # b
Vertex: call:os.is_debian() Vertex: call:os.is_debian()
Vertex: if(call:os.is_debian()) Vertex: if( call:os.is_debian() ) { str("bbb") } else { str("ccc") }
Vertex: str("bbb") Vertex: str("bbb")
Vertex: str("ccc") Vertex: str("ccc")
Vertex: str("hello") Vertex: str("hello")

View File

@@ -1 +1 @@
# err: err3: func `os.is_debian` does not exist in this scope # err: err2: import scope `second.mcl` failed: local import of `second.mcl` failed: could not set scope from import: func `os.is_debian` does not exist in this scope

View File

@@ -1,9 +1,9 @@
Edge: call:os.is_debian() -> if(call:os.is_debian()) # c Edge: call:os.is_debian() -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # c
Edge: if(call:os.is_debian()) -> var(aaa) # var:aaa Edge: if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } -> var(aaa) # var:aaa
Edge: str("bbb") -> if(call:os.is_debian()) # a Edge: str("bbb") -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # a
Edge: str("ccc") -> if(call:os.is_debian()) # b Edge: str("ccc") -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # b
Vertex: call:os.is_debian() Vertex: call:os.is_debian()
Vertex: if(call:os.is_debian()) Vertex: if( call:os.is_debian() ) { str("bbb") } else { str("ccc") }
Vertex: str("bbb") Vertex: str("bbb")
Vertex: str("ccc") Vertex: str("ccc")
Vertex: str("hello") Vertex: str("hello")

View File

@@ -0,0 +1,45 @@
Edge: call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) -> func(x) { call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) } # body
Edge: call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) -> func(x) { call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) } # body
Edge: call:_operator(str("+"), var(prefix), str(":")) -> call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) # a
Edge: call:_operator(str("+"), var(prefix), str(":")) -> call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) # a
Edge: call:prefixer(str("world")) -> var(out1) # var:out1
Edge: call:prefixer(str("world")) -> var(out1) # var:out1
Edge: call:prefixer(var(out1)) -> var(out2) # var:out2
Edge: func(x) { call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) } -> call:prefixer(str("world")) # call:prefixer
Edge: func(x) { call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) } -> call:prefixer(var(out1)) # call:prefixer
Edge: str("+") -> call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) # op
Edge: str("+") -> call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) # op
Edge: str("+") -> call:_operator(str("+"), var(prefix), str(":")) # op
Edge: str("+") -> call:_operator(str("+"), var(prefix), str(":")) # op
Edge: str(":") -> call:_operator(str("+"), var(prefix), str(":")) # b
Edge: str(":") -> call:_operator(str("+"), var(prefix), str(":")) # b
Edge: str("hello") -> var(prefix) # var:prefix
Edge: str("hello") -> var(prefix) # var:prefix
Edge: str("world") -> call:prefixer(str("world")) # x
Edge: str("world") -> var(x) # var:x
Edge: var(out1) -> call:prefixer(var(out1)) # x
Edge: var(out1) -> var(x) # var:x
Edge: var(prefix) -> call:_operator(str("+"), var(prefix), str(":")) # a
Edge: var(prefix) -> call:_operator(str("+"), var(prefix), str(":")) # a
Edge: var(x) -> call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) # b
Edge: var(x) -> call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) # b
Vertex: call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x))
Vertex: call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x))
Vertex: call:_operator(str("+"), var(prefix), str(":"))
Vertex: call:_operator(str("+"), var(prefix), str(":"))
Vertex: call:prefixer(str("world"))
Vertex: call:prefixer(var(out1))
Vertex: func(x) { call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) }
Vertex: func(x) { call:_operator(str("+"), call:_operator(str("+"), var(prefix), str(":")), var(x)) }
Vertex: str("+")
Vertex: str("+")
Vertex: str(":")
Vertex: str("hello")
Vertex: str("world")
Vertex: var(out1)
Vertex: var(out1)
Vertex: var(out2)
Vertex: var(prefix)
Vertex: var(prefix)
Vertex: var(x)
Vertex: var(x)

View File

@@ -0,0 +1,12 @@
$prefix = "hello"
# this should be a function as a value, iow a lambda
$prefixer = func($x) {
$prefix + ":" + $x # i'd only ever expect one ":" in the graph
}
$out1 = $prefixer("world")
$out2 = $prefixer($out1)
test $out1 {}
test $out2 {}

View File

@@ -14,7 +14,7 @@ Edge: int(5) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: i
Edge: list(str("foo:1"), str("bar:3")) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # sema Edge: list(str("foo:1"), str("bar:3")) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # sema
Edge: str("bar:3") -> list(str("foo:1"), str("bar:3")) # 1 Edge: str("bar:3") -> list(str("foo:1"), str("bar:3")) # 1
Edge: str("foo:1") -> list(str("foo:1"), str("bar:3")) # 0 Edge: str("foo:1") -> list(str("foo:1"), str("bar:3")) # 0
Edge: str("hello world") -> call:fmt.printf(str("hello world")) # a Edge: str("hello world") -> call:fmt.printf(str("hello world")) # format
Vertex: bool(false) Vertex: bool(false)
Vertex: bool(false) Vertex: bool(false)
Vertex: bool(false) Vertex: bool(false)

View File

@@ -4,37 +4,32 @@ Edge: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ an
Edge: call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) -> var(mod1.name) # var:mod1.name Edge: call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) -> var(mod1.name) # var:mod1.name
Edge: int(3) -> var(third.three) # var:third.three Edge: int(3) -> var(third.three) # var:third.three
Edge: int(42) -> call:_operator(str("+"), int(42), var(third.three)) # a Edge: int(42) -> call:_operator(str("+"), int(42), var(third.three)) # a
Edge: str("+") -> call:_operator(str("+"), int(42), var(third.three)) # x Edge: str("+") -> call:_operator(str("+"), int(42), var(third.three)) # op
Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # x Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # op
Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # x Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # op
Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example2/ and i contain: "), var(ex1)) # x Edge: str("+") -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # op
Edge: str("+") -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # x
Edge: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # a Edge: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # a
Edge: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # a Edge: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # a
Edge: str("i am github.com/purpleidea/mgmt-example2/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example2/ and i contain: "), var(ex1)) # a Edge: str("i imported local: %s") -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # format
Edge: str("i imported local: %s") -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # a Edge: str("i imported remote: %s and %s") -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # format
Edge: str("i imported remote: %s and %s") -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # a Edge: str("the answer is: %d") -> call:fmt.printf(str("the answer is: %d"), var(answer)) # format
Edge: str("the answer is: %d") -> call:fmt.printf(str("the answer is: %d"), var(answer)) # a
Edge: str("this is module mod1 which contains: ") -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # a Edge: str("this is module mod1 which contains: ") -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # a
Edge: str("this is the nested git module mod1") -> var(mod1.name) # var:mod1.name Edge: str("this is the nested git module mod1") -> var(mod1.name) # var:mod1.name
Edge: str("this is the nested git module mod1") -> var(mod1.name) # var:mod1.name Edge: str("this is the nested git module mod1") -> var(mod1.name) # var:mod1.name
Edge: str("this is the nested local module mod1") -> var(mod1.name) # var:mod1.name Edge: str("this is the nested local module mod1") -> var(mod1.name) # var:mod1.name
Edge: var(answer) -> call:fmt.printf(str("the answer is: %d"), var(answer)) # b Edge: var(answer) -> call:fmt.printf(str("the answer is: %d"), var(answer)) # a
Edge: var(ex1) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example2/ and i contain: "), var(ex1)) # b Edge: var(example1.name) -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # a
Edge: var(example1.name) -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # b
Edge: var(example1.name) -> var(ex1) # var:ex1
Edge: var(example1.name) -> var(example2.ex1) # var:example2.ex1 Edge: var(example1.name) -> var(example2.ex1) # var:example2.ex1
Edge: var(example2.ex1) -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # c Edge: var(example2.ex1) -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # b
Edge: var(h2g2.answer) -> var(answer) # var:answer Edge: var(h2g2.answer) -> var(answer) # var:answer
Edge: var(mod1.name) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # b Edge: var(mod1.name) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # b
Edge: var(mod1.name) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # b Edge: var(mod1.name) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # b
Edge: var(mod1.name) -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # b Edge: var(mod1.name) -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # b
Edge: var(mod1.name) -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # b Edge: var(mod1.name) -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # a
Edge: var(third.three) -> call:_operator(str("+"), int(42), var(third.three)) # b Edge: var(third.three) -> call:_operator(str("+"), int(42), var(third.three)) # b
Vertex: call:_operator(str("+"), int(42), var(third.three)) Vertex: call:_operator(str("+"), int(42), var(third.three))
Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name))
Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name))
Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example2/ and i contain: "), var(ex1))
Vertex: call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) Vertex: call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name))
Vertex: call:fmt.printf(str("i imported local: %s"), var(mod1.name)) Vertex: call:fmt.printf(str("i imported local: %s"), var(mod1.name))
Vertex: call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) Vertex: call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1))
@@ -45,13 +40,11 @@ Vertex: str("+")
Vertex: str("+") Vertex: str("+")
Vertex: str("+") Vertex: str("+")
Vertex: str("+") Vertex: str("+")
Vertex: str("+")
Vertex: str("hello") Vertex: str("hello")
Vertex: str("hello2") Vertex: str("hello2")
Vertex: str("hello3") Vertex: str("hello3")
Vertex: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") Vertex: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ")
Vertex: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") Vertex: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ")
Vertex: str("i am github.com/purpleidea/mgmt-example2/ and i contain: ")
Vertex: str("i imported local: %s") Vertex: str("i imported local: %s")
Vertex: str("i imported remote: %s and %s") Vertex: str("i imported remote: %s and %s")
Vertex: str("the answer is: %d") Vertex: str("the answer is: %d")
@@ -60,7 +53,6 @@ Vertex: str("this is the nested git module mod1")
Vertex: str("this is the nested git module mod1") Vertex: str("this is the nested git module mod1")
Vertex: str("this is the nested local module mod1") Vertex: str("this is the nested local module mod1")
Vertex: var(answer) Vertex: var(answer)
Vertex: var(ex1)
Vertex: var(example1.name) Vertex: var(example1.name)
Vertex: var(example1.name) Vertex: var(example1.name)
Vertex: var(example2.ex1) Vertex: var(example2.ex1)

View File

@@ -1,13 +1,13 @@
Edge: call:len(var(b)) -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # b Edge: call:len(var(b)) -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # a
Edge: call:len(var(b)) -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # b Edge: call:len(var(b)) -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # a
Edge: int(-37) -> list(int(13), int(42), int(0), int(-37)) # 3 Edge: int(-37) -> list(int(13), int(42), int(0), int(-37)) # 3
Edge: int(0) -> list(int(13), int(42), int(0), int(-37)) # 2 Edge: int(0) -> list(int(13), int(42), int(0), int(-37)) # 2
Edge: int(13) -> list(int(13), int(42), int(0), int(-37)) # 0 Edge: int(13) -> list(int(13), int(42), int(0), int(-37)) # 0
Edge: int(42) -> list(int(13), int(42), int(0), int(-37)) # 1 Edge: int(42) -> list(int(13), int(42), int(0), int(-37)) # 1
Edge: list(int(13), int(42), int(0), int(-37)) -> var(b) # var:b Edge: list(int(13), int(42), int(0), int(-37)) -> var(b) # var:b
Edge: str("hello") -> var(b) # var:b Edge: str("hello") -> var(b) # var:b
Edge: str("len is: %d") -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # a Edge: str("len is: %d") -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # format
Edge: str("len is: %d") -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # a Edge: str("len is: %d") -> call:fmt.printf(str("len is: %d"), call:len(var(b))) # format
Edge: str("t1") -> var(a) # var:a Edge: str("t1") -> var(a) # var:a
Edge: str("t2") -> var(a) # var:a Edge: str("t2") -> var(a) # var:a
Edge: var(b) -> call:len(var(b)) # 0 Edge: var(b) -> call:len(var(b)) # 0
@@ -23,7 +23,6 @@ Vertex: int(42)
Vertex: list(int(13), int(42), int(0), int(-37)) Vertex: list(int(13), int(42), int(0), int(-37))
Vertex: str("hello") Vertex: str("hello")
Vertex: str("len is: %d") Vertex: str("len is: %d")
Vertex: str("len is: %d")
Vertex: str("t1") Vertex: str("t1")
Vertex: str("t2") Vertex: str("t2")
Vertex: var(a) Vertex: var(a)

View File

@@ -1 +1 @@
# err: err2: recursive class `c1` found # err: err2: recursive reference while setting scope: not a dag

View File

@@ -1 +1 @@
# err: err2: class `c1` does not exist in this scope # err: err2: recursive reference while setting scope: not a dag

View File

@@ -4,37 +4,32 @@ Edge: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ an
Edge: call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) -> var(mod1.name) # var:mod1.name Edge: call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) -> var(mod1.name) # var:mod1.name
Edge: int(3) -> var(third.three) # var:third.three Edge: int(3) -> var(third.three) # var:third.three
Edge: int(42) -> call:_operator(str("+"), int(42), var(third.three)) # a Edge: int(42) -> call:_operator(str("+"), int(42), var(third.three)) # a
Edge: str("+") -> call:_operator(str("+"), int(42), var(third.three)) # x Edge: str("+") -> call:_operator(str("+"), int(42), var(third.three)) # op
Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # x Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # op
Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # x Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # op
Edge: str("+") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example2/ and i contain: "), var(ex1)) # x Edge: str("+") -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # op
Edge: str("+") -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # x
Edge: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # a Edge: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # a
Edge: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # a Edge: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # a
Edge: str("i am github.com/purpleidea/mgmt-example2/ and i contain: ") -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example2/ and i contain: "), var(ex1)) # a Edge: str("i imported local: %s") -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # format
Edge: str("i imported local: %s") -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # a Edge: str("i imported remote: %s and %s") -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # format
Edge: str("i imported remote: %s and %s") -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # a Edge: str("the answer is: %d") -> call:fmt.printf(str("the answer is: %d"), var(answer)) # format
Edge: str("the answer is: %d") -> call:fmt.printf(str("the answer is: %d"), var(answer)) # a
Edge: str("this is module mod1 which contains: ") -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # a Edge: str("this is module mod1 which contains: ") -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # a
Edge: str("this is the nested git module mod1") -> var(mod1.name) # var:mod1.name Edge: str("this is the nested git module mod1") -> var(mod1.name) # var:mod1.name
Edge: str("this is the nested git module mod1") -> var(mod1.name) # var:mod1.name Edge: str("this is the nested git module mod1") -> var(mod1.name) # var:mod1.name
Edge: str("this is the nested local module mod1") -> var(mod1.name) # var:mod1.name Edge: str("this is the nested local module mod1") -> var(mod1.name) # var:mod1.name
Edge: var(answer) -> call:fmt.printf(str("the answer is: %d"), var(answer)) # b Edge: var(answer) -> call:fmt.printf(str("the answer is: %d"), var(answer)) # a
Edge: var(ex1) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example2/ and i contain: "), var(ex1)) # b Edge: var(example1.name) -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # a
Edge: var(example1.name) -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # b
Edge: var(example1.name) -> var(ex1) # var:ex1
Edge: var(example1.name) -> var(example2.ex1) # var:example2.ex1 Edge: var(example1.name) -> var(example2.ex1) # var:example2.ex1
Edge: var(example2.ex1) -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # c Edge: var(example2.ex1) -> call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) # b
Edge: var(h2g2.answer) -> var(answer) # var:answer Edge: var(h2g2.answer) -> var(answer) # var:answer
Edge: var(mod1.name) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # b Edge: var(mod1.name) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # b
Edge: var(mod1.name) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # b Edge: var(mod1.name) -> call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) # b
Edge: var(mod1.name) -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # b Edge: var(mod1.name) -> call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) # b
Edge: var(mod1.name) -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # b Edge: var(mod1.name) -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # a
Edge: var(third.three) -> call:_operator(str("+"), int(42), var(third.three)) # b Edge: var(third.three) -> call:_operator(str("+"), int(42), var(third.three)) # b
Vertex: call:_operator(str("+"), int(42), var(third.three)) Vertex: call:_operator(str("+"), int(42), var(third.three))
Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name))
Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name)) Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example1/ and i contain: "), var(mod1.name))
Vertex: call:_operator(str("+"), str("i am github.com/purpleidea/mgmt-example2/ and i contain: "), var(ex1))
Vertex: call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name)) Vertex: call:_operator(str("+"), str("this is module mod1 which contains: "), var(mod1.name))
Vertex: call:fmt.printf(str("i imported local: %s"), var(mod1.name)) Vertex: call:fmt.printf(str("i imported local: %s"), var(mod1.name))
Vertex: call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1)) Vertex: call:fmt.printf(str("i imported remote: %s and %s"), var(example1.name), var(example2.ex1))
@@ -45,13 +40,11 @@ Vertex: str("+")
Vertex: str("+") Vertex: str("+")
Vertex: str("+") Vertex: str("+")
Vertex: str("+") Vertex: str("+")
Vertex: str("+")
Vertex: str("hello") Vertex: str("hello")
Vertex: str("hello2") Vertex: str("hello2")
Vertex: str("hello3") Vertex: str("hello3")
Vertex: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") Vertex: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ")
Vertex: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") Vertex: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ")
Vertex: str("i am github.com/purpleidea/mgmt-example2/ and i contain: ")
Vertex: str("i imported local: %s") Vertex: str("i imported local: %s")
Vertex: str("i imported remote: %s and %s") Vertex: str("i imported remote: %s and %s")
Vertex: str("the answer is: %d") Vertex: str("the answer is: %d")
@@ -60,7 +53,6 @@ Vertex: str("this is the nested git module mod1")
Vertex: str("this is the nested git module mod1") Vertex: str("this is the nested git module mod1")
Vertex: str("this is the nested local module mod1") Vertex: str("this is the nested local module mod1")
Vertex: var(answer) Vertex: var(answer)
Vertex: var(ex1)
Vertex: var(example1.name) Vertex: var(example1.name)
Vertex: var(example1.name) Vertex: var(example1.name)
Vertex: var(example2.ex1) Vertex: var(example2.ex1)

View File

@@ -0,0 +1,11 @@
Edge: call:fn() -> var(out) # var:out
Edge: call:funcgen() -> call:fn() # call:fn
Edge: func() { func() { str("hello") } } -> call:funcgen() # call:funcgen
Edge: func() { str("hello") } -> func() { func() { str("hello") } } # body
Edge: str("hello") -> func() { str("hello") } # body
Vertex: call:fn()
Vertex: call:funcgen()
Vertex: func() { func() { str("hello") } }
Vertex: func() { str("hello") }
Vertex: str("hello")
Vertex: var(out)

View File

@@ -0,0 +1,11 @@
# simple function definition containing function to be returned
func funcgen() {
func() {
"hello"
}
}
$fn = funcgen()
$out = $fn()
test $out {}

View File

@@ -0,0 +1,11 @@
Edge: call:fn() -> var(out) # var:out
Edge: call:funcgen() -> call:fn() # call:fn
Edge: func() { func() { str("hello") } } -> call:funcgen() # call:funcgen
Edge: func() { str("hello") } -> func() { func() { str("hello") } } # body
Edge: str("hello") -> func() { str("hello") } # body
Vertex: call:fn()
Vertex: call:funcgen()
Vertex: func() { func() { str("hello") } }
Vertex: func() { str("hello") }
Vertex: str("hello")
Vertex: var(out)

View File

@@ -0,0 +1,10 @@
$funcgen = func() {
func() {
"hello"
}
}
$fn = $funcgen()
$out = $fn()
test $out {}

View File

@@ -0,0 +1,4 @@
Edge: str("hello") -> var(x) # var:x
Vertex: bool(true)
Vertex: str("hello")
Vertex: var(x)

View File

@@ -0,0 +1,6 @@
# this should be okay, because var is shadowed
$x = "hello"
if true {
$x = "world" # shadowed
}
test $x {}

View File

@@ -0,0 +1,4 @@
Edge: str("world") -> var(x) # var:x
Vertex: bool(true)
Vertex: str("world")
Vertex: var(x)

View File

@@ -0,0 +1,6 @@
# this should be okay, because var is shadowed
$x = "hello"
if true {
$x = "world" # shadowed
test $x {}
}

View File

@@ -0,0 +1,7 @@
Edge: call:answer() -> var(out1) # var:out1
Edge: func() { str("the answer is 42") } -> call:answer() # call:answer
Edge: str("the answer is 42") -> func() { str("the answer is 42") } # body
Vertex: call:answer()
Vertex: func() { str("the answer is 42") }
Vertex: str("the answer is 42")
Vertex: var(out1)

View File

@@ -0,0 +1,7 @@
func answer() {
"the answer is 42"
}
$out1 = answer()
test $out1 {}

View File

@@ -0,0 +1,16 @@
Edge: call:answer() -> var(out1) # var:out1
Edge: call:answer() -> var(out2) # var:out2
Edge: func() { str("the answer is 42") } -> call:answer() # call:answer
Edge: func() { str("the answer is 42") } -> call:answer() # call:answer
Edge: str("+") -> call:_operator(str("+"), var(out1), var(out2)) # op
Edge: str("the answer is 42") -> func() { str("the answer is 42") } # body
Edge: var(out1) -> call:_operator(str("+"), var(out1), var(out2)) # a
Edge: var(out2) -> call:_operator(str("+"), var(out1), var(out2)) # b
Vertex: call:_operator(str("+"), var(out1), var(out2))
Vertex: call:answer()
Vertex: call:answer()
Vertex: func() { str("the answer is 42") }
Vertex: str("+")
Vertex: str("the answer is 42")
Vertex: var(out1)
Vertex: var(out2)

View File

@@ -0,0 +1,8 @@
func answer() {
"the answer is 42"
}
$out1 = answer()
$out2 = answer()
test $out1 + $out2 {}

View File

@@ -0,0 +1,7 @@
Edge: call:answer() -> var(out) # var:out
Edge: func() { str("the answer is 42") } -> call:answer() # call:answer
Edge: str("the answer is 42") -> func() { str("the answer is 42") } # body
Vertex: call:answer()
Vertex: func() { str("the answer is 42") }
Vertex: str("the answer is 42")
Vertex: var(out)

View File

@@ -0,0 +1,10 @@
import "fmt"
# this should be a function as a value, iow a lambda
$answer = func() {
"the answer is 42"
}
$out = $answer()
test $out {}

View File

@@ -0,0 +1,16 @@
Edge: call:answer() -> var(out1) # var:out1
Edge: call:answer() -> var(out2) # var:out2
Edge: func() { str("the answer is 42") } -> call:answer() # call:answer
Edge: func() { str("the answer is 42") } -> call:answer() # call:answer
Edge: str("+") -> call:_operator(str("+"), var(out1), var(out2)) # op
Edge: str("the answer is 42") -> func() { str("the answer is 42") } # body
Edge: var(out1) -> call:_operator(str("+"), var(out1), var(out2)) # a
Edge: var(out2) -> call:_operator(str("+"), var(out1), var(out2)) # b
Vertex: call:_operator(str("+"), var(out1), var(out2))
Vertex: call:answer()
Vertex: call:answer()
Vertex: func() { str("the answer is 42") }
Vertex: str("+")
Vertex: str("the answer is 42")
Vertex: var(out1)
Vertex: var(out2)

View File

@@ -0,0 +1,11 @@
import "fmt"
# this should be a function as a value, iow a lambda
$answer = func() {
"the answer is 42"
}
$out1 = $answer()
$out2 = $answer()
test $out1 + $out2 {}

View File

@@ -6,10 +6,10 @@ Edge: call:maplookup(var(exchanged), var(hostname), str("default")) -> var(state
Edge: call:maplookup(var(exchanged), var(hostname), str("default")) -> var(state) # var:state Edge: call:maplookup(var(exchanged), var(hostname), str("default")) -> var(state) # var:state
Edge: call:world.kvlookup(var(ns)) -> var(exchanged) # var:exchanged Edge: call:world.kvlookup(var(ns)) -> var(exchanged) # var:exchanged
Edge: str("") -> var(hostname) # var:hostname Edge: str("") -> var(hostname) # var:hostname
Edge: str("==") -> call:_operator(str("=="), var(state), str("default")) # x Edge: str("==") -> call:_operator(str("=="), var(state), str("default")) # op
Edge: str("==") -> call:_operator(str("=="), var(state), str("one")) # x Edge: str("==") -> call:_operator(str("=="), var(state), str("one")) # op
Edge: str("==") -> call:_operator(str("=="), var(state), str("three")) # x Edge: str("==") -> call:_operator(str("=="), var(state), str("three")) # op
Edge: str("==") -> call:_operator(str("=="), var(state), str("two")) # x Edge: str("==") -> call:_operator(str("=="), var(state), str("two")) # op
Edge: str("default") -> call:_operator(str("=="), var(state), str("default")) # b Edge: str("default") -> call:_operator(str("=="), var(state), str("default")) # b
Edge: str("default") -> call:maplookup(var(exchanged), var(hostname), str("default")) # default Edge: str("default") -> call:maplookup(var(exchanged), var(hostname), str("default")) # default
Edge: str("estate") -> var(ns) # var:ns Edge: str("estate") -> var(ns) # var:ns
@@ -25,7 +25,7 @@ Edge: str("estate") -> var(ns) # var:ns
Edge: str("one") -> call:_operator(str("=="), var(state), str("one")) # b Edge: str("one") -> call:_operator(str("=="), var(state), str("one")) # b
Edge: str("three") -> call:_operator(str("=="), var(state), str("three")) # b Edge: str("three") -> call:_operator(str("=="), var(state), str("three")) # b
Edge: str("two") -> call:_operator(str("=="), var(state), str("two")) # b Edge: str("two") -> call:_operator(str("=="), var(state), str("two")) # b
Edge: str("||") -> call:_operator(str("||"), call:_operator(str("=="), var(state), str("one")), call:_operator(str("=="), var(state), str("default"))) # x Edge: str("||") -> call:_operator(str("||"), call:_operator(str("=="), var(state), str("one")), call:_operator(str("=="), var(state), str("default"))) # op
Edge: var(exchanged) -> call:maplookup(var(exchanged), var(hostname), str("default")) # map Edge: var(exchanged) -> call:maplookup(var(exchanged), var(hostname), str("default")) # map
Edge: var(hostname) -> call:maplookup(var(exchanged), var(hostname), str("default")) # key Edge: var(hostname) -> call:maplookup(var(exchanged), var(hostname), str("default")) # key
Edge: var(ns) -> call:world.kvlookup(var(ns)) # namespace Edge: var(ns) -> call:world.kvlookup(var(ns)) # namespace

View File

@@ -0,0 +1,12 @@
Edge: func() { str("hello world") } -> call:fn() # call:fn
Edge: func() { str("hello world") } -> call:fn() # call:fn
Edge: func() { str("hello world") } -> call:fn() # call:fn
Edge: str("hello world") -> func() { str("hello world") } # body
Vertex: call:fn()
Vertex: call:fn()
Vertex: call:fn()
Vertex: func() { str("hello world") }
Vertex: str("greeting1")
Vertex: str("greeting2")
Vertex: str("greeting3")
Vertex: str("hello world")

View File

@@ -0,0 +1,16 @@
import "fmt"
# we should only see one copy of $fn
$fn = func() {
"hello world"
}
test "greeting1" {
anotherstr => $fn(),
}
test "greeting2" {
anotherstr => $fn(),
}
test "greeting3" {
anotherstr => $fn(),
}

View File

@@ -0,0 +1,56 @@
Edge: call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) -> func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) } # body
Edge: call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) -> func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) } # body
Edge: call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) -> func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) } # body
Edge: call:_operator(str("+"), var(s1), str(" ")) -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # a
Edge: call:_operator(str("+"), var(s1), str(" ")) -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # a
Edge: call:_operator(str("+"), var(s1), str(" ")) -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # a
Edge: func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) } -> call:fn() # call:fn
Edge: func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) } -> call:fn() # call:fn
Edge: func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) } -> call:fn() # call:fn
Edge: str(" ") -> call:_operator(str("+"), var(s1), str(" ")) # b
Edge: str(" ") -> call:_operator(str("+"), var(s1), str(" ")) # b
Edge: str(" ") -> call:_operator(str("+"), var(s1), str(" ")) # b
Edge: str("+") -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # op
Edge: str("+") -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # op
Edge: str("+") -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # op
Edge: str("+") -> call:_operator(str("+"), var(s1), str(" ")) # op
Edge: str("+") -> call:_operator(str("+"), var(s1), str(" ")) # op
Edge: str("+") -> call:_operator(str("+"), var(s1), str(" ")) # op
Edge: str("hello") -> var(s1) # var:s1
Edge: str("hello") -> var(s1) # var:s1
Edge: str("hello") -> var(s1) # var:s1
Edge: str("world") -> var(s2) # var:s2
Edge: str("world") -> var(s2) # var:s2
Edge: str("world") -> var(s2) # var:s2
Edge: var(s1) -> call:_operator(str("+"), var(s1), str(" ")) # a
Edge: var(s1) -> call:_operator(str("+"), var(s1), str(" ")) # a
Edge: var(s1) -> call:_operator(str("+"), var(s1), str(" ")) # a
Edge: var(s2) -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # b
Edge: var(s2) -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # b
Edge: var(s2) -> call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) # b
Vertex: call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2))
Vertex: call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2))
Vertex: call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2))
Vertex: call:_operator(str("+"), var(s1), str(" "))
Vertex: call:_operator(str("+"), var(s1), str(" "))
Vertex: call:_operator(str("+"), var(s1), str(" "))
Vertex: call:fn()
Vertex: call:fn()
Vertex: call:fn()
Vertex: func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) }
Vertex: func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) }
Vertex: func() { call:_operator(str("+"), call:_operator(str("+"), var(s1), str(" ")), var(s2)) }
Vertex: str(" ")
Vertex: str("+")
Vertex: str("+")
Vertex: str("greeting1")
Vertex: str("greeting2")
Vertex: str("greeting3")
Vertex: str("hello")
Vertex: str("world")
Vertex: var(s1)
Vertex: var(s1)
Vertex: var(s1)
Vertex: var(s2)
Vertex: var(s2)
Vertex: var(s2)

View File

@@ -0,0 +1,18 @@
import "fmt"
# we should only see one copy of $s1, $s2 and $fn
$s1 = "hello"
$fn = func() {
$s1 + " " + $s2
}
$s2 = "world"
test "greeting1" {
anotherstr => $fn(),
}
test "greeting2" {
anotherstr => $fn(),
}
test "greeting3" {
anotherstr => $fn(),
}

View File

@@ -0,0 +1 @@
Vertex: test[worldwide]

View File

@@ -0,0 +1,13 @@
$some_bool = false
$fn = if $some_bool {
func($b) {
"hello" + $b
}
} else {
func($bb) {
"world" + $bb
}
}
$out = $fn("wide")
test $out {}

View File

@@ -0,0 +1 @@
Vertex: test[world]

View File

@@ -0,0 +1,13 @@
$some_bool = false
$fn = if $some_bool {
func($b) {
"hello"
}
} else {
func($bb) {
"world"
}
}
$out = $fn(false)
test $out {}

View File

@@ -0,0 +1 @@
Vertex: test[hello]

View File

@@ -0,0 +1,13 @@
func funcgen() {
func() {
func() {
"hello"
}
}
}
$fn1 = funcgen()
$fn2 = $fn1()
$out = $fn2()
test $out {}

View File

@@ -0,0 +1 @@
Vertex: test[hello]

View File

@@ -0,0 +1,17 @@
# simple function definition containing function to be returned
$funcgen = func() {
func() {
func() {
func() {
"hello"
}
}
}
}
$fn1 = $funcgen()
$fn2 = $fn1()
$fn3 = $fn2()
$out = $fn3()
test $out {}

View File

@@ -0,0 +1,4 @@
Vertex: test[hey]
Vertex: test[there]
Vertex: test[wow: hello]
Vertex: test[wow: world]

View File

@@ -0,0 +1,38 @@
$funcgen = func() {
func($a) {
if $a {
func($b) {
if $b == "hello" {
func() {
"hey"
}
} else {
func() {
$b
}
}
}
} else {
func($b) {
func() {
"wow: " + $b
}
}
}
}
}
$fn = $funcgen()
$fn1 = $fn(true)
$fn2 = $fn(false)
$out1 = $fn1("hello")
$out2 = $fn1("there")
$out3 = $fn2("hello")
$out4 = $fn2("world")
test $out1() {} # hey
test $out2() {} # there
test $out3() {} # wow: hello
test $out4() {} # wow: world

View File

@@ -0,0 +1,2 @@
Vertex: test[hello]
Vertex: test[world]

View File

@@ -0,0 +1,21 @@
# this can return changing functions, and could be optimized, too
func funcgen($b) {
if $b {
func() {
"hello"
}
} else {
func() {
"world"
}
}
}
$fn1 = funcgen(true)
$fn2 = funcgen(false)
$out1 = $fn1()
$out2 = $fn2()
test $out1 {}
test $out2 {}

View File

@@ -0,0 +1,2 @@
Vertex: test[hello]
Vertex: test[world]

View File

@@ -0,0 +1,21 @@
# this can return changing functions, and could be optimized, too
$funcgen = func($b) {
if $b {
func() {
"hello"
}
} else {
func() {
"world"
}
}
}
$fn1 = $funcgen(true)
$fn2 = $funcgen(false)
$out1 = $fn1()
$out2 = $fn2()
test $out1 {}
test $out2 {}

View File

@@ -0,0 +1,4 @@
Vertex: test[true-true]
Vertex: test[true-false]
Vertex: test[false-true]
Vertex: test[false-false]

View File

@@ -0,0 +1,41 @@
# this can return changing functions, and could be optimized, too
$funcgen = func($a) {
if $a {
func($b) {
if $b == "hello" {
func() {
"true-true"
}
} else {
func() {
"true-false"
}
}
}
} else {
func($b) {
if $b == "hello" {
func() {
"false-true"
}
} else {
func() {
"false-false"
}
}
}
}
}
$fn1 = $funcgen(true)
$fn2 = $funcgen(false)
$out1 = $fn1("hello")
$out2 = $fn1("world")
$out3 = $fn2("hello")
$out4 = $fn2("world")
test $out1() {}
test $out2() {}
test $out3() {}
test $out4() {}

View File

@@ -0,0 +1,3 @@
Vertex: test[hello purpleidea]
Vertex: test[hello user]
Vertex: test[who is there?]

View File

@@ -0,0 +1,30 @@
$funcgen = func() {
func($b) {
"hello" + " " + "world"
}
}
$some_bool = false
$fn = if $some_bool {
$funcgen()
} else {
func($bb) {
if $bb == "james" {
"hello purpleidea"
} else {
if $bb == "" {
"who is there?"
} else {
"hello " + $bb
}
}
}
}
$out1 = $fn("user")
$out2 = $fn("james")
$out3 = $fn("")
test $out1 {}
test $out2 {}
test $out3 {}

View File

@@ -0,0 +1,2 @@
Vertex: test[so true]
Vertex: test[so false]

View File

@@ -0,0 +1,27 @@
# out of order
$out1 = $fn(true)
$some_bool = false
$fn = if $some_bool {
$funcgen()
} else {
func($bb) {
if $bb {
"so true"
} else {
"so false"
}
}
}
$out2 = $fn(false)
$funcgen = func() {
func($b) {
if $b {
"hello"
} else {
"world"
}
}
}
test $out1 {}
test $out2 {}

View File

@@ -0,0 +1,2 @@
Vertex: test[so true]
Vertex: test[so false]

View File

@@ -0,0 +1,27 @@
$funcgen = func() {
func($b) {
if $b {
"hello"
} else {
"world"
}
}
}
$some_bool = false
$fn = if $some_bool {
$funcgen()
} else {
func($bb) {
if $bb {
"so true"
} else {
"so false"
}
}
}
$out1 = $fn(true)
$out2 = $fn(false)
test $out1 {}
test $out2 {}

View File

@@ -0,0 +1,2 @@
Vertex: test[hello:a]
Vertex: test[hello:b]

Some files were not shown because too many files have changed in this diff Show More