Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

412 implement defalias #420

Merged
merged 3 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions pkg/corset/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,62 @@ func (e *ColumnName) Lisp() sexp.SExp {
return sexp.NewSymbol(e.name)
}

// ============================================================================
// defalias
// ============================================================================

// DefAliases represents the declaration of one or more aliases. That is,
// alternate names for existing symbols.
type DefAliases struct {
// Aliases
aliases []*DefAlias
// Symbols being aliased
symbols []Symbol
}

// Dependencies needed to signal declaration.
func (p *DefAliases) Dependencies() util.Iterator[Symbol] {
return util.NewArrayIterator[Symbol](nil)
}

// Definitions returns the set of symbols defined by this declaration. Observe
// that these may not yet have been finalised.
func (p *DefAliases) Definitions() util.Iterator[SymbolDefinition] {
return util.NewArrayIterator[SymbolDefinition](nil)
}

// Lisp converts this node into its lisp representation. This is primarily used
// for debugging purposes.
//
//nolint:revive
func (p *DefAliases) Lisp() sexp.SExp {
pairs := sexp.EmptyList()
//
for i, a := range p.aliases {
pairs.Append(sexp.NewSymbol(a.name))
pairs.Append(p.symbols[i].Lisp())
}
//
return sexp.NewList([]sexp.SExp{
sexp.NewSymbol("defalias"),
pairs,
})
}

// DefAlias provides a node on which to hang source information to an alias name.
type DefAlias struct {
// Name of the alias
name string
}

// Lisp converts this node into its lisp representation. This is primarily used
// for debugging purposes.
//
//nolint:revive
func (p *DefAlias) Lisp() sexp.SExp {
return sexp.NewSymbol(p.name)
}

