Skip to content

Commit

Permalink
Merge pull request #137 from Mido-sys/add_map_slice_index_access
Browse files Browse the repository at this point in the history
Add map/slice index access
  • Loading branch information
paganotoni authored Apr 22, 2021
2 parents 5e2fc47 + 4893530 commit 73f5081
Show file tree
Hide file tree
Showing 6 changed files with 552 additions and 20 deletions.
5 changes: 3 additions & 2 deletions ast/identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (

type Identifier struct {
TokenAble
Callee *Identifier
Value string
Callee *Identifier
Value string
OriginalCallee *Identifier // So robot.Avatar.Name the OriginalCallee will be robot
}

func (il *Identifier) validIfCondition() bool { return true }
Expand Down
16 changes: 12 additions & 4 deletions ast/index_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (

type IndexExpression struct {
TokenAble
Left Expression
Index Expression
Value Expression
Left Expression
Index Expression
Value Expression
Callee Expression
}

func (ie *IndexExpression) validIfCondition() bool { return true }
Expand All @@ -22,7 +23,14 @@ func (ie *IndexExpression) String() string {
out.WriteString(ie.Left.String())
out.WriteString("[")
out.WriteString(ie.Index.String())
out.WriteString("])")
if ie.Callee != nil {
out.WriteString("]")
out.WriteString("." + ie.Callee.String())
out.WriteString(")")
} else {

out.WriteString("])")
}
if ie.Value != nil {
out.WriteString("=")
out.WriteString(ie.Value.String())
Expand Down
147 changes: 133 additions & 14 deletions compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"html/template"
"reflect"
"regexp"
"strings"
"time"

"github.com/gobuffalo/helpers/hctx"
Expand Down Expand Up @@ -35,6 +36,7 @@ type compiler struct {
ctx hctx.Context
program *ast.Program
curStmt ast.Statement
inCheck bool
}

func (c *compiler) compile() (string, error) {
Expand Down Expand Up @@ -261,54 +263,112 @@ func (c *compiler) isTruthy(i interface{}) bool {
}

func (c *compiler) evalIndexExpression(node *ast.IndexExpression) (interface{}, error) {

index, err := c.evalExpression(node.Index)
if err != nil {
return nil, err
}

left, err := c.evalExpression(node.Left)
if err != nil {
return nil, err
}

var value interface{}

if node.Value != nil {

value, err = c.evalExpression(node.Value)
if err != nil {

return nil, err
}

return nil, c.evalUpdateIndex(left, index, value)
}

return c.evalAccessIndex(left, index, node)
}

func (c *compiler) evalUpdateIndex(left, index, value interface{}) error {

var err error
rv := reflect.ValueOf(left)
switch rv.Kind() {
case reflect.Map:

rv.SetMapIndex(reflect.ValueOf(index), reflect.ValueOf(value))

case reflect.Array, reflect.Slice:
if i, ok := index.(int); ok {

if rv.Len()-1 < i {

err = fmt.Errorf("array index out of bounds, got index %d, while array size is %v", i, rv.Len())

} else {

rv.Index(i).Set(reflect.ValueOf(value))

}

} else {

err = fmt.Errorf("can't access Slice/Array with a non int Index (%v)", index)
}

default:
err = fmt.Errorf("could not index %T with %T", left, index)

}

return err
}

func (c *compiler) evalAccessIndex(left, index interface{}, node *ast.IndexExpression) (interface{}, error) {

var returnValue interface{}
var err error
rv := reflect.ValueOf(left)
switch rv.Kind() {
case reflect.Map:
val := rv.MapIndex(reflect.ValueOf(index))
if !val.IsValid() && node.Value == nil {

if !val.IsValid() {
return nil, nil
}
if node.Value != nil {
rv.SetMapIndex(reflect.ValueOf(index), reflect.ValueOf(value))
return nil, nil

if node.Callee != nil {

returnValue, err = c.evalIndexCallee(val, node)

} else {

returnValue = val.Interface()
}

return val.Interface(), nil
case reflect.Array, reflect.Slice:
if i, ok := index.(int); ok {

if node.Value != nil {
if node.Callee != nil {

if rv.Len()-1 < i {
returnValue, err = c.evalIndexCallee(rv.Index(i), node)

return nil, fmt.Errorf("array index out of bounds, got index %d, while array size is %v", i, rv.Len())

}
rv.Index(i).Set(reflect.ValueOf(value))
return nil, nil
} else {
returnValue = rv.Index(i).Interface()
}
return rv.Index(i).Interface(), nil

} else {

err = fmt.Errorf("can't access Slice/Array with a non int Index (%v)", index)
}

default:
err = fmt.Errorf("could not index %T with %T", left, index)

}
return nil, fmt.Errorf("could not index %T with %T", left, index)

return returnValue, err
}

func (c *compiler) evalHashLiteral(node *ast.HashLiteral) (interface{}, error) {
Expand Down Expand Up @@ -358,6 +418,11 @@ func (c *compiler) evalIdentifier(node *ast.Identifier) (interface{}, error) {
}
return m.Interface(), nil
}

if !f.CanInterface() {

return nil, fmt.Errorf("'%s'cannot return value obtained from unexported field or method '%s' (%s)", node.Callee.String(), node.Value, node)
}
return f.Interface(), nil
}
if c.ctx.Has(node.Value) {
Expand Down Expand Up @@ -867,3 +932,57 @@ func (c *compiler) evalArrayLiteral(node *ast.ArrayLiteral) (interface{}, error)
}
return res, nil
}

func (c *compiler) evalIndexCallee(rv reflect.Value, node *ast.IndexExpression) (interface{}, error) {

octx := c.ctx.(*Context)
defer func() {
c.ctx = octx
}()

c.ctx = octx.New()
// must copy all data from original (it includes application defined helpers)
for k, v := range octx.data {
c.ctx.Set(k, v)
}

//The key here is needed to set the object in ctx for later evaluation
//For example, if this is a nested object person.Name[0]
//then we can set the value of Name[0] to person.Name
//As the evalIdent will look for that object by person.Name
//If key doesn't contain "." this means we got person[0].Name[0]
//If key does contain "." then indexed field that needs to be accessed will be set in Node.left and Node.Callee
key := node.Left.String()
if strings.Contains(key, ".") {

ggg := strings.Split(key, ".")
callee := node.Callee.String()
if !strings.Contains(callee, key) {

for {
if len(ggg) >= 2 {

ggg = ggg[1:]
} else {
key = ggg[0]
break
}
if strings.Contains(callee, strings.Join(ggg, ".")) {
key = strings.Join(ggg, ".")
break
}

}
}

}

c.ctx.Set(key, rv.Interface())

vvs, err := c.evalExpression(node.Callee)
if err != nil {
return nil, err
}

return vvs, nil
}
54 changes: 54 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ func (p *parser) curPrecedence() int {
func (p *parser) parseIdentifier() ast.Expression {

id := &ast.Identifier{TokenAble: ast.TokenAble{p.curToken}}
orignalCalleAddress := id
ss := strings.Split(p.curToken.Literal, ".")
id.Value = ss[0]

Expand All @@ -270,6 +271,10 @@ func (p *parser) parseIdentifier() ast.Expression {
id = &ast.Identifier{TokenAble: ast.TokenAble{p.curToken}, Value: s, Callee: id}
}

//To avoid a recursive loop to reach the original calle address
id.OriginalCallee = orignalCalleAddress
//}

if p.peekTokenIs(token.ASSIGN) {
return p.parseAssignExpression(id)
}
Expand Down Expand Up @@ -692,6 +697,19 @@ func (p *parser) parseIndexExpression(left ast.Expression) ast.Expression {
return nil
}

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

exp.Callee = p.assignCallee(parseExp, calleeIdent)
if exp.Callee == nil {

return nil
}
}

if p.peekTokenIs(token.ASSIGN) {
p.nextToken()
p.nextToken()
Expand All @@ -702,6 +720,42 @@ func (p *parser) parseIndexExpression(left ast.Expression) ast.Expression {
return exp
}

func (p *parser) assignCallee(exp ast.Expression, calleeIdent *ast.Identifier) (assignedCallee ast.Expression) {

assignedCallee = nil

switch ss := exp.(type) {

case *ast.IndexExpression:

ff, ok := ss.Left.(*ast.Identifier)
if ok {

ff.OriginalCallee.Callee = calleeIdent

assignedCallee = ss
} else {

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.Identifier:

ss.OriginalCallee.Callee = calleeIdent

assignedCallee = ss

default:

msg := fmt.Sprintf("line %d: syntax error: invalid nested index access, got %v", p.curToken.LineNumber, ss)
p.errors = append(p.errors, msg)

}

return
}

func (p *parser) parseHashLiteral() ast.Expression {

hash := &ast.HashLiteral{TokenAble: ast.TokenAble{p.curToken}}
Expand Down
48 changes: 48 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,54 @@ func Test_CallExpressionParsing_WithMultipleCallees(t *testing.T) {
r.Equal(exp.Arguments[0].String(), "\"mark\"")
}

func Test_IndexExpression_Nested_Structs_Start_WithCallee(t *testing.T) {
r := require.New(t)
input := `<% myarray[0].Name.Final %>`

program, err := Parse(input)
r.NoError(err)

r.Len(program.Statements, 1)

stmt := program.Statements[0].(*ast.ExpressionStatement)

exp := stmt.Expression.(*ast.IndexExpression)

r.Equal("myarray", exp.Left.String())
r.Equal("0", exp.Index.String())
gg := exp.Callee.(*ast.Identifier)
r.Equal("myarray.Name.Final", gg.String())

r.Equal("Final", gg.Value)
r.Equal("Name", gg.Callee.Value)
}

func Test_IndexExpression_Nested_Structs_Start_WithNested_Array(t *testing.T) {
r := require.New(t)
input := `<% myarray[0].Name[1].Final %>`

program, err := Parse(input)
r.NoError(err)

r.Len(program.Statements, 1)

stmt := program.Statements[0].(*ast.ExpressionStatement)

exp := stmt.Expression.(*ast.IndexExpression)

r.Equal("myarray", exp.Left.String())
r.Equal("0", exp.Index.String())
gg := exp.Callee.(*ast.IndexExpression)

r.Equal("myarray.Name", gg.Left.String())
r.Equal("1", gg.Index.String())

r.Equal("Name.Final", gg.Callee.String())
cc := gg.Callee.(*ast.Identifier)

r.Equal("Final", cc.Value)
}

func Test_CallExpressionParsing_WithBlock(t *testing.T) {
r := require.New(t)
input := `<p><%= foo() { %>hi<% } %></p>`
Expand Down
Loading

0 comments on commit 73f5081

Please sign in to comment.