diff --git a/pkg/corset/resolver.go b/pkg/corset/resolver.go index 1f9f147..81a2cee 100644 --- a/pkg/corset/resolver.go +++ b/pkg/corset/resolver.go @@ -404,14 +404,14 @@ func (r *resolver) resolveConstraintsInModule(enclosing Scope, decls []Declarati func (r *resolver) resolveDefConstraintInModule(enclosing Scope, decl *DefConstraint) []SyntaxError { var ( errors []SyntaxError - scope = NewEvaluationScope(enclosing) + scope = NewLocalScope(enclosing, false) ) // Resolve guard if decl.Guard != nil { - errors = r.resolveExpressionInModule(scope, false, decl.Guard) + errors = r.resolveExpressionInModule(scope, decl.Guard) } // Resolve constraint body - errors = append(errors, r.resolveExpressionInModule(scope, false, decl.Constraint)...) + errors = append(errors, r.resolveExpressionInModule(scope, decl.Constraint)...) // Done return errors } @@ -420,10 +420,10 @@ func (r *resolver) resolveDefConstraintInModule(enclosing Scope, decl *DefConstr func (r *resolver) resolveDefInRangeInModule(enclosing Scope, decl *DefInRange) []SyntaxError { var ( errors []SyntaxError - scope = NewEvaluationScope(enclosing) + scope = NewLocalScope(enclosing, false) ) // Resolve property body - errors = append(errors, r.resolveExpressionInModule(scope, false, decl.Expr)...) + errors = append(errors, r.resolveExpressionInModule(scope, decl.Expr)...) // Done return errors } @@ -432,14 +432,14 @@ func (r *resolver) resolveDefInRangeInModule(enclosing Scope, decl *DefInRange) func (r *resolver) resolveDefFunInModule(enclosing Scope, decl *DefFun) []SyntaxError { var ( errors []SyntaxError - scope = NewLocalScope(NewEvaluationScope(enclosing)) + scope = NewLocalScope(enclosing, false) ) // Declare parameters in local scope for _, p := range decl.Parameters { scope.DeclareLocal(p.Name) } // Resolve property body - errors = append(errors, r.resolveExpressionInModule(&scope, false, decl.Body)...) + errors = append(errors, r.resolveExpressionInModule(scope, decl.Body)...) // Remove parameters from enclosing environment // Done return errors @@ -449,14 +449,14 @@ func (r *resolver) resolveDefFunInModule(enclosing Scope, decl *DefFun) []Syntax func (r *resolver) resolveDefLookupInModule(enclosing Scope, decl *DefLookup) []SyntaxError { var ( errors []SyntaxError - sourceScope = NewEvaluationScope(enclosing) - targetScope = NewEvaluationScope(enclosing) + sourceScope = NewLocalScope(enclosing, true) + targetScope = NewLocalScope(enclosing, true) ) // Resolve source expressions - errors = append(errors, r.resolveExpressionsInModule(sourceScope, true, decl.Sources)...) + errors = append(errors, r.resolveExpressionsInModule(sourceScope, decl.Sources)...) // Resolve target expressions - errors = append(errors, r.resolveExpressionsInModule(targetScope, true, decl.Targets)...) + errors = append(errors, r.resolveExpressionsInModule(targetScope, decl.Targets)...) // Done return errors } @@ -465,22 +465,22 @@ func (r *resolver) resolveDefLookupInModule(enclosing Scope, decl *DefLookup) [] func (r *resolver) resolveDefPropertyInModule(enclosing Scope, decl *DefProperty) []SyntaxError { var ( errors []SyntaxError - scope = NewEvaluationScope(enclosing) + scope = NewLocalScope(enclosing, false) ) // Resolve property body - errors = append(errors, r.resolveExpressionInModule(scope, false, decl.Assertion)...) + errors = append(errors, r.resolveExpressionInModule(scope, decl.Assertion)...) // Done return errors } // Resolve a sequence of zero or more expressions within a given module. This // simply resolves each of the arguments in turn, collecting any errors arising. -func (r *resolver) resolveExpressionsInModule(scope Scope, global bool, args []Expr) []SyntaxError { +func (r *resolver) resolveExpressionsInModule(scope LocalScope, args []Expr) []SyntaxError { var errors []SyntaxError // Visit each argument for _, arg := range args { if arg != nil { - errs := r.resolveExpressionInModule(scope, global, arg) + errs := r.resolveExpressionInModule(scope, arg) errors = append(errors, errs...) } } @@ -493,27 +493,27 @@ func (r *resolver) resolveExpressionsInModule(scope Scope, global bool, args []E // variable accesses. As above, the goal is ensure variable refers to something // that was declared and, more specifically, what kind of access it is (e.g. // column access, constant access, etc). -func (r *resolver) resolveExpressionInModule(scope Scope, global bool, expr Expr) []SyntaxError { +func (r *resolver) resolveExpressionInModule(scope LocalScope, expr Expr) []SyntaxError { if _, ok := expr.(*Constant); ok { return nil } else if v, ok := expr.(*Add); ok { - return r.resolveExpressionsInModule(scope, global, v.Args) + return r.resolveExpressionsInModule(scope, v.Args) } else if v, ok := expr.(*Exp); ok { - return r.resolveExpressionInModule(scope, global, v.Arg) + return r.resolveExpressionInModule(scope, v.Arg) } else if v, ok := expr.(*IfZero); ok { - return r.resolveExpressionsInModule(scope, global, []Expr{v.Condition, v.TrueBranch, v.FalseBranch}) + return r.resolveExpressionsInModule(scope, []Expr{v.Condition, v.TrueBranch, v.FalseBranch}) } else if v, ok := expr.(*Invoke); ok { - return r.resolveInvokeInModule(scope, global, v) + return r.resolveInvokeInModule(scope, v) } else if v, ok := expr.(*List); ok { - return r.resolveExpressionsInModule(scope, global, v.Args) + return r.resolveExpressionsInModule(scope, v.Args) } else if v, ok := expr.(*Mul); ok { - return r.resolveExpressionsInModule(scope, global, v.Args) + return r.resolveExpressionsInModule(scope, v.Args) } else if v, ok := expr.(*Normalise); ok { - return r.resolveExpressionInModule(scope, global, v.Arg) + return r.resolveExpressionInModule(scope, v.Arg) } else if v, ok := expr.(*Sub); ok { - return r.resolveExpressionsInModule(scope, global, v.Args) + return r.resolveExpressionsInModule(scope, v.Args) } else if v, ok := expr.(*VariableAccess); ok { - return r.resolveVariableInModule(scope, global, v) + return r.resolveVariableInModule(scope, v) } else { return r.srcmap.SyntaxErrors(expr, "unknown expression") } @@ -522,9 +522,9 @@ func (r *resolver) resolveExpressionInModule(scope Scope, global bool, expr Expr // Resolve a specific invocation contained within some expression which, in // turn, is contained within some module. Note, qualified accesses are only // permitted in a global context. -func (r *resolver) resolveInvokeInModule(scope Scope, global bool, expr *Invoke) []SyntaxError { +func (r *resolver) resolveInvokeInModule(scope LocalScope, expr *Invoke) []SyntaxError { // Resolve arguments - if errors := r.resolveExpressionsInModule(scope, global, expr.Args); errors != nil { + if errors := r.resolveExpressionsInModule(scope, expr.Args); errors != nil { return errors } // Lookup the corresponding function definition. @@ -541,17 +541,20 @@ func (r *resolver) resolveInvokeInModule(scope Scope, global bool, expr *Invoke) // Resolve a specific variable access contained within some expression which, in // turn, is contained within some module. Note, qualified accesses are only // permitted in a global context. -func (r *resolver) resolveVariableInModule(scope Scope, global bool, +func (r *resolver) resolveVariableInModule(scope LocalScope, expr *VariableAccess) []SyntaxError { // Will identify module of variable //var module string = scope.EnclosingModule() var mid *uint = nil // Check whether this is a qualified access, or not. - if !global && expr.Module != nil { + if !scope.IsGlobal() && expr.Module != nil { return r.srcmap.SyntaxErrors(expr, "qualified access not permitted here") } else if expr.Module != nil && !scope.HasModule(*expr.Module) { return r.srcmap.SyntaxErrors(expr, fmt.Sprintf("unknown module %s", *expr.Module)) } else if expr.Module != nil { + tmp := scope.Module(*expr.Module) + mid = &tmp + } else { tmp := scope.EnclosingModule() mid = &tmp } diff --git a/pkg/corset/scope.go b/pkg/corset/scope.go index 5792afd..7934490 100644 --- a/pkg/corset/scope.go +++ b/pkg/corset/scope.go @@ -14,13 +14,10 @@ type Scope interface { // Get the name of the enclosing module. This is generally useful for // reporting errors. EnclosingModule() uint - // Fix the context for this scope. Since every scope requires exactly one - // context, this fails if we fix it to incompatible contexts. - FixContext(tr.Context) bool // HasModule checks whether a given module exists, or not. HasModule(string) bool - // Module determines the module index for a given module. This assumes the - // module exists, and will panic otherwise. + // Lookup the identifier for a given module. This assumes that the module + // exists, and will panic otherwise. Module(string) uint // Lookup a given variable being referenced with an optional module // specifier. This variable could correspond to a column, a function, a @@ -49,12 +46,6 @@ func (p *ModuleScope) EnclosingModule() uint { return p.module } -// FixContext fixes the context for this scope. Since every scope requires -// exactly one context, this fails if we fix it to incompatible contexts. -func (p *ModuleScope) FixContext(context tr.Context) bool { - panic("unreachable") -} - // HasModule checks whether a given module exists, or not. func (p *ModuleScope) HasModule(module string) bool { return p.environment.HasModule(module) @@ -96,75 +87,50 @@ func (p *ModuleScope) DeclareFunction(name string, arity uint, body Expr) { p.functions[name] = FunctionBinding{arity, body} } -// ============================================================================= -// Evaluation Scope -// ============================================================================= - -// EvaluationScope represents a scope in which a given expression can be -// evaluated. The key feature of an evaluation scope is that it must have a -// single context. -type EvaluationScope struct { - // Represents the enclosing scope - enclosing Scope - // Context for this scope - context tr.Context -} - -// NewEvaluationScope creates a fresh scope representing the evaluation of some -// expression. -func NewEvaluationScope(enclosing Scope) *EvaluationScope { - return &EvaluationScope{enclosing, tr.VoidContext()} -} - -// EnclosingModule returns the name of the enclosing module. This is generally -// useful for reporting errors. -func (p *EvaluationScope) EnclosingModule() uint { - return p.enclosing.EnclosingModule() -} - -// FixContext fixes the context for this scope. Since every scope requires -// exactly one context, this fails if we fix it to incompatible contexts. -func (p *EvaluationScope) FixContext(context tr.Context) bool { - // Join contexts together - p.context = p.context.Join(context) - // Check they were compatible - return !p.context.IsConflicted() -} - -// HasModule checks whether a given module exists, or not. -func (p *EvaluationScope) HasModule(module string) bool { - return p.enclosing.HasModule(module) -} - -// Module determines the module index for a given module. This assumes the -// module exists, and will panic otherwise. -func (p *EvaluationScope) Module(module string) uint { - return p.enclosing.Module(module) -} - -// Bind looks up a given variable being referenced within a given module. -func (p *EvaluationScope) Bind(module *uint, name string, fn bool) Binding { - return p.enclosing.Bind(module, name, fn) -} - // ============================================================================= // Local Scope // ============================================================================= // LocalScope represents a simple implementation of scope in which local -// variables can be declared. +// variables can be declared. A local scope must have a single context +// associated with it, and this will be inferred by resolving those expressions +// which must be evaluated within. type LocalScope struct { + global bool // Represents the enclosing scope enclosing Scope + // Context for this scope + context *tr.Context // Maps inputs parameters to the declaration index. locals map[string]uint } -// NewLocalScope constructs a new local scope within a given scope. A local -// scope can have local variables declared within it. -func NewLocalScope(enclosing Scope) LocalScope { +// NewLocalScope constructs a new local scope within a given enclosing scope. A +// local scope can have local variables declared within it. A local scope can +// also be "global" in the sense that accessing symbols from other modules is +// permitted. +func NewLocalScope(enclosing Scope, global bool) LocalScope { + context := tr.VoidContext() locals := make(map[string]uint) - return LocalScope{enclosing, locals} + // + return LocalScope{global, enclosing, &context, locals} +} + +// NestedScope creates a nested scope within this local scope. +func (p LocalScope) NestedScope() LocalScope { + nlocals := make(map[string]uint) + // Clone allocated variables + for k, v := range p.locals { + nlocals[k] = v + } + // Done + return LocalScope{p.global, p.enclosing, p.context, nlocals} +} + +// IsGlobal determines whether symbols can be accessed in modules other than the +// enclosing module. +func (p LocalScope) IsGlobal() bool { + return p.global } // EnclosingModule returns the name of the enclosing module. This is generally @@ -176,7 +142,10 @@ func (p LocalScope) EnclosingModule() uint { // FixContext fixes the context for this scope. Since every scope requires // exactly one context, this fails if we fix it to incompatible contexts. func (p LocalScope) FixContext(context tr.Context) bool { - return p.enclosing.FixContext(context) + // Join contexts together + *p.context = p.context.Join(context) + // Check they were compatible + return !p.context.IsConflicted() } // HasModule checks whether a given module exists, or not.