From 9538aa4b89fe0ee32efd80121594d267af7485da Mon Sep 17 00:00:00 2001 From: Tom Cole Date: Mon, 2 Dec 2024 15:12:47 -0500 Subject: [PATCH] chore: simplify lengthy routines by refactoring for readability --- .vscode/launch.json | 9 +- app-cli/cli/parse.go | 239 ++++++++++++++------------ bytecode/equal.go | 30 ++-- bytecode/member.go | 98 ++++++----- bytecode/serialize.go | 354 +++++++++++++++++++++------------------ commands/tables.go | 114 +++++++------ compiler/expr_atom.go | 187 ++++++++++++--------- tests/io/db_postgres.ego | 5 +- 8 files changed, 576 insertions(+), 460 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fa3bddfc..a48d7e24 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -132,7 +132,14 @@ "program": "${workspaceFolder}", "args": ["run", "test.ego", "--log"] }, - + { + "name": "Debug cli parsing with parms", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}", + "args": ["-l", "cli", "config", "ego.compiler.extensions"] + }, { "name": "test with optimizer logging", "type": "go", diff --git a/app-cli/cli/parse.go b/app-cli/cli/parse.go index 325d4fb0..b15234d7 100644 --- a/app-cli/cli/parse.go +++ b/app-cli/cli/parse.go @@ -15,6 +15,17 @@ import ( // an unknown but variable number of arguments can be presented. const Variable = -1 +type parseState struct { + args []string + defaultVerb *Option + currentArg int + lastArg int + parsedSoFar int + parametersOnly bool + helpVerb bool + done bool +} + // Parse processes the grammar associated with the current context, // using the []string array of arguments for that context. // @@ -42,12 +53,15 @@ func (c *Context) Parse() error { // This is never called by the user directly. func (c *Context) parseGrammar(args []string) error { var ( - err error - parsedSoFar = 0 - parametersOnly = false - helpVerb = true + err error ) + state := &parseState{ + args: args, + helpVerb: true, + lastArg: len(args), + } + // Are there parameters already stored away in the global? If so, // they are unrecognized verbs that were hoovered up by the grammar // parent processing, and are not parameters (which must always @@ -66,114 +80,21 @@ func (c *Context) parseGrammar(args []string) error { return errors.ErrUnrecognizedCommand.Context(parmList[0]) } - // No dangling parameters, let's keep going. - lastArg := len(args) - - // See if we have a default verb we should know about. - defaultVerb := findDefaultVerb(c) - - // Scan over the tokens, parsing until we hit a subcommand. - for currentArg := 0; currentArg < lastArg; currentArg++ { - var ( - location *Option - option = args[currentArg] - ) - - parsedSoFar = currentArg - - ui.Log(ui.CLILogger, "Processing token: %s", option) - - // Are we now only eating parameter values? - if parametersOnly { - globalContext := c.FindGlobal() - globalContext.Parameters = append(globalContext.Parameters, option) - count := len(globalContext.Parameters) - - ui.Log(ui.CLILogger, "added parameter %d", count) - - continue - } - - // Handle the special cases automatically. - if (helpVerb && option == "help") || option == "-h" || option == "--help" { - ShowHelp(c) - - return nil - } - - // Handle the "empty option" that means the remainder of the command - // line will be treated as parameters, even if it looks like it has - // options, etc. - if option == "--" { - parametersOnly = true - helpVerb = false - - continue - } - - // Convert the option name to a proper name, and remember if it was a short versus long form. - // Also, if there was an embedded value in the name, return that as well. - name, isShort, value, hasValue := parseOptionName(option) - - location = nil - - if name > "" { - location = findShortName(c, isShort, name, location) - } + // No dangling parameters, let's keep going. See if we have a default verb we should know about. + state.defaultVerb = findDefaultVerb(c) - if location != nil { - ui.Log(ui.CLILogger, "Setting value for option %s", location.LongName) - } - - // If it was an option (short or long) and not found, this is an error. - if name != "" && location == nil && defaultVerb == nil { - return errors.ErrUnknownOption.Context(option) - } - - // It could be a parameter, or a subcommand. - if location == nil { - // Is it a subcommand? - if done, err := evaluatePossibleSubcommand(c, option, args, currentArg, defaultVerb, parsedSoFar); done { - return err - } - } else { - location.Found = true - // If it's not a boolean type, see it already has a value from the = construct. - // If not, claim the next argument as the value. - if location.OptionType != BooleanType { - if !hasValue { - currentArg = currentArg + 1 - if currentArg >= lastArg { - return errors.ErrMissingOptionValue.Context(name) - } - - value = args[currentArg] - hasValue = true - } - } - - // Validate the argument type and store the appropriately typed value. - // if it has a value, use that to set the boolean (so it - // behaves like a BooleanValueType). If no value was parsed, - // just assume it is true. - if err := validateOption(location, value, hasValue); err != nil { - return err - } - - ui.Log(ui.CLILogger, "Option value set to %#v", location.Value) - - // After parsing the option value, if there is an action routine, call it - if location.Action != nil { - if err = location.Action(c); err != nil { - break - } - } + // Scan over the tokens, parsing until we hit a subcommand. If we get to the end of the tokens, + // we are done parsing. If there is an error we are done parsing. + for state.currentArg = 0; state.currentArg < state.lastArg; state.currentArg++ { + err = parseToken(c, state) + if err != nil || state.done { + return err } } // No subcommand found, but was there a default we should use anyway? - if err == nil && defaultVerb != nil { - return doDefaultSubcommand(parsedSoFar, c, defaultVerb, args) + if err == nil && state.defaultVerb != nil { + return doDefaultSubcommand(state.parsedSoFar, c, state.defaultVerb, args) } // Whew! Everything parsed and in it's place. Before we wind up, let's verify that @@ -207,6 +128,110 @@ func verifyRequiredOptionsPresent(c *Context) error { return nil } +func parseToken(c *Context, state *parseState) error { + var ( + location *Option + ) + + option := state.args[state.currentArg] + state.parsedSoFar = state.currentArg + + ui.Log(ui.CLILogger, "Processing token: %s", option) + + // Are we now only eating parameter values? + if state.parametersOnly { + globalContext := c.FindGlobal() + globalContext.Parameters = append(globalContext.Parameters, option) + count := len(globalContext.Parameters) + + ui.Log(ui.CLILogger, "added parameter %d", count) + + return nil + } + + // Handle the special cases automatically. + if (state.helpVerb && option == "help") || option == "-h" || option == "--help" { + ShowHelp(c) + + return nil + } + + // Handle the "empty option" that means the remainder of the command + // line will be treated as parameters, even if it looks like it has + // options, etc. + if option == "--" { + state.parametersOnly = true + state.helpVerb = false + + ui.Log(ui.CLILogger, "All remaining tokens are parameters") + + return nil + } + + // Convert the option name to a proper name, and remember if it was a short versus long form. + // Also, if there was an embedded value in the name, return that as well. + name, isShort, value, hasValue := parseOptionName(option) + + location = nil + + if name > "" { + location = findShortName(c, isShort, name, location) + } + + if location != nil { + ui.Log(ui.CLILogger, "Setting value for option %s", location.LongName) + } + + // If it was an option (short or long) and not found, this is an error. + if name != "" && location == nil && state.defaultVerb == nil { + return errors.ErrUnknownOption.Context(option) + } + + // It could be a parameter, or a subcommand. + if location == nil { + // Is it a subcommand? + if done, err := evaluatePossibleSubcommand(c, option, state.args, state.currentArg, state.defaultVerb, state.parsedSoFar); done { + state.done = true + + return err + } + } else { + location.Found = true + // If it's not a boolean type, see it already has a value from the = construct. + // If not, claim the next argument as the value. + if location.OptionType != BooleanType { + if !hasValue { + state.currentArg = state.currentArg + 1 + if state.currentArg >= state.lastArg { + return errors.ErrMissingOptionValue.Context(name) + } + + value = state.args[state.currentArg] + hasValue = true + } + } + + // Validate the argument type and store the appropriately typed value. + // if it has a value, use that to set the boolean (so it + // behaves like a BooleanValueType). If no value was parsed, + // just assume it is true. + if err := validateOption(location, value, hasValue); err != nil { + return err + } + + ui.Log(ui.CLILogger, "Option value set to %#v", location.Value) + + // After parsing the option value, if there is an action routine, call it + if location.Action != nil { + if err := location.Action(c); err != nil { + return err + } + } + } + + return nil +} + // For an option token, extract the name and note if it was the short form or long form. // Also, if the option has an value appended after an "=" sign, return the value and // a flag indicated it was present. diff --git a/bytecode/equal.go b/bytecode/equal.go index 585f0877..17873418 100644 --- a/bytecode/equal.go +++ b/bytecode/equal.go @@ -58,18 +58,7 @@ func equalByteCode(c *Context, i interface{}) error { } case *data.Type: - if v, ok := v2.(string); ok { - result = (actual.String() == v) - } else if v, ok := v2.(*data.Type); ok { - // Deep equal gets goobered up with types that have - // pointers, so let's conver to string values and compare - // the strings. - t1 := actual.String() - t2 := v.String() - result = (t1 == t2) - } else { - return errors.ErrNotAType.Context(v2) - } + return equalTypes(v2, c, actual) case nil: if err, ok := v2.(error); ok { @@ -137,9 +126,22 @@ func equalByteCode(c *Context, i interface{}) error { } } - _ = c.push(result) + return c.push(result) +} + +// Compare the v2 value with the actual type. Because deep equal testing cannot +// be used, we attempt to format the types as strings and compare the strings. +func equalTypes(v2 interface{}, c *Context, actual *data.Type) error { + if v, ok := v2.(string); ok { + return c.push(actual.String() == v) + } else if v, ok := v2.(*data.Type); ok { + t1 := actual.String() + t2 := v.String() + + return c.push(t1 == t2) + } - return nil + return errors.ErrNotAType.Context(v2) } func getComparisonTerms(c *Context, i interface{}) (interface{}, interface{}, error) { diff --git a/bytecode/member.go b/bytecode/member.go index d9e22a43..d0f5b114 100644 --- a/bytecode/member.go +++ b/bytecode/member.go @@ -94,68 +94,78 @@ func getMemberValue(c *Context, m interface{}, name string) (interface{}, error) return getStructMemberValue(c, mv, name) case *data.Package: - if util.HasCapitalizedName(name) { - if symV, ok := mv.Get(data.SymbolsMDKey); ok { - syms := symV.(*symbols.SymbolTable) + return getPackageMemberValue(name, mv, v, found, c, m) - if v, ok := syms.Get(name); ok { - return data.UnwrapConstant(v), nil - } - } - } + default: + return getNativePackageMemberValue(mv, name, c) + } - tt := data.TypeOf(mv) + return data.UnwrapConstant(v), err +} - v, found = mv.Get(name) - if !found { - if fv := tt.Function(name); fv == nil { - return nil, c.error(errors.ErrUnknownPackageMember).Context(name) - } else { - v = fv - } - } +func getNativePackageMemberValue(mv interface{}, name string, c *Context) (interface{}, error) { + gt := reflect.TypeOf(mv) + if _, found := gt.MethodByName(name); found { + text := gt.String() - c.lastStruct = m + if parts := strings.Split(text, "."); len(parts) == 2 { + pkg := strings.TrimPrefix(parts[0], "*") + typeName := parts[1] - default: - gt := reflect.TypeOf(mv) - if _, found := gt.MethodByName(name); found { - text := gt.String() - - if parts := strings.Split(text, "."); len(parts) == 2 { - pkg := strings.TrimPrefix(parts[0], "*") - typeName := parts[1] - - if pkgData, found := c.get(pkg); found { - if pkg, ok := pkgData.(*data.Package); ok { - if typeInterface, ok := pkg.Get(typeName); ok { - if typeData, ok := typeInterface.(*data.Type); ok { - fd := typeData.FunctionByName(name) - if fd != nil { - return *fd, nil - } + if pkgData, found := c.get(pkg); found { + if pkg, ok := pkgData.(*data.Package); ok { + if typeInterface, ok := pkg.Get(typeName); ok { + if typeData, ok := typeInterface.(*data.Type); ok { + fd := typeData.FunctionByName(name) + if fd != nil { + return *fd, nil } } } } } } + } - kind := data.TypeOf(mv) + kind := data.TypeOf(mv) - fnx := kind.Function(name) - if fnx != nil { - return fnx, nil - } + fnx := kind.Function(name) + if fnx != nil { + return fnx, nil + } + + if kind.Kind() < data.MaximumScalarType { + return nil, c.error(errors.ErrInvalidTypeForOperation).Context(kind.String()) + } + + return nil, c.error(errors.ErrUnknownIdentifier).Context(name) +} - if kind.Kind() < data.MaximumScalarType { - return nil, c.error(errors.ErrInvalidTypeForOperation).Context(kind.String()) +func getPackageMemberValue(name string, mv *data.Package, v interface{}, found bool, c *Context, m interface{}) (interface{}, error) { + if util.HasCapitalizedName(name) { + if symV, ok := mv.Get(data.SymbolsMDKey); ok { + syms := symV.(*symbols.SymbolTable) + + if v, ok := syms.Get(name); ok { + return data.UnwrapConstant(v), nil + } } + } - return nil, c.error(errors.ErrUnknownIdentifier).Context(name) + tt := data.TypeOf(mv) + + v, found = mv.Get(name) + if !found { + if fv := tt.Function(name); fv == nil { + return nil, c.error(errors.ErrUnknownPackageMember).Context(name) + } else { + v = fv + } } - return data.UnwrapConstant(v), err + c.lastStruct = m + + return data.UnwrapConstant(v), nil } func getNativePackageMember(c *Context, actual interface{}, name string, interfaceValue interface{}) (interface{}, error) { diff --git a/bytecode/serialize.go b/bytecode/serialize.go index f0333f5c..821cc6b4 100644 --- a/bytecode/serialize.go +++ b/bytecode/serialize.go @@ -160,89 +160,13 @@ func serializeValue(arg interface{}) (string, error) { return `{"type":"@null"}`, nil case *data.Array: - r := strings.Builder{} - r.WriteString(fmt.Sprintf(`{"type":"@array", "of":"%s", "values":[`, arg.Type())) - - for i := 0; i < arg.Len(); i++ { - vv, _ := arg.Get(i) - - vvText, err := serializeValue(vv) - if err != nil { - return "", err - } - - r.WriteString(vvText) - - if i < arg.Len()-1 { - r.WriteString(", ") - } - } - - r.WriteString("]}") - - return r.String(), nil + return serializeArrayValue(arg) case *data.Map: - r := strings.Builder{} - r.WriteString(fmt.Sprintf(`{"type":"@map", "t":"%s", "key":"%s", "of":"%s", "values":[`, arg.Type().String(), arg.Type().KeyType(), arg.Type().BaseType())) - - for index, i := range arg.Keys() { - vv, found, err := arg.Get(i) - if err != nil { - return "", err - } - - if !found { - return "", errors.ErrNotFound.Context(fmt.Sprintf("%v", i)) - } - - if index > 0 { - r.WriteString(",\n") - } else { - r.WriteString("\n") - } - - kkText, err := serializeValue(i) - if err != nil { - return "", err - } - - vvText, err := serializeValue(vv) - if err != nil { - return "", err - } - - r.WriteString(fmt.Sprintf(`{"k":%s, "v":%s}`, kkText, vvText)) - } - - r.WriteString("]}") - - return r.String(), nil + return serializeMapValue(arg) case *data.Struct: - r := strings.Builder{} - r.WriteString(fmt.Sprintf(`{"type":"@struct", "t":"%s", "fields":[`, arg.Type().String())) - - for index, i := range arg.FieldNames(true) { - vv := arg.GetAlways(i) - - if index > 0 { - r.WriteString(",\n") - } else { - r.WriteString("\n") - } - - vvText, err := serializeValue(vv) - if err != nil { - return "", err - } - - r.WriteString(fmt.Sprintf(`{"%s" : %s}`, i, vvText)) - } - - r.WriteString("]}") - - return r.String(), nil + return seralizeStructValue(arg) case *symbols.SymbolTable: return fmt.Sprintf(`{"type":"@symtable", "v":%s)`, arg.Name), nil @@ -251,60 +175,12 @@ func serializeValue(arg interface{}) (string, error) { return fmt.Sprintf(`{"type":"@func", "name":"%s", "t":"%s"}`, arg.Declaration.Name, arg.Declaration.String()), nil case *data.Package: - r := strings.Builder{} - r.WriteString(fmt.Sprintf(`{"type":"@pkg", "name":"%s", "id":"%s", "items":[`, arg.Name, arg.ID)) - - for index, i := range arg.Keys() { - vv, found := arg.Get(i) - if !found { - return "", errors.ErrNotFound.Context(fmt.Sprintf("%v", i)) - } - - // Is this the symbol table? If so, skip it. - if _, ok := vv.(*symbols.SymbolTable); ok && i == "__Symbols" { - continue - } - - // Format the key/value pair. - if index > 0 { - r.WriteString(",\n") - } else { - r.WriteString("\n") - } - - kkText, err := serializeValue(i) - if err != nil { - return "", err - } - - vvText, err := serializeValue(vv) - if err != nil { - return "", err - } - - r.WriteString(fmt.Sprintf(`{"k":%s, "v":%s}`, kkText, vvText)) - } - - r.WriteString("]}") - - return r.String(), nil + // Is this the symbol table? If so, skip it. + // Format the key/value pair. + return seralizePackageValue(arg) case *data.Type: - // Is it in the cache already? - if item, ok := cache[arg]; ok { - return fmt.Sprintf(`{"t":"@p", "v":%d}`, item.id), nil - } - - // Cache the type as a declaration string. - id := nextID.Add(1) - - if cache == nil { - cache = map[interface{}]cachedItem{} - } - - cache[arg] = cachedItem{id: id, kind: cachedType, data: arg.String()} - - return fmt.Sprintf(`{"t":"@ptr", "v":"%d"}`, id), nil + return serializeTypeValue(arg) case data.Immutable: vv, err := serializeValue(arg.Value) @@ -360,60 +236,212 @@ func serializeValue(arg interface{}) (string, error) { return fmt.Sprintf(`{"t":"@l", "v":%s}`, listText), nil case *ByteCode: - // Is it in our pointer cache already? - if item, found := cache[arg]; found { - return fmt.Sprintf(`{"t":"@ptr", "v":%d}`, item.id), nil + return serializeBytecodeValue(arg) + + case StackMarker: + return serializeStackMarker(arg) + + case tokenizer.Token: + return fmt.Sprintf(`{"t":"@tk", "v":{"spell":"%s", "class": %d}}`, + arg.Spelling(), arg.Class()), nil + + case *tokenizer.Tokenizer: + return `{"t":"@tokenizer"}`, nil + + default: + return "", errors.ErrInvalidType.Context(fmt.Sprintf("%T", arg)) + } +} + +func serializeStackMarker(arg StackMarker) (string, error) { + name := arg.label + value := "" + + if len(arg.values) > 0 { + argText, err := serializeValue(arg.values) + if err != nil { + return "", err } - buff := strings.Builder{} - buff.WriteString("{\n") + value = fmt.Sprintf(`, "v":%s`, argText) + } + + return fmt.Sprintf(`{"t":"@sm %s"%s}`, name, value), nil +} - buff.WriteString(fmt.Sprintf(`"name": "%s",`, arg.Name())) +func serializeBytecodeValue(arg *ByteCode) (string, error) { + // Is it in our pointer cache already? + if item, found := cache[arg]; found { + return fmt.Sprintf(`{"t":"@ptr", "v":%d}`, item.id), nil + } + + buff := strings.Builder{} + buff.WriteString("{\n") + + buff.WriteString(fmt.Sprintf(`"name": "%s",`, arg.Name())) + buff.WriteString("\n") + + if d := arg.Declaration(); d != nil { + buff.WriteString(fmt.Sprintf(`"declaration": "%s",`, arg.Declaration().String())) buff.WriteString("\n") + } + + codeText, err := serializeCode(arg.instructions, arg.nextAddress) + if err != nil { + return "", err + } + + buff.WriteString(fmt.Sprintf(`"code": %s}`, codeText)) + + // Store value in the cache + id := nextID.Add(1) + cache[arg] = cachedItem{id: id, kind: cachedByteCode, data: buff.String()} + + return fmt.Sprintf(`{"t":"@bc", "v":%d}`, id), nil +} + +func serializeTypeValue(arg *data.Type) (string, error) { + // Is it in the cache already? + if item, ok := cache[arg]; ok { + return fmt.Sprintf(`{"t":"@p", "v":%d}`, item.id), nil + } + + id := nextID.Add(1) - if d := arg.Declaration(); d != nil { - buff.WriteString(fmt.Sprintf(`"declaration": "%s",`, arg.Declaration().String())) - buff.WriteString("\n") + if cache == nil { + cache = map[interface{}]cachedItem{} + } + + // Cache the type as a declaration string. + cache[arg] = cachedItem{id: id, kind: cachedType, data: arg.String()} + + return fmt.Sprintf(`{"t":"@ptr", "v":"%d"}`, id), nil +} + +func seralizePackageValue(arg *data.Package) (string, error) { + r := strings.Builder{} + r.WriteString(fmt.Sprintf(`{"type":"@pkg", "name":"%s", "id":"%s", "items":[`, arg.Name, arg.ID)) + + for index, i := range arg.Keys() { + vv, found := arg.Get(i) + if !found { + return "", errors.ErrNotFound.Context(fmt.Sprintf("%v", i)) + } + + if _, ok := vv.(*symbols.SymbolTable); ok && i == "__Symbols" { + continue + } + + if index > 0 { + r.WriteString(",\n") + } else { + r.WriteString("\n") + } + + kkText, err := serializeValue(i) + if err != nil { + return "", err } - codeText, err := serializeCode(arg.instructions, arg.nextAddress) + vvText, err := serializeValue(vv) if err != nil { return "", err } - buff.WriteString(fmt.Sprintf(`"code": %s}`, codeText)) + r.WriteString(fmt.Sprintf(`{"k":%s, "v":%s}`, kkText, vvText)) + } - // Store value in the cache - id := nextID.Add(1) - cache[arg] = cachedItem{id: id, kind: cachedByteCode, data: buff.String()} + r.WriteString("]}") - return fmt.Sprintf(`{"t":"@bc", "v":%d}`, id), nil + return r.String(), nil +} - case StackMarker: - name := arg.label - value := "" +func seralizeStructValue(arg *data.Struct) (string, error) { + r := strings.Builder{} + r.WriteString(fmt.Sprintf(`{"type":"@struct", "t":"%s", "fields":[`, arg.Type().String())) - if len(arg.values) > 0 { - argText, err := serializeValue(arg.values) - if err != nil { - return "", err - } + for index, i := range arg.FieldNames(true) { + vv := arg.GetAlways(i) - value = fmt.Sprintf(`, "v":%s`, argText) + if index > 0 { + r.WriteString(",\n") + } else { + r.WriteString("\n") } - return fmt.Sprintf(`{"t":"@sm %s"%s}`, name, value), nil + vvText, err := serializeValue(vv) + if err != nil { + return "", err + } - case tokenizer.Token: - return fmt.Sprintf(`{"t":"@tk", "v":{"spell":"%s", "class": %d}}`, - arg.Spelling(), arg.Class()), nil + r.WriteString(fmt.Sprintf(`{"%s" : %s}`, i, vvText)) + } - case *tokenizer.Tokenizer: - return `{"t":"@tokenizer"}`, nil + r.WriteString("]}") - default: - return "", errors.ErrInvalidType.Context(fmt.Sprintf("%T", arg)) + return r.String(), nil +} + +func serializeMapValue(arg *data.Map) (string, error) { + r := strings.Builder{} + r.WriteString(fmt.Sprintf(`{"type":"@map", "t":"%s", "key":"%s", "of":"%s", "values":[`, arg.Type().String(), arg.Type().KeyType(), arg.Type().BaseType())) + + for index, i := range arg.Keys() { + vv, found, err := arg.Get(i) + if err != nil { + return "", err + } + + if !found { + return "", errors.ErrNotFound.Context(fmt.Sprintf("%v", i)) + } + + if index > 0 { + r.WriteString(",\n") + } else { + r.WriteString("\n") + } + + kkText, err := serializeValue(i) + if err != nil { + return "", err + } + + vvText, err := serializeValue(vv) + if err != nil { + return "", err + } + + r.WriteString(fmt.Sprintf(`{"k":%s, "v":%s}`, kkText, vvText)) } + + r.WriteString("]}") + + return r.String(), nil +} + +func serializeArrayValue(arg *data.Array) (string, error) { + r := strings.Builder{} + r.WriteString(fmt.Sprintf(`{"type":"@array", "of":"%s", "values":[`, arg.Type())) + + for i := 0; i < arg.Len(); i++ { + vv, _ := arg.Get(i) + + vvText, err := serializeValue(vv) + if err != nil { + return "", err + } + + r.WriteString(vvText) + + if i < arg.Len()-1 { + r.WriteString(", ") + } + } + + r.WriteString("]}") + + return r.String(), nil } // Generate the JSON for a list. This is most commonly used as an instruction diff --git a/commands/tables.go b/commands/tables.go index 7d8660db..f542b540 100644 --- a/commands/tables.go +++ b/commands/tables.go @@ -130,40 +130,7 @@ func TableShow(c *cli.Context) error { err = errors.Message(resp.Message) } else { if ui.OutputFormat == ui.TextFormat { - t, _ := tables.New([]string{ - i18n.L("Name"), - i18n.L("Type"), - i18n.L("Size"), - i18n.L("Nullable"), - i18n.L("Unique"), - }) - _ = t.SetOrderBy(i18n.L("Name")) - _ = t.SetAlignment(2, tables.AlignmentRight) - - for _, row := range resp.Columns { - nullable := "default" - unique := "default" - - if row.Nullable.Specified { - if row.Nullable.Value { - nullable = "yes" - } else { - nullable = "no" - } - } - - if row.Unique.Specified { - if row.Unique.Value { - unique = "yes" - } else { - unique = "no" - } - } - - _ = t.AddRowItems(row.Name, row.Type, row.Size, nullable, unique) - } - - t.Print(ui.OutputFormat) + formatColumnPropertiesAsText(resp) } else { _ = commandOutput(resp) } @@ -181,6 +148,45 @@ func TableShow(c *cli.Context) error { return err } +// Given a column info response object, format it as text output that shows the +// columns of the associated table and their properties. +func formatColumnPropertiesAsText(resp defs.TableColumnsInfo) { + t, _ := tables.New([]string{ + i18n.L("Name"), + i18n.L("Type"), + i18n.L("Size"), + i18n.L("Nullable"), + i18n.L("Unique"), + }) + _ = t.SetOrderBy(i18n.L("Name")) + _ = t.SetAlignment(2, tables.AlignmentRight) + + for _, row := range resp.Columns { + nullable := "default" + unique := "default" + + if row.Nullable.Specified { + if row.Nullable.Value { + nullable = "yes" + } else { + nullable = "no" + } + } + + if row.Unique.Specified { + if row.Unique.Value { + unique = "yes" + } else { + unique = "no" + } + } + + _ = t.AddRowItems(row.Name, row.Type, row.Size, nullable, unique) + } + + t.Print(ui.OutputFormat) +} + func TableDrop(c *cli.Context) error { var ( count int @@ -855,19 +861,9 @@ func TableSQL(c *cli.Context) error { sql = sql + " " + sqlItem } - if c.WasFound("sql-file") { - fn, _ := c.String("sql-file") - - b, err := os.ReadFile(fn) - if err != nil { - return errors.New(err) - } - - if len(sql) > 0 { - sql = sql + " " - } - - sql = sql + string(b) + err := appendSQLFileContents(c, &sql) + if err != nil { + return err } if len(strings.TrimSpace(sql)) == 0 { @@ -939,6 +935,30 @@ func TableSQL(c *cli.Context) error { return nil } +func appendSQLFileContents(c *cli.Context, sql *string) error { + if c.WasFound("sql-file") { + buff := strings.Builder{} + buff.WriteString(*sql) + + fn, _ := c.String("sql-file") + + b, err := os.ReadFile(fn) + if err != nil { + return errors.New(err) + } + + if buff.Len() > 0 { + buff.WriteRune(' ') + } + + buff.WriteString(string(b)) + + *sql = buff.String() + } + + return nil +} + func TablePermissions(c *cli.Context) error { permissions := defs.AllPermissionResponse{} url := rest.URLBuilder(defs.TablesPermissionsPath) diff --git a/compiler/expr_atom.go b/compiler/expr_atom.go index 4c1fa6e2..39f42032 100644 --- a/compiler/expr_atom.go +++ b/compiler/expr_atom.go @@ -72,67 +72,12 @@ func (c *Compiler) expressionAtom() error { // Is this address-of? if t == tokenizer.AddressToken { - c.t.Advance(1) - - // If it's address of a symbol, short-circuit that. We do not allow - // readonly symbols to be addressable. - if c.t.Peek(1).IsIdentifier() { - name := c.t.Next() - if strings.HasPrefix(name.Spelling(), defs.ReadonlyVariablePrefix) { - return c.error(errors.ErrReadOnlyAddressable, name) - } - - // If it's a type, is this an address of an initializer for a type? - if t, found := c.types[name.Spelling()]; found && c.t.Peek(1) == tokenizer.DataBeginToken { - if err := c.compileInitializer(t); err != nil { - return err - } else { - tempName := data.GenerateName() - - c.b.Emit(bytecode.StoreAlways, tempName) - c.b.Emit(bytecode.AddressOf, tempName) - - return nil - } - } - - c.b.Emit(bytecode.AddressOf, name.Spelling()) - } else { - // Address of an expression requires creating a temp symbol - if err := c.expressionAtom(); err != nil { - return err - } - - tempName := data.GenerateName() - - c.b.Emit(bytecode.StoreAlways, tempName) - c.b.Emit(bytecode.AddressOf, tempName) - } - - return nil + return c.compileAddressOf() } // 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 - if err := c.expressionAtom(); err != nil { - return err - } - - tempName := data.GenerateName() - - c.b.Emit(bytecode.StoreAlways, tempName) - c.b.Emit(bytecode.DeRef, tempName) - } - - return nil + return c.compilePointerDereference() } // Is this a parenthesis expression? @@ -220,31 +165,10 @@ func (c *Compiler) expressionAtom() error { 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 - } - } + // Skip the parentheses + err := c.compileTypeCast() + if err == nil { + return nil } c.t.Set(mark) @@ -312,6 +236,105 @@ func (c *Compiler) expressionAtom() error { return c.error(errors.ErrUnexpectedToken, t) } +func (c *Compiler) compileTypeCast() error { + var ( + err error + typeSpec *data.Type + ) + + if typeSpec, err = c.parseType("", true); err == nil { + if c.t.IsNext(tokenizer.StartOfListToken) { + 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 + } + } + } + + return err +} + +func (c *Compiler) compilePointerDereference() error { + 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 + if err := c.expressionAtom(); err != nil { + return err + } + + tempName := data.GenerateName() + + c.b.Emit(bytecode.StoreAlways, tempName) + c.b.Emit(bytecode.DeRef, tempName) + } + + return nil +} + +func (c *Compiler) compileAddressOf() error { + c.t.Advance(1) + + // If it's address of a symbol, short-circuit that. We do not allow + // readonly symbols to be addressable. + if c.t.Peek(1).IsIdentifier() { + name := c.t.Next() + if strings.HasPrefix(name.Spelling(), defs.ReadonlyVariablePrefix) { + return c.error(errors.ErrReadOnlyAddressable, name) + } + + // If it's a type, is this an address of an initializer for a type? + if t, found := c.types[name.Spelling()]; found && c.t.Peek(1) == tokenizer.DataBeginToken { + if err := c.compileInitializer(t); err != nil { + return err + } else { + tempName := data.GenerateName() + + c.b.Emit(bytecode.StoreAlways, tempName) + c.b.Emit(bytecode.AddressOf, tempName) + + return nil + } + } + + c.b.Emit(bytecode.AddressOf, name.Spelling()) + } else { + // Address of an expression requires creating a temp symbol + if err := c.expressionAtom(); err != nil { + return err + } + + tempName := data.GenerateName() + + c.b.Emit(bytecode.StoreAlways, tempName) + c.b.Emit(bytecode.AddressOf, tempName) + } + + return nil +} + func (c *Compiler) parseArray() error { var ( err error diff --git a/tests/io/db_postgres.ego b/tests/io/db_postgres.ego index 7de50ec8..4ee2e588 100644 --- a/tests/io/db_postgres.ego +++ b/tests/io/db_postgres.ego @@ -1,9 +1,10 @@ @test "io: db using local postgres" { - host, _ := os.Hostname() + // Use localhost or construct name of your postgres server system + host := "localhost" - constr := "postgres://tom:secret@" + host + ".local/tom?sslmode=disable" + constr := "postgres://tom:secret@" + host + "/tom?sslmode=disable" d := db.New(constr) // Is there a working database we can test against?