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:
5
Makefile
5
Makefile
@@ -21,6 +21,7 @@ SHELL = /usr/bin/env bash
|
||||
|
||||
# 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/*')
|
||||
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))
|
||||
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
|
||||
cp -a $< $@
|
||||
|
||||
$(PROGRAM).static: $(GO_FILES)
|
||||
$(PROGRAM).static: $(GO_FILES) $(MCL_FILES)
|
||||
@echo "Building: $(PROGRAM).static, version: $(SVERSION)..."
|
||||
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);
|
||||
@@ -146,7 +147,7 @@ build-debug: $(PROGRAM)
|
||||
# extract os and arch from target pattern
|
||||
GOOS=$(firstword $(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)..."
|
||||
@# reassigning GOOS and GOARCH to make build command copy/pastable
|
||||
@# go 1.10+ requires specifying the package for ldflags
|
||||
|
||||
@@ -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 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
|
||||
|
||||
Each expression must have a known type. The unpleasant option is to force the
|
||||
|
||||
@@ -33,6 +33,7 @@ build: $(GENERATED)
|
||||
|
||||
# add more input files as dependencies at the end here...
|
||||
$(GENERATED): $(MCL_FILES)
|
||||
@echo "Generating: native mcl..."
|
||||
@# go-bindata --pkg bindata -o <OUTPUT> <INPUT>
|
||||
go-bindata --pkg bindata -o ./$@ $^
|
||||
@# gofmt the output file
|
||||
|
||||
@@ -48,6 +48,15 @@ type ContainsPolyFunc 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
|
||||
// this static polymorphic function. It relies on type and value hints to limit
|
||||
// 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
|
||||
// will return correct data.
|
||||
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{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Sig: typ, // func kind
|
||||
Sig: sig, // func kind
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
21
lang/funcs/core/example/nativeanswer.mcl
Normal file
21
lang/funcs/core/example/nativeanswer.mcl
Normal 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
|
||||
}
|
||||
38
lang/funcs/core/example/plus_func.go
Normal file
38
lang/funcs/core/example/plus_func.go
Normal 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
|
||||
}
|
||||
31
lang/funcs/core/example/test1.mcl
Normal file
31
lang/funcs/core/example/test1.mcl
Normal 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",
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,15 @@ type VUMeterFunc 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
|
||||
// normal functions that users can use directly.
|
||||
func (obj *VUMeterFunc) Validate() error {
|
||||
|
||||
@@ -33,9 +33,7 @@ func init() {
|
||||
}
|
||||
|
||||
const (
|
||||
// XXX: does this need to be `a` ? -- for now yes, fix this compiler bug
|
||||
//formatArgName = "format" // name of the first arg
|
||||
formatArgName = "a" // name of the first arg
|
||||
formatArgName = "format" // name of the first arg
|
||||
)
|
||||
|
||||
// PrintfFunc is a static polymorphic function that compiles a format string and
|
||||
@@ -58,6 +56,14 @@ type PrintfFunc 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
|
||||
// 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
|
||||
@@ -108,9 +114,9 @@ func (obj *PrintfFunc) Polymorphisms(partialType *types.Type, partialValues []ty
|
||||
typ.Ord = append(typ.Ord, formatArgName)
|
||||
|
||||
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 {
|
||||
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!
|
||||
|
||||
@@ -49,6 +49,15 @@ type ReadFileFunc 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
|
||||
// normal functions that users can use directly.
|
||||
func (obj *ReadFileFunc) Validate() error {
|
||||
|
||||
@@ -52,6 +52,15 @@ type Random1Func 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
|
||||
// normal functions that users can use directly.
|
||||
func (obj *Random1Func) Validate() error {
|
||||
|
||||
@@ -41,8 +41,14 @@ func init() {
|
||||
funcs.Register("template", func() interfaces.Func { return &TemplateFunc{} })
|
||||
}
|
||||
|
||||
// TemplateName is the name of our template as required by the template library.
|
||||
const TemplateName = "template"
|
||||
const (
|
||||
// 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
|
||||
// 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{}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
@@ -72,7 +87,8 @@ type TemplateFunc struct {
|
||||
// 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) {
|
||||
// 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 {
|
||||
return variant, nil
|
||||
@@ -150,10 +166,15 @@ func (obj *TemplateFunc) Validate() error {
|
||||
|
||||
// Info returns some static info about itself.
|
||||
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{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(template str, vars %s) str", obj.Type.String())),
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
@@ -293,8 +314,8 @@ func (obj *TemplateFunc) Stream() error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
tmpl := input.Struct()["template"].Str()
|
||||
vars := input.Struct()["vars"]
|
||||
tmpl := input.Struct()[argNameTemplate].Str()
|
||||
vars := input.Struct()[argNameVars]
|
||||
|
||||
result, err := obj.run(tmpl, vars)
|
||||
if err != nil {
|
||||
|
||||
@@ -46,6 +46,15 @@ type ExchangeFunc 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
|
||||
// normal functions that users can use directly.
|
||||
func (obj *ExchangeFunc) Validate() error {
|
||||
|
||||
@@ -46,6 +46,15 @@ type KVLookupFunc 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
|
||||
// normal functions that users can use directly.
|
||||
func (obj *KVLookupFunc) Validate() error {
|
||||
|
||||
@@ -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
|
||||
// this static polymorphic function. It relies on type and value hints to limit
|
||||
// the number of returned possibilities.
|
||||
|
||||
@@ -277,6 +277,10 @@ Loop:
|
||||
if !ok {
|
||||
break Loop
|
||||
}
|
||||
if err == nil {
|
||||
// programming error
|
||||
err = fmt.Errorf("error was missing")
|
||||
}
|
||||
e := errwrap.Wrapf(err, "problem streaming func")
|
||||
reterr = errwrap.Append(reterr, e)
|
||||
}
|
||||
@@ -287,5 +291,13 @@ Loop:
|
||||
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
|
||||
}
|
||||
|
||||
@@ -57,6 +57,15 @@ type HistoryFunc 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
|
||||
// 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
|
||||
@@ -149,10 +158,15 @@ func (obj *HistoryFunc) Validate() error {
|
||||
|
||||
// Info returns some static info about itself.
|
||||
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{
|
||||
Pure: false, // definitely 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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ const (
|
||||
// starts with an underscore so that it cannot be used from the lexer.
|
||||
// XXX: change to _maplookup and add syntax in the lexer/parser
|
||||
MapLookupFuncName = "maplookup"
|
||||
|
||||
argNameMap = "map"
|
||||
argNameKey = "key"
|
||||
argNameDef = "default"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -48,6 +52,15 @@ type MapLookupPolyFunc 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
|
||||
// this static polymorphic function. It relies on type and value hints to limit
|
||||
// the number of returned possibilities.
|
||||
@@ -116,20 +129,20 @@ func (obj *MapLookupPolyFunc) Polymorphisms(partialType *types.Type, partialValu
|
||||
typFunc := &types.Type{
|
||||
Kind: types.KindFunc, // function type
|
||||
Map: make(map[string]*types.Type),
|
||||
Ord: []string{"map", "key", "default"},
|
||||
Ord: []string{argNameMap, argNameKey, argNameDef},
|
||||
Out: nil,
|
||||
}
|
||||
typFunc.Map["map"] = typ
|
||||
typFunc.Map["key"] = typ.Key
|
||||
typFunc.Map["default"] = typ.Val
|
||||
typFunc.Map[argNameMap] = typ
|
||||
typFunc.Map[argNameKey] = typ.Key
|
||||
typFunc.Map[argNameDef] = typ.Val
|
||||
typFunc.Out = typ.Val
|
||||
|
||||
// TODO: don't include partial internal func map's for now, allow in future?
|
||||
if typ.Key == nil || typ.Val == nil {
|
||||
typFunc.Map = make(map[string]*types.Type) // erase partial
|
||||
typFunc.Map["map"] = types.TypeVariant
|
||||
typFunc.Map["key"] = types.TypeVariant
|
||||
typFunc.Map["default"] = types.TypeVariant
|
||||
typFunc.Map[argNameMap] = types.TypeVariant
|
||||
typFunc.Map[argNameKey] = types.TypeVariant
|
||||
typFunc.Map[argNameDef] = types.TypeVariant
|
||||
}
|
||||
if typ.Val == nil {
|
||||
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
|
||||
// will return correct data.
|
||||
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{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
@@ -245,9 +264,9 @@ func (obj *MapLookupPolyFunc) Stream() error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
m := (input.Struct()["map"]).(*types.MapValue)
|
||||
key := input.Struct()["key"]
|
||||
def := input.Struct()["default"]
|
||||
m := (input.Struct()[argNameMap]).(*types.MapValue)
|
||||
key := input.Struct()[argNameKey]
|
||||
def := input.Struct()[argNameDef]
|
||||
|
||||
var result types.Value
|
||||
val, exists := m.Lookup(key)
|
||||
|
||||
@@ -33,8 +33,9 @@ const (
|
||||
// starts with an underscore so that it cannot be used from the lexer.
|
||||
OperatorFuncName = "_operator"
|
||||
|
||||
// operatorArgName is the edge and arg name used for the function's operator.
|
||||
operatorArgName = "x" // something short and arbitrary
|
||||
// operatorArgName is the edge and arg name used for the function's
|
||||
// operator.
|
||||
operatorArgName = "op" // something short and arbitrary
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
// yes this limits the arg max to 24 (`x`) including operator
|
||||
// if the operator is `x`...
|
||||
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))
|
||||
}
|
||||
@@ -387,8 +389,7 @@ func LookupOperator(operator string, size int) ([]*types.Type, error) {
|
||||
}
|
||||
|
||||
for _, fn := range fns {
|
||||
typ := addOperatorArg(fn.T) // add in the `operatorArgName` arg
|
||||
typ = unlabelOperatorArgNames(typ) // label in standard a..b..c
|
||||
typ := addOperatorArg(fn.T) // add in the `operatorArgName` arg
|
||||
|
||||
if size >= 0 && len(typ.Ord) != size {
|
||||
continue
|
||||
@@ -414,7 +415,7 @@ type OperatorPolyFunc struct {
|
||||
|
||||
// argNames returns the maximum list of possible argNames. This can be truncated
|
||||
// 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
|
||||
// wouldn't ever have to remember to update this list...
|
||||
max := 0
|
||||
@@ -434,12 +435,12 @@ func (obj *OperatorPolyFunc) argNames() []string {
|
||||
for i := 0; i < max; i++ {
|
||||
s := util.NumToAlpha(i)
|
||||
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)
|
||||
}
|
||||
|
||||
return args
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// this static polymorphic function. It relies on type and value hints to limit
|
||||
// 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,
|
||||
// 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
|
||||
// runs. It typically re-labels the input arg names to match what is actually
|
||||
// used.
|
||||
// runs.
|
||||
func (obj *OperatorPolyFunc) Build(typ *types.Type) error {
|
||||
// typ is the KindFunc signature we're trying to build...
|
||||
|
||||
if len(typ.Ord) < 1 {
|
||||
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")
|
||||
}
|
||||
|
||||
t, err := obj.relabelOperatorArgNames(typ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not build function from type: %+v", typ)
|
||||
}
|
||||
obj.Type = t // func type
|
||||
obj.Type = typ // func type
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -635,59 +642,6 @@ func (obj *OperatorPolyFunc) Close() error {
|
||||
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
|
||||
// operator arg which specifies which operator we're using. It *is* idempotent.
|
||||
func removeOperatorArg(typ *types.Type) *types.Type {
|
||||
|
||||
@@ -26,6 +26,13 @@ import (
|
||||
"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.
|
||||
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
|
||||
|
||||
// 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
|
||||
@@ -47,9 +54,9 @@ func ModuleRegister(module, name string, fn *types.FuncValue) {
|
||||
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.
|
||||
type simpleFunc struct {
|
||||
type WrappedFunc struct {
|
||||
Fn *types.FuncValue
|
||||
|
||||
init *interfaces.Init
|
||||
@@ -60,9 +67,22 @@ type simpleFunc 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
|
||||
// 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
|
||||
return fmt.Errorf("type is still unspecified")
|
||||
}
|
||||
@@ -70,7 +90,7 @@ func (obj *simpleFunc) Validate() error {
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *simpleFunc) Info() *interfaces.Info {
|
||||
func (obj *WrappedFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
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.
|
||||
func (obj *simpleFunc) Init(init *interfaces.Init) error {
|
||||
func (obj *WrappedFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.closeChan = make(chan struct{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
for {
|
||||
select {
|
||||
@@ -141,7 +161,7 @@ func (obj *simpleFunc) Stream() error {
|
||||
}
|
||||
|
||||
// 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)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ import (
|
||||
"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.
|
||||
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))
|
||||
}
|
||||
|
||||
_, 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
|
||||
|
||||
// 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
|
||||
@@ -72,10 +84,38 @@ func ModuleRegister(module, name string, fns []*types.FuncValue) {
|
||||
Register(module+funcs.ModuleSep+name, fns)
|
||||
}
|
||||
|
||||
// simplePolyFunc 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 simplePolyFunc struct {
|
||||
// consistentArgs returns the list of arg names across all the functions or
|
||||
// errors if one consistent list could not be found.
|
||||
func consistentArgs(fns []*types.FuncValue) ([]string, error) {
|
||||
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
|
||||
|
||||
fn *types.FuncValue // the concrete version of our chosen function
|
||||
@@ -88,10 +128,22 @@ type simplePolyFunc 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
|
||||
// this static polymorphic function. It relies on type and value hints to limit
|
||||
// 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 {
|
||||
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 {
|
||||
// TODO: if status is "both", should we skip as too difficult?
|
||||
_, 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 {
|
||||
continue
|
||||
}
|
||||
@@ -115,58 +167,28 @@ func (obj *simplePolyFunc) Polymorphisms(partialType *types.Type, partialValues
|
||||
// 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
|
||||
// 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...
|
||||
if typ.Out == nil {
|
||||
return fmt.Errorf("return type of function must be specified")
|
||||
}
|
||||
|
||||
// find typ in obj.Fns
|
||||
for ix, f := range obj.Fns {
|
||||
if f.T.HasVariant() {
|
||||
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
|
||||
}
|
||||
index, err := langutil.FnMatch(typ, obj.Fns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.buildFunction(typ, index) // found match at this index
|
||||
|
||||
// match concrete type against our list that might contain a variant
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildFunction builds our concrete static function, from the potentially
|
||||
// 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.T = typ.Copy() // overwrites any contained "variant" type
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *simplePolyFunc) Validate() error {
|
||||
func (obj *WrappedFunc) Validate() error {
|
||||
if len(obj.Fns) == 0 {
|
||||
return fmt.Errorf("missing list of functions")
|
||||
}
|
||||
@@ -195,24 +217,28 @@ func (obj *simplePolyFunc) Validate() error {
|
||||
}
|
||||
|
||||
// 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{
|
||||
Pure: true,
|
||||
Memo: false, // TODO: should this be something we specify here?
|
||||
Sig: obj.fn.Type(),
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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.closeChan = make(chan struct{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
for {
|
||||
select {
|
||||
@@ -275,7 +301,7 @@ func (obj *simplePolyFunc) Stream() error {
|
||||
}
|
||||
|
||||
// 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)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -50,6 +50,15 @@ type StructLookupPolyFunc 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
|
||||
// this static polymorphic function. It relies on type and value hints to limit
|
||||
// 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
|
||||
// will return correct data.
|
||||
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{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Sig: typ, // func kind
|
||||
Sig: sig, // func kind
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
177
lang/funcs/structs/call.go
Normal file
177
lang/funcs/structs/call.go
Normal 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
|
||||
}
|
||||
200
lang/funcs/structs/function.go
Normal file
200
lang/funcs/structs/function.go
Normal 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
|
||||
}
|
||||
@@ -22,15 +22,15 @@ import (
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"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
|
||||
// lookup. It exists so that the reactive function engine type checks correctly.
|
||||
type VarFunc struct {
|
||||
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
|
||||
last types.Value // last value received to use for diff
|
||||
@@ -44,22 +44,9 @@ func (obj *VarFunc) Validate() error {
|
||||
if obj.Type == nil {
|
||||
return fmt.Errorf("must specify a type")
|
||||
}
|
||||
if obj.Func == nil {
|
||||
return fmt.Errorf("must specify a func")
|
||||
}
|
||||
if obj.Edge == "" {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/gapi"
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/unification"
|
||||
"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
|
||||
},
|
||||
// all the built-in top-level, core functions enter here...
|
||||
Functions: funcs.LookupPrefix(""),
|
||||
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
|
||||
}
|
||||
|
||||
logf("building scope...")
|
||||
|
||||
@@ -31,7 +31,12 @@ import (
|
||||
// methods that they must both implement. In practice it is not used especially
|
||||
// often since we usually know which kind of node we want.
|
||||
type 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
|
||||
|
||||
//Parent() Node // TODO: should we implement this?
|
||||
}
|
||||
|
||||
@@ -40,12 +45,37 @@ type Node interface {
|
||||
// expression.)
|
||||
type Stmt interface {
|
||||
Node
|
||||
fmt.Stringer // String() string
|
||||
Init(*Data) error // initialize the populated node and validate
|
||||
Interpolate() (Stmt, error) // return expanded form of AST as a new AST
|
||||
SetScope(*Scope) error // set the scope here and propagate it downwards
|
||||
Unify() ([]Invariant, error) // TODO: is this named correctly?
|
||||
|
||||
// Init initializes the populated node and does some basic validation.
|
||||
Init(*Data) error
|
||||
|
||||
// Interpolate returns an expanded form of the AST as a new AST. It does
|
||||
// a recursive interpolate (copy) of all members in the AST.
|
||||
Interpolate() (Stmt, error) // return expanded form of AST as a new AST
|
||||
|
||||
// Copy returns a light copy of the struct. Anything static will not be
|
||||
// copied. For a full recursive copy consider using Interpolate instead.
|
||||
// TODO: do we need an error in the signature?
|
||||
Copy() (Stmt, error)
|
||||
|
||||
// Ordering returns a graph of the scope ordering that represents the
|
||||
// data flow. This can be used in SetScope so that it knows the correct
|
||||
// order to run it in.
|
||||
Ordering(map[string]Node) (*pgraph.Graph, map[Node]string, error)
|
||||
|
||||
// SetScope sets the scope here and propagates it downwards.
|
||||
SetScope(*Scope) error
|
||||
|
||||
// Unify returns the list of invariants that this node produces. It does
|
||||
// so recursively on any children elements that exist in the AST, and
|
||||
// returns the collection to the caller.
|
||||
Unify() ([]Invariant, error)
|
||||
|
||||
// Graph returns the reactive function graph expressed by this node.
|
||||
Graph() (*pgraph.Graph, error)
|
||||
|
||||
// Output returns the output that this "program" produces. This output
|
||||
// is what is used to build the output graph.
|
||||
Output() (*Output, error)
|
||||
}
|
||||
|
||||
@@ -55,17 +85,51 @@ type Stmt interface {
|
||||
// these can be stored as pointers in our graph data structure.
|
||||
type Expr interface {
|
||||
Node
|
||||
//fmt.Stringer // already provided by pgraph.Vertex
|
||||
pgraph.Vertex // must implement this since we store these in our graphs
|
||||
Init(*Data) error // initialize the populated node and validate
|
||||
Interpolate() (Expr, error) // return expanded form of AST as a new AST
|
||||
SetScope(*Scope) error // set the scope here and propagate it downwards
|
||||
SetType(*types.Type) error // sets the type definitively, errors if incompatible
|
||||
|
||||
// Init initializes the populated node and does some basic validation.
|
||||
Init(*Data) error
|
||||
|
||||
// Interpolate returns an expanded form of the AST as a new AST. It does
|
||||
// a recursive interpolate (copy) of all members in the AST. For a light
|
||||
// copy use Copy.
|
||||
Interpolate() (Expr, error)
|
||||
|
||||
// Copy returns a light copy of the struct. Anything static will not be
|
||||
// copied. For a full recursive copy consider using Interpolate instead.
|
||||
// TODO: do we need an error in the signature?
|
||||
Copy() (Expr, error)
|
||||
|
||||
// Ordering returns a graph of the scope ordering that represents the
|
||||
// data flow. This can be used in SetScope so that it knows the correct
|
||||
// order to run it in.
|
||||
Ordering(map[string]Node) (*pgraph.Graph, map[Node]string, error)
|
||||
|
||||
// SetScope sets the scope here and propagates it downwards.
|
||||
SetScope(*Scope) error
|
||||
|
||||
// SetType sets the type definitively, and errors if it is incompatible.
|
||||
SetType(*types.Type) error
|
||||
|
||||
// Type returns the type of this expression. It may speculate if it can
|
||||
// determine it statically. This errors if it is not yet known.
|
||||
Type() (*types.Type, error)
|
||||
Unify() ([]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)
|
||||
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
|
||||
|
||||
// Value returns the value of this expression in our type system.
|
||||
Value() (types.Value, error)
|
||||
}
|
||||
|
||||
@@ -147,10 +211,13 @@ type Data struct {
|
||||
// from the variables, which could actually contain lambda functions.
|
||||
type Scope struct {
|
||||
Variables map[string]Expr
|
||||
Functions map[string]func() Func
|
||||
Functions map[string]Expr // the Expr will usually be an *ExprFunc
|
||||
Classes map[string]Stmt
|
||||
// TODO: It is easier to shift a list, but let's use a map for Indexes
|
||||
// for now in case we ever need holes...
|
||||
Indexes map[int][]Expr // TODO: use [][]Expr instead?
|
||||
|
||||
Chain []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
|
||||
@@ -158,9 +225,30 @@ type Scope struct {
|
||||
func EmptyScope() *Scope {
|
||||
return &Scope{
|
||||
Variables: make(map[string]Expr),
|
||||
Functions: make(map[string]func() Func),
|
||||
Functions: make(map[string]Expr),
|
||||
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.
|
||||
func (obj *Scope) Copy() *Scope {
|
||||
variables := make(map[string]Expr)
|
||||
functions := make(map[string]func() Func)
|
||||
functions := make(map[string]Expr)
|
||||
classes := make(map[string]Stmt)
|
||||
chain := []Stmt{}
|
||||
indexes := make(map[int][]Expr)
|
||||
chain := []Node{}
|
||||
if obj != nil { // allow copying nil scopes
|
||||
obj.InitScope() // safety
|
||||
for k, v := range obj.Variables { // copy
|
||||
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
|
||||
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
|
||||
chain = append(chain, x) // we don't copy the Stmt pointer!
|
||||
}
|
||||
@@ -191,6 +288,7 @@ func (obj *Scope) Copy() *Scope {
|
||||
Variables: variables,
|
||||
Functions: functions,
|
||||
Classes: classes,
|
||||
Indexes: indexes,
|
||||
Chain: chain,
|
||||
}
|
||||
}
|
||||
@@ -220,6 +318,8 @@ func (obj *Scope) Merge(scope *Scope) error {
|
||||
sort.Strings(namedFunctions)
|
||||
sort.Strings(namedClasses)
|
||||
|
||||
obj.InitScope() // safety
|
||||
|
||||
for _, name := range namedVariables {
|
||||
if _, exists := obj.Variables[name]; exists {
|
||||
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]
|
||||
}
|
||||
|
||||
// FIXME: should we merge or overwrite? (I think this isn't even used)
|
||||
obj.Indexes = scope.Indexes // overwrite without error
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -257,12 +360,80 @@ func (obj *Scope) IsEmpty() bool {
|
||||
if len(obj.Functions) > 0 {
|
||||
return false
|
||||
}
|
||||
if len(obj.Indexes) > 0 { // FIXME: should we check each one? (unused?)
|
||||
return false
|
||||
}
|
||||
if len(obj.Classes) > 0 {
|
||||
return false
|
||||
}
|
||||
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
|
||||
// lang to express a dependency between two resources and optionally send/recv.
|
||||
type Edge struct {
|
||||
|
||||
@@ -21,4 +21,8 @@ const (
|
||||
// ModuleSep is the character used for the module scope separation. For
|
||||
// example when using `fmt.printf` or `math.sin` this is the char used.
|
||||
ModuleSep = "."
|
||||
|
||||
// VarPrefix is the prefix character that precedes the variables
|
||||
// identifer. For example, `$foo` or for a lambda, `$fn(42)`.
|
||||
VarPrefix = "$"
|
||||
)
|
||||
|
||||
@@ -53,6 +53,13 @@ type Init struct {
|
||||
// never change to avoid the overhead of the goroutine and channel listener?
|
||||
type Func interface {
|
||||
Validate() error // FIXME: this is only needed for PolyFunc. Get it moved and used!
|
||||
|
||||
// Info returns some information about the function in question, which
|
||||
// includes the function signature. For a polymorphic function, this
|
||||
// might not be known until after Build was called. As a result, the
|
||||
// sig should be allowed to return a partial or variant type if it is
|
||||
// not known yet. This is because the Info method might be called
|
||||
// speculatively to aid in type unification.
|
||||
Info() *Info
|
||||
Init(*Init) error
|
||||
Stream() error
|
||||
@@ -99,5 +106,5 @@ type NamedArgsFunc interface {
|
||||
|
||||
// ArgGen implements the arg name generator function. By default, we use
|
||||
// the util.NumToAlpha function when this interface isn't implemented...
|
||||
ArgGen(int) string
|
||||
ArgGen(int) (string, error)
|
||||
}
|
||||
|
||||
@@ -36,4 +36,13 @@ type Invariant interface {
|
||||
// Matches returns whether an invariant matches the existing solution.
|
||||
// If it is inconsistent, then it errors.
|
||||
Matches(solved map[Expr]*types.Type) (bool, error)
|
||||
|
||||
// Possible returns an error if it is certain that it is NOT possible to
|
||||
// get a solution with this invariant and the set of partials. In
|
||||
// certain cases, it might not be able to determine that it's not
|
||||
// possible, while simultaneously not being able to guarantee a possible
|
||||
// solution either. In this situation, it should return nil, since this
|
||||
// is used as a filtering mechanism, and the nil result of possible is
|
||||
// preferred over eliminating a tricky, but possible one.
|
||||
Possible(partials []Invariant) error
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestAstFunc0(t *testing.T) {
|
||||
"answer": &ExprInt{V: 42},
|
||||
},
|
||||
// all the built-in top-level, core functions enter here...
|
||||
Functions: funcs.LookupPrefix(""),
|
||||
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
|
||||
}
|
||||
|
||||
type test struct { // an individual test
|
||||
@@ -111,10 +111,11 @@ func TestAstFunc0(t *testing.T) {
|
||||
}
|
||||
{
|
||||
graph, _ := pgraph.NewGraph("g")
|
||||
v1, v2 := vtex("int(42)"), vtex("var(x)")
|
||||
e1 := edge("var:x")
|
||||
graph.AddVertex(&v1, &v2)
|
||||
graph.AddEdge(&v1, &v2, &e1)
|
||||
// empty graph at the moment, because they're all unused!
|
||||
//v1, v2 := vtex("int(42)"), vtex("var(x)")
|
||||
//e1 := edge("var:x")
|
||||
//graph.AddVertex(&v1, &v2)
|
||||
//graph.AddEdge(&v1, &v2, &e1)
|
||||
testCases = append(testCases, test{
|
||||
name: "two vars",
|
||||
code: `
|
||||
@@ -181,7 +182,7 @@ func TestAstFunc0(t *testing.T) {
|
||||
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))
|
||||
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(&v3, &v5, &e2)
|
||||
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))
|
||||
|
||||
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(&v4, &v7, &e2)
|
||||
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(&v7, &v8, &e5)
|
||||
graph.AddEdge(&v6, &v8, &e6)
|
||||
@@ -233,7 +234,7 @@ func TestAstFunc0(t *testing.T) {
|
||||
v5, v6 := vtex("var(i)"), vtex("var(x)")
|
||||
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.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)")
|
||||
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")
|
||||
// only one edge! (cool)
|
||||
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)")
|
||||
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")
|
||||
// only one edge! (cool)
|
||||
graph.AddEdge(&v2, &v4, &e1)
|
||||
@@ -552,7 +555,7 @@ func TestAstFunc1(t *testing.T) {
|
||||
"hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used
|
||||
},
|
||||
// all the built-in top-level, core functions enter here...
|
||||
Functions: funcs.LookupPrefix(""),
|
||||
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
|
||||
}
|
||||
|
||||
type errs struct {
|
||||
@@ -890,7 +893,22 @@ func TestAstFunc1(t *testing.T) {
|
||||
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
|
||||
if expstr == magicEmpty {
|
||||
expstr = ""
|
||||
@@ -954,7 +972,7 @@ func TestAstFunc2(t *testing.T) {
|
||||
"hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used
|
||||
},
|
||||
// all the built-in top-level, core functions enter here...
|
||||
Functions: funcs.LookupPrefix(""),
|
||||
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
|
||||
}
|
||||
|
||||
type errs struct {
|
||||
@@ -1307,6 +1325,22 @@ func TestAstFunc2(t *testing.T) {
|
||||
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
|
||||
funcs := &funcs.Engine{
|
||||
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)
|
||||
return
|
||||
}
|
||||
// TODO: cleanup before we print any test failures...
|
||||
defer funcs.Close() // cleanup
|
||||
|
||||
// wait for some activity
|
||||
|
||||
38
lang/interpret_test/TestAstFunc1/changing-func.graph
Normal file
38
lang/interpret_test/TestAstFunc1/changing-func.graph
Normal 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)
|
||||
21
lang/interpret_test/TestAstFunc1/changing-func/main.mcl
Normal file
21
lang/interpret_test/TestAstFunc1/changing-func/main.mcl
Normal 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 {}
|
||||
17
lang/interpret_test/TestAstFunc1/classes-polyfuncs.graph
Normal file
17
lang/interpret_test/TestAstFunc1/classes-polyfuncs.graph
Normal 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)
|
||||
@@ -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
|
||||
}
|
||||
69
lang/interpret_test/TestAstFunc1/doubleclass.graph
Normal file
69
lang/interpret_test/TestAstFunc1/doubleclass.graph
Normal 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)
|
||||
15
lang/interpret_test/TestAstFunc1/doubleclass/main.mcl
Normal file
15
lang/interpret_test/TestAstFunc1/doubleclass/main.mcl
Normal 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)
|
||||
@@ -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: str("/tmp/foo")
|
||||
Vertex: str("/tmp/foo")
|
||||
|
||||
30
lang/interpret_test/TestAstFunc1/efficient-lambda.graph
Normal file
30
lang/interpret_test/TestAstFunc1/efficient-lambda.graph
Normal 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)
|
||||
10
lang/interpret_test/TestAstFunc1/efficient-lambda/main.mcl
Normal file
10
lang/interpret_test/TestAstFunc1/efficient-lambda/main.mcl
Normal 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
|
||||
@@ -1,12 +1 @@
|
||||
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()
|
||||
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)
|
||||
# err: err3: only recursive solutions left
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# this is an empty list of test resources, iow test resources
|
||||
# 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 [] {}
|
||||
|
||||
# single resource
|
||||
test "name" {}
|
||||
|
||||
# single resource, defined by list variable
|
||||
$names = ["hey",]
|
||||
test $names {}
|
||||
|
||||
# multiples resources, defined by list
|
||||
test ["hello", "world",] {}
|
||||
|
||||
11
lang/interpret_test/TestAstFunc1/empty-res-list-1.graph
Normal file
11
lang/interpret_test/TestAstFunc1/empty-res-list-1.graph
Normal 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)
|
||||
@@ -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",] {}
|
||||
@@ -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: 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: str("greeting")
|
||||
Vertex: str("hello: %s")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Edge: call:os.is_debian() -> if(call:os.is_debian()) # c
|
||||
Edge: if(call:os.is_debian()) -> var(aaa) # var:aaa
|
||||
Edge: str("bbb") -> if(call:os.is_debian()) # a
|
||||
Edge: str("ccc") -> if(call:os.is_debian()) # b
|
||||
Edge: call:os.is_debian() -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # c
|
||||
Edge: if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } -> var(aaa) # var:aaa
|
||||
Edge: str("bbb") -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # a
|
||||
Edge: str("ccc") -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # b
|
||||
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("ccc")
|
||||
Vertex: str("hello")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Edge: call:os.is_debian() -> if(call:os.is_debian()) # c
|
||||
Edge: if(call:os.is_debian()) -> var(aaa) # var:aaa
|
||||
Edge: str("bbb") -> if(call:os.is_debian()) # a
|
||||
Edge: str("ccc") -> if(call:os.is_debian()) # b
|
||||
Edge: call:os.is_debian() -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # c
|
||||
Edge: if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } -> var(aaa) # var:aaa
|
||||
Edge: str("bbb") -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # a
|
||||
Edge: str("ccc") -> if( call:os.is_debian() ) { str("bbb") } else { str("ccc") } # b
|
||||
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("ccc")
|
||||
Vertex: str("hello")
|
||||
|
||||
45
lang/interpret_test/TestAstFunc1/lambda-chained.graph
Normal file
45
lang/interpret_test/TestAstFunc1/lambda-chained.graph
Normal 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)
|
||||
12
lang/interpret_test/TestAstFunc1/lambda-chained/main.mcl
Normal file
12
lang/interpret_test/TestAstFunc1/lambda-chained/main.mcl
Normal 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 {}
|
||||
@@ -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: 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("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)
|
||||
|
||||
@@ -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: int(3) -> var(third.three) # var:third.three
|
||||
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("+"), 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)) # x
|
||||
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)) # 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)) # op
|
||||
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("this is module mod1 which contains: "), var(mod1.name)) # op
|
||||
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)) # 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)) # a
|
||||
Edge: str("the answer is: %d") -> call:fmt.printf(str("the answer is: %d"), var(answer)) # a
|
||||
Edge: str("i imported local: %s") -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # 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)) # format
|
||||
Edge: str("the answer is: %d") -> call:fmt.printf(str("the answer is: %d"), var(answer)) # format
|
||||
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 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(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)) # b
|
||||
Edge: var(example1.name) -> var(ex1) # var:ex1
|
||||
Edge: var(answer) -> call:fmt.printf(str("the answer is: %d"), var(answer)) # a
|
||||
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) -> 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(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: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
|
||||
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-example2/ and i contain: "), var(ex1))
|
||||
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 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("hello")
|
||||
Vertex: str("hello2")
|
||||
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-example2/ and i contain: ")
|
||||
Vertex: str("i imported local: %s")
|
||||
Vertex: str("i imported remote: %s and %s")
|
||||
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 local module mod1")
|
||||
Vertex: var(answer)
|
||||
Vertex: var(ex1)
|
||||
Vertex: var(example1.name)
|
||||
Vertex: var(example1.name)
|
||||
Vertex: var(example2.ex1)
|
||||
|
||||
@@ -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))) # 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))) # 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("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))) # 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))) # format
|
||||
Edge: str("t1") -> var(a) # var:a
|
||||
Edge: str("t2") -> var(a) # var:a
|
||||
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: str("hello")
|
||||
Vertex: str("len is: %d")
|
||||
Vertex: str("len is: %d")
|
||||
Vertex: str("t1")
|
||||
Vertex: str("t2")
|
||||
Vertex: var(a)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# err: err2: recursive class `c1` found
|
||||
# err: err2: recursive reference while setting scope: not a dag
|
||||
|
||||
@@ -1 +1 @@
|
||||
# err: err2: class `c1` does not exist in this scope
|
||||
# err: err2: recursive reference while setting scope: not a dag
|
||||
|
||||
@@ -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: int(3) -> var(third.three) # var:third.three
|
||||
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("+"), 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)) # x
|
||||
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)) # 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)) # op
|
||||
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("this is module mod1 which contains: "), var(mod1.name)) # op
|
||||
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)) # 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)) # a
|
||||
Edge: str("the answer is: %d") -> call:fmt.printf(str("the answer is: %d"), var(answer)) # a
|
||||
Edge: str("i imported local: %s") -> call:fmt.printf(str("i imported local: %s"), var(mod1.name)) # 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)) # format
|
||||
Edge: str("the answer is: %d") -> call:fmt.printf(str("the answer is: %d"), var(answer)) # format
|
||||
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 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(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)) # b
|
||||
Edge: var(example1.name) -> var(ex1) # var:ex1
|
||||
Edge: var(answer) -> call:fmt.printf(str("the answer is: %d"), var(answer)) # a
|
||||
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) -> 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(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: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
|
||||
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-example2/ and i contain: "), var(ex1))
|
||||
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 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("hello")
|
||||
Vertex: str("hello2")
|
||||
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-example2/ and i contain: ")
|
||||
Vertex: str("i imported local: %s")
|
||||
Vertex: str("i imported remote: %s and %s")
|
||||
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 local module mod1")
|
||||
Vertex: var(answer)
|
||||
Vertex: var(ex1)
|
||||
Vertex: var(example1.name)
|
||||
Vertex: var(example1.name)
|
||||
Vertex: var(example2.ex1)
|
||||
|
||||
11
lang/interpret_test/TestAstFunc1/returned-func.graph
Normal file
11
lang/interpret_test/TestAstFunc1/returned-func.graph
Normal 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)
|
||||
11
lang/interpret_test/TestAstFunc1/returned-func/main.mcl
Normal file
11
lang/interpret_test/TestAstFunc1/returned-func/main.mcl
Normal file
@@ -0,0 +1,11 @@
|
||||
# simple function definition containing function to be returned
|
||||
func funcgen() {
|
||||
func() {
|
||||
"hello"
|
||||
}
|
||||
}
|
||||
|
||||
$fn = funcgen()
|
||||
$out = $fn()
|
||||
|
||||
test $out {}
|
||||
11
lang/interpret_test/TestAstFunc1/returned-lambda.graph
Normal file
11
lang/interpret_test/TestAstFunc1/returned-lambda.graph
Normal 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)
|
||||
10
lang/interpret_test/TestAstFunc1/returned-lambda/main.mcl
Normal file
10
lang/interpret_test/TestAstFunc1/returned-lambda/main.mcl
Normal file
@@ -0,0 +1,10 @@
|
||||
$funcgen = func() {
|
||||
func() {
|
||||
"hello"
|
||||
}
|
||||
}
|
||||
|
||||
$fn = $funcgen()
|
||||
$out = $fn()
|
||||
|
||||
test $out {}
|
||||
4
lang/interpret_test/TestAstFunc1/shadowing1.graph
Normal file
4
lang/interpret_test/TestAstFunc1/shadowing1.graph
Normal file
@@ -0,0 +1,4 @@
|
||||
Edge: str("hello") -> var(x) # var:x
|
||||
Vertex: bool(true)
|
||||
Vertex: str("hello")
|
||||
Vertex: var(x)
|
||||
6
lang/interpret_test/TestAstFunc1/shadowing1/main.mcl
Normal file
6
lang/interpret_test/TestAstFunc1/shadowing1/main.mcl
Normal file
@@ -0,0 +1,6 @@
|
||||
# this should be okay, because var is shadowed
|
||||
$x = "hello"
|
||||
if true {
|
||||
$x = "world" # shadowed
|
||||
}
|
||||
test $x {}
|
||||
4
lang/interpret_test/TestAstFunc1/shadowing2.graph
Normal file
4
lang/interpret_test/TestAstFunc1/shadowing2.graph
Normal file
@@ -0,0 +1,4 @@
|
||||
Edge: str("world") -> var(x) # var:x
|
||||
Vertex: bool(true)
|
||||
Vertex: str("world")
|
||||
Vertex: var(x)
|
||||
6
lang/interpret_test/TestAstFunc1/shadowing2/main.mcl
Normal file
6
lang/interpret_test/TestAstFunc1/shadowing2/main.mcl
Normal file
@@ -0,0 +1,6 @@
|
||||
# this should be okay, because var is shadowed
|
||||
$x = "hello"
|
||||
if true {
|
||||
$x = "world" # shadowed
|
||||
test $x {}
|
||||
}
|
||||
7
lang/interpret_test/TestAstFunc1/simple-func1.graph
Normal file
7
lang/interpret_test/TestAstFunc1/simple-func1.graph
Normal 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)
|
||||
7
lang/interpret_test/TestAstFunc1/simple-func1/main.mcl
Normal file
7
lang/interpret_test/TestAstFunc1/simple-func1/main.mcl
Normal file
@@ -0,0 +1,7 @@
|
||||
func answer() {
|
||||
"the answer is 42"
|
||||
}
|
||||
|
||||
$out1 = answer()
|
||||
|
||||
test $out1 {}
|
||||
16
lang/interpret_test/TestAstFunc1/simple-func2.graph
Normal file
16
lang/interpret_test/TestAstFunc1/simple-func2.graph
Normal 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)
|
||||
8
lang/interpret_test/TestAstFunc1/simple-func2/main.mcl
Normal file
8
lang/interpret_test/TestAstFunc1/simple-func2/main.mcl
Normal file
@@ -0,0 +1,8 @@
|
||||
func answer() {
|
||||
"the answer is 42"
|
||||
}
|
||||
|
||||
$out1 = answer()
|
||||
$out2 = answer()
|
||||
|
||||
test $out1 + $out2 {}
|
||||
7
lang/interpret_test/TestAstFunc1/simple-lambda1.graph
Normal file
7
lang/interpret_test/TestAstFunc1/simple-lambda1.graph
Normal 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)
|
||||
10
lang/interpret_test/TestAstFunc1/simple-lambda1/main.mcl
Normal file
10
lang/interpret_test/TestAstFunc1/simple-lambda1/main.mcl
Normal 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 {}
|
||||
16
lang/interpret_test/TestAstFunc1/simple-lambda2.graph
Normal file
16
lang/interpret_test/TestAstFunc1/simple-lambda2.graph
Normal 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)
|
||||
11
lang/interpret_test/TestAstFunc1/simple-lambda2/main.mcl
Normal file
11
lang/interpret_test/TestAstFunc1/simple-lambda2/main.mcl
Normal 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 {}
|
||||
@@ -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:world.kvlookup(var(ns)) -> var(exchanged) # var:exchanged
|
||||
Edge: str("") -> var(hostname) # var:hostname
|
||||
Edge: str("==") -> call:_operator(str("=="), var(state), str("default")) # x
|
||||
Edge: str("==") -> call:_operator(str("=="), var(state), str("one")) # x
|
||||
Edge: str("==") -> call:_operator(str("=="), var(state), str("three")) # x
|
||||
Edge: str("==") -> call:_operator(str("=="), var(state), str("two")) # x
|
||||
Edge: str("==") -> call:_operator(str("=="), var(state), str("default")) # op
|
||||
Edge: str("==") -> call:_operator(str("=="), var(state), str("one")) # op
|
||||
Edge: str("==") -> call:_operator(str("=="), var(state), str("three")) # op
|
||||
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:maplookup(var(exchanged), var(hostname), str("default")) # default
|
||||
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("three") -> call:_operator(str("=="), var(state), str("three")) # 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(hostname) -> call:maplookup(var(exchanged), var(hostname), str("default")) # key
|
||||
Edge: var(ns) -> call:world.kvlookup(var(ns)) # namespace
|
||||
|
||||
12
lang/interpret_test/TestAstFunc1/static-function0.graph
Normal file
12
lang/interpret_test/TestAstFunc1/static-function0.graph
Normal 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")
|
||||
16
lang/interpret_test/TestAstFunc1/static-function0/main.mcl
Normal file
16
lang/interpret_test/TestAstFunc1/static-function0/main.mcl
Normal 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(),
|
||||
}
|
||||
56
lang/interpret_test/TestAstFunc1/static-function1.graph
Normal file
56
lang/interpret_test/TestAstFunc1/static-function1.graph
Normal 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)
|
||||
18
lang/interpret_test/TestAstFunc1/static-function1/main.mcl
Normal file
18
lang/interpret_test/TestAstFunc1/static-function1/main.mcl
Normal 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(),
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Vertex: test[worldwide]
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1 @@
|
||||
Vertex: test[world]
|
||||
@@ -0,0 +1,13 @@
|
||||
$some_bool = false
|
||||
$fn = if $some_bool {
|
||||
func($b) {
|
||||
"hello"
|
||||
}
|
||||
} else {
|
||||
func($bb) {
|
||||
"world"
|
||||
}
|
||||
}
|
||||
|
||||
$out = $fn(false)
|
||||
test $out {}
|
||||
@@ -0,0 +1 @@
|
||||
Vertex: test[hello]
|
||||
@@ -0,0 +1,13 @@
|
||||
func funcgen() {
|
||||
func() {
|
||||
func() {
|
||||
"hello"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fn1 = funcgen()
|
||||
$fn2 = $fn1()
|
||||
$out = $fn2()
|
||||
|
||||
test $out {}
|
||||
@@ -0,0 +1 @@
|
||||
Vertex: test[hello]
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,4 @@
|
||||
Vertex: test[hey]
|
||||
Vertex: test[there]
|
||||
Vertex: test[wow: hello]
|
||||
Vertex: test[wow: world]
|
||||
@@ -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
|
||||
2
lang/interpret_test/TestAstFunc2/changing-func.output
Normal file
2
lang/interpret_test/TestAstFunc2/changing-func.output
Normal file
@@ -0,0 +1,2 @@
|
||||
Vertex: test[hello]
|
||||
Vertex: test[world]
|
||||
21
lang/interpret_test/TestAstFunc2/changing-func/main.mcl
Normal file
21
lang/interpret_test/TestAstFunc2/changing-func/main.mcl
Normal 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 {}
|
||||
2
lang/interpret_test/TestAstFunc2/changing-lambda.output
Normal file
2
lang/interpret_test/TestAstFunc2/changing-lambda.output
Normal file
@@ -0,0 +1,2 @@
|
||||
Vertex: test[hello]
|
||||
Vertex: test[world]
|
||||
21
lang/interpret_test/TestAstFunc2/changing-lambda/main.mcl
Normal file
21
lang/interpret_test/TestAstFunc2/changing-lambda/main.mcl
Normal 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 {}
|
||||
@@ -0,0 +1,4 @@
|
||||
Vertex: test[true-true]
|
||||
Vertex: test[true-false]
|
||||
Vertex: test[false-true]
|
||||
Vertex: test[false-false]
|
||||
@@ -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() {}
|
||||
@@ -0,0 +1,3 @@
|
||||
Vertex: test[hello purpleidea]
|
||||
Vertex: test[hello user]
|
||||
Vertex: test[who is there?]
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,2 @@
|
||||
Vertex: test[so true]
|
||||
Vertex: test[so false]
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,2 @@
|
||||
Vertex: test[so true]
|
||||
Vertex: test[so false]
|
||||
@@ -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 {}
|
||||
@@ -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
Reference in New Issue
Block a user