Skip to content

Commit

Permalink
Implement defunalias (#428)
Browse files Browse the repository at this point in the history
This puts in place a fairly straightforward implementation of the
`defunalias` primitive.  This is essentially identical to `defalias`
except that all symbols are for functions rather than anything else.
  • Loading branch information
DavePearce authored Dec 10, 2024
1 parent 6da83cd commit bad98c1
Show file tree
Hide file tree
Showing 17 changed files with 187 additions and 23 deletions.
41 changes: 27 additions & 14 deletions pkg/corset/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,46 +111,50 @@ type Assignment interface {
Resolve(*Environment) ([]ColumnAssignment, []SyntaxError)
}

// ColumnName represents a name within some syntactic item. Essentially this wraps a
// 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 ColumnName struct {
name string
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 *ColumnName) IsQualified() bool {
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 *ColumnName) IsFunction() bool {
return false
func (e *Name) IsFunction() bool {
return e.function
}

// IsResolved checks whether this symbol has been resolved already, or not.
func (e *ColumnName) IsResolved() bool {
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 *ColumnName) Module() string {
func (e *Name) Module() string {
panic("undefined")
}

// Name returns the (unqualified) name of the column to which this symbol
// refers.
func (e *ColumnName) Name() string {
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 *ColumnName) Binding() Binding {
func (e *Name) Binding() Binding {
if e.binding == nil {
panic("name not yet resolved")
}
Expand All @@ -160,7 +164,7 @@ func (e *ColumnName) Binding() Binding {

// Resolve this symbol by associating it with the binding associated with
// the definition of the symbol to which this refers.
func (e *ColumnName) Resolve(binding Binding) {
func (e *Name) Resolve(binding Binding) {
if e.binding != nil {
panic("name already resolved")
}
Expand All @@ -170,7 +174,7 @@ func (e *ColumnName) Resolve(binding Binding) {

// Lisp converts this node into its lisp representation. This is primarily used
// for debugging purposes.
func (e *ColumnName) Lisp() sexp.SExp {
func (e *Name) Lisp() sexp.SExp {
return sexp.NewSymbol(e.name)
}

Expand All @@ -181,6 +185,8 @@ func (e *ColumnName) Lisp() sexp.SExp {
// DefAliases represents the declaration of one or more aliases. That is,
// alternate names for existing symbols.
type DefAliases struct {
// Distinguishes defalias from defunalias
functions bool
// Aliases
aliases []*DefAlias
// Symbols being aliased
Expand Down Expand Up @@ -210,9 +216,16 @@ func (p *DefAliases) Lisp() sexp.SExp {
pairs.Append(p.symbols[i].Lisp())
}
//
var name *sexp.Symbol
//
if p.functions {
name = sexp.NewSymbol("defunalias")
} else {
name = sexp.NewSymbol("defalias")
}
//
return sexp.NewList([]sexp.SExp{
sexp.NewSymbol("defalias"),
pairs,
name, pairs,
})
}

Expand Down
20 changes: 11 additions & 9 deletions pkg/corset/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,15 @@ func (p *Parser) parseDeclaration(module string, s *sexp.List) (Declaration, []S
)
//
if s.MatchSymbols(1, "defalias") {
decl, errors = p.parseDefAlias(s.Elements)
decl, errors = p.parseDefAlias(false, 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)
} else if s.Len() == 4 && s.MatchSymbols(2, "defconstraint") {
decl, errors = p.parseDefConstraint(s.Elements)
} else if s.MatchSymbols(1, "defunalias") {
decl, errors = p.parseDefAlias(true, s.Elements)
} else if s.Len() == 3 && s.MatchSymbols(1, "defpurefun") {
decl, errors = p.parseDefFun(true, s.Elements)
} else if s.Len() == 3 && s.MatchSymbols(1, "defun") {
Expand Down Expand Up @@ -271,7 +273,7 @@ func (p *Parser) parseDeclaration(module string, s *sexp.List) (Declaration, []S
}

// Parse an alias declaration
func (p *Parser) parseDefAlias(elements []sexp.SExp) (Declaration, []SyntaxError) {
func (p *Parser) parseDefAlias(functions bool, elements []sexp.SExp) (Declaration, []SyntaxError) {
var (
errors []SyntaxError
aliases []*DefAlias
Expand All @@ -291,7 +293,7 @@ func (p *Parser) parseDefAlias(elements []sexp.SExp) (Declaration, []SyntaxError
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}
name := &Name{elements[i+1].AsSymbol().Value, functions, nil}
p.mapSourceNode(elements[i], alias)
p.mapSourceNode(elements[i+1], name)
//
Expand All @@ -300,7 +302,7 @@ func (p *Parser) parseDefAlias(elements []sexp.SExp) (Declaration, []SyntaxError
}
}
// Done
return &DefAliases{aliases, names}, errors
return &DefAliases{functions, aliases, names}, errors
}

// Parse a column declaration
Expand Down Expand Up @@ -452,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] = &ColumnName{ith.AsSymbol().Value, nil}
sources[i] = &Name{ith.AsSymbol().Value, false, nil}
p.mapSourceNode(ith, sources[i])
}
//
Expand Down Expand Up @@ -536,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) (*ColumnName, bool, *SyntaxError) {
func (p *Parser) parsePermutedColumnDeclaration(signRequired bool, e sexp.SExp) (*Name, bool, *SyntaxError) {
var (
err *SyntaxError
name *ColumnName
name *Name
sign bool
)
// Check whether extended declaration or not.
Expand All @@ -557,11 +559,11 @@ func (p *Parser) parsePermutedColumnDeclaration(signRequired bool, e sexp.SExp)
return nil, false, err
}
// Parse column name
name = &ColumnName{l.Get(1).AsSymbol().Value, nil}
name = &Name{l.Get(1).AsSymbol().Value, false, nil}
} else if signRequired {
return nil, false, p.translator.SyntaxError(e, "missing sort direction")
} else {
name = &ColumnName{e.String(false), nil}
name = &Name{e.String(false), false, nil}
}
// Update source mapping
p.mapSourceNode(e, name)
Expand Down
19 changes: 19 additions & 0 deletions pkg/test/invalid_corset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,25 @@ func Test_Invalid_Alias_07(t *testing.T) {
CheckInvalid(t, "alias_invalid_07")
}

// ===================================================================
// Function Alias Tests
// ===================================================================
func Test_Invalid_FunAlias_01(t *testing.T) {
CheckInvalid(t, "funalias_invalid_01")
}

func Test_Invalid_FunAlias_02(t *testing.T) {
CheckInvalid(t, "funalias_invalid_02")
}

func Test_Invalid_FunAlias_03(t *testing.T) {
CheckInvalid(t, "funalias_invalid_03")
}

func Test_Invalid_FunAlias_04(t *testing.T) {
CheckInvalid(t, "funalias_invalid_04")
}

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

// ===================================================================
// Function Alias Tests
// ===================================================================
func Test_FunAlias_01(t *testing.T) {
Check(t, "funalias_01")
}

func Test_FunAlias_02(t *testing.T) {
Check(t, "funalias_02")
}

func Test_FunAlias_03(t *testing.T) {
Check(t, "funalias_03")
}

// ===================================================================
// Domain Tests
// ===================================================================
Expand Down
6 changes: 6 additions & 0 deletions testdata/funalias_01.accepts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{ "A": [0] }
{ "A": [0,0] }
{ "A": [0,0,0] }
{ "A": [0,0,0,0] }
{ "A": [0,0,0,0,0] }
{ "A": [0,0,0,0,0,0] }
4 changes: 4 additions & 0 deletions testdata/funalias_01.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(defcolumns A)
(defpurefun (id x) x)
(defunalias ID id)
(defconstraint test () (ID A))
5 changes: 5 additions & 0 deletions testdata/funalias_01.rejects
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{ "A": [1] }
{ "A": [2] }
{ "A": [1,1] }
{ "A": [1,1] }
{ "A": [2,1] }
15 changes: 15 additions & 0 deletions testdata/funalias_02.accepts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{ "A": [], "B": [] }
{ "A": [0], "B": [0] }
{ "A": [1], "B": [1] }
{ "A": [2], "B": [2] }
{ "A": [3], "B": [3] }
{ "A": [4], "B": [4] }
;;
{ "A": [0,0], "B": [0,0] }
{ "A": [1,0], "B": [1,0] }
{ "A": [0,1], "B": [0,1] }
{ "A": [1,1], "B": [1,1] }
;;
{ "A": [125,0], "B": [125,0] }
{ "A": [0,125], "B": [0,125] }
{ "A": [125,125], "B": [125,125] }
4 changes: 4 additions & 0 deletions testdata/funalias_02.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(defcolumns A B)
(defpurefun (eq x y) (- y x))
(defunalias eq! eq)
(defconstraint test () (eq! A B))
35 changes: 35 additions & 0 deletions testdata/funalias_02.rejects
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{ "A": [0], "B": [1] }
{ "A": [1], "B": [0] }
{ "A": [0], "B": [1] }
{ "A": [0], "B": [2] }
{ "A": [0], "B": [3] }
{ "A": [1], "B": [0] }
{ "A": [2], "B": [0] }
{ "A": [3], "B": [0] }
;;
{ "A": [0,0], "B": [0,1] }
{ "A": [1,0], "B": [0,0] }
{ "A": [0,0], "B": [1,0] }
{ "A": [0,1], "B": [0,0] }
{ "A": [0,0], "B": [1,1] }
{ "A": [1,1], "B": [0,0] }
{ "A": [1,0], "B": [0,1] }
{ "A": [0,1], "B": [1,0] }
;;
{ "A": [0,0], "B": [0,125] }
{ "A": [125,0], "B": [0,0] }
{ "A": [0,0], "B": [125,0] }
{ "A": [0,125], "B": [0,0] }
{ "A": [0,0], "B": [125,125] }
{ "A": [125,125], "B": [0,0] }
{ "A": [125,0], "B": [0,125] }
{ "A": [0,125], "B": [125,0] }
;;
{ "A": [65,65], "B": [65,65573234] }
{ "A": [65573234,65], "B": [65,65] }
{ "A": [65,65], "B": [65573234,65] }
{ "A": [65,65573234], "B": [65,65] }
{ "A": [65,65], "B": [65573234,65573234] }
{ "A": [65573234,65573234], "B": [65,65] }
{ "A": [65573234,65], "B": [65,65573234] }
{ "A": [65,65573234], "B": [65573234,65] }
25 changes: 25 additions & 0 deletions testdata/funalias_03.accepts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{ "X": [], "Y": [] }
{ "X": [0], "Y": [0] }
{ "X": [1], "Y": [2] }
{ "X": [2], "Y": [4] }
{ "X": [3], "Y": [6] }
;;
{ "X": [0,0], "Y": [0,0] }
{ "X": [0,1], "Y": [0,2] }
{ "X": [0,2], "Y": [0,4] }
{ "X": [0,3], "Y": [0,6] }
;;
{ "X": [1,0], "Y": [2,0] }
{ "X": [1,1], "Y": [2,2] }
{ "X": [1,2], "Y": [2,4] }
{ "X": [1,3], "Y": [2,6] }
;;
{ "X": [2,0], "Y": [4,0] }
{ "X": [2,1], "Y": [4,2] }
{ "X": [2,2], "Y": [4,4] }
{ "X": [2,3], "Y": [4,6] }
;;
{ "X": [3,0], "Y": [6,0] }
{ "X": [3,1], "Y": [6,2] }
{ "X": [3,2], "Y": [6,4] }
{ "X": [3,3], "Y": [6,6] }
6 changes: 6 additions & 0 deletions testdata/funalias_03.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(defcolumns X Y)
(defun (double x) (+ x x))
(defpurefun (eq x y) (- x y))
(defunalias times2 double)
;; Y == 2 * X
(defconstraint c1 () (eq Y (times2 X)))
9 changes: 9 additions & 0 deletions testdata/funalias_03.rejects
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{ "X": [1], "Y": [0] }
{ "X": [1], "Y": [3] }
{ "X": [2], "Y": [3] }
{ "X": [3], "Y": [5] }
;;
{ "X": [0,1], "Y": [0,0] }
{ "X": [0,0], "Y": [0,2] }
{ "X": [0,3], "Y": [0,4] }
{ "X": [0,2], "Y": [0,6] }
2 changes: 2 additions & 0 deletions testdata/funalias_invalid_01.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(defcolumns X)
(defunalias INC X)
2 changes: 2 additions & 0 deletions testdata/funalias_invalid_02.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(defconst X 1)
(defunalias INC X)
1 change: 1 addition & 0 deletions testdata/funalias_invalid_03.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(defunalias X Y)
1 change: 1 addition & 0 deletions testdata/funalias_invalid_04.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(defunalias X X)

0 comments on commit bad98c1

Please sign in to comment.