From fd591032a57e744eef9b0dcef55dd7e1e31d86b4 Mon Sep 17 00:00:00 2001 From: David Pearce Date: Tue, 10 Dec 2024 20:35:00 +1300 Subject: [PATCH] fix: checks for assignment sources (#429) * Add test cases This adds a small number of test cases which illustrate the problem. * Fix invalid source columns This prevents source columns from being bound to definitions which are not appropriate. For example, it prevents the source column of an interleaving from being bound to a constant definition. --- pkg/corset/ast.go | 972 ---------------------------- pkg/corset/expr.go | 846 ++++++++++++++++++++++++ pkg/corset/parser.go | 12 +- pkg/corset/resolver.go | 2 +- pkg/corset/scope.go | 8 +- pkg/corset/symbol.go | 135 ++++ pkg/test/invalid_corset_test.go | 16 + testdata/interleave_invalid_11.lisp | 3 + testdata/interleave_invalid_12.lisp | 3 + testdata/permute_invalid_08.lisp | 2 + 10 files changed, 1014 insertions(+), 985 deletions(-) create mode 100644 pkg/corset/expr.go create mode 100644 pkg/corset/symbol.go create mode 100644 testdata/interleave_invalid_11.lisp create mode 100644 testdata/interleave_invalid_12.lisp create mode 100644 testdata/permute_invalid_08.lisp diff --git a/pkg/corset/ast.go b/pkg/corset/ast.go index 577efab..5660390 100644 --- a/pkg/corset/ast.go +++ b/pkg/corset/ast.go @@ -2,12 +2,10 @@ package corset import ( "fmt" - "math/big" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" sc "github.com/consensys/go-corset/pkg/schema" "github.com/consensys/go-corset/pkg/sexp" - tr "github.com/consensys/go-corset/pkg/trace" "github.com/consensys/go-corset/pkg/util" ) @@ -36,55 +34,6 @@ type Node interface { Lisp() sexp.SExp } -// ColumnAssignment provides a schematic for describing a column arising from an -// assignment. -type ColumnAssignment struct { - // Name of defined column - Name string - // Length multiplier for defined column - LengthMultiplier uint - // Type of defined column - Type sc.Type -} - -// Symbol represents a variable or function access within a declaration. -// Initially, such the proper interpretation of such accesses is unclear and it -// is only later when we can distinguish them (e.g. whether its a column access, -// a constant access, etc). -type Symbol interface { - Node - // Determines whether this symbol is qualfied or not (i.e. has an explicitly - // module specifier). - IsQualified() bool - // Indicates whether or not this is a function. - IsFunction() bool - // Checks whether this symbol has been resolved already, or not. - IsResolved() bool - // Optional module qualification - Module() string - // Name of the symbol - Name() string - // Get binding associated with this interface. This will panic if this - // symbol is not yet resolved. - Binding() Binding - // Resolve this symbol by associating it with the binding associated with - // the definition of the symbol to which this refers. - Resolve(Binding) -} - -// SymbolDefinition represents a declaration (or part thereof) which defines a -// particular symbol. For example, "defcolumns" will define one or more symbols -// representing columns, etc. -type SymbolDefinition interface { - Node - // Name of symbol being defined - Name() string - // Indicates whether or not this is a function definition. - IsFunction() bool - // Allocated binding for the symbol which may or may not be finalised. - Binding() Binding -} - // Declaration represents a top-level declaration in a Corset source file (e.g. // defconstraint, defcolumns, etc). type Declaration interface { @@ -96,88 +45,6 @@ type Declaration interface { Dependencies() util.Iterator[Symbol] } -// Assignment is a declaration which introduces one (or more) computed columns. -type Assignment interface { - Declaration - - // Return the set of columns which are declared by this assignment. - Targets() []string - - // Return the set of column assignments, or nil if the assignments cannot yet - // be determined (i.e. because the environment doesn't have complete - // information for one or more dependent columns). This can also fail for - // other reasons, such as when two columns in an interleaving have different - // length multipliers. - Resolve(*Environment) ([]ColumnAssignment, []SyntaxError) -} - -// Name represents a name within some syntactic item. Essentially this wraps a -// string and provides a mechanism for it to be associated with source line -// information. -type Name struct { - // Name of symbol - name string - // Indicates whether represents function or something else. - function bool - // Binding constructed for symbol. - binding Binding -} - -// IsQualified determines whether this symbol is qualfied or not (i.e. has an -// explicit module specifier). Column names are never qualified. -func (e *Name) IsQualified() bool { - return false -} - -// IsFunction indicates whether or not this symbol refers to a function (which -// of course it never does). -func (e *Name) IsFunction() bool { - return e.function -} - -// IsResolved checks whether this symbol has been resolved already, or not. -func (e *Name) IsResolved() bool { - return e.binding != nil -} - -// Module returns the optional module qualification. This always panics because -// column name's are never qualified. -func (e *Name) Module() string { - panic("undefined") -} - -// Name returns the (unqualified) name of the column to which this symbol -// refers. -func (e *Name) Name() string { - return e.name -} - -// Binding gets binding associated with this interface. This will panic if this -// symbol is not yet resolved. -func (e *Name) Binding() Binding { - if e.binding == nil { - panic("name not yet resolved") - } - // - return e.binding -} - -// Resolve this symbol by associating it with the binding associated with -// the definition of the symbol to which this refers. -func (e *Name) Resolve(binding Binding) { - if e.binding != nil { - panic("name already resolved") - } - // - e.binding = binding -} - -// Lisp converts this node into its lisp representation. This is primarily used -// for debugging purposes. -func (e *Name) Lisp() sexp.SExp { - return sexp.NewSymbol(e.name) -} - // ============================================================================ // defalias // ============================================================================ @@ -861,842 +728,3 @@ type DefParameter struct { func (p *DefParameter) Lisp() sexp.SExp { panic("got here") } - -// ============================================================================ -// Expressions -// ============================================================================ - -// Expr represents an arbitrary expression over the columns of a given context -// (or the parameters of an enclosing function). Such expressions are pitched -// at a higher-level than those of the underlying constraint system. For -// example, they can contain conditionals (i.e. if expressions) and -// normalisations, etc. During the lowering process down to the underlying -// constraints level (AIR), such expressions are "compiled out" using various -// techniques (such as introducing computed columns where necessary). -type Expr interface { - Node - // Evaluates this expression as a constant (signed) value. If this - // expression is not constant, then nil is returned. - AsConstant() *big.Int - // Multiplicity defines the number of values which will be returned when - // evaluating this expression. Due to the nature of expressions in Corset, - // they can (perhaps surprisingly) return multiple values. For example, - // lists return one value for each element in the list. Note, every - // expression must return at least one value. - Multiplicity() uint - - // Context returns the context for this expression. Observe that the - // expression must have been resolved for this to be defined (i.e. it may - // panic if it has not been resolved yet). - Context() Context - - // Substitute all variables (such as for function parameters) arising in - // this expression. - Substitute(args []Expr) Expr - - // Return set of columns on which this declaration depends. - Dependencies() []Symbol -} - -// Context represents the evaluation context for a given expression. -type Context = tr.RawContext[string] - -// ============================================================================ -// Addition -// ============================================================================ - -// Add represents the sum over zero or more expressions. -type Add struct{ Args []Expr } - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *Add) AsConstant() *big.Int { - fn := func(l *big.Int, r *big.Int) *big.Int { l.Add(l, r); return l } - return AsConstantOfExpressions(e.Args, fn) -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *Add) Multiplicity() uint { - return determineMultiplicity(e.Args) -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *Add) Context() Context { - return ContextOfExpressions(e.Args) -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *Add) Lisp() sexp.SExp { - return ListOfExpressions(sexp.NewSymbol("+"), e.Args) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *Add) Substitute(args []Expr) Expr { - return &Add{SubstituteExpressions(e.Args, args)} -} - -// Dependencies needed to signal declaration. -func (e *Add) Dependencies() []Symbol { - return DependenciesOfExpressions(e.Args) -} - -// ============================================================================ -// Constants -// ============================================================================ - -// Constant represents a constant value within an expression. -type Constant struct{ Val big.Int } - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *Constant) AsConstant() *big.Int { - return &e.Val -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *Constant) Multiplicity() uint { - return 1 -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *Constant) Context() Context { - return tr.VoidContext[string]() -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *Constant) Lisp() sexp.SExp { - return sexp.NewSymbol(e.Val.String()) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *Constant) Substitute(args []Expr) Expr { - return e -} - -// Dependencies needed to signal declaration. -func (e *Constant) Dependencies() []Symbol { - return nil -} - -// ============================================================================ -// Exponentiation -// ============================================================================ - -// Exp represents the a given value taken to a power. -type Exp struct { - Arg Expr - Pow Expr -} - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *Exp) AsConstant() *big.Int { - arg := e.Arg.AsConstant() - pow := e.Pow.AsConstant() - // Check if can evaluate - if arg != nil && pow != nil { - return arg.Exp(arg, pow, nil) - } - // - return nil -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *Exp) Multiplicity() uint { - return determineMultiplicity([]Expr{e.Arg}) -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *Exp) Context() Context { - return ContextOfExpressions([]Expr{e.Arg, e.Pow}) -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *Exp) Lisp() sexp.SExp { - return sexp.NewList([]sexp.SExp{ - sexp.NewSymbol("^"), - e.Arg.Lisp(), - e.Pow.Lisp()}) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *Exp) Substitute(args []Expr) Expr { - return &Exp{e.Arg.Substitute(args), e.Pow} -} - -// Dependencies needed to signal declaration. -func (e *Exp) Dependencies() []Symbol { - return DependenciesOfExpressions([]Expr{e.Arg, e.Pow}) -} - -// ============================================================================ -// IfZero -// ============================================================================ - -// IfZero returns the (optional) true branch when the condition evaluates to zero, and -// the (optional false branch otherwise. -type IfZero struct { - // Elements contained within this list. - Condition Expr - // True branch (optional). - TrueBranch Expr - // False branch (optional). - FalseBranch Expr -} - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *IfZero) AsConstant() *big.Int { - if condition := e.Condition.AsConstant(); condition != nil { - // Determine whether condition holds true (or not). - holds := condition.Cmp(big.NewInt(0)) == 0 - // - if holds && e.TrueBranch != nil { - return e.TrueBranch.AsConstant() - } else if !holds && e.FalseBranch != nil { - return e.FalseBranch.AsConstant() - } - } - // - return nil -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *IfZero) Multiplicity() uint { - return determineMultiplicity([]Expr{e.Condition, e.TrueBranch, e.FalseBranch}) -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *IfZero) Context() Context { - return ContextOfExpressions([]Expr{e.Condition, e.TrueBranch, e.FalseBranch}) -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *IfZero) Lisp() sexp.SExp { - if e.FalseBranch != nil { - return sexp.NewList([]sexp.SExp{ - sexp.NewSymbol("if"), - e.TrueBranch.Lisp(), - e.FalseBranch.Lisp()}) - } - // - return sexp.NewList([]sexp.SExp{ - sexp.NewSymbol("if"), - e.TrueBranch.Lisp()}) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *IfZero) Substitute(args []Expr) Expr { - return &IfZero{e.Condition.Substitute(args), - SubstituteOptionalExpression(e.TrueBranch, args), - SubstituteOptionalExpression(e.FalseBranch, args), - } -} - -// Dependencies needed to signal declaration. -func (e *IfZero) Dependencies() []Symbol { - return DependenciesOfExpressions([]Expr{e.Condition, e.TrueBranch, e.FalseBranch}) -} - -// ============================================================================ -// Function Invocation -// ============================================================================ - -// Invoke represents an attempt to invoke a given function. -type Invoke struct { - module *string - name string - args []Expr - binding *FunctionBinding -} - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *Invoke) AsConstant() *big.Int { - if e.binding == nil { - panic("unresolved invocation") - } - // Unroll body - body := e.binding.Apply(e.args) - // Attempt to evaluate as constant - return body.AsConstant() -} - -// IsQualified determines whether this symbol is qualfied or not (i.e. has an -// explicitly module specifier). -func (e *Invoke) IsQualified() bool { - return e.module != nil -} - -// IsFunction indicates whether or not this symbol refers to a function (which -// of course it always does). -func (e *Invoke) IsFunction() bool { - return true -} - -// IsResolved checks whether this symbol has been resolved already, or not. -func (e *Invoke) IsResolved() bool { - return e.binding != nil -} - -// Resolve this symbol by associating it with the binding associated with -// the definition of the symbol to which this refers. -func (e *Invoke) Resolve(binding Binding) { - if fb, ok := binding.(*FunctionBinding); ok { - e.binding = fb - return - } - // Problem - panic("cannot resolve function invocation with anything other than a function binding") -} - -// Module returns the optional module qualification. This will panic if this -// invocation is unqualified. -func (e *Invoke) Module() string { - if e.module == nil { - panic("invocation has no module qualifier") - } - - return *e.module -} - -// Name of the function being invoked. -func (e *Invoke) Name() string { - return e.name -} - -// Args returns the arguments provided by this invocation to the function being -// invoked. -func (e *Invoke) Args() []Expr { - return e.args -} - -// Binding gets binding associated with this interface. This will panic if this -// symbol is not yet resolved. -func (e *Invoke) Binding() Binding { - if e.binding == nil { - panic("invocation not yet resolved") - } - - return e.binding -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *Invoke) Context() Context { - if e.binding == nil { - panic("unresolved expressions encountered whilst resolving context") - } - // TODO: impure functions can have their own context. - return ContextOfExpressions(e.args) -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *Invoke) Multiplicity() uint { - // FIXME: is this always correct? - return 1 -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *Invoke) Lisp() sexp.SExp { - var fn sexp.SExp - if e.module != nil { - fn = sexp.NewSymbol(fmt.Sprintf("%s.%s", *e.module, e.name)) - } else { - fn = sexp.NewSymbol(e.name) - } - - return ListOfExpressions(fn, e.args) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *Invoke) Substitute(args []Expr) Expr { - return &Invoke{e.module, e.name, SubstituteExpressions(e.args, args), e.binding} -} - -// Dependencies needed to signal declaration. -func (e *Invoke) Dependencies() []Symbol { - deps := DependenciesOfExpressions(e.args) - // Include this expression as a symbol (which must be bound to the function - // being invoked) - return append(deps, e) -} - -// ============================================================================ -// List -// ============================================================================ - -// List represents a block of zero or more expressions. -type List struct{ Args []Expr } - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *List) AsConstant() *big.Int { - // Potentially we could do better here, but its not clear we need to. - return nil -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *List) Multiplicity() uint { - return determineMultiplicity(e.Args) -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *List) Context() Context { - return ContextOfExpressions(e.Args) -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *List) Lisp() sexp.SExp { - return ListOfExpressions(sexp.NewSymbol("begin"), e.Args) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *List) Substitute(args []Expr) Expr { - return &List{SubstituteExpressions(e.Args, args)} -} - -// Dependencies needed to signal declaration. -func (e *List) Dependencies() []Symbol { - return DependenciesOfExpressions(e.Args) -} - -// ============================================================================ -// Multiplication -// ============================================================================ - -// Mul represents the product over zero or more expressions. -type Mul struct{ Args []Expr } - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *Mul) AsConstant() *big.Int { - fn := func(l *big.Int, r *big.Int) *big.Int { l.Mul(l, r); return l } - return AsConstantOfExpressions(e.Args, fn) -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *Mul) Multiplicity() uint { - return determineMultiplicity(e.Args) -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *Mul) Context() Context { - return ContextOfExpressions(e.Args) -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *Mul) Lisp() sexp.SExp { - return ListOfExpressions(sexp.NewSymbol("*"), e.Args) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *Mul) Substitute(args []Expr) Expr { - return &Mul{SubstituteExpressions(e.Args, args)} -} - -// Dependencies needed to signal declaration. -func (e *Mul) Dependencies() []Symbol { - return DependenciesOfExpressions(e.Args) -} - -// ============================================================================ -// Normalise -// ============================================================================ - -// Normalise reduces the value of an expression to either zero (if it was zero) -// or one (otherwise). -type Normalise struct{ Arg Expr } - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *Normalise) AsConstant() *big.Int { - // FIXME: we could do better here. - return nil -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *Normalise) Multiplicity() uint { - return determineMultiplicity([]Expr{e.Arg}) -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *Normalise) Context() Context { - return ContextOfExpressions([]Expr{e.Arg}) -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *Normalise) Lisp() sexp.SExp { - return sexp.NewList([]sexp.SExp{ - sexp.NewSymbol("~"), - e.Arg.Lisp()}) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *Normalise) Substitute(args []Expr) Expr { - return &Normalise{e.Arg.Substitute(args)} -} - -// Dependencies needed to signal declaration. -func (e *Normalise) Dependencies() []Symbol { - return e.Arg.Dependencies() -} - -// ============================================================================ -// Subtraction -// ============================================================================ - -// Sub represents the subtraction over zero or more expressions. -type Sub struct{ Args []Expr } - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *Sub) AsConstant() *big.Int { - fn := func(l *big.Int, r *big.Int) *big.Int { l.Sub(l, r); return l } - return AsConstantOfExpressions(e.Args, fn) -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *Sub) Multiplicity() uint { - return determineMultiplicity(e.Args) -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *Sub) Context() Context { - return ContextOfExpressions(e.Args) -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *Sub) Lisp() sexp.SExp { - return ListOfExpressions(sexp.NewSymbol("-"), e.Args) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *Sub) Substitute(args []Expr) Expr { - return &Sub{SubstituteExpressions(e.Args, args)} -} - -// Dependencies needed to signal declaration. -func (e *Sub) Dependencies() []Symbol { - return DependenciesOfExpressions(e.Args) -} - -// ============================================================================ -// Shift -// ============================================================================ - -// Shift represents the result of a given expression shifted by a certain -// amount. In reality, the shift amount must be statically known. However, it -// is represented here as an expression to allow for constants and the results -// of function invocations, etc to be used. In all cases, these must still be -// eventually translated into constant values however. -type Shift struct { - // The expression being shifted - Arg Expr - // The amount it is being shifted by. - Shift Expr -} - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *Shift) AsConstant() *big.Int { - // Observe the shift doesn't matter as, in the case that the argument is a - // constant, then the shift has no effect anyway. - return e.Arg.AsConstant() -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *Shift) Multiplicity() uint { - return determineMultiplicity([]Expr{e.Arg}) -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *Shift) Context() Context { - return ContextOfExpressions([]Expr{e.Arg, e.Shift}) -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed. -func (e *Shift) Lisp() sexp.SExp { - return sexp.NewList([]sexp.SExp{ - sexp.NewSymbol("shift"), - e.Arg.Lisp(), - e.Shift.Lisp()}) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *Shift) Substitute(args []Expr) Expr { - return &Shift{e.Arg.Substitute(args), e.Shift.Substitute(args)} -} - -// Dependencies needed to signal declaration. -func (e *Shift) Dependencies() []Symbol { - return DependenciesOfExpressions([]Expr{e.Arg, e.Shift}) -} - -// ============================================================================ -// VariableAccess -// ============================================================================ - -// VariableAccess represents reading the value of a given local variable (such -// as a function parameter). -type VariableAccess struct { - module *string - name string - binding Binding -} - -// AsConstant attempts to evaluate this expression as a constant (signed) value. -// If this expression is not constant, then nil is returned. -func (e *VariableAccess) AsConstant() *big.Int { - if binding, ok := e.binding.(*ConstantBinding); ok { - return binding.value.AsConstant() - } - // not a constant - return nil -} - -// IsQualified determines whether this symbol is qualfied or not (i.e. has an -// explicitly module specifier). -func (e *VariableAccess) IsQualified() bool { - return e.module != nil -} - -// IsFunction determines whether this symbol refers to a function (which, of -// course, variable accesses never do). -func (e *VariableAccess) IsFunction() bool { - return false -} - -// IsResolved checks whether this symbol has been resolved already, or not. -func (e *VariableAccess) IsResolved() bool { - return e.binding != nil -} - -// Resolve this symbol by associating it with the binding associated with -// the definition of the symbol to which this refers. -func (e *VariableAccess) Resolve(binding Binding) { - if binding == nil { - panic("empty binding") - } else if e.binding != nil { - panic("already resolved") - } - - e.binding = binding -} - -// Module returns the optional module qualification. This will panic if this -// invocation is unqualified. -func (e *VariableAccess) Module() string { - return *e.module -} - -// Name returns the (unqualified) name of this symbol -func (e *VariableAccess) Name() string { - return e.name -} - -// Binding gets binding associated with this interface. This will panic if this -// symbol is not yet resolved. -func (e *VariableAccess) Binding() Binding { - if e.binding == nil { - panic("variable access is unresolved") - } - // - return e.binding -} - -// Multiplicity determines the number of values that evaluating this expression -// can generate. -func (e *VariableAccess) Multiplicity() uint { - return 1 -} - -// Context returns the context for this expression. Observe that the -// expression must have been resolved for this to be defined (i.e. it may -// panic if it has not been resolved yet). -func (e *VariableAccess) Context() Context { - binding, ok := e.binding.(*ColumnBinding) - // - if ok { - return binding.Context() - } - // - panic("invalid column access") -} - -// Lisp converts this schema element into a simple S-Expression, for example -// so it can be printed.a -func (e *VariableAccess) Lisp() sexp.SExp { - var name string - if e.module != nil { - name = fmt.Sprintf("%s.%s", *e.module, e.name) - } else { - name = e.name - } - // - return sexp.NewSymbol(name) -} - -// Substitute all variables (such as for function parameters) arising in -// this expression. -func (e *VariableAccess) Substitute(args []Expr) Expr { - if b, ok := e.binding.(*ParameterBinding); ok { - return args[b.index] - } - // Nothing to do here - return e -} - -// Dependencies needed to signal declaration. -func (e *VariableAccess) Dependencies() []Symbol { - return []Symbol{e} -} - -// ============================================================================ -// Helpers -// ============================================================================ - -// ContextOfExpressions returns the context for a set of zero or more -// expressions. Observe that, if there the expressions have no context (i.e. -// they are all constants) then the void context is returned. Likewise, if -// there are expressions with different contexts then the conflicted context -// will be returned. Otherwise, the one consistent context will be returned. -func ContextOfExpressions(exprs []Expr) Context { - context := tr.VoidContext[string]() - // - for _, e := range exprs { - context = context.Join(e.Context()) - } - // - return context -} - -// SubstituteExpressions substitutes all variables found in a given set of -// expressions. -func SubstituteExpressions(exprs []Expr, vars []Expr) []Expr { - nexprs := make([]Expr, len(exprs)) - // - for i := 0; i < len(nexprs); i++ { - nexprs[i] = exprs[i].Substitute(vars) - } - // - return nexprs -} - -// SubstituteOptionalExpression substitutes through an expression which is -// optional (i.e. might be nil). In such case, nil is returned. -func SubstituteOptionalExpression(expr Expr, vars []Expr) Expr { - if expr != nil { - expr = expr.Substitute(vars) - } - // - return expr -} - -// DependenciesOfExpressions determines the dependencies for a given set of zero -// or more expressions. -func DependenciesOfExpressions(exprs []Expr) []Symbol { - var deps []Symbol - // - for _, e := range exprs { - if e != nil { - deps = append(deps, e.Dependencies()...) - } - } - // - return deps -} - -// ListOfExpressions converts an array of one or more expressions into a list of -// corresponding lisp expressions. -func ListOfExpressions(head sexp.SExp, exprs []Expr) *sexp.List { - lisps := make([]sexp.SExp, len(exprs)+1) - // Assign head - lisps[0] = head - // - for i, e := range exprs { - lisps[i+1] = e.Lisp() - } - // - return sexp.NewList(lisps) -} - -// AsConstantOfExpressions attempts to fold one or more expressions across a -// given operation (e.g. add, subtract, etc) to produce a constant value. If -// any of the expressions are not themselves constant, then neither is the -// result. -func AsConstantOfExpressions(exprs []Expr, fn func(*big.Int, *big.Int) *big.Int) *big.Int { - var val *big.Int = big.NewInt(0) - // - for _, arg := range exprs { - c := arg.AsConstant() - if c == nil { - return nil - } - // Evaluate function - val = fn(val, c) - } - // - return val -} - -func determineMultiplicity(exprs []Expr) uint { - width := uint(1) - // - for _, e := range exprs { - if e != nil { - width *= e.Multiplicity() - } - } - // - return width -} diff --git a/pkg/corset/expr.go b/pkg/corset/expr.go new file mode 100644 index 0000000..adb02fd --- /dev/null +++ b/pkg/corset/expr.go @@ -0,0 +1,846 @@ +package corset + +import ( + "fmt" + "math/big" + + "github.com/consensys/go-corset/pkg/sexp" + tr "github.com/consensys/go-corset/pkg/trace" +) + +// Expr represents an arbitrary expression over the columns of a given context +// (or the parameters of an enclosing function). Such expressions are pitched +// at a higher-level than those of the underlying constraint system. For +// example, they can contain conditionals (i.e. if expressions) and +// normalisations, etc. During the lowering process down to the underlying +// constraints level (AIR), such expressions are "compiled out" using various +// techniques (such as introducing computed columns where necessary). +type Expr interface { + Node + // Evaluates this expression as a constant (signed) value. If this + // expression is not constant, then nil is returned. + AsConstant() *big.Int + // Multiplicity defines the number of values which will be returned when + // evaluating this expression. Due to the nature of expressions in Corset, + // they can (perhaps surprisingly) return multiple values. For example, + // lists return one value for each element in the list. Note, every + // expression must return at least one value. + Multiplicity() uint + + // Context returns the context for this expression. Observe that the + // expression must have been resolved for this to be defined (i.e. it may + // panic if it has not been resolved yet). + Context() Context + + // Substitute all variables (such as for function parameters) arising in + // this expression. + Substitute(args []Expr) Expr + + // Return set of columns on which this declaration depends. + Dependencies() []Symbol +} + +// Context represents the evaluation context for a given expression. +type Context = tr.RawContext[string] + +// ============================================================================ +// Addition +// ============================================================================ + +// Add represents the sum over zero or more expressions. +type Add struct{ Args []Expr } + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *Add) AsConstant() *big.Int { + fn := func(l *big.Int, r *big.Int) *big.Int { l.Add(l, r); return l } + return AsConstantOfExpressions(e.Args, fn) +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *Add) Multiplicity() uint { + return determineMultiplicity(e.Args) +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *Add) Context() Context { + return ContextOfExpressions(e.Args) +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *Add) Lisp() sexp.SExp { + return ListOfExpressions(sexp.NewSymbol("+"), e.Args) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *Add) Substitute(args []Expr) Expr { + return &Add{SubstituteExpressions(e.Args, args)} +} + +// Dependencies needed to signal declaration. +func (e *Add) Dependencies() []Symbol { + return DependenciesOfExpressions(e.Args) +} + +// ============================================================================ +// Constants +// ============================================================================ + +// Constant represents a constant value within an expression. +type Constant struct{ Val big.Int } + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *Constant) AsConstant() *big.Int { + return &e.Val +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *Constant) Multiplicity() uint { + return 1 +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *Constant) Context() Context { + return tr.VoidContext[string]() +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *Constant) Lisp() sexp.SExp { + return sexp.NewSymbol(e.Val.String()) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *Constant) Substitute(args []Expr) Expr { + return e +} + +// Dependencies needed to signal declaration. +func (e *Constant) Dependencies() []Symbol { + return nil +} + +// ============================================================================ +// Exponentiation +// ============================================================================ + +// Exp represents the a given value taken to a power. +type Exp struct { + Arg Expr + Pow Expr +} + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *Exp) AsConstant() *big.Int { + arg := e.Arg.AsConstant() + pow := e.Pow.AsConstant() + // Check if can evaluate + if arg != nil && pow != nil { + return arg.Exp(arg, pow, nil) + } + // + return nil +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *Exp) Multiplicity() uint { + return determineMultiplicity([]Expr{e.Arg}) +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *Exp) Context() Context { + return ContextOfExpressions([]Expr{e.Arg, e.Pow}) +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *Exp) Lisp() sexp.SExp { + return sexp.NewList([]sexp.SExp{ + sexp.NewSymbol("^"), + e.Arg.Lisp(), + e.Pow.Lisp()}) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *Exp) Substitute(args []Expr) Expr { + return &Exp{e.Arg.Substitute(args), e.Pow} +} + +// Dependencies needed to signal declaration. +func (e *Exp) Dependencies() []Symbol { + return DependenciesOfExpressions([]Expr{e.Arg, e.Pow}) +} + +// ============================================================================ +// IfZero +// ============================================================================ + +// IfZero returns the (optional) true branch when the condition evaluates to zero, and +// the (optional false branch otherwise. +type IfZero struct { + // Elements contained within this list. + Condition Expr + // True branch (optional). + TrueBranch Expr + // False branch (optional). + FalseBranch Expr +} + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *IfZero) AsConstant() *big.Int { + if condition := e.Condition.AsConstant(); condition != nil { + // Determine whether condition holds true (or not). + holds := condition.Cmp(big.NewInt(0)) == 0 + // + if holds && e.TrueBranch != nil { + return e.TrueBranch.AsConstant() + } else if !holds && e.FalseBranch != nil { + return e.FalseBranch.AsConstant() + } + } + // + return nil +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *IfZero) Multiplicity() uint { + return determineMultiplicity([]Expr{e.Condition, e.TrueBranch, e.FalseBranch}) +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *IfZero) Context() Context { + return ContextOfExpressions([]Expr{e.Condition, e.TrueBranch, e.FalseBranch}) +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *IfZero) Lisp() sexp.SExp { + if e.FalseBranch != nil { + return sexp.NewList([]sexp.SExp{ + sexp.NewSymbol("if"), + e.TrueBranch.Lisp(), + e.FalseBranch.Lisp()}) + } + // + return sexp.NewList([]sexp.SExp{ + sexp.NewSymbol("if"), + e.TrueBranch.Lisp()}) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *IfZero) Substitute(args []Expr) Expr { + return &IfZero{e.Condition.Substitute(args), + SubstituteOptionalExpression(e.TrueBranch, args), + SubstituteOptionalExpression(e.FalseBranch, args), + } +} + +// Dependencies needed to signal declaration. +func (e *IfZero) Dependencies() []Symbol { + return DependenciesOfExpressions([]Expr{e.Condition, e.TrueBranch, e.FalseBranch}) +} + +// ============================================================================ +// Function Invocation +// ============================================================================ + +// Invoke represents an attempt to invoke a given function. +type Invoke struct { + module *string + name string + args []Expr + binding *FunctionBinding +} + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *Invoke) AsConstant() *big.Int { + if e.binding == nil { + panic("unresolved invocation") + } + // Unroll body + body := e.binding.Apply(e.args) + // Attempt to evaluate as constant + return body.AsConstant() +} + +// IsQualified determines whether this symbol is qualfied or not (i.e. has an +// explicitly module specifier). +func (e *Invoke) IsQualified() bool { + return e.module != nil +} + +// IsFunction indicates whether or not this symbol refers to a function (which +// of course it always does). +func (e *Invoke) IsFunction() bool { + return true +} + +// IsResolved checks whether this symbol has been resolved already, or not. +func (e *Invoke) IsResolved() bool { + return e.binding != nil +} + +// Resolve this symbol by associating it with the binding associated with +// the definition of the symbol to which this refers. +func (e *Invoke) Resolve(binding Binding) bool { + if fb, ok := binding.(*FunctionBinding); ok { + e.binding = fb + return true + } + // Problem + return false +} + +// Module returns the optional module qualification. This will panic if this +// invocation is unqualified. +func (e *Invoke) Module() string { + if e.module == nil { + panic("invocation has no module qualifier") + } + + return *e.module +} + +// Name of the function being invoked. +func (e *Invoke) Name() string { + return e.name +} + +// Args returns the arguments provided by this invocation to the function being +// invoked. +func (e *Invoke) Args() []Expr { + return e.args +} + +// Binding gets binding associated with this interface. This will panic if this +// symbol is not yet resolved. +func (e *Invoke) Binding() Binding { + if e.binding == nil { + panic("invocation not yet resolved") + } + + return e.binding +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *Invoke) Context() Context { + if e.binding == nil { + panic("unresolved expressions encountered whilst resolving context") + } + // TODO: impure functions can have their own context. + return ContextOfExpressions(e.args) +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *Invoke) Multiplicity() uint { + // FIXME: is this always correct? + return 1 +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *Invoke) Lisp() sexp.SExp { + var fn sexp.SExp + if e.module != nil { + fn = sexp.NewSymbol(fmt.Sprintf("%s.%s", *e.module, e.name)) + } else { + fn = sexp.NewSymbol(e.name) + } + + return ListOfExpressions(fn, e.args) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *Invoke) Substitute(args []Expr) Expr { + return &Invoke{e.module, e.name, SubstituteExpressions(e.args, args), e.binding} +} + +// Dependencies needed to signal declaration. +func (e *Invoke) Dependencies() []Symbol { + deps := DependenciesOfExpressions(e.args) + // Include this expression as a symbol (which must be bound to the function + // being invoked) + return append(deps, e) +} + +// ============================================================================ +// List +// ============================================================================ + +// List represents a block of zero or more expressions. +type List struct{ Args []Expr } + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *List) AsConstant() *big.Int { + // Potentially we could do better here, but its not clear we need to. + return nil +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *List) Multiplicity() uint { + return determineMultiplicity(e.Args) +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *List) Context() Context { + return ContextOfExpressions(e.Args) +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *List) Lisp() sexp.SExp { + return ListOfExpressions(sexp.NewSymbol("begin"), e.Args) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *List) Substitute(args []Expr) Expr { + return &List{SubstituteExpressions(e.Args, args)} +} + +// Dependencies needed to signal declaration. +func (e *List) Dependencies() []Symbol { + return DependenciesOfExpressions(e.Args) +} + +// ============================================================================ +// Multiplication +// ============================================================================ + +// Mul represents the product over zero or more expressions. +type Mul struct{ Args []Expr } + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *Mul) AsConstant() *big.Int { + fn := func(l *big.Int, r *big.Int) *big.Int { l.Mul(l, r); return l } + return AsConstantOfExpressions(e.Args, fn) +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *Mul) Multiplicity() uint { + return determineMultiplicity(e.Args) +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *Mul) Context() Context { + return ContextOfExpressions(e.Args) +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *Mul) Lisp() sexp.SExp { + return ListOfExpressions(sexp.NewSymbol("*"), e.Args) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *Mul) Substitute(args []Expr) Expr { + return &Mul{SubstituteExpressions(e.Args, args)} +} + +// Dependencies needed to signal declaration. +func (e *Mul) Dependencies() []Symbol { + return DependenciesOfExpressions(e.Args) +} + +// ============================================================================ +// Normalise +// ============================================================================ + +// Normalise reduces the value of an expression to either zero (if it was zero) +// or one (otherwise). +type Normalise struct{ Arg Expr } + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *Normalise) AsConstant() *big.Int { + // FIXME: we could do better here. + return nil +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *Normalise) Multiplicity() uint { + return determineMultiplicity([]Expr{e.Arg}) +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *Normalise) Context() Context { + return ContextOfExpressions([]Expr{e.Arg}) +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *Normalise) Lisp() sexp.SExp { + return sexp.NewList([]sexp.SExp{ + sexp.NewSymbol("~"), + e.Arg.Lisp()}) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *Normalise) Substitute(args []Expr) Expr { + return &Normalise{e.Arg.Substitute(args)} +} + +// Dependencies needed to signal declaration. +func (e *Normalise) Dependencies() []Symbol { + return e.Arg.Dependencies() +} + +// ============================================================================ +// Subtraction +// ============================================================================ + +// Sub represents the subtraction over zero or more expressions. +type Sub struct{ Args []Expr } + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *Sub) AsConstant() *big.Int { + fn := func(l *big.Int, r *big.Int) *big.Int { l.Sub(l, r); return l } + return AsConstantOfExpressions(e.Args, fn) +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *Sub) Multiplicity() uint { + return determineMultiplicity(e.Args) +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *Sub) Context() Context { + return ContextOfExpressions(e.Args) +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *Sub) Lisp() sexp.SExp { + return ListOfExpressions(sexp.NewSymbol("-"), e.Args) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *Sub) Substitute(args []Expr) Expr { + return &Sub{SubstituteExpressions(e.Args, args)} +} + +// Dependencies needed to signal declaration. +func (e *Sub) Dependencies() []Symbol { + return DependenciesOfExpressions(e.Args) +} + +// ============================================================================ +// Shift +// ============================================================================ + +// Shift represents the result of a given expression shifted by a certain +// amount. In reality, the shift amount must be statically known. However, it +// is represented here as an expression to allow for constants and the results +// of function invocations, etc to be used. In all cases, these must still be +// eventually translated into constant values however. +type Shift struct { + // The expression being shifted + Arg Expr + // The amount it is being shifted by. + Shift Expr +} + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *Shift) AsConstant() *big.Int { + // Observe the shift doesn't matter as, in the case that the argument is a + // constant, then the shift has no effect anyway. + return e.Arg.AsConstant() +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *Shift) Multiplicity() uint { + return determineMultiplicity([]Expr{e.Arg}) +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *Shift) Context() Context { + return ContextOfExpressions([]Expr{e.Arg, e.Shift}) +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed. +func (e *Shift) Lisp() sexp.SExp { + return sexp.NewList([]sexp.SExp{ + sexp.NewSymbol("shift"), + e.Arg.Lisp(), + e.Shift.Lisp()}) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *Shift) Substitute(args []Expr) Expr { + return &Shift{e.Arg.Substitute(args), e.Shift.Substitute(args)} +} + +// Dependencies needed to signal declaration. +func (e *Shift) Dependencies() []Symbol { + return DependenciesOfExpressions([]Expr{e.Arg, e.Shift}) +} + +// ============================================================================ +// VariableAccess +// ============================================================================ + +// VariableAccess represents reading the value of a given local variable (such +// as a function parameter). +type VariableAccess struct { + module *string + name string + binding Binding +} + +// AsConstant attempts to evaluate this expression as a constant (signed) value. +// If this expression is not constant, then nil is returned. +func (e *VariableAccess) AsConstant() *big.Int { + if binding, ok := e.binding.(*ConstantBinding); ok { + return binding.value.AsConstant() + } + // not a constant + return nil +} + +// IsQualified determines whether this symbol is qualfied or not (i.e. has an +// explicitly module specifier). +func (e *VariableAccess) IsQualified() bool { + return e.module != nil +} + +// IsFunction determines whether this symbol refers to a function (which, of +// course, variable accesses never do). +func (e *VariableAccess) IsFunction() bool { + return false +} + +// IsResolved checks whether this symbol has been resolved already, or not. +func (e *VariableAccess) IsResolved() bool { + return e.binding != nil +} + +// Resolve this symbol by associating it with the binding associated with +// the definition of the symbol to which this refers. +func (e *VariableAccess) Resolve(binding Binding) bool { + if binding == nil { + panic("empty binding") + } else if e.binding != nil { + panic("already resolved") + } + // + e.binding = binding + // + return true +} + +// Module returns the optional module qualification. This will panic if this +// invocation is unqualified. +func (e *VariableAccess) Module() string { + return *e.module +} + +// Name returns the (unqualified) name of this symbol +func (e *VariableAccess) Name() string { + return e.name +} + +// Binding gets binding associated with this interface. This will panic if this +// symbol is not yet resolved. +func (e *VariableAccess) Binding() Binding { + if e.binding == nil { + panic("variable access is unresolved") + } + // + return e.binding +} + +// Multiplicity determines the number of values that evaluating this expression +// can generate. +func (e *VariableAccess) Multiplicity() uint { + return 1 +} + +// Context returns the context for this expression. Observe that the +// expression must have been resolved for this to be defined (i.e. it may +// panic if it has not been resolved yet). +func (e *VariableAccess) Context() Context { + binding, ok := e.binding.(*ColumnBinding) + // + if ok { + return binding.Context() + } + // + panic("invalid column access") +} + +// Lisp converts this schema element into a simple S-Expression, for example +// so it can be printed.a +func (e *VariableAccess) Lisp() sexp.SExp { + var name string + if e.module != nil { + name = fmt.Sprintf("%s.%s", *e.module, e.name) + } else { + name = e.name + } + // + return sexp.NewSymbol(name) +} + +// Substitute all variables (such as for function parameters) arising in +// this expression. +func (e *VariableAccess) Substitute(args []Expr) Expr { + if b, ok := e.binding.(*ParameterBinding); ok { + return args[b.index] + } + // Nothing to do here + return e +} + +// Dependencies needed to signal declaration. +func (e *VariableAccess) Dependencies() []Symbol { + return []Symbol{e} +} + +// ============================================================================ +// Helpers +// ============================================================================ + +// ContextOfExpressions returns the context for a set of zero or more +// expressions. Observe that, if there the expressions have no context (i.e. +// they are all constants) then the void context is returned. Likewise, if +// there are expressions with different contexts then the conflicted context +// will be returned. Otherwise, the one consistent context will be returned. +func ContextOfExpressions(exprs []Expr) Context { + context := tr.VoidContext[string]() + // + for _, e := range exprs { + context = context.Join(e.Context()) + } + // + return context +} + +// SubstituteExpressions substitutes all variables found in a given set of +// expressions. +func SubstituteExpressions(exprs []Expr, vars []Expr) []Expr { + nexprs := make([]Expr, len(exprs)) + // + for i := 0; i < len(nexprs); i++ { + nexprs[i] = exprs[i].Substitute(vars) + } + // + return nexprs +} + +// SubstituteOptionalExpression substitutes through an expression which is +// optional (i.e. might be nil). In such case, nil is returned. +func SubstituteOptionalExpression(expr Expr, vars []Expr) Expr { + if expr != nil { + expr = expr.Substitute(vars) + } + // + return expr +} + +// DependenciesOfExpressions determines the dependencies for a given set of zero +// or more expressions. +func DependenciesOfExpressions(exprs []Expr) []Symbol { + var deps []Symbol + // + for _, e := range exprs { + if e != nil { + deps = append(deps, e.Dependencies()...) + } + } + // + return deps +} + +// ListOfExpressions converts an array of one or more expressions into a list of +// corresponding lisp expressions. +func ListOfExpressions(head sexp.SExp, exprs []Expr) *sexp.List { + lisps := make([]sexp.SExp, len(exprs)+1) + // Assign head + lisps[0] = head + // + for i, e := range exprs { + lisps[i+1] = e.Lisp() + } + // + return sexp.NewList(lisps) +} + +// AsConstantOfExpressions attempts to fold one or more expressions across a +// given operation (e.g. add, subtract, etc) to produce a constant value. If +// any of the expressions are not themselves constant, then neither is the +// result. +func AsConstantOfExpressions(exprs []Expr, fn func(*big.Int, *big.Int) *big.Int) *big.Int { + var val *big.Int = big.NewInt(0) + // + for _, arg := range exprs { + c := arg.AsConstant() + if c == nil { + return nil + } + // Evaluate function + val = fn(val, c) + } + // + return val +} + +func determineMultiplicity(exprs []Expr) uint { + width := uint(1) + // + for _, e := range exprs { + if e != nil { + width *= e.Multiplicity() + } + } + // + return width +} diff --git a/pkg/corset/parser.go b/pkg/corset/parser.go index 438a87b..b667543 100644 --- a/pkg/corset/parser.go +++ b/pkg/corset/parser.go @@ -293,7 +293,7 @@ func (p *Parser) parseDefAlias(functions bool, elements []sexp.SExp) (Declaratio errors = append(errors, *p.translator.SyntaxError(elements[i+1], "invalid alias definition")) } else { alias := &DefAlias{elements[i].AsSymbol().Value} - name := &Name{elements[i+1].AsSymbol().Value, functions, nil} + name := NewName[Binding](elements[i+1].AsSymbol().Value, functions) p.mapSourceNode(elements[i], alias) p.mapSourceNode(elements[i+1], name) // @@ -454,7 +454,7 @@ func (p *Parser) parseDefInterleaved(module string, elements []sexp.SExp) (Decla return nil, p.translator.SyntaxError(ith, "malformed source column") } // Extract column name - sources[i] = &Name{ith.AsSymbol().Value, false, nil} + sources[i] = NewColumnName(ith.AsSymbol().Value) p.mapSourceNode(ith, sources[i]) } // @@ -538,10 +538,10 @@ func (p *Parser) parseDefPermutation(module string, elements []sexp.SExp) (Decla return &DefPermutation{targets, sources, signs}, nil } -func (p *Parser) parsePermutedColumnDeclaration(signRequired bool, e sexp.SExp) (*Name, bool, *SyntaxError) { +func (p *Parser) parsePermutedColumnDeclaration(signRequired bool, e sexp.SExp) (*ColumnName, bool, *SyntaxError) { var ( err *SyntaxError - name *Name + name *ColumnName sign bool ) // Check whether extended declaration or not. @@ -559,11 +559,11 @@ func (p *Parser) parsePermutedColumnDeclaration(signRequired bool, e sexp.SExp) return nil, false, err } // Parse column name - name = &Name{l.Get(1).AsSymbol().Value, false, nil} + name = NewColumnName(l.Get(1).AsSymbol().Value) } else if signRequired { return nil, false, p.translator.SyntaxError(e, "missing sort direction") } else { - name = &Name{e.String(false), false, nil} + name = NewColumnName(e.String(false)) } // Update source mapping p.mapSourceNode(e, name) diff --git a/pkg/corset/resolver.go b/pkg/corset/resolver.go index 67b75a7..898d050 100644 --- a/pkg/corset/resolver.go +++ b/pkg/corset/resolver.go @@ -238,7 +238,7 @@ func (r *resolver) declarationDependenciesAreFinalised(scope *ModuleScope, errors = append(errors, *r.srcmap.SyntaxError(symbol, "unknown symbol")) // not finalised yet finalised = false - } else if !symbol.Binding().IsFinalised() { + } else if symbol.IsResolved() && !symbol.Binding().IsFinalised() { // no, not finalised finalised = false } diff --git a/pkg/corset/scope.go b/pkg/corset/scope.go index bc589d0..7f5388c 100644 --- a/pkg/corset/scope.go +++ b/pkg/corset/scope.go @@ -133,9 +133,7 @@ func (p *ModuleScope) Bind(symbol Symbol) bool { // Extract binding binding := p.bindings[bid] // Resolve symbol - symbol.Resolve(binding) - // Success - return true + return symbol.Resolve(binding) } // failed return false @@ -286,9 +284,7 @@ func (p LocalScope) Bind(symbol Symbol) bool { // Check whether this is a local variable access. if id, ok := p.locals[symbol.Name()]; ok && !symbol.IsFunction() && !symbol.IsQualified() { // Yes, this is a local variable access. - symbol.Resolve(&ParameterBinding{id}) - // Success - return true + return symbol.Resolve(&ParameterBinding{id}) } // No, this is not a local variable access. return p.enclosing.Bind(symbol) diff --git a/pkg/corset/symbol.go b/pkg/corset/symbol.go new file mode 100644 index 0000000..c8a7ed5 --- /dev/null +++ b/pkg/corset/symbol.go @@ -0,0 +1,135 @@ +package corset + +import "github.com/consensys/go-corset/pkg/sexp" + +// Symbol represents a variable or function access within a declaration. +// Initially, such the proper interpretation of such accesses is unclear and it +// is only later when we can distinguish them (e.g. whether its a column access, +// a constant access, etc). +type Symbol interface { + Node + // Determines whether this symbol is qualfied or not (i.e. has an explicitly + // module specifier). + IsQualified() bool + // Indicates whether or not this is a function. + IsFunction() bool + // Checks whether this symbol has been resolved already, or not. + IsResolved() bool + // Optional module qualification + Module() string + // Name of the symbol + Name() string + // Get binding associated with this interface. This will panic if this + // symbol is not yet resolved. + Binding() Binding + // Resolve this symbol by associating it with the binding associated with + // the definition of the symbol to which this refers. Observe that + // resolution can fail if we cannot bind the symbol to the given binding + // (e.g. a function binding was provided, but we're expecting a column + // binding). + Resolve(Binding) bool +} + +// SymbolDefinition represents a declaration (or part thereof) which defines a +// particular symbol. For example, "defcolumns" will define one or more symbols +// representing columns, etc. +type SymbolDefinition interface { + Node + // Name of symbol being defined + Name() string + // Indicates whether or not this is a function definition. + IsFunction() bool + // Allocated binding for the symbol which may or may not be finalised. + Binding() Binding +} + +// ColumnName represents a name used in a position where it can only be resolved +// against a column. +type ColumnName = Name[*ColumnBinding] + +// NewColumnName construct a new column name which is (initially) unresolved. +func NewColumnName(name string) *ColumnName { + return &ColumnName{name, false, nil, false} +} + +// Name represents a name within some syntactic item. Essentially this wraps a +// string and provides a mechanism for it to be associated with source line +// information. +type Name[T Binding] struct { + // Name of symbol + name string + // Indicates whether represents function or something else. + function bool + // Binding constructed for symbol. + binding T + // Indicates whether resolved. + resolved bool +} + +// NewName construct a new name which is (initially) unresolved. +func NewName[T Binding](name string, function bool) *Name[T] { + // Default value for type T + var empty T + // Construct the name + return &Name[T]{name, function, empty, false} +} + +// IsQualified determines whether this symbol is qualfied or not (i.e. has an +// explicit module specifier). Column names are never qualified. +func (e *Name[T]) IsQualified() bool { + return false +} + +// IsFunction indicates whether or not this symbol refers to a function (which +// of course it never does). +func (e *Name[T]) IsFunction() bool { + return e.function +} + +// IsResolved checks whether this symbol has been resolved already, or not. +func (e *Name[T]) IsResolved() bool { + return e.resolved +} + +// Module returns the optional module qualification. This always panics because +// column name's are never qualified. +func (e *Name[T]) Module() string { + panic("undefined") +} + +// Name returns the (unqualified) name of the column to which this symbol +// refers. +func (e *Name[T]) Name() string { + return e.name +} + +// Binding gets binding associated with this interface. This will panic if this +// symbol is not yet resolved. +func (e *Name[T]) Binding() Binding { + if !e.resolved { + panic("name not yet resolved") + } + // + return e.binding +} + +// Resolve this symbol by associating it with the binding associated with +// the definition of the symbol to which this refers. +func (e *Name[T]) Resolve(binding Binding) bool { + var ok bool + // + if e.resolved { + panic("name already resolved") + } + // Attempt to assign binding. + e.binding, ok = binding.(T) + e.resolved = ok + // + return ok +} + +// Lisp converts this node into its lisp representation. This is primarily used +// for debugging purposes. +func (e *Name[T]) Lisp() sexp.SExp { + return sexp.NewSymbol(e.name) +} diff --git a/pkg/test/invalid_corset_test.go b/pkg/test/invalid_corset_test.go index 45d2255..c75ac1c 100644 --- a/pkg/test/invalid_corset_test.go +++ b/pkg/test/invalid_corset_test.go @@ -292,6 +292,10 @@ func Test_Invalid_Permute_07(t *testing.T) { CheckInvalid(t, "permute_invalid_07") } +func Test_Invalid_Permute_08(t *testing.T) { + CheckInvalid(t, "permute_invalid_08") +} + // =================================================================== // Lookups // =================================================================== @@ -367,6 +371,18 @@ func Test_Invalid_Interleave_09(t *testing.T) { CheckInvalid(t, "interleave_invalid_09") } +func Test_Invalid_Interleave_10(t *testing.T) { + CheckInvalid(t, "interleave_invalid_10") +} + +func Test_Invalid_Interleave_11(t *testing.T) { + CheckInvalid(t, "interleave_invalid_11") +} + +func Test_Invalid_Interleave_12(t *testing.T) { + CheckInvalid(t, "interleave_invalid_12") +} + // =================================================================== // Functions // =================================================================== diff --git a/testdata/interleave_invalid_11.lisp b/testdata/interleave_invalid_11.lisp new file mode 100644 index 0000000..caf3368 --- /dev/null +++ b/testdata/interleave_invalid_11.lisp @@ -0,0 +1,3 @@ +(defcolumns X) +(defconst Y 1) +(definterleaved Z (X Y)) diff --git a/testdata/interleave_invalid_12.lisp b/testdata/interleave_invalid_12.lisp new file mode 100644 index 0000000..cf3ce2b --- /dev/null +++ b/testdata/interleave_invalid_12.lisp @@ -0,0 +1,3 @@ +(defcolumns Y) +(defconst X 1) +(definterleaved Z (X Y)) diff --git a/testdata/permute_invalid_08.lisp b/testdata/permute_invalid_08.lisp new file mode 100644 index 0000000..527e13b --- /dev/null +++ b/testdata/permute_invalid_08.lisp @@ -0,0 +1,2 @@ +(defconst X 100) +(defpermutation (Y) ((+ X)))