diff options
| author | Marc Vertes <mvertes@free.fr> | 2026-01-07 19:06:23 +0100 |
|---|---|---|
| committer | Marc Vertes <mvertes@free.fr> | 2026-01-07 19:06:23 +0100 |
| commit | 6875facb39de63eb6353be2f700b9eacb631e9fa (patch) | |
| tree | 31b8d7c8701df2f52a7b3cecc82dd775c9d9f428 | |
| parent | ca80eeaa812b49afea75d3084d0c62770e4a8d18 (diff) | |
fix: improve handling of composite literal struct expressions
| -rw-r--r-- | comp/compiler.go | 61 | ||||
| -rw-r--r-- | interp/interpreter_test.go | 4 | ||||
| -rw-r--r-- | lang/spec.go | 3 | ||||
| -rw-r--r-- | lang/token_string.go | 5 | ||||
| -rw-r--r-- | parser/decl.go | 4 | ||||
| -rw-r--r-- | parser/expr.go | 31 | ||||
| -rw-r--r-- | symbol/kind_string.go | 19 | ||||
| -rw-r--r-- | symbol/symbol.go | 23 | ||||
| -rw-r--r-- | vm/op_string.go | 45 | ||||
| -rw-r--r-- | vm/vm.go | 14 |
10 files changed, 109 insertions, 100 deletions
diff --git a/comp/compiler.go b/comp/compiler.go index dd37788..9d787b6 100644 --- a/comp/compiler.go +++ b/comp/compiler.go @@ -57,7 +57,6 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) { fixList := parser.Tokens{} // list of tokens to fix after all necessary information is gathered stack := []*symbol.Symbol{} // for symbolic evaluation and type checking flen := []int{} // stack length according to function scopes - keyList := []string{} emit := func(t scanner.Token, op vm.Op, arg ...int) { _, file, line, _ := runtime.Caller(1) @@ -65,6 +64,7 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) { c.Code = append(c.Code, vm.Instruction{Pos: vm.Pos(t.Pos), Op: op, Arg: arg}) } push := func(s *symbol.Symbol) { stack = append(stack, s) } + top := func() *symbol.Symbol { return stack[len(stack)-1] } pop := func() *symbol.Symbol { l := len(stack) - 1; s := stack[l]; stack = stack[:l]; return s } popflen := func() int { le := len(flen) - 1; l := flen[le]; flen = flen[:le]; return l } @@ -76,7 +76,7 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) { } } - for i, t := range tokens { + for _, t := range tokens { switch t.Tok { case lang.Int: n, err := strconv.Atoi(t.Str) @@ -173,48 +173,20 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) { emit(t, vm.CallX, t.Beg) case lang.Colon: - // Keyed element in a literal composite expression: - // If the key is an ident (field name), then push it on the keystack, - // to be computed at compile time in Composite handling. - // Or generate instructions so key will be computed at runtime. showStack() - s, ok := c.Symbols[t.Str] - if ok { - j := s.Type.FieldIndex(tokens[i-2].Str) - log.Println("### fieldIndex", tokens[i-2].Str, j) + pop() + ks := pop() + switch ks.Kind { + case symbol.Const: + if v := ks.Value.Value; v.CanInt() { + emit(t, vm.FieldFset) + } + case symbol.Unset: + j := top().Type.FieldIndex(ks.Name) emit(t, vm.FieldSet, j...) } case lang.Composite: - showStack() - d := c.Symbols[t.Str] - switch d.Type.Rtype.Kind() { - case reflect.Struct: - emit(t, vm.Fnew, d.Index) - if len(keyList) == 0 { - nf := d.Type.Rtype.NumField() - for i := 0; i < nf; i++ { - emit(t, vm.FieldSet, i) - } - } else { - for _, fname := range keyList { - i := d.Type.FieldIndex(fname) - emit(t, vm.FieldSet, i...) - } - keyList = []string{} - } - case reflect.Slice: - emit(t, vm.Fnew, d.Index) - default: - return fmt.Errorf("composite kind not supported yet: %v", d.Type.Rtype.Kind()) - } - for j := len(stack) - 1; j >= 0; j-- { - // pop until type - if stack[j].Kind == symbol.Type { - stack = stack[:j+1] - break - } - } case lang.Grow: emit(t, vm.Grow, t.Beg) @@ -261,13 +233,12 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) { case lang.Ident: s, ok := c.Symbols[t.Str] if !ok { - // return errorf("symbol not found: %s", t.Str) // it could be either an undefined symbol or a key ident in a literal composite expr. - continue + s = &symbol.Symbol{Name: t.Str} } log.Println("Ident symbol", t.Str, s.Local, s.Index, s.Type) push(s) - if s.Kind == symbol.Pkg { + if s.Kind == symbol.Pkg || s.Kind == symbol.Unset { break } if s.Local { @@ -277,7 +248,9 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) { s.Index = len(c.Data) c.Data = append(c.Data, s.Value) } - if s.Kind != symbol.Type { + if s.Kind == symbol.Type { + emit(t, vm.Fnew, s.Index) + } else { emit(t, vm.Dup, s.Index) } } @@ -378,6 +351,8 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) { } push(sym) emit(t, vm.Dup, l) + case symbol.Unset: + return errorf("invalid symbol: %s", s.Name) default: if f, ok := s.Type.Rtype.FieldByName(t.Str[1:]); ok { emit(t, vm.Field, f.Index...) diff --git a/interp/interpreter_test.go b/interp/interpreter_test.go index e1e7107..5d0dd55 100644 --- a/interp/interpreter_test.go +++ b/interp/interpreter_test.go @@ -251,7 +251,7 @@ func TestImport(t *testing.T) { ) ` run(t, []etest{ - {src: "fmt.Println(4)", err: "missing symbol"}, // #00 + {src: "fmt.Println(4)", err: "invalid symbol: fmt"}, // #00 {src: `import "xxx"`, err: "package not found: xxx"}, // #01 {src: `import "fmt"; fmt.Println(4)`, res: "<nil>"}, // #02 {src: src0 + "fmt.Println(4)", res: "<nil>"}, // #03 @@ -268,7 +268,7 @@ func TestComposite(t *testing.T) { {src: `type T struct {}; var t T; t = T{}; t`, res: "{}"}, // #02 {src: `type T struct{N int; S string}; var t T; t = T{2, "foo"}; t`, res: `{2 foo}`}, // #03 {src: `type T struct{N int; S string}; t := T{2, "foo"}; t`, res: `{2 foo}`}, // #04 - // {src: `type T struct{N int; S string}; t := T{S: "foo"}; t`, res: `{0 foo}`}, // #05 + {src: `type T struct{N int; S string}; t := T{S: "foo"}; t`, res: `{0 foo}`}, // #05 // {src: `a := []int{}`, res: `[]`}, // #06 // {src: `a := []int{1, 2, 3}`, res: `[1 2 3]`}, // #07 }) diff --git a/lang/spec.go b/lang/spec.go index b8539e3..7cd748f 100644 --- a/lang/spec.go +++ b/lang/spec.go @@ -37,7 +37,7 @@ type TokenProp struct { Token SkipSemi bool // automatic semicolon insertion after newline Precedence int // operator precedence - Associativity // + Associativity // associativity of operator HasInit bool // true if may have an init clause } @@ -51,5 +51,4 @@ type Spec struct { DotNum bool // true if a number can start with '.' IdentASCII bool // true if an identifier can be in ASCII only NumUnder bool // true if a number can contain _ character - // TokenProps map[string]TokenProp // token properties } diff --git a/lang/token_string.go b/lang/token_string.go index fcceaeb..44215b3 100644 --- a/lang/token_string.go +++ b/lang/token_string.go @@ -102,11 +102,12 @@ func _() { _ = x[JumpSetTrue-91] _ = x[Label-92] _ = x[New-93] + _ = x[MaxTok-94] } -const _Token_name = "IllegalCommentIdentCharFloatImagIntStringAddSubMulQuoRemAndOrXorShlShrAndNotPeriodEqualGreaterGreaterEqualLandLessLessEqualLorNotEqualDefineAssignAddAssignSubAssignMulAssignQuoAssignRemAssignAndAssignOrAssignXorAssignShlAssignShrAssignAndNotAssignIncDecPlusMinusAddrDerefBitCompArrowEllipsisNotTildeCommaSemicolonColonParenBlockBracketBlockBraceBlockBreakCaseChanConstContinueDefaultDeferElseFallthroughForFuncGoGotoIfImportInterfaceMapPackageRangeReturnSelectStructSwitchTypeVarCallCallXCompositeEqualSetGrowIndexJumpFalseJumpSetFalseJumpSetTrueLabelNew" +const _Token_name = "IllegalCommentIdentCharFloatImagIntStringAddSubMulQuoRemAndOrXorShlShrAndNotPeriodEqualGreaterGreaterEqualLandLessLessEqualLorNotEqualDefineAssignAddAssignSubAssignMulAssignQuoAssignRemAssignAndAssignOrAssignXorAssignShlAssignShrAssignAndNotAssignIncDecPlusMinusAddrDerefBitCompArrowEllipsisNotTildeCommaSemicolonColonParenBlockBracketBlockBraceBlockBreakCaseChanConstContinueDefaultDeferElseFallthroughForFuncGoGotoIfImportInterfaceMapPackageRangeReturnSelectStructSwitchTypeVarCallCallXCompositeEqualSetGrowIndexJumpFalseJumpSetFalseJumpSetTrueLabelNewMaxTok" -var _Token_index = [...]uint16{0, 7, 14, 19, 23, 28, 32, 35, 41, 44, 47, 50, 53, 56, 59, 61, 64, 67, 70, 76, 82, 87, 94, 106, 110, 114, 123, 126, 134, 140, 146, 155, 164, 173, 182, 191, 200, 208, 217, 226, 235, 247, 250, 253, 257, 262, 266, 271, 278, 283, 291, 294, 299, 304, 313, 318, 328, 340, 350, 355, 359, 363, 368, 376, 383, 388, 392, 403, 406, 410, 412, 416, 418, 424, 433, 436, 443, 448, 454, 460, 466, 472, 476, 479, 483, 488, 497, 505, 509, 514, 523, 535, 546, 551, 554} +var _Token_index = [...]uint16{0, 7, 14, 19, 23, 28, 32, 35, 41, 44, 47, 50, 53, 56, 59, 61, 64, 67, 70, 76, 82, 87, 94, 106, 110, 114, 123, 126, 134, 140, 146, 155, 164, 173, 182, 191, 200, 208, 217, 226, 235, 247, 250, 253, 257, 262, 266, 271, 278, 283, 291, 294, 299, 304, 313, 318, 328, 340, 350, 355, 359, 363, 368, 376, 383, 388, 392, 403, 406, 410, 412, 416, 418, 424, 433, 436, 443, 448, 454, 460, 466, 472, 476, 479, 483, 488, 497, 505, 509, 514, 523, 535, 546, 551, 554, 560} func (i Token) String() string { idx := int(i) - 0 diff --git a/parser/decl.go b/parser/decl.go index 015f4bd..e418b5e 100644 --- a/parser/decl.go +++ b/parser/decl.go @@ -243,10 +243,10 @@ func (p *Parser) parseImportLine(in Tokens) (out Tokens, err error) { if n == "." { // Import package symbols in the current scope. for k, v := range pkg { - p.Symbols[k] = &symbol.Symbol{Index: symbol.UnsetAddr, PkgPath: pp, Value: v} + p.Symbols[k] = &symbol.Symbol{Index: symbol.UnsetAddr, Name: k, Kind: symbol.Value, PkgPath: pp, Value: v} } } else { - p.Symbols[n] = &symbol.Symbol{Kind: symbol.Pkg, PkgPath: pp, Index: symbol.UnsetAddr} + p.Symbols[n] = &symbol.Symbol{Kind: symbol.Pkg, PkgPath: pp, Index: symbol.UnsetAddr, Name: n} } return out, err } diff --git a/parser/expr.go b/parser/expr.go index 3327ec5..3895ea8 100644 --- a/parser/expr.go +++ b/parser/expr.go @@ -89,9 +89,6 @@ func (p *Parser) parseExpr(in Tokens, typeStr string) (out Tokens, err error) { ops = append(ops, t) case lang.Ident: - if i < lin-1 && in[i].Tok == lang.Colon { - continue - } s, sc, ok := p.Symbols.Get(t.Str, p.scope) if ok && sc != "" { t.Str = sc + "/" + t.Str @@ -123,7 +120,7 @@ func (p *Parser) parseExpr(in Tokens, typeStr string) (out Tokens, err error) { } case lang.BraceBlock: - toks, err := p.parseExprStr(t.Block(), typeStr) + toks, err := p.parseComposite(t.Block(), typeStr) out = append(out, toks...) if err != nil { return out, err @@ -163,6 +160,32 @@ func (p *Parser) parseExpr(in Tokens, typeStr string) (out Tokens, err error) { return out, err } +func (p *Parser) parseComposite(s, typ string) (Tokens, error) { + tokens, err := p.Scan(s, false) + if err != nil { + return nil, err + } + + noColon := len(tokens) > 0 && tokens.Index(lang.Colon) == -1 + var result Tokens + for i, sub := range tokens.Split(lang.Comma) { + toks, err := p.parseExpr(sub, typ) + if err != nil { + return result, err + } + if noColon { + // Insert a numeric index key and a colon operator. + result = append(result, scanner.Token{Tok: lang.Int, Str: strconv.Itoa(i)}) + result = append(result, toks...) + result = append(result, scanner.Token{Tok: lang.Colon, Str: ":"}) + } else { + result = append(result, toks...) + } + } + + return result, nil +} + func (p *Parser) parseExprStr(s, typ string) (tokens Tokens, err error) { if tokens, err = p.Scan(s, false); err != nil { return tokens, err diff --git a/symbol/kind_string.go b/symbol/kind_string.go index 07dbd6a..61a745b 100644 --- a/symbol/kind_string.go +++ b/symbol/kind_string.go @@ -8,18 +8,19 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[Value-0] - _ = x[Type-1] - _ = x[Label-2] - _ = x[Const-3] - _ = x[Var-4] - _ = x[Func-5] - _ = x[Pkg-6] + _ = x[Unset-0] + _ = x[Value-1] + _ = x[Type-2] + _ = x[Label-3] + _ = x[Const-4] + _ = x[Var-5] + _ = x[Func-6] + _ = x[Pkg-7] } -const _Kind_name = "ValueTypeLabelConstVarFuncPkg" +const _Kind_name = "UnsetValueTypeLabelConstVarFuncPkg" -var _Kind_index = [...]uint8{0, 5, 9, 14, 19, 22, 26, 29} +var _Kind_index = [...]uint8{0, 5, 10, 14, 19, 24, 27, 31, 34} func (i Kind) String() string { idx := int(i) - 0 diff --git a/symbol/symbol.go b/symbol/symbol.go index e00b26d..96c860f 100644 --- a/symbol/symbol.go +++ b/symbol/symbol.go @@ -14,13 +14,14 @@ type Kind int // Symbol kinds. const ( - Value Kind = iota // a value defined in the runtime - Type // a type - Label // a label indicating a position in the VM code - Const // a constant - Var // a variable, located in the VM memory - Func // a function, located in the VM code - Pkg // a package + Unset Kind = iota + Value // a value defined in the runtime + Type // a type + Label // a label indicating a position in the VM code + Const // a constant + Var // a variable, located in the VM memory + Func // a function, located in the VM code + Pkg // a package ) //go:generate stringer -type=Kind @@ -97,10 +98,10 @@ func (sm SymMap) Init() { sm["int"] = &Symbol{Name: "int", Kind: Type, Index: UnsetAddr, Type: vm.TypeOf((*int)(nil)).Elem()} sm["string"] = &Symbol{Name: "string", Kind: Type, Index: UnsetAddr, Type: vm.TypeOf((*string)(nil)).Elem()} - sm["nil"] = &Symbol{Name: "nil", Index: UnsetAddr} + sm["nil"] = &Symbol{Name: "nil", Kind: Value, Index: UnsetAddr} sm["iota"] = &Symbol{Name: "iota", Kind: Const, Index: UnsetAddr} - sm["true"] = &Symbol{Name: "true", Index: UnsetAddr, Value: vm.ValueOf(true), Type: vm.TypeOf(true)} - sm["false"] = &Symbol{Name: "false", Index: UnsetAddr, Value: vm.ValueOf(false), Type: vm.TypeOf(false)} + sm["true"] = &Symbol{Name: "true", Kind: Value, Index: UnsetAddr, Value: vm.ValueOf(true), Type: vm.TypeOf(true)} + sm["false"] = &Symbol{Name: "false", Kind: Value, Index: UnsetAddr, Value: vm.ValueOf(false), Type: vm.TypeOf(false)} - sm["println"] = &Symbol{Name: "println", Index: UnsetAddr, Value: vm.ValueOf(func(v ...any) { fmt.Println(v...) })} + sm["println"] = &Symbol{Name: "println", Kind: Value, Index: UnsetAddr, Value: vm.ValueOf(func(v ...any) { fmt.Println(v...) })} } diff --git a/vm/op_string.go b/vm/op_string.go index 7eb0e5d..15cc938 100644 --- a/vm/op_string.go +++ b/vm/op_string.go @@ -26,31 +26,32 @@ func _() { _ = x[Exit-15] _ = x[Field-16] _ = x[FieldSet-17] - _ = x[Greater-18] - _ = x[Grow-19] - _ = x[Index-20] - _ = x[Jump-21] - _ = x[JumpTrue-22] - _ = x[JumpFalse-23] - _ = x[JumpSetTrue-24] - _ = x[JumpSetFalse-25] - _ = x[Lower-26] - _ = x[Loweri-27] - _ = x[Mul-28] - _ = x[New-29] - _ = x[Negate-30] - _ = x[Not-31] - _ = x[Pop-32] - _ = x[Push-33] - _ = x[Return-34] - _ = x[Sub-35] - _ = x[Subi-36] - _ = x[Swap-37] + _ = x[FieldFset-18] + _ = x[Greater-19] + _ = x[Grow-20] + _ = x[Index-21] + _ = x[Jump-22] + _ = x[JumpTrue-23] + _ = x[JumpFalse-24] + _ = x[JumpSetTrue-25] + _ = x[JumpSetFalse-26] + _ = x[Lower-27] + _ = x[Loweri-28] + _ = x[Mul-29] + _ = x[New-30] + _ = x[Negate-31] + _ = x[Not-32] + _ = x[Pop-33] + _ = x[Push-34] + _ = x[Return-35] + _ = x[Sub-36] + _ = x[Subi-37] + _ = x[Swap-38] } -const _Op_name = "NopAddAddrAssignFassignVassignCallCalliCallXDerefDupFdupFnewEqualEqualSetExitFieldFieldSetGreaterGrowIndexJumpJumpTrueJumpFalseJumpSetTrueJumpSetFalseLowerLoweriMulNewNegateNotPopPushReturnSubSubiSwap" +const _Op_name = "NopAddAddrAssignFassignVassignCallCalliCallXDerefDupFdupFnewEqualEqualSetExitFieldFieldSetFieldFsetGreaterGrowIndexJumpJumpTrueJumpFalseJumpSetTrueJumpSetFalseLowerLoweriMulNewNegateNotPopPushReturnSubSubiSwap" -var _Op_index = [...]uint8{0, 3, 6, 10, 16, 23, 30, 34, 39, 44, 49, 52, 56, 60, 65, 73, 77, 82, 90, 97, 101, 106, 110, 118, 127, 138, 150, 155, 161, 164, 167, 173, 176, 179, 183, 189, 192, 196, 200} +var _Op_index = [...]uint8{0, 3, 6, 10, 16, 23, 30, 34, 39, 44, 49, 52, 56, 60, 65, 73, 77, 82, 90, 99, 106, 110, 115, 119, 127, 136, 147, 159, 164, 170, 173, 176, 182, 185, 188, 192, 198, 201, 205, 209} func (i Op) String() string { idx := int(i) - 0 @@ -37,6 +37,7 @@ const ( Exit // -- ; Field // s -- f ; f = s.FieldIndex($1, ...) FieldSet // s d -- s ; s.FieldIndex($1, ...) = d + FieldFset // s i v -- s; s.FieldIndex(i) = v Greater // n1 n2 -- cond; cond = n1 > n2 Grow // -- ; sp += $1 Index // a i -- a[i] ; @@ -177,14 +178,21 @@ func (m *Machine) Run() (err error) { } mem[sp-1].Value = fv case FieldSet: - fv := mem[sp-1].FieldByIndex(c.Arg) + fv := mem[sp-2].FieldByIndex(c.Arg) if !fv.CanSet() { // Normally private fields can not bet set via reflect. Override this limitation. fv = reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem() } - fv.Set(mem[sp-2].Value) - mem[sp-2] = mem[sp-1] + fv.Set(mem[sp-1].Value) mem = mem[:sp-1] + case FieldFset: + fv := mem[sp-3].Field(int(mem[sp-2].Int())) + if !fv.CanSet() { + // Normally private fields can not bet set via reflect. Override this limitation. + fv = reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem() + } + fv.Set(mem[sp-1].Value) + mem = mem[:sp-2] case Jump: ip += c.Arg[0] continue |
