Skip to content

Commit

Permalink
support register function
Browse files Browse the repository at this point in the history
  • Loading branch information
shockerli committed Apr 27, 2021
1 parent 36b4f36 commit ba7f747
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 134 deletions.
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html

.PHONY: check fmt lint test test-race vet test-cover-html help
.DEFAULT_GOAL := help

Expand Down
244 changes: 132 additions & 112 deletions builtin_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,161 +5,181 @@ import (
"unicode/utf8"
)

// builtin function type
type builtinFunc func(fnc *NodeFuncCallOp, scope *VariableScope) interface{}
// Func builtin function type
type Func func(fnc *NodeFuncCallOp, scope *VariableScope) interface{}

// builtin function pool
var builtinMap map[string]builtinFunc
var builtinMap = make(map[string]Func)

func init() {
// init builtin function
builtinMap = map[string]builtinFunc{
"export": export,
"len": length,
"exist": exist,
"del": del,
"print": prints,
registerExport()
registerLen()
registerExist()
registerDel()
registerPrint()
}

// RegisterFunc register builtin function
func RegisterFunc(name string, fn Func) {
if name != "" && fn != nil {
builtinMap[name] = fn
}
}

// return the expression value, and interrupt script
func export(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
if len(fnc.Params) != 1 {
panic(fmt.Sprintf("export() expects 1 parameters, %d given", len(fnc.Params)))
}
// Example: export(123)
func registerExport() {
RegisterFunc("export", func(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
if len(fnc.Params) != 1 {
panic(fmt.Sprintf("export() expects 1 parameters, %d given", len(fnc.Params)))
}

panic(directiveExport{val: evalExpr(fnc.Params[0], scope)})
panic(directiveExport{val: EvalExpr(fnc.Params[0], scope)})
})
}

// return the length of expression
func length(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
if len(fnc.Params) != 1 {
panic(fmt.Sprintf("len() expects 1 parameters, %d given", len(fnc.Params)))
}
// Example: len("123")
func registerLen() {
RegisterFunc("len", func(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
if len(fnc.Params) != 1 {
panic(fmt.Sprintf("len() expects 1 parameters, %d given", len(fnc.Params)))
}

val := evalExpr(fnc.Params[0], scope)
switch val := val.(type) {
case int, float64:
return -1
case string:
return utf8.RuneCountInString(val)
case ValueList:
return len(val)
case ValueMap:
return len(val)
}
val := EvalExpr(fnc.Params[0], scope)
switch val := val.(type) {
case int, float64:
return -1
case string:
return utf8.RuneCountInString(val)
case ValueList:
return len(val)
case ValueMap:
return len(val)
}

return -1
return -1
})
}

// whether a variable or index is existed
func exist(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
if len(fnc.Params) != 1 {
panic(fmt.Sprintf("exist() expects 1 parameters, %d given", len(fnc.Params)))
}
switch fnc.Params[0].(type) {
case *NodeVarIndex, *NodeVariable:
default:
panic("exist() expects parameter variable or index")
}

switch v := fnc.Params[0].(type) {
case *NodeVariable:
if _, ok := scope.Get(v.Format()); ok {
return true
// Example: exist(var), exist(var[9]), exist(var[name])
func registerExist() {
RegisterFunc("exist", func(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
if len(fnc.Params) != 1 {
panic(fmt.Sprintf("exist() expects 1 parameters, %d given", len(fnc.Params)))
}
switch fnc.Params[0].(type) {
case *NodeVarIndex, *NodeVariable:
default:
panic("exist() expects parameter variable or index")
}

case *NodeVarIndex:
varVal := evalExpr(v.Var, scope)
indexVal := evalExpr(v.Index, scope)
switch varVal := varVal.(type) {
case string, float64, int:
idx := int(Interface2Float64(indexVal))
if utf8.RuneCountInString(Interface2String(varVal)) > idx {
switch v := fnc.Params[0].(type) {
case *NodeVariable:
if _, ok := scope.Get(v.Format()); ok {
return true
}

case ValueList:
idx := int(Interface2Float64(indexVal))
r := varVal
if len(r) > idx {
return true
}
case *NodeVarIndex:
varVal := EvalExpr(v.Var, scope)
indexVal := EvalExpr(v.Index, scope)
switch varVal := varVal.(type) {
case string, float64, int:
idx := int(Interface2Float64(indexVal))
if utf8.RuneCountInString(Interface2String(varVal)) > idx {
return true
}

case ValueMap:
idx := Interface2String(indexVal)
r := varVal
if _, ok := r[idx]; ok {
return true
case ValueList:
idx := int(Interface2Float64(indexVal))
r := varVal
if len(r) > idx {
return true
}

case ValueMap:
idx := Interface2String(indexVal)
r := varVal
if _, ok := r[idx]; ok {
return true
}
}
}

}
}

return false
return false
})
}

// delete one or more variable or index
func del(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
if len(fnc.Params) < 1 {
panic(fmt.Sprintf("del() expects least 1 parameters, %d given", len(fnc.Params)))
}

for _, node := range fnc.Params {
switch v := node.(type) {
case *NodeVariable:
scope.enclosingScope.Del(v.Format())
// Example: del(var), del(var["name"]), del(var[9])
func registerDel() {
RegisterFunc("del", func(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
if len(fnc.Params) < 1 {
panic(fmt.Sprintf("del() expects least 1 parameters, %d given", len(fnc.Params)))
}

case *NodeVarIndex:
varVal := evalExpr(v.Var, scope)
indexVal := evalExpr(v.Index, scope)
switch varVal := varVal.(type) {
case ValueList:
idx := int(Interface2Float64(indexVal))
if len(varVal) <= idx {
continue
}
for _, node := range fnc.Params {
switch v := node.(type) {
case *NodeVariable:
scope.enclosingScope.Del(v.Format())

case *NodeVarIndex:
varVal := EvalExpr(v.Var, scope)
indexVal := EvalExpr(v.Index, scope)
switch varVal := varVal.(type) {
case ValueList:
idx := int(Interface2Float64(indexVal))
if len(varVal) <= idx {
continue
}

// delete index
varVal = append(varVal[:idx], varVal[idx+1:]...)
// delete index
varVal = append(varVal[:idx], varVal[idx+1:]...)

// only delete a index from variable
switch vr := v.Var.(type) {
case *NodeVariable:
// self scope no value
if _, ok := scope.enclosingScope.Get(vr.Value); ok {
scope.enclosingScope.Set(vr.Value, varVal)
// only delete a index from variable
switch vr := v.Var.(type) {
case *NodeVariable:
// self scope no value
if _, ok := scope.enclosingScope.Get(vr.Value); ok {
scope.enclosingScope.Set(vr.Value, varVal)
}
}
}

case ValueMap:
idx := Interface2String(indexVal)
if _, ok := varVal[idx]; !ok {
continue
}
case ValueMap:
idx := Interface2String(indexVal)
if _, ok := varVal[idx]; !ok {
continue
}

// delete key
delete(varVal, idx)
// delete key
delete(varVal, idx)

// only delete a index from variable
switch vr := v.Var.(type) {
case *NodeVariable:
// self scope no value
if _, ok := scope.enclosingScope.Get(vr.Value); ok {
scope.enclosingScope.Set(vr.Value, varVal)
// only delete a index from variable
switch vr := v.Var.(type) {
case *NodeVariable:
// self scope no value
if _, ok := scope.enclosingScope.Get(vr.Value); ok {
scope.enclosingScope.Set(vr.Value, varVal)
}
}
}
}
}
}

return nil
return nil
})
}

// print one or more expression value to the terminal
func prints(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
for _, v := range fnc.Params {
fmt.Print(evalExpr(v, scope))
}
return nil
// Example: print(123)
func registerPrint() {
RegisterFunc("print", func(fnc *NodeFuncCallOp, scope *VariableScope) interface{} {
for _, v := range fnc.Params {
fmt.Print(EvalExpr(v, scope))
}
return nil
})
}
86 changes: 86 additions & 0 deletions builtin_func_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package spiker_test

import (
"log"
"reflect"
"testing"

"github.com/shockerli/spiker"
)

func TestRegisterFunc(t *testing.T) {
type args struct {
name string
fn spiker.Func
code string
}
tests := []args{
{"log", func(fnc *spiker.NodeFuncCallOp, scope *spiker.VariableScope) interface{} {
for _, p := range fnc.Params {
log.Println(spiker.EvalExpr(p, scope))
}
return nil
}, `log(123, "abc")`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
spiker.RegisterFunc(tt.name, tt.fn)
if _, err := spiker.Execute(tt.code); err != nil {
t.Errorf("register func failed, error = %v", err)
}
})
}
}

func TestBuiltin_Export(t *testing.T) {
type args struct {
name string
code string
expect interface{}
}
tests := []args{
{`export-var-string`, `name="jioby";export(name);`, "jioby"},
{`export-var-int`, `age=18;export(age);`, float64(18)},
{`export-list`, `export([1,2,3]);`, spiker.ValueList{float64(1), float64(2), float64(3)}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := spiker.Execute(tt.code)
if err != nil {
t.Errorf("error = %v", err)
return
}

if !reflect.DeepEqual(res, tt.expect) {
t.Errorf("want = %v, got = %v", tt.expect, res)
}
})
}
}

func TestBuiltin_Len(t *testing.T) {
type args struct {
name string
code string
expect interface{}
}
tests := []args{
{`len-var-string`, `name="jioby";len(name);`, 5},
{`len-list`, `len([1,2,3]);`, 3},
{`len-map`, `len([1:11,2:22,3:33]);`, 3},
{`len-float`, `len(12.34);`, -1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := spiker.Execute(tt.code)
if err != nil {
t.Errorf("error = %v", err)
return
}

if !reflect.DeepEqual(res, tt.expect) {
t.Errorf("want = %v, got = %v", tt.expect, res)
}
})
}
}
Loading

0 comments on commit ba7f747

Please sign in to comment.