mirror of
https://github.com/mainflux/mainflux.git
synced 2025-05-02 22:17:10 +08:00

* Return Auth service Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update Compose to run with SpiceDB and Auth svc Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update auth gRPC API Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Remove Users' policies Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Move Groups to internal Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Use shared groups in Users Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Remove unused code Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Use pkg Groups in Things Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Remove Things groups Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Make imports consistent Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update Groups networking Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Remove things groups-specific API Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Move Things Clients to the root Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Move Clients to Users root Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Temporarily remove tracing Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Fix imports Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Add buffer config for gRPC Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update auth type for Things Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Use Auth for login Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Add temporary solution for refresh token Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update Tokenizer interface Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Updade tokens issuing Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Fix token issuing Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update JWT validator and refactor Tokenizer Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Rename access timeout Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Rename login to authenticate Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update Identify to use SubjectID Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Add Auth to Groups Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Use the Auth service for Groups Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update auth schema Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Fix Auth for Groups Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Add auth for addons (#14) Signed-off-by: Arvindh <arvindh91@gmail.com> Speparate Login and Refresh tokens Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Merge authN and authZ requests for things Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Add connect and disconnect Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update sharing Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Fix policies addition and removal Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Update relation with roels Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Add gRPC to Things Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Assign and Unassign members to group and Listing of Group members (#15) * add auth for addons Signed-off-by: Arvindh <arvindh91@gmail.com> * add assign and unassign to group Signed-off-by: Arvindh <arvindh91@gmail.com> * add group incomplete repo implementation Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users Signed-off-by: Arvindh <arvindh91@gmail.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com> Move coap mqtt and ws policies to spicedb (#16) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Remove old policies Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> NOISSUE - Things authorize to return thingID (#18) This commit modifies the authorize endpoint to the grpc endpoint to return thingID. The authorize endpoint allows adapters to get the publisher of the message. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Add Groups to users service (#17) * add assign and unassign to group Signed-off-by: Arvindh <arvindh91@gmail.com> * add group incomplete repo implementation Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users stable 1 Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users stable 2 Signed-off-by: Arvindh <arvindh91@gmail.com> * groups for users & things Signed-off-by: Arvindh <arvindh91@gmail.com> * Amend signature Signed-off-by: Arvindh <arvindh91@gmail.com> * fix merge error Signed-off-by: Arvindh <arvindh91@gmail.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * NOISSUE - Fix es code (#21) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * NOISSUE - Fix Bugs (#20) * fix bugs Signed-off-by: Arvindh <arvindh91@gmail.com> * fix bugs Signed-off-by: Arvindh <arvindh91@gmail.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * NOISSUE - Test e2e (#19) * fix: connect method Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * fix: e2e Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * fix changes in sdk and e2e Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * feat(docker): remove unnecessary port mapping Remove the port mapping for MQTT broker in the docker-compose.yml file. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Enable group listing Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * feat(responses): update ChannelsPage struct The ChannelsPage struct in the responses.go file has been updated. The "Channels" field has been renamed to "Groups" to provide more accurate naming. This change ensures consistency and clarity in the codebase. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * feat(things): add UpdateClientSecret method Add the UpdateClientSecret method to the things service. This method allows updating the client secret for a specific client identified by the provided token, id, and key parameters. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --------- Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * Use smaller buffers for gRPC Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * Clean up tests (#22) Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * Add Connect Disconnect endpoints (#23) * fix bugs Signed-off-by: Arvindh <arvindh91@gmail.com> * fix bugs Signed-off-by: Arvindh <arvindh91@gmail.com> * fix list of things in a channel and Add connect disconnect endpoint Signed-off-by: Arvindh <arvindh91@gmail.com> * fix list of things in a channel and Add connect disconnect endpoint Signed-off-by: Arvindh <arvindh91@gmail.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * Add: Things share with users (#25) * fix list of things in a channel and Add connect disconnect endpoint Signed-off-by: Arvindh <arvindh91@gmail.com> * add: things share with other users Signed-off-by: Arvindh <arvindh91@gmail.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * NOISSUE - Rename gRPC Services (#24) * Rename things and users auth service Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * docs: add authorization docs for gRPC services Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Rename things and users grpc services Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Remove mainflux.env package Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --------- Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * Add: Listing of things, channels, groups, users (#26) * add: listing of channels, users, groups, things Signed-off-by: Arvindh <arvindh91@gmail.com> * add: listing of channels, users, groups, things Signed-off-by: Arvindh <arvindh91@gmail.com> * add: listing of channels, users, groups, things Signed-off-by: Arvindh <arvindh91@gmail.com> * add: listing of channels, users, groups, things Signed-off-by: Arvindh <arvindh91@gmail.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * NOISSUE - Clean Up Users (#27) * feat(groups): rename redis package to events - Renamed the `redis` package to `events` in the `internal/groups` directory. - Updated the file paths and names accordingly. - This change reflects the more accurate purpose of the package and improves code organization. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * feat(auth): Modify identity method Change request and response of identity method Add accessToken and refreshToken to Token response Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * clean up users, remove dead code Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * feat(users): add unit tests for user service This commit adds unit tests for the user service in the `users` package. The tests cover various scenarios and ensure the correct behavior of the service. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --------- Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * Add: List of user groups & removed repeating code in groups (#29) * removed repeating code in list groups Signed-off-by: Arvindh <arvindh91@gmail.com> * add: list of user group Signed-off-by: Arvindh <arvindh91@gmail.com> * fix: otel handler operator name for endpoints Signed-off-by: Arvindh <arvindh91@gmail.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * NOISSUE - Clean Up Things Service (#28) * Rework things service Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * add tests Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --------- Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * NOISSUE - Clean Up Auth Service (#30) * clean up auth service Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * feat(auth): remove unused import Remove the unused import of `emptypb` in `auth.pb.go`. This import is not being used in the codebase and can be safely removed. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --------- Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * NOISSUE - Update API docs (#31) Signed-off-by: rodneyosodo <blackd0t@protonmail.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * Remove TODO comments and cleanup the code Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> * Update dependenices Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com> Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com> Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Signed-off-by: rodneyosodo <blackd0t@protonmail.com> Co-authored-by: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Co-authored-by: Arvindh <30824765+arvindh123@users.noreply.github.com>
1718 lines
47 KiB
Go
1718 lines
47 KiB
Go
// Copyright 2016 José Santos <henrique_1609@me.com>
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package jet
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/CloudyKit/fastprinter"
|
|
)
|
|
|
|
var (
|
|
funcType = reflect.TypeOf(Func(nil))
|
|
stringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
|
rangerType = reflect.TypeOf((*Ranger)(nil)).Elem()
|
|
rendererType = reflect.TypeOf((*Renderer)(nil)).Elem()
|
|
safeWriterType = reflect.TypeOf(SafeWriter(nil))
|
|
pool_State = sync.Pool{
|
|
New: func() interface{} {
|
|
return &Runtime{scope: &scope{}, escapeeWriter: new(escapeeWriter)}
|
|
},
|
|
}
|
|
)
|
|
|
|
// Renderer is used to detect if a value has its own rendering logic. If the value an action evaluates to implements this
|
|
// interface, it will not be printed using github.com/CloudyKit/fastprinter, instead, its Render() method will be called
|
|
// and is responsible for writing the value to the render output.
|
|
type Renderer interface {
|
|
Render(*Runtime)
|
|
}
|
|
|
|
// RendererFunc func implementing interface Renderer
|
|
type RendererFunc func(*Runtime)
|
|
|
|
func (renderer RendererFunc) Render(r *Runtime) {
|
|
renderer(r)
|
|
}
|
|
|
|
type escapeeWriter struct {
|
|
Writer io.Writer
|
|
escapee SafeWriter
|
|
set *Set
|
|
}
|
|
|
|
func (w *escapeeWriter) Write(b []byte) (int, error) {
|
|
if w.set.escapee == nil {
|
|
w.Writer.Write(b)
|
|
} else {
|
|
w.set.escapee(w.Writer, b)
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
// Runtime this type holds the state of the execution of an template
|
|
type Runtime struct {
|
|
*escapeeWriter
|
|
*scope
|
|
content func(*Runtime, Expression)
|
|
|
|
context reflect.Value
|
|
}
|
|
|
|
// Context returns the current context value
|
|
func (r *Runtime) Context() reflect.Value {
|
|
return r.context
|
|
}
|
|
|
|
func (st *Runtime) newScope() {
|
|
st.scope = &scope{parent: st.scope, variables: make(VarMap), blocks: st.blocks}
|
|
}
|
|
|
|
func (st *Runtime) releaseScope() {
|
|
st.scope = st.scope.parent
|
|
}
|
|
|
|
type scope struct {
|
|
parent *scope
|
|
variables VarMap
|
|
blocks map[string]*BlockNode
|
|
}
|
|
|
|
func (s scope) sortedBlocks() []string {
|
|
r := make([]string, 0, len(s.blocks))
|
|
for k := range s.blocks {
|
|
r = append(r, k)
|
|
}
|
|
sort.Strings(r)
|
|
return r
|
|
}
|
|
|
|
// YieldBlock yields a block in the current context, will panic if the context is not available
|
|
func (st *Runtime) YieldBlock(name string, context interface{}) {
|
|
block, has := st.getBlock(name)
|
|
|
|
if has == false {
|
|
panic(fmt.Errorf("Block %q was not found!!", name))
|
|
}
|
|
|
|
if context != nil {
|
|
current := st.context
|
|
st.context = reflect.ValueOf(context)
|
|
st.executeList(block.List)
|
|
st.context = current
|
|
}
|
|
|
|
st.executeList(block.List)
|
|
}
|
|
|
|
func (st *scope) getBlock(name string) (block *BlockNode, has bool) {
|
|
block, has = st.blocks[name]
|
|
for !has && st.parent != nil {
|
|
st = st.parent
|
|
block, has = st.blocks[name]
|
|
}
|
|
return
|
|
}
|
|
|
|
func (state *Runtime) setValue(name string, val reflect.Value) error {
|
|
// try changing existing variable in current or parent scope
|
|
sc := state.scope
|
|
for sc != nil {
|
|
if _, ok := sc.variables[name]; ok {
|
|
sc.variables[name] = val
|
|
return nil
|
|
}
|
|
sc = sc.parent
|
|
}
|
|
|
|
return fmt.Errorf("could not assign %q = %v because variable %q is uninitialised", name, val, name)
|
|
}
|
|
|
|
// LetGlobal sets or initialises a variable in the top-most template scope.
|
|
func (state *Runtime) LetGlobal(name string, val interface{}) {
|
|
sc := state.scope
|
|
|
|
// walk up to top-most valid scope
|
|
for sc.parent != nil && sc.parent.variables != nil {
|
|
sc = sc.parent
|
|
}
|
|
|
|
sc.variables[name] = reflect.ValueOf(val)
|
|
}
|
|
|
|
// Set sets an existing variable in the template scope it lives in.
|
|
func (state *Runtime) Set(name string, val interface{}) error {
|
|
return state.setValue(name, reflect.ValueOf(val))
|
|
}
|
|
|
|
// Let initialises a variable in the current template scope (possibly shadowing an existing variable of the same name in a parent scope).
|
|
func (state *Runtime) Let(name string, val interface{}) {
|
|
state.scope.variables[name] = reflect.ValueOf(val)
|
|
}
|
|
|
|
// SetOrLet calls Set() (if a variable with the given name is visible from the current scope) or Let() (if there is no variable with the given name in the current or any parent scope).
|
|
func (state *Runtime) SetOrLet(name string, val interface{}) {
|
|
_, err := state.resolve(name)
|
|
if err != nil {
|
|
state.Let(name, val)
|
|
} else {
|
|
state.Set(name, val)
|
|
}
|
|
}
|
|
|
|
// Resolve resolves a value from the execution context.
|
|
func (state *Runtime) resolve(name string) (reflect.Value, error) {
|
|
if name == "." {
|
|
return state.context, nil
|
|
}
|
|
|
|
// try current, then parent variable scopes
|
|
sc := state.scope
|
|
for sc != nil {
|
|
v, ok := sc.variables[name]
|
|
if ok {
|
|
return indirectEface(v), nil
|
|
}
|
|
sc = sc.parent
|
|
}
|
|
|
|
// try globals
|
|
state.set.gmx.RLock()
|
|
v, ok := state.set.globals[name]
|
|
state.set.gmx.RUnlock()
|
|
if ok {
|
|
return indirectEface(v), nil
|
|
}
|
|
|
|
// try default variables
|
|
v, ok = defaultVariables[name]
|
|
if ok {
|
|
return indirectEface(v), nil
|
|
}
|
|
|
|
return reflect.Value{}, fmt.Errorf("identifier %q not available in current (%+v) or parent scope, global, or default variables", name, state.scope.variables)
|
|
}
|
|
|
|
// Resolve calls resolve() and ignores any errors, meaning it may return a zero reflect.Value.
|
|
func (state *Runtime) Resolve(name string) reflect.Value {
|
|
v, _ := state.resolve(name)
|
|
return v
|
|
}
|
|
|
|
// Resolve calls resolve() and panics if there is an error.
|
|
func (state *Runtime) MustResolve(name string) reflect.Value {
|
|
v, err := state.resolve(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func (st *Runtime) recover(err *error) {
|
|
// reset state scope and context just to be safe (they might not be cleared properly if there was a panic while using the state)
|
|
st.scope = &scope{}
|
|
st.context = reflect.Value{}
|
|
pool_State.Put(st)
|
|
if recovered := recover(); recovered != nil {
|
|
var ok bool
|
|
if _, ok = recovered.(runtime.Error); ok {
|
|
panic(recovered)
|
|
}
|
|
*err, ok = recovered.(error)
|
|
if !ok {
|
|
panic(recovered)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (st *Runtime) executeSet(left Expression, right reflect.Value) {
|
|
typ := left.Type()
|
|
if typ == NodeIdentifier {
|
|
err := st.setValue(left.(*IdentifierNode).Ident, right)
|
|
if err != nil {
|
|
left.error(err)
|
|
}
|
|
return
|
|
}
|
|
var value reflect.Value
|
|
var fields []string
|
|
if typ == NodeChain {
|
|
chain := left.(*ChainNode)
|
|
value = st.evalPrimaryExpressionGroup(chain.Node)
|
|
fields = chain.Field
|
|
} else {
|
|
fields = left.(*FieldNode).Ident
|
|
value = st.context
|
|
}
|
|
lef := len(fields) - 1
|
|
for i := 0; i < lef; i++ {
|
|
var err error
|
|
value, err = resolveIndex(value, reflect.Value{}, fields[i])
|
|
if err != nil {
|
|
left.errorf("%v", err)
|
|
}
|
|
}
|
|
|
|
RESTART:
|
|
switch value.Kind() {
|
|
case reflect.Ptr:
|
|
value = value.Elem()
|
|
goto RESTART
|
|
case reflect.Struct:
|
|
value = value.FieldByName(fields[lef])
|
|
if !value.IsValid() {
|
|
left.errorf("identifier %q is not available in the current scope", fields[lef])
|
|
}
|
|
value.Set(right)
|
|
case reflect.Map:
|
|
value.SetMapIndex(reflect.ValueOf(&fields[lef]).Elem(), right)
|
|
}
|
|
}
|
|
|
|
func (st *Runtime) executeSetList(set *SetNode) {
|
|
if set.IndexExprGetLookup {
|
|
value := st.evalPrimaryExpressionGroup(set.Right[0])
|
|
if set.Left[0].Type() != NodeUnderscore {
|
|
st.executeSet(set.Left[0], value)
|
|
}
|
|
if set.Left[1].Type() != NodeUnderscore {
|
|
if value.IsValid() {
|
|
st.executeSet(set.Left[1], valueBoolTRUE)
|
|
} else {
|
|
st.executeSet(set.Left[1], valueBoolFALSE)
|
|
}
|
|
}
|
|
} else {
|
|
for i := 0; i < len(set.Left); i++ {
|
|
value := st.evalPrimaryExpressionGroup(set.Right[i])
|
|
if set.Left[i].Type() != NodeUnderscore {
|
|
st.executeSet(set.Left[i], value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (st *Runtime) executeLetList(set *SetNode) {
|
|
if set.IndexExprGetLookup {
|
|
value := st.evalPrimaryExpressionGroup(set.Right[0])
|
|
if set.Left[0].Type() != NodeUnderscore {
|
|
st.variables[set.Left[0].(*IdentifierNode).Ident] = value
|
|
}
|
|
if set.Left[1].Type() != NodeUnderscore {
|
|
if value.IsValid() {
|
|
st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolTRUE
|
|
} else {
|
|
st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolFALSE
|
|
}
|
|
}
|
|
} else {
|
|
for i := 0; i < len(set.Left); i++ {
|
|
value := st.evalPrimaryExpressionGroup(set.Right[i])
|
|
if set.Left[i].Type() != NodeUnderscore {
|
|
st.variables[set.Left[i].(*IdentifierNode).Ident] = value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (st *Runtime) executeYieldBlock(block *BlockNode, blockParam, yieldParam *BlockParameterList, expression Expression, content *ListNode) {
|
|
|
|
needNewScope := len(blockParam.List) > 0 || len(yieldParam.List) > 0
|
|
if needNewScope {
|
|
st.newScope()
|
|
for i := 0; i < len(yieldParam.List); i++ {
|
|
p := &yieldParam.List[i]
|
|
|
|
if p.Expression == nil {
|
|
block.errorf("missing name for block parameter '%s'", blockParam.List[i].Identifier)
|
|
}
|
|
|
|
st.variables[p.Identifier] = st.evalPrimaryExpressionGroup(p.Expression)
|
|
}
|
|
for i := 0; i < len(blockParam.List); i++ {
|
|
p := &blockParam.List[i]
|
|
if _, found := st.variables[p.Identifier]; !found {
|
|
if p.Expression == nil {
|
|
st.variables[p.Identifier] = valueBoolFALSE
|
|
} else {
|
|
st.variables[p.Identifier] = st.evalPrimaryExpressionGroup(p.Expression)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mycontent := st.content
|
|
if content != nil {
|
|
myscope := st.scope
|
|
st.content = func(st *Runtime, expression Expression) {
|
|
outscope := st.scope
|
|
outcontent := st.content
|
|
|
|
st.scope = myscope
|
|
st.content = mycontent
|
|
|
|
if expression != nil {
|
|
context := st.context
|
|
st.context = st.evalPrimaryExpressionGroup(expression)
|
|
st.executeList(content)
|
|
st.context = context
|
|
} else {
|
|
st.executeList(content)
|
|
}
|
|
|
|
st.scope = outscope
|
|
st.content = outcontent
|
|
}
|
|
}
|
|
|
|
if expression != nil {
|
|
context := st.context
|
|
st.context = st.evalPrimaryExpressionGroup(expression)
|
|
st.executeList(block.List)
|
|
st.context = context
|
|
} else {
|
|
st.executeList(block.List)
|
|
}
|
|
|
|
st.content = mycontent
|
|
if needNewScope {
|
|
st.releaseScope()
|
|
}
|
|
}
|
|
|
|
func (st *Runtime) executeList(list *ListNode) (returnValue reflect.Value) {
|
|
inNewScope := false // to use just one scope for multiple actions with variable declarations
|
|
|
|
for i := 0; i < len(list.Nodes); i++ {
|
|
node := list.Nodes[i]
|
|
switch node.Type() {
|
|
|
|
case NodeText:
|
|
node := node.(*TextNode)
|
|
_, err := st.Writer.Write(node.Text)
|
|
if err != nil {
|
|
node.error(err)
|
|
}
|
|
case NodeAction:
|
|
node := node.(*ActionNode)
|
|
if node.Set != nil {
|
|
if node.Set.Let {
|
|
if !inNewScope {
|
|
st.newScope()
|
|
inNewScope = true
|
|
defer st.releaseScope()
|
|
}
|
|
st.executeLetList(node.Set)
|
|
} else {
|
|
st.executeSetList(node.Set)
|
|
}
|
|
}
|
|
if node.Pipe != nil {
|
|
v, safeWriter := st.evalPipelineExpression(node.Pipe)
|
|
if !safeWriter && v.IsValid() {
|
|
if v.Type().Implements(rendererType) {
|
|
v.Interface().(Renderer).Render(st)
|
|
} else {
|
|
_, err := fastprinter.PrintValue(st.escapeeWriter, v)
|
|
if err != nil {
|
|
node.error(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case NodeIf:
|
|
node := node.(*IfNode)
|
|
var isLet bool
|
|
if node.Set != nil {
|
|
if node.Set.Let {
|
|
isLet = true
|
|
st.newScope()
|
|
st.executeLetList(node.Set)
|
|
} else {
|
|
st.executeSetList(node.Set)
|
|
}
|
|
}
|
|
|
|
if isTrue(st.evalPrimaryExpressionGroup(node.Expression)) {
|
|
returnValue = st.executeList(node.List)
|
|
} else if node.ElseList != nil {
|
|
returnValue = st.executeList(node.ElseList)
|
|
}
|
|
if isLet {
|
|
st.releaseScope()
|
|
}
|
|
case NodeRange:
|
|
node := node.(*RangeNode)
|
|
var expression reflect.Value
|
|
|
|
isSet := node.Set != nil
|
|
isLet := false
|
|
keyVarSlot := 0
|
|
valVarSlot := -1
|
|
|
|
context := st.context
|
|
|
|
if isSet {
|
|
if len(node.Set.Left) > 1 {
|
|
valVarSlot = 1
|
|
}
|
|
expression = st.evalPrimaryExpressionGroup(node.Set.Right[0])
|
|
if node.Set.Let {
|
|
isLet = true
|
|
st.newScope()
|
|
}
|
|
} else {
|
|
expression = st.evalPrimaryExpressionGroup(node.Expression)
|
|
}
|
|
|
|
ranger, cleanup, err := getRanger(expression)
|
|
if err != nil {
|
|
node.error(err)
|
|
}
|
|
if !ranger.ProvidesIndex() {
|
|
if isSet && len(node.Set.Left) > 1 {
|
|
// two-vars assignment with ranger that doesn't provide an index
|
|
node.error(errors.New("two-var range over ranger that does not provide an index"))
|
|
} else if isSet {
|
|
keyVarSlot, valVarSlot = -1, 0
|
|
}
|
|
}
|
|
|
|
indexValue, rangeValue, end := ranger.Range()
|
|
if !end {
|
|
for !end && !returnValue.IsValid() {
|
|
if isSet {
|
|
if isLet {
|
|
if keyVarSlot >= 0 {
|
|
st.variables[node.Set.Left[keyVarSlot].String()] = indexValue
|
|
}
|
|
if valVarSlot >= 0 {
|
|
st.variables[node.Set.Left[valVarSlot].String()] = rangeValue
|
|
}
|
|
} else {
|
|
if keyVarSlot >= 0 {
|
|
st.executeSet(node.Set.Left[keyVarSlot], indexValue)
|
|
}
|
|
if valVarSlot >= 0 {
|
|
st.executeSet(node.Set.Left[valVarSlot], rangeValue)
|
|
}
|
|
}
|
|
}
|
|
if valVarSlot < 0 {
|
|
st.context = rangeValue
|
|
}
|
|
returnValue = st.executeList(node.List)
|
|
indexValue, rangeValue, end = ranger.Range()
|
|
}
|
|
} else if node.ElseList != nil {
|
|
returnValue = st.executeList(node.ElseList)
|
|
}
|
|
cleanup()
|
|
st.context = context
|
|
if isLet {
|
|
st.releaseScope()
|
|
}
|
|
case NodeTry:
|
|
node := node.(*TryNode)
|
|
returnValue = st.executeTry(node)
|
|
case NodeYield:
|
|
node := node.(*YieldNode)
|
|
if node.IsContent {
|
|
if st.content != nil {
|
|
st.content(st, node.Expression)
|
|
}
|
|
} else {
|
|
block, has := st.getBlock(node.Name)
|
|
if has == false || block == nil {
|
|
node.errorf("unresolved block %q!!", node.Name)
|
|
}
|
|
st.executeYieldBlock(block, block.Parameters, node.Parameters, node.Expression, node.Content)
|
|
}
|
|
case NodeBlock:
|
|
node := node.(*BlockNode)
|
|
block, has := st.getBlock(node.Name)
|
|
if has == false {
|
|
block = node
|
|
}
|
|
st.executeYieldBlock(block, block.Parameters, block.Parameters, block.Expression, block.Content)
|
|
case NodeInclude:
|
|
node := node.(*IncludeNode)
|
|
returnValue = st.executeInclude(node)
|
|
case NodeReturn:
|
|
node := node.(*ReturnNode)
|
|
returnValue = st.evalPrimaryExpressionGroup(node.Value)
|
|
}
|
|
}
|
|
|
|
return returnValue
|
|
}
|
|
|
|
func (st *Runtime) executeTry(try *TryNode) (returnValue reflect.Value) {
|
|
writer := st.Writer
|
|
buf := new(bytes.Buffer)
|
|
|
|
defer func() {
|
|
r := recover()
|
|
|
|
// copy buffered render output to writer only if no panic occured
|
|
if r == nil {
|
|
io.Copy(writer, buf)
|
|
} else {
|
|
// st.Writer is already set to its original value since the later defer ran first
|
|
if try.Catch != nil {
|
|
if try.Catch.Err != nil {
|
|
st.newScope()
|
|
st.scope.variables[try.Catch.Err.Ident] = reflect.ValueOf(r)
|
|
}
|
|
if try.Catch.List != nil {
|
|
returnValue = st.executeList(try.Catch.List)
|
|
}
|
|
if try.Catch.Err != nil {
|
|
st.releaseScope()
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
st.Writer = buf
|
|
defer func() { st.Writer = writer }()
|
|
|
|
return st.executeList(try.List)
|
|
}
|
|
|
|
func (st *Runtime) executeInclude(node *IncludeNode) (returnValue reflect.Value) {
|
|
var templatePath string
|
|
name := st.evalPrimaryExpressionGroup(node.Name)
|
|
if !name.IsValid() {
|
|
node.errorf("evaluating name of template to include: name is not a valid value")
|
|
}
|
|
if name.Type().Implements(stringerType) {
|
|
templatePath = name.String()
|
|
} else if name.Kind() == reflect.String {
|
|
templatePath = name.String()
|
|
} else {
|
|
node.errorf("evaluating name of template to include: unexpected expression type %q", getTypeString(name))
|
|
}
|
|
|
|
t, err := st.set.getSiblingTemplate(templatePath, node.TemplatePath, true)
|
|
if err != nil {
|
|
node.error(err)
|
|
return reflect.Value{}
|
|
}
|
|
|
|
st.newScope()
|
|
defer st.releaseScope()
|
|
|
|
st.blocks = t.processedBlocks
|
|
|
|
var context reflect.Value
|
|
if node.Context != nil {
|
|
context = st.context
|
|
defer func() { st.context = context }()
|
|
st.context = st.evalPrimaryExpressionGroup(node.Context)
|
|
}
|
|
|
|
Root := t.Root
|
|
for t.extends != nil {
|
|
t = t.extends
|
|
Root = t.Root
|
|
}
|
|
|
|
return st.executeList(Root)
|
|
}
|
|
|
|
var (
|
|
valueBoolTRUE = reflect.ValueOf(true)
|
|
valueBoolFALSE = reflect.ValueOf(false)
|
|
)
|
|
|
|
func (st *Runtime) evalPrimaryExpressionGroup(node Expression) reflect.Value {
|
|
switch node.Type() {
|
|
case NodeAdditiveExpr:
|
|
return st.evalAdditiveExpression(node.(*AdditiveExprNode))
|
|
case NodeMultiplicativeExpr:
|
|
return st.evalMultiplicativeExpression(node.(*MultiplicativeExprNode))
|
|
case NodeComparativeExpr:
|
|
return st.evalComparativeExpression(node.(*ComparativeExprNode))
|
|
case NodeNumericComparativeExpr:
|
|
return st.evalNumericComparativeExpression(node.(*NumericComparativeExprNode))
|
|
case NodeLogicalExpr:
|
|
return st.evalLogicalExpression(node.(*LogicalExprNode))
|
|
case NodeNotExpr:
|
|
return reflect.ValueOf(!isTrue(st.evalPrimaryExpressionGroup(node.(*NotExprNode).Expr)))
|
|
case NodeTernaryExpr:
|
|
node := node.(*TernaryExprNode)
|
|
if isTrue(st.evalPrimaryExpressionGroup(node.Boolean)) {
|
|
return st.evalPrimaryExpressionGroup(node.Left)
|
|
}
|
|
return st.evalPrimaryExpressionGroup(node.Right)
|
|
case NodeCallExpr:
|
|
node := node.(*CallExprNode)
|
|
baseExpr := st.evalBaseExpressionGroup(node.BaseExpr)
|
|
if baseExpr.Kind() != reflect.Func {
|
|
node.errorf("node %q is not func kind %q", node.BaseExpr, baseExpr.Type())
|
|
}
|
|
ret, err := st.evalCallExpression(baseExpr, node.CallArgs)
|
|
if err != nil {
|
|
node.error(err)
|
|
}
|
|
return ret
|
|
case NodeIndexExpr:
|
|
node := node.(*IndexExprNode)
|
|
base := st.evalPrimaryExpressionGroup(node.Base)
|
|
index := st.evalPrimaryExpressionGroup(node.Index)
|
|
|
|
resolved, err := resolveIndex(base, index, "")
|
|
if err != nil {
|
|
node.error(err)
|
|
}
|
|
return resolved
|
|
case NodeSliceExpr:
|
|
node := node.(*SliceExprNode)
|
|
baseExpression := st.evalPrimaryExpressionGroup(node.Base)
|
|
|
|
var index, length int
|
|
if node.Index != nil {
|
|
indexExpression := st.evalPrimaryExpressionGroup(node.Index)
|
|
if canNumber(indexExpression.Kind()) {
|
|
index = int(castInt64(indexExpression))
|
|
} else {
|
|
node.Index.errorf("non numeric value in index expression kind %s", indexExpression.Kind().String())
|
|
}
|
|
}
|
|
|
|
if node.EndIndex != nil {
|
|
indexExpression := st.evalPrimaryExpressionGroup(node.EndIndex)
|
|
if canNumber(indexExpression.Kind()) {
|
|
length = int(castInt64(indexExpression))
|
|
} else {
|
|
node.EndIndex.errorf("non numeric value in index expression kind %s", indexExpression.Kind().String())
|
|
}
|
|
} else {
|
|
length = baseExpression.Len()
|
|
}
|
|
|
|
return baseExpression.Slice(index, length)
|
|
}
|
|
return st.evalBaseExpressionGroup(node)
|
|
}
|
|
|
|
// notNil returns false when v.IsValid() == false
|
|
// or when v's kind can be nil and v.IsNil() == true
|
|
func notNil(v reflect.Value) bool {
|
|
if !v.IsValid() {
|
|
return false
|
|
}
|
|
switch v.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
return !v.IsNil()
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (st *Runtime) isSet(node Node) (ok bool) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
// something panicked while evaluating node
|
|
ok = false
|
|
}
|
|
}()
|
|
|
|
nodeType := node.Type()
|
|
|
|
switch nodeType {
|
|
case NodeIndexExpr:
|
|
node := node.(*IndexExprNode)
|
|
if !st.isSet(node.Base) || !st.isSet(node.Index) {
|
|
return false
|
|
}
|
|
|
|
base := st.evalPrimaryExpressionGroup(node.Base)
|
|
index := st.evalPrimaryExpressionGroup(node.Index)
|
|
|
|
resolved, err := resolveIndex(base, index, "")
|
|
return err == nil && notNil(resolved)
|
|
case NodeIdentifier:
|
|
value, err := st.resolve(node.String())
|
|
return err == nil && notNil(value)
|
|
case NodeField:
|
|
node := node.(*FieldNode)
|
|
resolved := st.context
|
|
for i := 0; i < len(node.Ident); i++ {
|
|
var err error
|
|
resolved, err = resolveIndex(resolved, reflect.Value{}, node.Ident[i])
|
|
if err != nil || !notNil(resolved) {
|
|
return false
|
|
}
|
|
}
|
|
case NodeChain:
|
|
node := node.(*ChainNode)
|
|
resolved, err := st.evalChainNodeExpression(node)
|
|
return err == nil && notNil(resolved)
|
|
default:
|
|
//todo: maybe work some edge cases
|
|
if !(nodeType > beginExpressions && nodeType < endExpressions) {
|
|
node.errorf("unexpected %q node in isset clause", node)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (st *Runtime) evalNumericComparativeExpression(node *NumericComparativeExprNode) reflect.Value {
|
|
left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
|
|
isTrue := false
|
|
kind := left.Kind()
|
|
|
|
// if the left value is not a float and the right is, we need to promote the left value to a float before the calculation
|
|
// this is necessary for expressions like 4*1.23
|
|
needFloatPromotion := !isFloat(kind) && isFloat(right.Kind())
|
|
|
|
switch node.Operator.typ {
|
|
case itemGreat:
|
|
if isInt(kind) {
|
|
if needFloatPromotion {
|
|
isTrue = float64(left.Int()) > right.Float()
|
|
} else {
|
|
isTrue = left.Int() > toInt(right)
|
|
}
|
|
} else if isFloat(kind) {
|
|
isTrue = left.Float() > toFloat(right)
|
|
} else if isUint(kind) {
|
|
if needFloatPromotion {
|
|
isTrue = float64(left.Uint()) > right.Float()
|
|
} else {
|
|
isTrue = left.Uint() > toUint(right)
|
|
}
|
|
} else {
|
|
node.Left.errorf("a non numeric value in numeric comparative expression")
|
|
}
|
|
case itemGreatEquals:
|
|
if isInt(kind) {
|
|
if needFloatPromotion {
|
|
isTrue = float64(left.Int()) >= right.Float()
|
|
} else {
|
|
isTrue = left.Int() >= toInt(right)
|
|
}
|
|
} else if isFloat(kind) {
|
|
isTrue = left.Float() >= toFloat(right)
|
|
} else if isUint(kind) {
|
|
if needFloatPromotion {
|
|
isTrue = float64(left.Uint()) >= right.Float()
|
|
} else {
|
|
isTrue = left.Uint() >= toUint(right)
|
|
}
|
|
} else {
|
|
node.Left.errorf("a non numeric value in numeric comparative expression")
|
|
}
|
|
case itemLess:
|
|
if isInt(kind) {
|
|
if needFloatPromotion {
|
|
isTrue = float64(left.Int()) < right.Float()
|
|
} else {
|
|
isTrue = left.Int() < toInt(right)
|
|
}
|
|
} else if isFloat(kind) {
|
|
isTrue = left.Float() < toFloat(right)
|
|
} else if isUint(kind) {
|
|
if needFloatPromotion {
|
|
isTrue = float64(left.Uint()) < right.Float()
|
|
} else {
|
|
isTrue = left.Uint() < toUint(right)
|
|
}
|
|
} else {
|
|
node.Left.errorf("a non numeric value in numeric comparative expression")
|
|
}
|
|
case itemLessEquals:
|
|
if isInt(kind) {
|
|
if needFloatPromotion {
|
|
isTrue = float64(left.Int()) <= right.Float()
|
|
} else {
|
|
isTrue = left.Int() <= toInt(right)
|
|
}
|
|
} else if isFloat(kind) {
|
|
isTrue = left.Float() <= toFloat(right)
|
|
} else if isUint(kind) {
|
|
if needFloatPromotion {
|
|
isTrue = float64(left.Uint()) <= right.Float()
|
|
} else {
|
|
isTrue = left.Uint() <= toUint(right)
|
|
}
|
|
} else {
|
|
node.Left.errorf("a non numeric value in numeric comparative expression")
|
|
}
|
|
}
|
|
return reflect.ValueOf(isTrue)
|
|
}
|
|
|
|
func (st *Runtime) evalLogicalExpression(node *LogicalExprNode) reflect.Value {
|
|
truthy := isTrue(st.evalPrimaryExpressionGroup(node.Left))
|
|
if node.Operator.typ == itemAnd {
|
|
truthy = truthy && isTrue(st.evalPrimaryExpressionGroup(node.Right))
|
|
} else {
|
|
truthy = truthy || isTrue(st.evalPrimaryExpressionGroup(node.Right))
|
|
}
|
|
return reflect.ValueOf(truthy)
|
|
}
|
|
|
|
func (st *Runtime) evalComparativeExpression(node *ComparativeExprNode) reflect.Value {
|
|
left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
|
|
equal := checkEquality(left, right)
|
|
if node.Operator.typ == itemNotEquals {
|
|
return reflect.ValueOf(!equal)
|
|
}
|
|
return reflect.ValueOf(equal)
|
|
}
|
|
|
|
func toInt(v reflect.Value) int64 {
|
|
if !v.IsValid() {
|
|
panic(fmt.Errorf("invalid value can't be converted to int64"))
|
|
}
|
|
kind := v.Kind()
|
|
if isInt(kind) {
|
|
return v.Int()
|
|
} else if isFloat(kind) {
|
|
return int64(v.Float())
|
|
} else if isUint(kind) {
|
|
return int64(v.Uint())
|
|
} else if kind == reflect.String {
|
|
n, e := strconv.ParseInt(v.String(), 10, 0)
|
|
if e != nil {
|
|
panic(e)
|
|
}
|
|
return n
|
|
} else if kind == reflect.Bool {
|
|
if v.Bool() {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
panic(fmt.Errorf("type: %q can't be converted to int64", v.Type()))
|
|
}
|
|
|
|
func toUint(v reflect.Value) uint64 {
|
|
if !v.IsValid() {
|
|
panic(fmt.Errorf("invalid value can't be converted to uint64"))
|
|
}
|
|
kind := v.Kind()
|
|
if isUint(kind) {
|
|
return v.Uint()
|
|
} else if isInt(kind) {
|
|
return uint64(v.Int())
|
|
} else if isFloat(kind) {
|
|
return uint64(v.Float())
|
|
} else if kind == reflect.String {
|
|
n, e := strconv.ParseUint(v.String(), 10, 0)
|
|
if e != nil {
|
|
panic(e)
|
|
}
|
|
return n
|
|
} else if kind == reflect.Bool {
|
|
if v.Bool() {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
panic(fmt.Errorf("type: %q can't be converted to uint64", v.Type()))
|
|
}
|
|
|
|
func toFloat(v reflect.Value) float64 {
|
|
if !v.IsValid() {
|
|
panic(fmt.Errorf("invalid value can't be converted to float64"))
|
|
}
|
|
kind := v.Kind()
|
|
if isFloat(kind) {
|
|
return v.Float()
|
|
} else if isInt(kind) {
|
|
return float64(v.Int())
|
|
} else if isUint(kind) {
|
|
return float64(v.Uint())
|
|
} else if kind == reflect.String {
|
|
n, e := strconv.ParseFloat(v.String(), 0)
|
|
if e != nil {
|
|
panic(e)
|
|
}
|
|
return n
|
|
} else if kind == reflect.Bool {
|
|
if v.Bool() {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
panic(fmt.Errorf("type: %q can't be converted to float64", v.Type()))
|
|
}
|
|
|
|
func (st *Runtime) evalMultiplicativeExpression(node *MultiplicativeExprNode) reflect.Value {
|
|
left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
|
|
kind := left.Kind()
|
|
// if the left value is not a float and the right is, we need to promote the left value to a float before the calculation
|
|
// this is necessary for expressions like 4*1.23
|
|
needFloatPromotion := !isFloat(kind) && isFloat(right.Kind())
|
|
switch node.Operator.typ {
|
|
case itemMul:
|
|
if isInt(kind) {
|
|
if needFloatPromotion {
|
|
// do the promotion and calculates
|
|
left = reflect.ValueOf(float64(left.Int()) * right.Float())
|
|
} else {
|
|
// do not need float promotion
|
|
left = reflect.ValueOf(left.Int() * toInt(right))
|
|
}
|
|
} else if isFloat(kind) {
|
|
left = reflect.ValueOf(left.Float() * toFloat(right))
|
|
} else if isUint(kind) {
|
|
if needFloatPromotion {
|
|
left = reflect.ValueOf(float64(left.Uint()) * right.Float())
|
|
} else {
|
|
left = reflect.ValueOf(left.Uint() * toUint(right))
|
|
}
|
|
} else {
|
|
node.Left.errorf("a non numeric value in multiplicative expression")
|
|
}
|
|
case itemDiv:
|
|
if isInt(kind) {
|
|
if needFloatPromotion {
|
|
left = reflect.ValueOf(float64(left.Int()) / right.Float())
|
|
} else {
|
|
left = reflect.ValueOf(left.Int() / toInt(right))
|
|
}
|
|
} else if isFloat(kind) {
|
|
left = reflect.ValueOf(left.Float() / toFloat(right))
|
|
} else if isUint(kind) {
|
|
if needFloatPromotion {
|
|
left = reflect.ValueOf(float64(left.Uint()) / right.Float())
|
|
} else {
|
|
left = reflect.ValueOf(left.Uint() / toUint(right))
|
|
}
|
|
} else {
|
|
node.Left.errorf("a non numeric value in multiplicative expression")
|
|
}
|
|
case itemMod:
|
|
if isInt(kind) {
|
|
left = reflect.ValueOf(left.Int() % toInt(right))
|
|
} else if isFloat(kind) {
|
|
left = reflect.ValueOf(int64(left.Float()) % toInt(right))
|
|
} else if isUint(kind) {
|
|
left = reflect.ValueOf(left.Uint() % toUint(right))
|
|
} else {
|
|
node.Left.errorf("a non numeric value in multiplicative expression")
|
|
}
|
|
}
|
|
return left
|
|
}
|
|
|
|
func (st *Runtime) evalAdditiveExpression(node *AdditiveExprNode) reflect.Value {
|
|
isAdditive := node.Operator.typ == itemAdd
|
|
if node.Left == nil {
|
|
right := st.evalPrimaryExpressionGroup(node.Right)
|
|
if !right.IsValid() {
|
|
node.errorf("right side of additive expression is invalid value")
|
|
}
|
|
kind := right.Kind()
|
|
// todo: optimize
|
|
if isInt(kind) {
|
|
if isAdditive {
|
|
return reflect.ValueOf(+right.Int())
|
|
} else {
|
|
return reflect.ValueOf(-right.Int())
|
|
}
|
|
} else if isUint(kind) {
|
|
if isAdditive {
|
|
return right
|
|
} else {
|
|
return reflect.ValueOf(-int64(right.Uint()))
|
|
}
|
|
} else if isFloat(kind) {
|
|
if isAdditive {
|
|
return reflect.ValueOf(+right.Float())
|
|
} else {
|
|
return reflect.ValueOf(-right.Float())
|
|
}
|
|
}
|
|
node.Left.errorf("additive expression: right side %s (%s) is not a numeric value (no left side)", node.Right, getTypeString(right))
|
|
}
|
|
|
|
left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
|
|
if !left.IsValid() {
|
|
node.errorf("left side of additive expression is invalid value")
|
|
}
|
|
if !right.IsValid() {
|
|
node.errorf("right side of additive expression is invalid value")
|
|
}
|
|
kind := left.Kind()
|
|
// if the left value is not a float and the right is, we need to promote the left value to a float before the calculation
|
|
// this is necessary for expressions like 4+1.23
|
|
needFloatPromotion := !isFloat(kind) && kind != reflect.String && isFloat(right.Kind())
|
|
if needFloatPromotion {
|
|
if isInt(kind) {
|
|
if isAdditive {
|
|
left = reflect.ValueOf(float64(left.Int()) + right.Float())
|
|
} else {
|
|
left = reflect.ValueOf(float64(left.Int()) - right.Float())
|
|
}
|
|
} else if isUint(kind) {
|
|
if isAdditive {
|
|
left = reflect.ValueOf(float64(left.Uint()) + right.Float())
|
|
} else {
|
|
left = reflect.ValueOf(float64(left.Uint()) - right.Float())
|
|
}
|
|
} else {
|
|
node.Left.errorf("additive expression: left side (%s (%s) needs float promotion but neither int nor uint)", node.Left, getTypeString(left))
|
|
}
|
|
} else {
|
|
if isInt(kind) {
|
|
if isAdditive {
|
|
left = reflect.ValueOf(left.Int() + toInt(right))
|
|
} else {
|
|
left = reflect.ValueOf(left.Int() - toInt(right))
|
|
}
|
|
} else if isFloat(kind) {
|
|
if isAdditive {
|
|
left = reflect.ValueOf(left.Float() + toFloat(right))
|
|
} else {
|
|
left = reflect.ValueOf(left.Float() - toFloat(right))
|
|
}
|
|
} else if isUint(kind) {
|
|
if isAdditive {
|
|
left = reflect.ValueOf(left.Uint() + toUint(right))
|
|
} else {
|
|
left = reflect.ValueOf(left.Uint() - toUint(right))
|
|
}
|
|
} else if kind == reflect.String {
|
|
if !isAdditive {
|
|
node.Right.errorf("minus signal is not allowed with strings")
|
|
}
|
|
// converts []byte (and alias types of []byte) to string
|
|
if right.Kind() == reflect.Slice && right.Type().Elem().Kind() == reflect.Uint8 {
|
|
right = right.Convert(left.Type())
|
|
}
|
|
left = reflect.ValueOf(left.String() + fmt.Sprint(right))
|
|
} else {
|
|
node.Left.errorf("additive expression: left side %s (%s) is not a numeric value", node.Left, getTypeString(left))
|
|
}
|
|
}
|
|
|
|
return left
|
|
}
|
|
|
|
func getTypeString(value reflect.Value) string {
|
|
if value.IsValid() {
|
|
return value.Type().String()
|
|
}
|
|
return "<invalid>"
|
|
}
|
|
|
|
func (st *Runtime) evalBaseExpressionGroup(node Node) reflect.Value {
|
|
switch node.Type() {
|
|
case NodeNil:
|
|
return reflect.ValueOf(nil)
|
|
case NodeBool:
|
|
if node.(*BoolNode).True {
|
|
return valueBoolTRUE
|
|
}
|
|
return valueBoolFALSE
|
|
case NodeString:
|
|
return reflect.ValueOf(&node.(*StringNode).Text).Elem()
|
|
case NodeIdentifier:
|
|
resolved, err := st.resolve(node.(*IdentifierNode).Ident)
|
|
if err != nil {
|
|
node.error(err)
|
|
}
|
|
return resolved
|
|
case NodeField:
|
|
node := node.(*FieldNode)
|
|
resolved := st.context
|
|
for i := 0; i < len(node.Ident); i++ {
|
|
field, err := resolveIndex(resolved, reflect.Value{}, node.Ident[i])
|
|
if err != nil {
|
|
node.errorf("%v", err)
|
|
}
|
|
if !field.IsValid() {
|
|
node.errorf("there is no field or method '%s' in %s (.%s)", node.Ident[i], getTypeString(resolved), strings.Join(node.Ident, "."))
|
|
}
|
|
resolved = field
|
|
}
|
|
return resolved
|
|
case NodeChain:
|
|
resolved, err := st.evalChainNodeExpression(node.(*ChainNode))
|
|
if err != nil {
|
|
node.error(err)
|
|
}
|
|
return resolved
|
|
case NodeNumber:
|
|
node := node.(*NumberNode)
|
|
if node.IsFloat {
|
|
return reflect.ValueOf(&node.Float64).Elem()
|
|
}
|
|
|
|
if node.IsInt {
|
|
return reflect.ValueOf(&node.Int64).Elem()
|
|
}
|
|
|
|
if node.IsUint {
|
|
return reflect.ValueOf(&node.Uint64).Elem()
|
|
}
|
|
}
|
|
node.errorf("unexpected node type %s in unary expression evaluating", node)
|
|
return reflect.Value{}
|
|
}
|
|
|
|
func (st *Runtime) evalCallExpression(baseExpr reflect.Value, args CallArgs) (reflect.Value, error) {
|
|
return st.evalPipeCallExpression(baseExpr, args, nil)
|
|
}
|
|
|
|
func (st *Runtime) evalPipeCallExpression(baseExpr reflect.Value, args CallArgs, pipedArg *reflect.Value) (reflect.Value, error) {
|
|
if !baseExpr.IsValid() {
|
|
return reflect.Value{}, errors.New("base of call expression is invalid value")
|
|
}
|
|
if funcType.AssignableTo(baseExpr.Type()) {
|
|
return baseExpr.Interface().(Func)(Arguments{runtime: st, args: args, pipedVal: pipedArg}), nil
|
|
}
|
|
|
|
argValues, err := st.evaluateArgs(baseExpr.Type(), args, pipedArg)
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("call expression: %v", err)
|
|
}
|
|
|
|
var returns = baseExpr.Call(argValues)
|
|
if len(returns) == 0 {
|
|
return reflect.Value{}, nil
|
|
}
|
|
|
|
return returns[0], nil
|
|
}
|
|
|
|
func (st *Runtime) evalCommandExpression(node *CommandNode) (reflect.Value, bool) {
|
|
term := st.evalPrimaryExpressionGroup(node.BaseExpr)
|
|
if term.IsValid() && node.Exprs != nil {
|
|
if term.Kind() == reflect.Func {
|
|
if term.Type() == safeWriterType {
|
|
st.evalSafeWriter(term, node)
|
|
return reflect.Value{}, true
|
|
}
|
|
ret, err := st.evalCallExpression(term, node.CallArgs)
|
|
if err != nil {
|
|
node.BaseExpr.error(err)
|
|
}
|
|
return ret, false
|
|
}
|
|
node.Exprs[0].errorf("command %q has arguments but is %s, not a function", node.Exprs[0], term.Type())
|
|
}
|
|
return term, false
|
|
}
|
|
|
|
func (st *Runtime) evalChainNodeExpression(node *ChainNode) (reflect.Value, error) {
|
|
resolved := st.evalPrimaryExpressionGroup(node.Node)
|
|
|
|
for i := 0; i < len(node.Field); i++ {
|
|
field, err := resolveIndex(resolved, reflect.Value{}, node.Field[i])
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if !field.IsValid() {
|
|
if resolved.Kind() == reflect.Map && i == len(node.Field)-1 {
|
|
// return reflect.Zero(resolved.Type().Elem()), nil
|
|
return reflect.Value{}, nil
|
|
}
|
|
return reflect.Value{}, fmt.Errorf("there is no field or method '%s' in %s (%s)", node.Field[i], getTypeString(resolved), node)
|
|
}
|
|
resolved = field
|
|
}
|
|
|
|
return resolved, nil
|
|
}
|
|
|
|
type escapeWriter struct {
|
|
rawWriter io.Writer
|
|
safeWriter SafeWriter
|
|
}
|
|
|
|
func (w *escapeWriter) Write(b []byte) (int, error) {
|
|
w.safeWriter(w.rawWriter, b)
|
|
return 0, nil
|
|
}
|
|
|
|
func (st *Runtime) evalSafeWriter(term reflect.Value, node *CommandNode, v ...reflect.Value) {
|
|
sw := &escapeWriter{rawWriter: st.Writer, safeWriter: term.Interface().(SafeWriter)}
|
|
for i := 0; i < len(v); i++ {
|
|
fastprinter.PrintValue(sw, v[i])
|
|
}
|
|
for i := 0; i < len(node.Exprs); i++ {
|
|
fastprinter.PrintValue(sw, st.evalPrimaryExpressionGroup(node.Exprs[i]))
|
|
}
|
|
}
|
|
|
|
func (st *Runtime) evalCommandPipeExpression(node *CommandNode, value reflect.Value) (reflect.Value, bool) {
|
|
term := st.evalPrimaryExpressionGroup(node.BaseExpr)
|
|
if !term.IsValid() {
|
|
node.errorf("base expression of command pipe node is invalid value")
|
|
}
|
|
if term.Kind() != reflect.Func {
|
|
node.BaseExpr.errorf("pipe command %q must be a function, but is %s", node.BaseExpr, term.Type())
|
|
}
|
|
|
|
if term.Type() == safeWriterType {
|
|
st.evalSafeWriter(term, node, value)
|
|
return reflect.Value{}, true
|
|
}
|
|
|
|
ret, err := st.evalPipeCallExpression(term, node.CallArgs, &value)
|
|
if err != nil {
|
|
node.BaseExpr.error(err)
|
|
}
|
|
return ret, false
|
|
}
|
|
|
|
func (st *Runtime) evalPipelineExpression(node *PipeNode) (value reflect.Value, safeWriter bool) {
|
|
value, safeWriter = st.evalCommandExpression(node.Cmds[0])
|
|
for i := 1; i < len(node.Cmds); i++ {
|
|
if safeWriter {
|
|
node.Cmds[i].errorf("unexpected command %s, writer command should be the last command", node.Cmds[i])
|
|
}
|
|
value, safeWriter = st.evalCommandPipeExpression(node.Cmds[i], value)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (st *Runtime) evaluateArgs(fnType reflect.Type, args CallArgs, pipedArg *reflect.Value) ([]reflect.Value, error) {
|
|
numArgs := len(args.Exprs)
|
|
if !args.HasPipeSlot && pipedArg != nil {
|
|
numArgs++
|
|
}
|
|
numArgsRequired := fnType.NumIn()
|
|
isVariadic := fnType.IsVariadic()
|
|
if isVariadic {
|
|
numArgsRequired--
|
|
if numArgs < numArgsRequired {
|
|
return nil, fmt.Errorf("%s needs at least %d arguments, but have %d", fnType, numArgsRequired, numArgs)
|
|
}
|
|
} else {
|
|
if numArgs != numArgsRequired {
|
|
return nil, fmt.Errorf("%s needs %d arguments, but have %d", fnType, numArgsRequired, numArgs)
|
|
}
|
|
}
|
|
|
|
argValues := make([]reflect.Value, numArgs)
|
|
slot := 0 // index in argument values (evaluated expressions combined with piped argument if applicable)
|
|
|
|
if !args.HasPipeSlot && pipedArg != nil {
|
|
in := fnType.In(slot)
|
|
if !(*pipedArg).IsValid() {
|
|
return nil, fmt.Errorf("piped first argument for %s is not a valid value", fnType)
|
|
}
|
|
if !(*pipedArg).Type().AssignableTo(in) {
|
|
*pipedArg = (*pipedArg).Convert(in)
|
|
}
|
|
argValues[slot] = *pipedArg
|
|
slot++
|
|
}
|
|
|
|
i := 0 // index in parsed argument expression list
|
|
|
|
for slot < numArgsRequired {
|
|
in := fnType.In(slot)
|
|
var term reflect.Value
|
|
if args.Exprs[i].Type() == NodeUnderscore {
|
|
term = *pipedArg
|
|
} else {
|
|
term = st.evalPrimaryExpressionGroup(args.Exprs[i])
|
|
}
|
|
if !term.IsValid() {
|
|
return nil, fmt.Errorf("argument for position %d in %s is not a valid value", slot, fnType)
|
|
}
|
|
if !term.Type().AssignableTo(in) {
|
|
term = term.Convert(in)
|
|
}
|
|
argValues[slot] = term
|
|
i++
|
|
slot++
|
|
}
|
|
|
|
if isVariadic {
|
|
in := fnType.In(numArgsRequired).Elem()
|
|
for i < len(args.Exprs) {
|
|
var term reflect.Value
|
|
if args.Exprs[i].Type() == NodeUnderscore {
|
|
term = *pipedArg
|
|
} else {
|
|
term = st.evalPrimaryExpressionGroup(args.Exprs[i])
|
|
}
|
|
if !term.IsValid() {
|
|
return nil, fmt.Errorf("argument for position %d in %s is not a valid value", slot, fnType)
|
|
}
|
|
if !term.Type().AssignableTo(in) {
|
|
term = term.Convert(in)
|
|
}
|
|
argValues[slot] = term
|
|
i++
|
|
slot++
|
|
}
|
|
}
|
|
|
|
return argValues, nil
|
|
}
|
|
|
|
func isUint(kind reflect.Kind) bool {
|
|
return kind >= reflect.Uint && kind <= reflect.Uint64
|
|
}
|
|
func isInt(kind reflect.Kind) bool {
|
|
return kind >= reflect.Int && kind <= reflect.Int64
|
|
}
|
|
func isFloat(kind reflect.Kind) bool {
|
|
return kind == reflect.Float32 || kind == reflect.Float64
|
|
}
|
|
|
|
// checkEquality of two reflect values in the semantic of the jet runtime
|
|
func checkEquality(v1, v2 reflect.Value) bool {
|
|
v1 = indirectInterface(v1)
|
|
v2 = indirectInterface(v2)
|
|
|
|
if !v1.IsValid() || !v2.IsValid() {
|
|
return v1.IsValid() == v2.IsValid()
|
|
}
|
|
|
|
v1Type := v1.Type()
|
|
v2Type := v2.Type()
|
|
|
|
// fast path
|
|
if v1Type != v2Type && !v2Type.AssignableTo(v1Type) && !v2Type.ConvertibleTo(v1Type) {
|
|
return false
|
|
}
|
|
|
|
kind := v1.Kind()
|
|
if isInt(kind) {
|
|
return v1.Int() == toInt(v2)
|
|
}
|
|
if isFloat(kind) {
|
|
return v1.Float() == toFloat(v2)
|
|
}
|
|
if isUint(kind) {
|
|
return v1.Uint() == toUint(v2)
|
|
}
|
|
|
|
switch kind {
|
|
case reflect.Bool:
|
|
return v1.Bool() == isTrue(v2)
|
|
case reflect.String:
|
|
return v1.String() == v2.String()
|
|
case reflect.Array:
|
|
vlen := v1.Len()
|
|
if vlen == v2.Len() {
|
|
return false
|
|
}
|
|
for i := 0; i < vlen; i++ {
|
|
if !checkEquality(v1.Index(i), v2.Index(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Slice:
|
|
if v1.IsNil() != v2.IsNil() {
|
|
return false
|
|
}
|
|
|
|
vlen := v1.Len()
|
|
if vlen != v2.Len() {
|
|
return false
|
|
}
|
|
|
|
if v1.CanAddr() && v2.CanAddr() && v1.Pointer() == v2.Pointer() {
|
|
return true
|
|
}
|
|
|
|
for i := 0; i < vlen; i++ {
|
|
if !checkEquality(v1.Index(i), v2.Index(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Interface:
|
|
if v1.IsNil() || v2.IsNil() {
|
|
return v1.IsNil() == v2.IsNil()
|
|
}
|
|
return checkEquality(v1.Elem(), v2.Elem())
|
|
case reflect.Ptr:
|
|
return v1.Pointer() == v2.Pointer()
|
|
case reflect.Struct:
|
|
numField := v1.NumField()
|
|
for i, n := 0, numField; i < n; i++ {
|
|
if !checkEquality(v1.Field(i), v2.Field(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Map:
|
|
if v1.IsNil() != v2.IsNil() {
|
|
return false
|
|
}
|
|
if v1.Len() != v2.Len() {
|
|
return false
|
|
}
|
|
if v1.Pointer() == v2.Pointer() {
|
|
return true
|
|
}
|
|
for _, k := range v1.MapKeys() {
|
|
val1 := v1.MapIndex(k)
|
|
val2 := v2.MapIndex(k)
|
|
if !val1.IsValid() || !val2.IsValid() || !checkEquality(v1.MapIndex(k), v2.MapIndex(k)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Func:
|
|
return v1.IsNil() && v2.IsNil()
|
|
default:
|
|
// Normal equality suffices
|
|
return v1.Interface() == v2.Interface()
|
|
}
|
|
}
|
|
|
|
func isTrue(v reflect.Value) bool {
|
|
return v.IsValid() && !v.IsZero()
|
|
}
|
|
|
|
func canNumber(kind reflect.Kind) bool {
|
|
return isInt(kind) || isUint(kind) || isFloat(kind)
|
|
}
|
|
|
|
func castInt64(v reflect.Value) int64 {
|
|
kind := v.Kind()
|
|
switch {
|
|
case isInt(kind):
|
|
return v.Int()
|
|
case isUint(kind):
|
|
return int64(v.Uint())
|
|
case isFloat(kind):
|
|
return int64(v.Float())
|
|
}
|
|
return 0
|
|
}
|
|
|
|
var cachedStructsMutex = sync.RWMutex{}
|
|
var cachedStructsFieldIndex = map[reflect.Type]map[string][]int{}
|
|
|
|
// from text/template's exec.go:
|
|
//
|
|
// indirect returns the item at the end of indirection, and a bool to indicate
|
|
// if it's nil. If the returned bool is true, the returned value's kind will be
|
|
// either a pointer or interface.
|
|
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
|
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
|
if v.IsNil() {
|
|
return v, true
|
|
}
|
|
}
|
|
return v, false
|
|
}
|
|
|
|
// indirectInterface returns the concrete value in an interface value, or else v itself.
|
|
// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x):
|
|
// the fact that x was an interface value is forgotten.
|
|
func indirectInterface(v reflect.Value) reflect.Value {
|
|
if v.Kind() == reflect.Interface {
|
|
return v.Elem()
|
|
}
|
|
return v
|
|
}
|
|
|
|
// indirectEface is the same as indirectInterface, but only indirects through v if its type
|
|
// is the empty interface and its value is not nil.
|
|
func indirectEface(v reflect.Value) reflect.Value {
|
|
if v.Kind() == reflect.Interface && v.Type().NumMethod() == 0 && !v.IsNil() {
|
|
return v.Elem()
|
|
}
|
|
return v
|
|
}
|
|
|
|
// mostly copied from text/template's evalField() (exec.go):
|
|
//
|
|
// The index to use to access v can be specified in either index or indexAsStr.
|
|
// Which parameter is filled depends on the call path up to when a particular
|
|
// call to resolveIndex is made and whether that call site already has access
|
|
// to a reflect.Value for the index or just a string identifier.
|
|
//
|
|
// While having both options makes the implementation of this function more
|
|
// complex, it improves the memory allocation story for the most common
|
|
// execution paths when executing a template, such as when accessing a field
|
|
// element.
|
|
func resolveIndex(v, index reflect.Value, indexAsStr string) (reflect.Value, error) {
|
|
if !v.IsValid() {
|
|
return reflect.Value{}, fmt.Errorf("there is no field or method '%s' in %s (%s)", index, v, getTypeString(v))
|
|
}
|
|
|
|
v, isNil := indirect(v)
|
|
if v.Kind() == reflect.Interface && isNil {
|
|
// Calling a method on a nil interface can't work. The
|
|
// MethodByName method call below would panic.
|
|
return reflect.Value{}, fmt.Errorf("nil pointer evaluating %s.%s", v.Type(), index)
|
|
}
|
|
|
|
// Handle the caller passing either index or indexAsStr.
|
|
indexIsStr := indexAsStr != ""
|
|
indexAsValue := func() reflect.Value { return index }
|
|
if indexIsStr {
|
|
// indexAsStr was specified, so make the indexAsValue function
|
|
// obtain the corresponding reflect.Value. This is only used in
|
|
// some code paths, and since it causes an allocation, a
|
|
// function is used instead of always extracting the
|
|
// reflect.Value.
|
|
indexAsValue = func() reflect.Value {
|
|
return reflect.ValueOf(indexAsStr)
|
|
}
|
|
} else {
|
|
// index was specified, so extract the string value if the index
|
|
// is in fact a string.
|
|
indexIsStr = index.Kind() == reflect.String
|
|
if indexIsStr {
|
|
indexAsStr = index.String()
|
|
}
|
|
}
|
|
|
|
// Unless it's an interface, need to get to a value of type *T to guarantee
|
|
// we see all methods of T and *T.
|
|
if indexIsStr {
|
|
ptr := v
|
|
if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() {
|
|
ptr = ptr.Addr()
|
|
}
|
|
if method := ptr.MethodByName(indexAsStr); method.IsValid() {
|
|
return method, nil
|
|
}
|
|
}
|
|
|
|
// It's not a method on v; so now:
|
|
// - if v is array/slice/string, use index as numeric index
|
|
// - if v is a struct, use index as field name
|
|
// - if v is a map, use index as key
|
|
// - if v is (still) a pointer, indexing will fail but we check for nil to get a useful error
|
|
switch v.Kind() {
|
|
case reflect.Array, reflect.Slice, reflect.String:
|
|
indexVal := indexAsValue()
|
|
x, err := indexArg(indexVal, v.Len())
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
return indirectEface(v.Index(x)), nil
|
|
case reflect.Struct:
|
|
if !indexIsStr {
|
|
return reflect.Value{}, fmt.Errorf("can't use %s (%s, not string) as field name in struct type %s", index, indexAsValue().Type(), v.Type())
|
|
}
|
|
typ := v.Type()
|
|
key := indexAsStr
|
|
|
|
// Fast path: use the struct cache to avoid allocations.
|
|
cachedStructsMutex.RLock()
|
|
cache, ok := cachedStructsFieldIndex[typ]
|
|
cachedStructsMutex.RUnlock()
|
|
if !ok {
|
|
cachedStructsMutex.Lock()
|
|
if cache, ok = cachedStructsFieldIndex[typ]; !ok {
|
|
cache = make(map[string][]int)
|
|
buildCache(typ, cache, nil)
|
|
cachedStructsFieldIndex[typ] = cache
|
|
}
|
|
cachedStructsMutex.Unlock()
|
|
}
|
|
if id, ok := cache[key]; ok {
|
|
return v.FieldByIndex(id), nil
|
|
}
|
|
|
|
// Slow path: use reflect directly
|
|
tField, ok := typ.FieldByName(key)
|
|
if ok {
|
|
field := v.FieldByIndex(tField.Index)
|
|
if tField.PkgPath != "" { // field is unexported
|
|
return reflect.Value{}, fmt.Errorf("%s is an unexported field of struct type %s", indexAsStr, v.Type())
|
|
}
|
|
return indirectEface(field), nil
|
|
}
|
|
return reflect.Value{}, fmt.Errorf("can't use %s as field name in struct type %s", indexAsStr, v.Type())
|
|
case reflect.Map:
|
|
// If it's a map, attempt to use the field name as a key.
|
|
indexVal := indexAsValue()
|
|
if !indexVal.Type().ConvertibleTo(v.Type().Key()) {
|
|
return reflect.Value{}, fmt.Errorf("can't use %s (%s) as key for map of type %s", indexAsStr, indexVal.Type(), v.Type())
|
|
}
|
|
index = indexVal.Convert(v.Type().Key()) // noop in most cases, but not expensive
|
|
return indirectEface(v.MapIndex(indexVal)), nil
|
|
case reflect.Ptr:
|
|
etyp := v.Type().Elem()
|
|
if etyp.Kind() == reflect.Struct && indexIsStr {
|
|
if _, ok := etyp.FieldByName(indexAsStr); !ok {
|
|
// If there's no such field, say "can't evaluate"
|
|
// instead of "nil pointer evaluating".
|
|
break
|
|
}
|
|
}
|
|
if isNil {
|
|
return reflect.Value{}, fmt.Errorf("nil pointer evaluating %s.%s", v.Type(), index)
|
|
}
|
|
}
|
|
return reflect.Value{}, fmt.Errorf("can't evaluate index %s (%s) in type %s", index, indexAsStr, getTypeString(v))
|
|
}
|
|
|
|
// from Go's text/template's funcs.go:
|
|
//
|
|
// indexArg checks if a reflect.Value can be used as an index, and converts it to int if possible.
|
|
func indexArg(index reflect.Value, cap int) (int, error) {
|
|
var x int64
|
|
switch index.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
x = index.Int()
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
x = int64(index.Uint())
|
|
case reflect.Float32, reflect.Float64:
|
|
x = int64(index.Float())
|
|
case reflect.Invalid:
|
|
return 0, fmt.Errorf("cannot index slice/array/string with nil")
|
|
default:
|
|
return 0, fmt.Errorf("cannot index slice/array/string with type %s", getTypeString(index))
|
|
}
|
|
if int(x) < 0 || int(x) >= cap {
|
|
return 0, fmt.Errorf("index out of range: %d", x)
|
|
}
|
|
return int(x), nil
|
|
}
|
|
|
|
func buildCache(typ reflect.Type, cache map[string][]int, parent []int) {
|
|
numFields := typ.NumField()
|
|
max := len(parent) + 1
|
|
|
|
for i := 0; i < numFields; i++ {
|
|
|
|
index := make([]int, max)
|
|
copy(index, parent)
|
|
index[len(parent)] = i
|
|
|
|
field := typ.Field(i)
|
|
if field.Anonymous {
|
|
typ := field.Type
|
|
if typ.Kind() == reflect.Struct {
|
|
buildCache(typ, cache, index)
|
|
}
|
|
}
|
|
cache[field.Name] = index
|
|
}
|
|
}
|