summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2026-01-07 19:06:23 +0100
committerMarc Vertes <mvertes@free.fr>2026-01-07 19:06:23 +0100
commit6875facb39de63eb6353be2f700b9eacb631e9fa (patch)
tree31b8d7c8701df2f52a7b3cecc82dd775c9d9f428
parentca80eeaa812b49afea75d3084d0c62770e4a8d18 (diff)
fix: improve handling of composite literal struct expressions
-rw-r--r--comp/compiler.go61
-rw-r--r--interp/interpreter_test.go4
-rw-r--r--lang/spec.go3
-rw-r--r--lang/token_string.go5
-rw-r--r--parser/decl.go4
-rw-r--r--parser/expr.go31
-rw-r--r--symbol/kind_string.go19
-rw-r--r--symbol/symbol.go23
-rw-r--r--vm/op_string.go45
-rw-r--r--vm/vm.go14
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
diff --git a/vm/vm.go b/vm/vm.go
index caf9189..ed27148 100644
--- a/vm/vm.go
+++ b/vm/vm.go
@@ -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