package core import ( "io" ) type ( CloseFunc func() error RootScope struct { closed bool disposables []io.Closer } Scope struct { root *RootScope parent *Scope vars map[string]Value } ) func NewRootScope() (*Scope, CloseFunc) { root := &RootScope{ closed: false, disposables: make([]io.Closer, 0, 10), } return newScope(root, nil), func() error { return root.Close() } } func (s *RootScope) AddDisposable(disposable io.Closer) { if s.closed { return } if disposable != nil { s.disposables = append(s.disposables, disposable) } } func (s *RootScope) Close() error { if s.closed { return Error(ErrInvalidOperation, "scope is already closed") } s.closed = true var errors []error // close all values implemented io.Close for _, c := range s.disposables { if err := c.Close(); err != nil { if errors == nil { errors = make([]error, 0, len(s.disposables)) } errors = append(errors, err) } } if errors == nil { return nil } return Errors(errors...) } func newScope(root *RootScope, parent *Scope) *Scope { return &Scope{ root: root, parent: parent, vars: make(map[string]Value), } } func (s *Scope) SetVariable(name string, val Value) error { _, exists := s.vars[name] // it already has been declared in the current scope if exists { return Errorf(ErrNotUnique, "variable is already declared: '%s'", name) } disposable, ok := val.(io.Closer) if ok { s.root.AddDisposable(disposable) } s.vars[name] = val return nil } func (s *Scope) HasVariable(name string) bool { _, exists := s.vars[name] // does not exist in the current scope // try to find in a parent scope if !exists { if s.parent != nil { return s.parent.HasVariable(name) } } return exists } func (s *Scope) GetVariable(name string) (Value, error) { out, exists := s.vars[name] // does not exist in the current scope // try to find in the parent scope if !exists { if s.parent != nil { return s.parent.GetVariable(name) } return nil, Errorf(ErrNotFound, "variable: '%s'", name) } return out, nil } func (s *Scope) MustGetVariable(name string) Value { out, err := s.GetVariable(name) if err != nil { panic(err) } return out } func (s *Scope) UpdateVariable(name string, val Value) error { _, exists := s.vars[name] if !exists { return Errorf(ErrNotFound, "variable: '%s'", name) } delete(s.vars, name) return s.SetVariable(name, val) } func (s *Scope) Fork() *Scope { child := newScope(s.root, s) return child }