Skip to content

Commit

Permalink
Merge pull request #181 from Mido-sys/add_chaining_feature
Browse files Browse the repository at this point in the history
Implement Chaining function
  • Loading branch information
paganotoni authored Apr 25, 2024
2 parents 4814270 + 5ec33ed commit f6aa624
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 6 deletions.
11 changes: 6 additions & 5 deletions ast/call_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (

type CallExpression struct {
TokenAble
Callee Expression
Function Expression
Arguments []Expression
Block *BlockStatement
ElseBlock *BlockStatement
Callee Expression
ChainCallee Expression
Function Expression
Arguments []Expression
Block *BlockStatement
ElseBlock *BlockStatement
}

var _ Comparable = &CallExpression{}
Expand Down
21 changes: 20 additions & 1 deletion compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@ func (c *compiler) evalCallExpression(node *ast.CallExpression) (interface{}, er
ptr := reflect.New(reflect.TypeOf(c))
ptr.Elem().Set(rc)
rv = ptr.MethodByName(mname)
if !rv.IsValid() {
return nil, fmt.Errorf("'%s' does not have a method named '%s' (%s.%s)", node.Callee.String(), mname, node.Callee.String(), mname)
}
}

if !rv.IsValid() {
Expand Down Expand Up @@ -660,7 +663,6 @@ func (c *compiler) evalCallExpression(node *ast.CallExpression) (interface{}, er
if rt.Kind() != reflect.Func {
return nil, fmt.Errorf("%+v (%T) is an invalid function", node.String(), rt)
}

rtNumIn := rt.NumIn()
isVariadic := rt.IsVariadic()
args := []reflect.Value{}
Expand Down Expand Up @@ -800,6 +802,23 @@ func (c *compiler) evalCallExpression(node *ast.CallExpression) (interface{}, er
if e, ok := res[len(res)-1].Interface().(error); ok {
return nil, fmt.Errorf("could not call %s function: %w", node.Function, e)
}
if node.ChainCallee != nil {
octx := c.ctx.(*Context)
defer func() {
c.ctx = octx
}()

c.ctx = octx.New()
for k, v := range octx.data {
c.ctx.Set(k, v)
}
c.ctx.Set(node.Function.String(), res[0].Interface())
vvs, err := c.evalExpression(node.ChainCallee)
if err != nil {
return nil, err
}
return vvs, err
}
return res[0].Interface(), nil
}

Expand Down
15 changes: 15 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,18 @@ func (p *parser) parseCallExpression(function ast.Expression) ast.Expression {
exp.Block = p.parseBlockStatement()
}

if p.peekTokenIs(token.DOT) {
calleeIdent := &ast.Identifier{Value: exp.Function.String()}
p.nextToken()
p.nextToken()
parseExp := p.parseExpression(LOWEST)

exp.ChainCallee = p.assignCallee(parseExp, calleeIdent)
if exp.ChainCallee == nil {
return nil
}
}

return exp
}

Expand Down Expand Up @@ -749,6 +761,9 @@ func (p *parser) assignCallee(exp ast.Expression, calleeIdent *ast.Identifier) (
msg := fmt.Sprintf("line %d: syntax error: invalid nested index access, expected an identifier %v", p.curToken.LineNumber, ss)
p.errors = append(p.errors, msg)
}
case *ast.CallExpression:
ss.Callee = calleeIdent
assignedCallee = ss
case *ast.Identifier:
ss.OriginalCallee.Callee = calleeIdent
assignedCallee = ss
Expand Down
95 changes: 95 additions & 0 deletions struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package plush_test
import (
"strings"
"testing"
"time"

"github.com/gobuffalo/plush/v4"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -461,3 +462,97 @@ func Test_Render_Struct_Nested_Map_Access(t *testing.T) {
r.NoError(err)
r.Equal("John Dolittle", res)
}

type person struct {
likes []string
hates []string
born time.Time
}

func (a person) GetAge() time.Duration {
return time.Since(a.born)
}

func (a person) GetBorn() time.Time {
return a.born
}

func (a person) Hates() []string {
return a.hates
}
func (a person) Likes() []string {
return a.likes
}

func Test_Render_Struct_With_ChainingFunction_ArrayAccess(t *testing.T) {
r := require.New(t)

tt := person{likes: []string{"pringles", "galaxy", "carrot cake", "world pendant", "gold braclet"},
hates: []string{"boiled eggs", "coconut"}}
input := `<%= nour.Likes()[0] %>`
ctx := plush.NewContext()
ctx.Set("nour", tt)
res, err := plush.Render(input, ctx)
r.NoError(err)
r.Equal("pringles", res)
}

func Test_Render_Struct_With_ChainingFunction_ArrayAccess_Outofbound(t *testing.T) {
r := require.New(t)

tt := person{likes: []string{"pringles", "galaxy", "carrot cake", "world pendant", "gold bracelet"},
hates: []string{"boiled eggs", "coconut"}}
input := `<%= nour.Hates()[30] %>`
ctx := plush.NewContext()
ctx.Set("nour", tt)
_, err := plush.Render(input, ctx)
r.Error(err)
}

func Test_Render_Struct_With_ChainingFunction_FunctionCall(t *testing.T) {
r := require.New(t)

tt := person{born: time.Date(2024, time.January, 11, 0, 0, 0, 0, time.UTC).AddDate(-31, 0, 0)}
input := `<%= nour.GetBorn().Format("Jan 2, 2006") %>`
ctx := plush.NewContext()
ctx.Set("nour", tt)
res, err := plush.Render(input, ctx)
r.NoError(err)
r.Equal("Jan 11, 1993", res)
}

func Test_Render_Struct_With_ChainingFunction_UndefinedStructProperty(t *testing.T) {
r := require.New(t)

tt := person{born: time.Now()}
input := `<%= nour.GetBorn().TEST %>`
ctx := plush.NewContext()
ctx.Set("nour", tt)
_, err := plush.Render(input, ctx)
r.Error(err)

}

func Test_Render_Struct_With_ChainingFunction_InvalidFunctionCall(t *testing.T) {
r := require.New(t)

tt := person{born: time.Now()}
input := `<%= nour.GetBorn().TEST("Jan 2, 2006") %>`
ctx := plush.NewContext()
ctx.Set("nour", tt)
_, err := plush.Render(input, ctx)
r.Error(err)
r.Contains(err.Error(), "'nour.GetBorn' does not have a method named 'TEST' (nour.GetBorn.TEST)")
}

func Test_Render_Function_on_Invalid_Function_Struct(t *testing.T) {
r := require.New(t)
ctx := plush.NewContext()
bender := Robot{
Avatar: Avatar("bender.jpg"),
}
ctx.Set("robot", bender)
input := `<%= robot.Avatar.URL2() %>`
_, err := plush.Render(input, ctx)
r.Error(err)
}

0 comments on commit f6aa624

Please sign in to comment.