lang: ast, parser: Allow calling anonymous functions

I forgot to plumb this in through the parser. Pretty easy to add,
hopefully I didn't forget any weird corner scope cases here.
This commit is contained in:
James Shubin
2025-01-08 23:06:51 -05:00
parent 1af334f2ce
commit 1538befc93
6 changed files with 138 additions and 2 deletions

View File

@@ -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.
@@ -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
@@ -7949,6 +7959,11 @@ 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)
}
@@ -7956,11 +7971,21 @@ func (obj *ExprCall) Apply(fn func(interfaces.Node) error) error {
// 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
}
@@ -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!)
@@ -7994,6 +8027,7 @@ func (obj *ExprCall) Interpolate() (interfaces.Expr, error) {
Name: obj.Name,
Args: args,
Var: obj.Var,
Anon: anon,
}, nil
}
@@ -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 {
@@ -8051,6 +8097,7 @@ func (obj *ExprCall) Copy() (interfaces.Expr, error) {
Name: obj.Name,
Args: args,
Var: obj.Var,
Anon: anon,
}, nil
}
@@ -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
@@ -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
}
@@ -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 {
@@ -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
@@ -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")

View File

@@ -0,0 +1,8 @@
-- main.mcl --
$s = func() {
"hello"
}() # inline lambda call
test [$s,] {}
-- OUTPUT --
Vertex: test[hello]

View File

@@ -0,0 +1,8 @@
-- main.mcl --
$s = func($x) {
"hello" + $x
}("world") # inline lambda call
test [$s,] {}
-- OUTPUT --
Vertex: test[helloworld]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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