diff --git a/operator-plugin.go b/operator-plugin.go new file mode 100644 index 0000000..88bd392 --- /dev/null +++ b/operator-plugin.go @@ -0,0 +1,102 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// operator-plugin.go +package expr + +import ( + "fmt" + "io" + "plugin" +) + +//-------- plugin term + +func newPluginTerm(tk *Token) (inst *term) { + return &term{ + tk: *tk, + children: make([]*term, 0, 1), + position: posPrefix, + priority: priSign, + evalFunc: evalPlugin, + } +} + +func importPlugin(ctx ExprContext, dirList []string, name string) (err error) { + var filePath string + var p *plugin.Plugin + var sym plugin.Symbol + var moduleName string + var importFunc func(ctx ExprContext) + var ok bool + + decoratedName := fmt.Sprintf("expr-%s-plugin.so", name) + + if filePath, err = makeFilepath(decoratedName, dirList); err != nil { + return + } + + if p, err = plugin.Open(filePath); err != nil { + return + } + + if sym, err = p.Lookup("MODULE_NAME"); err != nil { + return + } + + if moduleName = *sym.(*string); moduleName == "" { + err = fmt.Errorf("plugin %q does not provide a valid module name", decoratedName) + return + } + + if sym, err = p.Lookup("ImportFuncs"); err != nil { + return + } + + if importFunc, ok = sym.(func(ExprContext)); !ok { + err = fmt.Errorf("plugin %q does not provide a valid import function", decoratedName) + return + } + + registerPlugin(moduleName, p) + importFunc(globalCtx) + + return +} + +func evalPlugin(ctx ExprContext, self *term) (v any, err error) { + var childValue any + var moduleSpec any + + if childValue, err = self.evalPrefix(ctx); err != nil { + return + } + + dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH) + count := 0 + it := NewAnyIterator(childValue) + for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() { + if module, ok := moduleSpec.(string); ok { + if err = importPlugin(ctx, dirList, module); err != nil { + break + } + count++ + } else { + err = self.Errorf("expected string as item nr %d, got %s", it.Index()+1, typeName(moduleSpec)) + break + } + } + if err == io.EOF { + err = nil + } + + if err == nil { + v = int64(count) + } + return +} + +// init +func init() { + registerTermConstructor(SymKwPlugin, newPluginTerm) +} diff --git a/plugins.go b/plugins.go new file mode 100644 index 0000000..c85b274 --- /dev/null +++ b/plugins.go @@ -0,0 +1,30 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// plugin.go +package expr + +import ( + "fmt" + "plugin" +) + +var pluginRegister map[string]*plugin.Plugin + +func registerPlugin(name string, p *plugin.Plugin) (err error) { + if pluginExists(name) { + err = fmt.Errorf("plugin %q already loaded", name) + } else { + pluginRegister[name] = p + } + return +} + +func pluginExists(name string) (exists bool) { + _, exists = pluginRegister[name] + return +} + +func init() { + pluginRegister = make(map[string]*plugin.Plugin) +} diff --git a/symbol.go b/symbol.go index 3004c24..c8997b1 100644 --- a/symbol.go +++ b/symbol.go @@ -101,6 +101,7 @@ const ( SymKwBut SymKwFunc SymKwBuiltin + SymKwPlugin SymKwIn SymKwInclude SymKwNil @@ -113,6 +114,7 @@ func init() { keywords = map[string]Symbol{ "AND": SymKwAnd, "BUILTIN": SymKwBuiltin, + "PLUGIN": SymKwPlugin, "BUT": SymKwBut, "FUNC": SymKwFunc, "IN": SymKwIn,