diff --git a/README.md b/README.md index 3d696a8..1a7e986 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ They allow for the definition of the command line grammar (including type checki values, missing arguments, etc) and a defined action routine called when a subcommand is processed successfully. -A simple command line tool defines a grammar for the commands and subcommands, and their +A simple command line tool defines a grammar for the commands and subcommands, and their options. It then calls the app package Run() method which handles parsing and execution control from then on. diff --git a/app-cli/app/app.go b/app-cli/app/app.go index 4dc06bb..b441cfc 100644 --- a/app-cli/app/app.go +++ b/app-cli/app/app.go @@ -7,7 +7,6 @@ package app import ( "fmt" "os" - "runtime" "strings" "github.com/tucats/gopackages/app-cli/cli" @@ -15,7 +14,6 @@ import ( "github.com/tucats/gopackages/app-cli/ui" "github.com/tucats/gopackages/defs" "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" "github.com/tucats/gopackages/expressions/symbols" ) @@ -135,35 +133,6 @@ func (app *App) Run(grammar []cli.Option, args []string) error { Action: app.Action, } - // Create the platform definition symbols - platformType := data.StructureType( - data.Field{ - Name: "os", - Type: data.StringType, - }, - data.Field{ - Name: "arch", - Type: data.StringType, - }, - data.Field{ - Name: "go", - Type: data.StringType, - }, - data.Field{ - Name: "cpus", - Type: data.IntType, - }, - ) - - platform := data.NewStruct(platformType) - _ = platform.Set("go", runtime.Version()) - _ = platform.Set("os", runtime.GOOS) - _ = platform.Set("arch", runtime.GOARCH) - _ = platform.Set("cpus", runtime.NumCPU()) - platform.SetReadonly(true) - _ = symbols.RootSymbolTable.SetWithAttributes(defs.PlatformVariable, platform, - symbols.SymbolAttribute{Readonly: true}) - if err := SetDefaultLoggers(); err != nil { return err } diff --git a/expressions/builtins/append.go b/expressions/builtins/append.go deleted file mode 100644 index f7f92f1..0000000 --- a/expressions/builtins/append.go +++ /dev/null @@ -1,40 +0,0 @@ -package builtins - -import ( - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// Append implements the builtin append() function, which concatenates all the items -// together as an array. The first argument is flattened into the result, and then each -// additional argument is added to the array as-is. -func Append(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - result := make([]interface{}, 0) - kind := data.InterfaceType - - for i, j := range args { - if array, ok := j.(*data.Array); ok && i == 0 { - if !kind.IsInterface() { - if err := array.Validate(kind); err != nil { - return nil, err - } - } - - result = append(result, array.BaseArray()...) - - if kind.IsInterface() { - kind = array.Type() - } - } else if array, ok := j.([]interface{}); ok && i == 0 { - result = append(result, array...) - } else { - if !kind.IsInterface() && !data.TypeOf(j).IsType(kind) { - return nil, errors.ErrWrongArrayValueType.In("append") - } - result = append(result, j) - } - } - - return data.NewArrayFromArray(kind, result), nil -} diff --git a/expressions/builtins/cast.go b/expressions/builtins/cast.go index 572a4cb..949f059 100644 --- a/expressions/builtins/cast.go +++ b/expressions/builtins/cast.go @@ -1,9 +1,6 @@ package builtins import ( - "math" - "strings" - "github.com/tucats/gopackages/errors" "github.com/tucats/gopackages/expressions/data" "github.com/tucats/gopackages/expressions/symbols" @@ -16,121 +13,23 @@ import ( func Cast(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { // Target t is the last parameter t := data.TypeOf(args[len(args)-1]) - - source := args[0] - if len(args) > 2 { - source = data.NewArrayFromArray(data.InterfaceType, args[:len(args)-1]) - } + source := args[len(args)-1] if t.IsString() { + // If the source is a []byte type, we can just fetch the bytes and do a direct convesion. // If the source is a []int type, we can convert each integer to a rune and add it to a // string builder. Otherwise, just format it as a string value. - if actual, ok := source.(*data.Array); ok && actual != nil && actual.Type().IsType(data.ByteType) { - b := actual.GetBytes() - - return string(b), nil - } else if actual, ok := source.(*data.Array); ok && actual != nil && actual.Type().IsIntegerType() { - r := strings.Builder{} - for i := 0; i < actual.Len(); i++ { - ch, _ := actual.Get(i) - r.WriteRune(rune(data.Int(ch) & math.MaxInt32)) - } + return data.FormatUnquoted(source), nil - return r.String(), nil - } else { - return data.FormatUnquoted(source), nil - } } - switch actual := source.(type) { - // Conversion of one array type to another - case *data.Array: - if t.IsType(actual.Type()) { - return actual, nil - } - - if t.IsString() && (actual.Type().IsIntegerType() || actual.Type().IsInterface()) { - r := strings.Builder{} - - for i := 0; i < actual.Len(); i++ { - ch, _ := actual.Get(i) - r.WriteRune(data.Int32(ch) & math.MaxInt32) - } - - return r.String(), nil - } - - elementKind := *t.BaseType() - r := data.NewArray(t.BaseType(), actual.Len()) - - for i := 0; i < actual.Len(); i++ { - v, _ := actual.Get(i) - - switch elementKind.Kind() { - case data.BoolKind: - _ = r.Set(i, data.Bool(v)) - - case data.ByteKind: - _ = r.Set(i, data.Byte(v)) - - case data.Int32Kind: - _ = r.Set(i, data.Int32(v)) - - case data.IntKind: - _ = r.Set(i, data.Int(v)) - - case data.Int64Kind: - _ = r.Set(i, data.Int64(v)) - - case data.Float32Kind: - _ = r.Set(i, data.Float32(v)) - - case data.Float64Kind: - _ = r.Set(i, data.Float64(v)) - - case data.StringKind: - _ = r.Set(i, data.String(v)) - - default: - return nil, errors.ErrInvalidType.Context(data.TypeOf(v).String()) - } - } - - return r, nil + switch source.(type) { case string: - if t.IsType(data.ArrayType(data.IntType)) { - r := data.NewArray(data.IntType, 0) - - for _, rune := range actual { - r.Append(int(rune)) - } - - return r, nil - } - - if t.IsType(data.ArrayType(data.ByteType)) { - r := data.NewArray(data.ByteType, 0) - - for i := 0; i < len(actual); i++ { - r.Append(actual[i]) - } - - return r, nil - } - return data.Coerce(source, data.InstanceOfType(t)), nil default: - if t.IsArray() { - r := data.NewArray(t.BaseType(), 1) - value := data.Coerce(source, data.InstanceOfType(t.BaseType())) - _ = r.Set(0, value) - - return r, nil - } - v := data.Coerce(source, data.InstanceOfType(t)) if v != nil { return v, nil diff --git a/expressions/builtins/close.go b/expressions/builtins/close.go deleted file mode 100644 index 168455f..0000000 --- a/expressions/builtins/close.go +++ /dev/null @@ -1,29 +0,0 @@ -package builtins - -import ( - "github.com/tucats/gopackages/defs" - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// Close implements the generic close() function which can be used -// to close a channel or a file, or a database connection. Maybe later, -// other items as well. -func Close(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - switch arg := args[0].(type) { - case *data.Channel: - return arg.Close(), nil - - case *data.Struct: - // A struct means it's really a type. Store the actual object - // as the "__this" value, and the try to call the "Close" - // method associated with the type. - s.SetAlways(defs.ThisVariable, arg) - - return callTypeMethod(arg.TypeString(), "Close", s, args) - - default: - return nil, errors.ErrInvalidType.In("close") - } -} diff --git a/expressions/builtins/copy.go b/expressions/builtins/copy.go index 8282edd..ed2faa5 100644 --- a/expressions/builtins/copy.go +++ b/expressions/builtins/copy.go @@ -1,7 +1,5 @@ package builtins -import "github.com/tucats/gopackages/expressions/data" - // MaxDeepCopyDepth specifies the maximum depth that a recursive // copy will go before failing. Setting this too small will // prevent complex structures from copying correctly. Setting it @@ -51,41 +49,6 @@ func DeepCopy(source interface{}, depth int) interface{} { return r - case *data.Struct: - return v.Copy() - - case *data.Array: - r := data.NewArray(v.Type(), v.Len()) - - for i := 0; i < v.Len(); i++ { - vv, _ := v.Get(i) - vv = DeepCopy(vv, depth-1) - _ = v.Set(i, vv) - } - - return r - - case *data.Map: - r := data.NewMap(v.KeyType(), v.ElementType()) - - for _, k := range v.Keys() { - d, _, _ := v.Get(k) - _, _ = r.Set(k, DeepCopy(d, depth-1)) - } - - return r - - case *data.Package: - r := data.Package{} - keys := v.Keys() - - for _, k := range keys { - d, _ := v.Get(k) - r.Set(k, DeepCopy(d, depth-1)) - } - - return &r - default: return v } diff --git a/expressions/builtins/delete.go b/expressions/builtins/delete.go index 68c9969..660eb9b 100644 --- a/expressions/builtins/delete.go +++ b/expressions/builtins/delete.go @@ -2,7 +2,6 @@ package builtins import ( "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" "github.com/tucats/gopackages/expressions/symbols" ) @@ -29,17 +28,6 @@ func Delete(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { return nil, s.Delete(v, false) - case *data.Map: - _, err := v.Delete(args[1]) - - return v, err - - case *data.Array: - i := data.Int(args[1]) - err := v.Delete(i) - - return v, err - default: return nil, errors.ErrInvalidType.In("delete") } diff --git a/expressions/builtins/functions.go b/expressions/builtins/functions.go index add71e3..a6b7e3c 100644 --- a/expressions/builtins/functions.go +++ b/expressions/builtins/functions.go @@ -6,7 +6,6 @@ import ( "reflect" "sort" "strings" - "sync" "github.com/tucats/gopackages/app-cli/ui" "github.com/tucats/gopackages/defs" @@ -60,44 +59,6 @@ const Any = math.MaxInt32 // ErrReturn:true flag to each function definition. var FunctionDictionary = map[string]FunctionDefinition{ "$new": {Min: 1, Max: 1, F: New}, - "append": {Min: 2, Max: Any, F: Append, D: &data.Declaration{ - Name: "append", - Parameters: []data.Parameter{ - { - Name: "array", - Type: data.ArrayType(data.InterfaceType), - }, - { - Name: "item", - Type: data.InterfaceType, - }, - }, - Variadic: true, - Returns: []*data.Type{data.ArrayType(data.InterfaceType)}, - }}, - "close": {Min: 1, Max: 1, F: Close, D: &data.Declaration{ - Name: "close", - Parameters: []data.Parameter{ - { - Name: "any", - Type: data.InterfaceType, - }, - }, - }}, - "delete": {Min: 1, Max: 2, F: Delete, FullScope: true, D: &data.Declaration{ - Name: "delete", - Parameters: []data.Parameter{ - { - Name: "item", - Type: data.InterfaceType, - }, - { - Name: "index", - Type: data.InterfaceType, - }, - }, - ArgCount: data.Range{1, 2}, - }}, "index": {Min: 2, Max: 2, F: Index, D: &data.Declaration{ Name: "index", Parameters: []data.Parameter{ @@ -146,9 +107,6 @@ var FunctionDictionary = map[string]FunctionDefinition{ }, Returns: []*data.Type{data.IntType}, }}, - "sync.__empty": {Min: 0, Max: 0, F: stubFunction}, // Package auto imports, but has no functions - "sync.WaitGroup": {V: sync.WaitGroup{}}, - "sync.Mutex": {V: sync.Mutex{}}, } // AddBuiltins adds or overrides the default function library in the symbol map. @@ -176,40 +134,7 @@ func AddBuiltins(symbolTable *symbols.SymbolTable) { n = n[dot+1:] } - if d.Pkg == "" { - _ = symbolTable.SetWithAttributes(n, d.F, symbols.SymbolAttribute{Readonly: true}) - } else { - // Does package already exist? If not, make it. The package - // is just a struct containing where each member is a function - // definition. - pkg := data.NewPackage(d.Pkg) - - if p, found := symbolTable.Root().Get(d.Pkg); found { - if pp, ok := p.(*data.Package); ok { - pkg = pp - } - } else { - ui.Log(ui.CompilerLogger, " AddBuiltins creating new package %s", d.Pkg) - } - - root := symbolTable.Root() - // Is this a value bound to the package, or a function? - if d.V != nil { - pkg.Set(n, d.V) - - _ = root.SetWithAttributes(d.Pkg, pkg, symbols.SymbolAttribute{Readonly: true}) - - ui.Log(ui.CompilerLogger, " adding value %s to %s", n, d.Pkg) - } else { - pkg.Set(n, d.F) - pkg.Set(data.TypeMDKey, data.PackageType(d.Pkg)) - pkg.Set(data.ReadonlyMDKey, true) - - _ = root.SetWithAttributes(d.Pkg, pkg, symbols.SymbolAttribute{Readonly: true}) - - ui.Log(ui.CompilerLogger, " adding builtin %s to %s", n, d.Pkg) - } - } + _ = symbolTable.SetWithAttributes(n, d.F, symbols.SymbolAttribute{Readonly: true}) } } @@ -247,28 +172,6 @@ func FindName(f func(*symbols.SymbolTable, []interface{}) (interface{}, error)) } func CallBuiltin(s *symbols.SymbolTable, name string, args ...interface{}) (interface{}, error) { - // See if it's a runtime package item (as opposed to a builtin) - if dot := strings.Index(name, "."); dot > 0 { - packageName := name[:dot] - functionName := name[dot+1:] - - if v, ok := s.Get(packageName); ok { - if pkg, ok := v.(*data.Package); ok { - if v, ok := pkg.Get(functionName); ok { - if fd, ok := v.(data.Function); ok { - if fn, ok := fd.Value.(func(*symbols.SymbolTable, []interface{}) (interface{}, error)); ok { - v, e := fn(s, args) - - return v, e - } - } - } - } - } - } - - // Nope, see if it's a builtin - var fdef = FunctionDefinition{} found := false @@ -305,13 +208,6 @@ func AddFunction(s *symbols.SymbolTable, fd FunctionDefinition) error { FunctionDictionary[fd.Name] = fd - // Has the package already been constructed? If so, we need to add this to the package. - if pkg, ok := s.Get(fd.Pkg); ok { - if p, ok := pkg.(*data.Package); ok { - p.Set(fd.Name, fd.F) - } - } - return nil } diff --git a/expressions/builtins/index.go b/expressions/builtins/index.go index 7edce81..a98112e 100644 --- a/expressions/builtins/index.go +++ b/expressions/builtins/index.go @@ -16,16 +16,6 @@ func Index(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error } switch arg := args[0].(type) { - case *data.Array: - for i := 0; i < arg.Len(); i++ { - vv, _ := arg.Get(i) - if reflect.DeepEqual(vv, args[1]) { - return i, nil - } - } - - return -1, nil - case []interface{}: for n, v := range arg { if reflect.DeepEqual(v, args[1]) { @@ -35,11 +25,6 @@ func Index(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error return -1, nil - case *data.Map: - _, found, err := arg.Get(args[1]) - - return found, err - default: v := data.String(args[0]) p := data.String(args[1]) diff --git a/expressions/builtins/length.go b/expressions/builtins/length.go index 72648ca..20b9f02 100644 --- a/expressions/builtins/length.go +++ b/expressions/builtins/length.go @@ -1,8 +1,6 @@ package builtins import ( - "math" - "github.com/tucats/gopackages/defs" "github.com/tucats/gopackages/errors" "github.com/tucats/gopackages/expressions/data" @@ -16,27 +14,9 @@ func Length(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { } switch arg := args[0].(type) { - // For a channel, it's length either zero if it's drained, or bottomless - case *data.Channel: - size := int(math.MaxInt32) - if arg.IsEmpty() { - size = 0 - } - - return size, nil - - case *data.Array: - return arg.Len(), nil - case error: return len(arg.Error()), nil - case *data.Map: - return len(arg.Keys()), nil - - case *data.Package: - return nil, errors.ErrInvalidType.Context(data.TypeOf(arg).String()) - case nil: return 0, nil diff --git a/expressions/builtins/make.go b/expressions/builtins/make.go index c89649a..27c9131 100644 --- a/expressions/builtins/make.go +++ b/expressions/builtins/make.go @@ -14,8 +14,6 @@ func Make(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { // if it's an Ego type, get the model for the type. if v, ok := kind.(*data.Type); ok { kind = data.InstanceOfType(v) - } else if egoArray, ok := kind.(*data.Array); ok { - return egoArray.Make(size), nil } array := make([]interface{}, size) @@ -28,13 +26,7 @@ func Make(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { // If the model is a type we know about, let's go ahead and populate the array // with specific values. - switch v := kind.(type) { - case *data.Channel: - return data.NewChannel(size), nil - - case *data.Array: - return v.Make(size), nil - + switch kind.(type) { case []int, int: for i := range array { array[i] = 0 diff --git a/expressions/builtins/method.go b/expressions/builtins/method.go deleted file mode 100644 index faa6c7f..0000000 --- a/expressions/builtins/method.go +++ /dev/null @@ -1,61 +0,0 @@ -package builtins - -import ( - "strings" - - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// callTypeMethod locates a function from the Ego metadata world and -// calls it. The typeName should be in "package.Type" format. -func callTypeMethod(typeName, methodName string, s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - dot := strings.Index(typeName, ".") - if dot < 1 { - return nil, errors.ErrInvalidTypeName.Context(typeName) - } - - packageName := typeName[:dot] - typeName = typeName[dot+1:] - - // 1. Get the package interface - if pkgV, found := s.Get(packageName); found { - // 2. Unwrap the interface to get the package - if pkg, ok := pkgV.(*data.Package); ok { - // 3. Get the type interface from the package - if ftv, found := pkg.Get(typeName); found { - // 4. Unwrap the type interface to get the type - if ft, ok := ftv.(*data.Type); ok { - // 5. Locate the type method as a function interface - if finfo := ft.Get(methodName); finfo != nil { - // 6. Unwrap the function interface to create a function object - if fd, ok := finfo.(data.Function); ok { - // 7. Get the entrypoint interface from the function object - fn := fd.Value - // 8. Unwrap the entrypoint interface to get the entypoint - if f, ok := fn.(func(s *symbols.SymbolTable, args []interface{}) (interface{}, error)); ok { - // 9. Use the entrypoint to call the method - return f(s, []interface{}{}) - } - - return nil, errors.ErrInvalidFunctionName.Context(methodName) - } - - return nil, errors.ErrInvalidFunctionName.Context(methodName) - } - - return nil, errors.ErrInvalidFunctionName.Context(methodName) - } - - return nil, errors.ErrInvalidTypeName.Context(typeName) - } - - return nil, errors.ErrInvalidTypeName.Context(typeName) - } - - return nil, errors.ErrInvalidPackageName.Context(packageName) - } - - return nil, errors.ErrInvalidPackageName.Context(packageName) -} diff --git a/expressions/builtins/new.go b/expressions/builtins/new.go index 232dde4..b21e4f8 100644 --- a/expressions/builtins/new.go +++ b/expressions/builtins/new.go @@ -5,7 +5,6 @@ import ( "strings" "sync" - "github.com/tucats/gopackages/defs" "github.com/tucats/gopackages/errors" "github.com/tucats/gopackages/expressions/data" "github.com/tucats/gopackages/expressions/symbols" @@ -93,33 +92,6 @@ func New(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { return data.InstanceOfType(data.WaitGroupType), nil } - // If it's a Mutex, make a new one. We hae to do this as a swtich on the type, since a - // cast attempt will yield a warning on invalid mutex copy operation. - switch args[0].(type) { - case *sync.Mutex: - return data.InstanceOfType(data.MutexType), nil - } - - // If it's a channel, just return the value - if typeValue, ok := args[0].(*data.Channel); ok { - return typeValue, nil - } - - // Some native complex types work using the data package deep - // copy operation on that type. - - switch actual := args[0].(type) { - case *data.Struct: - return data.DeepCopy(actual), nil - - case *data.Array: - return data.DeepCopy(actual), nil - - case *data.Map: - return data.DeepCopy(actual), nil - } - - // Otherwise, make a deep copy of the item ourselves. r := DeepCopy(args[0], MaxDeepCopyDepth) // If there was a user-defined type in the source, make the clone point back to it @@ -136,33 +108,6 @@ func New(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { // No action for this group case byte, int32, int, int64, string, float32, float64: - case *data.Package: - dropList := []string{} - - // Organize the new item by removing things that are handled via the parent. - keys := v.Keys() - for _, k := range keys { - vv, _ := v.Get(k) - // IF it's an internal function, we don't want to copy it; it can be found via the - // __parent link to the type - vx := reflect.ValueOf(vv) - - if vx.Kind() == reflect.Ptr { - ts := vx.String() - if ts == defs.ByteCodeReflectionTypeString { - dropList = append(dropList, k) - } - } else { - if vx.Kind() == reflect.Func { - dropList = append(dropList, k) - } - } - } - - for _, name := range dropList { - v.Delete(name) - } - default: return nil, errors.ErrInvalidType.In("new").Context(v) } diff --git a/expressions/builtins/utility_test.go b/expressions/builtins/utility_test.go index 493b200..9801383 100644 --- a/expressions/builtins/utility_test.go +++ b/expressions/builtins/utility_test.go @@ -3,8 +3,6 @@ package builtins import ( "reflect" "testing" - - "github.com/tucats/gopackages/expressions/data" ) func TestFunctionLen(t *testing.T) { @@ -34,21 +32,6 @@ func TestFunctionLen(t *testing.T) { want: 0, wantErr: true, }, - { - name: "array length", - args: args{ - []interface{}{ - data.NewArrayFromArray( - data.InterfaceType, - []interface{}{ - true, - 3.14, - "Tom", - }), - }, - }, - want: 3, - }, } for _, tt := range tests { @@ -83,24 +66,6 @@ func TestLength(t *testing.T) { args: []interface{}{"\u2318foo\u2318"}, want: 9, }, - { - name: "simple array", - args: []interface{}{ - data.NewArrayFromArray(data.IntType, []interface{}{1, 2, 3, 4}), - }, - want: 4, - }, - { - name: "simple map", - args: []interface{}{ - data.NewMapFromMap( - map[string]interface{}{ - "name": "Bob", - "age": 35, - }), - }, - want: 2, - }, { name: "int converted to string", args: []interface{}{"123456"}, diff --git a/expressions/bytecode/compare.go b/expressions/bytecode/compare.go index aae81fc..bf55761 100644 --- a/expressions/bytecode/compare.go +++ b/expressions/bytecode/compare.go @@ -57,24 +57,6 @@ func equalByteCode(c *Context, i interface{}) error { case *errors.Error: result = actual.Equal(v2) - case *data.Struct: - str, ok := v2.(*data.Struct) - if ok { - result = reflect.DeepEqual(actual, str) - } else { - result = false - } - - case *data.Map: - result = reflect.DeepEqual(v1, v2) - - case *data.Array: - if array, ok := v2.(*data.Array); ok { - result = actual.DeepEqual(array) - } else { - result = false - } - default: if c.typeStrictness > 0 { v1, v2 = data.Normalize(v1, v2) @@ -160,15 +142,6 @@ func notEqualByteCode(c *Context, i interface{}) error { case error: result = !reflect.DeepEqual(v1, v2) - case data.Map: - result = !reflect.DeepEqual(v1, v2) - - case data.Array: - result = !reflect.DeepEqual(v1, v2) - - case data.Struct: - result = !reflect.DeepEqual(v1, v2) - default: if c.typeStrictness > 0 { v1, v2 = data.Normalize(v1, v2) @@ -241,36 +214,31 @@ func greaterThanByteCode(c *Context, i interface{}) error { var result bool - switch v1.(type) { - case *data.Map, *data.Struct, *data.Package, *data.Array: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - - default: - if c.typeStrictness > 0 { - v1, v2 = data.Normalize(v1, v2) - } else { - if !data.TypeOf(v1).IsType(data.TypeOf(v2)) { - return c.error(errors.ErrTypeMismatch). - Context(data.TypeOf(v2).String() + ", " + data.TypeOf(v1).String()) - } + if c.typeStrictness > 0 { + v1, v2 = data.Normalize(v1, v2) + } else { + if !data.TypeOf(v1).IsType(data.TypeOf(v2)) { + return c.error(errors.ErrTypeMismatch). + Context(data.TypeOf(v2).String() + ", " + data.TypeOf(v1).String()) } + } - switch v1.(type) { - case byte, int32, int, int64: - result = data.Int64(v1) > data.Int64(v2) + switch v1.(type) { + case byte, int32, int, int64: + result = data.Int64(v1) > data.Int64(v2) - case float32: - result = v1.(float32) > v2.(float32) + case float32: + result = v1.(float32) > v2.(float32) - case float64: - result = v1.(float64) > v2.(float64) + case float64: + result = v1.(float64) > v2.(float64) - case string: - result = v1.(string) > v2.(string) + case string: + result = v1.(string) > v2.(string) + + default: + return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - default: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - } } _ = c.push(result) @@ -316,36 +284,30 @@ func greaterThanOrEqualByteCode(c *Context, i interface{}) error { var result bool - switch v1.(type) { - case *data.Map, *data.Struct, *data.Package, *data.Array: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - - default: - if c.typeStrictness > 0 { - v1, v2 = data.Normalize(v1, v2) - } else { - if !data.TypeOf(v1).IsType(data.TypeOf(v2)) { - return c.error(errors.ErrTypeMismatch). - Context(data.TypeOf(v2).String() + ", " + data.TypeOf(v1).String()) - } + if c.typeStrictness > 0 { + v1, v2 = data.Normalize(v1, v2) + } else { + if !data.TypeOf(v1).IsType(data.TypeOf(v2)) { + return c.error(errors.ErrTypeMismatch). + Context(data.TypeOf(v2).String() + ", " + data.TypeOf(v1).String()) } + } - switch v1.(type) { - case byte, int32, int, int64: - result = data.Int64(v1) >= data.Int64(v2) + switch v1.(type) { + case byte, int32, int, int64: + result = data.Int64(v1) >= data.Int64(v2) - case float32: - result = v1.(float32) >= v2.(float32) + case float32: + result = v1.(float32) >= v2.(float32) - case float64: - result = v1.(float64) >= v2.(float64) + case float64: + result = v1.(float64) >= v2.(float64) - case string: - result = v1.(string) >= v2.(string) + case string: + result = v1.(string) >= v2.(string) - default: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - } + default: + return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) } _ = c.push(result) @@ -391,36 +353,30 @@ func lessThanByteCode(c *Context, i interface{}) error { // Nope, going to have to do type-sensitive compares. var result bool - switch v1.(type) { - case *data.Map, *data.Struct, *data.Package, *data.Array: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - - default: - if c.typeStrictness > 0 { - v1, v2 = data.Normalize(v1, v2) - } else { - if !data.TypeOf(v1).IsType(data.TypeOf(v2)) { - return c.error(errors.ErrTypeMismatch). - Context(data.TypeOf(v2).String() + ", " + data.TypeOf(v1).String()) - } + if c.typeStrictness > 0 { + v1, v2 = data.Normalize(v1, v2) + } else { + if !data.TypeOf(v1).IsType(data.TypeOf(v2)) { + return c.error(errors.ErrTypeMismatch). + Context(data.TypeOf(v2).String() + ", " + data.TypeOf(v1).String()) } + } - switch v1.(type) { - case byte, int32, int, int64: - result = data.Int64(v1) < data.Int64(v2) + switch v1.(type) { + case byte, int32, int, int64: + result = data.Int64(v1) < data.Int64(v2) - case float32: - result = v1.(float32) < v2.(float32) + case float32: + result = v1.(float32) < v2.(float32) - case float64: - result = v1.(float64) < v2.(float64) + case float64: + result = v1.(float64) < v2.(float64) - case string: - result = v1.(string) < v2.(string) + case string: + result = v1.(string) < v2.(string) - default: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - } + default: + return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) } _ = c.push(result) @@ -465,36 +421,30 @@ func lessThanOrEqualByteCode(c *Context, i interface{}) error { var result bool - switch v1.(type) { - case *data.Map, *data.Struct, *data.Package, *data.Array: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - - default: - if c.typeStrictness > 0 { - v1, v2 = data.Normalize(v1, v2) - } else { - if !data.TypeOf(v1).IsType(data.TypeOf(v2)) { - return c.error(errors.ErrTypeMismatch). - Context(data.TypeOf(v2).String() + ", " + data.TypeOf(v1).String()) - } + if c.typeStrictness > 0 { + v1, v2 = data.Normalize(v1, v2) + } else { + if !data.TypeOf(v1).IsType(data.TypeOf(v2)) { + return c.error(errors.ErrTypeMismatch). + Context(data.TypeOf(v2).String() + ", " + data.TypeOf(v1).String()) } + } - switch v1.(type) { - case byte, int32, int, int64: - result = data.Int64(v1) <= data.Int64(v2) + switch v1.(type) { + case byte, int32, int, int64: + result = data.Int64(v1) <= data.Int64(v2) - case float32: - result = v1.(float32) <= v2.(float32) + case float32: + result = v1.(float32) <= v2.(float32) - case float64: - result = v1.(float64) <= v2.(float64) + case float64: + result = v1.(float64) <= v2.(float64) - case string: - result = v1.(string) <= v2.(string) + case string: + result = v1.(string) <= v2.(string) - default: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) - } + default: + return c.error(errors.ErrInvalidType).Context(data.TypeOf(v1).String()) } _ = c.push(result) diff --git a/expressions/bytecode/compare_test.go b/expressions/bytecode/compare_test.go index 03180a2..1e005c8 100644 --- a/expressions/bytecode/compare_test.go +++ b/expressions/bytecode/compare_test.go @@ -2,8 +2,6 @@ package bytecode import ( "testing" - - "github.com/tucats/gopackages/expressions/data" ) func TestComparisons(t *testing.T) { @@ -94,27 +92,6 @@ func TestComparisons(t *testing.T) { v1: 42, v2: "42", r: true, f: equalByteCode, i: nil, err: false, }, - { - name: "array equality", - v1: data.NewArrayFromArray(data.IntType, []interface{}{5, 2, 6}), - v2: data.NewArrayFromArray(data.IntType, []interface{}{5, 2, 6}), - r: true, - f: equalByteCode, i: nil, err: false, - }, - { - name: "array inequality due to type", - v1: data.NewArrayFromArray(data.IntType, []interface{}{5, 2, 6}), - v2: data.NewArrayFromArray(data.Float64Type, []interface{}{5, 2, 6}), - r: false, - f: equalByteCode, i: nil, err: false, - }, - { - name: "array inequality due to values", - v1: data.NewArrayFromArray(data.IntType, []interface{}{5, 2, 6}), - v2: data.NewArrayFromArray(data.IntType, []interface{}{5, 6, 2}), - r: false, - f: equalByteCode, i: nil, err: false, - }, } for _, tt := range tests { diff --git a/expressions/bytecode/context.go b/expressions/bytecode/context.go index c7e9a5c..79454dd 100644 --- a/expressions/bytecode/context.go +++ b/expressions/bytecode/context.go @@ -47,9 +47,7 @@ type Context struct { symbols *symbols.SymbolTable tokenizer *tokenizer.Tokenizer stack []interface{} - rangeStack []*rangeDefinition thisStack []this - packageStack []packageDef output *strings.Builder lastStruct interface{} result interface{} @@ -124,8 +122,6 @@ func NewContext(s *symbols.SymbolTable, b *ByteCode) *Context { thisStack: nil, throwUncheckedErrors: settings.GetBool(defs.ThrowUncheckedErrorsSetting), fullStackTrace: settings.GetBool(defs.FullStackTraceSetting), - packageStack: make([]packageDef, 0), - rangeStack: make([]*rangeDefinition, 0), tracing: false, extensions: extensions, } diff --git a/expressions/bytecode/context_test.go b/expressions/bytecode/context_test.go deleted file mode 100644 index 40086ab..0000000 --- a/expressions/bytecode/context_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package bytecode - -import ( - "reflect" - "testing" - - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -func TestNewContext(t *testing.T) { - s := symbols.NewSymbolTable("context test") - b := New("context test") - - s.SetAlways("foo", 1) - - b.Emit(Load, "foo") - b.Emit(Push, 2) - b.Emit(Add) - b.Emit(Return, 1) - - // Create a new context to test and validate the initial state - c := NewContext(s, b) - - if c.symbols != s { - t.Error("Symbol table not set") - } - - if c.bc != b { - t.Error("bytecode not set") - } - - if len(c.stack) != initialStackSize { - t.Error("stack not sized correctly") - } - - if c.stackPointer != 0 { - t.Error("stack not empty before run") - } - - e := c.setConstant("xyzzy", "frobozz") - if e != nil { - t.Errorf("Unexpected constant set error: %v", e) - } - - if !c.isConstant("xyzzy") { - t.Error("symbol not seen as constant") - } - - if c.isConstant("zork") { - t.Error("unknown symbol was constant") - } - - r, found := c.get("xyzzy") - if !found { - t.Error("Failed to find previously created constant") - } - - // Note that the value of a constant is an immutable object. So - // unwrap it for the comparison. - if !reflect.DeepEqual(data.UnwrapConstant(r), "frobozz") { - t.Errorf("Retrieval of constant has wrong value: %#v", r) - } - // Change some of the attributes of the context and validate - if !c.fullSymbolScope { - t.Error("failed to initialize full symbol scope") - } - - c.SetFullSymbolScope(false) - - if c.fullSymbolScope { - t.Error("failed to clear full symbol scope") - } - - c.SetDebug(true) - - if !c.debugging { - t.Error("Failed to set debugging flag") - } - - c.SetDebug(false) - - if c.debugging { - t.Error("Failed to clear debugging flag") - } - - c.EnableConsoleOutput(false) - - if c.output == nil { - t.Error("Failed to set non-empty console output") - } - - c.output.WriteString("foobar") - - o := c.GetOutput() - if o != "foobar" { - t.Errorf("Incorrect captured console text: %v", o) - } - - c.EnableConsoleOutput(true) - - if c.output != nil { - t.Error("Failed to clear console output state") - } - - // Now run the short segment of bytecode, and see what the ending - // context state looks like. - e = c.Run() - if !errors.Nil(e) && e.Error() != errors.ErrStop.Error() { - t.Errorf("Failed to run bytecode: %v", e) - } - - _, e = c.Pop() - - if errors.Nil(e) { - t.Errorf("Expected stack underflow not found") - } - - r = c.Result() - if !reflect.DeepEqual(r, 3) { - t.Errorf("Wrong final result: %v", r) - } - - if c.stackPointer != 0 { - t.Error("stack not empty after run") - } - - if c.GetLine() != 100 { - t.Errorf("Incorrect line number: %v", c.GetLine()) - } -} diff --git a/expressions/bytecode/create.go b/expressions/bytecode/create.go deleted file mode 100644 index 8d0eb90..0000000 --- a/expressions/bytecode/create.go +++ /dev/null @@ -1,349 +0,0 @@ -package bytecode - -import ( - "reflect" - "strings" - - "github.com/tucats/gopackages/app-cli/ui" - "github.com/tucats/gopackages/defs" - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" -) - -// makeArrayByteCode implements the MakeArray opcode -// -// This is used when a make() pseudo-function is called, -// or the user creates a typed array constant like []int{}. -// -// Inputs: -// -// operand - Count of array items to create -// stack+0 - the array count -// stack+n - each array value in reverse order -// -// If the operand is equal to 2, then the stack has an -// initial value that is stored in each element of the -// resulting array. This is followed by the size of the -// array as an integer. -// -// If the operand is equal to 1, then the array is assumed -// to have no type (interface{} elements) and the only value -// on the stack is the size/ -// -// The function allocates a new EgoArray of the given size -// and type. If the operand was 1, then the values of each -// element of the array are set to the initial value. -func makeArrayByteCode(c *Context, i interface{}) error { - var baseType *data.Type - - count := data.Int(i) - - if value, err := c.Pop(); err == nil { - if isStackMarker(value) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - baseType = data.TypeOf(value) - } - - isInt := baseType.IsIntegerType() - isFloat := baseType.IsFloatType() - - result := data.NewArray(baseType, count) - - for i := 0; i < count; i++ { - if value, err := c.Pop(); err == nil { - if isStackMarker(value) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - valueType := data.TypeOf(value) - - // If we are initializing any integer or float array, we can coerce - // value from another integer type if we are in relaxed or dynamic - // typing. - if c.typeStrictness < 2 { - if isInt && valueType.IsIntegerType() { - value = baseType.Coerce(value) - } else if isFloat && (valueType.IsIntegerType() || valueType.IsFloatType()) { - value = baseType.Coerce(value) - } else { - if !valueType.IsType(baseType) { - return c.error(errors.ErrWrongArrayValueType).Context(valueType.String()) - } - } - } - - err = result.Set(count-i-1, value) - if err != nil { - return err - } - } - } - - return c.push(result) -} - -// arrayByteCode implements the Array opcode -// -// This is used to create an anonymous array constant -// value, such as [true, "fred"] in the Ego language. -// If static typing is enabled, it requires that the -// elements of the array all be the same type. -// -// Inputs: -// -// operand - indicates size or size and type -// -// stack+0 - first array element -// stack+1 - second array element -// stack+n = nth array element -// -// If the operand is an []interface{} array, it contains -// the count as element zero, and the type code as element -// one. If the operand is just a single value, it is the -// count value, and the type is assumed to be interface{} -// -// This must be followed by 'count' items on the stack, which -// are loaded into the array. The resulting array is validated -// if static types are enabled. The resulting array is then -// pushed back on the stack. -func arrayByteCode(c *Context, i interface{}) error { - var arrayType reflect.Type - - var count int - - var kind *data.Type - - if args, ok := i.([]interface{}); ok { - count = data.Int(args[0]) - kind = data.TypeOf(args[1]) - } else { - count = data.Int(i) - kind = data.ArrayType(data.InterfaceType) - } - - result := data.NewArray(kind.BaseType(), count) - - for index := 0; index < count; index++ { - value, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(value) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - // If we are in static mode, array must be homogeneous unless - // we are making an array of interfaces. - if c.typeStrictness == 0 && !kind.IsType(data.ArrayType(data.InterfaceType)) { - if index == 0 { - arrayType = reflect.TypeOf(value) - _ = result.SetType(data.TypeOf(value)) - } else { - if arrayType != reflect.TypeOf(value) { - return c.error(errors.ErrInvalidType).Context(data.TypeOf(value).String()) - } - } - } - - // All good, load it into the array after making an attempt at a coercion. - value = kind.BaseType().Coerce(value) - - err = result.Set((count-index)-1, value) - if err != nil { - return err - } - } - - _ = c.push(result) - - return nil -} - -// structByteCode implements the Struct opcode -// -// This is used to create an Ego "struct" constant. A struct is -// implemented as a map[string]interface{}, where the field -// names are they keys and the field value is the map value. -// -// Inputs: -// -// operand - number of field name/values on stack -// -// stack+0 - name of field 1 -// stack+1 - value of field 1 -// stack+2 = name of field 2 -// stack+3 = value of field 2 -// .... -// -// Items on the stack are pulled off in pairs representing a -// string containing the field name, and an arbitrary value. -// Any field names that start with data.MetadataPrefix (defs.InvisiblePrefix) -// are considered metadata and are stored as metadata in the -// resulting structure. This allows type names, etc. to be added -// to the struct definition -// The resulting map is then pushed back on the stack. -func structByteCode(c *Context, i interface{}) error { - var model interface{} - - count := data.Int(i) - structMap := map[string]interface{}{} - fields := make([]string, 0) - typeInfo := data.StructType - typeName := "" - - // Pull `count` pairs of items off the stack (name and - // value) and add them into the map. - for index := 0; index < count; index++ { - nameValue, err := c.Pop() - if err != nil { - return err - } - - name := data.String(nameValue) - if !strings.HasPrefix(name, data.MetadataPrefix) { - fields = append(fields, name) - } - - value, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(value) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - // If this is the type, use it to make a model. Otherwise, put it in the structure. - if name == data.TypeMDKey { - if t, ok := value.(*data.Type); ok { - typeInfo = t - model = t.InstanceOf(t) - typeName = t.Name() - } else { - ui.WriteLog(ui.InternalLogger, "ERROR: structByteCode() unexpected type value %v", value) - - return errors.ErrStop - } - } else { - structMap[name] = value - } - } - - if model != nil { - if model, ok := model.(*data.Struct); ok { - // Check all the fields in the new value to ensure they - // are valid. - for fieldName := range structMap { - if _, found := model.Get(fieldName); !strings.HasPrefix(fieldName, data.MetadataPrefix) && !found { - return c.error(errors.ErrInvalidField, fieldName) - } - } - - // Add in any fields from the type model not present - // in the new structure we're creating. We ignore any - // function definitions in the model, as they will be - // found later during function invocation if needed - // by chasing the model chain. - for _, fieldName := range model.FieldNames(false) { - fieldValue, _ := model.Get(fieldName) - - if value := reflect.ValueOf(fieldValue); value.Kind() == reflect.Ptr { - typeString := value.String() - if typeString == defs.ByteCodeReflectionTypeString { - continue - } - } - - if _, found := structMap[fieldName]; !found { - structMap[fieldName] = fieldValue - } - } - } else { - return c.error(errors.ErrUnknownType, typeInfo.String()) - } - } else { - // No type, default it to a struct. - t := data.StructureType() - for _, name := range fields { - t.DefineField(name, data.TypeOf(structMap[name])) - } - } - - // Put the newly created instance of a struct on the stack. - structure := data.NewStructFromMap(structMap) - - if typeName != "" { - structure.AsType(typeInfo) - } - - // If we are in static mode, or this is a non-empty definition, - // mark the structure as having static members. That means you - // cannot modify the field names or add/delete fields. - if c.typeStrictness == 0 || count > 0 { - structure.SetStatic(true) - } - - return c.push(structure) -} - -// makeMapByteCode implements the MakeMap opcode -// -// Inputs: -// -// argument - The count of key/values on the stack -// stack+0 = The map key type -// stack+1 = The map value type -// stack+2 - The first key -// stack+3 - The first value -// stack+4 - ... -// -// Create a new map. The argument is the number of key/value -// pairs on the stack, preceded by the key and value types. -func makeMapByteCode(c *Context, i interface{}) error { - count := data.Int(i) - - v, err := c.Pop() - if err != nil { - return err - } - - keyType := data.TypeOf(v) - - v, err = c.Pop() - if err != nil { - return err - } - - if isStackMarker(v) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - valueType := data.TypeOf(v) - - result := data.NewMap(keyType, valueType) - - for index := 0; index < count; index++ { - value, err := c.Pop() - if err != nil { - return err - } - - key, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(value) || isStackMarker(key) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - if _, err = result.Set(key, value); err != nil { - return err - } - } - - return c.push(result) -} diff --git a/expressions/bytecode/create_test.go b/expressions/bytecode/create_test.go deleted file mode 100644 index bcc2cb8..0000000 --- a/expressions/bytecode/create_test.go +++ /dev/null @@ -1,275 +0,0 @@ -package bytecode - -import ( - "fmt" - "reflect" - "testing" - - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -func Test_makeArrayByteCode(t *testing.T) { - type args struct { - stack []interface{} - i int - } - - tests := []struct { - name string - args args - want *data.Array - }{ - { - name: "[]int{5,3}", - args: args{ - stack: []interface{}{3, 5, data.IntType}, - i: 2, - }, - want: data.NewArrayFromArray(data.IntType, []interface{}{3, 5}), - }, - { - name: "[]string{\"Tom\", \"Cole\"}", - args: args{ - stack: []interface{}{"Cole", "Tom", data.StringType}, - i: 2, - }, - want: data.NewArrayFromArray(data.IntType, []interface{}{3, 5}), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := &Context{stack: tt.args.stack, stackPointer: len(tt.args.stack)} - - e := makeArrayByteCode(ctx, tt.args.i) - if e != nil { - t.Errorf("Unexpected error %v", e) - } - }) - } -} - -func Test_arrayByteCode(t *testing.T) { - target := arrayByteCode - name := "arrayByteCode" - - tests := []struct { - name string - arg interface{} - stack []interface{} - want interface{} - err error - static int - debug bool - }{ - { - name: "untyped array", - arg: 2, - stack: []interface{}{3, "test", float64(3.5)}, - err: nil, - want: data.NewArrayFromArray(data.InterfaceType, []interface{}{"test", float64(3.5)}), - }, - { - name: "typed array", - arg: []interface{}{3, data.Int32Type}, - stack: []interface{}{byte(3), "55", float64(3.5)}, - err: nil, - static: 2, - want: data.NewArrayFromArray(data.Int32Type, []interface{}{int32(3), int32(55), int32(3)}), - }, - { - name: "untyped static (valid) array", - arg: 3, - stack: []interface{}{int32(10), int32(11), int32(12)}, - static: 0, - want: data.NewArrayFromArray(data.InterfaceType, []interface{}{int32(10), int32(11), int32(12)}), - }, - { - name: "stack underflow", - arg: 3, - stack: []interface{}{"test", float64(3.5)}, - err: errors.ErrStackUnderflow, - static: 2, - want: data.NewArrayFromArray(data.InterfaceType, []interface{}{"test", float64(3.5)}), - }, - } - - for _, tt := range tests { - syms := symbols.NewSymbolTable("testing") - bc := ByteCode{} - - c := NewContext(syms, &bc) - c.typeStrictness = tt.static - - for _, item := range tt.stack { - _ = c.push(item) - } - - t.Run(tt.name, func(t *testing.T) { - if tt.debug { - fmt.Println("DEBUG") - } - - err := target(c, tt.arg) - - if err != nil { - e1 := nilError - e2 := nilError - - if tt.err != nil { - e1 = tt.err.Error() - } - if err != nil { - e2 = err.Error() - } - - if e1 == e2 { - return - } - - t.Errorf("%s() error %v", name, err) - } else if tt.err != nil { - t.Errorf("%s() expected error not reported: %v", name, tt.err) - } - - v, err := c.Pop() - - if err != nil { - t.Errorf("%s() stack error %v", name, err) - } - - if !reflect.DeepEqual(v, tt.want) { - t.Errorf("%s() got %v, want %v", name, v, tt.want) - } - }) - } -} - -func Test_makeMapByteCode(t *testing.T) { - target := makeMapByteCode - name := "makeMapByteCode" - - tests := []struct { - name string - arg interface{} - stack []interface{} - want interface{} - err error - static int - debug bool - }{ - { - name: "map[string]int", - arg: 4, - stack: []interface{}{ - "tom", 63, // Key/value pair - "mary", 47, // Key/value pair - "chelsea", 10, // Key/value pair - "sarah", 31, // Key/value pair - data.IntType, // Value type - data.StringType, // Key type - }, - static: 2, - err: nil, - want: data.NewMapFromMap(map[string]int{"tom": 63, "mary": 47, "chelsea": 10, "sarah": 31}), - }, - { - name: "Missing key type", - arg: 4, - static: 2, - stack: []interface{}{}, - err: errors.ErrStackUnderflow, - want: data.NewMapFromMap(map[string]int{"tom": 63, "mary": 47, "chelsea": 10, "sarah": 31}), - }, - { - name: "Missing value type", - arg: 4, - static: 2, - stack: []interface{}{ - data.StringType, // Key type - }, - err: errors.ErrStackUnderflow, - want: data.NewMapFromMap(map[string]int{"tom": 63, "mary": 47, "chelsea": 10, "sarah": 31}), - }, - { - name: "Missing key", - arg: 4, - static: 2, - stack: []interface{}{ - "mary", 47, // Key/value pair - "chelsea", 10, // Key/value pair - "sarah", 31, // Key/value pair - data.IntType, // Value type - data.StringType, // Key type - }, - err: errors.ErrStackUnderflow, - want: data.NewMapFromMap(map[string]int{"tom": 63, "mary": 47, "chelsea": 10, "sarah": 31}), - }, - { - name: "missing value", - arg: 4, - static: 2, - stack: []interface{}{ - "tom", // Key/value pair - "mary", 47, // Key/value pair - "chelsea", 10, // Key/value pair - "sarah", 31, // Key/value pair - data.IntType, // Value type - data.StringType, // Key type - }, - err: errors.ErrStackUnderflow, - want: data.NewMapFromMap(map[string]int{"tom": 63, "mary": 47, "chelsea": 10, "sarah": 31}), - }, - } - - for _, tt := range tests { - syms := symbols.NewSymbolTable("testing") - bc := ByteCode{} - - c := NewContext(syms, &bc) - c.typeStrictness = tt.static - - for _, item := range tt.stack { - _ = c.push(item) - } - - t.Run(tt.name, func(t *testing.T) { - if tt.debug { - fmt.Println("DEBUG") - } - - err := target(c, tt.arg) - - if err != nil { - e1 := nilError - e2 := nilError - - if tt.err != nil { - e1 = tt.err.Error() - } - if err != nil { - e2 = err.Error() - } - - if e1 == e2 { - return - } - - t.Errorf("%s() error %v", name, err) - } else if tt.err != nil { - t.Errorf("%s() expected error not reported: %v", name, tt.err) - } - - v, err := c.Pop() - - if err != nil { - t.Errorf("%s() stack error %v", name, err) - } - - if !reflect.DeepEqual(v, tt.want) { - t.Errorf("%s() got %v, want %v", name, v, tt.want) - } - }) - } -} diff --git a/expressions/bytecode/data.go b/expressions/bytecode/data.go index 1fa90b0..fd02566 100644 --- a/expressions/bytecode/data.go +++ b/expressions/bytecode/data.go @@ -1,341 +1,10 @@ package bytecode import ( - "strings" - - "github.com/tucats/gopackages/defs" "github.com/tucats/gopackages/errors" "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" ) -// defs.DiscardedVariable is the reserved name for the variable -// whose value is not used. This is place-holder in some Ego/Go -// syntax constructs where a value is not used. It can also be -// used to discard the result of a function call. - -// storeByteCode implements the Store opcode -// -// Inputs: -// -// operand - The name of the variable in which -// the top of stack is stored. -// stack+0 - The item to be "stored" is read -// on the stack. -// -// The value to be stored is popped from the stack. The -// variable name and value are used to do a type check -// to ensure that the value is compatible if we are in -// static type mode. -// -// Note that if the operand is actually an interface -// array, then the first item is the name and the second -// is the value, and the stack is not popped. -// -// The value is then written to the symbol table. -// -// If the variable name begins with "_" then it is -// considered a read-only variable, so if the stack -// contains a map then that map is marked with the -// metadata indicator that it is readonly. -func storeByteCode(c *Context, i interface{}) error { - var value interface{} - - var err error - - var name string - - if operands, ok := i.([]interface{}); ok && len(operands) == 2 { - name = data.String(operands[0]) - value = operands[1] - } else { - name = data.String(i) - - value, err = c.Pop() - if err != nil { - return err - } - } - - if len(name) > 1 && name[0:1] == defs.DiscardedVariable { - oldValue, found := c.get(name) - if !found { - return c.error(errors.ErrReadOnly).Context(name) - } - - if _, ok := oldValue.(symbols.UndefinedValue); !ok { - return c.error(errors.ErrReadOnly).Context(name) - } - } - - if isStackMarker(value) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - // Get the name. If it is the reserved name "_" it means - // to just discard the value. - if name == defs.DiscardedVariable { - return nil - } - - value, err = c.checkType(name, value) - if err != nil { - return c.error(err) - } - - if strings.HasPrefix(name, defs.DiscardedVariable) { - return c.set(name, data.Constant(value)) - } - - return c.set(name, value) -} - -// storeGlobalByteCode instruction processor. -func storeGlobalByteCode(c *Context, i interface{}) error { - value, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(value) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - // Get the name and set it in the global table. - name := data.String(i) - - // Is this a readonly variable that is a complex native type? - // If so, mark it as readonly. - if len(name) > 1 && name[0:1] == defs.DiscardedVariable { - constantValue := data.DeepCopy(value) - switch a := constantValue.(type) { - case *data.Map: - a.SetReadonly(true) - - case *data.Array: - a.SetReadonly(true) - - case *data.Struct: - a.SetReadonly(true) - } - - c.symbols.Root().SetAlways(name, constantValue) - } else { - c.symbols.Root().SetAlways(name, value) - } - - return err -} - -// StoreViaPointer has a name as it's argument. It loads the value, -// verifies it is a pointer, and stores TOS into that pointer. -func storeViaPointerByteCode(c *Context, i interface{}) error { - var dest interface{} - - name := "" - ok := false - - if i != nil { - name = data.String(i) - - if name == "" || name[0:1] == defs.DiscardedVariable { - return c.error(errors.ErrInvalidIdentifier) - } - - if d, ok := c.get(name); !ok { - return c.error(errors.ErrUnknownIdentifier).Context(name) - } else { - dest = d - } - } else { - if d, err := c.Pop(); err != nil { - return err - } else { - dest = d - } - } - - if data.IsNil(dest) { - return c.error(errors.ErrNilPointerReference).Context(name) - } - - // IF the destination is a pointer type and it's a pointer to an - // immutable object, we don't allow that. If we have a name, add - // that to the context of the error we create. - if x, ok := dest.(*interface{}); ok { - z := *x - if _, ok := z.(data.Immutable); ok { - e := c.error(errors.ErrReadOnlyValue) - if name != "" { - e = e.Context("*" + name) - } - - return e - } - } - - // Get the value we are going to store from the stack. if it's - // a stack marker, there was no return value on the stack. - src, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(src) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - // Based on the type, do the store. - switch actual := dest.(type) { - case *data.Immutable: - return c.error(errors.ErrReadOnlyValue) - - case *interface{}: - *actual = src - - case *bool: - d := src - if c.typeStrictness > 1 { - d = data.Coerce(src, true) - } else if _, ok := d.(string); !ok { - return c.error(errors.ErrInvalidVarType).Context(name) - } - - *actual = d.(bool) - - case *byte: - d := src - if c.typeStrictness > 1 { - d = data.Coerce(src, byte(1)) - } else if _, ok := d.(string); !ok { - return c.error(errors.ErrInvalidVarType).Context(name) - } - - *actual = d.(byte) - - case *int32: - d := src - if c.typeStrictness > 1 { - d = data.Coerce(src, int32(1)) - } else if _, ok := d.(string); !ok { - return c.error(errors.ErrInvalidVarType).Context(name) - } - - *actual = d.(int32) - - case *int: - d := src - if c.typeStrictness > 1 { - d = data.Coerce(src, int(1)) - } else if _, ok := d.(string); !ok { - return c.error(errors.ErrInvalidVarType).Context(name) - } - - *actual = d.(int) - - case *int64: - d := src - if c.typeStrictness > 1 { - d = data.Coerce(src, int64(1)) - } else if _, ok := d.(string); !ok { - return c.error(errors.ErrInvalidVarType).Context(name) - } - - *actual = d.(int64) - - case *float64: - d := src - if c.typeStrictness > 1 { - d = data.Coerce(src, float64(0)) - } else if _, ok := d.(string); !ok { - return c.error(errors.ErrInvalidVarType).Context(name) - } - - *actual = d.(float64) - - case *float32: - d := src - if c.typeStrictness > 1 { - d = data.Coerce(src, float32(0)) - } else if _, ok := d.(string); !ok { - return c.error(errors.ErrInvalidVarType).Context(name) - } - - *actual = d.(float32) - - case *string: - d := src - if c.typeStrictness > 1 { - d = data.Coerce(src, "") - } else if _, ok := d.(string); !ok { - return c.error(errors.ErrInvalidVarType).Context(name) - } - - *actual = d.(string) - - case *data.Array: - *actual, ok = src.(data.Array) - if !ok { - return c.error(errors.ErrNotAPointer).Context(name) - } - - case **data.Channel: - *actual, ok = src.(*data.Channel) - if !ok { - return c.error(errors.ErrNotAPointer).Context(name) - } - - default: - return c.error(errors.ErrNotAPointer).Context(name) - } - - return nil -} - -// storeAlwaysByteCode instruction processor. -func storeAlwaysByteCode(c *Context, i interface{}) error { - var v interface{} - - var symbolName string - - var err error - - if array, ok := i.([]interface{}); ok && len(array) == 2 { - symbolName = data.String(array[0]) - v = array[1] - } else { - symbolName = data.String(i) - - v, err = c.Pop() - if err != nil { - return err - } - - if isStackMarker(v) { - return c.error(errors.ErrFunctionReturnedVoid) - } - } - - c.setAlways(symbolName, v) - - // Is this a readonly variable that is a structure? If so, mark it - // with the embedded readonly flag. - if len(symbolName) > 1 && symbolName[0:1] == defs.DiscardedVariable { - switch a := v.(type) { - case *data.Map: - a.SetReadonly(true) - - case *data.Array: - a.SetReadonly(true) - - case *data.Struct: - a.SetReadonly(true) - } - } - - return err -} - // loadByteCode instruction processor. func loadByteCode(c *Context, i interface{}) error { name := data.String(i) @@ -368,28 +37,7 @@ func explodeByteCode(c *Context, i interface{}) error { return c.error(errors.ErrFunctionReturnedVoid) } - empty := true - - if m, ok := v.(*data.Map); ok { - if !m.KeyType().IsString() { - err = c.error(errors.ErrWrongMapKeyType) - } else { - keys := m.Keys() - - for _, k := range keys { - empty = false - v, _, _ := m.Get(k) - - c.setAlways(data.String(k), v) - } - - if err == nil { - return c.push(empty) - } - } - } else { - err = c.error(errors.ErrInvalidType).Context(data.TypeOf(v).String()) - } + err = c.error(errors.ErrInvalidType).Context(data.TypeOf(v).String()) return err } diff --git a/expressions/bytecode/data_test.go b/expressions/bytecode/data_test.go index 4720152..4497068 100644 --- a/expressions/bytecode/data_test.go +++ b/expressions/bytecode/data_test.go @@ -5,749 +5,11 @@ import ( "reflect" "testing" - "github.com/tucats/gopackages/defs" "github.com/tucats/gopackages/errors" "github.com/tucats/gopackages/expressions/data" "github.com/tucats/gopackages/expressions/symbols" ) -func TestStruct(t *testing.T) { - typeDef := data.TypeDefinition("usertype", data.StructureType( - data.Field{Name: "active", Type: data.BoolType}, - data.Field{Name: "test", Type: data.IntType}, - )) - - tests := []struct { - name string - stack []interface{} - arg interface{} - want interface{} - wantErr bool - static int - }{ - { - name: "two member incomplete test", - arg: 2, - stack: []interface{}{typeDef, data.TypeMDKey, true, "active"}, - want: data.NewStructFromMap(map[string]interface{}{ - "active": true, - "test": 0, - }).SetStatic(true).AsType(typeDef), - wantErr: false, - static: 0, - }, - { - name: "one member test", - arg: 1, - stack: []interface{}{123, "test"}, - want: data.NewStructFromMap(map[string]interface{}{ - "test": 123, - }).SetStatic(true), - static: 2, - wantErr: false, - }, - { - name: "two member test", - arg: 2, - stack: []interface{}{true, "active", 123, "test"}, - want: data.NewStructFromMap(map[string]interface{}{ - "test": 123, - "active": true, - }).SetStatic(true), - static: 2, - wantErr: false, - }, - { - name: "two member invalid static test", - arg: 3, - stack: []interface{}{typeDef, data.TypeMDKey, true, "invalid", 123, "test"}, - want: data.NewStructFromMap(map[string]interface{}{ - "active": true, - "test": 0, - }).SetStatic(true).AsType(typeDef), - wantErr: true, - static: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := &Context{ - stack: tt.stack, - stackPointer: len(tt.stack), - typeStrictness: tt.static, - symbols: symbols.NewSymbolTable("test bench"), - } - - ctx.symbols.SetAlways("usertype", typeDef) - - err := structByteCode(ctx, tt.arg) - if (err != nil) != tt.wantErr { - t.Errorf("StructImpl() error = %v, wantErr %v", err, tt.wantErr) - } else if err == nil { - got, _ := ctx.Pop() - f := reflect.DeepEqual(got, tt.want) - - if !f { - t.Errorf("StructImpl()\n got %v\n want %v", got, tt.want) - } - } - }) - } -} - -func Test_storeByteCode(t *testing.T) { - target := storeByteCode - name := "storeByteCode" - - tests := []struct { - name string - arg interface{} - initialValue interface{} - stack []interface{} - want interface{} - err error - static int - debug bool - }{ - { - name: "stack underflow", - arg: "a", - initialValue: 0, - stack: []interface{}{}, - static: 2, - err: errors.ErrStackUnderflow, - want: 0, - }, - { - name: "store to nul name", - arg: defs.DiscardedVariable, - initialValue: 0, - stack: []interface{}{int32(55)}, - err: nil, - static: 2, - want: 0, - }, - { - name: "simple integer store", - arg: "a", - initialValue: 0, - stack: []interface{}{int32(55)}, - err: nil, - static: 2, - want: int32(55), - }, - { - name: "simple integer store to readonly value", - arg: "_a", - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - err: errors.ErrReadOnly.Context("_a"), - }, - { - name: "replace string value with int32 value", - arg: "a", - initialValue: "test", - stack: []interface{}{int32(55)}, - err: nil, - static: 2, - want: int32(55), - }, - { - name: "invalid static replace string value with int32 value", - arg: "a", - initialValue: "test", - stack: []interface{}{int32(55)}, - err: errors.ErrInvalidVarType, - static: 0, - want: int32(55), - }, - { - name: "store of unknown variable", - arg: "a", - initialValue: nil, - stack: []interface{}{int32(55)}, - err: errors.ErrUnknownSymbol.Context("a"), - static: 2, - want: int32(55), - }, - } - - for _, tt := range tests { - syms := symbols.NewSymbolTable("testing") - bc := ByteCode{} - varname := data.String(tt.arg) - - c := NewContext(syms, &bc) - c.typeStrictness = tt.static - - for _, item := range tt.stack { - _ = c.push(item) - } - - if tt.initialValue != nil { - _ = c.create(varname) - _ = c.set(varname, tt.initialValue) - } - - t.Run(tt.name, func(t *testing.T) { - if tt.debug { - fmt.Println("DEBUG") - } - - err := target(c, tt.arg) - - if err != nil { - e1 := nilError - e2 := nilError - - if tt.err != nil { - e1 = tt.err.Error() - } - if err != nil { - e2 = err.Error() - } - - if e1 == e2 { - return - } - - t.Errorf("%s() error %v", name, err) - } else if tt.err != nil { - t.Errorf("%s() expected error not reported: %v", name, tt.err) - } - - v, found := c.symbols.Get(data.String(tt.arg)) - - if !found { - t.Errorf("%s() value not in symbol table: %v", name, tt.arg) - } - - if !reflect.DeepEqual(v, tt.want) { - t.Errorf("%s() got %v, want %v", name, v, tt.want) - } - }) - } -} - -func Test_storeAlwaysByteCode(t *testing.T) { - target := storeAlwaysByteCode - name := "storeAlwaysByteCode" - - tests := []struct { - name string - arg interface{} - initialValue interface{} - stack []interface{} - want interface{} - err error - static int - debug bool - }{ - { - name: "stack underflow", - arg: "a", - initialValue: 0, - stack: []interface{}{}, - err: errors.ErrStackUnderflow, - static: 2, - want: 0, - }, - { - name: "store to nul name is allowed", - arg: defs.DiscardedVariable, - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - err: nil, - want: int32(55), - }, - { - name: "simple integer store", - arg: "a", - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - err: nil, - want: int32(55), - }, - { - name: "simple integer store to readonly value", - arg: "_a", - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - want: int32(55), - }, - { - name: "replace string value with int32 value", - arg: "a", - initialValue: "test", - stack: []interface{}{int32(55)}, - static: 2, - err: nil, - want: int32(55), - }, - { - name: "static replace string value with int32 value", - arg: "a", - initialValue: "test", - stack: []interface{}{int32(55)}, - static: 0, - want: int32(55), - }, - { - name: "store of unknown variable", - arg: "a", - initialValue: nil, - stack: []interface{}{int32(55)}, - static: 2, - want: int32(55), - }, - } - - for _, tt := range tests { - syms := symbols.NewSymbolTable("testing") - bc := ByteCode{} - varname := data.String(tt.arg) - - c := NewContext(syms, &bc) - c.typeStrictness = tt.static - - for _, item := range tt.stack { - _ = c.push(item) - } - - if tt.initialValue != nil { - _ = c.create(varname) - _ = c.set(varname, tt.initialValue) - } - - t.Run(tt.name, func(t *testing.T) { - if tt.debug { - fmt.Println("DEBUG") - } - - err := target(c, tt.arg) - - if err != nil { - e1 := nilError - e2 := nilError - - if tt.err != nil { - e1 = tt.err.Error() - } - if err != nil { - e2 = err.Error() - } - - if e1 == e2 { - return - } - - t.Errorf("%s() error %v", name, err) - } else if tt.err != nil { - t.Errorf("%s() expected error not reported: %v", name, tt.err) - } - - v, found := c.symbols.Get(data.String(tt.arg)) - - if !found { - t.Errorf("%s() value not in symbol table: %v", name, tt.arg) - } - - if !reflect.DeepEqual(v, tt.want) { - t.Errorf("%s() got %v, want %v", name, v, tt.want) - } - }) - } -} - -func Test_storeGlobalByteCode(t *testing.T) { - target := storeGlobalByteCode - name := "storeGlobalByteCode" - - tests := []struct { - name string - arg interface{} - initialValue interface{} - stack []interface{} - want interface{} - err error - static int - debug bool - }{ - { - name: "stack underflow", - arg: "a", - initialValue: 0, - stack: []interface{}{}, - err: errors.ErrStackUnderflow, - static: 2, - want: 0, - }, - { - name: "store to nul name is allowed", - arg: defs.DiscardedVariable, - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - err: nil, - want: int32(55), - }, - { - name: "simple integer store", - arg: "a", - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - err: nil, - want: int32(55), - }, - { - name: "simple integer store to readonly value", - arg: "_a", - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - want: int32(55), - }, - { - name: "replace string value with int32 value", - arg: "a", - initialValue: "test", - stack: []interface{}{int32(55)}, - static: 2, - err: nil, - want: int32(55), - }, - { - name: "static replace string value with int32 value", - arg: "a", - initialValue: "test", - stack: []interface{}{int32(55)}, - static: 0, - want: int32(55), - }, - { - name: "store of unknown variable", - arg: "a", - initialValue: nil, - stack: []interface{}{int32(55)}, - static: 2, - want: int32(55), - }, - } - - for _, tt := range tests { - root := symbols.NewRootSymbolTable("root table") - syms := symbols.NewChildSymbolTable("testing", root) - - bc := ByteCode{} - varname := data.String(tt.arg) - - c := NewContext(syms, &bc) - c.typeStrictness = tt.static - - for _, item := range tt.stack { - _ = c.push(item) - } - - if tt.initialValue != nil { - _ = c.create(varname) - _ = c.set(varname, tt.initialValue) - } - - t.Run(tt.name, func(t *testing.T) { - if tt.debug { - fmt.Println("DEBUG") - } - - err := target(c, tt.arg) - - if err != nil { - e1 := nilError - e2 := nilError - - if tt.err != nil { - e1 = tt.err.Error() - } - if err != nil { - e2 = err.Error() - } - - if e1 == e2 { - return - } - - t.Errorf("%s() error %v", name, err) - } else if tt.err != nil { - t.Errorf("%s() expected error not reported: %v", name, tt.err) - } - - v, found := root.Get(data.String(tt.arg)) - - if !found { - t.Errorf("%s() value not in root symbol table: %v", name, tt.arg) - } - - if !reflect.DeepEqual(v, tt.want) { - t.Errorf("%s() got %v, want %v", name, v, tt.want) - } - }) - } -} - -func Test_storeViaPointerByteCode(t *testing.T) { - target := storeViaPointerByteCode - name := "storeViaPointerByteCode" - - tests := []struct { - name string - arg interface{} - initialValue interface{} - stack []interface{} - want interface{} - err error - static int - debug bool - }{ - { - name: "stack underflow", - arg: "a", - initialValue: 0, - stack: []interface{}{}, - err: errors.ErrStackUnderflow, - static: 2, - want: 0, - }, - { - name: "store to nul name is not allowed", - arg: defs.DiscardedVariable, - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - err: errors.ErrInvalidIdentifier, - want: int32(55), - }, - { - name: "*bool store", - arg: "a", - initialValue: true, - stack: []interface{}{byte(55)}, - static: 2, - err: nil, - want: true, - }, - { - name: "*byte store", - arg: "a", - initialValue: byte(1), - stack: []interface{}{byte(55)}, - static: 2, - err: nil, - want: byte(55), - }, - { - name: "*int32 store", - arg: "a", - initialValue: int32(55), - stack: []interface{}{55}, - static: 2, - err: nil, - want: int32(55), - }, - { - name: "*int store", - arg: "a", - initialValue: 55, - stack: []interface{}{55}, - static: 2, - err: nil, - want: 55, - }, - { - name: "*int64 store", - arg: "a", - initialValue: int64(55), - stack: []interface{}{int64(55)}, - static: 2, - err: nil, - want: int64(55), - }, - { - name: "*float32 store", - arg: "a", - initialValue: float32(0), - stack: []interface{}{float32(3.14)}, - static: 2, - err: nil, - want: float32(3.14), - }, - { - name: "*float64 store", - arg: "a", - initialValue: float64(0), - stack: []interface{}{float64(3.14)}, - static: 2, - err: nil, - want: float64(3.14), - }, - { - name: "*int store to readonly value", - arg: "_a", - initialValue: 0, - stack: []interface{}{int32(55)}, - static: 2, - want: int32(55), - err: errors.ErrInvalidIdentifier, - }, - { - name: "*str store of int32 value", - arg: "a", - initialValue: "test", - stack: []interface{}{int32(55)}, - static: 2, - err: nil, - want: "55", - }, - { - name: "static *int store int32 value", - arg: "a", - initialValue: int(0), - stack: []interface{}{int32(55)}, - static: 0, - want: int(55), - err: errors.ErrInvalidVarType.Context("a"), - }, - { - name: "static *byte store int32 value", - arg: "a", - initialValue: byte(9), - stack: []interface{}{int32(55)}, - static: 0, - want: byte(55), - err: errors.ErrInvalidVarType.Context("a"), - }, - { - name: "static *int64 store int32 value", - arg: "a", - initialValue: int64(0), - stack: []interface{}{int32(55)}, - static: 0, - want: int64(55), - err: errors.ErrInvalidVarType.Context("a"), - }, - { - name: "static *float32 store int32 value", - arg: "a", - initialValue: float32(0), - stack: []interface{}{int32(55)}, - static: 0, - want: float32(55), - err: errors.ErrInvalidVarType.Context("a"), - }, - { - name: "static *float64 store int32 value", - arg: "a", - initialValue: float64(0), - stack: []interface{}{int32(55)}, - static: 0, - want: float64(55), - err: errors.ErrInvalidVarType.Context("a"), - }, - { - name: "static *bool store int32 value", - arg: "a", - initialValue: false, - stack: []interface{}{int32(55)}, - static: 0, - want: true, - err: errors.ErrInvalidVarType.Context("a"), - }, - { - name: "static *str store int32 value", - arg: "a", - initialValue: "test", - stack: []interface{}{int32(55)}, - static: 0, - want: "55", - err: errors.ErrInvalidVarType.Context("a"), - }, - { - name: "store of unknown variable", - arg: "a", - initialValue: nil, - stack: []interface{}{int32(55)}, - want: int32(55), - err: errors.ErrUnknownIdentifier.Context("a"), - }, - } - - for _, tt := range tests { - syms := symbols.NewSymbolTable("testing") - bc := ByteCode{} - varname := data.String(tt.arg) - - if tt.debug { - fmt.Println("DEBUG") - } - - c := NewContext(syms, &bc) - c.typeStrictness = tt.static - - for _, item := range tt.stack { - _ = c.push(item) - } - - if tt.initialValue != nil { - _ = c.create(varname) - ptr, _ := data.AddressOf(tt.initialValue) - _ = c.set(varname, ptr) - } - - t.Run(tt.name, func(t *testing.T) { - if tt.debug { - fmt.Println("DEBUG") - } - - err := target(c, tt.arg) - - if err != nil { - e1 := nilError - e2 := nilError - - if tt.err != nil { - e1 = tt.err.Error() - } - if err != nil { - e2 = err.Error() - } - - if e1 == e2 { - return - } - - t.Errorf("%s() unexpected error %v", name, err) - } else if tt.err != nil { - t.Errorf("%s() expected error not reported: %v", name, tt.err) - } - - v, found := c.symbols.Get(data.String(tt.arg)) - - if !found { - t.Errorf("%s() value not in symbol table: %v", name, tt.arg) - } - - v, _ = data.Dereference(v) - if !reflect.DeepEqual(v, tt.want) { - t.Errorf("%s() got %v, want %v", name, v, tt.want) - } - }) - } -} - func Test_loadByteCode(t *testing.T) { target := loadByteCode name := "loadByteCode" @@ -845,83 +107,3 @@ func Test_loadByteCode(t *testing.T) { }) } } - -func Test_explodeByteCode(t *testing.T) { - tests := []struct { - name string - value interface{} - want map[string]interface{} - err error - }{ - { - name: "simple map explosion", - value: data.NewMapFromMap(map[string]interface{}{ - "foo": byte(1), - "bar": "frobozz", - }), - want: map[string]interface{}{"foo": byte(1), "bar": "frobozz"}, - }, - { - name: "wrong map key type", - value: data.NewMapFromMap(map[int]interface{}{ - 1: byte(1), - 2: "frobozz", - }), - err: errors.ErrWrongMapKeyType, - }, - { - name: "not a map", - value: "not a map", - err: errors.ErrInvalidType, - }, - { - name: "empty stack", - err: errors.ErrStackUnderflow, - }, - } - - for _, tt := range tests { - syms := symbols.NewSymbolTable("testing") - bc := ByteCode{} - - c := NewContext(syms, &bc) - - if tt.value != nil { - _ = c.push(tt.value) - } - - err := explodeByteCode(c, nil) - if err != nil { - e1 := nilError - e2 := nilError - - if tt.err != nil { - e1 = tt.err.Error() - } - - if err != nil { - e2 = err.Error() - } - - if e1 == e2 { - return - } - - t.Errorf("explodeByteCode() error %v", err) - } else if tt.err != nil { - t.Errorf("explodeByteCode() expected error not reported: %v", tt.err) - } - - for k, wantValue := range tt.want { - v, found := syms.Get(k) - - if !found { - t.Error("explodeByteCode() symbol 'foo' not found") - } - - if !reflect.DeepEqual(v, wantValue) { - t.Errorf("explodeByteCode() symbol '%s' contains: %v want %#v", k, v, wantValue) - } - } - } -} diff --git a/expressions/bytecode/doc.go b/expressions/bytecode/doc.go index 09cca86..018fcf0 100644 --- a/expressions/bytecode/doc.go +++ b/expressions/bytecode/doc.go @@ -14,7 +14,7 @@ // table, data stack, and other execution state information. // // Bytecode is executed until an error occurs, in which time the context.Run() -// operation returns with an error. If the code exits, it retursn the special +// operation returns with an error. If the code exits, it returns the special // errors.Exit return code. If the code runs to the start of a new line of // source (based on information stored in the bytecode) and the debugger is // enabled, the context returns errors.Debugger and the caller should invoke @@ -68,35 +68,4 @@ // accessing members of those complex types by index value, key value, or field // name. There are instrutions for managing flow-of-control, including branching // within the bytecode stream or calling functions in the same or another stream. -// -// Here is a trivial example of generating bytecode and executing it. -// -// // Create a ByteCode object and write some instructions into it. -// b := bytecode.New("sample program") -// b.Emit(bytecode.Load, "strings") -// b.Emit(bytecode.Member, "Left") -// b.Emit{bytecode.Push, "fruitcake") -// b.Emit(bytecode.Push, 5) -// b.Emit(bytecode.Call, 2) -// b.Emit(bytecode.Stop) -// -// // Make a symbol table, so we can call the function library. -// s := symbols.NewSymbolTable("sample program") -// functions.AddBuiltins(s) -// -// // Make a runtime context for this bytecode, and then run it. -// // The context has the symbol table and bytecode attached to it. -// c := bytecode.NewContext(s, b) -// err := c.Run() -// -// // Retrieve the last value and extract a string -// v, err := b.Pop() -// fmt.Printf("The result is %s\n", data.GetString(v)) -// -// This creates a new bytecode stream, and then adds instructions to it. -// -// The generated bytecode puts arguments to a function on a stack, and then -// calls the function. The result is left on the stack, and can be popped off -// after execution completes. The result (which is always an abstract -// interface{}) is then converted to a string and printed. package bytecode diff --git a/expressions/bytecode/flow.go b/expressions/bytecode/flow.go index 5f31941..a688265 100644 --- a/expressions/bytecode/flow.go +++ b/expressions/bytecode/flow.go @@ -4,9 +4,7 @@ import ( "reflect" "runtime" "strings" - "sync" - "github.com/tucats/gopackages/app-cli/ui" "github.com/tucats/gopackages/defs" "github.com/tucats/gopackages/errors" "github.com/tucats/gopackages/expressions/builtins" @@ -339,148 +337,3 @@ func callByteCode(c *Context, i interface{}) error { return err } - -// argCheckByteCode instruction processor verifies that there are enough items -// on the stack to satisfy the function's argument list. The operand is the -// number of values that must be available. Alternatively, the operand can be -// an array of objects, which are the minimum count, maximum count, and -// function name. -func argCheckByteCode(c *Context, i interface{}) error { - min := 0 - max := 0 - name := "function call" - - switch operand := i.(type) { - case []interface{}: - if len(operand) < 2 || len(operand) > 3 { - return c.error(errors.ErrArgumentTypeCheck) - } - - min = data.Int(operand[0]) - max = data.Int(operand[1]) - - if len(operand) == 3 { - name = data.String(operand[2]) - } - - case int: - if operand >= 0 { - min = operand - max = operand - } else { - min = 0 - max = -operand - } - - case []int: - if len(operand) != 2 { - return c.error(errors.ErrArgumentTypeCheck) - } - - min = operand[0] - max = operand[1] - - default: - return c.error(errors.ErrArgumentTypeCheck) - } - - args, found := c.get(defs.ArgumentListVariable) - if !found { - return c.error(errors.ErrArgumentTypeCheck) - } - - // Do the actual compare. Note that if we ended up with a negative - // max, that means variable argument list size, and we just assume - // what we found in the max... - if array, ok := args.(*data.Array); ok { - if max < 0 { - max = array.Len() - } - - if array.Len() < min || array.Len() > max { - return c.error(errors.ErrArgumentCount).In(name) - } - - return nil - } - - return c.error(errors.ErrArgumentTypeCheck) -} - -// See if the top of the "this" stack is a package, and if so return -// it's symbol table. The stack is not modified. -func (c *Context) getPackageSymbols() *symbols.SymbolTable { - if len(c.thisStack) == 0 { - return nil - } - - this := c.thisStack[len(c.thisStack)-1] - - if pkg, ok := this.value.(*data.Package); ok { - if s, ok := pkg.Get(data.SymbolsMDKey); ok { - if table, ok := s.(*symbols.SymbolTable); ok { - if !c.inPackageSymbolTable(table.Package()) { - ui.Log(ui.TraceLogger, "(%d) Using symbol table from package %s", c.threadID, table.Package()) - - return table - } - } - } - } - - return nil -} - -// Determine if the current symbol table stack is already within -// the named package symbol table structure. -func (c *Context) inPackageSymbolTable(name string) bool { - p := c.symbols - for p != nil { - if p.Package() == name { - return true - } - - p = p.Parent() - } - - return false -} - -func waitByteCode(c *Context, i interface{}) error { - if _, ok := i.(*sync.WaitGroup); ok { - i.(*sync.WaitGroup).Wait() - } else { - waitGroup.Wait() - } - - return nil -} - -func modeCheckBytecode(c *Context, i interface{}) error { - mode, found := c.symbols.Get(defs.ModeVariable) - - if found && (data.String(i) == data.String(mode)) { - return nil - } - - return c.error(errors.ErrWrongMode).Context(mode) -} - -func entryPointByteCode(c *Context, i interface{}) error { - var entryPointName string - - if i != nil { - entryPointName = data.String(i) - } else { - v, _ := c.Pop() - entryPointName = data.String(v) - } - - if entryPoint, found := c.get(entryPointName); found { - _ = c.push(entryPoint) - - return callByteCode(c, 0) - } - - return c.error(errors.ErrUndefinedEntrypoint).Context(entryPointName) -} diff --git a/expressions/bytecode/math.go b/expressions/bytecode/math.go index 42aab8d..ffd97a3 100644 --- a/expressions/bytecode/math.go +++ b/expressions/bytecode/math.go @@ -80,17 +80,6 @@ func negateByteCode(c *Context, i interface{}) error { return c.push(result) - case *data.Array: - // Create an array in inverse order. - r := data.NewArray(value.Type(), value.Len()) - - for n := 0; n < value.Len(); n = n + 1 { - d, _ := value.Get(n) - _ = r.Set(value.Len()-n-1, d) - } - - return c.push(r) - default: return c.error(errors.ErrInvalidType) } diff --git a/expressions/bytecode/math_test.go b/expressions/bytecode/math_test.go index af23ee7..b019268 100644 --- a/expressions/bytecode/math_test.go +++ b/expressions/bytecode/math_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" "github.com/tucats/gopackages/expressions/symbols" ) @@ -133,30 +132,6 @@ func Test_negateByteCode(t *testing.T) { stack: []interface{}{int32(-12)}, want: int32(12), }, - { - name: "negate (reverse) an array", - arg: nil, - stack: []interface{}{data.NewArrayFromArray( - data.StringType, - []interface{}{-1, 2})}, - want: data.NewArrayFromArray( - data.StringType, - []interface{}{2, -1}), - }, - { - name: "negate (reverse) a map", - arg: nil, - stack: []interface{}{data.NewMapFromMap(map[string]int32{ - "foo": int32(5), - "bar": int32(-3), - })}, - want: data.NewArrayFromArray( - data.StringType, - []interface{}{2, -1}), - err: errors.ErrInvalidType, - }, - - // TODO: Add test cases. } for _, tt := range tests { @@ -299,20 +274,6 @@ func Test_addByteCode(t *testing.T) { want: int32(12), err: errors.ErrStackUnderflow, }, - { - name: "add a string an array", - arg: nil, - stack: []interface{}{ - "xyzzy", - data.NewArrayFromArray( - data.StringType, - []interface{}{"foo", "bar"}), - }, - want: data.NewArrayFromArray( - data.StringType, - []interface{}{"foo", "bar", "xyzzy"}), - err: errors.ErrInvalidType.Context("interface{}"), - }, } for _, tt := range tests { @@ -444,17 +405,6 @@ func Test_andByteCode(t *testing.T) { stack: []interface{}{float32(1.0), float32(6.6)}, want: true, }, - { - name: "add a string an array", - arg: nil, - stack: []interface{}{ - "xyzzy", - data.NewArrayFromArray( - data.StringType, - []interface{}{"arrays are invalid but cast as", "false"}), - }, - want: false, - }, } for _, tt := range tests { @@ -587,17 +537,6 @@ func Test_orByteCode(t *testing.T) { stack: []interface{}{float32(1.0), float32(6.6)}, want: true, }, - { - name: "OR a string boolean value with an array", - arg: nil, - stack: []interface{}{ - "true", - data.NewArrayFromArray( - data.StringType, - []interface{}{"arrays are invalid but cast as", "false"}), - }, - want: true, - }, } for _, tt := range tests { diff --git a/expressions/bytecode/opcodes.go b/expressions/bytecode/opcodes.go index c1b63fe..1cdd69c 100644 --- a/expressions/bytecode/opcodes.go +++ b/expressions/bytecode/opcodes.go @@ -22,17 +22,13 @@ type Opcode int const ( Stop Opcode = iota // Stop must be the zero-th item. Add - AddressOf And - Array BitAnd BitOr BitShift Call Coerce Copy - CreateAndStore - DeRef Div Drop DropToMarker @@ -49,8 +45,6 @@ const ( LoadIndex LoadSlice LoadThis - MakeArray - MakeMap Modulo Mul Negate @@ -60,21 +54,10 @@ const ( Push ReadStack RequiredType - Return SetThis StaticTyping - Store - StoreAlways - StoreGlobal - StoreIndex - StoreInto - StoreViaPointer - Struct Sub Swap - SymbolCreate - SymbolDelete - SymbolOptCreate // Everything from here on is a branch instruction, whose // operand must be present and is an integer instruction @@ -89,16 +72,11 @@ const ( Branch BranchTrue BranchFalse - LocalCall - RangeNext - Try ) var opcodeNames = map[Opcode]string{ Add: "Add", - AddressOf: "AddressOf", And: "And", - Array: "Array", BitAnd: "BitAnd", BitOr: "BitOr", BitShift: "BitShift", @@ -108,8 +86,6 @@ var opcodeNames = map[Opcode]string{ Call: "Call", Coerce: "Coerce", Copy: "Copy", - CreateAndStore: "CreateAndStore", - DeRef: "DeRef", Div: "Div", Drop: "Drop", DropToMarker: "DropToMarker", @@ -126,9 +102,6 @@ var opcodeNames = map[Opcode]string{ LoadIndex: "LoadIndex", LoadSlice: "LoadSlice", LoadThis: "LoadThis", - LocalCall: "LocalCall", - MakeArray: "MakeArray", - MakeMap: "MakeMap", Modulo: "Modulo", Mul: "Mul", Negate: "Negate", @@ -138,31 +111,17 @@ var opcodeNames = map[Opcode]string{ Push: "Push", ReadStack: "ReadStack", RequiredType: "RequiredType", - Return: "Return", StaticTyping: "StaticTyping", Stop: "Stop", - Store: "Store", - StoreAlways: "StoreAlways", - StoreGlobal: "StoreGlobal", - StoreIndex: "StoreIndex", - StoreInto: "StoreInto", - StoreViaPointer: "StorePointer", - Struct: "Struct", Sub: "Sub", Swap: "Swap", - SymbolCreate: "SymbolCreate", - SymbolDelete: "SymbolDelete", - SymbolOptCreate: "SymbolOptCreate", - Try: "Try", } func initializeDispatch() { if dispatch == nil { dispatch = dispatchMap{ Add: addByteCode, - AddressOf: addressOfByteCode, And: andByteCode, - Array: arrayByteCode, BitAnd: bitAndByteCode, BitOr: bitOrByteCode, BitShift: bitShiftByteCode, @@ -171,8 +130,6 @@ func initializeDispatch() { BranchTrue: branchTrueByteCode, Call: callByteCode, Coerce: coerceByteCode, - CreateAndStore: createAndStoreByteCode, - DeRef: deRefByteCode, Div: divideByteCode, Drop: dropByteCode, DropToMarker: dropToMarkerByteCode, @@ -180,17 +137,12 @@ func initializeDispatch() { Equal: equalByteCode, Exp: exponentByteCode, Explode: explodeByteCode, - Flatten: flattenByteCode, GreaterThan: greaterThanByteCode, GreaterThanOrEqual: greaterThanOrEqualByteCode, LessThan: lessThanByteCode, LessThanOrEqual: lessThanOrEqualByteCode, Load: loadByteCode, - LoadIndex: loadIndexByteCode, - LoadSlice: loadSliceByteCode, LoadThis: loadThisByteCode, - MakeArray: makeArrayByteCode, - MakeMap: makeMapByteCode, Modulo: moduloByteCode, Mul: multiplyByteCode, Negate: negateByteCode, @@ -203,18 +155,8 @@ func initializeDispatch() { SetThis: setThisByteCode, StaticTyping: staticTypingByteCode, Stop: stopByteCode, - Store: storeByteCode, - StoreAlways: storeAlwaysByteCode, - StoreGlobal: storeGlobalByteCode, - StoreIndex: storeIndexByteCode, - StoreInto: storeIntoByteCode, - StoreViaPointer: storeViaPointerByteCode, - Struct: structByteCode, Sub: subtractByteCode, Swap: swapByteCode, - SymbolCreate: symbolCreateByteCode, - SymbolDelete: symbolDeleteByteCode, - SymbolOptCreate: symbolCreateIfByteCode, } } } diff --git a/expressions/bytecode/package.go b/expressions/bytecode/package.go deleted file mode 100644 index a928752..0000000 --- a/expressions/bytecode/package.go +++ /dev/null @@ -1,216 +0,0 @@ -package bytecode - -import ( - "strings" - "sync" - - "github.com/tucats/gopackages/app-cli/ui" - "github.com/tucats/gopackages/defs" - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" - "github.com/tucats/gopackages/util" -) - -type packageDef struct { - name string -} - -// Note there are reflection dependencies on the name of the -// field; it must be named "Value". -type ConstantWrapperx struct { - Value interface{} -} - -var packageCache = map[string]*data.Package{} -var packageCacheLock sync.RWMutex - -func CopyPackagesToSymbols(s *symbols.SymbolTable) { - packageCacheLock.Lock() - defer packageCacheLock.Unlock() - - for k, v := range packageCache { - s.SetAlways(k, v) - } -} - -func IsPackage(name string) bool { - packageCacheLock.Lock() - defer packageCacheLock.Unlock() - - _, found := packageCache[name] - - return found -} - -func GetPackage(name string) (*data.Package, bool) { - packageCacheLock.Lock() - defer packageCacheLock.Unlock() - - // Is this one we've already processed? IF so, return the - // cached value. - if p, ok := packageCache[name]; ok { - return p, true - } - - // No such package already defined, so let's create one and store a new - // empty symbol table for it's use. - pkg := data.NewPackage(name) - pkg.Set(data.SymbolsMDKey, symbols.NewSymbolTable("package "+name)) - - packageCache[name] = pkg - - return pkg, false -} - -func inFileByteCode(c *Context, i interface{}) error { - c.name = "file " + data.String(i) - - return nil -} - -func inPackageByteCode(c *Context, i interface{}) error { - c.pkg = data.String(i) - - return nil -} - -func importByteCode(c *Context, i interface{}) error { - name := data.String(i) - - pkg, ok := GetPackage(name) - if !ok { - return c.error(errors.ErrImportNotCached).Context(name) - } - - // Do we already have the local symbol table in the tree? - alreadyFound := false - - for s := c.symbols; s != nil; s = s.Parent() { - if s.Package() == name { - alreadyFound = true - - break - } - } - - // If the package table isn't already in the tree, inject if it - // there is one. - if !alreadyFound { - if symV, found := pkg.Get(data.SymbolsMDKey); found { - sym := symV.(*symbols.SymbolTable) - sym.SetPackage(name) - - sym.SetParent(c.symbols) - c.symbols = sym - } - } - - // Finally, store the entire package definition by name as well. - c.setAlways(name, pkg) - - return nil -} - -func pushPackageByteCode(c *Context, i interface{}) error { - name := data.String(i) - - // Are we already in this package? Happens when a directory of package - // files are concatenated together... - if len(c.packageStack) > 0 && c.packageStack[len(c.packageStack)-1].name == name { - return nil - } - - // Add the package to the stack, and create a nested symbol table scope. - c.packageStack = append(c.packageStack, packageDef{ - name, - }) - - // Create an initialize the package variable. If it already exists - // as a package (from a previous import or autoimport) re-use it - pkg, _ := GetPackage(name) - - // Define a symbol table to be used with the package. If there - // already is one for this package, use it. Else create a new one. - var syms *symbols.SymbolTable - - if symV, ok := pkg.Get(data.SymbolsMDKey); ok { - syms = symV.(*symbols.SymbolTable) - } else { - syms = symbols.NewSymbolTable("package " + name) - } - - syms.SetParent(c.symbols) - syms.SetPackage(name) - - c.symbols = syms - - return nil -} - -// Instruction to indicate we are done with any definitions for a -// package. The current (package-specific) symbol table is drained -// and any visible names are copied into the package structure, which -// is then saved in the package cache. -func popPackageByteCode(c *Context, i interface{}) error { - size := len(c.packageStack) - if size == 0 { - return c.error(errors.ErrMissingPackageStatement) - } - - // Pop the item off the package stack. - pkgdef := c.packageStack[size-1] - c.packageStack = c.packageStack[:size-1] - - // Verify that we're on the right package. - if pkgdef.name != data.String(i) { - return c.error(errors.ErrPanic).Context("package name mismatch: " + pkgdef.name) - } - - // Retrieve the package variable - pkg, found := GetPackage(pkgdef.name) - if !found { - return c.error(errors.ErrMissingPackageStatement) - } - - first := true - // Copy all the upper-case ("external") symbols names to the package level. - for _, k := range c.symbols.Names() { - if !strings.HasPrefix(k, defs.InvisiblePrefix) && util.HasCapitalizedName(k) { - v, attr, _ := c.symbols.GetWithAttributes(k) - - if first { - ui.Log(ui.TraceLogger, "(%d) Updating package %s", c.threadID, pkgdef.name) - - first = false - } - - ui.Log(ui.TraceLogger, "(%d) symbol %s", c.threadID, k) - - // If it was readonly, store it as a constant value now. - if attr.Readonly { - pkg.Set(k, data.Constant(v)) - } else { - pkg.Set(k, v) - } - } - } - - // Save a copy of symbol table as well in the package, containing the non-exported - // symbols that aren't hidden values used by Ego itself. - s := symbols.NewSymbolTable("package " + pkgdef.name + " local values") - - for _, k := range c.symbols.Names() { - if !strings.HasPrefix(k, defs.InvisiblePrefix) { - v, _ := c.symbols.Get(k) - - s.SetAlways(k, v) - } - } - - pkg.Set(data.SymbolsMDKey, s) - - // Reset the active symbol table to the state before we processed - // the package. - return c.popSymbolTable() -} diff --git a/expressions/bytecode/range.go b/expressions/bytecode/range.go deleted file mode 100644 index 629b30d..0000000 --- a/expressions/bytecode/range.go +++ /dev/null @@ -1,251 +0,0 @@ -package bytecode - -import ( - "github.com/tucats/gopackages/defs" - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" -) - -// rangeDefinition describes what we know about the (current) for..range loop. This -// is created by the RangeInit instruction and pushed on a stack in the -// context. The RangeNext instruction uses this information to advance -// through the range, and determine when the range is exhausted. -type rangeDefinition struct { - indexName string - valueName string - value interface{} - keySet []interface{} - runes []rune - index int -} - -// rangeInitByteCode implements the RangeInit opcode -// -// Inputs: -// -// operand - an array of two strings containing -// the names of the index and value -// variables. -// stack+0 - The item to be "ranged" is stored -// on the stack. This can be a map, -// an array, a structure, or a channel -// -// The RangeInit opcode sets up the runtime context for -// a for..range operation. The index and value variables -// create created in a new symbol scope for the range, -// and for map types, a keyset is derived that will be -// used to step through the map. -// -// This information describing the range operation is -// pushed on a stack in the runtime context where it -// can be accessed by the RangeNext opcode. The stack -// allows nested for...range statements. -func rangeInitByteCode(c *Context, i interface{}) error { - var v interface{} - - var err error - - r := rangeDefinition{} - - if list, ok := i.([]interface{}); ok && len(list) == 2 { - r.indexName = data.String(list[0]) - r.valueName = data.String(list[1]) - - if r.indexName != "" && r.indexName != defs.DiscardedVariable { - err = c.symbols.Create(r.indexName) - } - - if err == nil && r.valueName != "" && r.valueName != defs.DiscardedVariable { - err = c.symbols.Create(r.valueName) - } - } - - if err == nil { - if v, err = c.Pop(); err == nil { - if isStackMarker(v) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - r.value = v - - switch actual := v.(type) { - case string: - keySet := make([]interface{}, 0) - runes := make([]rune, 0) - - for i, ch := range actual { - keySet = append(keySet, i) - runes = append(runes, ch) - } - - r.keySet = keySet - r.runes = runes - - case *data.Map: - r.keySet = actual.Keys() - actual.SetReadonly(true) - - case *data.Array: - actual.SetReadonly(true) - - case *data.Channel: - // No further init required - - case []interface{}: - // No further init required. This is used for ranges - // where an array is generated by the compiler... - - default: - err = c.error(errors.ErrInvalidType) - } - - r.index = 0 - c.rangeStack = append(c.rangeStack, &r) - } - } - - return err -} - -// rangeNextByteCode implements the RangeNext opcode -// -// Inputs: -// -// operand - The bytecode address to branch to -// when the range is exhausted. -// -// The RangeNext opcode fetches the top of the range -// stack from the runtime context, and evaluates the -// type of the item being ranged. For each type, the -// operations are similar: -// -// 1. Determine if the index is already outside the -// range, in which case the branch is taken. The -// topmost item on the range stack is discarded. -// -// 2. The range is incremented and value is read. -// The value (map member, array index, channel) -// is stored in the value variable. The index -// number is also stored in the index variable. -func rangeNextByteCode(c *Context, i interface{}) error { - var err error - - destination := data.Int(i) - - if stackSize := len(c.rangeStack); stackSize == 0 { - c.programCounter = destination - } else { - r := c.rangeStack[stackSize-1] - - switch actual := r.value.(type) { - case string: - if r.index >= len(r.keySet) { - c.programCounter = destination - c.rangeStack = c.rangeStack[:stackSize-1] - } else { - key := r.keySet[r.index] - value := r.runes[r.index] - - if r.indexName != "" && r.indexName != defs.DiscardedVariable { - err = c.symbols.Set(r.indexName, key) - } - - if err == nil && r.valueName != "" && r.valueName != defs.DiscardedVariable { - err = c.symbols.Set(r.valueName, string(value)) - } - - r.index++ - } - - case *data.Map: - if r.index >= len(r.keySet) { - c.programCounter = destination - c.rangeStack = c.rangeStack[:stackSize-1] - - actual.SetReadonly(false) - } else { - key := r.keySet[r.index] - - if r.indexName != "" && r.indexName != defs.DiscardedVariable { - err = c.symbols.Set(r.indexName, key) - } - - if err == nil && r.valueName != "" && r.valueName != defs.DiscardedVariable { - var value interface{} - - ok := false - if value, ok, err = actual.Get(key); ok && err == nil { - err = c.symbols.Set(r.valueName, value) - } else { - // If the key was deleted inside the loop, we set the value to nil - err = c.symbols.Set(r.valueName, nil) - } - } - - r.index++ - } - - case *data.Channel: - var datum interface{} - - if actual.IsEmpty() { - c.programCounter = destination - c.rangeStack = c.rangeStack[:stackSize-1] - } else { - datum, err = actual.Receive() - if err == nil { - if r.indexName != "" && r.indexName != defs.DiscardedVariable { - err = c.symbols.Set(r.indexName, r.index) - } - if err == nil && r.valueName != "" && r.valueName != defs.DiscardedVariable { - err = c.symbols.Set(r.valueName, datum) - } - - r.index++ - } else { - c.programCounter = destination - c.rangeStack = c.rangeStack[:stackSize-1] - } - } - - case *data.Array: - if r.index >= actual.Len() { - c.programCounter = destination - actual.SetReadonly(false) - c.rangeStack = c.rangeStack[:stackSize-1] - } else { - if r.indexName != "" && r.indexName != defs.DiscardedVariable { - err = c.symbols.Set(r.indexName, r.index) - } - if err == nil && r.valueName != "" && r.valueName != defs.DiscardedVariable { - var d interface{} - - d, err = actual.Get(r.index) - if err == nil { - err = c.symbols.Set(r.valueName, d) - } - } - r.index++ - } - - case []interface{}: - if r.index >= len(actual) { - c.programCounter = destination - c.rangeStack = c.rangeStack[:stackSize-1] - } else { - if r.indexName != "" && r.indexName != defs.DiscardedVariable { - err = c.symbols.Set(r.indexName, r.index) - } - if err == nil && r.valueName != "" && r.valueName != defs.DiscardedVariable { - err = c.symbols.Set(r.valueName, actual[r.index]) - } - r.index++ - } - - default: - c.programCounter = destination - } - } - - return err -} diff --git a/expressions/bytecode/rest.go b/expressions/bytecode/rest.go deleted file mode 100644 index e530f9c..0000000 --- a/expressions/bytecode/rest.go +++ /dev/null @@ -1,178 +0,0 @@ -package bytecode - -import ( - "fmt" - "net/http" - - "github.com/tucats/gopackages/app-cli/ui" - "github.com/tucats/gopackages/defs" - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" -) - -/******************************************\ -* * -* R E S T I / O * -* * -\******************************************/ - -// authByteCode validates if the current user is authenticated or not, using the global -// variable _authenticated whose value was set during REST service initialization. -// The operand determines what kind of authentication is required; i.e. via token -// or username or either, and whether the user must be an admin (root) user. -func authByteCode(c *Context, i interface{}) error { - var user, pass, token string - - if _, ok := c.get("_authenticated"); !ok { - return c.error(errors.ErrNotAService) - } - - kind := data.String(i) - - if v, ok := c.get("_user"); ok { - user = data.String(v) - } - - if v, ok := c.get("_password"); ok { - pass = data.String(v) - } - - if v, ok := c.get("_token"); ok { - token = data.String(v) - } - - tokenValid := false - if v, ok := c.get("_token_valid"); ok { - tokenValid = data.Bool(v) - } - - // Before we do anything else, if we don't have a username/password - // and we don't have credentials, this is a 401 in all cases. - if user == "" && pass == "" && token == "" { - c.running = false - c.GetSymbols().Root().SetAlways(defs.RestStatusVariable, http.StatusUnauthorized) - writeResponse(c, "401 Not authorized") - writeStatus(c, http.StatusUnauthorized) - - ui.Log(ui.InfoLogger, "@authenticated request provides no credentials") - - return nil - } - - // See if the authentication required is for a token or admin token. - if (kind == defs.TokenRequired || kind == defs.AdminTokenRequired) && !tokenValid { - c.running = false - - c.GetSymbols().Root().SetAlways(defs.RestStatusVariable, http.StatusForbidden) - writeResponse(c, "403 Forbidden") - writeStatus(c, http.StatusForbidden) - ui.Log(ui.InfoLogger, "@authenticated token: no valid token") - - return nil - } - - if kind == defs.UserAuthenticationRequired { - if user == "" && pass == "" { - c.running = false - - c.GetSymbols().Root().SetAlways(defs.RestStatusVariable, http.StatusUnauthorized) - writeResponse(c, "401 Not authorized") - writeStatus(c, http.StatusUnauthorized) - - ui.Log(ui.InfoLogger, "@authenticated user: no credentials") - - return nil - } - - kind = defs.Any - } - - if kind == defs.Any { - isAuth := false - - if v, ok := c.get("_authenticated"); ok { - isAuth = data.Bool(v) - } - - if !isAuth { - c.running = false - - c.GetSymbols().Root().SetAlways(defs.RestStatusVariable, http.StatusForbidden) - writeResponse(c, "403 Forbidden") - writeStatus(c, http.StatusForbidden) - ui.Log(ui.InfoLogger, "@authenticated any: not authenticated") - - return nil - } - } - - if kind == defs.AdminAuthneticationRequired || kind == defs.AdminTokenRequired { - isAuth := false - - if v, ok := c.get("_superuser"); ok { - isAuth = data.Bool(v) - } - - if !isAuth { - c.running = false - - c.GetSymbols().Root().SetAlways(defs.RestStatusVariable, http.StatusForbidden) - writeResponse(c, "403 Forbidden") - writeStatus(c, http.StatusForbidden) - ui.Log(ui.InfoLogger, fmt.Sprintf("@authenticated %s: not admin", kind)) - } - } - - return nil -} - -// Generate a response body for a REST service. If the current media type is JSON, then the -// top of stack is formatted as JSON, otherwise it is formatted as text, and written to the -// response. -func responseByteCode(c *Context, i interface{}) error { - v, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(v) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - isJSON := false - if v, ok := c.symbols.Get("_json"); ok { - isJSON = data.Bool(v) - } - - if isJSON { - c.symbols.Root().SetAlways("_rest_response", v) - } else { - if b, ok := v.(*data.Array); ok { - if bs := b.GetBytes(); bs != nil { - writeResponse(c, string(bs)+"\n") - - return nil - } - } - - writeResponse(c, data.FormatUnquoted(v)+"\n") - } - - return nil -} - -func writeStatus(c *Context, status int) { - responseSymbol, _ := c.get("$response") - if responseStruct, ok := responseSymbol.(*data.Struct); ok { - _ = responseStruct.SetAlways("Status", status) - } -} - -func writeResponse(c *Context, output string) { - responseSymbol, _ := c.get("$response") - if responseStruct, ok := responseSymbol.(*data.Struct); ok { - bufferValue, _ := responseStruct.Get("Buffer") - - _ = responseStruct.SetAlways("Buffer", data.String(bufferValue)+output) - } -} diff --git a/expressions/bytecode/stack.go b/expressions/bytecode/stack.go index 9e0c2a6..3d59369 100644 --- a/expressions/bytecode/stack.go +++ b/expressions/bytecode/stack.go @@ -245,28 +245,3 @@ func copyByteCode(c *Context, i interface{}) error { return err } - -func getVarArgsByteCode(c *Context, i interface{}) error { - err := c.error(errors.ErrInvalidVariableArguments) - argPos := data.Int(i) - - if arrayV, ok := c.get(defs.ArgumentListVariable); ok { - if args, ok := arrayV.(*data.Array); ok { - // If no more args in the list to satisfy, push empty array - if args.Len() < argPos { - r := data.NewArray(data.InterfaceType, 0) - - return c.push(r) - } - - value, err := args.GetSlice(argPos, args.Len()) - if err != nil { - return err - } - - return c.push(data.NewArrayFromArray(data.InterfaceType, value)) - } - } - - return err -} diff --git a/expressions/bytecode/structs.go b/expressions/bytecode/structs.go deleted file mode 100644 index 2af3738..0000000 --- a/expressions/bytecode/structs.go +++ /dev/null @@ -1,420 +0,0 @@ -package bytecode - -import ( - "fmt" - "reflect" - - "github.com/tucats/gopackages/defs" - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" - "github.com/tucats/gopackages/util" -) - -// This manages operations on structures (structs, maps, and arrays) - -// loadIndexByteCode instruction processor. If the operand is non-nil then -// it is used as the index value, else the index value comes from the -// stack. Note that LoadIndex cannot be used to lcoate a package member, -// that can only be done using the Member opcoode. This is used to detect -// when an (illegal) attempt is made to write to a package member. -func loadIndexByteCode(c *Context, i interface{}) error { - var err error - - var index interface{} - - if i != nil { - index = i - } else { - index, err = c.Pop() - if err != nil { - return err - } - } - - array, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(index) || isStackMarker(array) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - switch a := array.(type) { - case *data.Package: - return c.error(errors.ErrReadOnlyValue) - - case *data.Map: - var v interface{} - - // A bit of a hack here. If this is a map index, and we - // know the next instruction is a StackCheck 2, then - // it is the condition that allows for an optional second - // value indicating if the map value was found or not. - // - // If the list-based lvalue processor changes it's use of - // the StackCheck opcode this will need to be revised! - pc := c.programCounter - opcodes := c.bc.Opcodes() - twoValues := false - - if len(opcodes) > pc { - next := opcodes[pc] - if count, ok := next.Operand.(int); ok && count == 2 { - found := false - - if v, found, err = a.Get(index); err == nil { - _ = c.push(NewStackMarker("results")) - _ = c.push(found) - err = c.push(v) - twoValues = true - } - } - } - - if !twoValues { - if v, _, err = a.Get(index); err == nil { - err = c.push(v) - } - } - - // Reading from a channel ignores the index value. - case *data.Channel: - var datum interface{} - - datum, err = a.Receive() - if err == nil { - err = c.push(datum) - } - - case *data.Struct: - key := data.String(index) - v, _ := a.Get(key) - err = c.push(v) - c.lastStruct = a - - case *data.Array: - subscript := data.Int(index) - if subscript < 0 || subscript >= a.Len() { - return c.error(errors.ErrArrayIndex).Context(subscript) - } - - v, _ := a.Get(subscript) - err = c.push(v) - - case []interface{}: - // Needed for varars processing - subscript := data.Int(index) - if subscript < 0 || subscript >= len(a) { - return c.error(errors.ErrArrayIndex).Context(subscript) - } - - v := a[subscript] - err = c.push(v) - - default: - err = c.error(errors.ErrInvalidType) - } - - return err -} - -// loadSliceByteCode instruction processor. -func loadSliceByteCode(c *Context, i interface{}) error { - index2, err := c.Pop() - if err != nil { - return err - } - - index1, err := c.Pop() - if err != nil { - return err - } - - array, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(array) || isStackMarker(index1) || isStackMarker(index2) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - switch a := array.(type) { - case string: - subscript1 := data.Int(index1) - subscript2 := data.Int(index2) - - if subscript2 > len(a) || subscript2 < 0 { - return errors.ErrInvalidSliceIndex.Context(subscript2) - } - - if subscript1 < 0 || subscript1 > subscript2 { - return errors.ErrInvalidSliceIndex.Context(subscript1) - } - - return c.push(a[subscript1:subscript2]) - - case *data.Array: - subscript1 := data.Int(index1) - subscript2 := data.Int(index2) - - v, err := a.GetSliceAsArray(subscript1, subscript2) - if err == nil { - err = c.push(v) - } - - return err - // Array of objects means we retrieve a slice. - case []interface{}: - subscript1 := data.Int(index1) - if subscript1 < 0 || subscript1 >= len(a) { - return c.error(errors.ErrInvalidSliceIndex).Context(subscript1) - } - - subscript2 := data.Int(index2) - if subscript2 < subscript1 || subscript2 >= len(a) { - return c.error(errors.ErrInvalidSliceIndex).Context(subscript2) - } - - v := a[subscript1 : subscript2+1] - _ = c.push(v) - - default: - return c.error(errors.ErrInvalidType) - } - - return nil -} - -// storeIndexByteCode instruction processor. -func storeIndexByteCode(c *Context, i interface{}) error { - var index interface{} - - var err error - - // If the index value is in the parameter, then use that, else get - // it from the stack. - if i != nil { - index = i - } else { - index, err = c.Pop() - if err != nil { - return err - } - } - - destination, err := c.Pop() - if err != nil { - return err - } - - v, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(destination) || isStackMarker(index) || isStackMarker(v) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - switch a := destination.(type) { - case *data.Package: - name := data.String(index) - - // Must be an exported (capitalized) name. - if !util.HasCapitalizedName(name) { - return c.error(errors.ErrSymbolNotExported, a.Name+"."+name) - } - - // Cannot start with the read-only name - if name[0:1] == defs.DiscardedVariable { - return c.error(errors.ErrReadOnlyValue, a.Name+"."+name) - } - - // If it's a declared item in the package, is it one of the ones - // that is readOnly by default? - if oldItem, found := a.Get(name); found { - switch oldItem.(type) { - // These types cannot be written to. - case *ByteCode, - func(*symbols.SymbolTable, []interface{}) (interface{}, error), - data.Immutable: - // Tell the caller nope... - return c.error(errors.ErrReadOnlyValue, a.Name+"."+name) - } - } - - // Get the associated symbol table - symV, found := a.Get(data.SymbolsMDKey) - if found { - syms := symV.(*symbols.SymbolTable) - - existingValue, found := syms.Get(name) - if found { - if _, ok := existingValue.(data.Immutable); ok { - return c.error(errors.ErrInvalidConstant, a.Name+"."+name) - } - } - - return syms.Set(name, v) - } - - case *data.Type: - var defn *data.Declaration - if actual, ok := v.(*ByteCode); ok { - defn = actual.declaration - } else if actual, ok := v.(*data.Declaration); ok { - defn = actual - } - - if defn == nil { - fmt.Printf("DEBUG: unknown function value: %#v\n", v) - } - - a.DefineFunction(data.String(index), defn, nil) - - case *data.Map: - if _, err = a.Set(index, v); err == nil { - err = c.push(a) - } - - if err != nil { - return errors.NewError(err).In(c.GetModuleName()).At(c.GetLine(), 0) - } - - case *data.Struct: - key := data.String(index) - - err = a.Set(key, v) - if err != nil { - return c.error(err) - } - - // If this is from a package, we must be in the same package to access it. - if pkg := a.PackageName(); pkg != "" && pkg != c.pkg { - if !util.HasCapitalizedName(key) { - return c.error(errors.ErrSymbolNotExported).Context(key) - } - } - - _ = c.push(a) - - // Index into array is integer index - case *data.Array: - subscript := data.Int(index) - if subscript < 0 || subscript >= a.Len() { - return c.error(errors.ErrArrayIndex).Context(subscript) - } - - if c.typeStrictness == 0 { - vv, _ := a.Get(subscript) - if vv != nil && (reflect.TypeOf(vv) != reflect.TypeOf(v)) { - return c.error(errors.ErrInvalidVarType) - } - } - - err = a.Set(subscript, v) - if err == nil { - err = c.push(a) - } - - return err - - // Index into array is integer index - case []interface{}: - subscript := data.Int(index) - if subscript < 0 || subscript >= len(a) { - return c.error(errors.ErrArrayIndex).Context(subscript) - } - - if c.typeStrictness == 0 { - vv := a[subscript] - if vv != nil && (reflect.TypeOf(vv) != reflect.TypeOf(v)) { - return c.error(errors.ErrInvalidVarType) - } - } - - a[subscript] = v - _ = c.push(a) - - default: - return c.error(errors.ErrInvalidType).Context(data.TypeOf(a).String()) - } - - return nil -} - -// storeIntoByteCode instruction processor. -func storeIntoByteCode(c *Context, i interface{}) error { - index, err := c.Pop() - if err != nil { - return err - } - - v, err := c.Pop() - if err != nil { - return err - } - - destination, err := c.Pop() - if err != nil { - return err - } - - if isStackMarker(destination) || isStackMarker(v) || isStackMarker(index) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - switch a := destination.(type) { - case *data.Map: - if _, err = a.Set(index, v); err == nil { - err = c.push(a) - } - - if err != nil { - return c.error(err) - } - - default: - return c.error(errors.ErrInvalidType) - } - - return nil -} - -func flattenByteCode(c *Context, i interface{}) error { - c.argCountDelta = 0 - - v, err := c.Pop() - if err == nil { - if isStackMarker(v) { - return c.error(errors.ErrFunctionReturnedVoid) - } - - if array, ok := v.(*data.Array); ok { - for idx := 0; idx < array.Len(); idx = idx + 1 { - vv, _ := array.Get(idx) - _ = c.push(vv) - c.argCountDelta++ - } - } else if array, ok := v.([]interface{}); ok { - for _, vv := range array { - _ = c.push(vv) - c.argCountDelta++ - } - } else { - _ = c.push(v) - } - } - - // If we found stuff to expand, reduce the count by one (since - // any argument list knows about the pre-flattened array value - // in the function call count) - if c.argCountDelta > 0 { - c.argCountDelta-- - } - - return err -} diff --git a/expressions/bytecode/symbols.go b/expressions/bytecode/symbols.go index f5b6f58..a5e7ade 100644 --- a/expressions/bytecode/symbols.go +++ b/expressions/bytecode/symbols.go @@ -8,7 +8,6 @@ import ( "github.com/tucats/gopackages/errors" "github.com/tucats/gopackages/expressions/data" "github.com/tucats/gopackages/expressions/symbols" - "github.com/tucats/gopackages/util" ) /******************************************\ @@ -33,42 +32,6 @@ func pushScopeByteCode(c *Context, i interface{}) error { return nil } -// popScopeByteCode instruction processor. This drops the current -// symbol table and reverts to its parent table. It also flushes -// any pending "this" stack objects. A chain of receivers -// cannot span a block, so this is a good time to clean up -// any asymmetric pushes. -// -// Note special logic; if this was a package symbol table, take -// time to update the readonly copies of the values in the package -// object itself. -func popScopeByteCode(c *Context, i interface{}) error { - count := 1 - if i != nil { - count = data.Int(i) - } - - for count > 0 { - // See if we're popping off a package table; if so there is work to do to - // copy the values back to the named package object. - if err := c.syncPackageSymbols(); err != nil { - return errors.NewError(err) - } - - // Pop off the symbol table and clear up the "this" stack - if err := c.popSymbolTable(); err != nil { - return errors.NewError(err) - } - - c.thisStack = nil - c.blockDepth-- - - count-- - } - - return nil -} - // symbolCreateByteCode instruction processor. func createAndStoreByteCode(c *Context, i interface{}) error { var value interface{} @@ -105,17 +68,6 @@ func createAndStoreByteCode(c *Context, i interface{}) error { if len(name) > 1 && name[0:1] == defs.ReadonlyVariablePrefix { constantValue := data.DeepCopy(value) - switch a := constantValue.(type) { - case *data.Map: - a.SetReadonly(true) - - case *data.Array: - a.SetReadonly(true) - - case *data.Struct: - a.SetReadonly(true) - } - err = c.setConstant(name, constantValue) } else { err = c.set(name, value) @@ -191,29 +143,3 @@ func constantByteCode(c *Context, i interface{}) error { return err } - -func (c *Context) syncPackageSymbols() error { - // Before we toss away this, check to see if there are package symbols - // that need updating in the package object. - if c.symbols.Parent() != nil && c.symbols.Parent().Package() != "" { - packageSymbols := c.symbols.Parent() - pkgname := c.symbols.Parent().Package() - - if err := c.popSymbolTable(); err != nil { - return errors.NewError(err) - } - - if pkg, ok := c.symbols.Root().Get(pkgname); ok { - if m, ok := pkg.(*data.Package); ok { - for _, k := range packageSymbols.Names() { - if util.HasCapitalizedName(k) { - v, _ := packageSymbols.Get(k) - m.Set(k, v) - } - } - } - } - } - - return nil -} diff --git a/expressions/bytecode/types.go b/expressions/bytecode/types.go index 71c00a6..ba67065 100644 --- a/expressions/bytecode/types.go +++ b/expressions/bytecode/types.go @@ -193,27 +193,6 @@ func coerceByteCode(c *Context, i interface{}) error { switch t.Kind() { case data.MapKind, data.ErrorKind, data.InterfaceKind, data.UndefinedKind: - case data.StructKind: - // Check all the fields in the struct to ensure they exist in the type. - vv := v.(*data.Struct) - for _, k := range vv.FieldNames(false) { - _, e2 := t.Field(k) - if e2 != nil { - return errors.NewError(e2) - } - } - - // Verify that all the fields in the type are found in the object; if not, - // create a zero-value for that type. - for _, k := range t.FieldNames() { - if _, found := vv.Get(k); !found { - ft, _ := t.Field(k) - vv.SetAlways(k, data.InstanceOfType(ft)) - } - } - - v = vv - case data.IntKind: v = data.Int(v) @@ -244,23 +223,7 @@ func coerceByteCode(c *Context, i interface{}) error { return c.push(v) } - var base []interface{} - - if a, ok := v.(*data.Array); ok { - base = a.BaseArray() - } else { - base = v.([]interface{}) - } - - elementType := t.BaseType() - array := data.NewArray(elementType, len(base)) - model := data.InstanceOfType(elementType) - - for i, element := range base { - _ = array.Set(i, data.Coerce(element, model)) - } - - v = array + return errors.ErrInvalidType } return c.push(v) diff --git a/expressions/compiler/compiler.go b/expressions/compiler/compiler.go index a3c9677..5da9a99 100644 --- a/expressions/compiler/compiler.go +++ b/expressions/compiler/compiler.go @@ -1,11 +1,8 @@ package compiler import ( - "sort" "strings" - "sync" - "github.com/google/uuid" "github.com/tucats/gopackages/app-cli/settings" "github.com/tucats/gopackages/app-cli/ui" "github.com/tucats/gopackages/defs" @@ -16,58 +13,22 @@ import ( "github.com/tucats/gopackages/expressions/tokenizer" ) -// requiredPackages is the list of packages that are always imported, regardless -// of user import statements or auto-import profile settings. -var requiredPackages []string = []string{ - "os", - "profile", -} - -// loop is a structure that defines a loop type. -type loop struct { - parent *loop - loopType int - // Fixup locations for break or continue statements in a - // loop. These are the addresses that must be fixed up with - // a target address pointing to exit point or start of the loop. - breaks []int - continues []int -} - // flagSet contains flags that generally identify the state of // the compiler at any given moment. For example, when parsing // something like a switch conditional value, the value cannot // be a struct initializer, though that is allowed elsewhere. type flagSet struct { - disallowStructInits bool - extensionsEnabled bool normalizedIdentifiers bool strictTypes bool - testMode bool - mainSeen bool } // Compiler is a structure defining what we know about the compilation. type Compiler struct { activePackageName string - sourceFile string - id string b *bytecode.ByteCode t *tokenizer.Tokenizer s *symbols.SymbolTable - rootTable *symbols.SymbolTable - loops *loop - coercions []*bytecode.ByteCode - constants []string - deferQueue []int - packages map[string]*data.Package - packageMutex sync.Mutex - types map[string]*data.Type - functionDepth int - blockDepth int - statementCount int flags flagSet // Use to hold parser state flags - exitEnabled bool // Only true in interactive mode } // New creates a new compiler instance. @@ -78,20 +39,13 @@ func New(name string) *Compiler { } cInstance := Compiler{ - b: bytecode.New(name), - t: nil, - s: symbols.NewRootSymbolTable(name), - id: uuid.NewString(), - constants: make([]string, 0), - deferQueue: make([]int, 0), - types: map[string]*data.Type{}, - packageMutex: sync.Mutex{}, - packages: map[string]*data.Package{}, + b: bytecode.New(name), + t: nil, + s: symbols.NewRootSymbolTable(name), flags: flagSet{ normalizedIdentifiers: false, strictTypes: typeChecking, }, - rootTable: &symbols.RootSymbolTable, } return &cInstance @@ -112,47 +66,6 @@ func (c *Compiler) SetNormalizedIdentifiers(flag bool) *Compiler { return c } -// Override the default root symbol table for this compilation. This determines -// where package names are stored/found, for example. This is overridden by the -// web service handlers as they have per-call instances of root. This function -// supports attribute chaining for a compiler instance. -func (c *Compiler) SetRoot(s *symbols.SymbolTable) *Compiler { - c.rootTable = s - c.s.SetParent(s) - - return c -} - -// If set to true, the compiler allows the "exit" statement. This function supports -// attribute chaining for a compiler instance. -func (c *Compiler) ExitEnabled(b bool) *Compiler { - c.exitEnabled = b - - return c -} - -// TesetMode returns whether the compiler is being used under control -// of the Ego "test" command, which has slightly different rules for -// block constructs. -func (c *Compiler) TestMode() bool { - return c.flags.testMode -} - -// MainSeen indicates if a "package main" has been seen in this -// compilation. -func (c *Compiler) MainSeen() bool { - return c.flags.mainSeen -} - -// SetTestMode is used to set the test mode indicator for the compiler. -// This is set to true only when running in Ego "test" mode. This -// function supports attribute chaining for a compiler instance. -func (c *Compiler) SetTestMode(b bool) *Compiler { - c.flags.testMode = b - - return c -} - // Set the given symbol table as the default symbol table for // compilation. This mostly affects how builtins are processed. // This function supports attribute chaining for a compiler instance. @@ -162,14 +75,6 @@ func (c *Compiler) WithSymbols(s *symbols.SymbolTable) *Compiler { return c } -// If set to true, the compiler allows the PRINT, TRY/CATCH, etc. statements. -// This function supports attribute chaining for a compiler instance. -func (c *Compiler) ExtensionsEnabled(b bool) *Compiler { - c.flags.extensionsEnabled = b - - return c -} - // WithTokens supplies the token stream to a compiler. This function supports // attribute chaining for a compiler instance. func (c *Compiler) WithTokens(t *tokenizer.Tokenizer) *Compiler { @@ -186,65 +91,6 @@ func (c *Compiler) WithNormalization(f bool) *Compiler { return c } -// AddBuiltins adds the builtins for the named package (or prebuilt builtins if the package name -// is empty). -func (c *Compiler) AddBuiltins(pkgname string) bool { - added := false - - pkg, _ := bytecode.GetPackage(pkgname) - symV, _ := pkg.Get(data.SymbolsMDKey) - syms := symV.(*symbols.SymbolTable) - - ui.Log(ui.CompilerLogger, "### Adding builtin packages to %s package", pkgname) - - functionNames := make([]string, 0) - for k := range builtins.FunctionDictionary { - functionNames = append(functionNames, k) - } - - sort.Strings(functionNames) - - for _, name := range functionNames { - f := builtins.FunctionDictionary[name] - - if dot := strings.Index(name, "."); dot >= 0 { - f.Pkg = name[:dot] - f.Name = name[dot+1:] - name = f.Name - } else { - f.Name = name - } - - if f.Pkg == pkgname { - if ui.IsActive(ui.CompilerLogger) { - debugName := name - if f.Pkg != "" { - debugName = f.Pkg + "." + name - } - - ui.Log(ui.CompilerLogger, "... processing builtin %s", debugName) - } - - added = true - - if pkgname == "" && c.s != nil { - syms.SetAlways(name, f.F) - pkg.Set(name, f.F) - } else { - if f.F != nil { - syms.SetAlways(name, f.F) - pkg.Set(name, f.F) - } else { - syms.SetAlways(name, f.V) - pkg.Set(name, f.V) - } - } - } - } - - return added -} - // AddStandard adds the package-independent standard functions (like len() or make()) to the // given symbol table. func (c *Compiler) AddStandard(s *symbols.SymbolTable) bool { @@ -264,151 +110,3 @@ func (c *Compiler) AddStandard(s *symbols.SymbolTable) bool { return added } - -// Get retrieves a compile-time symbol value. -func (c *Compiler) Get(name string) (interface{}, bool) { - return c.s.Get(name) -} - -// normalize performs case-normalization based on the current -// compiler settings. -func (c *Compiler) normalize(name string) string { - if c.flags.normalizedIdentifiers { - return strings.ToLower(name) - } - - return name -} - -// normalizeToken performs case-normalization based on the current -// compiler settings for an identifier token. -func (c *Compiler) normalizeToken(t tokenizer.Token) tokenizer.Token { - if t.IsIdentifier() && c.flags.normalizedIdentifiers { - return tokenizer.NewIdentifierToken(strings.ToLower(t.Spelling())) - } - - return t -} - -// SetInteractive indicates if the compilation is happening in interactive -// (i.e. REPL) mode. This function supports attribute chaining for a compiler -// instance. -func (c *Compiler) SetInteractive(b bool) *Compiler { - if b { - c.functionDepth++ - } - - return c -} - -var packageMerge sync.Mutex - -// AddPackageToSymbols adds all the defined packages for this compilation -// to the given symbol table. This function supports attribute chaining -// for a compiler instance. -func (c *Compiler) AddPackageToSymbols(s *symbols.SymbolTable) *Compiler { - ui.Log(ui.CompilerLogger, "Adding compiler packages to %s(%v)", s.Name, s.ID()) - packageMerge.Lock() - defer packageMerge.Unlock() - - for packageName, packageDictionary := range c.packages { - // Skip over any metadata - if strings.HasPrefix(packageName, data.MetadataPrefix) { - continue - } - - m := data.NewPackage(packageName) - - keys := packageDictionary.Keys() - if len(keys) == 0 { - continue - } - - for _, k := range keys { - v, _ := packageDictionary.Get(k) - // Do we already have a package of this name defined? - _, found := s.Get(k) - if found { - ui.Log(ui.CompilerLogger, "Duplicate package %s already in table", k) - } - - // If the package name is empty, we add the individual items - if packageName == "" { - _ = s.SetConstant(k, v) - } else { - // Otherwise, copy the entire map - m.Set(k, v) - } - } - // Make sure the package is marked as readonly so the user can't modify - // any function definitions, etc. that are built in. - m.Set(data.TypeMDKey, data.PackageType(packageName)) - m.Set(data.ReadonlyMDKey, true) - - if packageName != "" { - s.SetAlways(packageName, m) - } - } - - return c -} - -// isStatementEnd returns true when the next token is -// the end-of-statement boundary. -func (c *Compiler) isStatementEnd() bool { - next := c.t.Peek(1) - - return tokenizer.InList(next, tokenizer.EndOfTokens, tokenizer.SemicolonToken, tokenizer.BlockEndToken) -} - -// Symbols returns the symbol table map from compilation. -func (c *Compiler) Symbols() *symbols.SymbolTable { - return c.s -} - -// Clone makes a new copy of the current compiler. The withLock flag -// indicates if the clone should respect symbol table locking. This -// function supports attribute chaining for a compiler instance. -func (c *Compiler) Clone(withLock bool) *Compiler { - cx := Compiler{ - activePackageName: c.activePackageName, - sourceFile: c.sourceFile, - b: c.b, - t: c.t, - s: c.s.Clone(withLock), - rootTable: c.s.Clone(withLock), - coercions: c.coercions, - constants: c.constants, - packageMutex: sync.Mutex{}, - deferQueue: []int{}, - flags: flagSet{ - normalizedIdentifiers: c.flags.normalizedIdentifiers, - extensionsEnabled: c.flags.extensionsEnabled, - }, - exitEnabled: c.exitEnabled, - } - - packages := map[string]*data.Package{} - - c.packageMutex.Lock() - defer c.packageMutex.Unlock() - - for n, m := range c.packages { - packageDef := data.NewPackage(n) - - keys := m.Keys() - for _, k := range keys { - v, _ := m.Get(k) - packageDef.Set(k, v) - } - - packages[n] = packageDef - } - - // Put the newly created data in the copy of the compiler, with - // it's own mutex - cx.packageMutex = sync.Mutex{} - cx.packages = packages - - return &cx -} diff --git a/expressions/compiler/errors.go b/expressions/compiler/errors.go index 151a6bc..c5a4b60 100644 --- a/expressions/compiler/errors.go +++ b/expressions/compiler/errors.go @@ -26,8 +26,6 @@ func (c *Compiler) error(err error, args ...interface{}) *errors.Error { if c.activePackageName != "" { e = e.In(c.activePackageName) - } else if c.sourceFile != "" { - e = e.In(c.sourceFile) } // Get the context info if possible. diff --git a/expressions/compiler/expr_atom.go b/expressions/compiler/expr_atom.go index 6b52259..56f06a8 100644 --- a/expressions/compiler/expr_atom.go +++ b/expressions/compiler/expr_atom.go @@ -8,20 +8,12 @@ import ( "github.com/tucats/gopackages/defs" "github.com/tucats/gopackages/errors" "github.com/tucats/gopackages/expressions/bytecode" - "github.com/tucats/gopackages/expressions/data" "github.com/tucats/gopackages/expressions/tokenizer" ) func (c *Compiler) expressionAtom() error { t := c.t.Peek(1) - // Is it a short-form try/catch? - if t == tokenizer.OptionalToken { - c.t.Advance(1) - - return c.optional() - } - // Is it a binary constant? If so, convert to decimal. text := t.Spelling() if strings.HasPrefix(strings.ToLower(text), "0b") { @@ -52,61 +44,6 @@ func (c *Compiler) expressionAtom() error { return nil } - // Is an empty struct? - if t == tokenizer.EmptyInitializerToken { - c.t.Advance(1) - c.b.Emit(bytecode.Push, data.NewStruct(data.StructType).SetStatic(false)) - - return nil - } - - // Is this address-of? - if t == tokenizer.AddressToken { - c.t.Advance(1) - - // If it's address of a symbol, short-circuit that - if c.t.Peek(1).IsIdentifier() { - c.b.Emit(bytecode.AddressOf, c.t.Next()) - } else { - // Address of an expression requires creating a temp symbol - err := c.expressionAtom() - if err != nil { - return err - } - - tempName := data.GenerateName() - - c.b.Emit(bytecode.StoreAlways, tempName) - c.b.Emit(bytecode.AddressOf, tempName) - } - - return nil - } - - // Is this dereference? - if t == tokenizer.PointerToken { - c.t.Advance(1) - - // If it's dereference of a symbol, short-circuit that - if c.t.Peek(1).IsIdentifier() { - name := c.t.Next() - c.b.Emit(bytecode.DeRef, name) - } else { - // Dereference of an expression requires creating a temp symbol - err := c.expressionAtom() - if err != nil { - return err - } - - tempName := data.GenerateName() - - c.b.Emit(bytecode.StoreAlways, tempName) - c.b.Emit(bytecode.DeRef, tempName) - } - - return nil - } - // Is this a parenthesis expression? if t == tokenizer.StartOfListToken { c.t.Advance(1) @@ -123,22 +60,6 @@ func (c *Compiler) expressionAtom() error { return nil } - // Is this an array constant? - if t == tokenizer.StartOfArrayToken && c.t.Peek(2) != tokenizer.EndOfArrayToken { - return c.parseArray() - } - - // Is it a map constant? - if t == tokenizer.DataBeginToken { - return c.parseStruct() - } - - if t == tokenizer.StructToken && c.t.Peek(2) == tokenizer.DataBeginToken { - c.t.Advance(1) - - return c.parseStruct() - } - // If the token is a number, convert it if t.IsClass(tokenizer.IntegerTokenClass) { if i, err := strconv.ParseInt(text, 10, 32); err == nil { @@ -181,94 +102,10 @@ func (c *Compiler) expressionAtom() error { return nil } - // Is it a type cast? - if c.t.Peek(2) == tokenizer.StartOfListToken { - mark := c.t.Mark() - - if typeSpec, err := c.parseType("", true); err == nil { - if c.t.IsNext(tokenizer.StartOfListToken) { // Skip the parentheses - b, err := c.Expression() - if err == nil { - for c.t.IsNext(tokenizer.CommaToken) { - b2, e2 := c.Expression() - if e2 == nil { - b.Append(b2) - } else { - err = e2 - - break - } - } - } - - if err == nil && c.t.Peek(1) == tokenizer.EndOfListToken { - c.t.Next() - c.b.Emit(bytecode.Push, typeSpec) - c.b.Append(b) - c.b.Emit(bytecode.Call, 1) - - return err - } - } - } - - c.t.Set(mark) - } - - // Watch out for function calls here. - isPanic := c.flags.extensionsEnabled && (c.t.Peek(1) == tokenizer.PanicToken) - if !isPanic && c.t.Peek(2) != tokenizer.StartOfListToken { - marker := c.t.Mark() - - if typeSpec, err := c.parseType("", true); err == nil { - // Is there an initial value for the type? - if c.t.Peek(1) == tokenizer.DataBeginToken { - err = c.compileInitializer(typeSpec) - - return err - } - - if c.t.IsNext(tokenizer.EmptyInitializerToken) { - c.b.Emit(bytecode.Load, "$new") - c.b.Emit(bytecode.Push, typeSpec) - c.b.Emit(bytecode.Call, 1) - } else { - c.b.Emit(bytecode.Push, typeSpec) - } - - return nil - } - - c.t.Set(marker) - } - // Is it just a symbol needing a load? if t.IsIdentifier() { - // Check for auto-increment or decrement - autoMode := bytecode.NoOperation - - if c.t.Peek(2) == tokenizer.IncrementToken { - autoMode = bytecode.Add - } - - if c.t.Peek(2) == tokenizer.DecrementToken { - autoMode = bytecode.Sub - } - - // If language extensions are supported and this is an auto-increment - // or decrement operation, do it now. The modification is applied after - // the value is read; i.e. the atom is the pre-modified value. - if c.flags.extensionsEnabled && (autoMode != bytecode.NoOperation) { - c.b.Emit(bytecode.Load, t) - c.b.Emit(bytecode.Dup) - c.b.Emit(bytecode.Push, 1) - c.b.Emit(autoMode) - c.b.Emit(bytecode.Store, t) - c.t.Advance(2) - } else { - c.b.Emit(bytecode.Load, t) - c.t.Advance(1) - } + c.b.Emit(bytecode.Load, t) + c.t.Advance(1) return nil } @@ -276,245 +113,3 @@ func (c *Compiler) expressionAtom() error { // Not something we know what to do with... return c.error(errors.ErrUnexpectedToken, t) } - -func (c *Compiler) parseArray() error { - var err error - - var listTerminator = tokenizer.EmptyToken - - // Lets see if this is a type name. Remember where - // we came from, and back up over the previous "[" - // already parsed in the expression atom. - marker := c.t.Mark() - - kind, err := c.parseTypeSpec() - if err != nil { - return err - } - - if !kind.IsUndefined() { - if !kind.IsArray() { - return c.error(errors.ErrInvalidTypeName) - } - - // Is it an empty declaration, such as []int{} ? - if c.t.IsNext(tokenizer.EmptyInitializerToken) { - c.b.Emit(bytecode.Array, 0, kind) - - return nil - } - - // There better be at least the start of an initialization block then. - if !c.t.IsNext(tokenizer.DataBeginToken) { - return c.error(errors.ErrMissingBlock) - } - - listTerminator = tokenizer.DataEndToken - } else { - c.t.Set(marker) - - if c.t.Peek(1) == tokenizer.StartOfListToken { - listTerminator = tokenizer.EndOfListToken - } else { - if c.t.Peek(1) == tokenizer.StartOfArrayToken { - listTerminator = tokenizer.EndOfArrayToken - } - } - - c.t.Advance(1) - - // Let's experimentally see if this is a range constant expression. This can be - // of the form [start:end] which creates an array of integers between the start - // and end values (inclusive). It can also be of the form [:end] which assumes - // a start number of 1. - t1 := 1 - - var e2 error - - if c.t.Peek(1) == tokenizer.ColonToken { - err = nil - - c.t.Advance(-1) - } else { - t1, e2 = strconv.Atoi(c.t.PeekText(1)) - if e2 != nil { - err = errors.NewError(e2) - } - } - - if err == nil { - if c.t.Peek(2) == tokenizer.ColonToken { - t2, err := strconv.Atoi(c.t.PeekText(3)) - if err == nil { - c.t.Advance(3) - - count := t2 - t1 + 1 - if count < 0 { - count = (-count) + 2 - - for n := t1; n >= t2; n = n - 1 { - c.b.Emit(bytecode.Push, n) - } - } else { - for n := t1; n <= t2; n = n + 1 { - c.b.Emit(bytecode.Push, n) - } - } - - c.b.Emit(bytecode.Array, count) - - if !c.t.IsNext(tokenizer.EndOfArrayToken) { - return c.error(errors.ErrInvalidRange) - } - - return nil - } - } - } - } - - if listTerminator == tokenizer.EmptyToken { - return nil - } - - count := 0 - - for c.t.Peek(1) != listTerminator { - err := c.conditional() - if err != nil { - return err - } - - // If this is an array of a specific type, check to see - // if the previous value was a constant. If it wasn't, or - // was of the wrong type, emit a coerce... - if !kind.IsUndefined() { - if c.b.NeedsCoerce(kind) { - c.b.Emit(bytecode.Coerce, kind) - } - } - - count = count + 1 - - if c.t.AtEnd() { - break - } - - if c.t.Peek(1) == listTerminator { - break - } - - if c.t.Peek(1) != tokenizer.CommaToken { - return c.error(errors.ErrInvalidList) - } - - c.t.Advance(1) - } - - if !kind.IsUndefined() { - c.b.Emit(bytecode.Array, count, kind) - } else { - c.b.Emit(bytecode.Array, count) - } - - c.t.Advance(1) - - return nil -} - -func (c *Compiler) parseStruct() error { - var listTerminator = tokenizer.DataEndToken - - var err error - - c.t.Advance(1) - - count := 0 - - for c.t.Peek(1) != listTerminator { - // First element: name - name := c.t.Next() - if !name.IsString() && !name.IsIdentifier() { - return c.error(errors.ErrInvalidSymbolName, name) - } - - name = c.normalizeToken(name) - - // Second element: colon - if c.t.Next() != tokenizer.ColonToken { - return c.error(errors.ErrMissingColon) - } - - // Third element: value, which is emitted. - err := c.conditional() - if err != nil { - return err - } - - // Now write the name as a string. - c.b.Emit(bytecode.Push, name) - - count = count + 1 - - if c.t.AtEnd() { - break - } - - if c.t.Peek(1) == listTerminator { - break - } - - if c.t.Peek(1) != tokenizer.CommaToken { - return c.error(errors.ErrInvalidList) - } - - c.t.Advance(1) - } - - c.b.Emit(bytecode.Struct, count) - c.t.Advance(1) - - if err != nil { - err = errors.NewError(err) - } - - return err -} - -// Handle the ? optional operation. This precedes an expression -// element. If the element causes an error then a default value is -// provided following a ":" operator. This only works for specific -// errors such as a nil object reference, invalid type, unknown -// structure member, or divide-by-zero. -// -// This is only supported when extensions are enabled. -func (c *Compiler) optional() error { - if !c.flags.extensionsEnabled { - return c.error(errors.ErrUnexpectedToken).Context("?") - } - - catch := c.b.Mark() - c.b.Emit(bytecode.Try) - - err := c.unary() - if err != nil { - return err - } - - toEnd := c.b.Mark() - c.b.Emit(bytecode.Branch) - _ = c.b.SetAddressHere(catch) - - if !c.t.IsNext(tokenizer.ColonToken) { - return c.error(errors.ErrMissingCatch) - } - - err = c.unary() - if err != nil { - return err - } - - _ = c.b.SetAddressHere(toEnd) - - return nil -} diff --git a/expressions/compiler/expr_condiitional.go b/expressions/compiler/expr_condiitional.go index 835f232..e9b6a44 100644 --- a/expressions/compiler/expr_condiitional.go +++ b/expressions/compiler/expr_condiitional.go @@ -18,7 +18,7 @@ func (c *Compiler) conditional() error { // If this is not a conditional, we're done. Conditionals // are only permitted when extensions are enabled. - if c.t.AtEnd() || !c.flags.extensionsEnabled || c.t.Peek(1) != tokenizer.OptionalToken { + if c.t.AtEnd() || c.t.Peek(1) != tokenizer.OptionalToken { return nil } diff --git a/expressions/compiler/expr_reference.go b/expressions/compiler/expr_reference.go index 1d3f2c6..361a654 100644 --- a/expressions/compiler/expr_reference.go +++ b/expressions/compiler/expr_reference.go @@ -23,12 +23,6 @@ func (c *Compiler) reference() error { switch op { // Structure initialization case tokenizer.DataBeginToken: - // If this is during switch statement processing, it can't be - // a structure initialization. - if c.flags.disallowStructInits { - return nil - } - name := c.t.Peek(2) colon := c.t.Peek(3) diff --git a/expressions/compiler/expression.go b/expressions/compiler/expression.go index 11b90e1..064ee45 100644 --- a/expressions/compiler/expression.go +++ b/expressions/compiler/expression.go @@ -24,8 +24,6 @@ func (c *Compiler) Expression() (*bytecode.ByteCode, error) { cx.t = c.t cx.flags = c.flags cx.b = bytecode.New("subexpression") - cx.types = c.types - cx.sourceFile = c.sourceFile err := cx.conditional() if err == nil { diff --git a/expressions/compiler/initializer.go b/expressions/compiler/initializer.go deleted file mode 100644 index f7d6eab..0000000 --- a/expressions/compiler/initializer.go +++ /dev/null @@ -1,150 +0,0 @@ -package compiler - -import ( - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/bytecode" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/tokenizer" -) - -// Compile an initializer, given a type definition. This can be a literal -// initializer in braces or a simple value. -func (c *Compiler) compileInitializer(t *data.Type) error { - if !c.t.IsNext(tokenizer.DataBeginToken) { - // It's not an initializer constant, but it could still be an expression. Try the - // top-level expression compiler. - return c.conditional() - } - - base := t - if t.IsTypeDefinition() { - base = t.BaseType() - } - - switch base.Kind() { - case data.StructKind: - count := 0 - - for !c.t.IsNext(tokenizer.DataEndToken) { - // Pairs of name:value - name := c.t.Next() - if !name.IsIdentifier() { - return c.error(errors.ErrInvalidSymbolName) - } - - name = tokenizer.NewIdentifierToken(c.normalize(name.Spelling())) - - fieldType, err := base.Field(name.Spelling()) - if err != nil { - return err - } - - if !c.t.IsNext(tokenizer.ColonToken) { - return c.error(errors.ErrMissingColon) - } - - err = c.compileInitializer(fieldType) - if err != nil { - return err - } - - // Now emit the name (names always come first on the stack) - c.b.Emit(bytecode.Push, name) - - count++ - - if c.t.IsNext(tokenizer.DataEndToken) { - break - } - - if !c.t.IsNext(tokenizer.CommaToken) { - return c.error(errors.ErrInvalidList) - } - } - - c.b.Emit(bytecode.Push, t) - c.b.Emit(bytecode.Push, data.TypeMDKey) - c.b.Emit(bytecode.Struct, count+1) - - return nil - - case data.MapKind: - count := 0 - - for !c.t.IsNext(tokenizer.DataEndToken) { - // Pairs of values with a colon between. - err := c.unary() - if err != nil { - return err - } - - c.b.Emit(bytecode.Coerce, base.KeyType()) - - if !c.t.IsNext(tokenizer.ColonToken) { - return c.error(errors.ErrMissingColon) - } - - // Note we compile the value using ourselves, to allow for nested - // type specifications. - err = c.compileInitializer(base.BaseType()) - if err != nil { - return err - } - - count++ - - if c.t.IsNext(tokenizer.DataEndToken) { - break - } - - if !c.t.IsNext(tokenizer.CommaToken) { - return c.error(errors.ErrInvalidList) - } - } - - c.b.Emit(bytecode.Push, base.BaseType()) - c.b.Emit(bytecode.Push, base.KeyType()) - c.b.Emit(bytecode.MakeMap, count) - - return nil - - case data.ArrayKind: - count := 0 - - for !c.t.IsNext(tokenizer.DataEndToken) { - // Values separated by commas. - err := c.compileInitializer(base.BaseType()) - if err != nil { - return err - } - - count++ - - if c.t.IsNext(tokenizer.DataEndToken) { - break - } - - if !c.t.IsNext(tokenizer.CommaToken) { - return c.error(errors.ErrInvalidList) - } - } - - // Emit the type as the final datum, and then emit the instruction - // that will construct the array from the type and stack items. - c.b.Emit(bytecode.Push, base.BaseType()) - c.b.Emit(bytecode.MakeArray, count) - - return nil - - default: - err := c.unary() - if err != nil { - return err - } - - // If we are doing dynamic typing, let's allow a coercion as well. - c.b.Emit(bytecode.Coerce, base) - - return nil - } -} diff --git a/expressions/compiler/type.go b/expressions/compiler/type.go deleted file mode 100644 index df9de7f..0000000 --- a/expressions/compiler/type.go +++ /dev/null @@ -1,194 +0,0 @@ -package compiler - -import ( - "fmt" - - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/bytecode" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/tokenizer" -) - -// compileTypeDefinition compiles a type statement which creates -// a user-defined type specification. -func (c *Compiler) compileTypeDefinition() error { - if c.t.AnyNext(tokenizer.SemicolonToken, tokenizer.EndOfTokens) { - return c.error(errors.ErrMissingType) - } - - name := c.t.Next() - if !name.IsIdentifier() { - return c.error(errors.ErrInvalidSymbolName) - } - - name = c.normalizeToken(name) - - if c.t.AnyNext(tokenizer.SemicolonToken, tokenizer.EndOfTokens) { - return c.error(errors.ErrMissingType) - } - - return c.typeEmitter(name.Spelling()) -} - -// Parses a token stream for a generic type declaration. -func (c *Compiler) typeDeclaration() (interface{}, error) { - theType, err := c.parseType("", false) - if err != nil { - return nil, err - } - - return data.InstanceOfType(theType), nil -} - -func (c *Compiler) parseTypeSpec() (*data.Type, error) { - if c.t.Peek(1) == tokenizer.PointerToken { - c.t.Advance(1) - t, err := c.parseTypeSpec() - - return data.PointerType(t), err - } - - if c.t.Peek(1) == tokenizer.StartOfArrayToken && c.t.Peek(2) == tokenizer.EndOfArrayToken { - c.t.Advance(2) - t, err := c.parseTypeSpec() - - return data.ArrayType(t), err - } - - if c.t.Peek(1) == tokenizer.MapToken && c.t.Peek(2) == tokenizer.StartOfArrayToken { - c.t.Advance(2) - - keyType, err := c.parseTypeSpec() - if err != nil { - return data.UndefinedType, err - } - - c.t.IsNext(tokenizer.EndOfArrayToken) - - valueType, err := c.parseTypeSpec() - if err != nil { - return data.UndefinedType, err - } - - return data.MapType(keyType, valueType), nil - } - - for _, typeDef := range data.TypeDeclarations { - found := true - - if c.t.PeekText(1) == "interface" { - fmt.Println("DEBUG") - } - - for pos, token := range typeDef.Tokens { - eval := c.t.Peek(1 + pos) - if eval.Spelling() != token { - found = false - } - } - - if found { - c.t.Advance(len(typeDef.Tokens)) - - return typeDef.Kind, nil - } - } - - // Is it a type we already know about? - typeName := c.t.Peek(1) - if typeDef, ok := c.types[typeName.Spelling()]; ok { - c.t.Advance(1) - - return typeDef, nil - } - - return data.UndefinedType, nil -} - -// Given a string expression of a type specification, compile it asn return the -// type it represents, and an optional error if it was incorrectly formed. This -// cannot reference user types as they are not visible to this function. -// -// If the string starts with the keyword `type` followed by a type name, then -// the resulting value is a type definition of the given name. -// -// The dependent types map contains types from previous standalone type -// compilations that the current spec is dependent upon. For example, -// a type definition for a sub-structure that is then referenced in -// the current type compilation. Passing nil just means there are no -// dependent types. -func CompileTypeSpec(source string, dependentTypes map[string]*data.Type) (*data.Type, error) { - typeCompiler := New("type compiler") - typeCompiler.t = tokenizer.New(source, true) - - if dependentTypes != nil { - typeCompiler.types = dependentTypes - } - - nameSpelling := "" - // Does it have a type prefix? And is that a package.name style name? - if typeCompiler.t.IsNext(tokenizer.TypeToken) { - name := typeCompiler.t.Next() - if !name.IsIdentifier() { - return data.UndefinedType, errors.ErrInvalidSymbolName.Context(name) - } - - nameSpelling = name.Spelling() - - if typeCompiler.t.IsNext(tokenizer.DotToken) { - name2 := typeCompiler.t.Next() - if !name2.IsIdentifier() { - return data.UndefinedType, errors.ErrInvalidSymbolName.Context(name2) - } - - nameSpelling = nameSpelling + "." + name2.Spelling() - } - } - - t, err := typeCompiler.parseType("", true) - if err == nil && nameSpelling != "" { - t = data.TypeDefinition(nameSpelling, t) - } - - return t, err -} - -// For a given package and type name, get the underlying type. -func (c *Compiler) GetPackageType(packageName, typeName string) (*data.Type, bool) { - if p, found := c.packages[packageName]; found { - if t, found := p.Get(typeName); found { - if theType, ok := t.(*data.Type); ok { - return theType, true - } - } - - // It was a package, but without a package body. Already moved to global storage? - if pkg, found := c.s.Root().Get(packageName); found { - if m, ok := pkg.(*data.Package); ok { - if t, found := m.Get(typeName); found { - if theType, ok := t.(*data.Type); ok { - return theType, true - } - } - - if t, found := m.Get(data.TypeMDKey); found { - if theType, ok := t.(*data.Type); ok { - return theType.BaseType(), true - } - } - } - } - } - - // Is it a previously imported package type? - if bytecode.IsPackage(packageName) { - p, _ := bytecode.GetPackage(packageName) - if tV, ok := p.Get(typeName); ok { - if t, ok := tV.(*data.Type); ok { - return t, true - } - } - } - - return nil, false -} diff --git a/expressions/compiler/typeCompiler.go b/expressions/compiler/typeCompiler.go deleted file mode 100644 index 998904c..0000000 --- a/expressions/compiler/typeCompiler.go +++ /dev/null @@ -1,276 +0,0 @@ -package compiler - -import ( - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/bytecode" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" - "github.com/tucats/gopackages/expressions/tokenizer" -) - -func (c *Compiler) typeEmitter(name string) error { - typeInfo, err := c.typeCompiler(name) - if err == nil { - if typeInfo.Kind() == data.TypeKind { - typeInfo.SetPackage(c.activePackageName) - } - - c.b.Emit(bytecode.Push, typeInfo) - c.b.Emit(bytecode.StoreAlways, name) - } - - return err -} - -func (c *Compiler) typeCompiler(name string) (*data.Type, error) { - if c.t.IsNext(tokenizer.DotToken) { - packageName := name - name = c.t.Next().Spelling() - - if pkgV, found := symbols.RootSymbolTable.Get(packageName); found { - if pkg, ok := pkgV.(*data.Package); ok { - if typeV, found := pkg.Get(name); found { - if t, ok := typeV.(*data.Type); ok { - return t, nil - } - } - } - } - - return nil, c.error(errors.ErrUnknownType).Context(packageName + "." + name) - } - - if _, found := c.types[name]; found { - return data.UndefinedType, c.error(errors.ErrDuplicateTypeName).Context(name) - } - - baseType, err := c.parseType(name, false) - if err != nil { - return data.UndefinedType, err - } - - typeInfo := data.TypeDefinition(name, baseType).SetPackage(c.activePackageName) - c.types[name] = typeInfo - - return typeInfo, nil -} - -func (c *Compiler) parseType(name string, anonymous bool) (*data.Type, error) { - found := false - - if !anonymous { - // Is it a previously defined type? - typeName := c.t.Peek(1) - if typeName.IsIdentifier() { - if t, ok := c.types[typeName.Spelling()]; ok { - c.t.Advance(1) - - return t, nil - } - } - } - - // Is it a known complex type? - - // Base error type - if c.t.Peek(1) == tokenizer.ErrorToken { - c.t.Advance(1) - - return data.ErrorType, nil - } - - // Empty interface - if c.t.Peek(1) == tokenizer.EmptyInterfaceToken { - c.t.Advance(1) - - return data.InterfaceType, nil - } - - if c.t.Peek(1) == tokenizer.InterfaceToken && c.t.Peek(2) == tokenizer.EmptyInitializerToken { - c.t.Advance(2) - - return data.InterfaceType, nil - } - - // Interfaces - if c.t.Peek(1) == tokenizer.InterfaceToken && c.t.Peek(2) == tokenizer.DataBeginToken { - c.t.Advance(2) - - t := data.NewInterfaceType(name) - - return t, nil - } - - // Maps - if c.t.Peek(1) == tokenizer.MapToken && c.t.Peek(2) == tokenizer.StartOfArrayToken { - c.t.Advance(2) - - keyType, err := c.parseType("", false) - if err != nil { - return data.UndefinedType, err - } - - if !c.t.IsNext(tokenizer.EndOfArrayToken) { - return data.UndefinedType, c.error(errors.ErrMissingBracket) - } - - valueType, err := c.parseType("", false) - if err != nil { - return data.UndefinedType, err - } - - return data.MapType(keyType, valueType), nil - } - - // Structures - if c.t.Peek(1) == tokenizer.StructToken && c.t.Peek(2) == tokenizer.DataBeginToken { - t := data.StructureType() - c.t.Advance(2) - - for !c.t.IsNext(tokenizer.DataEndToken) { - _ = c.t.IsNext(tokenizer.SemicolonToken) - - if c.t.IsNext(tokenizer.DataEndToken) { - break - } - - name := c.t.Next() - - if !name.IsIdentifier() { - return data.UndefinedType, c.error(errors.ErrInvalidSymbolName).Context(name) - } - - // Is it a compound name? Could be a package reference to an embedded type. - if c.t.Peek(1) == tokenizer.DotToken && c.t.Peek(2).IsIdentifier() { - packageName := name - name := c.t.Peek(2) - - // Is it a package name? If so, convert it to an actual package type and - // look to see if this is a known type. If so, copy the embedded fields to - // the newly created type we're working on. - if pkgData, found := c.Symbols().Get(packageName.Spelling()); found { - if pkg, ok := pkgData.(*data.Package); ok { - if typeInterface, ok := pkg.Get(name.Spelling()); ok { - if typeData, ok := typeInterface.(*data.Type); ok { - embedType(t, typeData) - - // Skip past the tokens and any optional trailing comma - c.t.Advance(2) - c.t.IsNext(tokenizer.CommaToken) - - continue - } - } - } - } - } - - // Is the name actually a type that we embed? If so, get the base type and iterate - // over its fields, copying them into our current structure definition. - if typeData, found := c.types[name.Spelling()]; found { - embedType(t, typeData) - c.t.IsNext(tokenizer.CommaToken) - - continue - } - - // Nope, parse a type. This can include multiple field names, all - // separated by commas before the actual type definition. So scoop - // up the names first, and then use each one on the list against - // the type definition. - fieldNames := make([]string, 1) - fieldNames[0] = name.Spelling() - - for c.t.IsNext(tokenizer.CommaToken) { - nextField := c.t.Next() - if !nextField.IsIdentifier() { - return data.UndefinedType, c.error(errors.ErrInvalidSymbolName) - } - - fieldNames = append(fieldNames, nextField.Spelling()) - } - - fieldType, err := c.parseType("", false) - if err != nil { - return data.UndefinedType, err - } - - for _, fieldName := range fieldNames { - t.DefineField(fieldName, fieldType) - } - - c.t.IsNext(tokenizer.CommaToken) - } - - return t, nil - } - - // Arrays - if c.t.Peek(1) == tokenizer.StartOfArrayToken && c.t.Peek(2) == tokenizer.EndOfArrayToken { - c.t.Advance(2) - - valueType, err := c.parseType("", false) - if err != nil { - return data.UndefinedType, err - } - - return data.ArrayType(valueType), nil - } - - // Known base types? - for _, typeDeclaration := range data.TypeDeclarations { - found = true - - for idx, token := range typeDeclaration.Tokens { - if c.t.PeekText(1+idx) != token { - found = false - - break - } - } - - if found { - c.t.Advance(len(typeDeclaration.Tokens)) - - return typeDeclaration.Kind, nil - } - } - - // User type known to this compilation? - typeName := c.t.Peek(1) - - if t, found := c.types[typeName.Spelling()]; found { - c.t.Advance(1) - - return t, nil - } - - typeNameSpelling := typeName.Spelling() - - if typeName.IsIdentifier() && c.t.Peek(2) == tokenizer.DotToken && c.t.Peek(3).IsIdentifier() { - packageName := typeName - typeName = c.t.Peek(3) - - if t, found := c.GetPackageType(packageName.Spelling(), typeName.Spelling()); found { - c.t.Advance(3) - - return t, nil - } - - typeNameSpelling = packageName.Spelling() + "." + typeNameSpelling - } - - return data.UndefinedType, c.error(errors.ErrUnknownType, typeNameSpelling) -} - -// Embed a given user-defined type's fields in the current type we are compiling. -func embedType(newType *data.Type, embeddedType *data.Type) { - baseType := embeddedType.BaseType() - if baseType.Kind() == data.StructKind { - fieldNames := baseType.FieldNames() - for _, fieldName := range fieldNames { - fieldType, _ := baseType.Field(fieldName) - newType.DefineField(fieldName, fieldType) - } - } -} diff --git a/expressions/compiler/typeCompiler_test.go b/expressions/compiler/typeCompiler_test.go deleted file mode 100644 index 4999456..0000000 --- a/expressions/compiler/typeCompiler_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package compiler - -import ( - "fmt" - "reflect" - "testing" - - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/tokenizer" -) - -func TestCompiler_typeCompiler(t *testing.T) { - tests := []struct { - name string - arg string - typeName string - want *data.Type - wantErr error - debug bool - }{ - { - name: "map", - arg: "map[string]int", - typeName: "type0", - want: data.TypeDefinition("type0", data.MapType( - data.StringType, - data.IntType)), - wantErr: nil, - }, - { - name: "struct", - arg: "struct{ age int name string }", - typeName: "type1", - want: data.TypeDefinition("type1", data.StructureType( - data.Field{Name: "age", Type: data.IntType}, - data.Field{Name: "name", Type: data.StringType}, - )), - wantErr: nil, - }, - { - name: "int", - arg: "int", - typeName: "type2", - want: data.TypeDefinition("type2", data.IntType), - wantErr: nil, - }, - - // TODO: Add test cases. - } - - c := New("unit test") - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c.t = tokenizer.New(tt.arg, true) - - if tt.debug { - fmt.Println("DEBUG") - } - - got, err := c.typeCompiler(tt.typeName) - if err != tt.wantErr { - t.Errorf("Compiler.typeCompiler() Unexpected error condition %v", err) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Compiler.typeCompiler() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/expressions/compiler/type_test.go b/expressions/compiler/type_test.go deleted file mode 100644 index f7ded84..0000000 --- a/expressions/compiler/type_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package compiler - -import ( - "reflect" - "testing" - - "github.com/tucats/gopackages/expressions/data" -) - -func TestCompileTypeSpec(t *testing.T) { - tests := []struct { - name string - want *data.Type - }{ - { - name: "int", - want: data.IntType, - }, - { - name: "float64", - want: data.Float64Type, - }, - { - name: "string", - want: data.StringType, - }, - { - name: "*int", - want: data.PointerType(data.IntType), - }, - { - name: "[]string", - want: data.ArrayType(data.StringType), - }, - { - name: "struct { name int }", - want: data.StructureType(data.Field{Name: "name", Type: data.IntType}), - }, - { - name: "struct { name string, age int }", - want: data.StructureType( - data.Field{Name: "name", Type: data.StringType}, - data.Field{Name: "age", Type: data.IntType}, - ), - }, - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := CompileTypeSpec(tt.name, nil) - if err != nil { - t.Errorf("CompileTypeSpec() error %v", err) - } else { - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("CompileTypeSpec() got = %v, want %v", got, tt.want) - } - } - }) - } -} diff --git a/expressions/compiler/utility.go b/expressions/compiler/utility.go deleted file mode 100644 index a7c56b6..0000000 --- a/expressions/compiler/utility.go +++ /dev/null @@ -1,14 +0,0 @@ -package compiler - -import ( - "github.com/google/uuid" -) - -// MakeSymbol creates a unique symbol name for use -// as a temporary variable, etc. during compilation. -func MakeSymbol() string { - - x := uuid.New().String() - - return "#" + x[28:] -} diff --git a/expressions/data/arrays.go b/expressions/data/arrays.go deleted file mode 100644 index 51b02b7..0000000 --- a/expressions/data/arrays.go +++ /dev/null @@ -1,626 +0,0 @@ -package data - -import ( - "encoding/json" - "fmt" - "reflect" - "sort" - "strings" - - "github.com/tucats/gopackages/errors" -) - -// Array is the representation in native code of an Ego array. This includes -// an array of interfaces that contain the actual data items, a base type (which -// may be InterfaceType if the array is untypted) and a counting semaphore used -// to track if the array should be considered writable or not. -type Array struct { - data []interface{} - bytes []byte - valueType *Type - immutable int -} - -// Create a new empty array of the given type and size. The values of the array -// members are all initialized to nil. Note special case for []byte which is stored -// natively so it can be used with native Go methods that expect a byte array. -func NewArray(valueType *Type, size int) *Array { - if valueType.kind == ByteKind { - m := &Array{ - bytes: make([]byte, size), - valueType: valueType, - immutable: 0, - } - - return m - } - - m := &Array{ - data: make([]interface{}, size), - valueType: valueType, - immutable: 0, - } - - return m -} - -// NewArrayFromArray accepts a type and an array of interfaces, and constructs -// an EgoArray that uses the source array as it's base array. Note special -// processing for []byte which results in a native Go []byte array. -func NewArrayFromArray(valueType *Type, source []interface{}) *Array { - if valueType.kind == ArrayKind && valueType.BaseType().kind == ByteKind { - m := &Array{ - bytes: make([]byte, len(source)), - valueType: valueType, - immutable: 0, - } - - for n, v := range source { - m.bytes[n] = Byte(v) - } - - return m - } - - m := &Array{ - data: source, - valueType: valueType, - immutable: 0, - } - - return m -} - -// Make creates a new array patterned off of the type of the receiver array, -// of the given size. Note special handling for []byte types which creates -// a native Go array. -func (a *Array) Make(size int) *Array { - if a.valueType.kind == ArrayKind && a.valueType.BaseType().kind == ByteKind { - m := &Array{ - bytes: make([]byte, size), - valueType: a.valueType, - immutable: 0, - } - - return m - } - - m := &Array{ - data: make([]interface{}, size), - valueType: a.valueType, - immutable: 0, - } - - model := InstanceOfType(a.valueType) - - for index := range m.data { - m.data[index] = model - } - - return m -} - -// DeepEqual is a recursive compare with another Ego array. The recursive -// compare is performed on each member of the array. -func (a *Array) DeepEqual(b *Array) bool { - if a.valueType.IsType(InterfaceType) || b.valueType.IsType(InterfaceType) { - return reflect.DeepEqual(a.data, b.data) - } - - return reflect.DeepEqual(a, b) -} - -// BaseArray returns the underlying native array that contains the individual -// array members. This is needed for things like sort.Slice(). Note that if its -// a []byte type, we must convert the native Go array into an []interface{} -// first... -func (a *Array) BaseArray() []interface{} { - r := a.data - - if a.valueType.kind == ByteKind { - r = make([]interface{}, len(a.bytes)) - - for index := range r { - r[index] = a.bytes[index] - } - } - - return r -} - -// Type returns the base type of the array. -func (a *Array) Type() *Type { - return a.valueType -} - -// Validate checks that all the members of the array are of a given -// type. This is used to validate anonymous arrays for use as a typed -// array. -func (a *Array) Validate(kind *Type) error { - if kind.IsType(InterfaceType) { - return nil - } - - // Special case for []byte, which requires an integer value. For - // all other array types, validate compatible type with existing - // array member - if a.valueType.Kind() == ByteType.kind { - if !kind.IsIntegerType() { - return errors.ErrWrongArrayValueType - } - } else { - for _, v := range a.data { - if !IsType(v, kind) { - return errors.ErrWrongArrayValueType - } - } - } - - return nil -} - -// SetReadonly sets or clears the flag that marks the array as immutable. When -// an array is marked as immutable, it cannot be modified (but can be deleted -// in it's entirety). Note that this function actually uses a semaphore to -// track the state, so there must bre an exact match of calls to SetReadonly(false) -// as there were to SetReadonly(true) to allow modifiations to the array. -func (a *Array) SetReadonly(b bool) *Array { - if b { - a.immutable++ - } else { - a.immutable-- - } - - return a -} - -// Get retrieves a member of the array. If the array index is out-of-bounds -// for the array size, an error is returned. -func (a *Array) Get(i interface{}) (interface{}, error) { - index := getInt(i) - - if a.valueType.Kind() == ByteKind { - if index < 0 || index >= len(a.bytes) { - return nil, errors.ErrArrayBounds - } - - return a.bytes[index], nil - } - - if index < 0 || index >= len(a.data) { - return nil, errors.ErrArrayBounds - } - - return a.data[index], nil -} - -// Len returns the length of the array. -func (a *Array) Len() int { - if a.valueType.Kind() == ByteKind { - return len(a.bytes) - } - - return len(a.data) -} - -// SetType can be called once on an anonymous array (whose members are -// all abstract interfaces). This sets the base type of the array to the -// given type. If the array already has a base type, you cannot set a new -// one. This (along with the Validate() function) can be used to convert -// an anonymous array to a typed array. -func (a *Array) SetType(i *Type) error { - if a.valueType.IsType(InterfaceType) { - a.valueType = i - - return nil - } - - return errors.ErrImmutableArray -} - -// Force the size of the array. Existing values are retained if the -// array grows; existing values are truncated if the size is reduced. -func (a *Array) SetSize(size int) *Array { - if size < 0 { - size = 0 - } - - // If we are making it smaller, just convert the array to a slice of itself. - // If we hae to expand it, append empty items to the array. Note that if the - // type is []byte, we operate on the native Go []byte array instead. - if a.valueType.Kind() == ByteKind { - if size < len(a.bytes) { - a.bytes = a.bytes[:size] - } else { - a.bytes = append(a.bytes, make([]byte, size-len(a.data))...) - } - } - - if size < len(a.data) { - a.data = a.data[:size] - } else { - a.data = append(a.data, make([]interface{}, size-len(a.data))...) - } - - return a -} - -// Set stores a value in the array. The array must not be set to immutable. -// The array index must be within the size of the array. If the array is a -// typed array, the type must match the array type. The value can handle -// conversion of integer and float types to fit the target array base type. -func (a *Array) Set(i interface{}, value interface{}) error { - v := value - - if a.immutable > 0 { - return errors.ErrImmutableArray - } - - index := getInt(i) - - // If it's a []byte array, use the native byte array, else use - // the Ego array. - if a.valueType.Kind() == ByteKind { - if index < 0 || index >= len(a.bytes) { - return errors.ErrArrayBounds - } - } else { - if index < 0 || index >= len(a.data) { - return errors.ErrArrayBounds - } - } - - // Address float64/int issues before testing the type. - if a.valueType.kind == IntKind { - if x, ok := v.(float64); ok { - v = int(x) - } - } - - if a.valueType.kind == Float64Kind { - if x, ok := v.(float32); ok { - v = float64(x) - } else if x, ok := v.(int); ok { - v = float64(x) - } else if x, ok := v.(int32); ok { - v = float64(x) - } else if x, ok := v.(int64); ok { - v = float64(x) - } - } - - if a.valueType.kind == Float32Kind { - if x, ok := v.(float64); ok { - v = float32(x) - } else if x, ok := v.(int); ok { - v = float32(x) - } else if x, ok := v.(int32); ok { - v = float32(x) - } else if x, ok := v.(int64); ok { - v = float32(x) - } - } - - // Now, ensure it's of the right type for this array. As always, special case - // for []byte arrays. - if a.valueType.Kind() == ByteKind && !TypeOf(v).IsIntegerType() { - return errors.ErrWrongArrayValueType - } - - if a.valueType.Kind() == ByteKind { - i := Int32(value) - a.bytes[index] = byte(i) - } else { - a.data[index] = v - } - - return nil -} - -// Simplified Set() that does no type checking. Used internally to -// load values into an array that is known to be of the correct -// kind. -func (a *Array) SetAlways(i interface{}, value interface{}) *Array { - if a.immutable > 0 { - return a - } - - index := getInt(i) - if index < 0 || index >= len(a.data) { - return a - } - - if a.valueType.Kind() == ByteKind { - a.bytes[index] = Byte(value) - } else { - a.data[index] = value - } - - return a -} - -// Generate a type description string for this array. -func (a *Array) TypeString() string { - return fmt.Sprintf("[]%s", a.valueType) -} - -// Make a string representation of the array suitable for display. -// This is called when you use fmt.Printf with the "%v" operator, -// for example. -func (a *Array) String() string { - var b strings.Builder - - b.WriteString("[") - - isInterface := a.valueType.IsInterface() - - if a.valueType.Kind() == ByteType.kind { - for i, element := range a.bytes { - if i > 0 { - b.WriteString(", ") - } - - b.WriteString(Format(element)) - } - } else { - for i, element := range a.data { - if i > 0 { - b.WriteString(", ") - } - - if isInterface { - b.WriteString(FormatWithType(element)) - } else { - b.WriteString(Format(element)) - } - } - } - - b.WriteString("]") - - return b.String() -} - -// Fetach a slice of the underlying array and return it as an array of interfaces. -// This can't be used directly as a new array, but can be used to create a new -// array. -func (a *Array) GetSlice(first, last int) ([]interface{}, error) { - if first < 0 || last < 0 || first > len(a.data) || last > len(a.data) { - return nil, errors.ErrArrayBounds - } - - // If it's a []byte we must build an Ego slide from the native bytes. - if a.valueType.Kind() == ByteType.kind { - slice := a.bytes[first:last] - - r := make([]interface{}, len(slice)) - for index := range r { - r[index] = slice[index] - } - - return r, nil - } - - return a.data[first:last], nil -} - -// Fetach a slice of the underlying array and return it as an array of interfaces. -// This can't be used directly as a new array, but can be used to create a new -// array. -func (a *Array) GetSliceAsArray(first, last int) (*Array, error) { - if first < 0 || last < first || first > len(a.data) || last > len(a.data) { - return nil, errors.ErrArrayBounds - } - - slice, err := a.GetSlice(first, last) - if err != nil { - return nil, err - } - - r := NewArrayFromArray(a.valueType, slice) - - return r, nil -} - -// Append an item to the array. If the item being appended is an array itself, -// we append the elements of the array. -func (a *Array) Append(i interface{}) *Array { - if i == nil { - return a - } - - if a.valueType.Kind() == ByteKind { - // If the value is already a byte array, then we just move it into our - // byte array. - if ba, ok := i.([]byte); ok { - if len(a.bytes) == 0 { - a.bytes = make([]byte, len(ba)) - copy(a.bytes, ba) - } else { - a.bytes = append(a.bytes, ba...) - } - } else { - // Otherwise, append the value to the array... - v := byte(Int32(i)) - a.bytes = append(a.bytes, v) - } - } else { - switch v := i.(type) { - case *Array: - a.data = append(a.data, v.data...) - - default: - a.data = append(a.data, v) - } - } - - return a -} - -// GetBytes returns the native byte array for this array, or nil if this -// is not a byte array. -func (a *Array) GetBytes() []byte { - return a.bytes -} - -// getInt is a helper function that is used to retrieve an int value from -// an abstract interface, regardless of the interface type. It returns -1 -// for any interface type that can't be converted to an int value. This is -// used to retrieve array index parameters. -func getInt(i interface{}) int { - switch v := i.(type) { - case byte: - return int(v) - case int32: - return int(v) - case int: - return v - case int64: - return int(v) - case float32: - return int(v) - case float64: - return int(v) - default: - return -1 - } -} - -// Delete removes an item from the array by index number. The index -// must be a valid array index. The return value is nil if no error -// occurs, else an error if the index is out-of-bounds or the array -// is marked as immutable. -func (a *Array) Delete(i int) error { - if i >= len(a.data) || i < 0 { - return errors.ErrArrayBounds - } - - if a.immutable != 0 { - return errors.ErrImmutableArray - } - - if a.valueType.Kind() == ByteType.kind { - a.bytes = append(a.bytes[:i], a.bytes[i+1:]...) - } else { - a.data = append(a.data[:i], a.data[i+1:]...) - } - - return nil -} - -// Sort will sort the array into ascending order. It uses either native -// sort functions or the native sort.Slice function to do the sort. This -// can only be performed on an array of scalar types (no structs, arrays, -// or maps). -func (a *Array) Sort() error { - var err error - - switch a.valueType.kind { - case StringType.kind: - stringArray := make([]string, a.Len()) - for i, v := range a.data { - stringArray[i] = String(v) - } - - sort.Strings(stringArray) - - for i, v := range stringArray { - a.data[i] = v - } - - case ByteType.kind: - byteArray := make([]byte, len(a.bytes)) - - copy(byteArray, a.bytes) - sort.Slice(byteArray, func(i, j int) bool { return byteArray[i] < byteArray[j] }) - - a.bytes = byteArray - - case IntType.kind, Int32Type.kind, Int64Type.kind: - integerArray := make([]int64, a.Len()) - for i, v := range a.data { - integerArray[i] = Int64(v) - } - - sort.Slice(integerArray, func(i, j int) bool { return integerArray[i] < integerArray[j] }) - - for i, v := range integerArray { - switch a.valueType.kind { - case ByteType.kind: - a.data[i] = byte(v) - - case IntType.kind: - a.data[i] = int(v) - - case Int32Type.kind: - a.data[i] = int32(v) - - case Int64Type.kind: - a.data[i] = v - - default: - return errors.ErrInvalidType.In("Sort") - } - } - - case Float32Type.kind, Float64Type.kind: - floatArray := make([]float64, a.Len()) - for i, v := range a.data { - floatArray[i] = Float64(v) - } - - sort.Float64s(floatArray) - - for i, v := range floatArray { - switch a.valueType.kind { - case Float32Type.kind: - a.data[i] = float32(v) - - case Float64Type.kind: - a.data[i] = v - - default: - return errors.ErrInvalidType.In("Sort") - } - } - - default: - err = errors.ErrArgumentType - } - - return err -} - -// MarshalJSON converts the array representation to valid JSON and returns -// the data as a byte array. -func (a Array) MarshalJSON() ([]byte, error) { - b := strings.Builder{} - b.WriteString("[") - - if a.valueType.Kind() == ByteKind { - for k, v := range a.bytes { - if k > 0 { - b.WriteString(",") - } - - b.WriteString(fmt.Sprintf("%v", v)) - } - } else { - for k, v := range a.data { - if k > 0 { - b.WriteString(",") - } - - jsonBytes, err := json.Marshal(v) - if err != nil { - return nil, errors.NewError(err) - } - - b.WriteString(string(jsonBytes)) - } - } - - b.WriteString("]") - - return []byte(b.String()), nil -} diff --git a/expressions/data/channel.go b/expressions/data/channel.go deleted file mode 100644 index 976745b..0000000 --- a/expressions/data/channel.go +++ /dev/null @@ -1,145 +0,0 @@ -package data - -import ( - "fmt" - "sync" - - "github.com/google/uuid" - "github.com/tucats/gopackages/app-cli/ui" - "github.com/tucats/gopackages/errors" -) - -// Structure of an Ego channel wrapper around Go channels. In addition to -// a native channel object (of type interface{}), it includes a mutex to -// protect threaded access to the structure, as well as info about the -// size and state (open, closed, queue size) of the Ego channel. -type Channel struct { - channel chan interface{} - mutex sync.RWMutex - size int - isOpen bool - count int - id string -} - -// Create a mew instance of an Ego channel. The size passed indicates -// the buffer size, which is 1 unless size is greater than 1, in which -// case it is set to the given size. -func NewChannel(size int) *Channel { - if size < 1 { - size = 1 - } - - c := &Channel{ - isOpen: true, - size: size, - mutex: sync.RWMutex{}, - count: 0, - id: uuid.New().String(), - channel: make(chan interface{}, size), - } - - ui.Log(ui.TraceLogger, "--> Created %s", c.String()) - - return c -} - -// Send transmits an arbitrary data object through the channel, if it -// is open. We must verify that the chanel is open before using it. It -// is important to put the logging message out brefore re-locking the -// channel since c.String needs a read-lock. -func (c *Channel) Send(datum interface{}) error { - if c.IsOpen() { - if ui.IsActive(ui.TraceLogger) { - ui.Log(ui.TraceLogger, "--> Sending on %s", c.String()) - } - - c.channel <- datum - - c.mutex.Lock() - defer c.mutex.Unlock() - - c.count++ - - return nil - } - - return errors.ErrChannelNotOpen -} - -// Receive accepts an arbitrary data object through the channel, waiting -// if there is no information available yet. If it's not open, we also -// check to see if the messages have all been drained by looking at the -// counter. -func (c *Channel) Receive() (interface{}, error) { - ui.Log(ui.TraceLogger, "--> Receiving on %s", c.String()) - - if !c.IsOpen() && c.count == 0 { - return nil, errors.ErrChannelNotOpen - } - - datum := <-c.channel - - c.mutex.Lock() - defer c.mutex.Unlock() - - c.count-- - - return datum, nil -} - -// Return a boolean value indicating if this channel is still open for -// business. -func (c *Channel) IsOpen() bool { - c.mutex.RLock() - defer c.mutex.RUnlock() - - return c.isOpen -} - -// IsEmpty checks to see if a channel has been drained (i.e. it is -// closed and there are no more items). This is used by the len() -// function, for example. -func (c *Channel) IsEmpty() bool { - c.mutex.RLock() - defer c.mutex.RUnlock() - - return !c.isOpen && c.count == 0 -} - -// Close the channel so no more sends are permitted to the channel, and -// the receiver can test for channel completion. Must do the logging -// before taking the exclusive lock so c.String() can work. -func (c *Channel) Close() bool { - if ui.IsActive(ui.TraceLogger) { - ui.Log(ui.TraceLogger, "--> Closing %s", c.String()) - } - - c.mutex.Lock() - defer c.mutex.Unlock() - - close(c.channel) - - wasActive := c.isOpen - c.isOpen = false - - return wasActive -} - -// Generate a human-readlable expression of a channel object. This is -// most often used for debugging, so it includes the UUID of the -// channel object so debugging a program with multiple channels is -// easier. -func (c *Channel) String() string { - c.mutex.RLock() - defer c.mutex.RUnlock() - - state := "open" - - if !c.isOpen { - state = "closed" - } - - return fmt.Sprintf("chan(%s, size %d(%d), id %s)", - state, c.size, c.count, c.id) -} diff --git a/expressions/data/channel_test.go b/expressions/data/channel_test.go deleted file mode 100644 index 6aeb779..0000000 --- a/expressions/data/channel_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package data - -import ( - "testing" -) - -func TestNewChannel(t *testing.T) { - fakeID := "49473e93-9f74-4c88-9234-5e037f2bac13" - - type args struct { - size int - } - - tests := []struct { - name string - args args - want *Channel - }{ - { - name: "single item channel", - args: args{size: 1}, - want: &Channel{ - size: 1, - isOpen: true, - count: 0, - id: fakeID, - }, - }, - { - name: "multi item channel", - args: args{size: 5}, - want: &Channel{ - size: 5, - isOpen: true, - count: 0, - id: fakeID, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := NewChannel(tt.args.size) - // Compare what we can. - match := true - if got.count != tt.want.count || - got.isOpen != tt.want.isOpen || - got.size != tt.want.size { - match = false - } - if !match { - t.Errorf("NewChannel() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/expressions/data/declarations.go b/expressions/data/declarations.go index 0d95790..5df8adf 100644 --- a/expressions/data/declarations.go +++ b/expressions/data/declarations.go @@ -22,7 +22,6 @@ var float64Model float64 = 0.0 var float32Model float32 = 0.0 var boolModel = false var stringModel = "" -var chanModel = NewChannel(1) // These are instances of the zero value of each object, expressed // as an interface{}. @@ -39,81 +38,11 @@ var stringInterface interface{} = "" // This includes _APP_ types and also native types, such as sync.WaitGroup. There // should be a type in InstanceOf to match each of these types. var TypeDeclarations = []TypeDeclaration{ - { - []string{"sync", ".", "WaitGroup"}, - nil, // Model generated in instance-of - WaitGroupType, - }, - { - []string{"*", "sync", ".", "WaitGroup"}, - nil, // Model generated in instance-of - PointerType(WaitGroupType), - }, - { - []string{"sync", ".", "Mutex"}, - nil, // Model generated in instance-of - MutexType, - }, - { - []string{"*", "sync", ".", "Mutex"}, - nil, // Model generated in instance-of - PointerType(MutexType), - }, - { - []string{"chan"}, - chanModel, - ChanType, - }, { []string{ErrorTypeName}, &errors.Error{}, ErrorType, }, - { - []string{"[", "]", ByteTypeName}, - NewArray(ByteType, 0), - ArrayType(ByteType), - }, - { - []string{"[", "]", Int32TypeName}, - NewArray(Int32Type, 0), - ArrayType(Int32Type), - }, - { - []string{"[", "]", IntTypeName}, - NewArray(IntType, 0), - ArrayType(IntType), - }, - { - []string{"[", "]", Int64TypeName}, - NewArray(Int64Type, 0), - ArrayType(Int64Type), - }, - { - []string{"[", "]", BoolTypeName}, - NewArray(BoolType, 0), - ArrayType(BoolType), - }, - { - []string{"[", "]", Float64TypeName}, - NewArray(Float64Type, 0), - ArrayType(Float64Type), - }, - { - []string{"[", "]", Float32TypeName}, - NewArray(Float32Type, 0), - ArrayType(Float32Type), - }, - { - []string{"[", "]", StringTypeName}, - NewArray(StringType, 0), - ArrayType(StringType), - }, - { - []string{"[", "]", InterfaceTypeName}, - NewArray(InterfaceType, 0), - ArrayType(InterfaceType), - }, { []string{BoolTypeName}, boolModel, diff --git a/expressions/data/format.go b/expressions/data/format.go index f7ddc02..33e1bb0 100644 --- a/expressions/data/format.go +++ b/expressions/data/format.go @@ -128,33 +128,9 @@ func Format(element interface{}) string { case *Type: return v.String() + v.FunctionNameList() - // Naked WaitGroup is a model for a type - case sync.WaitGroup: - return "T(sync.WaitGroup)" - // Pointer to WaitGroup is what an _APP_ WaitGroup is - case *sync.WaitGroup: - return "sync.WaitGroup{}" - - // Naked WaitGroup is a model for a type - case sync.Mutex: - return "Mutex type" - - // Pointer to sync.Mutex is what an _APP_ Mutex is - case *sync.Mutex: - return "sync.Mutex{}" - - case **sync.Mutex: - return "*sync.Mutex{}" - case *time.Time: return v.String() - case Channel: - return v.String() - - case *Channel: - return "*" + v.String() - case error: return fmt.Sprintf("%v", v) @@ -192,60 +168,6 @@ func Format(element interface{}) string { case Declaration: return v.String() - case *Package: - var b strings.Builder - - keys := v.Keys() - - b.WriteString("Pkg<") - - b.WriteString(strconv.Quote(v.Name)) - - if v.Builtins { - b.WriteString(", builtins") - } - - if v.Source { - b.WriteString(", source") - } - - if v.HasTypes() { - b.WriteString(", types") - } - - if verbose { - for _, k := range keys { - // Skip over hidden values - if strings.HasPrefix(k, defs.InvisiblePrefix) { - continue - } - - if !hasCapitalizedName(k) { - continue - } - - i, _ := v.Get(k) - - b.WriteRune(' ') - b.WriteString(k) - b.WriteString(": ") - b.WriteString(Format(i)) - } - } - - b.WriteString(">") - - return b.String() - - case *Struct: - return v.String() - - case *Array: - return v.String() - - case *Map: - return v.String() - case *interface{}: if v != nil { vv := *v diff --git a/expressions/data/format_test.go b/expressions/data/format_test.go index ec533ce..47e1fbd 100644 --- a/expressions/data/format_test.go +++ b/expressions/data/format_test.go @@ -23,11 +23,6 @@ func TestFormat(t *testing.T) { arg: StructType, want: "struct", }, - { - name: "array", - arg: NewArrayFromArray(IntType, []interface{}{1, 2, 3}), - want: "[1, 2, 3]", - }, { name: "array type", arg: ArrayType(IntType), diff --git a/expressions/data/instance.go b/expressions/data/instance.go index ecf5aad..95ff29b 100644 --- a/expressions/data/instance.go +++ b/expressions/data/instance.go @@ -8,15 +8,6 @@ import ( // model of that type. This only applies to base types. func InstanceOfType(t *Type) interface{} { switch t.kind { - case StructKind: - return NewStruct(t) - - case MapKind: - return NewMap(t.keyType, t.valueType) - - case ArrayKind: - return NewArray(t.valueType, 0) - case TypeKind: return t.InstanceOf(nil) @@ -59,19 +50,6 @@ func (t Type) InstanceOf(superType *Type) interface{} { case TypeKind: return t.valueType.InstanceOf(&t) - case StructKind: - if superType == nil { - superType = StructType - } - - return NewStruct(superType) - - case ArrayKind: - return NewArray(t.valueType, 0) - - case MapKind: - return NewMap(t.keyType, t.valueType) - case PointerKind: return t.valueType.InstanceOf(nil) diff --git a/expressions/data/interfaces.go b/expressions/data/interfaces.go index 727a97e..a13b4cf 100644 --- a/expressions/data/interfaces.go +++ b/expressions/data/interfaces.go @@ -201,38 +201,6 @@ func DeepCopy(v interface{}) interface{} { case string: return actual - case *Array: - size := actual.Len() - result := NewArray(actual.valueType, size) - - for i := 0; i < size; i++ { - v, _ := actual.Get(i) - _ = result.Set(i, DeepCopy(v)) - } - - return result - - case *Map: - result := NewMap(actual.keyType, actual.elementType) - keys := actual.Keys() - - for _, k := range keys { - v, _, _ := actual.Get(k) - _, _ = result.Set(k, DeepCopy(v)) - } - - return result - - case *Struct: - result := actual.Copy() - result.fields = map[string]interface{}{} - - for k, v := range actual.fields { - result.fields[k] = DeepCopy(v) - } - - return result - default: return nil // Unsupported type, (for example, pointers) } diff --git a/expressions/data/maps.go b/expressions/data/maps.go deleted file mode 100644 index 7dba7eb..0000000 --- a/expressions/data/maps.go +++ /dev/null @@ -1,388 +0,0 @@ -package data - -import ( - "encoding/json" - "fmt" - "reflect" - "sort" - "strings" - "sync" - - "github.com/tucats/gopackages/app-cli/ui" - "github.com/tucats/gopackages/errors" -) - -// Map is a wrapper around a native Go map. The actual map supports interface items -// for both key and value. The wrapper contains additional information about the expected -// types for key and value, as well as a counting semaphore to determine if the map -// should be considered immutable (such as during a for...range loop). -type Map struct { - data map[interface{}]interface{} - keyType *Type - elementType *Type - immutable int - mutex sync.RWMutex -} - -// Generate a new map value. The caller must supply the data type codes for the expected -// key and value types (such as data.StringType or data.FloatType). You can also -// use data.InterfaceType for a type value, which means any type is accepted. The -// result is an initialized map that you can begin to store or read values from. -func NewMap(keyType, valueType *Type) *Map { - return &Map{ - data: map[interface{}]interface{}{}, - keyType: keyType, - elementType: valueType, - immutable: 0, - } -} - -// ValueType returns the integer description of the declared key type for -// this map. -func (m *Map) KeyType() *Type { - return m.keyType -} - -// ElementType returns the integer description of the declared value type for -// this map. -func (m *Map) ElementType() *Type { - return m.elementType -} - -// SetReadonly marks the map as immutable. This is passed in as a boolean -// value (true means immutable). Internally, this is actually a counting -// semaphore, so the calls to SetReadonly to set/clear the state must -// be balanced to prevent having a map that is permanently locked or unlocked. -func (m *Map) SetReadonly(b bool) { - m.mutex.Lock() - defer m.mutex.Unlock() - - if b { - m.immutable++ - } else { - m.immutable-- - } -} - -// Get reads a value from the map. The key value must be compatible with the -// type declaration of the map (no coercion occurs). This returns the actual -// value, or nil if not found. It also returns a flag indicating if the -// interface was found or not (i.e. should the result be considered value). -// Finally, it returns an error code if there is a type mismatch. -func (m *Map) Get(key interface{}) (interface{}, bool, error) { - m.mutex.RLock() - defer m.mutex.RUnlock() - - if IsType(key, m.keyType) { - v, found := m.data[key] - - return v, found, nil - } - - return nil, false, errors.ErrWrongMapKeyType.Context(key) -} - -// Set sets a value in the map. The key value and type value must be compatible -// with the type declaration for the map. Bad type values result in an error. -// The function also returns a boolean indicating if the value replaced an -// existing item or not. -func (m *Map) Set(key interface{}, value interface{}) (bool, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - if m.immutable > 0 { - return false, errors.ErrImmutableMap - } - - if !IsBaseType(key, m.keyType) { - return false, errors.ErrWrongMapKeyType.Context(key) - } - - if !IsBaseType(value, m.elementType) { - return false, errors.ErrWrongMapValueType.Context(value) - } - - _, found := m.data[key] - m.data[key] = value - - return found, nil -} - -// Keys returns the set of keys for the map as an array. If the values are strings, -// ints, or floats they are returned in ascending sorted order. -func (m *Map) Keys() []interface{} { - m.mutex.RLock() - defer m.mutex.RUnlock() - - if m.keyType.IsType(StringType) { - idx := 0 - array := make([]string, len(m.data)) - - for k := range m.data { - array[idx] = String(k) - idx++ - } - - sort.Strings(array) - - result := make([]interface{}, len(array)) - - for i, v := range array { - result[i] = v - } - - return result - } else if m.keyType.IsType(IntType) { - idx := 0 - array := make([]int, len(m.data)) - - sort.Ints(array) - - for k := range m.data { - array[idx] = Int(k) - idx++ - } - - sort.Ints(array) - - result := make([]interface{}, len(array)) - - for i, v := range array { - result[i] = v - } - - return result - } else if m.keyType.IsType(Float64Type) { - idx := 0 - array := make([]float64, len(m.data)) - - sort.Float64s(array) - - for k := range m.data { - array[idx] = Float64(k) - idx++ - } - - sort.Float64s(array) - - result := make([]interface{}, len(array)) - - for i, v := range array { - result[i] = v - } - - return result - } else if m.keyType.IsType(Float32Type) { - idx := 0 - array := make([]float64, len(m.data)) - - for k := range m.data { - array[idx] = Float64(k) - idx++ - } - - sort.Float64s(array) - - result := make([]interface{}, len(array)) - - for i, v := range array { - result[i] = float32(v) - } - - return result - } else { - r := []interface{}{} - for _, k := range m.data { - r = append(r, k) - } - - return r - } -} - -// Delete will delete a given value from the map based on key. The return -// value indicates if the value was found (and therefore deleted) versus -// was not found. -func (m *Map) Delete(key interface{}) (bool, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - if m.immutable > 0 { - return false, errors.ErrImmutableMap - } - - if !IsType(key, m.keyType) { - return false, errors.ErrWrongMapKeyType.Context(key) - } - - _, found, err := m.Get(key) - if err == nil { - delete(m.data, key) - } - - return found, err -} - -// TypeString produces a human-readable string describing the map type in Ego -// native terms. -func (m *Map) TypeString() string { - return fmt.Sprintf("map[%s]%s", m.keyType.String(), m.elementType.String()) -} - -// String displays a simplified formatted string value of a map, using the Ego -// anonymous struct syntax. Key values are not quoted, but data values are if -// they are strings. -func (m *Map) String() string { - m.mutex.RLock() - defer m.mutex.RUnlock() - - var b strings.Builder - - b.WriteString("[") - - for i, k := range m.Keys() { - v, _, _ := m.Get(k) - - if i > 0 { - b.WriteString(", ") - } - - if s, ok := v.(string); ok { - b.WriteString(fmt.Sprintf("%s: \"%s\"", Format(k), s)) - } else { - b.WriteString(fmt.Sprintf("%s: %s", Format(k), Format(v))) - } - } - - b.WriteString("]") - - return b.String() -} - -// Type returns a type descriptor for the current map. -func (m *Map) Type() *Type { - return &Type{ - name: "map", - kind: MapKind, - keyType: m.keyType, - valueType: m.elementType, - } -} - -// Given a map whose keys and values are simple types (string, int, float64, bool), -// create a new EgoMap with the appropriate types, populated with the values from -// the source map. -func NewMapFromMap(sourceMap interface{}) *Map { - valueType := InterfaceType - keyType := InterfaceType - - valueKind := reflect.TypeOf(sourceMap).Elem().Kind() - keyKind := reflect.TypeOf(sourceMap).Key().Kind() - - switch valueKind { - case reflect.Int, reflect.Int32, reflect.Int64: - valueType = IntType - - case reflect.Float64: - valueType = Float64Type - - case reflect.Float32: - valueType = Float32Type - - case reflect.Bool: - valueType = BoolType - - case reflect.String: - valueType = StringType - - case reflect.Map: - valueType = MapType(InterfaceType, InterfaceType) - - case reflect.Array: - valueType = ArrayType(InterfaceType) - } - - switch keyKind { - case reflect.Int, reflect.Int32, reflect.Int64: - keyType = IntType - - case reflect.Float32: - keyType = Float32Type - - case reflect.Float64: - keyType = Float64Type - - case reflect.Bool: - keyType = BoolType - - case reflect.String: - keyType = StringType - } - - result := NewMap(keyType, valueType) - val := reflect.ValueOf(sourceMap) - - for _, key := range val.MapKeys() { - value := val.MapIndex(key) - _, _ = result.Set(key.Interface(), value.Interface()) - } - - return result -} - -// MarshalJSON is a helper function used by the JSON package to marshal -// an Ego map value. This is required to create and maintain the metadata -// for the Ego map in sync with the JSON stream. -func (m *Map) MarshalJSON() ([]byte, error) { - m.mutex.RLock() - defer m.mutex.RUnlock() - - b := strings.Builder{} - b.WriteString("{") - - // Need to use the sorted list of names so results are deterministic, - // as opposed to ranging over the fields directly. - keys := m.Keys() - for i, k := range keys { - if i > 0 { - b.WriteString(",") - } - - v, _, _ := m.Get(k) - key := String(k) - - jsonBytes, err := json.Marshal(v) - if err != nil { - return nil, errors.NewError(err) - } - - b.WriteString(fmt.Sprintf(`"%s":%s`, key, string(jsonBytes))) - } - - b.WriteString("}") - - return []byte(b.String()), nil -} - -// ToMap extracts the Ego map and converts it to a native map. This is needed -// to access an Ego map object from a native Go runtime. Currently this only -// supports making maps with string keys. -func (m *Map) ToMap() map[string]interface{} { - m.mutex.RLock() - defer m.mutex.RUnlock() - - switch TypeOf(m.keyType).kind { - case StringKind: - result := map[string]interface{}{} - - for k, v := range m.data { - result[String(k)] = DeepCopy(v) - } - - return result - } - - ui.Log(ui.InternalLogger, "Attempt to convert unsupported Ego map to native map, with key type %s", TypeOf(m.KeyType)) - - return nil -} diff --git a/expressions/data/maps_test.go b/expressions/data/maps_test.go deleted file mode 100644 index a2cd35c..0000000 --- a/expressions/data/maps_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package data - -import ( - "reflect" - "testing" -) - -func TestNewMapFromMap(t *testing.T) { - type args struct { - v interface{} - } - - tests := []struct { - name string - args args - want *Map - }{ - { - name: "int map", - args: args{v: map[string]int{"tom": 15, "sue": 19}}, - want: &Map{ - keyType: StringType, - elementType: IntType, - immutable: 0, - data: map[interface{}]interface{}{"tom": 15, "sue": 19}, - }, - }, - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewMapFromMap(tt.args.v); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewMapFromMap() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/expressions/data/packages.go b/expressions/data/packages.go deleted file mode 100644 index 84f3bec..0000000 --- a/expressions/data/packages.go +++ /dev/null @@ -1,233 +0,0 @@ -package data - -import ( - "fmt" - "sort" - "sync" - - "github.com/google/uuid" - "github.com/tucats/gopackages/app-cli/ui" -) - -// This describes the items in a package. A package consists of a map of items, -// which may includes constant defintions, type definitions, function bodies, -// or receiver function bodies. It also includes metadata regarding whether it -// has import source, or includes Go-native builtins. -type Package struct { - // Name is the name of the package. This must be a valid Ego identifier string. - Name string - - // ID is the UUID of this package. Each package is given a unique ID on creation, - // to assist in debugging package operations. - ID string - - // Source indicates that this package includes constants, types, or functions - // that were read from source files as part of an "import" operations. - Source bool - - // Builtins is true if the package includes one ore more native (Go) functions. - Builtins bool - - // Types is true if the package includes one ore more type definitions. - Types bool - - // Constants is true if the package includes one or more const declarations. - Constants bool - - // Items contains map of named constants, types, and functions for this package. - items map[string]interface{} -} - -// This mutex protects ALL packages. This serializes package operations across all threads. This -// should only materially affect parallel compilation operations, which will become slightly more -// synchronous. -var packageLock sync.RWMutex - -// NewPackage creates a new, empty package definition. The supplied name must be a valid Ego -// identifier name. The package is assigned a unique UUID at the time of creation that never -// changes for the life of this package object. -func NewPackage(name string) *Package { - pkg := Package{ - Name: name, - ID: uuid.New().String(), - items: map[string]interface{}{}, - } - - return &pkg -} - -// NewPackageFromMap creates a new package, and then populates it using the provided map. If the map -// is a nil value, then an empty package definition is created. -func NewPackageFromMap(name string, items map[string]interface{}) *Package { - if items == nil { - items = map[string]interface{}{} - } - - pkg := &Package{ - Name: name, - ID: uuid.New().String(), - items: items, - } - - for _, v := range items { - updatePackageClassIndicators(pkg, v) - } - - return pkg -} - -// SetBuiltins sets the imported flag for the package. This flag indicates -// that the package includes type, constants, or functions that came from -// a source file that was read as part of an "import" statement. -// -// The function returns the same *Package it received, so this can be -// chained with other "set" functions. -func (p *Package) SetImported(f bool) *Package { - p.Source = f - - return p -} - -// HasTypes returns true if the package contains one ore more Type -// declarations. -func (p *Package) HasTypes() bool { - for _, v := range p.items { - if t, ok := v.(*Type); ok { - if hasCapitalizedName(t.name) { - return true - } - } - } - - return false -} - -// IsEmpty reports if a package is empty. This could be due to a null pointer, uninitialized -// internal hash map, or an empty hash map. -func (p *Package) IsEmpty() bool { - if p == nil { - return true - } - - if p.items == nil { - return true - } - - return len(p.items) == 0 -} - -// String formats the package data as a string value, to support "%v" operations. -func (p *Package) String() string { - return Format(p) -} - -// Delete removes an item from the package. It is not an error if the package did -// not contain the named item. This operation is thread-safe. -func (p *Package) Delete(name string) { - packageLock.Lock() - defer packageLock.Unlock() - - if p.items != nil { - delete(p.items, name) - } -} - -// Keys provides a list of keys for the package as an array of strings. The array will -// be empty if the package pointer is null, the hash map is uninitialized, or the hash -// map is empty. -func (p *Package) Keys() []string { - packageLock.RLock() - defer packageLock.RUnlock() - - keys := make([]string, 0) - - if p != nil && p.items != nil { - for k := range p.items { - keys = append(keys, k) - } - - sort.Strings(keys) - } - - return keys -} - -// Set sets a given value in the package. If the hash map was not yet initialized, -// it is created now before setting the value. -func (p *Package) Set(key string, value interface{}) { - packageLock.Lock() - defer packageLock.Unlock() - - if p.items == nil { - p.items = map[string]interface{}{} - } - - // If we're doing symbol tracing, indicate what we're doing (set vs. update) for the - // given package, key, and value. - if ui.IsActive(ui.SymbolLogger) { - v := Format(value) - action := "set" - - if _, ok := p.items[key]; ok { - action = "update" - } - - ui.Log(ui.SymbolLogger, fmt.Sprintf(" for package %s, %s %s to %#v", p.Name, action, key, v)) - } - - updatePackageClassIndicators(p, value) - - p.items[key] = value -} - -// Get retrieves a value from the package structure by name. It returns the value and -// a boolean value indicating if it was found. The flag is true if the package has been -// initialized, the hash map is initialized, and the named value is found in the hashmap. -func (p *Package) Get(key string) (interface{}, bool) { - packageLock.RLock() - defer packageLock.RUnlock() - - if p.items == nil { - return nil, false - } - - value, found := p.items[key] - - return value, found -} - -// Merge adds any entries from a package to the current package that do not already -// exist. -func (p *Package) Merge(source *Package) *Package { - if source.Builtins { - p.Builtins = true - } - - if source.Source { - p.Source = true - } - - keys := source.Keys() - for _, key := range keys { - if _, found := p.Get(key); !found { - value, _ := source.Get(key) - p.Set(key, value) - ui.Log(ui.CompilerLogger, "... merging key %s from existing package", key) - } - } - - return p -} - -// updatePackageClassIndicators updates the various boolean flags in the package -// based on the type of the value. These flags track whether there are Types, -// Constants, Builtins, or Imports in this package. -func updatePackageClassIndicators(pkg *Package, v interface{}) { - if _, ok := v.(*Type); ok { - pkg.Types = true - } else if _, ok := v.(Immutable); ok { - pkg.Constants = true - } else if _, ok := v.(Function); ok { - pkg.Builtins = true - } -} diff --git a/expressions/data/pointers.go b/expressions/data/pointers.go index 08e6c5a..3d0a21b 100644 --- a/expressions/data/pointers.go +++ b/expressions/data/pointers.go @@ -27,16 +27,6 @@ func AddressOf(v interface{}) (interface{}, error) { return &actual, nil case string: return &actual, nil - case Package: - return &actual, nil - case *Struct: - return &actual, nil - case *Map: - return &actual, nil - case *Array: - return &actual, nil - case *Channel: - return &actual, nil default: return &v, nil } @@ -68,14 +58,6 @@ func Dereference(v interface{}) (interface{}, error) { return *actual, nil case *string: return *actual, nil - case *Package: - return *actual, nil - case **Map: - return *actual, nil - case **Array: - return *actual, nil - case **Channel: - return *actual, nil default: return nil, errors.ErrNotAPointer diff --git a/expressions/data/sanitize.go b/expressions/data/sanitize.go index e97d8de..6c0957d 100644 --- a/expressions/data/sanitize.go +++ b/expressions/data/sanitize.go @@ -4,57 +4,6 @@ import ( "strings" ) -// For any given _APP_ object type, remove any metadata from it -// and return a sanitized version. For scalar types like int, -// bool, or string, there is no operation performed and the -// object is returned unchanged. For Struct and Map types, the -// response is always a map[string]interface{}. For Array types, -// this will always be an []interface{} structure. This can then -// be used serialized to JSON to send HTTP response -// bodies, for example. -func Sanitize(v interface{}) interface{} { - switch v := v.(type) { - case *Array: - return v.data - - case *Struct: - return v.fields - - case *Map: - result := map[string]interface{}{} - keys := v.Keys() - - for _, key := range keys { - if keyString, ok := key.(string); ok { - if strings.HasPrefix(keyString, MetadataPrefix) { - continue - } - } - - value, _, _ := v.Get(key) - result[String(key)] = Sanitize(value) - } - - return result - - case *Package: - result := map[string]interface{}{} - - for _, key := range v.Keys() { - value, _ := v.Get(key) - if !strings.HasPrefix(key, MetadataPrefix) { - result[key] = Sanitize(value) - } - } - - return result - - // For anything else, just return the thing we were given. - default: - return v - } -} - // SanitizeName is used to examine a string that is used as a name (a filename, // a module name, etc.). The function will ensure it has no embedded characters // that would either reformat a string inappropriately -- such as entries in a diff --git a/expressions/data/structs.go b/expressions/data/structs.go deleted file mode 100644 index 2e4ab6b..0000000 --- a/expressions/data/structs.go +++ /dev/null @@ -1,555 +0,0 @@ -package data - -import ( - "encoding/json" - "fmt" - "sort" - "strings" - "sync" - "unicode" - - "github.com/tucats/gopackages/app-cli/ui" - "github.com/tucats/gopackages/defs" - "github.com/tucats/gopackages/errors" -) - -// Struct describes an Ego structure. This includes the associated type -// definition if it is a struct that is part of a type, and the name of -// that type. It also has metadata indicating if the structure's definition -// is static or if fields can be added to it, and if the structure is -// considered read-only. -// -// Access to the struct is thread-safe, and will enforce package namespace -// visibility if the struct is defined in one package but referneced from -// outside that pacikage. -type Struct struct { - typeDef *Type - typeName string - static bool - readonly bool - strictTypeChecks bool - fromBuiltinPackage bool - mutex sync.RWMutex - fields map[string]interface{} -} - -// Create a new Struct of the given type. This can be a type wrapper -// for the struct type, or data.StructType to indicate an anonymous -// struct value. -func NewStruct(t *Type) *Struct { - // If this is a user type, get the base type. - typeName := "" - baseType := t - - for baseType.IsTypeDefinition() { - typeName = t.name - if baseType.pkg != "" { - typeName = baseType.pkg + "." + typeName - } - - // If there are receiver functions in the type definition, - // make them part of this structure as well. - baseType = baseType.BaseType() - if baseType.functions == nil { - baseType.functions = t.functions - } - } - - // It must be a structure - if baseType.kind != StructKind { - return nil - } - - // If there are fields defined, this is static. - static := true - if len(baseType.fields) == 0 { - static = false - } - - // Create the fields structure, and fill it in using the field - // names and the zero-type of each kind - fields := map[string]interface{}{} - - for k, v := range baseType.fields { - fields[k] = InstanceOfType(v) - } - - // Create the structure and pass it back. - result := Struct{ - typeDef: baseType, - static: static, - fields: fields, - typeName: typeName, - } - - return &result -} - -// NewStructFromMap will create an anonymous Struct (with no type), -// and populate the fields and their types and values based on the -// provided map. This is often used within Ego to build a map with -// the information needed in the structure, and then assign the -// associated values in the map to the fields of the structure. -// Note that the only fields that will be defined are the ones -// indicated by the map. -func NewStructFromMap(m map[string]interface{}) *Struct { - t := StructureType() - - if value, ok := m[TypeMDKey]; ok { - t = TypeOf(value) - } else { - for k, v := range m { - t.DefineField(k, TypeOf(v)) - } - } - - static := (len(m) > 0) - if value, ok := m[StaticMDKey]; ok { - static = Bool(value) - } - - readonly := false - if value, ok := m[ReadonlyMDKey]; ok { - readonly = Bool(value) - } - - fields := map[string]interface{}{} - - // Copy all the map items except any metadata items. - for k, v := range m { - if !strings.HasPrefix(k, MetadataPrefix) { - fields[k] = v - } - } - - result := Struct{ - static: static, - typeDef: t, - readonly: readonly, - fields: fields, - } - - return &result -} - -// NewStructOfTypeFromMap creates a new structure of a given type, and -// populates the fields using a map. The map does not have to have a value -// for each field in the new structure (the fields are determined by the -// type information), but the map cannot contain values that do not map -// to a structure field name. -func NewStructOfTypeFromMap(t *Type, m map[string]interface{}) *Struct { - if value, ok := m[TypeMDKey]; ok { - t = TypeOf(value) - } else if t == nil { - t = StructureType() - for k, v := range m { - t.DefineField(k, TypeOf(v)) - } - } - - static := (len(m) > 0) - if value, ok := m[StaticMDKey]; ok { - static = Bool(value) - } - - readonly := false - if value, ok := m[ReadonlyMDKey]; ok { - readonly = Bool(value) - } - - fields := map[string]interface{}{} - - // Populate the map with all the required fields and - // a nil value. - - typeFields := t.fields - if typeFields == nil && t.BaseType() != nil { - typeFields = t.BaseType().fields - } - - for k := range typeFields { - fields[k] = nil - } - - // Copy all the map items except any metadata items. Make - // sure the map value matches the field definition. - for k, v := range m { - if !strings.HasPrefix(k, MetadataPrefix) { - f := t.fields[k] - if f != nil { - v = Coerce(v, InstanceOfType(f)) - } - - fields[k] = v - } - } - - result := Struct{ - static: static, - typeName: t.name, - typeDef: t, - readonly: readonly, - fields: fields, - fromBuiltinPackage: t.pkg != "", - } - - return &result -} - -// FromBuiltinPackage sets the flag in the structure metadata that -// indicates that it should be considered a member of a package. -// This setting is used to enforce package visibility rules. The -// function returns the same pointer that was passed to it, so -// this can be chained with other operations on the structure. -func (s *Struct) FromBuiltinPackage() *Struct { - s.fromBuiltinPackage = true - - return s -} - -// Type returns the Type description of the structure. -func (s *Struct) Type() *Type { - return s.typeDef -} - -// SetStrictTypeChecks enables or disables strict type checking -// for field assignments. This is typically set when -// a structure is created. -func (s *Struct) SetStrictTypeChecks(b bool) *Struct { - s.strictTypeChecks = b - - return s -} - -// SetReadonly marks this structure as readonly. -func (s *Struct) SetReadonly(b bool) *Struct { - s.readonly = b - - return s -} - -// SetStatic indicates if this structure is static. That is, -// once defined, fields cannot be added to the structure. -func (s *Struct) SetStatic(b bool) *Struct { - s.static = b - - return s -} - -// This is used only by the unit testing to explicitly set the type -// of a structure. It changes no data, only updates the type value. -func (s *Struct) AsType(t *Type) *Struct { - s.typeDef = t - s.typeName = t.name - - return s -} - -// GetAlways retrieves a value from the named field. No error checking is done -// to verify that the field exists; if it does not then a nil value is returned. -// This is a short-cut used in runtime code to access well-known fields from -// pre-defined object types, such as a db.Client(). -func (s *Struct) GetAlways(name string) interface{} { - s.mutex.RLock() - defer s.mutex.RUnlock() - - value, ok := s.fields[name] - // If it's not a field, it might be locatable via the typedef's - // declared receiver functions. - if !ok { - value = s.typeDef.functions[name] - } - - return value -} - -// PackageName returns the package in which the structure was -// defined. This is used to compare against the current package -// code being executed to determine if private structure members -// are accessible. -func (s *Struct) PackageName() string { - if s.typeDef.pkg != "" { - return s.typeDef.pkg - } - - parts := strings.Split(s.typeName, ".") - if len(parts) > 1 { - return parts[0] - } - - return "" -} - -// Get retrieves a field from the structure. Note that if the -// structure is a synthetic package type, we only allow access -// to exported names. -func (s *Struct) Get(name string) (interface{}, bool) { - s.mutex.RLock() - defer s.mutex.RUnlock() - - if s.fromBuiltinPackage && !hasCapitalizedName(name) { - return nil, false - } - - value, ok := s.fields[name] - // If it's not a field, it might be locatable via the typedef's - // declared receiver functions. - if !ok { - value, ok = s.typeDef.functions[name] - } - - return value, ok -} - -// ToMap generates a map[string]interface{} from the structure, so -// it's fields can be accessed natively, such as using a structure -// to hold the parameter data for a template evaluation when not in -// strict type checking mode. -func (s *Struct) ToMap() map[string]interface{} { - s.mutex.RLock() - defer s.mutex.RUnlock() - - result := map[string]interface{}{} - - for k, v := range s.fields { - result[k] = DeepCopy(v) - } - - return result -} - -// Store a value in the structure under the given name. This ignores type safety, -// static, or readonly attributes, so be VERY sure the value is the right type! -func (s *Struct) SetAlways(name string, value interface{}) *Struct { - if s == nil { - ui.WriteLog(ui.InfoLogger, "Fatal error - null struct pointer in SetAlways") - - return s - } - - s.mutex.Lock() - defer s.mutex.Unlock() - - s.fields[name] = value - - return s -} - -// Set stores a value in the structure. The structure must not be -// readonly, and the field must not be a readonly field. If strict -// type checking is enabled, the type is validated. -func (s *Struct) Set(name string, value interface{}) error { - if s.readonly { - return errors.ErrReadOnly - } - - s.mutex.Lock() - defer s.mutex.Unlock() - - // Is it a readonly symbol name and it already exists? If so, fail... - if name[0:1] == defs.ReadonlyVariablePrefix { - _, ok := s.fields[name] - if ok { - return errors.ErrReadOnly - } - } - - if s.static { - _, ok := s.fields[name] - if !ok { - return errors.ErrInvalidField - } - } - - if s.typeDef.fields != nil { - if t, ok := s.typeDef.fields[name]; ok { - // Does it have to match already? - if s.strictTypeChecks && !IsType(value, t) { - return errors.ErrInvalidType.Context(TypeOf(value).String()) - } - // Make sure it is compatible with the field type. - value = t.Coerce(value) - } - } - - s.fields[name] = value - - return nil -} - -// Make a copy of the current structure object. The resulting structure -// will be an exact duplicate, but allocated in new storage. -func (s *Struct) Copy() *Struct { - s.mutex.RLock() - defer s.mutex.RUnlock() - - result := NewStructFromMap(s.fields) - result.typeDef = s.typeDef - result.typeName = s.typeName - result.readonly = s.readonly - result.static = s.static - result.strictTypeChecks = s.strictTypeChecks - result.typeDef = s.typeDef - result.typeName = s.typeName - result.fromBuiltinPackage = s.fromBuiltinPackage - - return result -} - -// FieldNames returns an array of strings containing the names of the -// structure fields that can be accessed. The private flag is used to -// indicate if private structures should be visible. When this is false, -// the list of names is filtered to remove any private (lower-case) names. -func (s *Struct) FieldNames(private bool) []string { - s.mutex.RLock() - defer s.mutex.RUnlock() - - keys := make([]string, 0) - - for k := range s.fields { - if !private && s.fromBuiltinPackage && !hasCapitalizedName(k) { - continue - } - - if !strings.HasPrefix(k, MetadataPrefix) { - keys = append(keys, k) - } - } - - sort.Strings(keys) - - return keys -} - -// FieldNamesArray constructs an Ego array of strings containing -// the names of the fields in the structure that can be accessed. -// The private flag is used to -// indicate if private structures should be visible. When this is false, -// the list of names is filtered to remove any private (lower-case) names. -func (s *Struct) FieldNamesArray(private bool) *Array { - keys := s.FieldNames(private) - keyValues := make([]interface{}, len(keys)) - - for i, v := range keys { - keyValues[i] = v - } - - return NewArrayFromArray(StringType, keyValues) -} - -// TypeString generates a string representation fo this current -// Type. If the structure is actually a typed structure, then the -// result is th name of the type. Otherwise it is the full -// type string that enumerates the names of the fields and their -// types. -func (s *Struct) TypeString() string { - if s.typeName != "" { - return s.typeName - } - - if s.typeDef.IsTypeDefinition() { - name := s.typeDef.name - if s.typeDef.pkg != "" { - name = s.typeDef.pkg + "." + s.typeDef.name - } - - return name - } - - return s.typeDef.String() -} - -// String creates a human-readable representation of a structure, showing -// the field names and their values. -func (s *Struct) String() string { - s.mutex.RLock() - defer s.mutex.RUnlock() - - if len(s.fields) == 0 { - return "{}" - } - - keys := make([]string, 0) - b := strings.Builder{} - - if s.typeName != "" { - b.WriteString(s.typeName) - } - - b.WriteString("{ ") - - for k := range s.fields { - if !strings.HasPrefix(k, MetadataPrefix) { - keys = append(keys, k) - } - } - - sort.Strings(keys) - - for i, k := range keys { - if s.fromBuiltinPackage && !hasCapitalizedName(k) { - continue - } - - if i > 0 { - b.WriteString(", ") - } - - v := s.fields[k] - - b.WriteString(k) - b.WriteString(": ") - b.WriteString(Format(v)) - } - - b.WriteString(" }") - - return b.String() -} - -// MarshalJSON is a helper function that assists the json package -// operations that must generate the JSON sequence that represents -// the Ego structure (as opposed to the native Struct object itself). -func (s *Struct) MarshalJSON() ([]byte, error) { - s.mutex.RLock() - defer s.mutex.RUnlock() - - b := strings.Builder{} - b.WriteString("{") - - // Need to use the sorted list of names so results are deterministic, - // as opposed to ranging over the fields directly. - keys := s.FieldNames(false) - for i, k := range keys { - if i > 0 { - b.WriteString(",") - } - - v := s.GetAlways(k) - - jsonBytes, err := json.Marshal(v) - if err != nil { - return nil, errors.NewError(err) - } - - b.WriteString(fmt.Sprintf(`"%s":%s`, k, string(jsonBytes))) - } - - b.WriteString("}") - - return []byte(b.String()), nil -} - -// hasCapitalizedName returns true if the first rune/character of the -// string is considered a capital letter in Unicode. -func hasCapitalizedName(name string) bool { - var firstRune rune - - for _, ch := range name { - firstRune = ch - - break - } - - return unicode.IsUpper(firstRune) -} diff --git a/expressions/data/types.go b/expressions/data/types.go index a45af64..46e8f7e 100644 --- a/expressions/data/types.go +++ b/expressions/data/types.go @@ -854,21 +854,6 @@ func KindOf(i interface{}) int { case string: return StringKind - case Package: - return UndefinedKind - - case *Package: - return PackageKind - - case *Map: - return MapKind - - case *Struct: - return StructKind - - case *Channel: - return PointerKind - default: return InterfaceKind } @@ -982,25 +967,6 @@ func TypeOf(i interface{}) *Type { case *bool: return PointerType(BoolType) - case *Package: - return PointerType(TypeOf(*v)) - - case *Map: - return v.Type() - - case *Struct: - return v.typeDef - - case *Array: - return &Type{ - name: "[]", - kind: ArrayKind, - valueType: v.valueType, - } - - case *Channel: - return PointerType(ChanType) - case *errors.Error, errors.Error: return ErrorType @@ -1023,8 +989,6 @@ func IsType(v interface{}, t *Type) bool { for m := range t.functions { found := true switch mv := v.(type) { - case *Struct: - _, found = mv.Get(m) case *Type: _, found = mv.functions[m] diff --git a/expressions/symbols/copy.go b/expressions/symbols/copy.go index 7749282..396f67e 100644 --- a/expressions/symbols/copy.go +++ b/expressions/symbols/copy.go @@ -2,7 +2,6 @@ package symbols import ( "github.com/google/uuid" - "github.com/tucats/gopackages/expressions/data" ) // Make a copy of the symbol table, retaining the same values @@ -33,22 +32,3 @@ func (s *SymbolTable) Clone(withLock bool) *SymbolTable { return &t } - -// For a given source table, find all the packages in the table and put them -// in the current table. -func (s *SymbolTable) GetPackages(source *SymbolTable) (count int) { - if source == nil { - return - } - - for k, attributes := range source.symbols { - v := source.GetValue(attributes.slot) - if p, ok := v.(*data.Package); ok { - s.SetAlways(k, p) - - count++ - } - } - - return count -} diff --git a/expressions/symbols/format.go b/expressions/symbols/format.go index e65e75b..eedbbd9 100644 --- a/expressions/symbols/format.go +++ b/expressions/symbols/format.go @@ -51,37 +51,6 @@ func (s *SymbolTable) Format(includeBuiltins bool) string { typeString := dt.String() switch actual := v.(type) { - case *data.Map: - typeString = actual.TypeString() - - case *data.Array: - typeString = actual.TypeString() - - case *data.Struct: - typeString = actual.TypeString() - - case *data.Package: - if tsx, ok := actual.Get(data.TypeMDKey); ok { - typeString = data.String(tsx) - } - - hasBuiltins := false - keys := actual.Keys() - - for _, k := range keys { - k2, _ := actual.Get(k) - if _, ok := k2.(func(*SymbolTable, []interface{}) (interface{}, error)); ok { - hasBuiltins = true - omitType = true - } - } - - if hasBuiltins && !includeBuiltins { - omitThisSymbol = true - - continue - } - case func(*SymbolTable, []interface{}) (interface{}, error): if !includeBuiltins { omitThisSymbol = true @@ -166,35 +135,6 @@ func (s *SymbolTable) FormattedData(includeBuiltins bool) [][]string { typeString := dt.String() switch actual := v.(type) { - case *data.Map: - typeString = actual.TypeString() - - case *data.Array: - typeString = actual.TypeString() - - case *data.Struct: - typeString = actual.TypeString() - - case *data.Package: - if tsx, ok := actual.Get(data.TypeMDKey); ok { - typeString = data.String(tsx) - } - - hasBuiltins := false - keys := actual.Keys() - - for _, k := range keys { - k2, _ := actual.Get(k) - if _, ok := k2.(func(*SymbolTable, []interface{}) (interface{}, error)); ok { - hasBuiltins = true - } - } - - if hasBuiltins && !includeBuiltins { - omitThisSymbol = true - - continue - } case func(*SymbolTable, []interface{}) (interface{}, error): if !includeBuiltins { diff --git a/expressions/symbols/symbols.go b/expressions/symbols/symbols.go index 642dffe..e1c517b 100644 --- a/expressions/symbols/symbols.go +++ b/expressions/symbols/symbols.go @@ -426,19 +426,6 @@ func (s *SymbolTable) Set(name string, v interface{}) error { v = data.DeepCopy(v) } - switch actual := v.(type) { - case *data.Array: - actual.SetReadonly(true) - v = actual - - case *data.Map: - actual.SetReadonly(true) - v = actual - - case *data.Struct: - actual.SetReadonly(true) - v = actual - } } // Store the value in the slot, and if it was readonly, write diff --git a/gopackages b/gopackages deleted file mode 100755 index 8604298..0000000 Binary files a/gopackages and /dev/null differ diff --git a/strings/compare.go b/strings/compare.go deleted file mode 100644 index 2f11b89..0000000 --- a/strings/compare.go +++ /dev/null @@ -1,24 +0,0 @@ -package strings - -import ( - "strings" - - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// Wrapper around strings.compare(). -func compare(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - a := data.String(args[0]) - b := data.String(args[1]) - - return strings.Compare(a, b), nil -} - -// Wrapper around strings.equalFold(). -func equalFold(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - a := data.String(args[0]) - b := data.String(args[1]) - - return strings.EqualFold(a, b), nil -} diff --git a/strings/conversion.go b/strings/conversion.go deleted file mode 100644 index 407787d..0000000 --- a/strings/conversion.go +++ /dev/null @@ -1,99 +0,0 @@ -package strings - -import ( - "fmt" - "strings" - - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// format implements the strings.format() function. -func format(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - if len(args) == 0 { - return "", nil - } - - if len(args) == 1 { - return data.String(args[0]), nil - } - - return fmt.Sprintf(data.String(args[0]), args[1:]...), nil -} - -// chars implements the strings.chars() function. This accepts a string -// value and converts it to an array of characters. -func chars(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - count := 0 - - // Count the number of characters in the string. (We can't use len() here - // which onl returns number of bytes) - v := data.String(args[0]) - for i := range v { - count = i + 1 - } - - r := data.NewArray(data.StringType, count) - - for i, ch := range v { - err := r.Set(i, string(ch)) - if err != nil { - return nil, err - } - } - - return r, nil -} - -// extractInts implements the strings.ints() function. This accepts a string -// value and converts it to an array of integer rune values. -func extractInts(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - count := 0 - - // Count the number of characters in the string. (We can't use len() here - // which onl returns number of bytes) - v := data.String(args[0]) - for i := range v { - count = i + 1 - } - - r := data.NewArray(data.IntType, count) - - for i, ch := range v { - err := r.Set(i, int(ch)) - if err != nil { - return nil, err - } - } - - return r, nil -} - -// toString implements the strings.toString() function, which accepts an array -// of items and converts it to a single long string of each item. Normally , this is -// an array of characters. -func toString(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - var b strings.Builder - - for _, v := range args { - switch a := v.(type) { - case string: - b.WriteString(a) - - case byte: - b.WriteRune(rune(a)) - - case int32: - b.WriteRune(a) - - case int: - b.WriteRune(rune(a)) - - default: - return nil, errors.ErrArgumentCount.In("String") - } - } - - return b.String(), nil -} diff --git a/strings/parse.go b/strings/parse.go deleted file mode 100644 index eff24b5..0000000 --- a/strings/parse.go +++ /dev/null @@ -1,43 +0,0 @@ -package strings - -import ( - "strings" - - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" - "github.com/tucats/gopackages/expressions/tokenizer" -) - -// Wrapper around strings.fields(). -func fields(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - a := data.String(args[0]) - - fields := strings.Fields(a) - - result := data.NewArray(data.StringType, len(fields)) - - for idx, f := range fields { - _ = result.Set(idx, f) - } - - return result, nil -} - -// tokenize splits a string into tokens. -func tokenize(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - src := data.String(args[0]) - t := tokenizer.New(src, false) - - r := data.NewArray(data.StringType, len(t.Tokens)) - - var err error - - for i, n := range t.Tokens { - err = r.Set(i, n) - if err != nil { - return nil, err - } - } - - return r, err -} diff --git a/strings/string_test.go b/strings/string_test.go deleted file mode 100644 index e697413..0000000 --- a/strings/string_test.go +++ /dev/null @@ -1,340 +0,0 @@ -package strings - -import ( - "reflect" - "testing" -) - -func TestFunctionLeft(t *testing.T) { - type args struct { - args []interface{} - } - - tests := []struct { - name string - args args - want interface{} - wantErr bool - }{ - { - name: "simple test", - args: args{[]interface{}{"Abraham", 4}}, - want: "Abra", - }, - { - name: "negative length test", - args: args{[]interface{}{"Abraham", -5}}, - want: "", - }, - { - name: "length too long test", - args: args{[]interface{}{"Abraham", 50}}, - want: "Abraham", - }, - { - name: "unicode string", - args: args{[]interface{}{"\u2318foo\u2318", 3}}, - want: "\u2318fo", - }, - // TODO: Add test cases. - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := leftSubstring(nil, tt.args.args) - if (err != nil) != tt.wantErr { - t.Errorf("FunctionLeft() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("FunctionLeft() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestFunctionRight(t *testing.T) { - type args struct { - args []interface{} - } - - tests := []struct { - name string - args args - want interface{} - wantErr bool - }{ - { - name: "simple test", - args: args{[]interface{}{"Abraham", 3}}, - want: "ham", - }, - { - name: "length too small test", - args: args{[]interface{}{"Abraham", -5}}, - want: "", - }, - { - name: "length too long test", - args: args{[]interface{}{"Abraham", 103}}, - want: "Abraham", - }, - { - name: "empty string test", - args: args{[]interface{}{"", 3}}, - want: "", - }, - { - name: "unicode string", - args: args{[]interface{}{"\u2318foo\u2318", 3}}, - want: "oo\u2318", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := rightSubstring(nil, tt.args.args) - if (err != nil) != tt.wantErr { - t.Errorf("FunctionRight() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("FunctionRight() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestFunctionLower(t *testing.T) { - type args struct { - args []interface{} - } - - tests := []struct { - name string - args args - want interface{} - wantErr bool - }{ - { - name: "lower case", - args: args{[]interface{}{"short"}}, - want: "short", - }, - { - name: "upper case", - args: args{[]interface{}{"TALL"}}, - want: "tall", - }, - { - name: "mixed case", - args: args{[]interface{}{"camelCase"}}, - want: "camelcase", - }, - { - name: "empty string", - args: args{[]interface{}{""}}, - want: "", - }, - { - name: "non-string", - args: args{[]interface{}{3.14}}, - want: "3.14", - }, - // TODO: Add test cases. - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := toLower(nil, tt.args.args) - if (err != nil) != tt.wantErr { - t.Errorf("FunctionLower() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("FunctionLower() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestFunctionUpper(t *testing.T) { - type args struct { - args []interface{} - } - - tests := []struct { - name string - args args - want interface{} - wantErr bool - }{ - { - name: "lower case", - args: args{[]interface{}{"short"}}, - want: "SHORT", - }, - { - name: "upper case", - args: args{[]interface{}{"TALL"}}, - want: "TALL", - }, - { - name: "mixed case", - args: args{[]interface{}{"camelCase"}}, - want: "CAMELCASE", - }, - { - name: "empty string", - args: args{[]interface{}{""}}, - want: "", - }, - { - name: "non-string", - args: args{[]interface{}{3.14}}, - want: "3.14", - }, - // TODO: Add test cases. - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := toUpper(nil, tt.args.args) - if (err != nil) != tt.wantErr { - t.Errorf("FunctionUpper() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("FunctionUpper() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSubstring(t *testing.T) { - tests := []struct { - name string - args []interface{} - want interface{} - wantErr bool - }{ - { - name: "left case", - args: []interface{}{"simple", 1, 3}, - want: "sim", - }, - { - name: "right case", - args: []interface{}{"simple", 3, 4}, - want: "mple", - }, - { - name: "middle case", - args: []interface{}{"simple", 3, 1}, - want: "m", - }, - { - name: "invalid start case", - args: []interface{}{"simple", -5, 3}, - want: "sim", - }, - { - name: "invalid len case", - args: []interface{}{"simple", 1, 355}, - want: "simple", - }, - { - name: "simple ASCII string starting at 1", - args: []interface{}{"foobar", 1, 3}, - want: "foo", - }, - { - name: "simple ASCII string starting at 3", - args: []interface{}{"foobar", 3, 2}, - want: "ob", - }, - { - name: "simple ASCII string with zero len", - args: []interface{}{"foobar", 3, 0}, - want: "", - }, - { - name: "simple ASCII string with len too big", - args: []interface{}{"foobar", 3, 10}, - want: "obar", - }, - { - name: "Unicode string with zero len", - args: []interface{}{"\u2318foo\u2318", 3, 0}, - want: "", - }, - { - name: "Unicode string starting at 1", - args: []interface{}{"\u2318foo\u2318", 1, 3}, - want: "\u2318fo", - }, - { - name: "Unicode string starting at 2", - args: []interface{}{"\u2318foo\u2318", 2, 3}, - want: "foo", - }, - - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := substring(nil, tt.args) - if (err != nil) != tt.wantErr { - t.Errorf("Substring() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Substring() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestStrLen(t *testing.T) { - tests := []struct { - name string - args []interface{} - want interface{} - wantErr bool - }{ - { - name: "length of ASCII string", - args: []interface{}{"foo"}, - want: 3, - }, - { - name: "length of empty string", - args: []interface{}{""}, - want: 0, - }, - { - name: "length of Unicode string", - args: []interface{}{"\u2318Foo\u2318"}, - want: 5, - }, - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := length(nil, tt.args) - if (err != nil) != tt.wantErr { - t.Errorf("StrLen() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("StrLen() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/strings/substrings.go b/strings/substrings.go deleted file mode 100644 index 825322a..0000000 --- a/strings/substrings.go +++ /dev/null @@ -1,169 +0,0 @@ -package strings - -import ( - "strings" - - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// index implements the strings.index() function. -func index(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error) { - v := data.String(args[0]) - p := data.String(args[1]) - - return strings.Index(v, p) + 1, nil -} - -// substring implements the substring() function. -func substring(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error) { - v := data.String(args[0]) - - p1 := data.Int(args[1]) // Starting character position - if p1 < 1 { - p1 = 1 - } - - p2 := data.Int(args[2]) // Number of characters - if p2 == 0 { - return "", nil - } - - // Calculate length of v in characters - count := 0 - for range v { - count++ - } - - // Limit the ending bounds by the actual length - if p2+p1 > count { - p2 = count - p1 + 1 - } - - var b strings.Builder - - pos := 1 - - for _, ch := range v { - if pos >= p1+p2 { - break - } - - if pos >= p1 { - b.WriteRune(ch) - } - - pos++ - } - - return b.String(), nil -} - -// leftSubstring implements the left() function. -func leftSubstring(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error) { - var b strings.Builder - - count := 0 - v := data.String(args[0]) - - p := data.Int(args[1]) - if p <= 0 { - return "", nil - } - - for _, ch := range v { - if count < p { - b.WriteRune(ch) - - count++ - } else { - break - } - } - - return b.String(), nil -} - -// rightSubstring implements the right() function. -func rightSubstring(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error) { - var charPos int - - var b strings.Builder - - v := data.String(args[0]) - - p := data.Int(args[1]) - if p <= 0 { - return "", nil - } - - // What's the actual length? - count := 0 - for range v { - count++ - } - - for _, ch := range v { - if charPos >= count-p { - b.WriteRune(ch) - } - charPos++ - } - - return b.String(), nil -} - -// Wrapper around strings.contains(). -func contains(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - a := data.String(args[0]) - b := data.String(args[1]) - - return strings.Contains(a, b), nil -} - -// Wrapper around strings.Contains(). -func containsAny(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - a := data.String(args[0]) - b := data.String(args[1]) - - return strings.ContainsAny(a, b), nil -} - -func truncate(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error) { - name := data.String(args[0]) - maxWidth := data.Int(args[1]) - - if len(name) <= maxWidth { - return name, nil - } - - result := name - chars := 0 - dots := "..." - limit := maxWidth - len(dots) // name + `...` - - // iterating over strings is based on runes, not bytes. - for i := range name { - if chars >= limit { - result = name[:i] + dots - - break - } - chars++ - } - - return result, nil -} - -// length is the strings.length() function, which counts characters/runes instead of -// bytes like len() does. -func length(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error) { - count := 0 - v := data.String(args[0]) - - for range v { - count++ - } - - return count, nil -} diff --git a/strings/template.go b/strings/template.go deleted file mode 100644 index e575133..0000000 --- a/strings/template.go +++ /dev/null @@ -1,68 +0,0 @@ -package strings - -import ( - "bytes" - "text/template" - "text/template/parse" - - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// evaluateTemplate implements the strings.template() function. -func evaluateTemplate(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - var err error - - tree, ok := args[0].(*template.Template) - if !ok { - return data.List(nil, errors.ErrInvalidType), errors.ErrInvalidType.In("Template").Context(data.TypeOf(args[0]).String()) - } - - root := tree.Tree.Root - - for _, n := range root.Nodes { - if n.Type() == parse.NodeTemplate { - templateNode := n.(*parse.TemplateNode) - // Get the named template and add it's tree here - tv, ok := s.Get(templateNode.Name) - if !ok { - e := errors.ErrInvalidTemplateName.In("Template").Context(templateNode.Name) - - return data.List(nil, e), e - } - - t, ok := tv.(*template.Template) - if !ok { - e := errors.ErrInvalidType.In("Template").Context(data.TypeOf(tv).String()) - - return data.List(nil, e), e - } - - _, err = tree.AddParseTree(templateNode.Name, t.Tree) - if err != nil { - return data.List(nil, err), errors.NewError(err) - } - } - } - - var r bytes.Buffer - - if len(args) == 1 { - err = tree.Execute(&r, nil) - } else { - if structure, ok := args[1].(*data.Struct); ok { - err = tree.Execute(&r, structure.ToMap()) - } else if m, ok := args[1].(*data.Map); ok { - err = tree.Execute(&r, m.ToMap()) - } else { - err = tree.Execute(&r, args[1]) - } - } - - if err != nil { - err = errors.NewError(err) - } - - return data.List(r.String(), err), err -} diff --git a/strings/transform.go b/strings/transform.go deleted file mode 100644 index ddfe38b..0000000 --- a/strings/transform.go +++ /dev/null @@ -1,71 +0,0 @@ -package strings - -import ( - "strings" - - "github.com/tucats/gopackages/errors" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// toLower implements the lower() function. -func toLower(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error) { - return strings.ToLower(data.String(args[0])), nil -} - -// toUpper implements the upper() function. -func toUpper(symbols *symbols.SymbolTable, args []interface{}) (interface{}, error) { - return strings.ToUpper(data.String(args[0])), nil -} - -// splitString splits a string into lines separated by a newline. Optionally -// a different delimiter can be supplied as the second argument. -func splitString(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - var v []string - - src := data.String(args[0]) - delim := "\n" - - if len(args) > 1 { - delim = data.String(args[1]) - } - - // Are we seeing Windows-style line endings? If we are doing a split - // based on line endings, use Windows line endings. - if delim == "\n" && strings.Index(src, "\r\n") > 0 { - v = strings.Split(src, "\r\n") - } else { - // Otherwise, split by the delimiter - v = strings.Split(src, delim) - } - - // We need to store the result in a native Ego array. - r := data.NewArray(data.StringType, len(v)) - - for i, n := range v { - err := r.Set(i, n) - if err != nil { - return nil, errors.NewError(err) - } - } - - return r, nil -} - -// Wrapper around strings.join(). -func join(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - elemArray, ok := args[0].(*data.Array) - if !ok { - return nil, errors.ErrArgumentType.In("Join") - } - - separator := data.String(args[1]) - elements := make([]string, elemArray.Len()) - - for i := 0; i < elemArray.Len(); i++ { - element, _ := elemArray.Get(i) - elements[i] = data.String(element) - } - - return strings.Join(elements, separator), nil -} diff --git a/strings/types.go b/strings/types.go deleted file mode 100644 index 0e27186..0000000 --- a/strings/types.go +++ /dev/null @@ -1,366 +0,0 @@ -package strings - -import ( - "github.com/tucats/gopackages/expressions/bytecode" - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -func Initialize(s *symbols.SymbolTable) { - newpkg := data.NewPackageFromMap("strings", map[string]interface{}{ - "Chars": data.Function{ - Declaration: &data.Declaration{ - Name: "Chars", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.ArrayType(data.StringType)}, - }, - Value: chars, - }, - "Compare": data.Function{ - Declaration: &data.Declaration{ - Name: "Compare", - Parameters: []data.Parameter{ - { - Name: "a", - Type: data.StringType, - }, - { - Name: "b", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.IntType}, - }, - Value: compare, - }, - "Contains": data.Function{ - Declaration: &data.Declaration{ - Name: "Contains", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - { - Name: "search", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.BoolType}, - }, - Value: contains, - }, - "ContainsAny": data.Function{ - Declaration: &data.Declaration{ - Name: "Contains", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - { - Name: "chars", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.BoolType}, - }, - Value: containsAny, - }, - "EqualFold": data.Function{ - Declaration: &data.Declaration{ - Name: "EqualFold", - Parameters: []data.Parameter{ - { - Name: "a", - Type: data.StringType, - }, - { - Name: "b", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: equalFold, - }, - "Fields": data.Function{ - Declaration: &data.Declaration{ - Name: "Fields", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.ArrayType(data.StringType)}, - }, - Value: fields, - }, - "Format": data.Function{ - Declaration: &data.Declaration{ - Name: "Format", - Parameters: []data.Parameter{ - { - Name: "format", - Type: data.StringType, - }, - { - Name: "item", - Type: data.InterfaceType, - }, - }, - ArgCount: data.Range{1, 2}, - Variadic: true, - Returns: []*data.Type{data.StringType}, - }, - Value: format, - }, - "Index": data.Function{ - Declaration: &data.Declaration{ - Name: "Contains", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - { - Name: "substr", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.IntType}, - }, - Value: index, - }, - "Ints": data.Function{ - Declaration: &data.Declaration{ - Name: "Ints", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.IntType}, - }, - Value: extractInts, - }, - "Join": data.Function{ - Declaration: &data.Declaration{ - Name: "Join", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.ArrayType(data.StringType), - }, - { - Name: "separator", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: join, - }, - "Left": data.Function{ - Declaration: &data.Declaration{ - Name: "Left", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - { - Name: "position", - Type: data.IntType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: leftSubstring, - }, - "Length": data.Function{ - Declaration: &data.Declaration{ - Name: "Length", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.IntType}, - }, - Value: length, - }, - "Right": data.Function{ - Declaration: &data.Declaration{ - Name: "Right", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - { - Name: "position", - Type: data.IntType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: rightSubstring, - }, - "Split": data.Function{ - Declaration: &data.Declaration{ - Name: "Split", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - { - Name: "separator", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.ArrayType(data.StringType)}, - }, - Value: splitString, - }, - "String": data.Function{ - Declaration: &data.Declaration{ - Name: "String", - Parameters: []data.Parameter{ - { - Name: "any", - Type: data.InterfaceType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: toString, - }, - "Substring": data.Function{ - Declaration: &data.Declaration{ - Name: "Substring", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - { - Name: "position", - Type: data.IntType, - }, - { - Name: "count", - Type: data.IntType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: substring, - }, - "Template": data.Function{ - Declaration: &data.Declaration{ - Name: "Template", - Parameters: []data.Parameter{ - { - Name: "name", - Type: data.StringType, - }, - { - Name: "parameters", - Type: data.MapType(data.StringType, data.InterfaceType), - }, - }, - ArgCount: data.Range{1, 2}, - Scope: true, - Returns: []*data.Type{data.StringType, data.ErrorType}, - }, - Value: evaluateTemplate, - }, - "ToLower": data.Function{ - Declaration: &data.Declaration{ - Name: "ToLower", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: toLower, - }, - "ToUpper": data.Function{ - Declaration: &data.Declaration{ - Name: "ToUpper", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: toUpper, - }, - "Tokenize": data.Function{ - Declaration: &data.Declaration{ - Name: "Tokenize", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.ArrayType(data.StringType)}, - }, - Value: tokenize, - }, - "Truncate": data.Function{ - Declaration: &data.Declaration{ - Name: "Truncate", - Parameters: []data.Parameter{ - { - Name: "text", - Type: data.StringType, - }, - { - Name: "maxlength", - Type: data.IntType, - }, - }, - Returns: []*data.Type{data.StringType}, - }, - Value: truncate, - }, - "URLPattern": data.Function{ - Declaration: &data.Declaration{ - Name: "URLPattern", - Parameters: []data.Parameter{ - { - Name: "url", - Type: data.StringType, - }, - { - Name: "pattern", - Type: data.StringType, - }, - }, - Returns: []*data.Type{data.MapType(data.StringType, data.InterfaceType)}, - }, - Value: URLPattern, - }, - }) - - pkg, _ := bytecode.GetPackage(newpkg.Name) - pkg.Merge(newpkg) - s.Root().SetAlways(newpkg.Name, newpkg) -} diff --git a/strings/urls.go b/strings/urls.go deleted file mode 100644 index 1ac004e..0000000 --- a/strings/urls.go +++ /dev/null @@ -1,113 +0,0 @@ -package strings - -import ( - "strings" - - "github.com/tucats/gopackages/expressions/data" - "github.com/tucats/gopackages/expressions/symbols" -) - -// URLPattern parses a URL using a provided pattern for what the string -// is expected to be, and then generates an Ego map that indicates each -// segment of the url endopint and it's value. -// -// If the pattern is -// -// "/services/debug/processes/{{ID}}" -// -// and the url is -// -// /services/debug/processses/1653 -// -// Then the result map will be -// -// map[string]interface{} { -// "ID" : 1653 -// } -func URLPattern(s *symbols.SymbolTable, args []interface{}) (interface{}, error) { - result := data.NewMap(data.StringType, data.InterfaceType) - - patternMap, match := ParseURLPattern(data.String(args[0]), data.String(args[1])) - if !match { - return result, nil - } - - for k, v := range patternMap { - _, err := result.Set(k, v) - if err != nil { - return result, err - } - } - - return result, nil -} - -// ParseURLPattern accepts a pattern that tells what part of the URL is -// meant to be literal, and what is a user-supplied item. The result is -// a map of the URL items parsed. -// -// If the pattern is -// -// "/services/debug/processes/{{ID}}" -// -// and the url is -// -// /services/debug/processses/1653 -// -// Then the result map will be -// -// map[string]interface{} { -// "ID" : 1653 -// } -func ParseURLPattern(url, pattern string) (map[string]interface{}, bool) { - urlParts := strings.Split(url, "/") - patternParts := strings.Split(pattern, "/") - result := map[string]interface{}{} - - if len(urlParts) > len(patternParts) { - return nil, false - } - - for idx, pat := range patternParts { - if len(pat) == 0 { - continue - } - - // If the pattern continues longer than the - // URL given, mark those as being absent - if idx >= len(urlParts) { - // Is this part of the pattern a substitution? If not, we store - // it in the result as a field-not-found. If it is a substitution - // operator, store as an empty string. - if !strings.HasPrefix(pat, "{{") || !strings.HasSuffix(pat, "}}") { - result[pat] = false - } else { - name := strings.Replace(strings.Replace(pat, "{{", "", 1), "}}", "", 1) - result[name] = "" - } - - continue - } - - // If this part just matches, mark it as present. - if strings.EqualFold(pat, urlParts[idx]) { - result[pat] = true - - continue - } - - // If this pattern is a substitution operator, get the value now - // and store in the map using the substitution name - if strings.HasPrefix(pat, "{{") && strings.HasSuffix(pat, "}}") { - // Strip off the {{ }} from the name we going to save it as. - name := strings.Replace(strings.Replace(pat, "{{", "", 1), "}}", "", 1) - // Put it in the result using that key and the original value from the URL. - result[name] = urlParts[idx] - } else { - // It didn't match the url, so no data - return nil, false - } - } - - return result, true -} diff --git a/strings/urls_test.go b/strings/urls_test.go deleted file mode 100644 index e32f0a6..0000000 --- a/strings/urls_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package strings - -import ( - "reflect" - "testing" -) - -func TestParseURLPattern(t *testing.T) { - tests := []struct { - name string - url string - pattern string - want map[string]interface{} - matches bool - }{ - { - name: "constant pattern unused segment", - url: "/service/debug", - pattern: "/service/debug/age", - want: map[string]interface{}{ - "service": true, - "debug": true, - "age": false, - }, - matches: true, - }, - { - name: "constant pattern unused sub", - url: "/service/debug", - pattern: "/service/debug/{{age}}", - want: map[string]interface{}{ - "service": true, - "debug": true, - "age": "", - }, - matches: true, - }, - { - name: "constant pattern matches", - url: "/service/debug", - pattern: "/service/debug", - want: map[string]interface{}{ - "service": true, - "debug": true, - }, - matches: true, - }, - { - name: "constant pattern does not match", - url: "/service/debug", - pattern: "/service/debugz", - want: nil, - matches: false, - }, - { - name: "constant pattern trailing separator mismatch", - url: "/service/debug/", - pattern: "/service/debug", - want: nil, - matches: false, - }, - { - name: "one sub pattern matches", - url: "/service/proc/1653", - pattern: "/service/proc/{{pid}}", - want: map[string]interface{}{ - "service": true, - "proc": true, - "pid": "1653", - }, - matches: true, - }, - { - name: "case sensitive string matches", - url: "/service/proc/Accounts", - pattern: "/service/proc/{{table}}", - want: map[string]interface{}{ - "service": true, - "proc": true, - "table": "Accounts", - }, - matches: true, - }, - { - name: "two subs pattern matches", - url: "/service/proc/1653/window/foobar", - pattern: "/service/proc/{{pid}}/window/{{name}}", - want: map[string]interface{}{ - "service": true, - "proc": true, - "window": true, - "pid": "1653", - "name": "foobar", - }, - matches: true, - }, - { - name: "two subs pattern does not match", - url: "/service/proc/1653/frame/foobar", - pattern: "/service/proc/{{pid}}/window/{{name}}", - want: nil, - matches: false, - }, - - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1 := ParseURLPattern(tt.url, tt.pattern) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ParseURLPattern() got = %v, want %v", got, tt.want) - } - if got1 != tt.matches { - t.Errorf("ParseURLPattern() got1 = %v, want %v", got1, tt.matches) - } - }) - } -} diff --git a/util/strings.go b/util/strings.go index b05b72f..5281cdd 100644 --- a/util/strings.go +++ b/util/strings.go @@ -6,8 +6,6 @@ import ( "sort" "strings" "unicode" - - "github.com/tucats/gopackages/expressions/data" ) // Unquote removes quotation marks from a string if present. @@ -52,22 +50,6 @@ func Hostname() string { } } -// Given a list of strings, convert them to a sorted list in -// Ego array format. -func MakeSortedArray(array []string) *data.Array { - sort.Strings(array) - - intermediateArray := make([]interface{}, len(array)) - - for i, v := range array { - intermediateArray[i] = v - } - - result := data.NewArrayFromArray(data.StringType, intermediateArray) - - return result -} - func InterfaceMapKeys(data map[string]interface{}) []string { keys := make([]string, 0)