Skip to content

Commit 77a64a3

Browse files
authored
feat: enable recursive definition for a defcomputedcolumn (#1088)
* add tests / updated parser This adds some tests for recursive computed columns, and updates the parser to support the annotations ":fwd" and ":bwd". * Recursive computation now operational * detect recursive definitions This adds support for detecting and report errors for recursive definitions. * mark tests as ignored (for now) These tests cannot be resolved without further work, and have been marked as ignored (alongw with issue number). * fix ModuleScope.IsVisible() This was not sufficiently flexible to account for all lookup scenarios. Instead, have copied the essential structure of Bind() to get this to work correctly.
1 parent cfe21d6 commit 77a64a3

36 files changed

+640
-121
lines changed

pkg/corset/ast/binding.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
type Binding interface {
2525
// Determine whether this binding is finalised or not.
2626
IsFinalised() bool
27+
// Determine whether this binding can be defined recursively or not.
28+
IsRecursive() bool
2729
}
2830

2931
// FunctionBinding is a special kind of binding which captures the essence of
@@ -99,6 +101,21 @@ func (p *FunctionSignature) Apply(args []Expr, srcmap *source.Maps[Node]) Expr {
99101
// ColumnBinding
100102
// ============================================================================
101103

104+
const (
105+
// NOT_COMPUTED signals a column is not a computed column.
106+
NOT_COMPUTED = 0
107+
// COMPUTED signals a column is a (non-recursive) computed column.
108+
COMPUTED = 1
109+
// COMPUTED_FWD signals a column is a (forward recursive) computed column.
110+
// This means its value is computed starting from the first row (hence it
111+
// cannot use a forward shift in its declaration).
112+
COMPUTED_FWD = 2
113+
// COMPUTED_BWD signals a column is a (backward recursive) computed column.
114+
// This means its value is computed starting from the first row (hence it
115+
// cannot use a backward shift in its declaration).
116+
COMPUTED_BWD = 3
117+
)
118+
102119
// ColumnBinding represents something bound to a given column.
103120
type ColumnBinding struct {
104121
// Context determines the real (i.e. non-virtual) enclosing module of this
@@ -115,8 +132,8 @@ type ColumnBinding struct {
115132
MustProve bool
116133
// Column's length Multiplier
117134
Multiplier uint
118-
// Determines whether this is a Computed column, or not.
119-
Computed bool
135+
// Determines the kind of this column.
136+
Kind uint8
120137
// Display modifier
121138
Display string
122139
}
@@ -126,11 +143,21 @@ func (p *ColumnBinding) AbsolutePath() *util.Path {
126143
return &p.Path
127144
}
128145

146+
// IsComputed checks whether this binding is for a computed column (or not).
147+
func (p *ColumnBinding) IsComputed() bool {
148+
return p.Kind != NOT_COMPUTED
149+
}
150+
129151
// IsFinalised checks whether this binding has been finalised yet or not.
130152
func (p *ColumnBinding) IsFinalised() bool {
131153
return p.Multiplier != 0
132154
}
133155

156+
// IsRecursive implementation for Binding interface.
157+
func (p *ColumnBinding) IsRecursive() bool {
158+
return p.Kind == COMPUTED_FWD || p.Kind == COMPUTED_BWD
159+
}
160+
134161
// Finalise this binding by providing the necessary missing information.
135162
func (p *ColumnBinding) Finalise(multiplier uint, datatype Type) {
136163
p.Multiplier = multiplier
@@ -175,6 +202,12 @@ func (p *ConstantBinding) IsFinalised() bool {
175202
return p.finalised
176203
}
177204

205+
// IsRecursive implementation for Binding interface.
206+
func (p *ConstantBinding) IsRecursive() bool {
207+
// Constants can never be defined recursively
208+
return false
209+
}
210+
178211
// Finalise this binding.
179212
func (p *ConstantBinding) Finalise() {
180213
p.finalised = true
@@ -211,6 +244,11 @@ func (p *LocalVariableBinding) IsFinalised() bool {
211244
return p.Index != math.MaxUint
212245
}
213246

247+
// IsRecursive implementation for Binding interface.
248+
func (p *LocalVariableBinding) IsRecursive() bool {
249+
return false
250+
}
251+
214252
// Finalise this local variable binding by allocating it an identifier.
215253
func (p *LocalVariableBinding) Finalise(index uint) {
216254
p.Index = index
@@ -258,6 +296,12 @@ func (p *DefunBinding) IsFinalised() bool {
258296
return p.finalised
259297
}
260298

299+
// IsRecursive implementation for Binding interface.
300+
func (p *DefunBinding) IsRecursive() bool {
301+
// Functions can never be defined recursively (for now, at least).
302+
return false
303+
}
304+
261305
// Signature returns the corresponding function signature for this user-defined
262306
// function.
263307
func (p *DefunBinding) Signature() *FunctionSignature {
@@ -294,6 +338,12 @@ func (p *PerspectiveBinding) IsFinalised() bool {
294338
return p.resolved
295339
}
296340

341+
// IsRecursive implementation for Binding interface.
342+
func (p *PerspectiveBinding) IsRecursive() bool {
343+
// Recursive perspectives don't make sense!
344+
return false
345+
}
346+
297347
// Finalise this binding, which indicates the selector expression has been
298348
// finalised.
299349
func (p *PerspectiveBinding) Finalise() {

pkg/corset/ast/declaration.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,17 +214,15 @@ var _ SymbolDefinition = &DefColumn{}
214214
// NewDefColumn constructs a new (non-computed) column declaration. Such a
215215
// column is automatically finalised, since all information is provided at the
216216
// point of creation.
217-
func NewDefColumn(context util.Path, name util.Path, datatype Type, mustProve bool, multiplier uint,
218-
computed bool, display string) *DefColumn {
219-
binding := ColumnBinding{context, name, datatype, mustProve, multiplier, computed, display}
217+
func NewDefColumn(binding ColumnBinding) *DefColumn {
220218
return &DefColumn{binding}
221219
}
222220

223221
// NewDefComputedColumn constructs a new column declaration for a computed
224222
// column. Such a column cannot be finalised yet, since its type and multiplier
225223
// remains to be determined, etc.
226224
func NewDefComputedColumn(context util.Path, name util.Path) *DefColumn {
227-
binding := ColumnBinding{context, name, nil, false, 0, true, "hex"}
225+
binding := ColumnBinding{context, name, nil, false, 0, COMPUTED, "hex"}
228226
return &DefColumn{binding}
229227
}
230228

@@ -240,6 +238,12 @@ func (e *DefColumn) Binding() Binding {
240238
return &e.binding
241239
}
242240

241+
// InnerBinding returns the allocated binding for this symbol (which may or may
242+
// not be finalised).
243+
func (e *DefColumn) InnerBinding() ColumnBinding {
244+
return e.binding
245+
}
246+
243247
// Name returns the (unqualified) name of this symbol. For example, "X" for
244248
// a column X defined in a module m1.
245249
func (e *DefColumn) Name() string {

pkg/corset/compiler/environment.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func (p *GlobalEnvironment) initColumnsAndRegisters(modules []*ModuleScope) {
170170
// Apply aliases
171171
for _, m := range modules {
172172
for id, binding_id := range m.ids {
173-
if binding, ok := m.bindings[binding_id].(*ast.ColumnBinding); ok && !id.IsFunction() {
173+
if binding, ok := m.bindings[binding_id].binding.(*ast.ColumnBinding); ok && !id.IsFunction() {
174174
orig := binding.Path.String()
175175
alias := m.path.Extend(id.name).String()
176176
p.columnMap[alias] = p.columnMap[orig]

pkg/corset/compiler/externs.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,20 @@ type ExternSymbolDefinition struct {
4242
// NewExternSymbolDefinition creates a new external symbol definition for a
4343
// given register in a given module.
4444
func NewExternSymbolDefinition(path util.Path, reg schema.Register) *ExternSymbolDefinition {
45+
var kind uint8 = ast.NOT_COMPUTED
46+
//
47+
if reg.IsComputed() {
48+
kind = ast.COMPUTED
49+
}
50+
//
4551
return &ExternSymbolDefinition{
4652
binding: ast.ColumnBinding{
4753
ColumnContext: path,
4854
Path: *path.Extend(reg.Name),
4955
DataType: ast.NewUintType(reg.Width),
5056
MustProve: true,
5157
Multiplier: 1,
52-
Computed: reg.IsComputed(),
58+
Kind: kind,
5359
Display: "hex", // default
5460
},
5561
}

pkg/corset/compiler/intrinsics.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ func (p *IntrinsicDefinition) IsFinalised() bool {
6868
return true
6969
}
7070

71+
// IsRecursive implementation for Binding interface.
72+
func (p *IntrinsicDefinition) IsRecursive() bool {
73+
return false
74+
}
75+
7176
// Binding returns the binding associated with this intrinsic.
7277
func (p *IntrinsicDefinition) Binding() ast.Binding {
7378
return p

pkg/corset/compiler/natives.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ func (p *NativeDefinition) IsNative() bool {
6464
return true
6565
}
6666

67+
// IsRecursive implementation for Binding interface.
68+
func (p *NativeDefinition) IsRecursive() bool {
69+
return false
70+
}
71+
6772
// Arity indicates whether or not this is a function and, if so, what arity
6873
// (i.e. how many arguments) the function has.
6974
func (p *NativeDefinition) Arity() util.Option[uint] {

pkg/corset/compiler/parser.go

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -388,13 +388,20 @@ func (p *Parser) parseColumnDeclaration(context util.Path, path util.Path, compu
388388
e sexp.SExp) (*ast.DefColumn, *SyntaxError) {
389389
//
390390
var (
391-
error *SyntaxError
392-
name util.Path
393-
multiplier uint = 1
394-
datatype ast.Type
395-
mustProve bool
396-
display string
391+
error *SyntaxError
392+
// Initial binding with defaults
393+
binding = ast.ColumnBinding{
394+
ColumnContext: context,
395+
Kind: ast.NOT_COMPUTED,
396+
Multiplier: 1,
397+
MustProve: false,
398+
Display: "hex",
399+
}
397400
)
401+
// Update computed status
402+
if computed {
403+
binding.Kind = ast.COMPUTED
404+
}
398405
// Check whether extended declaration or not.
399406
if l := e.AsList(); l != nil {
400407
// Check at least the name provided.
@@ -404,43 +411,40 @@ func (p *Parser) parseColumnDeclaration(context util.Path, path util.Path, compu
404411
return nil, p.translator.SyntaxError(l.Elements[0], "invalid column name")
405412
}
406413
// Column name is always first
407-
name = *path.Extend(l.Elements[0].String(false))
414+
binding.Path = *path.Extend(l.Elements[0].String(false))
408415
// Parse type (if applicable)
409-
if datatype, mustProve, display, error = p.parseColumnDeclarationAttributes(e, l.Elements[1:]); error != nil {
416+
if binding, error = p.parseColumnDeclarationAttributes(e, binding, l.Elements[1:]); error != nil {
410417
return nil, error
411418
}
412419
} else if computed {
413420
// Only computed columns can be given without attributes.
414-
name = *path.Extend(e.String(false))
421+
binding.Path = *path.Extend(e.String(false))
415422
} else {
416423
return nil, p.translator.SyntaxError(e, "column is untyped")
417424
}
418425
// Final sanity checks
419-
if computed && datatype == nil {
426+
if computed && binding.DataType == nil {
420427
// computed columns initially have multiplier 0 in order to signal that
421428
// this needs to be subsequently determined from context.
422-
multiplier = 0
423-
datatype = ast.INT_TYPE
424-
} else if !datatype.HasUnderlying() {
429+
binding.Multiplier = 0
430+
binding.DataType = ast.INT_TYPE
431+
} else if !binding.DataType.HasUnderlying() {
425432
return nil, p.translator.SyntaxError(e, "invalid column type")
426433
}
427434
//
428-
def := ast.NewDefColumn(context, name, datatype, mustProve, multiplier, computed, display)
435+
def := ast.NewDefColumn(binding)
429436
// Update source mapping
430437
p.mapSourceNode(e, def)
431438
//
432439
return def, nil
433440
}
434441

435-
func (p *Parser) parseColumnDeclarationAttributes(node sexp.SExp, attrs []sexp.SExp) (ast.Type, bool, string,
436-
*SyntaxError) {
442+
func (p *Parser) parseColumnDeclarationAttributes(node sexp.SExp, binding ast.ColumnBinding,
443+
attrs []sexp.SExp) (ast.ColumnBinding, *SyntaxError) {
437444
//
438445
var (
439-
dataType ast.Type
440-
mustProve bool = false
441446
array_min uint
442447
array_max uint
443-
display string = "hex"
444448
err *SyntaxError
445449
)
446450

@@ -449,49 +453,67 @@ func (p *Parser) parseColumnDeclarationAttributes(node sexp.SExp, attrs []sexp.S
449453
symbol := ith.AsSymbol()
450454
// Sanity check
451455
if symbol == nil {
452-
return nil, false, "", p.translator.SyntaxError(ith, "unknown column attribute")
456+
return binding, p.translator.SyntaxError(ith, "unknown column attribute")
453457
}
454458
//
455459
switch symbol.Value {
456460
case ":display":
457461
// skip these for now, as they are only relevant to the inspector.
458462
if i+1 == len(attrs) {
459-
return nil, false, "", p.translator.SyntaxError(ith, "incomplete display definition")
463+
return binding, p.translator.SyntaxError(ith, "incomplete display definition")
460464
} else if attrs[i+1].AsSymbol() == nil {
461-
return nil, false, "", p.translator.SyntaxError(ith, "malformed display definition")
465+
return binding, p.translator.SyntaxError(ith, "malformed display definition")
462466
}
463467
//
464-
display = attrs[i+1].AsSymbol().String(false)
468+
binding.Display = attrs[i+1].AsSymbol().String(false)
465469
// Check what display attribute we have
466-
switch display {
470+
switch binding.Display {
467471
case ":dec", ":hex", ":bytes", ":opcode":
468-
display = display[1:]
472+
binding.Display = binding.Display[1:]
469473
// all good
470474
i = i + 1
471475
default:
472476
// not good
473-
return nil, false, "", p.translator.SyntaxError(ith, "unknown display definition")
477+
return binding, p.translator.SyntaxError(ith, "unknown display definition")
474478
}
475479
case ":array":
476480
if array_min, array_max, err = p.parseArrayDimension(attrs[i+1]); err != nil {
477-
return nil, false, "", err
481+
return binding, err
478482
}
479483
// skip dimension
480484
i++
485+
case ":fwd":
486+
switch binding.Kind {
487+
case ast.NOT_COMPUTED:
488+
return binding, p.translator.SyntaxError(ith, "input columns cannot be recursive")
489+
case ast.COMPUTED_BWD:
490+
return binding, p.translator.SyntaxError(ith, "conflicting direction of recursion")
491+
default:
492+
binding.Kind = ast.COMPUTED_FWD
493+
}
494+
case ":bwd":
495+
switch binding.Kind {
496+
case ast.NOT_COMPUTED:
497+
return binding, p.translator.SyntaxError(ith, "input columns cannot be recursive")
498+
case ast.COMPUTED_FWD:
499+
return binding, p.translator.SyntaxError(ith, "conflicting direction of recursion")
500+
default:
501+
binding.Kind = ast.COMPUTED_BWD
502+
}
481503
default:
482-
if dataType, mustProve, err = p.parseType(ith); err != nil {
483-
return nil, false, "", err
504+
if binding.DataType, binding.MustProve, err = p.parseType(ith); err != nil {
505+
return binding, err
484506
}
485507
}
486508
}
487509
// Done
488-
if dataType == nil {
489-
return nil, false, "", p.translator.SyntaxError(node, "column is untyped")
510+
if binding.DataType == nil {
511+
return binding, p.translator.SyntaxError(node, "column is untyped")
490512
} else if array_max != 0 {
491-
return ast.NewArrayType(dataType, array_min, array_max), mustProve, display, nil
513+
binding.DataType = ast.NewArrayType(binding.DataType, array_min, array_max)
492514
}
493-
//
494-
return dataType, mustProve, display, nil
515+
// Success!
516+
return binding, nil
495517
}
496518

497519
func (p *Parser) parseArrayDimension(s sexp.SExp) (uint, uint, *SyntaxError) {

0 commit comments

Comments
 (0)