Procházet zdrojové kódy

Changed ID, starting server operations

luis před 7 roky
rodič
revize
0faefe55d5

+ 1 - 0
browser/vue-flow/src/components/flow/manager.vue

@@ -423,6 +423,7 @@ export default {
       this.$flowService[type](param)
     },
     sendDocumentUpdate (nodeData) {
+      console.log('T:', JSON.stringify(this.nodeData, null, ' '))
       this.$flowService.documentUpdate(this.nodeData, this.$route.params.sessId)
     },
     // HELPERS depending on svg ref

+ 13 - 8
browser/vue-flow/src/utils/utils.js

@@ -11,13 +11,18 @@ module.exports = {
     document.addEventListener('mousemove', drag)
     document.addEventListener('mouseup', drop)
   },
-  guid () {
-    function s4 () {
-      return Math.floor((1 + Math.random()) * 0x10000)
-        .toString(16)
-        .substring(1)
-    }
-    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
-    s4() + '-' + s4() + s4() + s4()
+  guid (n) {
+    var text = ''
+    if (!n) n = 5
+    var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+    const d = new Date()
+
+    text += possible[d.getMilliseconds() % possible.length]
+    text += possible[d.getSeconds()]
+    text += possible[d.getMinutes()]
+    text += possible[d.getDate()]
+    for (var i = 0; i < n; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)) }
+
+    return text
   }
 }

+ 96 - 24
go/src/flow/flow.go

@@ -2,8 +2,12 @@ package flow
 
 import (
 	"bytes"
+	"crypto/rand"
 	"encoding/json"
 	"fmt"
+	"io"
+	"log"
+	"os"
 	"reflect"
 )
 
@@ -24,7 +28,7 @@ type Flow struct {
 
 	consts     []Data
 	data       map[string]Data // Should be named, to fetch later
-	operations []opEntry
+	operations map[string]opEntry
 
 	err   error
 	runID int
@@ -37,10 +41,15 @@ func New() *Flow {
 		// Data
 		consts:     []Data{},
 		data:       map[string]Data{},
-		operations: []opEntry{},
+		operations: map[string]opEntry{},
 	}
 }
 
+// Err returns internal error state
+func (f *Flow) Err() error {
+	return f.err
+}
+
 //SetRegistry use the registry specified
 func (f *Flow) SetRegistry(r *Registry) *Flow {
 	f.registry = r
@@ -48,6 +57,35 @@ func (f *Flow) SetRegistry(r *Registry) *Flow {
 	return f
 }
 
+// DefOp Manual tag an Operation
+func (f *Flow) DefOp(id string, name string, params ...interface{}) Operation {
+	inputs := make([]*operation, len(params))
+	for i, p := range params {
+		switch v := p.(type) {
+		case *operation:
+			inputs[i] = v
+		default:
+			log.Println("WARNING defining const with value", v)
+			inputs[i] = f.Const(v).(*operation)
+		}
+	}
+	// Grab executor here
+	executor, err := f.registry.Get(name)
+	if err != nil {
+		f.err = err
+		return nil
+	}
+	f.operations[id] = opEntry{name, inputs, executor}
+	return opFunc(f, id)
+}
+
+// Res returns a deferred operation result
+// passing the Id
+func (f *Flow) Res(id string) Operation {
+	// Defered operation
+	return opFunc(f, id)
+}
+
 // Op return an function operator
 //  name - a previous registered function
 //  params - the function inputs
@@ -59,6 +97,8 @@ func (f *Flow) Op(name string, params ...interface{}) Operation {
 		case *operation:
 			inputs[i] = v
 		default:
+			// fail here
+			log.Println("WARNING defining const with value", v)
 			inputs[i] = f.Const(v).(*operation)
 		}
 	}
@@ -69,11 +109,19 @@ func (f *Flow) Op(name string, params ...interface{}) Operation {
 		f.err = err
 		return nil
 	}
-	f.operations = append(f.operations, opEntry{name, inputs, executor})
-	refID := len(f.operations) - 1
-	// Initialize opfunc maybe
-	return opFunc(f, refID)
+	// generate ID
 
+	//f.operations = append(f.operations, opEntry{name, inputs, executor})
+	//refID := len(f.operations) - 1
+	for {
+		uuid := puuid()
+		if _, ok := f.operations[uuid]; ok {
+			continue
+		}
+		f.operations[uuid] = opEntry{name, inputs, executor}
+		return opFunc(f, uuid)
+	}
+	// Initialize opfunc maybe
 }
 
 // Const returns a const operation
