glycerine/zygomys

Namespace support?

nsjph opened this issue · 7 comments

nsjph commented

I'd like to be able to load multiple .zy scripts into a single Glisp environment, however two scripts with same functions/vars could clobber each other. (The specific use case would be a bot with multiple plugins). So I'd love to be able to load each script into a different namespace within the same Glisp environment.

An alternative approach is to have one Glisp environment per script, which seems like overkill. How scalable would one Glisp environment per script be?

I saw (newScope) function but wasn't sure if it would apply in this scenario.

Are multiple namespaces on the roadmap?

@nsjph Good suggestion. Shouldn't be hugely difficult. I'll look into it.

Re your other question about having one Glisp environment per script: this is also entirely reasonable and should be quite efficient.

The only downside would be that one package (namespace) couldn't refer to functions/variables in another. Depending on security concerns of course, that may be what you want.

nsjph commented

Security-wise, I'd probably implement something like all scripts from "plugins/blah.zy" to be loaded (via env.LoadString()) into a shared sandboxed environment, able to access each-other's namespaces, while any located in plugins/trusted/blah.zy would be in a separate, un-sandboxed environment.

f85274a / as of release v4.7.0, zygo has package functionality which can be used by library writer or consumer of libraries/scripts to package and isolate sourced code.

Check out tests/package.zy (https://github.com/glycerine/zygomys/blob/master/tests/package.zy) for example use. Either the client/consumer can create the package themselves upon import (like this):

{p = (package "doggerel" (source "tests/pkghelp"))}

...or the package library itself can put a package statement at the top of the file. If one wants multiple files in a package, then add multiple embedded source statements:

{p = (package "doggerel" (source "dog_part_1.zy") (source "dog_part_2.zy"))}

Generally I took the approach of trying to match Go's semantics. This means that the consumer of the package can rename it if they need to do so to avoid collisions. Hence while the package name (e.g. "doggerel" above) is required, the client chooses their own name ("p" above).

Play with it and let me know what you think.

nsjph commented

Works like a charm!

Here's an example of my loading code (which is very close to what I was envisaging in the first place):

func loadPlugins() {

    scripts, err := filepath.Glob("*.zy")
    if err != nil {
        log.Fatalln("loadPlugins filepath: ", err)
    }

    for i := 0; i < len(scripts); i++ {
        log.Printf("loading %s\n", scripts[i])

        dat, err := ioutil.ReadFile(scripts[i])
        if err != nil {
            log.Fatalf("Error reading %s: %s\n", scripts[i], err.Error())
        }

        wrappedScript := fmt.Sprintf("(def %s %s)", strings.TrimSuffix(scripts[i], ".zy"), dat)
        log.Println(wrappedScript)
        err = Env.LoadString(wrappedScript)
        if err != nil {
            log.Fatalf("Error loading %s (%s)\n", scripts[i], err.Error())
        }
    }
}

So if I have a stdlib.zy, I can easily eval stdlib.myfunc elsewhere in the code.

Thanks a bunch.

PS, Is LoadString or EvalString the best to use for this type of initialization/loading? Would I only use Evalstring if i care about the output?

Good to hear :-)

PS, Is LoadString or EvalString the best to use for this type of initialization/loading? Would I only use Evalstring if i care about the output?

You almost surely want EvalString(), or possibly SourceFile() (in source.go), as these will actually "run" the generated bytecode, establishing symbol bindings in the environment. The Load* functions parse and generate bytecode, but do not actually run it and so don't change the environment.

NB: As of 4.8.0 (4856ddf) released just now, I added the Public/private distinction for identifiers within an imported (sourced) package. This supports the eventual aim of enabling trivial compile-down to actual Go code. By matching Go's expectations, variables won't have to be renamed later to accomplish the compilation. See the extended https://github.com/glycerine/zygomys/blob/master/tests/package.zy for examples.