lang: Split lang package out into many subpackages

This is a giant refactor to split the giant lang package into many
subpackages. The most difficult piece was figuring out how to extract
the extra ast structs into their own package, because they needed to
call two functions which also needed to import the ast.

The solution was to separate out those functions into their own
packages, and to pass them into the ast at the root when they're needed,
and to let the relevant ast portions call a handle.

This isn't terribly ugly because we already had a giant data struct
woven through the ast.

The bad part is rebasing any WIP work on top of this.
This commit is contained in:
James Shubin
2021-10-21 03:35:31 -04:00
parent 8ae47bd490
commit 23b5a4729f
23 changed files with 1212 additions and 1129 deletions

View File

@@ -20,23 +20,23 @@ SHELL = /usr/bin/env bash
all: build all: build
build: lexer.nn.go y.go interpolate/parse.generated.go build: parser/lexer.nn.go parser/y.go interpolate/parse.generated.go
@# recursively run make in child dir named types @# recursively run make in child dir named types
@$(MAKE) --quiet -C types @$(MAKE) --quiet -C types
clean: clean:
$(MAKE) --quiet -C types clean $(MAKE) --quiet -C types clean
@rm -f lexer.nn.go y.go y.output interpolate/parse.generated.go || true @rm -f parser/lexer.nn.go parser/y.go parser/y.output interpolate/parse.generated.go || true
lexer.nn.go: lexer.nex parser/lexer.nn.go: parser/lexer.nex
@echo "Generating: lexer..." @echo "Generating: lexer..."
nex -e lexer.nex nex -e -o $@ $<
@ROOT="$$( cd "$$( dirname "$${BASH_SOURCE[0]}" )" && cd .. && pwd )" && $$ROOT/misc/header.sh 'lexer.nn.go' @ROOT="$$( cd "$$( dirname "$${BASH_SOURCE[0]}" )" && cd .. && pwd )" && $$ROOT/misc/header.sh 'parser/lexer.nn.go'
y.go: parser.y parser/y.go: parser/parser.y
@echo "Generating: parser..." @echo "Generating: parser..."
goyacc parser.y goyacc -v parser/y.output -o $@ $<
@ROOT="$$( cd "$$( dirname "$${BASH_SOURCE[0]}" )" && cd .. && pwd )" && $$ROOT/misc/header.sh 'y.go' @ROOT="$$( cd "$$( dirname "$${BASH_SOURCE[0]}" )" && cd .. && pwd )" && $$ROOT/misc/header.sh 'parser/y.go'
interpolate/parse.generated.go: interpolate/parse.rl interpolate/parse.generated.go: interpolate/parse.rl
@echo "Generating: interpolation..." @echo "Generating: interpolation..."

View File

