Skip to content

Commit

Permalink
lang: ast, parser: Allow calling anonymous functions
Browse files Browse the repository at this point in the history
I forgot to plumb this in through the parser. Pretty easy to add,
hopefully I didn't forget any weird corner scope cases here.
  • Loading branch information
purpleidea committed Jan 26, 2025
1 parent 1af334f commit 1538bef
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 2 deletions.
94 changes: 92 additions & 2 deletions lang/ast/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7923,10 +7923,16 @@ type ExprCall struct {

// Name of the function to be called. We look for it in the scope.
Name string

// Args are the list of inputs to this function.
Args []interfaces.Expr // list of args in parsed order

// Var specifies whether the function being called is a lambda in a var.
Var bool

// Anon is an *ExprFunc which is used if we are calling anonymously. If
// this is specified, Name must be the empty string.
Anon interfaces.Expr
}

// String returns a short representation of this expression.
Expand All @@ -7935,7 +7941,11 @@ func (obj *ExprCall) String() string {
for _, x := range obj.Args {
s = append(s, fmt.Sprintf("%s", x.String()))
}
return fmt.Sprintf("call:%s(%s)", obj.Name, strings.Join(s, ", "))
name := obj.Name
if obj.Name == "" && obj.Anon != nil {
name = "<anon>"
}
return fmt.Sprintf("call:%s(%s)", name, strings.Join(s, ", "))
}

// Apply is a general purpose iterator method that operates on any AST node. It
Expand All @@ -7949,18 +7959,33 @@ func (obj *ExprCall) Apply(fn func(interfaces.Node) error) error {
return err
}
}
if obj.Anon != nil {
if err := obj.Anon.Apply(fn); err != nil {
return err
}
}
return fn(obj)
}

// Init initializes this branch of the AST, and returns an error if it fails to
// validate.
func (obj *ExprCall) Init(data *interfaces.Data) error {
obj.data = data

if obj.Name == "" && obj.Anon == nil {
return fmt.Errorf("missing call name")
}

for _, x := range obj.Args {
if err := x.Init(data); err != nil {
return err
}
}
if obj.Anon != nil {
if err := obj.Anon.Init(data); err != nil {
return err
}
}
return nil
}

Expand All @@ -7976,6 +8001,14 @@ func (obj *ExprCall) Interpolate() (interfaces.Expr, error) {
}
args = append(args, interpolated)
}
var anon interfaces.Expr
if obj.Anon != nil {
f, err := obj.Anon.Interpolate()
if err != nil {
return nil, err
}
anon = f
}

orig := obj
if obj.orig != nil { // preserve the original pointer (the identifier!)
Expand All @@ -7994,6 +8027,7 @@ func (obj *ExprCall) Interpolate() (interfaces.Expr, error) {
Name: obj.Name,
Args: args,
Var: obj.Var,
Anon: anon,
}, nil
}

Expand All @@ -8018,6 +8052,18 @@ func (obj *ExprCall) Copy() (interfaces.Expr, error) {
args = obj.Args // don't re-package it unnecessarily!
}

var anon interfaces.Expr
if obj.Anon != nil {
cp, err := obj.Anon.Copy()
if err != nil {
return nil, err
}
if cp != obj.Anon { // must have been copied, or pointer would be same
copied = true
}
anon = cp
}

var err error
var expr interfaces.Expr
if obj.expr != nil {
Expand Down Expand Up @@ -8051,6 +8097,7 @@ func (obj *ExprCall) Copy() (interfaces.Expr, error) {
Name: obj.Name,
Args: args,
Var: obj.Var,
Anon: anon,
}, nil
}

Expand All @@ -8063,7 +8110,7 @@ func (obj *ExprCall) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap
}
graph.AddVertex(obj)

