diff --git a/bytecode/callframe.go b/bytecode/callframe.go index 91985f41..dfe833de 100644 --- a/bytecode/callframe.go +++ b/bytecode/callframe.go @@ -46,6 +46,14 @@ const ( func (f *CallFrame) String() string { name := f.Module + if name == "" { + name = f.name + } + + if name != "" && f.Package != "" { + name = f.Package + "." + name + } + if name == "" { name = defs.Anon } @@ -71,7 +79,7 @@ func (c *Context) callframePushWithTable(table *symbols.SymbolTable, bc *ByteCod blockDepth: c.blockDepth, singleStep: c.singleStep, tokenizer: c.tokenizer, - thisStack: c.thisStack, + thisStack: c.receiverStack, deferStack: c.deferStack, pc: c.programCounter, fp: c.framePointer, @@ -137,7 +145,7 @@ func (c *Context) callFramePop() error { c.symbols = callFrame.symbols c.singleStep = callFrame.singleStep c.tokenizer = callFrame.tokenizer - c.thisStack = callFrame.thisStack + c.receiverStack = callFrame.thisStack c.bc = callFrame.bytecode c.programCounter = callFrame.pc c.framePointer = callFrame.fp diff --git a/bytecode/context.go b/bytecode/context.go index 401d11c0..f0a92b50 100644 --- a/bytecode/context.go +++ b/bytecode/context.go @@ -49,6 +49,9 @@ type deferStatement struct { // Function target target interface{} + // Receiver stack in effect for this defer statement + receiverStack []this + // Arguments args []interface{} } @@ -66,7 +69,7 @@ type Context struct { tryStack []tryInfo rangeStack []*rangeDefinition timerStack []time.Time - thisStack []this + receiverStack []this packageStack []packageDef deferStack []deferStatement output *strings.Builder @@ -82,6 +85,7 @@ type Context struct { lastLine int blockDepth int argCountDelta int + deferThisSize int threadID int32 fullSymbolScope bool running bool @@ -146,7 +150,7 @@ func NewContext(s *symbols.SymbolTable, b *ByteCode) *Context { line: 0, symbols: s, fullSymbolScope: true, - thisStack: nil, + receiverStack: nil, deferStack: make([]deferStatement, 0), throwUncheckedErrors: settings.GetBool(defs.ThrowUncheckedErrorsSetting), fullStackTrace: settings.GetBool(defs.FullStackTraceSetting), diff --git a/bytecode/defer.go b/bytecode/defer.go index 3992f60c..a13da255 100644 --- a/bytecode/defer.go +++ b/bytecode/defer.go @@ -7,6 +7,12 @@ import ( "github.com/tucats/ego/errors" ) +func deferStartByteCode(c *Context, i interface{}) error { + c.deferThisSize = len(c.receiverStack) + + return nil +} + // deferByteCode is the bytecode for the defer statement. This captures // the arguments to the function as well as the function target object, // and stores them in the runtime context. This is then executed when @@ -32,11 +38,24 @@ func deferByteCode(c *Context, i interface{}) error { return err } + var receivers []this + + // If at the start of the defer we captured the size of the receiver stack + // and the receiver stack size is now larger, it means the defer exprssion + // included receiver values. We need to capture these values and store them + // in the defer stack object. + if c.deferThisSize > 0 && (c.deferThisSize < len(c.receiverStack)) { + // Capture the slide of the this stack since we started the defer. + receivers = c.receiverStack[len(c.receiverStack)-c.deferThisSize:] + c.receiverStack = c.receiverStack[:c.deferThisSize] + } + // Push the function and arguments onto the defer stack c.deferStack = append(c.deferStack, deferStatement{ - target: f, - args: args, - name: name, + target: f, + receiverStack: receivers, + args: args, + name: name, }) return nil @@ -57,23 +76,25 @@ func runDefersByteCode(c *Context, i interface{}) error { func (c *Context) invokeDeferredStatements() error { // Run the deferred statements in reverse order. for i := len(c.deferStack) - 1; i >= 0; i-- { - ds := c.deferStack[i] + deferTask := c.deferStack[i] // Create a new bytecode area to execute the defer operations. - cb := New("defer " + ds.name) + cb := New("defer " + deferTask.name) // Push the target function onto the stack - cb.Emit(Push, ds.target) + cb.Emit(Push, deferTask.target) // Push the arguments in reverse order from the stack - for j := len(ds.args) - 1; j >= 0; j-- { - cb.Emit(Push, ds.args[j]) + for j := len(deferTask.args) - 1; j >= 0; j-- { + cb.Emit(Push, deferTask.args[j]) } - cb.Emit(Call, len(ds.args)) + cb.Emit(Call, len(deferTask.args)) - // Create a context for executing the deferred statement + // Create a context for executing the deferred statement. The context + // is given the active receiver stack associated with the defer statement. cx := NewContext(c.symbols, cb) + cx.receiverStack = deferTask.receiverStack // Execute the deferred statement if err := cx.Run(); err != nil && err != errors.ErrStop { diff --git a/bytecode/flow.go b/bytecode/flow.go index 5ceb4215..37e42ab4 100644 --- a/bytecode/flow.go +++ b/bytecode/flow.go @@ -153,6 +153,7 @@ func atLineByteCode(c *Context, i interface{}) error { text := c.tokenizer.GetLine(c.line) if len(strings.TrimSpace(text)) > 0 { location := fmt.Sprintf("line %d", c.line) + ui.Log(ui.TraceLogger, "(%d) >>> %-19s %s", c.threadID, location, strings.TrimSpace(text)) } } @@ -165,11 +166,11 @@ func atLineByteCode(c *Context, i interface{}) error { // 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 { + if len(c.receiverStack) == 0 { return nil } - this := c.thisStack[len(c.thisStack)-1] + this := c.receiverStack[len(c.receiverStack)-1] if pkg, ok := this.value.(*data.Package); ok { if s, ok := pkg.Get(data.SymbolsMDKey); ok { diff --git a/bytecode/instruction_test.go b/bytecode/instruction_test.go index bb65d981..1ae0fc85 100644 --- a/bytecode/instruction_test.go +++ b/bytecode/instruction_test.go @@ -34,7 +34,7 @@ func Test_instruction_String(t *testing.T) { name: "token operand", Operation: Push, Operand: tokenizer.BoolToken, - want: "Push kind struct Type(bool)", + want: "Push Type(bool)", }, } for _, tt := range tests { diff --git a/bytecode/opcodes.go b/bytecode/opcodes.go index 560cf7f8..d5e8236b 100644 --- a/bytecode/opcodes.go +++ b/bytecode/opcodes.go @@ -55,6 +55,7 @@ const ( Copy CreateAndStore Defer + DeferStart DeRef Div Drop @@ -183,6 +184,7 @@ var opcodeNames = map[Opcode]string{ Copy: "Copy", CreateAndStore: "CreateAndStore", Defer: "Defer", + DeferStart: "DeferStart", DeRef: "DeRef", Div: "Div", Drop: "Drop", @@ -308,6 +310,7 @@ func initializeDispatch() { dispatchTable[Copy] = copyByteCode dispatchTable[CreateAndStore] = createAndStoreByteCode dispatchTable[Defer] = deferByteCode + dispatchTable[DeferStart] = deferStartByteCode dispatchTable[DeRef] = deRefByteCode dispatchTable[Div] = divideByteCode dispatchTable[Drop] = dropByteCode diff --git a/bytecode/stack.go b/bytecode/stack.go index 9ebebb5c..13924cad 100644 --- a/bytecode/stack.go +++ b/bytecode/stack.go @@ -81,7 +81,7 @@ func isStackMarker(i interface{}, values ...string) bool { // Produce a string reprsentation of a stack marker. func (sm StackMarker) String() string { b := strings.Builder{} - b.WriteString("M<") + b.WriteString("Marker<") b.WriteString(sm.label) for _, data := range sm.values { diff --git a/bytecode/stack_test.go b/bytecode/stack_test.go index 6515413c..d05b0809 100644 --- a/bytecode/stack_test.go +++ b/bytecode/stack_test.go @@ -57,17 +57,17 @@ func TestStackMarker_String(t *testing.T) { { name: "simple marker", marker: NewStackMarker("test"), - want: "M", + want: "Marker", }, { name: "marker with one item", marker: NewStackMarker("test", 33), - want: "M", + want: "Marker", }, { name: "marker with multiple items", marker: NewStackMarker("test", 33, "foo", true), - want: "M", + want: "Marker", }, } for _, tt := range tests { diff --git a/bytecode/symbols.go b/bytecode/symbols.go index 92722903..0aac8caa 100644 --- a/bytecode/symbols.go +++ b/bytecode/symbols.go @@ -145,7 +145,7 @@ func popScopeByteCode(c *Context, i interface{}) error { return errors.New(err) } - c.thisStack = nil + c.receiverStack = nil c.blockDepth-- count-- diff --git a/bytecode/this.go b/bytecode/this.go index db26b14d..7c5c8055 100644 --- a/bytecode/this.go +++ b/bytecode/this.go @@ -36,19 +36,31 @@ func setThisByteCode(c *Context, i interface{}) error { // loadThisByteCode implements the LoadThis opcode. This combines the // functionality of the Load followed by the SetThis opcodes. func loadThisByteCode(c *Context, i interface{}) error { - name := data.String(i) - if len(name) == 0 { - return c.error(errors.ErrInvalidIdentifier) - } + var ( + found bool + this interface{} + ) + + // If the operand is a name, look up the value in the + // symbol table. If it's not a name, then it's a value for + // a receiver and we use it as-is. + if name, ok := i.(string); ok { + if len(name) == 0 { + return c.error(errors.ErrInvalidIdentifier) + } - v, found := c.get(name) - if !found { - return c.error(errors.ErrUnknownIdentifier).Context(name) + this, found = c.get(name) + if !found { + return c.error(errors.ErrUnknownIdentifier).Context(name) + } + } else { + this = i } - _ = c.push(v) - name = data.GenerateName() - c.setAlways(name, v) + // Assign the value to a geenated name and put it on the reciver stack. + _ = c.push(this) + name := data.GenerateName() + c.setAlways(name, this) if v, ok := c.get(name); ok { c.pushThis(name, v) @@ -73,21 +85,21 @@ func getThisByteCode(c *Context, i interface{}) error { // pushThis adds a receiver value to the "this" stack. func (c *Context) pushThis(name string, v interface{}) { - if c.thisStack == nil { - c.thisStack = []this{} + if c.receiverStack == nil { + c.receiverStack = []this{} } - c.thisStack = append(c.thisStack, this{name, v}) + c.receiverStack = append(c.receiverStack, this{name, v}) } // popThis removes a receiver value from this "this" stack. func (c *Context) popThis() (interface{}, bool) { - if len(c.thisStack) == 0 { + if len(c.receiverStack) == 0 { return nil, false } - this := c.thisStack[len(c.thisStack)-1] - c.thisStack = c.thisStack[:len(c.thisStack)-1] + this := c.receiverStack[len(c.receiverStack)-1] + c.receiverStack = c.receiverStack[:len(c.receiverStack)-1] return this.value, true } diff --git a/commands/run.go b/commands/run.go index 969c442a..e7e41509 100644 --- a/commands/run.go +++ b/commands/run.go @@ -400,7 +400,12 @@ func runREPL(interactive bool, extensions bool, text string, debug bool, lineNum // Compile the token stream we have accumulated, using the entrypoint name provided by // the user (or defaulting to "main"). - b, err = comp.Compile("main '"+mainName+"'", t) + label := "console" + if mainName != "" { + label = "main '" + mainName + "'" + } + + b, err = comp.Compile(label, t) if err != nil { exitValue = 1 msg := fmt.Sprintf("%s: %s\n", i18n.L("Error"), err.Error()) diff --git a/compiler/defer.go b/compiler/defer.go index 9c0c488f..97fc6a4c 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -23,10 +23,14 @@ func (c *Compiler) compileDefer() error { return c.error(errors.ErrDeferOutsideFunction) } - if c.t.AnyNext(tokenizer.SemicolonToken, tokenizer.EndOfTokens) { + if c.t.EndofStatement() { return c.error(errors.ErrMissingFunction) } + // Mark the start of the defer statement, which also captures + // state that the defer statement will use. + c.b.Emit(bc.DeferStart) + // Is it a function constant? if c.t.IsNext(tokenizer.FuncToken) { // Compile a function literal onto the stack. @@ -61,4 +65,3 @@ func (c *Compiler) compileDefer() error { return nil } - diff --git a/data/constant.go b/data/constant.go index dd62dba1..d34db86c 100644 --- a/data/constant.go +++ b/data/constant.go @@ -36,5 +36,5 @@ func UnwrapConstant(i interface{}) interface{} { // String generates a human-readable string describing the value // in the immutable wrapper. func (w Immutable) String() string { - return fmt.Sprintf("%s ", Format(w.Value)) + return fmt.Sprintf("^%s", Format(w.Value)) } diff --git a/data/constructors.go b/data/constructors.go index f4d089bd..f5d7be3f 100644 --- a/data/constructors.go +++ b/data/constructors.go @@ -207,7 +207,7 @@ func PackageType(name string) *Type { // the required functions in the Interface specification. func NewInterfaceType(name string) *Type { if name == "" { - name = "interface{}" + name = InterfaceTypeName } t := &Type{ diff --git a/data/format.go b/data/format.go index c54ab0a8..c491aad8 100644 --- a/data/format.go +++ b/data/format.go @@ -16,6 +16,15 @@ import ( var verbose = false +// This is a map of well known types and their corresponding format strings. +var knownTypes = map[string]string{ + "tokenizer.Token": "%v", + "Tokenizer.Tokenizer": "%v", + "bytecode.StackMarker": "%v", + "bytecode.CallFrame": "Frame<%v>", + "bytecode.Immutable": "%v", +} + // FormatUnquoted formats a value but does not put quotes on strings. func FormatUnquoted(arg interface{}) string { if arg == nil { @@ -89,7 +98,7 @@ func FormatWithType(element interface{}) string { fmtString := Format(element) // For things already formatted with a type prefix, don't add to the string. - for _, prefix := range []string{"[", "M<", "F<", "Pkg<"} { + for _, prefix := range []string{"[", "Marker<", "Frame<", "Package<"} { if strings.HasPrefix(fmtString, prefix) { return fmtString } @@ -119,8 +128,12 @@ func FormatWithType(element interface{}) string { } // It's some kind of more complex type, output the type and the value(s) + ts := TypeOf(element).String() + if ts == InterfaceTypeName { + return fmtString + } - return TypeOf(element).String() + "{" + fmtString + "}" + return ts + "{" + fmtString + "}" } // Format a value as a human-readable value, such as you would see from fmt.Printf() @@ -135,14 +148,14 @@ func Format(element interface{}) string { // Interface object case Interface: if v.BaseType == nil { - return "interface{}" + return InterfaceTypeName } return "interface{ " + v.BaseType.String() + " " + Format(v.Value) + " }" // Immutable object case Immutable: - return "constant{ " + Format(v.Value) + " }" + return "^" + Format(v.Value) // Built-in types case Type: @@ -195,9 +208,6 @@ func Format(element interface{}) string { case *Declaration: return v.String() - case Declaration: - return v.String() - case *Package: return formatPackageAsString(v) @@ -248,6 +258,19 @@ func Format(element interface{}) string { return text.String() + case []interface{}: + text := strings.Builder{} + + for i := 0; i < len(v); i++ { + if i > 0 { + text.WriteString(", ") + } + + text.WriteString(Format(v[i])) + } + + return text.String() + default: return formatNativeGoValue(v) } @@ -258,7 +281,7 @@ func formatPackageAsString(v *Package) string { keys := v.Keys() - b.WriteString("Pkg<") + b.WriteString("Package<") b.WriteString(strconv.Quote(v.Name)) if v.Builtins { @@ -329,6 +352,45 @@ func formatNativeGoValue(v interface{}) string { return name } + ts := strings.TrimPrefix(vv.String(), "<") + for key, format := range knownTypes { + if strings.Contains(ts, key) { + return fmt.Sprintf(format, v) + } + } + + if vv.Kind() == reflect.Struct { + // Use reflection to format the contents of the structure vv + var b strings.Builder + + ts := vv.String() + ts = strings.TrimPrefix(ts, "<") + ts = strings.TrimSuffix(ts, " Value>") + " " + + if ts != "reflect.Value " { + b.WriteString(ts) + } + + b.WriteString("struct{") + + for i := 0; i < vv.NumField(); i++ { + field := vv.Type().Field(i) + + if i > 0 { + b.WriteString(", ") + } + + b.WriteString(field.Name) + b.WriteString(": ") + b.WriteString(vv.Field(i).Type().String()) + } + + b.WriteRune('}') + b.WriteString(fmt.Sprintf(" = %v", v)) + + return b.String() + } + if vv.Kind() == reflect.Ptr { ts := vv.String() @@ -339,19 +401,14 @@ func formatNativeGoValue(v interface{}) string { return fmt.Sprintf("%v", v) } - return "ptr " + ts + return strings.TrimPrefix(strings.TrimSuffix(ts, " Value>"), "<") + " " + formatNativeGoValue(vv.Elem()) } - if strings.HasPrefix(vv.String(), "", v) - } - - if strings.HasPrefix(vv.String(), " 0 { + b.WriteString(", ") + } + + b.WriteString(Format(v)) + } + + b.WriteByte(']') + + return b.String() +} diff --git a/tokenizer/tokenizer.go b/tokenizer/tokenizer.go index 83b3b6d1..b8d50b26 100644 --- a/tokenizer/tokenizer.go +++ b/tokenizer/tokenizer.go @@ -4,6 +4,7 @@ package tokenizer import ( + "fmt" "strconv" "strings" "time" @@ -160,3 +161,8 @@ func (t *Tokenizer) Close() { // We no longer need the token array, so free up the memory. t.Tokens = nil } + +// String returns a string representation of the token stream. +func (t *Tokenizer) String() string { + return fmt.Sprintf("Tokenizer( %d tokens, %d lines)", len(t.Tokens), len(t.Source)) +} diff --git a/tools/buildver.txt b/tools/buildver.txt index eec7571d..65bb8c22 100644 --- a/tools/buildver.txt +++ b/tools/buildver.txt @@ -1 +1 @@ -1.5-1153 +1.5-1154