@@ -118,7 +166,7 @@ func (f *Flow) run(cache map[*operation]Data, op Operation, params ...Data) (Dat
 	var r Data
 	// This is wrong since the only source of func should be on operation
 	if o.kind == "func" {
-		op := f.operations[o.id.(int)]
+		op := f.operations[o.id.(string)]
 		callParam := make([]reflect.Value, len(op.inputs))
 		for i, in := range op.inputs {
 			fr, _ := f.run(cache, in, params...) // ignore error
@@ -133,28 +181,38 @@ func (f *Flow) run(cache map[*operation]Data, op Operation, params ...Data) (Dat
 }
 
 // Analyse every operations
-func (f *Flow) Analyse(params ...Data) string {
-	ret := bytes.NewBuffer(nil)
-	fmt.Fprintf(ret, "Ops analysis:\n")
-
-	for i, op := range f.operations {
-		fmt.Fprintf(ret, "  [%d] %s(", i, op.name)
+func (f *Flow) Analyse(w io.Writer, params ...Data) {
+	if w == nil {
+		w = os.Stdout
+	}
+	fmt.Fprintf(w, "Ops analysis:\n")
+	for k, op := range f.operations {
+		fw := bytes.NewBuffer(nil)
+		//fmt.Fprintf(w, "  [%s] (%v)", k, op.name)
+		fmt.Fprintf(fw, "  [%s] %s(", k, op.name)
 		for j, in := range op.inputs {
 			//ref := in.(Op)
 			if j != 0 {
-				fmt.Fprintf(ret, ", ")
+				fmt.Fprintf(fw, ", ")
 			}
 			ires := in.Process(params...)
-			fmt.Fprintf(ret, "%s[%v](%v)", in.kind, in.id, ires)
+			if f.err != nil {
+				fmt.Fprintf(w, "Operator: %s error#%s\n", op.name, f.err.Error())
+				return
+			}
+			fmt.Fprintf(fw, " %s[%v](%v)", in.kind, in.id, ires)
 		}
-		fmt.Fprintf(ret, ") - ")
+		fmt.Fprintf(fw, ") - ")
 		// Create OpProcessor and execute
 		//
-		opfn := opFunc(f, i)
+		opfn := opFunc(f, k)
 		res := opfn.Process(params...)
-		fmt.Fprintf(ret, "%v\n", res)
+		fmt.Fprintf(fw, "%v\n", res)
+
+		fmt.Fprintf(w, "%s", fw.String())
+
 	}
-	return ret.String()
+
 }
 
 func (f *Flow) String() string {
@@ -170,8 +228,8 @@ func (f *Flow) String() string {
 	}
 
 	fmt.Fprintf(ret, "operations:\n")
-	for i, v := range f.operations {
-		fmt.Fprintf(ret, "  [%d] %s(", i, v.name)
+	for k, v := range f.operations {
+		fmt.Fprintf(ret, "  [%s] %s(", k, v.name)
 		for j, in := range v.inputs {
 			if j != 0 {
 				fmt.Fprintf(ret, ", ")
@@ -191,8 +249,8 @@ func (f *Flow) MarshalJSON() ([]byte, error) {
 		Name  string
 		Input []map[string]interface{}
 	}
-	operations := make([]opMarshal, len(f.operations))
-	for i, o := range f.operations {
+	operations := map[string]opMarshal{}
+	for k, o := range f.operations {
 		refs := []map[string]interface{}{}
 		for _, in := range o.inputs { // Switch type?
 			refs = append(refs, map[string]interface{}{
@@ -200,7 +258,7 @@ func (f *Flow) MarshalJSON() ([]byte, error) {
 				"id":   in.id,
 			})
 		}
-		operations[i] = opMarshal{o.name, refs}
+		operations[k] = opMarshal{o.name, refs}
 	}
 	data["operations"] = operations
 	data["data"] = f.data
@@ -208,3 +266,17 @@ func (f *Flow) MarshalJSON() ([]byte, error) {
 
 	return json.Marshal(data)
 }
+
+func puuid() (uuid string) {
+
+	b := make([]byte, 16)
+	_, err := rand.Read(b)
+	if err != nil {
+		fmt.Println("Error: ", err)
+		return
+	}
+
+	uuid = fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
+
+	return
+}

+ 32 - 81
go/src/flow/flow_test.go

@@ -3,13 +3,11 @@ package flow_test
 import (
 	"bytes"
 	"encoding/json"
-	"fmt"
-	"reflect"
 	"testing"
 
 	"flow"
 
-	vecasm "github.com/gohxs/vec-benchmark/asm"
+	. "flow/internal/tests"
 )
 
 func TestSerialize(t *testing.T) {
@@ -32,7 +30,9 @@ func TestSerialize(t *testing.T) {
 	mul3 := f.Op("vecmul", c2, mul2)      // op:4 - expected 96, 256, 480, 0
 	mul4 := f.Op("vecmul", mul3, f.In(0)) // op:5 - expected 96, 512, 1440,0
 
-	t.Log(f.Analyse([]float32{1, 2, 3, 4}))
+	s := bytes.NewBuffer(nil)
+	f.Analyse(s, []float32{1, 2, 3, 4})
+	t.Log(s)
 	res := mul4.Process([]float32{1, 2, 3, 4})
 
 	t.Log("Res:", res)
@@ -50,7 +50,7 @@ func TestConst(t *testing.T) {
 
 	c := f.Const(1)
 	res := c.Process()
-	checkeq(t, res, 1)
+	CheckEq(t, res, 1)
 }
 func TestOp(t *testing.T) {
 	f := flow.New()
@@ -63,10 +63,10 @@ func TestOp(t *testing.T) {
 		[]float32{1, 2, 3},
 	)
 	res, err := f.Run(add)
-	checkeq(t, err, nil)
+	CheckEq(t, err, nil)
 
 	test := []float32{3, 6, 9}
-	checkeq(t, test, res)
+	CheckEq(t, test, res)
 }
 
 func TestVariable(t *testing.T) {
@@ -74,11 +74,11 @@ func TestVariable(t *testing.T) {
 	v := f.Var("v1", 1)
 
 	res := v.Process()
-	checkeq(t, res, 1)
+	CheckEq(t, res, 1)
 
 	v.Set(2)
 	res = v.Process()
-	checkeq(t, res, 2)
+	CheckEq(t, res, 2)
 }
 
 func TestCache(t *testing.T) {
@@ -88,7 +88,7 @@ func TestCache(t *testing.T) {
 		var res interface{}
 		for i := 1; i < 5; i++ {
 			res = r.Process()
-			checkeq(t, res, i)
+			CheckEq(t, res, i)
 		}
 	}
 	{
@@ -96,11 +96,25 @@ func TestCache(t *testing.T) {
 		inc := f.Op("inc")
 		add := f.Op("add", inc, inc)
 		res = add.Process() // 1+1
-		checkeq(t, res, 2)
+		CheckEq(t, res, 2)
 		res = add.Process() // 2+2
-		checkeq(t, res, 4)
-
+		CheckEq(t, res, 4)
 	}
+}
+func TestReference(t *testing.T) {
+	f := flow.New()
+
+	f.DefOp("1", "vecadd", []float32{1, 2, 3}, []float32{1, 2, 3}) // result 2 4 6
+
+	op := f.Op("vecmul", f.Res("1"), []float32{2, 2, 2}) // 4 8 12
+	CheckErr(t, f.Err())
+
+	CheckDiff(t, nil, op)
+
+	desired := []float32{4, 8, 12}
+
+	res := op.Process()
+	CheckEq(t, res, desired)
 
 }
 
@@ -120,11 +134,11 @@ func BenchmarkComplex(b *testing.B) {
 }
 
 func init() {
-	flow.Register("vecmul", vecmul)
-	flow.Register("vecadd", vecadd)
-	flow.Register("vecdiv", vecdiv)
-	flow.Register("inc", inc)
-	flow.Register("add", add)
+	flow.Register("vecmul", VecMul)
+	flow.Register("vecadd", VecAdd)
+	flow.Register("vecdiv", VecDiv)
+	flow.Register("inc", Inc)
+	flow.Register("add", Add)
 }
 
 func prepareComplex() (*flow.Flow, flow.Operation) {
@@ -147,66 +161,3 @@ func prepareComplex() (*flow.Flow, flow.Operation) {
 
 	return f, div1
 }
-
-// Some funcs
-func vecmul(a, b []float32) []float32 {
-
-	sz := min(len(a), len(b))
-
-	out := make([]float32, sz)
-	vecasm.VecMulf32x8(a, b, out)
-	return out
-}
-func vecadd(a, b []float32) []float32 {
-	sz := min(len(a), len(b))
-	out := make([]float32, sz)
-	for i := 0; i < sz; i++ {
-		out[i] = a[i] + b[i]
-	}
-	return out
-}
-func vecdiv(a, b []float32) []float32 {
-	sz := min(len(a), len(b))
-	out := make([]float32, sz)
-	for i := 0; i < sz; i++ {
-		out[i] = a[i] / b[i]
-	}
-	return out
-}
-
-// ScalarInt
-// Every time this operator is called we increase 1
-func inc() func() int {
-	i := 0
-	return func() int {
-		i++
-		return i
-	}
-}
-func add(a, b int) int {
-	return a + b
-}
-
-// Utils
-func min(p ...int) int {
-	min := p[0]
-	for _, v := range p[1:] {
-		if min < v {
-			min = v
-		}
-	}
-	return min
-}
-func checkerr(t *testing.T, err error) {
-	if err != nil {
-		t.Fatal(err)
-	}
-}
-
-func checkeq(t *testing.T, a, b interface{}) {
-	msg := fmt.Sprintf("Expect %v got %v", a, b)
-	if !reflect.DeepEqual(a, b) {
-		t.Fatalf("\033[31m[FAIL] %s\033[0m", msg)
-	}
-	t.Logf("\033[32m[PASS]\033[m %s", msg)
-}

+ 80 - 0
go/src/flow/internal/tests/utils.go

@@ -0,0 +1,80 @@
+package tests
+
+// Some funcs
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	vecasm "github.com/gohxs/vec-benchmark/asm"
+)
+
+func VecMul(a, b []float32) []float32 {
+
+	sz := Min(len(a), len(b))
+
+	out := make([]float32, sz)
+	vecasm.VecMulf32x8(a, b, out)
+	return out
+}
+func VecAdd(a, b []float32) []float32 {
+	sz := Min(len(a), len(b))
+	out := make([]float32, sz)
+	for i := 0; i < sz; i++ {
+		out[i] = a[i] + b[i]
+	}
+	return out
+}
+func VecDiv(a, b []float32) []float32 {
+	sz := Min(len(a), len(b))
+	out := make([]float32, sz)
+	for i := 0; i < sz; i++ {
+		out[i] = a[i] / b[i]
+	}
+	return out
+}
+
+// ScalarInt
+// Every time this operator is called we increase 1
+func Inc() func() int {
+	i := 0
+	return func() int {
+		i++
+		return i
+	}
+}
+func Add(a, b int) int {
+	return a + b
+}
+
+// Utils
+func Min(p ...int) int {
+	min := p[0]
+	for _, v := range p[1:] {
+		if min < v {
+			min = v
+		}
+	}
+	return min
+}
+func CheckErr(t *testing.T, err error) {
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func CheckEq(t *testing.T, a, b interface{}) {
+	msg := fmt.Sprintf("Expect %v got %v", a, b)
+	if !reflect.DeepEqual(a, b) {
+		t.Fatalf("\033[31m[FAIL] %s\033[0m", msg)
+	}
+	t.Logf("\033[32m[PASS]\033[m %s", msg)
+}
+
+func CheckDiff(t *testing.T, a, b interface{}) {
+	msg := fmt.Sprintf("Expect NOT %v got %v", a, b)
+	if reflect.DeepEqual(a, b) {
+		t.Fatalf("\033[31m[FAIL] %s\033[0m", msg)
+	}
+	t.Logf("\033[32m[PASS]\033[m %s", msg)
+}

+ 29 - 13
go/src/flow/operation.go

@@ -7,6 +7,7 @@ package flow
 
 import (
 	"errors"
+	"fmt"
 	"reflect"
 )
 
@@ -14,7 +15,7 @@ import (
 type OpCtx map[Operation]Data
 
 // NewOpCtx creates a running context
-func NewOpCtx() OpCtx {
+func newOpCtx() OpCtx {
 	return OpCtx{}
 }
 
@@ -25,7 +26,6 @@ func dumbSet(params ...Data) {}
 type Operation interface {
 	Set(inputs ...Data) // Special var method
 	Process(params ...Data) Data
-	ProcessWithCtx(ctx OpCtx, params ...Data) Data
 }
 
 // Run Context actually not OpCTX
@@ -41,11 +41,14 @@ type operation struct {
 
 // Process operation process wrapper
 func (o *operation) Process(params ...Data) Data {
-	return o.ProcessWithCtx(NewOpCtx(), params...)
+	return o.processWithCtx(newOpCtx(), params...)
 }
 
 // Every single one is run with this internally
-func (o *operation) ProcessWithCtx(ctx OpCtx, params ...Data) Data {
+func (o *operation) processWithCtx(ctx OpCtx, params ...Data) Data {
+	if o.flow.err != nil {
+		return nil
+	}
 	if ctx == nil { // No cache/Context
 		return o.process(ctx, params...)
 	}
@@ -71,7 +74,7 @@ func opIn(f *Flow, id int) *operation {
 		set:  dumbSet,
 		process: func(ctx OpCtx, params ...Data) Data {
 			if id >= len(params) || id < 0 {
-				f.err = errors.New("Invalid input")
+				f.err = errors.New("invalid input")
 				return nil
 			}
 			return params[id]
@@ -80,26 +83,39 @@ func opIn(f *Flow, id int) *operation {
 }
 func opConst(f *Flow, id int) *operation {
 	return &operation{
-		flow:    f,
-		id:      id,
-		kind:    "const",
-		set:     dumbSet,
-		process: func(ctx OpCtx, params ...Data) Data { return f.consts[id] },
+		flow: f,
+		id:   id,
+		kind: "const",
+		set:  dumbSet,
+		process: func(ctx OpCtx, params ...Data) Data {
+			ret := f.consts[id]
+			return ret
+		},
 	}
 }
-func opFunc(f *Flow, id int) *operation {
+func opFunc(f *Flow, id string) *operation {
 	return &operation{
 		flow: f,
 		id:   id,
 		kind: "func",
 		set:  dumbSet,
 		process: func(ctx OpCtx, params ...Data) Data {
-			op := f.operations[id]
+			op, ok := f.operations[id]
+			if !ok {
+				f.err = fmt.Errorf("invalid operation %s", id)
+				return nil
+			}
 			callParam := make([]reflect.Value, len(op.inputs))
 			for i, in := range op.inputs {
-				fr := in.ProcessWithCtx(ctx, params...)
+				fr := in.processWithCtx(ctx, params...)
+				if fr == nil {
+					f.err = errors.New("returning nil")
+					return nil
+				}
 				callParam[i] = reflect.ValueOf(fr)
+
 			}
+
 			return reflect.ValueOf(op.executor).Call(callParam)[0].Interface()
 		},
 	}

+ 5 - 2
go/src/flow/registry.go

@@ -1,6 +1,9 @@
 package flow
 
-import "reflect"
+import (
+	"fmt"
+	"reflect"
+)
 
 // Global
 var (
@@ -36,7 +39,7 @@ func (r *Registry) Register(name string, v interface{}) error {
 func (r *Registry) Get(name string, params ...interface{}) (interface{}, error) {
 	v, ok := r.data[name]
 	if !ok {
-		return nil, ErrNotFound
+		return nil, fmt.Errorf("Entry '%s' not found", name)
 	}
 
 	// We already know this is a function

+ 165 - 0
go/src/flowx/main.go

@@ -0,0 +1,165 @@
+package main
+
+import (
+	"encoding/json"
+	"flow"
+	"log"
+
+	"github.com/gohxs/prettylog"
+)
+
+const jsonRaw = `
+{
+ "nodes": [
+  {
+   "id": "38d76987-290f-ea1d-0ba0-3c63c1bde6a0",
+   "label": "MatMul",
+   "src": "StrCat",
+   "x": 447,
+   "y": 290.34375
+  },
+  {
+   "id": "378e389b-27d3-8b66-90fa-664bcab18a38",
+   "label": "Variable",
+   "src": "Variable",
+   "x": 229,
+   "y": 383.34375
+  },
+  {
+   "id": "3e589bf7-f415-7a31-94e4-7ec8fa28b0c1",
+   "label": "Input",
+   "src": "Input",
+   "x": 250,
+   "y": 165.34375
+  },
+  {
+   "id": "97d5a94f-0e43-df2d-e97d-f347dd186682",
+   "label": "Activator",
+   "src": "StrReverse",
+   "x": 637,
+   "y": 332.34375
+  }
+ ],
+ "links": [
+  {
+   "from": "3e589bf7-f415-7a31-94e4-7ec8fa28b0c1",
+   "in": 0,
+   "to": "38d76987-290f-ea1d-0ba0-3c63c1bde6a0"
+  },
+  {
+   "from": "378e389b-27d3-8b66-90fa-664bcab18a38",
+   "in": 1,
+   "to": "38d76987-290f-ea1d-0ba0-3c63c1bde6a0"
+  },
+  {
+   "from": "38d76987-290f-ea1d-0ba0-3c63c1bde6a0",
+   "in": 0,
+   "to": "97d5a94f-0e43-df2d-e97d-f347dd186682"
+  }
+ ]
+}`
+
+// Node that will contain registry src
+type Node struct {
+	ID  string `json:"id"`
+	Src string `json:"src"`
+}
+
+// Link that joins two nodes
+type Link struct {
+	From string `json:"from"`
+	To   string `json:"to"`
+	In   int    `json:"in"`
+}
+
+type FlowDocument struct {
+	Nodes []Node `json:"nodes"`
+	Links []Link `json:"links"`
+}
+
+func main() {
+	prettylog.Global()
+	doc := FlowDocument{[]Node{}, []Link{}}
+	json.Unmarshal([]byte(jsonRaw), &doc)
+
+	log.Println("Doc is:", doc)
+	// Handling flow
+	//
+	reg := flow.NewRegistry()
+	reg.Register("StrCat", StrCat)
+	reg.Register("StrReverse", StrReverse)
+	f := flow.New()
+	f.SetRegistry(reg)
+
+	nodeMap := map[string]Node{}
+	for _, n := range doc.Nodes {
+		nodeMap[n.ID] = n
+	}
+	inputMap := map[string]flow.Operation{}
+
+	ninput := 0
+	for _, n := range doc.Nodes {
+		log.Println("Processing node:", n.ID)
+		// Find link refered as To
+		param := make([]interface{}, 10) // 10 is temporary to test out operations
+		lastParamID := -1
+		for _, l := range doc.Links {
+			if l.To != n.ID {
+				continue
+			}
+			if l.In > lastParamID {
+				lastParamID = l.In
+			}
+			log.Println("Attaching input:", l.In)
+			// Define operators here
+			from := nodeMap[l.From]
+			switch from.Src {
+			case "Input":
+				log.Println("Input is external Input")
+				inOp, ok := inputMap[n.ID]
+				if !ok {
+					inOp = f.In(ninput)
+					inputMap[n.ID] = inOp
+					ninput++
+				}
+				param[l.In] = inOp // By id perhaps
+			case "Variable":
+				log.Println("Input is a variable")
+				param[l.In] = f.Var(from.ID, "Temporary")
+			default:
+				log.Println("Input is another node")
+				param[l.In] = f.Res(from.ID)
+			}
+		}
+		log.Println("Params")
+		param = param[:lastParamID+1]
+		log.Println("Defining operation", n.ID, n.Src)
+		if n.Src == "Input" || n.Src == "Variable" {
+			log.Println("Not a func, moving")
+			continue
+		}
+		if len(param) > 0 {
+			f.DefOp(n.ID, n.Src, param...)
+		} else {
+			f.DefOp(n.ID, n.Src)
+		}
+		log.Println("Operation using", n.Src, "Has", len(param), "Parameters")
+	}
+
+	log.Println("Flow:\n", f)
+	f.Analyse(nil, "my string")
+
+}
+
+func StrCat(a1 string, a2 string) string {
+	return a1 + a2
+}
+func StrReverse(s string) string {
+	n := len(s)
+	runes := make([]rune, n)
+	for _, r := range s {
+		n--
+		runes[n] = r
+	}
+	return string(runes[n:])
+}

+ 18 - 0
test/index.js

@@ -0,0 +1,18 @@
+function makeid(n) {
+  var text = "";
+  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+  const d = new Date()
+
+  text += possible[d.getMilliseconds() % possible.length]
+  text += possible[d.getSeconds()]
+  text += possible[d.getMinutes()]
+  text += possible[d.getDate()]
+ 
+  for (var i = 0; i < n; i++)
+    text += possible.charAt(Math.floor(Math.random() * possible.length));
+
+  return text;
+}
+
+console.log(makeid(5))
+