if obj.Name == "" {
if obj.Name == "" && obj.Anon == nil {
return nil, nil, fmt.Errorf("missing call name")
}
uid := funcOrderingPrefix + obj.Name // ordering id
Expand Down Expand Up @@ -8123,6 +8170,33 @@ func (obj *ExprCall) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap
}
}

if obj.Anon != nil {
g, c, err := obj.Anon.Ordering(produces)
if err != nil {
return nil, nil, err
}
graph.AddGraph(g) // add in the child graph

// additional constraints...
edge := &pgraph.SimpleEdge{Name: "exprcallanon1"}
graph.AddEdge(obj.Anon, obj, edge) // prod -> cons

for k, v := range c { // c is consumes
x, exists := cons[k]
if exists && v != x {
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
}
cons[k] = v // add to map

n, exists := produces[v]
if !exists {
continue
}
edge := &pgraph.SimpleEdge{Name: "exprcallanon2"}
graph.AddEdge(n, k, edge)
}
}

return graph, cons, nil
}

Expand All @@ -8147,6 +8221,12 @@ func (obj *ExprCall) SetScope(scope *interfaces.Scope, sctx map[string]interface
}
}

if obj.Anon != nil {
if err := obj.Anon.SetScope(scope, sctx); err != nil {
return err
}
}

var prefixedName string
var target interfaces.Expr
if obj.Var {
Expand All @@ -8165,6 +8245,10 @@ func (obj *ExprCall) SetScope(scope *interfaces.Scope, sctx map[string]interface
}
target = f
}
} else if obj.Name == "" && obj.Anon != nil {
// The call looks like <anon>().

target = obj.Anon
} else {
// The call looks like f().
prefixedName = obj.Name
Expand Down Expand Up @@ -8226,12 +8310,18 @@ func (obj *ExprCall) SetType(typ *types.Type) error {
return obj.typ.Cmp(typ) // if not set, ensure it doesn't change
}
obj.typ = typ // set

// XXX: Do we need to do something to obj.Anon ?

return nil
}

// Type returns the type of this expression, which is the return type of the
// function call.
func (obj *ExprCall) Type() (*types.Type, error) {

// XXX: If we have the function statically in obj.Anon, run this?

if obj.expr == nil {
// possible programming error
return nil, fmt.Errorf("call doesn't contain an expr pointer yet")
Expand Down
8 changes: 8 additions & 0 deletions lang/interpret_test/TestAstFunc2/call-inline-lambda1.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- main.mcl --
$s = func() {
"hello"
}() # inline lambda call

test [$s,] {}
-- OUTPUT --
Vertex: test[hello]
8 changes: 8 additions & 0 deletions lang/interpret_test/TestAstFunc2/call-inline-lambda2.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- main.mcl --
$s = func($x) {
"hello" + $x
}("world") # inline lambda call

test [$s,] {}
-- OUTPUT --
Vertex: test[helloworld]
10 changes: 10 additions & 0 deletions lang/interpret_test/TestAstFunc2/call-inline-lambda3.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- main.mcl --
import "fmt"

$s = fmt.printf("%v", func($x) {
len($x)
}("helloworld")) # inline lambda call

test [$s,] {}
-- OUTPUT --
Vertex: test[10]
10 changes: 10 additions & 0 deletions lang/interpret_test/TestAstFunc2/call-inline-lambda4.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- main.mcl --
import "fmt"

$s = fmt.printf("%v", func($x) {
len($x)
}(func($x){ $x }("helloworld"))) # inline lambda call as an arg to another

test [$s,] {}
-- OUTPUT --
Vertex: test[10]
10 changes: 10 additions & 0 deletions lang/parser/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,16 @@ call:
Var: true, // lambda
}
}
// calling an inline function
| func OPEN_PAREN call_args CLOSE_PAREN
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: "", // anonymous!
Args: $3.exprs,
Anon: $1.expr,
}
}
| expr PLUS expr
{
posLast(yylex, yyDollar) // our pos
Expand Down

0 comments on commit 1538bef

Please sign in to comment.