@@ -17,7 +17,7 @@
// +build !root // +build !root
package lang package ast
import ( import (
"fmt" "fmt"

View File

@@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package lang // TODO: move this into a sub package of lang/$name? package ast
import ( import (
"bytes" "bytes"
@@ -3065,13 +3065,13 @@ func (obj *StmtProg) importSystemScope(name string) (*interfaces.Scope, error) {
isEmpty := true // assume empty (which should cause an error) isEmpty := true // assume empty (which should cause an error)
funcs := FuncPrefixToFunctionsScope(name) // runs funcs.LookupPrefix functions := FuncPrefixToFunctionsScope(name) // runs funcs.LookupPrefix
if len(funcs) > 0 { if len(functions) > 0 {
isEmpty = false isEmpty = false
} }
// perform any normal "startup" for these functions... // perform any normal "startup" for these functions...
for _, fn := range funcs { for _, fn := range functions {
// XXX: is this the right place for this, or should it be elsewhere? // XXX: is this the right place for this, or should it be elsewhere?
// XXX: do we need a modified obj.data for this b/c it's in a scope? // XXX: do we need a modified obj.data for this b/c it's in a scope?
if err := fn.Init(obj.data); err != nil { if err := fn.Init(obj.data); err != nil {
@@ -3084,7 +3084,7 @@ func (obj *StmtProg) importSystemScope(name string) (*interfaces.Scope, error) {
scope := &interfaces.Scope{ scope := &interfaces.Scope{
// TODO: we could use the core API for variables somehow... // TODO: we could use the core API for variables somehow...
//Variables: make(map[string]interfaces.Expr), //Variables: make(map[string]interfaces.Expr),
Functions: funcs, // map[string]interfaces.Expr Functions: functions, // map[string]interfaces.Expr
// TODO: we could add a core API for classes too! // TODO: we could add a core API for classes too!
//Classes: make(map[string]interfaces.Stmt), //Classes: make(map[string]interfaces.Stmt),
} }
@@ -3101,7 +3101,7 @@ func (obj *StmtProg) importSystemScope(name string) (*interfaces.Scope, error) {
// XXX: consider using a virtual `append *` statement to combine these instead. // XXX: consider using a virtual `append *` statement to combine these instead.
for _, p := range paths { for _, p := range paths {
// we only want code from this prefix // we only want code from this prefix
prefix := CoreDir + name + "/" prefix := funcs.CoreDir + name + "/"
if !strings.HasPrefix(p, prefix) { if !strings.HasPrefix(p, prefix) {
continue continue
} }
@@ -3126,7 +3126,7 @@ func (obj *StmtProg) importSystemScope(name string) (*interfaces.Scope, error) {
reader := bytes.NewReader(b) // wrap the byte stream reader := bytes.NewReader(b) // wrap the byte stream
// now run the lexer/parser to do the import // now run the lexer/parser to do the import
ast, err := LexParse(reader) ast, err := obj.data.LexParser(reader)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "could not generate AST from import `%s`", name) return nil, errwrap.Wrapf(err, "could not generate AST from import `%s`", name)
} }
@@ -3240,7 +3240,7 @@ func (obj *StmtProg) importScopeWithInputs(s string, scope *interfaces.Scope, pa
metadata.Metadata = obj.data.Metadata metadata.Metadata = obj.data.Metadata
// now run the lexer/parser to do the import // now run the lexer/parser to do the import
ast, err := LexParse(reader) ast, err := obj.data.LexParser(reader)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "could not generate AST from import") return nil, errwrap.Wrapf(err, "could not generate AST from import")
} }
@@ -3252,15 +3252,18 @@ func (obj *StmtProg) importScopeWithInputs(s string, scope *interfaces.Scope, pa
// init and validate the structure of the AST // init and validate the structure of the AST
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
Fs: obj.data.Fs, Fs: obj.data.Fs,
FsURI: obj.data.FsURI, FsURI: obj.data.FsURI,
Base: output.Base, // new base dir (absolute path) Base: output.Base, // new base dir (absolute path)
Files: files, Files: files,
Imports: parentVertex, // the parent vertex that imported me Imports: parentVertex, // the parent vertex that imported me
Metadata: metadata, Metadata: metadata,
Modules: obj.data.Modules, Modules: obj.data.Modules,
Downloader: obj.data.Downloader,
//World: obj.data.World, LexParser: obj.data.LexParser,
Downloader: obj.data.Downloader,
StrInterpolater: obj.data.StrInterpolater,
//World: obj.data.World, // TODO: do we need this?
//Prefix: obj.Prefix, // TODO: add a path on? //Prefix: obj.Prefix, // TODO: add a path on?
Debug: obj.data.Debug, Debug: obj.data.Debug,
@@ -3359,7 +3362,7 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
return fmt.Errorf("import `%s` already exists in this scope", imp.Name) return fmt.Errorf("import `%s` already exists in this scope", imp.Name)
} }
result, err := ParseImportName(imp.Name) result, err := langutil.ParseImportName(imp.Name)
if err != nil { if err != nil {
return errwrap.Wrapf(err, "import `%s` is not valid", imp.Name) return errwrap.Wrapf(err, "import `%s` is not valid", imp.Name)
} }
@@ -3447,16 +3450,16 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
} }
// now collect all the functions, and group by name (if polyfunc is ok) // now collect all the functions, and group by name (if polyfunc is ok)
funcs := make(map[string][]*StmtFunc) functions := make(map[string][]*StmtFunc)
for _, x := range obj.Body { for _, x := range obj.Body {
fn, ok := x.(*StmtFunc) fn, ok := x.(*StmtFunc)
if !ok { if !ok {
continue continue
} }
_, exists := funcs[fn.Name] _, exists := functions[fn.Name]
if !exists { if !exists {
funcs[fn.Name] = []*StmtFunc{} // initialize functions[fn.Name] = []*StmtFunc{} // initialize
} }
// check for duplicates *in this scope* // check for duplicates *in this scope*
@@ -3464,11 +3467,11 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
return fmt.Errorf("func `%s` already exists in this scope", fn.Name) return fmt.Errorf("func `%s` already exists in this scope", fn.Name)
} }
// collect funcs (if multiple, this is a polyfunc) // collect functions (if multiple, this is a polyfunc)
funcs[fn.Name] = append(funcs[fn.Name], fn) functions[fn.Name] = append(functions[fn.Name], fn)
} }
for name, fnList := range funcs { for name, fnList := range functions {
if obj.data.Debug { // TODO: is this message ever useful? if obj.data.Debug { // TODO: is this message ever useful?
obj.data.Logf("prog: set scope: collect: (%+v -> %d): %+v (%T)", name, len(fnList), fnList[0].Func, fnList[0].Func) obj.data.Logf("prog: set scope: collect: (%+v -> %d): %+v (%T)", name, len(fnList), fnList[0].Func, fnList[0].Func)
} }
@@ -4816,7 +4819,7 @@ func (obj *ExprStr) Init(data *interfaces.Data) error {
// which need interpolation. If any are found, it returns a larger AST which has // which need interpolation. If any are found, it returns a larger AST which has
// a function which returns a string as its root. Otherwise it returns itself. // a function which returns a string as its root. Otherwise it returns itself.
func (obj *ExprStr) Interpolate() (interfaces.Expr, error) { func (obj *ExprStr) Interpolate() (interfaces.Expr, error) {
pos := &Pos{ pos := &interfaces.Pos{
// column/line number, starting at 1 // column/line number, starting at 1
//Column: -1, // TODO //Column: -1, // TODO
//Line: -1, // TODO //Line: -1, // TODO
@@ -4825,22 +4828,27 @@ func (obj *ExprStr) Interpolate() (interfaces.Expr, error) {
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
Fs: obj.data.Fs, Fs: obj.data.Fs,
FsURI: obj.data.FsURI, FsURI: obj.data.FsURI,
Base: obj.data.Base, Base: obj.data.Base,
Files: obj.data.Files, Files: obj.data.Files,
Imports: obj.data.Imports, Imports: obj.data.Imports,
Metadata: obj.data.Metadata, Metadata: obj.data.Metadata,
Modules: obj.data.Modules, Modules: obj.data.Modules,
Downloader: obj.data.Downloader,
//World: obj.data.World, LexParser: obj.data.LexParser,
Downloader: obj.data.Downloader,
StrInterpolater: obj.data.StrInterpolater,
//World: obj.data.World, // TODO: do we need this?
Prefix: obj.data.Prefix, Prefix: obj.data.Prefix,
Debug: obj.data.Debug, Debug: obj.data.Debug,
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
obj.data.Logf("interpolate: "+format, v...) obj.data.Logf("interpolate: "+format, v...)
}, },
} }
result, err := InterpolateStr(obj.V, pos, data)
result, err := obj.data.StrInterpolater(obj.V, pos, data)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package lang package ast
import ( import (
"fmt" "fmt"
@@ -27,6 +27,7 @@ import (
"github.com/purpleidea/mgmt/lang/funcs/vars" "github.com/purpleidea/mgmt/lang/funcs/vars"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types" "github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util/errwrap"
) )
// FuncPrefixToFunctionsScope is a helper function to return the functions // FuncPrefixToFunctionsScope is a helper function to return the functions
@@ -215,3 +216,29 @@ func ValueToExpr(val types.Value) (interfaces.Expr, error) {
return expr, expr.SetType(val.Type()) return expr, expr.SetType(val.Type())
} }
// CollectFiles collects all the files used in the AST. You will see more files
// based on how many compiling steps have run. In general, this is useful for
// collecting all the files needed to store in our file system for a deploy.
func CollectFiles(ast interfaces.Stmt) ([]string, error) {
// collect the list of files
fileList := []string{}
fn := func(node interfaces.Node) error {
// redundant check for example purposes
stmt, ok := node.(interfaces.Stmt)
if !ok {
return nil
}
body, ok := stmt.(*StmtProg)
if !ok {
return nil
}
// collect into global
fileList = append(fileList, body.importFiles...)
return nil
}
if err := ast.Apply(fn); err != nil {
return nil, errwrap.Wrapf(err, "can't retrieve paths")
}
return fileList, nil
}

View File

@@ -40,6 +40,9 @@ const (
// we don't want to allow this as the first or last character in a name. // we don't want to allow this as the first or last character in a name.
// NOTE: the template library will panic if it is one of: .-# // NOTE: the template library will panic if it is one of: .-#
ReplaceChar = "_" ReplaceChar = "_"
// CoreDir is the directory prefix where core bindata mcl code is added.
CoreDir = "core/"
) )
// registeredFuncs is a global map of all possible funcs which can be used. You // registeredFuncs is a global map of all possible funcs which can be used. You

View File

@@ -20,7 +20,7 @@ package fuzz
import ( import (
"bytes" "bytes"
"github.com/purpleidea/mgmt/lang" "github.com/purpleidea/mgmt/lang/parser"
) )
// Fuzz is repeatedly called by go-fuzz with semi-random inputs in an attempt to // Fuzz is repeatedly called by go-fuzz with semi-random inputs in an attempt to
@@ -32,7 +32,7 @@ import (
// gives new coverage; and 0 otherwise; other values are reserved for future // gives new coverage; and 0 otherwise; other values are reserved for future
// use. // use.
func Fuzz(data []byte) int { func Fuzz(data []byte) int {
ast, err := lang.LexParse(bytes.NewReader(data)) ast, err := parser.LexParse(bytes.NewReader(data))
if err != nil { if err != nil {
if ast != nil { if ast != nil {
panic("ast != nil on error") panic("ast != nil on error")

View File

@@ -25,10 +25,13 @@ import (
"github.com/purpleidea/mgmt/gapi" "github.com/purpleidea/mgmt/gapi"
"github.com/purpleidea/mgmt/lang" "github.com/purpleidea/mgmt/lang"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/download" "github.com/purpleidea/mgmt/lang/download"
"github.com/purpleidea/mgmt/lang/funcs/vars" "github.com/purpleidea/mgmt/lang/funcs/vars"
"github.com/purpleidea/mgmt/lang/inputs" "github.com/purpleidea/mgmt/lang/inputs"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/interpolate"
"github.com/purpleidea/mgmt/lang/parser"
"github.com/purpleidea/mgmt/lang/unification" "github.com/purpleidea/mgmt/lang/unification"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
@@ -191,12 +194,12 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
// TODO: do the paths need to be cleaned for "../" before comparison? // TODO: do the paths need to be cleaned for "../" before comparison?
logf("lexing/parsing...") logf("lexing/parsing...")
ast, err := lang.LexParse(bytes.NewReader(output.Main)) xast, err := parser.LexParse(bytes.NewReader(output.Main))
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "could not generate AST") return nil, errwrap.Wrapf(err, "could not generate AST")
} }
if debug { if debug {
logf("behold, the AST: %+v", ast) logf("behold, the AST: %+v", xast)
} }
var downloader interfaces.Downloader var downloader interfaces.Downloader
@@ -239,16 +242,19 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
// init and validate the structure of the AST // init and validate the structure of the AST
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
Fs: localFs, // the local fs! Fs: localFs, // the local fs!
FsURI: localFs.URI(), // TODO: is this right? FsURI: localFs.URI(), // TODO: is this right?
Base: output.Base, // base dir (absolute path) that this is rooted in Base: output.Base, // base dir (absolute path) that this is rooted in
Files: output.Files, Files: output.Files,
Imports: importVertex, Imports: importVertex,
Metadata: output.Metadata, Metadata: output.Metadata,
Modules: modules, Modules: modules,
Downloader: downloader,
LexParser: parser.LexParse,
Downloader: downloader,
StrInterpolater: interpolate.InterpolateStr,
//World: obj.World, // TODO: do we need this? //World: obj.World, // TODO: do we need this?
Prefix: prefix, Prefix: prefix,
Debug: debug, Debug: debug,
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
@@ -257,25 +263,25 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
}, },
} }
// some of this might happen *after* interpolate in SetScope or Unify... // some of this might happen *after* interpolate in SetScope or Unify...
if err := ast.Init(data); err != nil { if err := xast.Init(data); err != nil {
return nil, errwrap.Wrapf(err, "could not init and validate AST") return nil, errwrap.Wrapf(err, "could not init and validate AST")
} }
logf("interpolating...") logf("interpolating...")
// interpolate strings and other expansionable nodes in AST // interpolate strings and other expansionable nodes in AST
interpolated, err := ast.Interpolate() interpolated, err := xast.Interpolate()
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "could not interpolate AST") return nil, errwrap.Wrapf(err, "could not interpolate AST")
} }
variables := map[string]interfaces.Expr{ variables := map[string]interfaces.Expr{
"purpleidea": &lang.ExprStr{V: "hello world!"}, // james says hi "purpleidea": &ast.ExprStr{V: "hello world!"}, // james says hi
// TODO: change to a func when we can change hostname dynamically! // TODO: change to a func when we can change hostname dynamically!
"hostname": &lang.ExprStr{V: ""}, // NOTE: empty b/c not used "hostname": &ast.ExprStr{V: ""}, // NOTE: empty b/c not used
} }
consts := lang.VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix! consts := ast.VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix!
addback := vars.ConstNamespace + interfaces.ModuleSep // add it back... addback := vars.ConstNamespace + interfaces.ModuleSep // add it back...
variables, err = lang.MergeExprMaps(variables, consts, addback) variables, err = ast.MergeExprMaps(variables, consts, addback)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "couldn't merge in consts") return nil, errwrap.Wrapf(err, "couldn't merge in consts")
} }
@@ -284,7 +290,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
scope := &interfaces.Scope{ scope := &interfaces.Scope{
Variables: variables, Variables: variables,
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: lang.FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix Functions: ast.FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
logf("building scope...") logf("building scope...")
@@ -316,7 +322,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
} }
// get the list of needed files (this is available after SetScope) // get the list of needed files (this is available after SetScope)
fileList, err := lang.CollectFiles(interpolated) fileList, err := ast.CollectFiles(interpolated)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "could not collect files") return nil, errwrap.Wrapf(err, "could not collect files")
} }
@@ -664,7 +670,7 @@ func (obj *GAPI) Get(getInfo *gapi.GetInfo) error {
// TODO: do the paths need to be cleaned for "../" before comparison? // TODO: do the paths need to be cleaned for "../" before comparison?
logf("lexing/parsing...") logf("lexing/parsing...")
ast, err := lang.LexParse(bytes.NewReader(output.Main)) ast, err := parser.LexParse(bytes.NewReader(output.Main))
if err != nil { if err != nil {
return errwrap.Wrapf(err, "could not generate AST") return errwrap.Wrapf(err, "could not generate AST")
} }
@@ -709,16 +715,19 @@ func (obj *GAPI) Get(getInfo *gapi.GetInfo) error {
// init and validate the structure of the AST // init and validate the structure of the AST
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
Fs: localFs, // the local fs! Fs: localFs, // the local fs!
FsURI: localFs.URI(), // TODO: is this right? FsURI: localFs.URI(), // TODO: is this right?
Base: output.Base, // base dir (absolute path) that this is rooted in Base: output.Base, // base dir (absolute path) that this is rooted in
Files: output.Files, Files: output.Files,
Imports: importVertex, Imports: importVertex,
Metadata: output.Metadata, Metadata: output.Metadata,
Modules: modules, Modules: modules,
Downloader: downloader,
LexParser: parser.LexParse,
Downloader: downloader,
StrInterpolater: interpolate.InterpolateStr,
//World: obj.World, // TODO: do we need this? //World: obj.World, // TODO: do we need this?
Prefix: prefix, Prefix: prefix,
Debug: debug, Debug: debug,
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {

View File

@@ -19,6 +19,7 @@ package interfaces
import ( import (
"fmt" "fmt"
"io"
"sort" "sort"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
@@ -186,6 +187,16 @@ type Data struct {
// deploys, however that is not blocked at the level of this interface. // deploys, however that is not blocked at the level of this interface.
Downloader Downloader Downloader Downloader
// LexParser is a function that needs to get passed in to run the lexer
// and parser to build the initial AST. This is passed in this way to
// avoid dependency cycles.
LexParser func(io.Reader) (Stmt, error)
// StrInterpolater is a function that needs to get passed in to run the
// string interpolation. This is passed in this way to avoid dependency
// cycles.
StrInterpolater func(string, *Pos, *Data) (Expr, error)
//World engine.World // TODO: do we need this? //World engine.World // TODO: do we need this?
// Prefix provides a unique path prefix that we can namespace in. It is // Prefix provides a unique path prefix that we can namespace in. It is

27
lang/interfaces/parser.go Normal file
View File

@@ -0,0 +1,27 @@
// Mgmt
// Copyright (C) 2013-2021+ 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 interfaces
// Pos represents a position in the code. This is used by the parser and string
// interpolation.
// TODO: consider expanding with range characteristics.
type Pos struct {
Line int // line number starting at 1
Column int // column number starting at 1
Filename string // optional source filename, if known
}

View File

@@ -15,13 +15,14 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package lang // TODO: move this into a sub package of lang/$name? package interpolate
import ( import (
"fmt" "fmt"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/interpolate"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
"github.com/hashicorp/hil" "github.com/hashicorp/hil"
@@ -35,16 +36,8 @@ const (
UseHilInterpolation = false UseHilInterpolation = false
) )
// Pos represents a position in the code.
// TODO: consider expanding with range characteristics.
type Pos struct {
Line int // line number starting at 1
Column int // column number starting at 1
Filename string // optional source filename, if known
}
// InterpolateStr interpolates a string and returns the representative AST. // InterpolateStr interpolates a string and returns the representative AST.
func InterpolateStr(str string, pos *Pos, data *interfaces.Data) (interfaces.Expr, error) { func InterpolateStr(str string, pos *interfaces.Pos, data *interfaces.Data) (interfaces.Expr, error) {
if data.Debug { if data.Debug {
data.Logf("interpolating: %s", str) data.Logf("interpolating: %s", str)
} }
@@ -57,8 +50,8 @@ func InterpolateStr(str string, pos *Pos, data *interfaces.Data) (interfaces.Exp
// InterpolateRagel interpolates a string and returns the representative AST. It // InterpolateRagel interpolates a string and returns the representative AST. It
// uses the ragel parser to perform the string interpolation. // uses the ragel parser to perform the string interpolation.
func InterpolateRagel(str string, pos *Pos, data *interfaces.Data) (interfaces.Expr, error) { func InterpolateRagel(str string, pos *interfaces.Pos, data *interfaces.Data) (interfaces.Expr, error) {
sequence, err := interpolate.Parse(str) sequence, err := Parse(str)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "parser failed") return nil, errwrap.Wrapf(err, "parser failed")
} }
@@ -67,14 +60,14 @@ func InterpolateRagel(str string, pos *Pos, data *interfaces.Data) (interfaces.E
for _, term := range sequence { for _, term := range sequence {
switch t := term.(type) { switch t := term.(type) {
case interpolate.Literal: case Literal:
expr := &ExprStr{ expr := &ast.ExprStr{
V: t.Value, V: t.Value,
} }
exprs = append(exprs, expr) exprs = append(exprs, expr)
case interpolate.Variable: case Variable:
expr := &ExprVar{ expr := &ast.ExprVar{
Name: t.Name, Name: t.Name,
} }
exprs = append(exprs, expr) exprs = append(exprs, expr)
@@ -85,7 +78,7 @@ func InterpolateRagel(str string, pos *Pos, data *interfaces.Data) (interfaces.E
// If we didn't find anything of value, we got an empty string... // If we didn't find anything of value, we got an empty string...
if len(sequence) == 0 && str == "" { // be doubly sure... if len(sequence) == 0 && str == "" { // be doubly sure...
expr := &ExprStr{ expr := &ast.ExprStr{
V: "", V: "",
} }
exprs = append(exprs, expr) exprs = append(exprs, expr)
@@ -108,7 +101,7 @@ func InterpolateRagel(str string, pos *Pos, data *interfaces.Data) (interfaces.E
// InterpolateHil interpolates a string and returns the representative AST. This // InterpolateHil interpolates a string and returns the representative AST. This
// particular implementation uses the hashicorp hil library and syntax to do so. // particular implementation uses the hashicorp hil library and syntax to do so.
func InterpolateHil(str string, pos *Pos, data *interfaces.Data) (interfaces.Expr, error) { func InterpolateHil(str string, pos *interfaces.Pos, data *interfaces.Data) (interfaces.Expr, error) {
var line, column int = -1, -1 var line, column int = -1, -1
var filename string var filename string
if pos != nil { if pos != nil {
@@ -132,15 +125,19 @@ func InterpolateHil(str string, pos *Pos, data *interfaces.Data) (interfaces.Exp
transformData := &interfaces.Data{ transformData := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
Fs: data.Fs, Fs: data.Fs,
FsURI: data.FsURI, FsURI: data.FsURI,
Base: data.Base, Base: data.Base,
Files: data.Files, Files: data.Files,
Imports: data.Imports, Imports: data.Imports,
Metadata: data.Metadata, Metadata: data.Metadata,
Modules: data.Modules, Modules: data.Modules,
Downloader: data.Downloader,
//World: data.World, LexParser: data.LexParser,
Downloader: data.Downloader,
StrInterpolater: data.StrInterpolater,
//World: data.World, // TODO: do we need this?
Prefix: data.Prefix, Prefix: data.Prefix,
Debug: data.Debug, Debug: data.Debug,
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
@@ -205,7 +202,7 @@ func hilTransform(root hilast.Node, data *interfaces.Data) (interfaces.Expr, err
args = append(args, arg) args = append(args, arg)
} }
return &ExprCall{ return &ast.ExprCall{
Name: node.Func, // name Name: node.Func, // name
Args: args, Args: args,
}, nil }, nil
@@ -217,23 +214,23 @@ func hilTransform(root hilast.Node, data *interfaces.Data) (interfaces.Expr, err
switch node.Typex { switch node.Typex {
case hilast.TypeBool: case hilast.TypeBool:
return &ExprBool{ return &ast.ExprBool{
V: node.Value.(bool), V: node.Value.(bool),
}, nil }, nil
case hilast.TypeString: case hilast.TypeString:
return &ExprStr{ return &ast.ExprStr{
V: node.Value.(string), V: node.Value.(string),
}, nil }, nil
case hilast.TypeInt: case hilast.TypeInt:
return &ExprInt{ return &ast.ExprInt{
// node.Value is an int stored as an interface // node.Value is an int stored as an interface
V: int64(node.Value.(int)), V: int64(node.Value.(int)),
}, nil }, nil
case hilast.TypeFloat: case hilast.TypeFloat:
return &ExprFloat{ return &ast.ExprFloat{
V: node.Value.(float64), V: node.Value.(float64),
}, nil }, nil
@@ -249,7 +246,7 @@ func hilTransform(root hilast.Node, data *interfaces.Data) (interfaces.Expr, err
if data.Debug { if data.Debug {
data.Logf("got variable access type: %+v", node) data.Logf("got variable access type: %+v", node)
} }
return &ExprVar{ return &ast.ExprVar{
Name: node.Name, Name: node.Name,
}, nil }, nil
@@ -284,7 +281,7 @@ func concatExprListIntoCall(exprs []interfaces.Expr) (interfaces.Expr, error) {
return nil, fmt.Errorf("empty list") return nil, fmt.Errorf("empty list")
} }
operator := &ExprStr{ operator := &ast.ExprStr{
V: "+", // for PLUS this is a `+` character V: "+", // for PLUS this is a `+` character
} }
@@ -293,11 +290,11 @@ func concatExprListIntoCall(exprs []interfaces.Expr) (interfaces.Expr, error) {
} }
//if len(exprs) == 1 { //if len(exprs) == 1 {
// arg := exprs[0] // arg := exprs[0]
// emptyStr := &ExprStr{ // emptyStr := &ast.ExprStr{
// V: "", // empty str // V: "", // empty str
// } // }
// return &ExprCall{ // return &ast.ExprCall{
// Name: operatorFuncName, // concatenate the two strings with + operator // Name: funcs.OperatorFuncName, // concatenate the two strings with + operator
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// operator, // operator first // operator, // operator first
// arg, // string arg // arg, // string arg
@@ -313,9 +310,9 @@ func concatExprListIntoCall(exprs []interfaces.Expr) (interfaces.Expr, error) {
return nil, err return nil, err
} }
return &ExprCall{ return &ast.ExprCall{
// NOTE: if we don't set the data field we need Init() called on it! // NOTE: if we don't set the data field we need Init() called on it!
Name: operatorFuncName, // concatenate the two strings with + operator Name: funcs.OperatorFuncName, // concatenate the two strings with + operator
Args: []interfaces.Expr{ Args: []interfaces.Expr{
operator, // operator first operator, // operator first
head, // string arg head, // string arg
@@ -333,7 +330,7 @@ func simplifyExprList(exprs []interfaces.Expr) ([]interfaces.Expr, error) {
for _, x := range exprs { for _, x := range exprs {
switch v := x.(type) { switch v := x.(type) {
case *ExprStr: case *ast.ExprStr:
if !last { if !last {
last = true last = true
result = append(result, x) result = append(result, x)
@@ -342,7 +339,7 @@ func simplifyExprList(exprs []interfaces.Expr) ([]interfaces.Expr, error) {
// combine! // combine!
expr := result[len(result)-1] // there has to be at least one expr := result[len(result)-1] // there has to be at least one
str, ok := expr.(*ExprStr) str, ok := expr.(*ast.ExprStr)
if !ok { if !ok {
// programming error // programming error
return nil, fmt.Errorf("unexpected type (%T)", expr) return nil, fmt.Errorf("unexpected type (%T)", expr)
@@ -351,7 +348,7 @@ func simplifyExprList(exprs []interfaces.Expr) ([]interfaces.Expr, error) {
//last = true // redundant, it's already true //last = true // redundant, it's already true
// ... and don't append, we've combined! // ... and don't append, we've combined!
case *ExprVar: case *ast.ExprVar:
last = false // the next one can't combine with me last = false // the next one can't combine with me
result = append(result, x) result = append(result, x)

View File

@@ -17,7 +17,7 @@
// +build !root // +build !root
package lang package interpolate
import ( import (
"fmt" "fmt"
@@ -25,7 +25,10 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/parser"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@@ -45,28 +48,28 @@ func TestInterpolate0(t *testing.T) {
// names, and then run `go test -run <pattern>` with the name(s) to run. // names, and then run `go test -run <pattern>` with the name(s) to run.
{ {
ast := &StmtProg{ xast := &ast.StmtProg{
Body: []interfaces.Stmt{}, Body: []interfaces.Stmt{},
} }
testCases = append(testCases, test{ // 0 testCases = append(testCases, test{ // 0
"nil", "nil",
``, ``,
false, false,
ast, xast,
}) })
} }
{ {
ast := &StmtProg{ xast := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: "foo", V: "foo",
}, },
}, },
@@ -82,33 +85,33 @@ func TestInterpolate0(t *testing.T) {
} }
`, `,
fail: false, fail: false,
ast: ast, ast: xast,
}) })
} }
{ {
fieldName := &ExprCall{ fieldName := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprStr{ &ast.ExprStr{
V: "foo-", V: "foo-",
}, },
&ExprVar{ &ast.ExprVar{
Name: "x", Name: "x",
}, },
}, },
} }
ast := &StmtProg{ xast := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: fieldName, Value: fieldName,
}, },
@@ -125,21 +128,21 @@ func TestInterpolate0(t *testing.T) {
} }
`, `,
fail: false, fail: false,
ast: ast, ast: xast,
}) })
} }
{ {
ast := &StmtProg{ xast := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: "${hello}", V: "${hello}",
}, },
}, },
@@ -155,21 +158,21 @@ func TestInterpolate0(t *testing.T) {
} }
`, `,
fail: false, fail: false,
ast: ast, ast: xast,
}) })
} }
{ {
ast := &StmtProg{ xast := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: `\` + `$` + `{hello}`, V: `\` + `$` + `{hello}`,
}, },
}, },
@@ -185,7 +188,7 @@ func TestInterpolate0(t *testing.T) {
} }
`, `,
fail: false, fail: false,
ast: ast, ast: xast,
}) })
} }
@@ -204,7 +207,7 @@ func TestInterpolate0(t *testing.T) {
code, fail, exp := tc.code, tc.fail, tc.ast code, fail, exp := tc.code, tc.fail, tc.ast
str := strings.NewReader(code) str := strings.NewReader(code)
ast, err := LexParse(str) ast, err := parser.LexParse(str)
if err != nil { if err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: lex/parse failed with: %+v", index, err) t.Errorf("test #%d: lex/parse failed with: %+v", index, err)
@@ -213,6 +216,9 @@ func TestInterpolate0(t *testing.T) {
t.Logf("test #%d: AST: %+v", index, ast) t.Logf("test #%d: AST: %+v", index, ast)
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed
StrInterpolater: InterpolateStr,
Debug: testing.Verbose(), // set via the -test.v flag to `go test` Debug: testing.Verbose(), // set via the -test.v flag to `go test`
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
t.Logf("ast: "+format, v...) t.Logf("ast: "+format, v...)
@@ -296,17 +302,17 @@ func TestInterpolateBasicStmt(t *testing.T) {
// }) // })
//} //}
{ {
ast := &StmtProg{ xast := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: "foo", V: "foo",
}, },
}, },
@@ -314,17 +320,17 @@ func TestInterpolateBasicStmt(t *testing.T) {
}, },
}, },
} }
exp := &StmtProg{ exp := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: "foo", V: "foo",
}, },
}, },
@@ -334,23 +340,23 @@ func TestInterpolateBasicStmt(t *testing.T) {
} }
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "basic resource", name: "basic resource",
ast: ast, ast: xast,
fail: false, fail: false,
exp: exp, exp: exp,
}) })
} }
{ {
ast := &StmtProg{ xast := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t${blah}", V: "t${blah}",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: "foo", V: "foo",
}, },
}, },
@@ -358,29 +364,29 @@ func TestInterpolateBasicStmt(t *testing.T) {
}, },
}, },
} }
resName := &ExprCall{ resName := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprStr{ &ast.ExprStr{
V: "t", V: "t",
}, },
&ExprVar{ &ast.ExprVar{
Name: "blah", Name: "blah",
}, },
}, },
} }
exp := &StmtProg{ exp := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: resName, Name: resName,
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: "foo", V: "foo",
}, },
}, },
@@ -390,23 +396,23 @@ func TestInterpolateBasicStmt(t *testing.T) {
} }
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "expanded resource", name: "expanded resource",
ast: ast, ast: xast,
fail: false, fail: false,
exp: exp, exp: exp,
}) })
} }
{ {
ast := &StmtProg{ xast := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t${42}", // incorrect type V: "t${42}", // incorrect type
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: "foo", V: "foo",
}, },
}, },
@@ -414,30 +420,30 @@ func TestInterpolateBasicStmt(t *testing.T) {
}, },
}, },
} }
resName := &ExprCall{ resName := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
// incorrect sig for this function, and now invalid interpolation // incorrect sig for this function, and now invalid interpolation
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprStr{ &ast.ExprStr{
V: "t", V: "t",
}, },
&ExprInt{ &ast.ExprInt{
V: 42, V: 42,
}, },
}, },
} }
exp := &StmtProg{ exp := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: resName, Name: resName,
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: &ExprStr{ Value: &ast.ExprStr{
V: "foo", V: "foo",
}, },
}, },
@@ -448,7 +454,7 @@ func TestInterpolateBasicStmt(t *testing.T) {
_ = exp // historical _ = exp // historical
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "expanded invalid resource name", name: "expanded invalid resource name",
ast: ast, ast: xast,
fail: true, fail: true,
//exp: exp, //exp: exp,
}) })
@@ -466,6 +472,8 @@ func TestInterpolateBasicStmt(t *testing.T) {
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
StrInterpolater: InterpolateStr,
Debug: testing.Verbose(), // set via the -test.v flag to `go test` Debug: testing.Verbose(), // set via the -test.v flag to `go test`
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
t.Logf("ast: "+format, v...) t.Logf("ast: "+format, v...)
@@ -535,51 +543,51 @@ func TestInterpolateBasicExpr(t *testing.T) {
// }) // })
//} //}
{ {
ast := &ExprStr{ xast := &ast.ExprStr{
V: "hello", V: "hello",
} }
exp := &ExprStr{ exp := &ast.ExprStr{
V: "hello", V: "hello",
} }
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "basic string", name: "basic string",
ast: ast, ast: xast,
fail: false, fail: false,
exp: exp, exp: exp,
}) })
} }
{ {
ast := &ExprStr{ xast := &ast.ExprStr{
V: "hello ${person_name}", V: "hello ${person_name}",
} }
exp := &ExprCall{ exp := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprStr{ &ast.ExprStr{
V: "hello ", V: "hello ",
}, },
&ExprVar{ &ast.ExprVar{
Name: "person_name", Name: "person_name",
}, },
}, },
} }
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "basic expansion", name: "basic expansion",
ast: ast, ast: xast,
fail: false, fail: false,
exp: exp, exp: exp,
}) })
} }
{ {
ast := &ExprStr{ xast := &ast.ExprStr{
V: "hello ${x ${y} z}", V: "hello ${x ${y} z}",
} }
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "invalid expansion", name: "invalid expansion",
ast: ast, ast: xast,
fail: true, fail: true,
}) })
} }
@@ -587,31 +595,31 @@ func TestInterpolateBasicExpr(t *testing.T) {
// library, but are not yet supported by our translation layer, nor do // library, but are not yet supported by our translation layer, nor do
// they necessarily work or make much sense at this point in time... // they necessarily work or make much sense at this point in time...
//{ //{
// ast := &ExprStr{ // xast := &ast.ExprStr{
// V: `hello ${func("hello ${var.foo}")}`, // V: `hello ${func("hello ${var.foo}")}`,
// } // }
// exp := nil // TODO: add this // exp := nil // TODO: add this
// testCases = append(testCases, test{ // testCases = append(testCases, test{
// name: "double expansion", // name: "double expansion",
// ast: ast, // ast: xast,
// fail: false, // fail: false,
// exp: exp, // exp: exp,
// }) // })
//} //}
{ {
ast := &ExprStr{ xast := &ast.ExprStr{
V: "sweetie${3.14159}", // invalid V: "sweetie${3.14159}", // invalid
} }
exp := &ExprCall{ exp := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprStr{ &ast.ExprStr{
V: "sweetie", V: "sweetie",
}, },
&ExprFloat{ &ast.ExprFloat{
V: 3.14159, V: 3.14159,
}, },
}, },
@@ -619,24 +627,24 @@ func TestInterpolateBasicExpr(t *testing.T) {
_ = exp // historical _ = exp // historical
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "float expansion", name: "float expansion",
ast: ast, ast: xast,
fail: true, fail: true,
}) })
} }
{ {
ast := &ExprStr{ xast := &ast.ExprStr{
V: "i am: ${sys.hostname()}", V: "i am: ${sys.hostname()}",
} }
exp := &ExprCall{ exp := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprStr{ &ast.ExprStr{
V: "i am: ", V: "i am: ",
}, },
&ExprCall{ &ast.ExprCall{
Name: "sys.hostname", Name: "sys.hostname",
Args: []interfaces.Expr{}, Args: []interfaces.Expr{},
}, },
@@ -645,30 +653,30 @@ func TestInterpolateBasicExpr(t *testing.T) {
_ = exp // historical _ = exp // historical
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "function expansion", name: "function expansion",
ast: ast, ast: xast,
fail: true, fail: true,
}) })
} }
{ {
ast := &ExprStr{ xast := &ast.ExprStr{
V: "i am: ${blah(21, 12.3)}", V: "i am: ${blah(21, 12.3)}",
} }
exp := &ExprCall{ exp := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprStr{ &ast.ExprStr{
V: "i am: ", V: "i am: ",
}, },
&ExprCall{ &ast.ExprCall{
Name: "blah", Name: "blah",
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprInt{ &ast.ExprInt{
V: 21, V: 21,
}, },
&ExprFloat{ &ast.ExprFloat{
V: 12.3, V: 12.3,
}, },
}, },
@@ -678,31 +686,31 @@ func TestInterpolateBasicExpr(t *testing.T) {
_ = exp // historical _ = exp // historical
testCases = append(testCases, test{ testCases = append(testCases, test{
name: "function expansion arg", name: "function expansion arg",
ast: ast, ast: xast,
fail: true, fail: true,
}) })
} }
// FIXME: i am broken, i don't deal well with negatives for some reason // FIXME: i am broken, i don't deal well with negatives for some reason
//{ //{
// ast := &ExprStr{ // xast := &ast.ExprStr{
// V: "i am: ${blah(21, -12.3)}", // V: "i am: ${blah(21, -12.3)}",
// } // }
// exp := &ExprCall{ // exp := &ast.ExprCall{
// Name: operatorFuncName, // Name: funcs.OperatorFuncName,
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// &ExprStr{ // &ast.ExprStr{
// V: "+", // V: "+",
// }, // },
// &ExprStr{ // &ast.ExprStr{
// V: "i am: ", // V: "i am: ",
// }, // },
// &ExprCall{ // &ast.ExprCall{
// Name: "blah", // Name: "blah",
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// &ExprInt{ // &ast.ExprInt{
// V: 21, // V: 21,
// }, // },
// &ExprFloat{ // &ast.ExprFloat{
// V: -12.3, // V: -12.3,
// }, // },
// }, // },
@@ -711,58 +719,58 @@ func TestInterpolateBasicExpr(t *testing.T) {
// } // }
// testCases = append(testCases, test{ // testCases = append(testCases, test{
// name: "function expansion arg negative", // name: "function expansion arg negative",
// ast: ast, // ast: xast,
// fail: false, // fail: false,
// exp: exp, // exp: exp,
// }) // })
//} //}
// FIXME: i am broken :( // FIXME: i am broken :(
//{ //{
// ast := &ExprStr{ // xast := &ast.ExprStr{
// V: "sweetie${-3.14159}", // FIXME: only the negative breaks this // V: "sweetie${-3.14159}", // FIXME: only the negative breaks this
// } // }
// exp := &ExprCall{ // exp := &ast.ExprCall{
// Name: operatorFuncName, // Name: funcs.OperatorFuncName,
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// &ExprStr{ // &ast.ExprStr{
// V: "+", // V: "+",
// }, // },
// &ExprStr{ // &ast.ExprStr{
// V: "sweetie", // V: "sweetie",
// }, // },
// &ExprFloat{ // &ast.ExprFloat{
// V: -3.14159, // V: -3.14159,
// }, // },
// }, // },
// } // }
// testCases = append(testCases, test{ // testCases = append(testCases, test{
// name: "negative float expansion", // name: "negative float expansion",
// ast: ast, // ast: xast,
// fail: false, // fail: false,
// exp: exp, // exp: exp,
// }) // })
//} //}
// FIXME: i am also broken, but less important // FIXME: i am also broken, but less important
//{ //{
// ast := &ExprStr{ // xast := &ast.ExprStr{
// V: `i am: ${blah(42, "${foo}")}`, // V: `i am: ${blah(42, "${foo}")}`,
// } // }
// exp := &ExprCall{ // exp := &ast.ExprCall{
// Name: operatorFuncName, // Name: funcs.OperatorFuncName,
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// &ExprStr{ // &ast.ExprStr{
// V: "+", // V: "+",
// }, // },
// &ExprStr{ // &ast.ExprStr{
// V: "i am: ", // V: "i am: ",
// }, // },
// &ExprCall{ // &ast.ExprCall{
// Name: "blah", // Name: "blah",
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// &ExprInt{ // &ast.ExprInt{
// V: 42, // V: 42,
// }, // },
// &ExprVar{ // &ast.ExprVar{
// Name: "foo", // Name: "foo",
// }, // },
// }, // },
@@ -771,7 +779,7 @@ func TestInterpolateBasicExpr(t *testing.T) {
// } // }
// testCases = append(testCases, test{ // testCases = append(testCases, test{
// name: "function expansion arg with var", // name: "function expansion arg with var",
// ast: ast, // ast: xast,
// fail: false, // fail: false,
// exp: exp, // exp: exp,
// }) // })
@@ -789,6 +797,8 @@ func TestInterpolateBasicExpr(t *testing.T) {
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
StrInterpolater: InterpolateStr,
Debug: testing.Verbose(), // set via the -test.v flag to `go test` Debug: testing.Verbose(), // set via the -test.v flag to `go test`
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
t.Logf("ast: "+format, v...) t.Logf("ast: "+format, v...)

View File

@@ -33,11 +33,14 @@ import (
"github.com/purpleidea/mgmt/engine/graph/autoedge" "github.com/purpleidea/mgmt/engine/graph/autoedge"
"github.com/purpleidea/mgmt/engine/resources" "github.com/purpleidea/mgmt/engine/resources"
"github.com/purpleidea/mgmt/etcd" "github.com/purpleidea/mgmt/etcd"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/funcs" "github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/funcs/vars" "github.com/purpleidea/mgmt/lang/funcs/vars"
"github.com/purpleidea/mgmt/lang/inputs" "github.com/purpleidea/mgmt/lang/inputs"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/interpolate"
"github.com/purpleidea/mgmt/lang/interpret" "github.com/purpleidea/mgmt/lang/interpret"
"github.com/purpleidea/mgmt/lang/parser"
"github.com/purpleidea/mgmt/lang/unification" "github.com/purpleidea/mgmt/lang/unification"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
@@ -83,11 +86,11 @@ func (obj *edge) String() string {
func TestAstFunc0(t *testing.T) { func TestAstFunc0(t *testing.T) {
scope := &interfaces.Scope{ // global scope scope := &interfaces.Scope{ // global scope
Variables: map[string]interfaces.Expr{ Variables: map[string]interfaces.Expr{
"hello": &ExprStr{V: "world"}, "hello": &ast.ExprStr{V: "world"},
"answer": &ExprInt{V: 42}, "answer": &ast.ExprInt{V: 42},
}, },
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix Functions: ast.FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
type test struct { // an individual test type test struct { // an individual test
@@ -190,7 +193,7 @@ func TestAstFunc0(t *testing.T) {
} }
{ {
graph, _ := pgraph.NewGraph("g") graph, _ := pgraph.NewGraph("g")
v1, v2, v3, v4, v5 := vtex(`str("t")`), vtex(`str("+")`), vtex("int(42)"), vtex("int(13)"), vtex(fmt.Sprintf(`call:%s(str("+"), int(42), int(13))`, operatorFuncName)) v1, v2, v3, v4, v5 := vtex(`str("t")`), vtex(`str("+")`), vtex("int(42)"), vtex("int(13)"), vtex(fmt.Sprintf(`call:%s(str("+"), int(42), int(13))`, funcs.OperatorFuncName))
graph.AddVertex(&v1, &v2, &v3, &v4, &v5) graph.AddVertex(&v1, &v2, &v3, &v4, &v5)
e1, e2, e3 := edge("op"), edge("a"), edge("b") e1, e2, e3 := edge("op"), edge("a"), edge("b")
graph.AddEdge(&v2, &v5, &e1) graph.AddEdge(&v2, &v5, &e1)
@@ -212,8 +215,8 @@ func TestAstFunc0(t *testing.T) {
graph, _ := pgraph.NewGraph("g") graph, _ := pgraph.NewGraph("g")
v1, v2, v3 := vtex(`str("t")`), vtex(`str("-")`), vtex(`str("+")`) v1, v2, v3 := vtex(`str("t")`), vtex(`str("-")`), vtex(`str("+")`)
v4, v5, v6 := vtex("int(42)"), vtex("int(13)"), vtex("int(99)") v4, v5, v6 := vtex("int(42)"), vtex("int(13)"), vtex("int(99)")
v7 := vtex(fmt.Sprintf(`call:%s(str("+"), int(42), int(13))`, operatorFuncName)) v7 := vtex(fmt.Sprintf(`call:%s(str("+"), int(42), int(13))`, funcs.OperatorFuncName))
v8 := vtex(fmt.Sprintf(`call:%s(str("-"), call:%s(str("+"), int(42), int(13)), int(99))`, operatorFuncName, operatorFuncName)) v8 := vtex(fmt.Sprintf(`call:%s(str("-"), call:%s(str("+"), int(42), int(13)), int(99))`, funcs.OperatorFuncName, funcs.OperatorFuncName))
graph.AddVertex(&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8) graph.AddVertex(&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8)
e1, e2, e3 := edge("op"), edge("a"), edge("b") e1, e2, e3 := edge("op"), edge("a"), edge("b")
@@ -242,7 +245,7 @@ func TestAstFunc0(t *testing.T) {
v1, v2 := vtex("bool(true)"), vtex(`str("t")`) v1, v2 := vtex("bool(true)"), vtex(`str("t")`)
v3, v4 := vtex("int(13)"), vtex("int(42)") v3, v4 := vtex("int(13)"), vtex("int(42)")
v5, v6 := vtex("var(i)"), vtex("var(x)") v5, v6 := vtex("var(i)"), vtex("var(x)")
v7, v8 := vtex(`str("+")`), vtex(fmt.Sprintf(`call:%s(str("+"), int(42), var(i))`, operatorFuncName)) v7, v8 := vtex(`str("+")`), vtex(fmt.Sprintf(`call:%s(str("+"), int(42), var(i))`, funcs.OperatorFuncName))
e1, e2, e3, e4, e5 := edge("op"), edge("a"), edge("b"), edge("var:i"), edge("var:x") e1, e2, e3, e4, e5 := edge("op"), edge("a"), edge("b"), edge("var:i"), edge("var:x")
graph.AddVertex(&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8) graph.AddVertex(&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8)
@@ -437,29 +440,31 @@ func TestAstFunc0(t *testing.T) {
t.Logf("\n\ntest #%d (%s) ----------------\n\n", index, name) t.Logf("\n\ntest #%d (%s) ----------------\n\n", index, name)
str := strings.NewReader(code) str := strings.NewReader(code)
ast, err := LexParse(str) xast, err := parser.LexParse(str)
if err != nil { if err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: lex/parse failed with: %+v", index, err) t.Errorf("test #%d: lex/parse failed with: %+v", index, err)
return return
} }
t.Logf("test #%d: AST: %+v", index, ast) t.Logf("test #%d: AST: %+v", index, xast)
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
StrInterpolater: interpolate.InterpolateStr,
Debug: testing.Verbose(), // set via the -test.v flag to `go test` Debug: testing.Verbose(), // set via the -test.v flag to `go test`
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
t.Logf("ast: "+format, v...) t.Logf("ast: "+format, v...)
}, },
} }
// some of this might happen *after* interpolate in SetScope or Unify... // some of this might happen *after* interpolate in SetScope or Unify...
if err := ast.Init(data); err != nil { if err := xast.Init(data); err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: could not init and validate AST: %+v", index, err) t.Errorf("test #%d: could not init and validate AST: %+v", index, err)
return return
} }
iast, err := ast.Interpolate() iast, err := xast.Interpolate()
if err != nil { if err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: interpolate failed with: %+v", index, err) t.Errorf("test #%d: interpolate failed with: %+v", index, err)
@@ -562,13 +567,13 @@ func TestAstFunc1(t *testing.T) {
t.Logf("tests directory is: %s", dir) t.Logf("tests directory is: %s", dir)
variables := map[string]interfaces.Expr{ variables := map[string]interfaces.Expr{
"purpleidea": &ExprStr{V: "hello world!"}, // james says hi "purpleidea": &ast.ExprStr{V: "hello world!"}, // james says hi
// TODO: change to a func when we can change hostname dynamically! // TODO: change to a func when we can change hostname dynamically!
"hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used "hostname": &ast.ExprStr{V: ""}, // NOTE: empty b/c not used
} }
consts := VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix! consts := ast.VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix!
addback := vars.ConstNamespace + interfaces.ModuleSep // add it back... addback := vars.ConstNamespace + interfaces.ModuleSep // add it back...
variables, err = MergeExprMaps(variables, consts, addback) variables, err = ast.MergeExprMaps(variables, consts, addback)
if err != nil { if err != nil {
t.Errorf("couldn't merge in consts: %+v", err) t.Errorf("couldn't merge in consts: %+v", err)
return return
@@ -577,7 +582,7 @@ func TestAstFunc1(t *testing.T) {
scope := &interfaces.Scope{ // global scope scope := &interfaces.Scope{ // global scope
Variables: variables, Variables: variables,
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix Functions: ast.FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
type errs struct { type errs struct {
@@ -778,7 +783,7 @@ func TestAstFunc1(t *testing.T) {
logf("main:\n%s", output.Main) // debug logf("main:\n%s", output.Main) // debug
reader := bytes.NewReader(output.Main) reader := bytes.NewReader(output.Main)
ast, err := LexParse(reader) xast, err := parser.LexParse(reader)
if (!fail || !failLexParse) && err != nil { if (!fail || !failLexParse) && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: lex/parse failed with: %+v", index, err) t.Errorf("test #%d: lex/parse failed with: %+v", index, err)
@@ -800,7 +805,7 @@ func TestAstFunc1(t *testing.T) {
return return
} }
t.Logf("test #%d: AST: %+v", index, ast) t.Logf("test #%d: AST: %+v", index, xast)
importGraph, err := pgraph.NewGraph("importGraph") importGraph, err := pgraph.NewGraph("importGraph")
if err != nil { if err != nil {
@@ -824,13 +829,16 @@ func TestAstFunc1(t *testing.T) {
Metadata: output.Metadata, Metadata: output.Metadata,
Modules: "/" + interfaces.ModuleDirectory, // not really needed here afaict Modules: "/" + interfaces.ModuleDirectory, // not really needed here afaict
LexParser: parser.LexParse,
StrInterpolater: interpolate.InterpolateStr,
Debug: testing.Verbose(), // set via the -test.v flag to `go test` Debug: testing.Verbose(), // set via the -test.v flag to `go test`
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
logf("ast: "+format, v...) logf("ast: "+format, v...)
}, },
} }
// some of this might happen *after* interpolate in SetScope or Unify... // some of this might happen *after* interpolate in SetScope or Unify...
err = ast.Init(data) err = xast.Init(data)
if (!fail || !failInit) && err != nil { if (!fail || !failInit) && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: could not init and validate AST: %+v", index, err) t.Errorf("test #%d: could not init and validate AST: %+v", index, err)
@@ -852,7 +860,7 @@ func TestAstFunc1(t *testing.T) {
return return
} }
iast, err := ast.Interpolate() iast, err := xast.Interpolate()
if err != nil { if err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: interpolate failed with: %+v", index, err) t.Errorf("test #%d: interpolate failed with: %+v", index, err)
@@ -1017,13 +1025,13 @@ func TestAstFunc2(t *testing.T) {
t.Logf("tests directory is: %s", dir) t.Logf("tests directory is: %s", dir)
variables := map[string]interfaces.Expr{ variables := map[string]interfaces.Expr{
"purpleidea": &ExprStr{V: "hello world!"}, // james says hi "purpleidea": &ast.ExprStr{V: "hello world!"}, // james says hi
// TODO: change to a func when we can change hostname dynamically! // TODO: change to a func when we can change hostname dynamically!
"hostname": &ExprStr{V: ""}, // NOTE: empty b/c not used "hostname": &ast.ExprStr{V: ""}, // NOTE: empty b/c not used
} }
consts := VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix! consts := ast.VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix!
addback := vars.ConstNamespace + interfaces.ModuleSep // add it back... addback := vars.ConstNamespace + interfaces.ModuleSep // add it back...
variables, err = MergeExprMaps(variables, consts, addback) variables, err = ast.MergeExprMaps(variables, consts, addback)
if err != nil { if err != nil {
t.Errorf("couldn't merge in consts: %+v", err) t.Errorf("couldn't merge in consts: %+v", err)
return return
@@ -1032,7 +1040,7 @@ func TestAstFunc2(t *testing.T) {
scope := &interfaces.Scope{ // global scope scope := &interfaces.Scope{ // global scope
Variables: variables, Variables: variables,
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix Functions: ast.FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
type errs struct { type errs struct {
@@ -1274,7 +1282,7 @@ func TestAstFunc2(t *testing.T) {
logf("main:\n%s", output.Main) // debug logf("main:\n%s", output.Main) // debug
reader := bytes.NewReader(output.Main) reader := bytes.NewReader(output.Main)
ast, err := LexParse(reader) xast, err := parser.LexParse(reader)
if (!fail || !failLexParse) && err != nil { if (!fail || !failLexParse) && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: lex/parse failed with: %+v", index, err) t.Errorf("test #%d: lex/parse failed with: %+v", index, err)
@@ -1296,7 +1304,7 @@ func TestAstFunc2(t *testing.T) {
return return
} }
t.Logf("test #%d: AST: %+v", index, ast) t.Logf("test #%d: AST: %+v", index, xast)
importGraph, err := pgraph.NewGraph("importGraph") importGraph, err := pgraph.NewGraph("importGraph")
if err != nil { if err != nil {
@@ -1320,13 +1328,16 @@ func TestAstFunc2(t *testing.T) {
Metadata: output.Metadata, Metadata: output.Metadata,
Modules: "/" + interfaces.ModuleDirectory, // not really needed here afaict Modules: "/" + interfaces.ModuleDirectory, // not really needed here afaict
LexParser: parser.LexParse,
StrInterpolater: interpolate.InterpolateStr,
Debug: testing.Verbose(), // set via the -test.v flag to `go test` Debug: testing.Verbose(), // set via the -test.v flag to `go test`
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
logf("ast: "+format, v...) logf("ast: "+format, v...)
}, },
} }
// some of this might happen *after* interpolate in SetScope or Unify... // some of this might happen *after* interpolate in SetScope or Unify...
err = ast.Init(data) err = xast.Init(data)
if (!fail || !failInit) && err != nil { if (!fail || !failInit) && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: could not init and validate AST: %+v", index, err) t.Errorf("test #%d: could not init and validate AST: %+v", index, err)
@@ -1348,7 +1359,7 @@ func TestAstFunc2(t *testing.T) {
return return
} }
iast, err := ast.Interpolate() iast, err := xast.Interpolate()
if (!fail || !failInterpolate) && err != nil { if (!fail || !failInterpolate) && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: Interpolate failed with: %+v", index, err) t.Errorf("test #%d: Interpolate failed with: %+v", index, err)
@@ -1806,12 +1817,12 @@ func TestAstInterpret0(t *testing.T) {
t.Logf("\n\ntest #%d (%s) ----------------\n\n", index, name) t.Logf("\n\ntest #%d (%s) ----------------\n\n", index, name)
str := strings.NewReader(code) str := strings.NewReader(code)
ast, err := LexParse(str) xast, err := parser.LexParse(str)
if err != nil { if err != nil {
t.Errorf("test #%d: lex/parse failed with: %+v", index, err) t.Errorf("test #%d: lex/parse failed with: %+v", index, err)
continue continue
} }
t.Logf("test #%d: AST: %+v", index, ast) t.Logf("test #%d: AST: %+v", index, xast)
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
@@ -1821,7 +1832,7 @@ func TestAstInterpret0(t *testing.T) {
}, },
} }
// some of this might happen *after* interpolate in SetScope or Unify... // some of this might happen *after* interpolate in SetScope or Unify...
if err := ast.Init(data); err != nil { if err := xast.Init(data); err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: could not init and validate AST: %+v", index, err) t.Errorf("test #%d: could not init and validate AST: %+v", index, err)
return return
@@ -1831,7 +1842,7 @@ func TestAstInterpret0(t *testing.T) {
// perform type unification, run the function graph engine, and // perform type unification, run the function graph engine, and
// only gives you limited results... don't expect normal code to // only gives you limited results... don't expect normal code to
// run and produce meaningful things in this test... // run and produce meaningful things in this test...
graph, err := interpret.Interpret(ast) graph, err := interpret.Interpret(xast)
if !fail && err != nil { if !fail && err != nil {
t.Errorf("test #%d: interpret failed with: %+v", index, err) t.Errorf("test #%d: interpret failed with: %+v", index, err)

View File

@@ -23,25 +23,21 @@ import (
"sync" "sync"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/funcs" "github.com/purpleidea/mgmt/lang/funcs"
_ "github.com/purpleidea/mgmt/lang/funcs/core" // import so the funcs register _ "github.com/purpleidea/mgmt/lang/funcs/core" // import so the funcs register
"github.com/purpleidea/mgmt/lang/funcs/vars" "github.com/purpleidea/mgmt/lang/funcs/vars"
"github.com/purpleidea/mgmt/lang/inputs" "github.com/purpleidea/mgmt/lang/inputs"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/interpolate"
"github.com/purpleidea/mgmt/lang/interpret" "github.com/purpleidea/mgmt/lang/interpret"
"github.com/purpleidea/mgmt/lang/parser"
"github.com/purpleidea/mgmt/lang/unification" "github.com/purpleidea/mgmt/lang/unification"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
) )
const (
// make these available internally without requiring the import
operatorFuncName = funcs.OperatorFuncName
historyFuncName = funcs.HistoryFuncName
containsFuncName = funcs.ContainsFuncName
)
// Lang is the main language lexer/parser object. // Lang is the main language lexer/parser object.
type Lang struct { type Lang struct {
Fs engine.Fs // connected fs where input dir or metadata exists Fs engine.Fs // connected fs where input dir or metadata exists
@@ -117,12 +113,12 @@ func (obj *Lang) Init() error {
// run the lexer/parser and build an AST // run the lexer/parser and build an AST
obj.Logf("lexing/parsing...") obj.Logf("lexing/parsing...")
// this reads an io.Reader, which might be a stream of multiple files... // this reads an io.Reader, which might be a stream of multiple files...
ast, err := LexParse(reader) xast, err := parser.LexParse(reader)
if err != nil { if err != nil {
return errwrap.Wrapf(err, "could not generate AST") return errwrap.Wrapf(err, "could not generate AST")
} }
if obj.Debug { if obj.Debug {
obj.Logf("behold, the AST: %+v", ast) obj.Logf("behold, the AST: %+v", xast)
} }
importGraph, err := pgraph.NewGraph("importGraph") importGraph, err := pgraph.NewGraph("importGraph")
@@ -147,7 +143,11 @@ func (obj *Lang) Init() error {
Metadata: output.Metadata, Metadata: output.Metadata,
Modules: "/" + interfaces.ModuleDirectory, // do not set from env for a deploy! Modules: "/" + interfaces.ModuleDirectory, // do not set from env for a deploy!
LexParser: parser.LexParse,
Downloader: nil, // XXX: is this used here?
StrInterpolater: interpolate.InterpolateStr,
//World: obj.World, // TODO: do we need this? //World: obj.World, // TODO: do we need this?
Prefix: obj.Prefix, Prefix: obj.Prefix,
Debug: obj.Debug, Debug: obj.Debug,
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
@@ -156,26 +156,26 @@ func (obj *Lang) Init() error {
}, },
} }
// some of this might happen *after* interpolate in SetScope or Unify... // some of this might happen *after* interpolate in SetScope or Unify...
if err := ast.Init(data); err != nil { if err := xast.Init(data); err != nil {
return errwrap.Wrapf(err, "could not init and validate AST") return errwrap.Wrapf(err, "could not init and validate AST")
} }
obj.Logf("interpolating...") obj.Logf("interpolating...")
// interpolate strings and other expansionable nodes in AST // interpolate strings and other expansionable nodes in AST
interpolated, err := ast.Interpolate() interpolated, err := xast.Interpolate()
if err != nil { if err != nil {
return errwrap.Wrapf(err, "could not interpolate AST") return errwrap.Wrapf(err, "could not interpolate AST")
} }
obj.ast = interpolated obj.ast = interpolated
variables := map[string]interfaces.Expr{ variables := map[string]interfaces.Expr{
"purpleidea": &ExprStr{V: "hello world!"}, // james says hi "purpleidea": &ast.ExprStr{V: "hello world!"}, // james says hi
// TODO: change to a func when we can change hostname dynamically! // TODO: change to a func when we can change hostname dynamically!
"hostname": &ExprStr{V: obj.Hostname}, "hostname": &ast.ExprStr{V: obj.Hostname},
} }
consts := VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix! consts := ast.VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix!
addback := vars.ConstNamespace + interfaces.ModuleSep // add it back... addback := vars.ConstNamespace + interfaces.ModuleSep // add it back...
variables, err = MergeExprMaps(variables, consts, addback) variables, err = ast.MergeExprMaps(variables, consts, addback)
if err != nil { if err != nil {
return errwrap.Wrapf(err, "couldn't merge in consts") return errwrap.Wrapf(err, "couldn't merge in consts")
} }
@@ -184,7 +184,7 @@ func (obj *Lang) Init() error {
scope := &interfaces.Scope{ scope := &interfaces.Scope{
Variables: variables, Variables: variables,
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix Functions: ast.FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
obj.Logf("building scope...") obj.Logf("building scope...")

View File

@@ -402,7 +402,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package lang package parser
import ( import (
"fmt" "fmt"

View File

@@ -15,13 +15,12 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package lang // TODO: move this into a sub package of lang/$name? package parser
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"net/url"
"path" "path"
"sort" "sort"
"strings" "strings"
@@ -32,17 +31,6 @@ import (
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
) )
const (
// ModuleMagicPrefix is the prefix which, if found as a prefix to the
// last token in an import path, will be removed silently if there are
// remaining characters following the name. If this is the empty string
// then it will be ignored.
ModuleMagicPrefix = "mgmt-"
// CoreDir is the directory prefix where core bindata mcl code is added.
CoreDir = "core/"
)
// These constants represent the different possible lexer/parser errors. // These constants represent the different possible lexer/parser errors.
const ( const (
ErrLexerUnrecognized = interfaces.Error("unrecognized") ErrLexerUnrecognized = interfaces.Error("unrecognized")
@@ -236,144 +224,3 @@ func DirectoryReader(fs engine.Fs, dir string) (io.Reader, map[uint64]string, er
return io.MultiReader(readers...), offsets, nil return io.MultiReader(readers...), offsets, nil
} }
// ParseImportName parses an import name and returns the default namespace name
// that should be used with it. For example, if the import name was:
// "git://example.com/purpleidea/Module-Name", this might return an alias of
// "module_name". It also returns a bunch of other data about the parsed import.
// TODO: check for invalid or unwanted special characters
func ParseImportName(name string) (*interfaces.ImportData, error) {
magicPrefix := ModuleMagicPrefix
if name == "" {
return nil, fmt.Errorf("empty name")
}
if strings.HasPrefix(name, "/") {
return nil, fmt.Errorf("absolute paths are not allowed")
}
u, err := url.Parse(name)
if err != nil {
return nil, errwrap.Wrapf(err, "name is not a valid url")
}
if u.Path == "" {
return nil, fmt.Errorf("empty path")
}
p := u.Path
// catch bad paths like: git:////home/james/ (note the quad slash!)
// don't penalize if we have a dir with a trailing slash at the end
if s := path.Clean(u.Path); u.Path != s && u.Path != s+"/" {
// TODO: are there any cases where this is not what we want?
return nil, fmt.Errorf("dirty path, cleaned it's: `%s`", s)
}
for strings.HasSuffix(p, "/") { // remove trailing slashes
p = p[:len(p)-len("/")]
}
split := strings.Split(p, "/") // take last chunk if slash separated
s := split[0]
if len(split) > 1 {
s = split[len(split)-1] // pick last chunk
}
// TODO: should we treat a special name: "purpleidea/mgmt-foo" as "foo"?
if magicPrefix != "" && strings.HasPrefix(s, magicPrefix) && len(s) > len(magicPrefix) {
s = s[len(magicPrefix):]
}
s = strings.Replace(s, "-", "_", -1) // XXX: allow underscores in IDENTIFIER
if strings.HasPrefix(s, "_") || strings.HasSuffix(s, "_") {
return nil, fmt.Errorf("name can't begin or end with dash or underscore")
}
alias := strings.ToLower(s)
// if this is a local import, it's a straight directory path
// if it's an fqdn import, it should contain a metadata file
// if there's no protocol prefix, then this must be a local path
isLocal := u.Scheme == ""
// if it has a trailing slash or .mcl extension it's not a system import
isSystem := isLocal && !strings.HasSuffix(u.Path, "/") && !strings.HasSuffix(u.Path, interfaces.DotFileNameExtension)
// is it a local file?
isFile := !isSystem && isLocal && strings.HasSuffix(u.Path, interfaces.DotFileNameExtension)
xpath := u.Path // magic path
if isSystem {
xpath = ""
}
if !isLocal {
host := u.Host // host or host:port
split := strings.Split(host, ":")
if l := len(split); l == 1 || l == 2 {
host = split[0]
} else {
return nil, fmt.Errorf("incorrect number of colons (%d) in hostname", l)
}
xpath = path.Join(host, xpath)
}
if !isLocal && !strings.HasSuffix(xpath, "/") {
xpath = xpath + "/"
}
// we're a git repo with a local path instead of an fqdn over http!
// this still counts as isLocal == false, since it's still a remote
if u.Host == "" && strings.HasPrefix(u.Path, "/") {
xpath = strings.TrimPrefix(xpath, "/") // make it a relative dir
}
if strings.HasPrefix(xpath, "/") { // safety check (programming error?)
return nil, fmt.Errorf("can't parse strange import")
}
// build a url to clone from if we're not local...
// TODO: consider adding some logic that is similar to the logic in:
// https://github.com/golang/go/blob/054640b54df68789d9df0e50575d21d9dbffe99f/src/cmd/go/internal/get/vcs.go#L972
// so that we can more correctly figure out the correct url to clone...
xurl := ""
if !isLocal {
u.Fragment = ""
// TODO: maybe look for ?sha1=... or ?tag=... to pick a real ref
u.RawQuery = ""
u.ForceQuery = false
xurl = u.String()
}
// if u.Path is local file like: foo/server.mcl alias should be "server"
// we should trim the alias to remove the .mcl (the dir is already gone)
if isFile && strings.HasSuffix(alias, interfaces.DotFileNameExtension) {
alias = strings.TrimSuffix(alias, interfaces.DotFileNameExtension)
}
return &interfaces.ImportData{
Name: name, // save the original value here
Alias: alias,
IsSystem: isSystem,
IsLocal: isLocal,
IsFile: isFile,
Path: xpath,
URL: xurl,
}, nil
}
// CollectFiles collects all the files used in the AST. You will see more files
// based on how many compiling steps have run. In general, this is useful for
// collecting all the files needed to store in our file system for a deploy.
func CollectFiles(ast interfaces.Stmt) ([]string, error) {
// collect the list of files
fileList := []string{}
fn := func(node interfaces.Node) error {
// redundant check for example purposes
stmt, ok := node.(interfaces.Stmt)
if !ok {
return nil
}
prog, ok := stmt.(*StmtProg)
if !ok {
return nil
}
// collect into global
fileList = append(fileList, prog.importFiles...)
return nil
}
if err := ast.Apply(fn); err != nil {
return nil, errwrap.Wrapf(err, "can't retrieve paths")
}
return fileList, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,12 +16,14 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
%{ %{
package lang package parser
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types" "github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
@@ -56,22 +58,22 @@ func init() {
exprs []interfaces.Expr exprs []interfaces.Expr
expr interfaces.Expr expr interfaces.Expr
mapKVs []*ExprMapKV mapKVs []*ast.ExprMapKV
mapKV *ExprMapKV mapKV *ast.ExprMapKV
structFields []*ExprStructField structFields []*ast.ExprStructField
structField *ExprStructField structField *ast.ExprStructField
args []*interfaces.Arg args []*interfaces.Arg
arg *interfaces.Arg arg *interfaces.Arg
resContents []StmtResContents // interface resContents []ast.StmtResContents // interface
resField *StmtResField resField *ast.StmtResField
resEdge *StmtResEdge resEdge *ast.StmtResEdge
resMeta *StmtResMeta resMeta *ast.StmtResMeta
edgeHalfList []*StmtEdgeHalf edgeHalfList []*ast.StmtEdgeHalf
edgeHalf *StmtEdgeHalf edgeHalf *ast.StmtEdgeHalf
} }
%token OPEN_CURLY CLOSE_CURLY %token OPEN_CURLY CLOSE_CURLY
@@ -133,7 +135,7 @@ prog:
/* end of list */ /* end of list */
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtProg{ $$.stmt = &ast.StmtProg{
Body: []interfaces.Stmt{}, Body: []interfaces.Stmt{},
} }
} }
@@ -141,12 +143,12 @@ prog:
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
// TODO: should we just skip comments for now? // TODO: should we just skip comments for now?
//if _, ok := $2.stmt.(*StmtComment); !ok { //if _, ok := $2.stmt.(*ast.StmtComment); !ok {
//} //}
if stmt, ok := $1.stmt.(*StmtProg); ok { if stmt, ok := $1.stmt.(*ast.StmtProg); ok {
stmts := stmt.Body stmts := stmt.Body
stmts = append(stmts, $2.stmt) stmts = append(stmts, $2.stmt)
$$.stmt = &StmtProg{ $$.stmt = &ast.StmtProg{
Body: stmts, Body: stmts,
} }
} }
@@ -156,7 +158,7 @@ stmt:
COMMENT COMMENT
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtComment{ $$.stmt = &ast.StmtComment{
Value: $1.str, Value: $1.str,
} }
} }
@@ -178,7 +180,7 @@ stmt:
| IF expr OPEN_CURLY prog CLOSE_CURLY | IF expr OPEN_CURLY prog CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtIf{ $$.stmt = &ast.StmtIf{
Condition: $2.expr, Condition: $2.expr,
ThenBranch: $4.stmt, ThenBranch: $4.stmt,
//ElseBranch: nil, //ElseBranch: nil,
@@ -187,7 +189,7 @@ stmt:
| IF expr OPEN_CURLY prog CLOSE_CURLY ELSE OPEN_CURLY prog CLOSE_CURLY | IF expr OPEN_CURLY prog CLOSE_CURLY ELSE OPEN_CURLY prog CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtIf{ $$.stmt = &ast.StmtIf{
Condition: $2.expr, Condition: $2.expr,
ThenBranch: $4.stmt, ThenBranch: $4.stmt,
ElseBranch: $8.stmt, ElseBranch: $8.stmt,
@@ -200,9 +202,9 @@ stmt:
| FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY | FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtFunc{ $$.stmt = &ast.StmtFunc{
Name: $2.str, Name: $2.str,
Func: &ExprFunc{ Func: &ast.ExprFunc{
Args: $4.args, Args: $4.args,
//Return: nil, //Return: nil,
Body: $7.expr, Body: $7.expr,
@@ -213,7 +215,7 @@ stmt:
| FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY | FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
fn := &ExprFunc{ fn := &ast.ExprFunc{
Args: $4.args, Args: $4.args,
Return: $6.typ, // return type is known Return: $6.typ, // return type is known
Body: $8.expr, Body: $8.expr,
@@ -242,7 +244,7 @@ stmt:
yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err)) yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err))
} }
} }
$$.stmt = &StmtFunc{ $$.stmt = &ast.StmtFunc{
Name: $2.str, Name: $2.str,
Func: fn, Func: fn,
} }
@@ -251,7 +253,7 @@ stmt:
| CLASS_IDENTIFIER IDENTIFIER OPEN_CURLY prog CLOSE_CURLY | CLASS_IDENTIFIER IDENTIFIER OPEN_CURLY prog CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtClass{ $$.stmt = &ast.StmtClass{
Name: $2.str, Name: $2.str,
Args: nil, Args: nil,
Body: $4.stmt, Body: $4.stmt,
@@ -262,7 +264,7 @@ stmt:
| CLASS_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY prog CLOSE_CURLY | CLASS_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY prog CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtClass{ $$.stmt = &ast.StmtClass{
Name: $2.str, Name: $2.str,
Args: $4.args, Args: $4.args,
Body: $7.stmt, Body: $7.stmt,
@@ -272,7 +274,7 @@ stmt:
| INCLUDE_IDENTIFIER dotted_identifier | INCLUDE_IDENTIFIER dotted_identifier
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtInclude{ $$.stmt = &ast.StmtInclude{
Name: $2.str, Name: $2.str,
} }
} }
@@ -280,7 +282,7 @@ stmt:
| INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN | INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtInclude{ $$.stmt = &ast.StmtInclude{
Name: $2.str, Name: $2.str,
Args: $4.exprs, Args: $4.exprs,
} }
@@ -289,7 +291,7 @@ stmt:
| IMPORT_IDENTIFIER STRING | IMPORT_IDENTIFIER STRING
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtImport{ $$.stmt = &ast.StmtImport{
Name: $2.str, Name: $2.str,
//Alias: "", //Alias: "",
} }
@@ -298,7 +300,7 @@ stmt:
| IMPORT_IDENTIFIER STRING AS_IDENTIFIER IDENTIFIER | IMPORT_IDENTIFIER STRING AS_IDENTIFIER IDENTIFIER
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtImport{ $$.stmt = &ast.StmtImport{
Name: $2.str, Name: $2.str,
Alias: $4.str, Alias: $4.str,
} }
@@ -307,7 +309,7 @@ stmt:
| IMPORT_IDENTIFIER STRING AS_IDENTIFIER MULTIPLY | IMPORT_IDENTIFIER STRING AS_IDENTIFIER MULTIPLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtImport{ $$.stmt = &ast.StmtImport{
Name: $2.str, Name: $2.str,
Alias: $4.str, Alias: $4.str,
} }
@@ -325,28 +327,28 @@ expr:
BOOL BOOL
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprBool{ $$.expr = &ast.ExprBool{
V: $1.bool, V: $1.bool,
} }
} }
| STRING | STRING
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprStr{ $$.expr = &ast.ExprStr{
V: $1.str, V: $1.str,
} }
} }
| INTEGER | INTEGER
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprInt{ $$.expr = &ast.ExprInt{
V: $1.int, V: $1.int,
} }
} }
| FLOAT | FLOAT
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprFloat{ $$.expr = &ast.ExprFloat{
V: $1.float, V: $1.float,
} }
} }
@@ -389,7 +391,7 @@ expr:
| IF expr OPEN_CURLY expr CLOSE_CURLY ELSE OPEN_CURLY expr CLOSE_CURLY | IF expr OPEN_CURLY expr CLOSE_CURLY ELSE OPEN_CURLY expr CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprIf{ $$.expr = &ast.ExprIf{
Condition: $2.expr, Condition: $2.expr,
ThenBranch: $4.expr, ThenBranch: $4.expr,
ElseBranch: $8.expr, ElseBranch: $8.expr,
@@ -407,7 +409,7 @@ list:
OPEN_BRACK list_elements CLOSE_BRACK OPEN_BRACK list_elements CLOSE_BRACK
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprList{ $$.expr = &ast.ExprList{
Elements: $2.exprs, Elements: $2.exprs,
} }
} }
@@ -436,7 +438,7 @@ map:
OPEN_CURLY map_kvs CLOSE_CURLY OPEN_CURLY map_kvs CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprMap{ $$.expr = &ast.ExprMap{
KVs: $2.mapKVs, KVs: $2.mapKVs,
} }
} }
@@ -445,7 +447,7 @@ map_kvs:
/* end of list */ /* end of list */
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.mapKVs = []*ExprMapKV{} $$.mapKVs = []*ast.ExprMapKV{}
} }
| map_kvs map_kv | map_kvs map_kv
{ {
@@ -457,7 +459,7 @@ map_kv:
expr ROCKET expr COMMA expr ROCKET expr COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.mapKV = &ExprMapKV{ $$.mapKV = &ast.ExprMapKV{
Key: $1.expr, Key: $1.expr,
Val: $3.expr, Val: $3.expr,
} }
@@ -468,7 +470,7 @@ struct:
STRUCT_IDENTIFIER OPEN_CURLY struct_fields CLOSE_CURLY STRUCT_IDENTIFIER OPEN_CURLY struct_fields CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprStruct{ $$.expr = &ast.ExprStruct{
Fields: $3.structFields, Fields: $3.structFields,
} }
} }
@@ -477,7 +479,7 @@ struct_fields:
/* end of list */ /* end of list */
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.structFields = []*ExprStructField{} $$.structFields = []*ast.ExprStructField{}
} }
| struct_fields struct_field | struct_fields struct_field
{ {
@@ -489,7 +491,7 @@ struct_field:
IDENTIFIER ROCKET expr COMMA IDENTIFIER ROCKET expr COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.structField = &ExprStructField{ $$.structField = &ast.ExprStructField{
Name: $1.str, Name: $1.str,
Value: $3.expr, Value: $3.expr,
} }
@@ -500,7 +502,7 @@ call:
dotted_identifier OPEN_PAREN call_args CLOSE_PAREN dotted_identifier OPEN_PAREN call_args CLOSE_PAREN
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: $1.str, Name: $1.str,
Args: $3.exprs, Args: $3.exprs,
//Var: false, // default //Var: false, // default
@@ -511,7 +513,7 @@ call:
| VAR_IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN | VAR_IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: $1.str, Name: $1.str,
Args: $3.exprs, Args: $3.exprs,
// Instead of `Var: true`, we could have added a `$` // Instead of `Var: true`, we could have added a `$`
@@ -522,11 +524,11 @@ call:
| expr PLUS expr | expr PLUS expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, // for PLUS this is a `+` character V: $2.str, // for PLUS this is a `+` character
}, },
$1.expr, $1.expr,
$3.expr, $3.expr,
@@ -536,10 +538,10 @@ call:
| expr MINUS expr | expr MINUS expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -550,10 +552,10 @@ call:
| expr MULTIPLY expr | expr MULTIPLY expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -564,10 +566,10 @@ call:
| expr DIVIDE expr | expr DIVIDE expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -578,10 +580,10 @@ call:
| expr EQ expr | expr EQ expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -592,10 +594,10 @@ call:
| expr NEQ expr | expr NEQ expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -606,10 +608,10 @@ call:
| expr LT expr | expr LT expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -620,10 +622,10 @@ call:
| expr GT expr | expr GT expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -634,10 +636,10 @@ call:
| expr LTE expr | expr LTE expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -648,10 +650,10 @@ call:
| expr GTE expr | expr GTE expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -662,10 +664,10 @@ call:
| expr AND expr | expr AND expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -676,10 +678,10 @@ call:
| expr OR expr | expr OR expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $2.str, V: $2.str,
}, },
$1.expr, $1.expr,
@@ -690,10 +692,10 @@ call:
| NOT expr | NOT expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ // operator first &ast.ExprStr{ // operator first
V: $1.str, V: $1.str,
}, },
$2.expr, $2.expr,
@@ -704,13 +706,13 @@ call:
// get the N-th historical value, eg: $foo{3} is equivalent to: history($foo, 3) // get the N-th historical value, eg: $foo{3} is equivalent to: history($foo, 3)
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: historyFuncName, Name: funcs.HistoryFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprVar{ &ast.ExprVar{
Name: $1.str, Name: $1.str,
}, },
&ExprInt{ &ast.ExprInt{
V: $1.int, V: $1.int,
}, },
}, },
@@ -722,13 +724,13 @@ call:
//| dotted_var_identifier OPEN_CURLY INTEGER CLOSE_CURLY //| dotted_var_identifier OPEN_CURLY INTEGER CLOSE_CURLY
// { // {
// posLast(yylex, yyDollar) // our pos // posLast(yylex, yyDollar) // our pos
// $$.expr = &ExprCall{ // $$.expr = &ast.ExprCall{
// Name: historyFuncName, // Name: funcs.HistoryFuncName,
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// &ExprVar{ // &ast.ExprVar{
// Name: $1.str, // Name: $1.str,
// }, // },
// &ExprInt{ // &ast.ExprInt{
// V: $3.int, // V: $3.int,
// }, // },
// }, // },
@@ -737,8 +739,8 @@ call:
| expr IN expr | expr IN expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprCall{ $$.expr = &ast.ExprCall{
Name: containsFuncName, Name: funcs.ContainsFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
$1.expr, $1.expr,
$3.expr, $3.expr,
@@ -770,7 +772,7 @@ var:
dotted_var_identifier dotted_var_identifier
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprVar{ $$.expr = &ast.ExprVar{
Name: $1.str, Name: $1.str,
} }
} }
@@ -783,7 +785,7 @@ func:
FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprFunc{ $$.expr = &ast.ExprFunc{
Args: $3.args, Args: $3.args,
//Return: nil, //Return: nil,
Body: $6.expr, Body: $6.expr,
@@ -793,7 +795,7 @@ func:
| FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY | FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.expr = &ExprFunc{ $$.expr = &ast.ExprFunc{
Args: $3.args, Args: $3.args,
Return: $5.typ, // return type is known Return: $5.typ, // return type is known
Body: $7.expr, Body: $7.expr,
@@ -863,7 +865,7 @@ bind:
VAR_IDENTIFIER EQUALS expr VAR_IDENTIFIER EQUALS expr
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtBind{ $$.stmt = &ast.StmtBind{
Ident: $1.str, Ident: $1.str,
Value: $3.expr, Value: $3.expr,
} }
@@ -878,7 +880,7 @@ bind:
// this will ultimately cause a parser error to occur... // this will ultimately cause a parser error to occur...
yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err)) yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err))
} }
$$.stmt = &StmtBind{ $$.stmt = &ast.StmtBind{
Ident: $1.str, Ident: $1.str,
Value: expr, Value: expr,
} }
@@ -893,7 +895,7 @@ rbind:
// XXX: this kind of bind is different than the others, because // XXX: this kind of bind is different than the others, because
// it can only really be used for send->recv stuff, eg: // it can only really be used for send->recv stuff, eg:
// foo.SomeString -> bar.SomeOtherString // foo.SomeString -> bar.SomeOtherString
$$.expr = &StmtBind{ $$.expr = &ast.StmtBind{
Ident: $1.str, Ident: $1.str,
Value: $3.stmt, Value: $3.stmt,
} }
@@ -905,7 +907,7 @@ resource:
RES_IDENTIFIER expr OPEN_CURLY resource_body CLOSE_CURLY RES_IDENTIFIER expr OPEN_CURLY resource_body CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtRes{ $$.stmt = &ast.StmtRes{
Kind: $1.str, Kind: $1.str,
Name: $2.expr, Name: $2.expr,
Contents: $4.resContents, Contents: $4.resContents,
@@ -916,7 +918,7 @@ resource:
| IDENTIFIER expr OPEN_CURLY resource_body CLOSE_CURLY | IDENTIFIER expr OPEN_CURLY resource_body CLOSE_CURLY
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtRes{ $$.stmt = &ast.StmtRes{
Kind: $1.str, Kind: $1.str,
Name: $2.expr, Name: $2.expr,
Contents: $4.resContents, Contents: $4.resContents,
@@ -927,7 +929,7 @@ resource_body:
/* end of list */ /* end of list */
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.resContents = []StmtResContents{} $$.resContents = []ast.StmtResContents{}
} }
| resource_body resource_field | resource_body resource_field
{ {
@@ -974,7 +976,7 @@ resource_field:
IDENTIFIER ROCKET expr COMMA IDENTIFIER ROCKET expr COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.resField = &StmtResField{ $$.resField = &ast.StmtResField{
Field: $1.str, Field: $1.str,
Value: $3.expr, Value: $3.expr,
} }
@@ -985,7 +987,7 @@ conditional_resource_field:
IDENTIFIER ROCKET expr ELVIS expr COMMA IDENTIFIER ROCKET expr ELVIS expr COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.resField = &StmtResField{ $$.resField = &ast.StmtResField{
Field: $1.str, Field: $1.str,
Value: $5.expr, Value: $5.expr,
Condition: $3.expr, Condition: $3.expr,
@@ -997,7 +999,7 @@ resource_edge:
CAPITALIZED_IDENTIFIER ROCKET edge_half COMMA CAPITALIZED_IDENTIFIER ROCKET edge_half COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.resEdge = &StmtResEdge{ $$.resEdge = &ast.StmtResEdge{
Property: $1.str, Property: $1.str,
EdgeHalf: $3.edgeHalf, EdgeHalf: $3.edgeHalf,
} }
@@ -1008,7 +1010,7 @@ conditional_resource_edge:
CAPITALIZED_IDENTIFIER ROCKET expr ELVIS edge_half COMMA CAPITALIZED_IDENTIFIER ROCKET expr ELVIS edge_half COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.resEdge = &StmtResEdge{ $$.resEdge = &ast.StmtResEdge{
Property: $1.str, Property: $1.str,
EdgeHalf: $5.edgeHalf, EdgeHalf: $5.edgeHalf,
Condition: $3.expr, Condition: $3.expr,
@@ -1020,11 +1022,11 @@ resource_meta:
CAPITALIZED_IDENTIFIER COLON IDENTIFIER ROCKET expr COMMA CAPITALIZED_IDENTIFIER COLON IDENTIFIER ROCKET expr COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
if strings.ToLower($1.str) != strings.ToLower(MetaField) { if strings.ToLower($1.str) != strings.ToLower(ast.MetaField) {
// this will ultimately cause a parser error to occur... // this will ultimately cause a parser error to occur...
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str)) yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
} }
$$.resMeta = &StmtResMeta{ $$.resMeta = &ast.StmtResMeta{
Property: $3.str, Property: $3.str,
MetaExpr: $5.expr, MetaExpr: $5.expr,
} }
@@ -1035,11 +1037,11 @@ conditional_resource_meta:
CAPITALIZED_IDENTIFIER COLON IDENTIFIER ROCKET expr ELVIS expr COMMA CAPITALIZED_IDENTIFIER COLON IDENTIFIER ROCKET expr ELVIS expr COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
if strings.ToLower($1.str) != strings.ToLower(MetaField) { if strings.ToLower($1.str) != strings.ToLower(ast.MetaField) {
// this will ultimately cause a parser error to occur... // this will ultimately cause a parser error to occur...
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str)) yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
} }
$$.resMeta = &StmtResMeta{ $$.resMeta = &ast.StmtResMeta{
Property: $3.str, Property: $3.str,
MetaExpr: $7.expr, MetaExpr: $7.expr,
Condition: $5.expr, Condition: $5.expr,
@@ -1051,11 +1053,11 @@ resource_meta_struct:
CAPITALIZED_IDENTIFIER ROCKET expr COMMA CAPITALIZED_IDENTIFIER ROCKET expr COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
if strings.ToLower($1.str) != strings.ToLower(MetaField) { if strings.ToLower($1.str) != strings.ToLower(ast.MetaField) {
// this will ultimately cause a parser error to occur... // this will ultimately cause a parser error to occur...
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str)) yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
} }
$$.resMeta = &StmtResMeta{ $$.resMeta = &ast.StmtResMeta{
Property: $1.str, Property: $1.str,
MetaExpr: $3.expr, MetaExpr: $3.expr,
} }
@@ -1066,11 +1068,11 @@ conditional_resource_meta_struct:
CAPITALIZED_IDENTIFIER ROCKET expr ELVIS expr COMMA CAPITALIZED_IDENTIFIER ROCKET expr ELVIS expr COMMA
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
if strings.ToLower($1.str) != strings.ToLower(MetaField) { if strings.ToLower($1.str) != strings.ToLower(ast.MetaField) {
// this will ultimately cause a parser error to occur... // this will ultimately cause a parser error to occur...
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str)) yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
} }
$$.resMeta = &StmtResMeta{ $$.resMeta = &ast.StmtResMeta{
Property: $1.str, Property: $1.str,
MetaExpr: $5.expr, MetaExpr: $5.expr,
Condition: $3.expr, Condition: $3.expr,
@@ -1084,7 +1086,7 @@ edge:
edge_half_list edge_half_list
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtEdge{ $$.stmt = &ast.StmtEdge{
EdgeHalfList: $1.edgeHalfList, EdgeHalfList: $1.edgeHalfList,
//Notify: false, // unused here //Notify: false, // unused here
} }
@@ -1093,8 +1095,8 @@ edge:
| edge_half_sendrecv ARROW edge_half_sendrecv | edge_half_sendrecv ARROW edge_half_sendrecv
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.stmt = &StmtEdge{ $$.stmt = &ast.StmtEdge{
EdgeHalfList: []*StmtEdgeHalf{ EdgeHalfList: []*ast.StmtEdgeHalf{
$1.edgeHalf, $1.edgeHalf,
$3.edgeHalf, $3.edgeHalf,
}, },
@@ -1106,7 +1108,7 @@ edge_half_list:
edge_half edge_half
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.edgeHalfList = []*StmtEdgeHalf{$1.edgeHalf} $$.edgeHalfList = []*ast.StmtEdgeHalf{$1.edgeHalf}
} }
| edge_half_list ARROW edge_half | edge_half_list ARROW edge_half
{ {
@@ -1119,7 +1121,7 @@ edge_half:
capitalized_res_identifier OPEN_BRACK expr CLOSE_BRACK capitalized_res_identifier OPEN_BRACK expr CLOSE_BRACK
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.edgeHalf = &StmtEdgeHalf{ $$.edgeHalf = &ast.StmtEdgeHalf{
Kind: $1.str, Kind: $1.str,
Name: $3.expr, Name: $3.expr,
//SendRecv: "", // unused //SendRecv: "", // unused
@@ -1131,7 +1133,7 @@ edge_half_sendrecv:
capitalized_res_identifier OPEN_BRACK expr CLOSE_BRACK DOT IDENTIFIER capitalized_res_identifier OPEN_BRACK expr CLOSE_BRACK DOT IDENTIFIER
{ {
posLast(yylex, yyDollar) // our pos posLast(yylex, yyDollar) // our pos
$$.edgeHalf = &StmtEdgeHalf{ $$.edgeHalf = &ast.StmtEdgeHalf{
Kind: $1.str, Kind: $1.str,
Name: $3.expr, Name: $3.expr,
SendRecv: $6.str, SendRecv: $6.str,

View File

@@ -17,13 +17,15 @@
// +build !root // +build !root
package lang package lang // XXX: move this to the unification package
import ( import (
"fmt" "fmt"
"strings" "strings"
"testing" "testing"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/funcs/vars" "github.com/purpleidea/mgmt/lang/funcs/vars"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types" "github.com/purpleidea/mgmt/lang/types"
@@ -52,14 +54,14 @@ func TestUnification1(t *testing.T) {
// }) // })
//} //}
{ {
expr := &ExprStr{V: "hello"} expr := &ast.ExprStr{V: "hello"}
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "t1"}, Name: &ast.ExprStr{V: "t1"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "str", Field: "str",
Value: expr, Value: expr,
}, },
@@ -77,23 +79,23 @@ func TestUnification1(t *testing.T) {
}) })
} }
{ {
v1 := &ExprStr{} v1 := &ast.ExprStr{}
v2 := &ExprStr{} v2 := &ast.ExprStr{}
v3 := &ExprStr{} v3 := &ast.ExprStr{}
expr := &ExprList{ expr := &ast.ExprList{
Elements: []interfaces.Expr{ Elements: []interfaces.Expr{
v1, v1,
v2, v2,
v3, v3,
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "test"}, Name: &ast.ExprStr{V: "test"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "slicestring", Field: "slicestring",
Value: expr, Value: expr,
}, },
@@ -114,26 +116,26 @@ func TestUnification1(t *testing.T) {
}) })
} }
{ {
k1 := &ExprInt{} k1 := &ast.ExprInt{}
k2 := &ExprInt{} k2 := &ast.ExprInt{}
k3 := &ExprInt{} k3 := &ast.ExprInt{}
v1 := &ExprFloat{} v1 := &ast.ExprFloat{}
v2 := &ExprFloat{} v2 := &ast.ExprFloat{}
v3 := &ExprFloat{} v3 := &ast.ExprFloat{}
expr := &ExprMap{ expr := &ast.ExprMap{
KVs: []*ExprMapKV{ KVs: []*ast.ExprMapKV{
{Key: k1, Val: v1}, {Key: k1, Val: v1},
{Key: k2, Val: v2}, {Key: k2, Val: v2},
{Key: k3, Val: v3}, {Key: k3, Val: v3},
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "test"}, Name: &ast.ExprStr{V: "test"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "mapintfloat", Field: "mapintfloat",
Value: expr, Value: expr,
}, },
@@ -157,25 +159,25 @@ func TestUnification1(t *testing.T) {
}) })
} }
{ {
b := &ExprBool{} b := &ast.ExprBool{}
s := &ExprStr{} s := &ast.ExprStr{}
i := &ExprInt{} i := &ast.ExprInt{}
f := &ExprFloat{} f := &ast.ExprFloat{}
expr := &ExprStruct{ expr := &ast.ExprStruct{
Fields: []*ExprStructField{ Fields: []*ast.ExprStructField{
{Name: "somebool", Value: b}, {Name: "somebool", Value: b},
{Name: "somestr", Value: s}, {Name: "somestr", Value: s},
{Name: "someint", Value: i}, {Name: "someint", Value: i},
{Name: "somefloat", Value: f}, {Name: "somefloat", Value: f},
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "test"}, Name: &ast.ExprStr{V: "test"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "mixedstruct", Field: "mixedstruct",
Value: expr, Value: expr,
}, },
@@ -200,30 +202,30 @@ func TestUnification1(t *testing.T) {
// test "n1" { // test "n1" {
// int64ptr => 13 + 42, // int64ptr => 13 + 42,
//} //}
expr := &ExprCall{ expr := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprInt{ &ast.ExprInt{
V: 13, V: 13,
}, },
&ExprInt{ &ast.ExprInt{
V: 42, V: 42,
}, },
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "n1", V: "n1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "int64ptr", Field: "int64ptr",
Value: expr, // func Value: expr, // func
}, },
@@ -244,41 +246,41 @@ func TestUnification1(t *testing.T) {
//test "n1" { //test "n1" {
// int64ptr => 13 + 42 - 4, // int64ptr => 13 + 42 - 4,
//} //}
innerFunc := &ExprCall{ innerFunc := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "-", V: "-",
}, },
&ExprInt{ &ast.ExprInt{
V: 42, V: 42,
}, },
&ExprInt{ &ast.ExprInt{
V: 4, V: 4,
}, },
}, },
} }
expr := &ExprCall{ expr := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprInt{ &ast.ExprInt{
V: 13, V: 13,
}, },
innerFunc, // nested func, can we unify? innerFunc, // nested func, can we unify?
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "n1", V: "n1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "int64ptr", Field: "int64ptr",
Value: expr, Value: expr,
}, },
@@ -300,41 +302,41 @@ func TestUnification1(t *testing.T) {
//test "n1" { //test "n1" {
// float32 => -25.38789 + 32.6 + 13.7, // float32 => -25.38789 + 32.6 + 13.7,
//} //}
innerFunc := &ExprCall{ innerFunc := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprFloat{ &ast.ExprFloat{
V: 32.6, V: 32.6,
}, },
&ExprFloat{ &ast.ExprFloat{
V: 13.7, V: 13.7,
}, },
}, },
} }
expr := &ExprCall{ expr := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "+", V: "+",
}, },
&ExprFloat{ &ast.ExprFloat{
V: -25.38789, V: -25.38789,
}, },
innerFunc, // nested func, can we unify? innerFunc, // nested func, can we unify?
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "n1", V: "n1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "float32", Field: "float32",
Value: expr, Value: expr,
}, },
@@ -357,35 +359,35 @@ func TestUnification1(t *testing.T) {
//test "t1" { //test "t1" {
// int64 => $x, // int64 => $x,
//} //}
innerFunc := &ExprCall{ innerFunc := &ast.ExprCall{
Name: operatorFuncName, Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "-", V: "-",
}, },
&ExprInt{ &ast.ExprInt{
V: 42, V: 42,
}, },
&ExprInt{ &ast.ExprInt{
V: 13, V: 13,
}, },
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtBind{ &ast.StmtBind{
Ident: "x", Ident: "x",
Value: innerFunc, Value: innerFunc,
}, },
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "int64", Field: "int64",
Value: &ExprVar{ Value: &ast.ExprVar{
Name: "x", Name: "x",
}, },
}, },
@@ -407,32 +409,32 @@ func TestUnification1(t *testing.T) {
//test "t1" { //test "t1" {
// anotherstr => $x, // anotherstr => $x,
//} //}
innerFunc := &ExprCall{ innerFunc := &ast.ExprCall{
Name: "template", Name: "template",
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "hello", V: "hello",
}, },
&ExprInt{ &ast.ExprInt{
V: 42, V: 42,
}, },
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtBind{ &ast.StmtBind{
Ident: "x", Ident: "x",
Value: innerFunc, Value: innerFunc,
}, },
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "anotherstr", Field: "anotherstr",
Value: &ExprVar{ Value: &ast.ExprVar{
Name: "x", Name: "x",
}, },
}, },
@@ -455,38 +457,38 @@ func TestUnification1(t *testing.T) {
//test "t1" { //test "t1" {
// anotherstr => $x, // anotherstr => $x,
//} //}
innerFunc := &ExprCall{ innerFunc := &ast.ExprCall{
Name: "template", Name: "template",
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "hello", // whatever... V: "hello", // whatever...
}, },
&ExprVar{ &ast.ExprVar{
Name: "v", Name: "v",
}, },
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtBind{ &ast.StmtBind{
Ident: "v", Ident: "v",
Value: &ExprInt{ Value: &ast.ExprInt{
V: 42, V: 42,
}, },
}, },
&StmtBind{ &ast.StmtBind{
Ident: "x", Ident: "x",
Value: innerFunc, Value: innerFunc,
}, },
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{ Name: &ast.ExprStr{
V: "t1", V: "t1",
}, },
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "anotherstr", Field: "anotherstr",
Value: &ExprVar{ Value: &ast.ExprVar{
Name: "x", Name: "x",
}, },
}, },
@@ -508,20 +510,20 @@ func TestUnification1(t *testing.T) {
//test "t1" { //test "t1" {
// stringptr => datetime.now(), # bad (str vs. int) // stringptr => datetime.now(), # bad (str vs. int)
//} //}
expr := &ExprCall{ expr := &ast.ExprCall{
Name: "datetime.now", Name: "datetime.now",
Args: []interfaces.Expr{}, Args: []interfaces.Expr{},
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtImport{ &ast.StmtImport{
Name: "datetime", Name: "datetime",
}, },
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "t1"}, Name: &ast.ExprStr{V: "t1"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: expr, Value: expr,
}, },
@@ -540,27 +542,27 @@ func TestUnification1(t *testing.T) {
//test "t1" { //test "t1" {
// stringptr => sys.getenv("GOPATH", "bug"), # bad (two args vs. one) // stringptr => sys.getenv("GOPATH", "bug"), # bad (two args vs. one)
//} //}
expr := &ExprCall{ expr := &ast.ExprCall{
Name: "sys.getenv", Name: "sys.getenv",
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "GOPATH", V: "GOPATH",
}, },
&ExprStr{ &ast.ExprStr{
V: "bug", V: "bug",
}, },
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtImport{ &ast.StmtImport{
Name: "sys", Name: "sys",
}, },
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "t1"}, Name: &ast.ExprStr{V: "t1"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: expr, Value: expr,
}, },
@@ -580,27 +582,27 @@ func TestUnification1(t *testing.T) {
// //test "t1" { // //test "t1" {
// // stringptr => fmt.printf("hello %s and %s", "one"), # bad // // stringptr => fmt.printf("hello %s and %s", "one"), # bad
// //} // //}
// expr := &ExprCall{ // expr := &ast.ExprCall{
// Name: "fmt.printf", // Name: "fmt.printf",
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// &ExprStr{ // &ast.ExprStr{
// V: "hello %s and %s", // V: "hello %s and %s",
// }, // },
// &ExprStr{ // &ast.ExprStr{
// V: "one", // V: "one",
// }, // },
// }, // },
// } // }
// stmt := &StmtProg{ // stmt := &ast.StmtProg{
// Body: []interfaces.Stmt{ // Body: []interfaces.Stmt{
// &StmtImport{ // &ast.StmtImport{
// Name: "fmt", // Name: "fmt",
// }, // },
// &StmtRes{ // &ast.StmtRes{
// Kind: "test", // Kind: "test",
// Name: &ExprStr{V: "t1"}, // Name: &ast.ExprStr{V: "t1"},
// Contents: []StmtResContents{ // Contents: []ast.StmtResContents{
// &StmtResField{ // &ast.StmtResField{
// Field: "stringptr", // Field: "stringptr",
// Value: expr, // Value: expr,
// }, // },
@@ -619,33 +621,33 @@ func TestUnification1(t *testing.T) {
// //test "t1" { // //test "t1" {
// // stringptr => fmt.printf("hello %s and %s", "one", "two", "three"), # bad // // stringptr => fmt.printf("hello %s and %s", "one", "two", "three"), # bad
// //} // //}
// expr := &ExprCall{ // expr := &ast.ExprCall{
// Name: "fmt.printf", // Name: "fmt.printf",
// Args: []interfaces.Expr{ // Args: []interfaces.Expr{
// &ExprStr{ // &ast.ExprStr{
// V: "hello %s and %s", // V: "hello %s and %s",
// }, // },
// &ExprStr{ // &ast.ExprStr{
// V: "one", // V: "one",
// }, // },
// &ExprStr{ // &ast.ExprStr{
// V: "two", // V: "two",
// }, // },
// &ExprStr{ // &ast.ExprStr{
// V: "three", // V: "three",
// }, // },
// }, // },
// } // }
// stmt := &StmtProg{ // stmt := &ast.StmtProg{
// Body: []interfaces.Stmt{ // Body: []interfaces.Stmt{
// &StmtImport{ // &ast.StmtImport{
// Name: "fmt", // Name: "fmt",
// }, // },
// &StmtRes{ // &ast.StmtRes{
// Kind: "test", // Kind: "test",
// Name: &ExprStr{V: "t1"}, // Name: &ast.ExprStr{V: "t1"},
// Contents: []StmtResContents{ // Contents: []ast.StmtResContents{
// &StmtResField{ // &ast.StmtResField{
// Field: "stringptr", // Field: "stringptr",
// Value: expr, // Value: expr,
// }, // },
@@ -664,30 +666,30 @@ func TestUnification1(t *testing.T) {
//test "t1" { //test "t1" {
// stringptr => fmt.printf("hello %s and %s", "one", "two"), // stringptr => fmt.printf("hello %s and %s", "one", "two"),
//} //}
expr := &ExprCall{ expr := &ast.ExprCall{
Name: "fmt.printf", Name: "fmt.printf",
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "hello %s and %s", V: "hello %s and %s",
}, },
&ExprStr{ &ast.ExprStr{
V: "one", V: "one",
}, },
&ExprStr{ &ast.ExprStr{
V: "two", V: "two",
}, },
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtImport{ &ast.StmtImport{
Name: "fmt", Name: "fmt",
}, },
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "t1"}, Name: &ast.ExprStr{V: "t1"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "stringptr", Field: "stringptr",
Value: expr, Value: expr,
}, },
@@ -714,37 +716,37 @@ func TestUnification1(t *testing.T) {
//test "t1" { //test "t1" {
// stringptr => fmt.printf("hello %s", $x), // stringptr => fmt.printf("hello %s", $x),
//} //}
cond := &ExprIf{ cond := &ast.ExprIf{
Condition: &ExprBool{V: true}, Condition: &ast.ExprBool{V: true},
ThenBranch: &ExprInt{V: 42}, ThenBranch: &ast.ExprInt{V: 42},
ElseBranch: &ExprInt{V: 13}, ElseBranch: &ast.ExprInt{V: 13},
} }
cond.SetType(types.TypeStr) // should fail unification cond.SetType(types.TypeStr) // should fail unification
expr := &ExprCall{ expr := &ast.ExprCall{
Name: "fmt.printf", Name: "fmt.printf",
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "hello %s", V: "hello %s",
}, },
&ExprVar{ &ast.ExprVar{
Name: "x", // the var Name: "x", // the var
}, },
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtImport{ &ast.StmtImport{
Name: "fmt", Name: "fmt",
}, },
&StmtBind{ &ast.StmtBind{
Ident: "x", // the var Ident: "x", // the var
Value: cond, Value: cond,
}, },
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "t1"}, Name: &ast.ExprStr{V: "t1"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "anotherstr", Field: "anotherstr",
Value: expr, Value: expr,
}, },
@@ -766,38 +768,38 @@ func TestUnification1(t *testing.T) {
//test "t1" { //test "t1" {
// stringptr => fmt.printf("hello %s", $x), // stringptr => fmt.printf("hello %s", $x),
//} //}
wvar := &ExprBool{V: true} wvar := &ast.ExprBool{V: true}
xvar := &ExprVar{Name: "w"} xvar := &ast.ExprVar{Name: "w"}
xvar.SetType(types.TypeStr) // should fail unification xvar.SetType(types.TypeStr) // should fail unification
expr := &ExprCall{ expr := &ast.ExprCall{
Name: "fmt.printf", Name: "fmt.printf",
Args: []interfaces.Expr{ Args: []interfaces.Expr{
&ExprStr{ &ast.ExprStr{
V: "hello %s", V: "hello %s",
}, },
&ExprVar{ &ast.ExprVar{
Name: "x", // the var Name: "x", // the var
}, },
}, },
} }
stmt := &StmtProg{ stmt := &ast.StmtProg{
Body: []interfaces.Stmt{ Body: []interfaces.Stmt{
&StmtImport{ &ast.StmtImport{
Name: "fmt", Name: "fmt",
}, },
&StmtBind{ &ast.StmtBind{
Ident: "w", Ident: "w",
Value: wvar, Value: wvar,
}, },
&StmtBind{ &ast.StmtBind{
Ident: "x", // the var Ident: "x", // the var
Value: xvar, Value: xvar,
}, },
&StmtRes{ &ast.StmtRes{
Kind: "test", Kind: "test",
Name: &ExprStr{V: "t1"}, Name: &ast.ExprStr{V: "t1"},
Contents: []StmtResContents{ Contents: []ast.StmtResContents{
&StmtResField{ &ast.StmtResField{
Field: "anotherstr", Field: "anotherstr",
Value: expr, Value: expr,
}, },
@@ -825,16 +827,16 @@ func TestUnification1(t *testing.T) {
} }
names = append(names, tc.name) names = append(names, tc.name)
t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) { t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) {
ast, fail, expect, experr, experrstr := tc.ast, tc.fail, tc.expect, tc.experr, tc.experrstr xast, fail, expect, experr, experrstr := tc.ast, tc.fail, tc.expect, tc.experr, tc.experrstr
//str := strings.NewReader(code) //str := strings.NewReader(code)
//ast, err := LexParse(str) //xast, err := parser.LexParse(str)
//if err != nil { //if err != nil {
// t.Errorf("test #%d: lex/parse failed with: %+v", index, err) // t.Errorf("test #%d: lex/parse failed with: %+v", index, err)
// return // return
//} //}
// TODO: print out the AST's so that we can see the types // TODO: print out the AST's so that we can see the types
t.Logf("\n\ntest #%d: AST (before): %+v\n", index, ast) t.Logf("\n\ntest #%d: AST (before): %+v\n", index, xast)
data := &interfaces.Data{ data := &interfaces.Data{
// TODO: add missing fields here if/when needed // TODO: add missing fields here if/when needed
@@ -844,7 +846,7 @@ func TestUnification1(t *testing.T) {
}, },
} }
// some of this might happen *after* interpolate in SetScope or Unify... // some of this might happen *after* interpolate in SetScope or Unify...
if err := ast.Init(data); err != nil { if err := xast.Init(data); err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: could not init and validate AST: %+v", index, err) t.Errorf("test #%d: could not init and validate AST: %+v", index, err)
return return
@@ -860,13 +862,13 @@ func TestUnification1(t *testing.T) {
//t.Logf("test #%d: astInterpolated: %+v", index, astInterpolated) //t.Logf("test #%d: astInterpolated: %+v", index, astInterpolated)
variables := map[string]interfaces.Expr{ variables := map[string]interfaces.Expr{
"purpleidea": &ExprStr{V: "hello world!"}, // james says hi "purpleidea": &ast.ExprStr{V: "hello world!"}, // james says hi
//"hostname": &ExprStr{V: obj.Hostname}, //"hostname": &ast.ExprStr{V: obj.Hostname},
} }
consts := VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix! consts := ast.VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix!
addback := vars.ConstNamespace + interfaces.ModuleSep // add it back... addback := vars.ConstNamespace + interfaces.ModuleSep // add it back...
var err error var err error
variables, err = MergeExprMaps(variables, consts, addback) variables, err = ast.MergeExprMaps(variables, consts, addback)
if err != nil { if err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: couldn't merge in consts: %+v", index, err) t.Errorf("test #%d: couldn't merge in consts: %+v", index, err)
@@ -877,10 +879,10 @@ func TestUnification1(t *testing.T) {
scope := &interfaces.Scope{ scope := &interfaces.Scope{
Variables: variables, Variables: variables,
// all the built-in top-level, core functions enter here... // all the built-in top-level, core functions enter here...
Functions: FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix Functions: ast.FuncPrefixToFunctionsScope(""), // runs funcs.LookupPrefix
} }
// propagate the scope down through the AST... // propagate the scope down through the AST...
if err := ast.SetScope(scope); err != nil { if err := xast.SetScope(scope); err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: set scope failed with: %+v", index, err) t.Errorf("test #%d: set scope failed with: %+v", index, err)
return return
@@ -891,7 +893,7 @@ func TestUnification1(t *testing.T) {
t.Logf(fmt.Sprintf("test #%d", index)+": unification: "+format, v...) t.Logf(fmt.Sprintf("test #%d", index)+": unification: "+format, v...)
} }
unifier := &unification.Unifier{ unifier := &unification.Unifier{
AST: ast, AST: xast,
Solver: unification.SimpleInvariantSolverLogger(logf), Solver: unification.SimpleInvariantSolverLogger(logf),
Debug: testing.Verbose(), Debug: testing.Verbose(),
Logf: logf, Logf: logf,
@@ -899,7 +901,7 @@ func TestUnification1(t *testing.T) {
err = unifier.Unify() err = unifier.Unify()
// TODO: print out the AST's so that we can see the types // TODO: print out the AST's so that we can see the types
t.Logf("\n\ntest #%d: AST (after): %+v\n", index, ast) t.Logf("\n\ntest #%d: AST (after): %+v\n", index, xast)
if !fail && err != nil { if !fail && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)

View File

@@ -19,13 +19,24 @@ package util
import ( import (
"fmt" "fmt"
"net/url"
"path"
"regexp" "regexp"
"strings" "strings"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types" "github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
) )
const (
// ModuleMagicPrefix is the prefix which, if found as a prefix to the
// last token in an import path, will be removed silently if there are
// remaining characters following the name. If this is the empty string
// then it will be ignored.
ModuleMagicPrefix = "mgmt-"
)
// HasDuplicateTypes returns an error if the list of types is not unique. // HasDuplicateTypes returns an error if the list of types is not unique.
func HasDuplicateTypes(typs []*types.Type) error { func HasDuplicateTypes(typs []*types.Type) error {
// FIXME: do this comparison in < O(n^2) ? // FIXME: do this comparison in < O(n^2) ?
@@ -127,3 +138,118 @@ func ValidateVarName(name string) error {
return nil return nil
} }
// ParseImportName parses an import name and returns the default namespace name
// that should be used with it. For example, if the import name was:
// "git://example.com/purpleidea/Module-Name", this might return an alias of
// "module_name". It also returns a bunch of other data about the parsed import.
// TODO: check for invalid or unwanted special characters
func ParseImportName(name string) (*interfaces.ImportData, error) {
magicPrefix := ModuleMagicPrefix
if name == "" {
return nil, fmt.Errorf("empty name")
}
if strings.HasPrefix(name, "/") {
return nil, fmt.Errorf("absolute paths are not allowed")
}
u, err := url.Parse(name)
if err != nil {
return nil, errwrap.Wrapf(err, "name is not a valid url")
}
if u.Path == "" {
return nil, fmt.Errorf("empty path")
}
p := u.Path
// catch bad paths like: git:////home/james/ (note the quad slash!)
// don't penalize if we have a dir with a trailing slash at the end
if s := path.Clean(u.Path); u.Path != s && u.Path != s+"/" {
// TODO: are there any cases where this is not what we want?
return nil, fmt.Errorf("dirty path, cleaned it's: `%s`", s)
}
for strings.HasSuffix(p, "/") { // remove trailing slashes
p = p[:len(p)-len("/")]
}
split := strings.Split(p, "/") // take last chunk if slash separated
s := split[0]
if len(split) > 1 {
s = split[len(split)-1] // pick last chunk
}
// TODO: should we treat a special name: "purpleidea/mgmt-foo" as "foo"?
if magicPrefix != "" && strings.HasPrefix(s, magicPrefix) && len(s) > len(magicPrefix) {
s = s[len(magicPrefix):]
}
s = strings.Replace(s, "-", "_", -1) // XXX: allow underscores in IDENTIFIER
if strings.HasPrefix(s, "_") || strings.HasSuffix(s, "_") {
return nil, fmt.Errorf("name can't begin or end with dash or underscore")
}
alias := strings.ToLower(s)
// if this is a local import, it's a straight directory path
// if it's an fqdn import, it should contain a metadata file
// if there's no protocol prefix, then this must be a local path
isLocal := u.Scheme == ""
// if it has a trailing slash or .mcl extension it's not a system import
isSystem := isLocal && !strings.HasSuffix(u.Path, "/") && !strings.HasSuffix(u.Path, interfaces.DotFileNameExtension)
// is it a local file?
isFile := !isSystem && isLocal && strings.HasSuffix(u.Path, interfaces.DotFileNameExtension)
xpath := u.Path // magic path
if isSystem {
xpath = ""
}
if !isLocal {
host := u.Host // host or host:port
split := strings.Split(host, ":")
if l := len(split); l == 1 || l == 2 {
host = split[0]
} else {
return nil, fmt.Errorf("incorrect number of colons (%d) in hostname", l)
}
xpath = path.Join(host, xpath)
}
if !isLocal && !strings.HasSuffix(xpath, "/") {
xpath = xpath + "/"
}
// we're a git repo with a local path instead of an fqdn over http!
// this still counts as isLocal == false, since it's still a remote
if u.Host == "" && strings.HasPrefix(u.Path, "/") {
xpath = strings.TrimPrefix(xpath, "/") // make it a relative dir
}
if strings.HasPrefix(xpath, "/") { // safety check (programming error?)
return nil, fmt.Errorf("can't parse strange import")
}
// build a url to clone from if we're not local...
// TODO: consider adding some logic that is similar to the logic in:
// https://github.com/golang/go/blob/054640b54df68789d9df0e50575d21d9dbffe99f/src/cmd/go/internal/get/vcs.go#L972
// so that we can more correctly figure out the correct url to clone...
xurl := ""
if !isLocal {
u.Fragment = ""
// TODO: maybe look for ?sha1=... or ?tag=... to pick a real ref
u.RawQuery = ""
u.ForceQuery = false
xurl = u.String()
}
// if u.Path is local file like: foo/server.mcl alias should be "server"
// we should trim the alias to remove the .mcl (the dir is already gone)
if isFile && strings.HasSuffix(alias, interfaces.DotFileNameExtension) {
alias = strings.TrimSuffix(alias, interfaces.DotFileNameExtension)
}
return &interfaces.ImportData{
Name: name, // save the original value here
Alias: alias,
IsSystem: isSystem,
IsLocal: isLocal,
IsFile: isFile,
Path: xpath,
URL: xurl,
}, nil
}

View File

@@ -27,7 +27,7 @@ if [ "$COMMITS" != "" ] && [ "$COMMITS" -gt "1" ]; then
fi fi
# find all go files, exluding temporary directories and generated files # find all go files, exluding temporary directories and generated files
LINT=$(find * -maxdepth 9 -iname '*.go' -not -path 'old/*' -not -path 'tmp/*' -not -path 'bindata/*' -not -path 'lang/y.go' -not -path 'lang/lexer.nn.go' -not -path 'lang/interpolate/parse.generated.go' -not -path 'lang/types/kind_stringer.go' -not -path 'vendor/*' -exec golint {} \;) # current golint output LINT=$(find * -maxdepth 9 -iname '*.go' -not -path 'old/*' -not -path 'tmp/*' -not -path 'bindata/*' -not -path 'lang/parser/y.go' -not -path 'lang/parser/lexer.nn.go' -not -path 'lang/interpolate/parse.generated.go' -not -path 'lang/types/kind_stringer.go' -not -path 'vendor/*' -exec golint {} \;) # current golint output
COUNT=`echo -e "$LINT" | wc -l` # number of golint problems in current branch COUNT=`echo -e "$LINT" | wc -l` # number of golint problems in current branch
[ "$LINT" = "" ] && echo PASS && exit # everything is "perfect" [ "$LINT" = "" ] && echo PASS && exit # everything is "perfect"
@@ -55,7 +55,7 @@ while read -r line; do
done <<< "$NUMSTAT1" # three < is the secret to putting a variable into read done <<< "$NUMSTAT1" # three < is the secret to putting a variable into read
git checkout "$PREVIOUS" &>/dev/null # previous commit git checkout "$PREVIOUS" &>/dev/null # previous commit
LINT1=$(find * -maxdepth 9 -iname '*.go' -not -path 'old/*' -not -path 'tmp/*' -not -path 'bindata/*' -not -path 'lang/y.go' -not -path 'lang/lexer.nn.go' -not -path 'vendor/*' -exec golint {} \;) LINT1=$(find * -maxdepth 9 -iname '*.go' -not -path 'old/*' -not -path 'tmp/*' -not -path 'bindata/*' -not -path 'lang/parser/y.go' -not -path 'lang/parser/lexer.nn.go' -not -path 'vendor/*' -exec golint {} \;)
COUNT1=`echo -e "$LINT1" | wc -l` # number of golint problems in older branch COUNT1=`echo -e "$LINT1" | wc -l` # number of golint problems in older branch
# clean up # clean up

View File

@@ -45,8 +45,8 @@ gml="$gml --enable=misspell"
# exclude generated files: # exclude generated files:
# TODO: at least until https://github.com/alecthomas/gometalinter/issues/270 # TODO: at least until https://github.com/alecthomas/gometalinter/issues/270
gml="$gml --exclude=lang/lexer.nn.go" gml="$gml --exclude=lang/parser/lexer.nn.go"
gml="$gml --exclude=lang/y.go" gml="$gml --exclude=lang/parser/y.go"
gml="$gml --exclude=bindata/bindata.go" gml="$gml --exclude=bindata/bindata.go"
gml="$gml --exclude=lang/types/kind_stringer.go" gml="$gml --exclude=lang/types/kind_stringer.go"
gml="$gml --exclude=lang/interpolate/parse.generated.go" gml="$gml --exclude=lang/interpolate/parse.generated.go"

View File

@@ -109,7 +109,7 @@ function reflowed-comments() {
return 0 return 0
fi fi
if [ "$1" = './lang/lexer.nn.go' ]; then if [ "$1" = './lang/parser/lexer.nn.go' ]; then
return 0 return 0
fi fi