// ============================================================================
// defcolumns
// ============================================================================
Expand Down
37 changes: 36 additions & 1 deletion pkg/corset/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ func (p *Parser) parseDeclaration(module string, s *sexp.List) (Declaration, []S
err *SyntaxError
)
//
if s.MatchSymbols(1, "defcolumns") {
if s.MatchSymbols(1, "defalias") {
decl, errors = p.parseDefAlias(s.Elements)
} else if s.MatchSymbols(1, "defcolumns") {
decl, errors = p.parseDefColumns(module, s)
} else if s.Len() > 1 && s.MatchSymbols(1, "defconst") {
decl, errors = p.parseDefConst(s.Elements)
Expand Down Expand Up @@ -266,6 +268,39 @@ func (p *Parser) parseDeclaration(module string, s *sexp.List) (Declaration, []S
return decl, errors
}

// Parse an alias declaration
func (p *Parser) parseDefAlias(elements []sexp.SExp) (Declaration, []SyntaxError) {
var (
errors []SyntaxError
aliases []*DefAlias
names []Symbol
)

for i := 1; i < len(elements); i += 2 {
// Sanity check first
if i+1 == len(elements) {
// Uneven number of constant declarations!
errors = append(errors, *p.translator.SyntaxError(elements[i], "missing alias definition"))
} else if !isIdentifier(elements[i]) {
// Symbol expected!
errors = append(errors, *p.translator.SyntaxError(elements[i], "invalid alias name"))
} else if !isIdentifier(elements[i+1]) {
// Symbol expected!
errors = append(errors, *p.translator.SyntaxError(elements[i+1], "invalid alias definition"))
} else {
alias := &DefAlias{elements[i].AsSymbol().Value}
name := &ColumnName{elements[i+1].AsSymbol().Value, nil}
p.mapSourceNode(elements[i], alias)
p.mapSourceNode(elements[i+1], name)
//
aliases = append(aliases, alias)
names = append(names, name)
}
}
// Done
return &DefAliases{aliases, names}, errors
}

// Parse a column declaration
func (p *Parser) parseDefColumns(module string, l *sexp.List) (Declaration, []SyntaxError) {
columns := make([]*DefColumn, l.Len()-1)
Expand Down
59 changes: 57 additions & 2 deletions pkg/corset/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,15 @@ func (r *resolver) resolveDeclarations(scope *GlobalScope, circuit *Circuit) []S
// to process all columns before we can sure that they are all declared
// correctly.
func (r *resolver) resolveDeclarationsInModule(scope *ModuleScope, decls []Declaration) []SyntaxError {
// Columns & Assignments
if errors := r.initialiseDeclarationsInModule(scope, decls); len(errors) > 0 {
return errors
}
// Iterate until all columns finalised
// Aliases
if errors := r.initialiseAliasesInModule(scope, decls); len(errors) > 0 {
return errors
}
// Finalise everything
return r.finaliseDeclarationsInModule(scope, decls)
}

Expand All @@ -83,7 +88,7 @@ func (r *resolver) resolveDeclarationsInModule(scope *ModuleScope, decls []Decla
func (r *resolver) initialiseDeclarationsInModule(scope *ModuleScope, decls []Declaration) []SyntaxError {
module := scope.EnclosingModule()
errors := make([]SyntaxError, 0)
//
// Initialise all columns
for _, d := range decls {
for iter := d.Definitions(); iter.HasNext(); {
def := iter.Next()
Expand All @@ -95,6 +100,56 @@ func (r *resolver) initialiseDeclarationsInModule(scope *ModuleScope, decls []De
}
}
}
//
return errors
}

// Initialise all alias declarations in the given module scope. This means
// declaring them within the module scope, whilst also supporting aliases of
// aliases, etc. Since the order of aliases is unspecified, this means we have
// to iterate the alias declarations until a fixed point is reached. Once that
// is done, if there are any aliases left unallocated then they indicate errors.
func (r *resolver) initialiseAliasesInModule(scope *ModuleScope, decls []Declaration) []SyntaxError {
// Apply any aliases
errors := make([]SyntaxError, 0)
visited := make(map[string]Declaration)
changed := true
// Iterate aliases to fixed point (i.e. until no new aliases discovered)
for changed {
changed = false
// Look for all aliases
for _, d := range decls {
if a, ok := d.(*DefAliases); ok {
for i, alias := range a.aliases {
symbol := a.symbols[i]
if _, ok := visited[alias.name]; !ok {
if change := scope.Alias(alias.name, symbol); change {
visited[alias.name] = d
changed = true
}
}
}
}
}
}
// Check for any aliases which remain incomplete
for _, decl := range decls {
if a, ok := decl.(*DefAliases); ok {
for i, alias := range a.aliases {
symbol := a.symbols[i]
// Check whether it already exists (or not)
if d, ok := visited[alias.name]; ok && d == decl {
continue
} else if scope.Binding(alias.name) != nil {
err := r.srcmap.SyntaxError(alias, "symbol already exists")
errors = append(errors, *err)
} else {
err := r.srcmap.SyntaxError(symbol, "unknown symbol")
errors = append(errors, *err)
}
}
}
}
// Done
return errors
}
Expand Down
31 changes: 31 additions & 0 deletions pkg/corset/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,17 @@ func (p *ModuleScope) Bind(symbol Symbol) bool {
return false
}

// Binding returns information about the binding of a particular symbol defined
// in this module.
func (p *ModuleScope) Binding(name string) Binding {
// construct binding identifier
if bid, ok := p.ids[BindingId{name, false}]; ok {
return p.bindings[bid]
}
// Failure
return nil
}

// Column returns information about a particular column declared within this
// module.
func (p *ModuleScope) Column(name string) *ColumnBinding {
Expand All @@ -167,6 +178,26 @@ func (p *ModuleScope) Declare(symbol SymbolDefinition) bool {
return true
}

// Alias constructs an alias for an existing symbol. If the symbol does not
// exist, then this returns false.
func (p *ModuleScope) Alias(alias string, symbol Symbol) bool {
// construct symbol identifier
symbol_id := BindingId{symbol.Name(), symbol.IsFunction()}
// construct alias identifier
alias_id := BindingId{alias, symbol.IsFunction()}
// Check alias does not already exist
if _, ok := p.ids[alias_id]; !ok {
// Check symbol being aliased exists
if id, ok := p.ids[symbol_id]; ok {
p.ids[alias_id] = id
// Done
return true
}
}
// Symbol not known (yet)
return false
}

// =============================================================================
// Local Scope
// =============================================================================
Expand Down
4 changes: 3 additions & 1 deletion pkg/corset/translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ func (t *translator) translateOtherDeclarationsInModule(module string, decls []D
func (t *translator) translateDeclaration(decl Declaration, module string) []SyntaxError {
var errors []SyntaxError
//
if _, ok := decl.(*DefColumns); ok {
if _, ok := decl.(*DefAliases); ok {
// Not an assignment or a constraint, hence ignore.
} else if _, ok := decl.(*DefColumns); ok {
// Not an assignment or a constraint, hence ignore.
} else if _, ok := decl.(*DefConst); ok {
// For now, constants are always compiled out when going down to HIR.
Expand Down
31 changes: 31 additions & 0 deletions pkg/test/invalid_corset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,37 @@ func Test_Invalid_Constant_14(t *testing.T) {
CheckInvalid(t, "constant_invalid_14")
}

// ===================================================================
// Alias Tests
// ===================================================================
func Test_Invalid_Alias_01(t *testing.T) {
CheckInvalid(t, "alias_invalid_01")
}

func Test_Invalid_Alias_02(t *testing.T) {
CheckInvalid(t, "alias_invalid_02")
}

func Test_Invalid_Alias_03(t *testing.T) {
CheckInvalid(t, "alias_invalid_03")
}

func Test_Invalid_Alias_04(t *testing.T) {
CheckInvalid(t, "alias_invalid_04")
}

func Test_Invalid_Alias_05(t *testing.T) {
CheckInvalid(t, "alias_invalid_05")
}

func Test_Invalid_Alias_06(t *testing.T) {
CheckInvalid(t, "alias_invalid_06")
}

func Test_Invalid_Alias_07(t *testing.T) {
CheckInvalid(t, "alias_invalid_07")
}

// ===================================================================
// Property Tests
// ===================================================================
Expand Down
22 changes: 22 additions & 0 deletions pkg/test/valid_corset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,28 @@ func Test_Constant_07(t *testing.T) {
Check(t, "constant_07")
}

// ===================================================================
// Alias Tests
// ===================================================================
func Test_Alias_01(t *testing.T) {
Check(t, "alias_01")
}
func Test_Alias_02(t *testing.T) {
Check(t, "alias_02")
}
func Test_Alias_03(t *testing.T) {
Check(t, "alias_03")
}
func Test_Alias_04(t *testing.T) {
Check(t, "alias_04")
}
func Test_Alias_05(t *testing.T) {
Check(t, "alias_05")
}
func Test_Alias_06(t *testing.T) {
Check(t, "alias_06")
}

// ===================================================================
// Domain Tests
// ===================================================================
Expand Down
7 changes: 7 additions & 0 deletions testdata/alias_02.accepts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"X": [], "Y": []}
{"X": [-1], "Y": [1]}
{"X": [0], "Y": [0]}
{"X": [1], "Y": [-1]}
{"X": [0,-1], "Y": [0,1]}
{"X": [0,0], "Y": [0,0]}
{"X": [0,1], "Y": [0,-1]}
13 changes: 13 additions & 0 deletions testdata/alias_02.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
(defcolumns X Y)
(defalias
X' X
Y' Y)

(defconstraint c1 () (+ X' Y'))
(defconstraint c2 () (+ Y' X'))
(defconstraint c3 () (+ X Y'))
(defconstraint c4 () (+ Y X'))
(defconstraint c5 () (+ X' Y))
(defconstraint c6 () (+ Y' X))
(defconstraint c7 () (+ X Y))
(defconstraint c8 () (+ Y X))
6 changes: 6 additions & 0 deletions testdata/alias_02.rejects
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{"X": [1], "Y": [0]}
{"X": [-1], "Y": [0]}
{"X": [0], "Y": [1]}
{"X": [0], "Y": [-1]}
{"X": [2], "Y": [-1]}
{"X": [-2], "Y": [1]}
11 changes: 11 additions & 0 deletions testdata/alias_03.accepts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{"X": [], "Y": []}
{"X": [-4], "Y": [2]}
{"X": [-2], "Y": [1]}
{"X": [0], "Y": [0]}
{"X": [2], "Y": [-1]}
{"X": [4], "Y": [-2]}
{"X": [0,-4], "Y": [0,2]}
{"X": [0,-2], "Y": [0,1]}
{"X": [0,0], "Y": [0,0]}
{"X": [0,2], "Y": [0,-1]}
{"X": [0,4], "Y": [0,-2]}
18 changes: 18 additions & 0 deletions testdata/alias_03.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
(defconst
ONE 1
TWO 2
)

(defalias
one ONE
two TWO)

(defcolumns X Y)
(defconstraint c1 () (+ X (* two Y)))
(defconstraint c2 () (+ (* two Y) X))
(defconstraint c3 () (+ X Y Y))
(defconstraint c4 () (+ Y X Y))
(defconstraint c5 () (+ Y Y X))
(defconstraint c6 () (+ (* one X) Y Y))
(defconstraint c7 () (+ Y (* one X) Y))
(defconstraint c8 () (+ Y Y (* one X)))
7 changes: 7 additions & 0 deletions testdata/alias_03.rejects
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"X": [-2], "Y": [-2]}
{"X": [-2], "Y": [-1]}
{"X": [-2], "Y": [0]}
{"X": [-2], "Y": [2]}
{"X": [-2], "Y": [3]}
{"X": [0,1], "Y": [0,1]}
{"X": [-2,1], "Y": [1,1]}
3 changes: 3 additions & 0 deletions testdata/alias_04.accepts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"COUNTER": [0]}
{"COUNTER": [0,0]}
{"COUNTER": [0,0,0]}
4 changes: 4 additions & 0 deletions testdata/alias_04.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(defcolumns COUNTER)
(defalias CT1 COUNTER)
(defalias CT2 CT1)
(defconstraint heartbeat () CT2)
Loading
Loading