Closed lthibault closed 4 years ago
Cross-posting the comment that I had accidentally posted to #17 :
Added synchronization to stack frames in order to support GoExpr
.
Keep in mind:
Context
is bound to a single goroutine (= not shared).StackFrame
holds a pointer to a map of values (via ConcurrentMap
interface), so value maps are shared across goroutines and require synchronization.Pros:
Context
.Context
is now a concrete struct, and exhibits the same modularity pattern as e.g. reader.Reader
as discussed in https://github.com/spy16/sabre/issues/28.StackFrame
is a concrete struct, so context and stack operations should benefit from inlining by the compiler, and pointer-chasing is reduced.ConcurrentMap
is the only thing that requires sync.ConcurrentMap
implementations if the built-in, lock-based implementation is not satsifactory. (Wetware is very likely to do this, using a custom CHAMP map, so that readers are never blocked by writers).Cons: I can't think of any 😄
Edit: re-converting to draft because I'm still making minor improvements. Should nevertheless be testable/runnable.
~I am doing some changes. Do you think I should go ahead on this branch or start from scratch or branch off from this and refactor ?~
I am adding stuff on the feature/interpreter
branch for now since I didn't know the exact changes I was going to do 😅
Also, Just to give you an idea on what I had in mind: I am thinking the Context
should mostly be a closed type (except Eval, mostly private) and bunch of core Expr
implementations that interact with internals of context and implement different functionality. Now, this approach will not leave much room for customisation at context level (except for whatever we make configurable through the Option
params of New()
)..
This doesn't limit the user a lot since we are simply providing re-usable Exprs instead of public methods exposed on Context
type. (For example, if user wants to bind a global variable, he/she can simply evaluate a DefExpr
against a context)
Reasons for this:
With this, the usage patterns at context level remain same regardless of how the entire parens is being used (since, all forms turn into one of these Exprs by Analyser implementations). With that abstraction, we can make generic optimisations in the internals of Context (For example how we manage lifecycle of contexts and the gouroutine running against it) and stacks (we can experiment with stack implementations) and Exprs, without changing external behaviour.
This is similar to how different Lisps work. There are handful of special forms that are highly optimised. Everything else is built on top and it works great because the building blocks are solid.
To be honest, I was even tempted to define Expr as below (sealed). But this might be too limiting, so I guess it's okay to make it a publicly implementable interface which compose builtin exprs.
type Expr interface {
eval(....) (...) // notice the private `eval`
}
(Another (arguably) good outcome is, if someone actually finds a way to optimise some builtin component, they will have to send a PR to get it integrated 😁 )
~I am doing some changes. Do you think I should go ahead on this branch or start from scratch or branch off from this and refactor ?~
I am adding stuff on the
feature/interpreter
branch for now since I didn't know the exact changes I was going to do 😅
No worries! Let's shift discussion over to #20.
TODO:
Evaluator
intoContext
repl.WithAnalyzer
andrepl.WithExpander
toparens
package