The implementation of useOp has not been updated correctly to accomodate the
fact that the namespaces are no longer modified in place.
This fixes#1212.
This commit replaces scopeOp, the only remaining place that mutates *Ns in
place, with nsOp, which performs copy-on-write for *Ns.
As a result, the "eval" command no longer mutates the passed namespace. The
default namespace it uses is also changed to match the default of "-source" (an
amalgamated namespace from the local and upvalue scopes), making "-source $file"
equivalent to "eval (slurp <$file)", and formally deprecated.
Another result is that (*Evaler).Eval can now guard the mutation of the global
namespace with the mutex, making it concurrency-safe to execute multiple sources
that touch the global namespace.
This fixes#1137.
Also change the variable name used to keep the Exception returned from "err" to
"exc".
This uncovers several error scenarios that were not returning Exception, and
would result in the absense of stack traces when such errors occur.
This change is a preparation step for refining all *Op types to return Exception
as the error.
Keeping Exception as a struct type will make such a change error-prone, since
a (*Exception)(nil) != error(nil), so if an *Op returns a nil *Exception, it
is not nil if the return value is stored in an error-typed variable.
Doing something like the following is likely to result in too many open
files (assuming `ulimit -n` == 256) resulting in a panic:
> fn fact [n]{ if (== $n 0) { put 1 } else { put (* $n (fact (- $n 1))) } }
> fact 60
panic: interface conversion: error is *os.SyscallError, not
*eval.Exception
goroutine 24161 [running]:
github.com/elves/elvish/pkg/eval.(*pipelineOp).exec.func1(0x152a5a0,
0xc000dca210, 0xc000a44e70, 0xc00057b540, 0xc001030590, 0x203000)
github.com/elves/elvish/pkg/eval/compile_effect.go:153 +0x152
created by github.com/elves/elvish/pkg/eval.(*pipelineOp).exec
github.com/elves/elvish/pkg/eval/compile_effect.go:149 +0x225
Exception: elvish exited with 2
Fixes#1208
- Move NewEnvListVar to pkg/eval/vars.
- De-export GlobPattern, GlobFlag and ExternalCmd.
- Merge editor.go and chdir.go into eval.go, value_helper.go into compile_value.go.
- Remove eval_internal_test.go and replace it with a new test for $pid.
This change makes Ns immutable from the exposed API. Internally there is exactly
one place that still mutates Ns, in scopeOp; this will be addressed later.
- Make Evaler mostly thread-safe. The only remaining thread-unsafe part is the
modules field, which is more tricky than other fields.
- Remove the state and evalerScopes type, and move their fields into Evaler.
- Expose valuePrefix via a get method, and change PortsFromFiles to take the
prefix instead of a *Evaler. Also expose a PortsFromStdFiles.
- Make Evaler a normal field of Frame, instead of an embedded field. This makes
access to global states more explicit.
The Evaler keeps global states and needs to be accessed concurrently. Mutations
to global states have fairly low throughput, so it makes sense to use a single
mutex.
On the other hand, the compiler is always used on a single thread, so it does
not need any mutex protection, so there is no need to put the mutex inside
deprecationRegistry.
- Remove the Op type; it is no longer used by any code outside the eval package
and its use within the package is limited.
- Replace (*Evaler).execOp with (*Evaler).prepareFrame.
- Remove reliance on scopeOp, concentrating all the scope for creating the local
scope in (*closure).call.
- Check options at the very beginning, and include all unsupported options in the
error.
The -source command now runs with a temporary namespace that is amalgamated
from the local and up namespace of the caller -source; this means that it can
no longer mutate its caller's local scope, which is the only possible sensible
behavior anyway.
This fixes#1202.
Most of the places that need to directly call a function is in the edit package,
which need to call user-defined callbacks.
This change eliminates most call sites of NewTopFrame (including all call sites
outside the eval package). Remove the function and inline it in the remaining
few call sites.
Remove NewTopFrame means that the eval package no longer offers other packages
a way to construct Frame instances. This is intended: Frame is a relatively
low-level concept, and all code outside the eval package now uses the more
high-level Eval, Call, Check/CheckTree methods of *Evaler. The most notable
exception is packages that implement modules; they may still use Frame to access
the information kept in it, but they never construct Frame instances.
In future, the Frame type can be changed to an interface.
This feature supersedes the CompileWithGlobal method, and simplifies the only
use of that method in pkg/edit, where the default binding is evaluated using the
edit: namespace as the global Ns.
The Compile method was used only by the syntax highlighting code to find
compilation errors. Since it only needs the error part but not the Op part,
provide an alternative API that only exposes the error.
The new (*Evaler).Check method allows the implementation to be simpler.
Also test the JSON format feature in an integration test instead of unit test;
this makes it unnecessary for the eval package to expose NewCompilationError.
This method has the property that it always tries to compile the code even if
there is a parse failure. This is the more desirable behavior when checking
code: if there is a parse failure near the end of a chunk of code, the user may
like to learn about compile errors earlier in the code.
The test contained a race condition: when the mock TimeAfter implementation
sends the process a SIGINT, the "sleep" function may not have entered the
"select" block, meaning that the signal will be ignored.
This commit works around this by waiting 1ms (scaled with
$ELVISH_TEST_TIME_SCALE) before sending SIGINT. The test now consistently
succeeds on my local laptop.
Introduces two functions, PipePort and CapturePort, and implement output capture
in terms of them. These two functions return *Port instances, which can also be
used in (*Evaler).Eval calls.