Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically generated code for import #348

Closed
bfreis opened this issue Nov 6, 2021 · 0 comments · Fixed by #349
Closed

Dynamically generated code for import #348

bfreis opened this issue Nov 6, 2021 · 0 comments · Fixed by #349

Comments

@bfreis
Copy link
Contributor

bfreis commented Nov 6, 2021

Hi!

While using tengo embedded into some code, I wanted to make it possible to dynamically provide source code for imports, without having to import files from disk. Specifically, the import names I want to use do not map to files on disk, and are unknown before compiling a script (so I couldn't even somehow pre-generate them anywhere).

I couldn't find any easy way to do it, just a very ugly hack, based on trying to compile on a loop, handling any CompilerError on ImportExpr by generating the module I need, adding it to a ModuleMap, and trying again. Something like this:

moduleMap := tengo.NewModuleMap()
script := tengo.NewScript(src)
script.SetImports(moduleMap)

var c *tengo.Compiled
var err error

done := false
for !done {
  done = true
  c, err = script.Compile()
  var cerr *tengo.CompilerError
  if errors.As(err, &cerr) {
    if node, ok := cerr.Node.(*parser.ImportExpr); ok {
      mSrc := generateModuleSource(node.ModuleName)
      moduleMap.AddSourceModule(node.ModuleName, mSrc)
      err = nil
      done = false
    }
  }
}

if err != nil { ... }

// use c here

But this is beyond ugly.

There's a rather simple alternative: if we extract a trivial interface from *tengo.ModuleMap with just its Get method, and change a few places that declare parameters or fields with the new interface, I can pass an interface instead, and do my dynamic generation there.

E.g.:

// (some changes omitted for brevity)

// tengo: modules.go:
type ModuleGetter interface {
  Get(name string) Importable
}

// tengo: script.go:
func (s *Script) SetImports(modules ModuleGetter) {
	s.modules = modules
}

// my_code.go:
type DynamicModuleGetter { ... }
func (d *DynamicModuleGetter) Get(name string) tengo.Importable {
  src := ...
  return &tengo.SourceModule{Src: src}
}
// ...
m := &DynamicModuleGetter{...}
script := tengo.NewScript(src)
script.SetImports(m)
c, err := script.Compile()
if err != nil { ... }
// use c here

This has the added benefit of getting the code closer to Go's good practice of taking interfaces instead of concrete types.

Will send a PR soon.

Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant