summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2026-01-14 18:44:17 +0100
committerMarc Vertes <mvertes@free.fr>2026-01-14 18:44:17 +0100
commit31e3793202402fda21905027c18ebfa5c8d8c832 (patch)
treedbe52ae31fada099bc65fcd9c7d61f6266c6ba78
parentde3baf0e06862f0420950f025b3328068f3b6df2 (diff)
fix: improve handling of indirections
Added 2 VM instructions, FnewE variant of Fnew with a dereference, and FieldE, variant of Field with dereference. It's now possible to pointers in structs and literal composites.
-rw-r--r--comp/compiler.go55
-rw-r--r--interp/interpreter_test.go4
-rw-r--r--lang/golang/go.go2
-rw-r--r--symbol/symbol.go10
-rw-r--r--vm/op_string.go64
-rw-r--r--vm/type.go3
-rw-r--r--vm/vm.go11
7 files changed, 97 insertions, 52 deletions
diff --git a/comp/compiler.go b/comp/compiler.go
index 8a136e1..8a7db22 100644
--- a/comp/compiler.go
+++ b/comp/compiler.go
@@ -99,15 +99,15 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
emit(t, vm.Dup, i)
case lang.Add:
- push(&symbol.Symbol{Type: arithmeticOpType(pop(), pop())})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: arithmeticOpType(pop(), pop())})
emit(t, vm.Add)
case lang.Mul:
- push(&symbol.Symbol{Type: arithmeticOpType(pop(), pop())})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: arithmeticOpType(pop(), pop())})
emit(t, vm.Mul)
case lang.Sub:
- push(&symbol.Symbol{Type: arithmeticOpType(pop(), pop())})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: arithmeticOpType(pop(), pop())})
emit(t, vm.Sub)
case lang.Minus:
@@ -120,11 +120,11 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
// Unary '+' is idempotent. Nothing to do.
case lang.Addr:
- push(&symbol.Symbol{Type: vm.PointerTo(pop().Type)})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: vm.PointerTo(pop().Type)})
emit(t, vm.Addr)
case lang.Deref:
- push(&symbol.Symbol{Type: pop().Type.Elem()})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: pop().Type.Elem()})
emit(t, vm.Deref)
case lang.Index:
@@ -136,14 +136,14 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
} else {
emit(t, vm.Index)
}
- push(&symbol.Symbol{Type: s.Type.Elem()})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: s.Type.Elem()})
case lang.Greater:
- push(&symbol.Symbol{Type: booleanOpType(pop(), pop())})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: booleanOpType(pop(), pop())})
emit(t, vm.Greater)
case lang.Less:
- push(&symbol.Symbol{Type: booleanOpType(pop(), pop())})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: booleanOpType(pop(), pop())})
emit(t, vm.Lower)
case lang.Call:
@@ -159,7 +159,7 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
pop()
}
for i := 0; i < typ.Rtype.NumOut(); i++ {
- push(&symbol.Symbol{Type: typ.Out(i)})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: typ.Out(i)})
}
emit(t, vm.Call, narg)
@@ -173,7 +173,7 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
rtyp := s.Value.Value.Type()
// TODO: pop input types (careful with variadic function).
for i := 0; i < rtyp.NumOut(); i++ {
- push(&symbol.Symbol{Type: &vm.Type{Rtype: rtyp.Out(i)}})
+ push(&symbol.Symbol{Kind: symbol.Value, Type: &vm.Type{Rtype: rtyp.Out(i)}})
}
emit(t, vm.CallX, t.Beg)
@@ -182,6 +182,10 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
pop()
ks := pop()
ts := top()
+ if ts.IsPtr() {
+ // Resolve index on the element type
+ ts = &symbol.Symbol{Kind: symbol.Value, Type: &vm.Type{Rtype: ts.Type.Rtype.Elem()}}
+ }
switch ks.Kind {
case symbol.Const:
switch ts.Type.Rtype.Kind() {
@@ -190,9 +194,10 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
emit(t, vm.FieldFset)
}
case reflect.Slice:
- if v := ks.Value.Value; v.CanInt() {
- emit(t, vm.IndexSet)
+ if ts.Type.Elem().IsPtr() {
+ emit(t, vm.Addr)
}
+ emit(t, vm.IndexSet)
case reflect.Map:
emit(t, vm.MapSet)
}
@@ -276,9 +281,12 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
c.Data = append(c.Data, s.Value)
}
if s.Kind == symbol.Type {
- if s.Type.Rtype.Kind() == reflect.Slice {
+ switch s.Type.Rtype.Kind() {
+ case reflect.Slice:
emit(t, vm.Fnew, s.Index, s.SliceLen)
- } else {
+ case reflect.Pointer:
+ emit(t, vm.FnewE, s.Index, 1)
+ default:
emit(t, vm.Fnew, s.Index, 1)
}
} else {
@@ -357,6 +365,7 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
if len(stack) < 1 {
return errorf("missing symbol")
}
+ showStack()
s := pop()
switch s.Kind {
case symbol.Pkg:
@@ -384,9 +393,21 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
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...)
- push(&symbol.Symbol{Type: s.Type.FieldType(t.Str[1:])})
+ // FIXME: handle pointer indirection here
+ log.Println("## XXX", s.Type, s.Type.IsPtr())
+ typ := s.Type.Rtype
+ isPtr := typ.Kind() == reflect.Pointer
+ if isPtr {
+ typ = typ.Elem()
+ }
+ if f, ok := typ.FieldByName(t.Str[1:]); ok {
+ if isPtr {
+ emit(t, vm.FieldE, f.Index...)
+ push(&symbol.Symbol{Type: s.Type.Elem().FieldType(t.Str[1:])})
+ } else {
+ emit(t, vm.Field, f.Index...)
+ push(&symbol.Symbol{Type: s.Type.FieldType(t.Str[1:])})
+ }
break
}
return fmt.Errorf("field or method not found: %s", t.Str[1:])
diff --git a/interp/interpreter_test.go b/interp/interpreter_test.go
index 0cb4160..bf90357 100644
--- a/interp/interpreter_test.go
+++ b/interp/interpreter_test.go
@@ -214,6 +214,8 @@ func TestStruct(t *testing.T) {
{src: "type T struct {a string; b, c int}; var t T; t", res: "{ 0 0}"}, // #00
{src: "type T struct {a int}; var t T; t.a", res: "0"}, // #01
{src: "type T struct {a int}; var t T; t.a = 1; t.a", res: "1"}, // #02
+ {src: "type T struct {a int}; var t T = T{1}; t.a", res: "1"}, // #03
+ {src: "type T struct {a int}; var t *T = &T{1}; t.a", res: "1"}, // #04
})
}
@@ -289,5 +291,7 @@ func TestComposite(t *testing.T) {
{src: `type T struct {b bool}; m := []T{{true}}; m`, res: `[{true}]`}, // #12
{src: `m := []struct{b bool}{{true}}; m`, res: `[{true}]`}, // #13
{src: `m := map[int]struct{b bool}{1:{true}}; m`, res: `map[1:{true}]`}, // #14
+ {src: `type T *struct {b bool}; m := []T{{true}}; m[0]`, res: `&{true}`}, // #15
+ {src: `type T *struct {b bool}; m := []T{{true}}; m[0].b`, res: `true`}, // #16
})
}
diff --git a/lang/golang/go.go b/lang/golang/go.go
index 2085c3b..0d60e8b 100644
--- a/lang/golang/go.go
+++ b/lang/golang/go.go
@@ -136,7 +136,7 @@ var GoSpec = &lang.Spec{
lang.Minus: {Precedence: 6},
lang.Not: {Precedence: 6},
lang.Call: {Precedence: 6},
- lang.Index: {Precedence: 6},
+ lang.Index: {Precedence: 8},
lang.Period: {Precedence: 7},
lang.Colon: {Precedence: 7},
lang.Inc: {SkipSemi: true},
diff --git a/symbol/symbol.go b/symbol/symbol.go
index 2ed8907..5e8610a 100644
--- a/symbol/symbol.go
+++ b/symbol/symbol.go
@@ -4,6 +4,7 @@ package symbol
import (
"fmt"
"go/constant"
+ "reflect"
"strings"
"github.com/mvertes/parscan/vm"
@@ -47,15 +48,18 @@ type Symbol struct {
// return fmt.Sprintf("{Kind: %v, Name: %v, Index: %v, Type: %v}\n", s.Kind, s.Name, s.Index, s.Type)
//}
-// IsConst return true if symbol is a constant.
+// IsConst returns true if symbol is a constant.
func (s *Symbol) IsConst() bool { return s.Kind == Const }
-// IsType return true if symbol is a type.
+// IsType returns true if symbol is a type.
func (s *Symbol) IsType() bool { return s.Kind == Type }
-// IsFunc return true if symbol is a function.
+// IsFunc returns true if symbol is a function.
func (s *Symbol) IsFunc() bool { return s.Kind == Func }
+// IsPtr returns true if symbol is a pointer.
+func (s *Symbol) IsPtr() bool { return s.Type.Rtype.Kind() == reflect.Pointer }
+
// Vtype returns the VM type of a symbol.
func Vtype(s *Symbol) *vm.Type {
if s.Type != nil {
diff --git a/vm/op_string.go b/vm/op_string.go
index e838628..5542c6e 100644
--- a/vm/op_string.go
+++ b/vm/op_string.go
@@ -21,40 +21,42 @@ func _() {
_ = x[Dup-10]
_ = x[Fdup-11]
_ = x[Fnew-12]
- _ = x[Equal-13]
- _ = x[EqualSet-14]
- _ = x[Exit-15]
- _ = x[Field-16]
- _ = x[FieldSet-17]
- _ = x[FieldFset-18]
- _ = x[Greater-19]
- _ = x[Grow-20]
- _ = x[Index-21]
- _ = x[IndexSet-22]
- _ = x[MapIndex-23]
- _ = x[MapSet-24]
- _ = x[Jump-25]
- _ = x[JumpTrue-26]
- _ = x[JumpFalse-27]
- _ = x[JumpSetTrue-28]
- _ = x[JumpSetFalse-29]
- _ = x[Lower-30]
- _ = x[Loweri-31]
- _ = x[Mul-32]
- _ = x[New-33]
- _ = x[Negate-34]
- _ = x[Not-35]
- _ = x[Pop-36]
- _ = x[Push-37]
- _ = x[Return-38]
- _ = x[Sub-39]
- _ = x[Subi-40]
- _ = x[Swap-41]
+ _ = x[FnewE-13]
+ _ = x[Equal-14]
+ _ = x[EqualSet-15]
+ _ = x[Exit-16]
+ _ = x[Field-17]
+ _ = x[FieldE-18]
+ _ = x[FieldSet-19]
+ _ = x[FieldFset-20]
+ _ = x[Greater-21]
+ _ = x[Grow-22]
+ _ = x[Index-23]
+ _ = x[IndexSet-24]
+ _ = x[MapIndex-25]
+ _ = x[MapSet-26]
+ _ = x[Jump-27]
+ _ = x[JumpTrue-28]
+ _ = x[JumpFalse-29]
+ _ = x[JumpSetTrue-30]
+ _ = x[JumpSetFalse-31]
+ _ = x[Lower-32]
+ _ = x[Loweri-33]
+ _ = x[Mul-34]
+ _ = x[New-35]
+ _ = x[Negate-36]
+ _ = x[Not-37]
+ _ = x[Pop-38]
+ _ = x[Push-39]
+ _ = x[Return-40]
+ _ = x[Sub-41]
+ _ = x[Subi-42]
+ _ = x[Swap-43]
}
-const _Op_name = "NopAddAddrAssignFassignVassignCallCalliCallXDerefDupFdupFnewEqualEqualSetExitFieldFieldSetFieldFsetGreaterGrowIndexIndexSetMapIndexMapSetJumpJumpTrueJumpFalseJumpSetTrueJumpSetFalseLowerLoweriMulNewNegateNotPopPushReturnSubSubiSwap"
+const _Op_name = "NopAddAddrAssignFassignVassignCallCalliCallXDerefDupFdupFnewFnewEEqualEqualSetExitFieldFieldEFieldSetFieldFsetGreaterGrowIndexIndexSetMapIndexMapSetJumpJumpTrueJumpFalseJumpSetTrueJumpSetFalseLowerLoweriMulNewNegateNotPopPushReturnSubSubiSwap"
-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, 123, 131, 137, 141, 149, 158, 169, 181, 186, 192, 195, 198, 204, 207, 210, 214, 220, 223, 227, 231}
+var _Op_index = [...]uint8{0, 3, 6, 10, 16, 23, 30, 34, 39, 44, 49, 52, 56, 60, 65, 70, 78, 82, 87, 93, 101, 110, 117, 121, 126, 134, 142, 148, 152, 160, 169, 180, 192, 197, 203, 206, 209, 215, 218, 221, 225, 231, 234, 238, 242}
func (i Op) String() string {
idx := int(i) - 0
diff --git a/vm/type.go b/vm/type.go
index 88e3dc0..58b6c46 100644
--- a/vm/type.go
+++ b/vm/type.go
@@ -132,3 +132,6 @@ func (t *Type) FieldType(name string) *Type {
}
return nil
}
+
+// IsPtr returns true if type t is of pointer kind.
+func (t *Type) IsPtr() bool { return t.Rtype.Kind() == reflect.Pointer }
diff --git a/vm/vm.go b/vm/vm.go
index 2baf384..3f04bf6 100644
--- a/vm/vm.go
+++ b/vm/vm.go
@@ -32,10 +32,12 @@ const (
Dup // addr -- value ; value = mem[addr]
Fdup // addr -- value ; value = mem[addr]
Fnew // -- x; x = new mem[$1]
+ FnewE // -- x; x = new mem[$1].Elem()
Equal // n1 n2 -- cond ; cond = n1 == n2
EqualSet // n1 n2 -- n1 cond ; cond = n1 == n2
Exit // -- ;
Field // s -- f ; f = s.FieldIndex($1, ...)
+ FieldE // 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
@@ -173,6 +175,8 @@ func (m *Machine) Run() (err error) {
mem = append(mem, mem[c.Arg[0]+fp-1])
case Fnew:
mem = append(mem, NewValue(mem[c.Arg[0]].Type, c.Arg[1:]...))
+ case FnewE:
+ mem = append(mem, NewValue(mem[c.Arg[0]].Type.Elem(), c.Arg[1:]...))
case Field:
fv := mem[sp-1].FieldByIndex(c.Arg)
if !fv.CanSet() {
@@ -180,6 +184,13 @@ func (m *Machine) Run() (err error) {
fv = reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem()
}
mem[sp-1].Value = fv
+ case FieldE:
+ fv := mem[sp-1].Value.Elem().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()
+ }
+ mem[sp-1].Value = fv
case FieldSet:
fv := mem[sp-2].FieldByIndex(c.Arg)
if !fv.CanSet() {