summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2025-03-03 10:49:27 +0100
committerMarc Vertes <mvertes@free.fr>2025-03-03 10:49:27 +0100
commitab69cd9ba61092650abdff6484b12021182385ce (patch)
tree4805a0365fc481df57c8f0b5d45f7f993b9a0ef9
parentdabd9e5eb81bbc9aeaeb32fb3e3ce83eef258a77 (diff)
fix: improve structparscan-struct
Use 'unsafe' to modify private struct fields, allowing to keep unmodified field names: before they were prefixed with a capital. Parse package statement. Provide a also a default package name for REPL and tests. The support of packages is still incomplete.
-rw-r--r--parser/compiler.go52
-rw-r--r--parser/decl.go14
-rw-r--r--parser/parse.go29
-rw-r--r--parser/symbol.go22
-rw-r--r--parser/type.go4
-rw-r--r--vm/type.go11
-rw-r--r--vm/vm.go8
7 files changed, 93 insertions, 47 deletions
diff --git a/parser/compiler.go b/parser/compiler.go
index 05462eb..634409e 100644
--- a/parser/compiler.go
+++ b/parser/compiler.go
@@ -25,7 +25,7 @@ type Compiler struct {
// NewCompiler returns a new compiler state for a given scanner.
func NewCompiler(scanner *scanner.Scanner) *Compiler {
return &Compiler{
- Parser: &Parser{Scanner: scanner, symbols: initUniverse(), framelen: map[string]int{}, labelCount: map[string]int{}},
+ Parser: NewParser(scanner, true),
Entry: -1,
strings: map[string]int{},
}
@@ -56,7 +56,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
if err != nil {
return err
}
- push(&symbol{kind: symConst, value: vm.ValueOf(n), Type: vm.TypeOf(0)})
+ push(&symbol{kind: symConst, value: vm.ValueOf(n), typ: vm.TypeOf(0)})
emit(int64(t.Pos), vm.Push, int64(n))
case lang.String:
@@ -72,15 +72,15 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
emit(int64(t.Pos), vm.Dup, int64(i))
case lang.Add:
- push(&symbol{Type: arithmeticOpType(pop(), pop())})
+ push(&symbol{typ: arithmeticOpType(pop(), pop())})
emit(int64(t.Pos), vm.Add)
case lang.Mul:
- push(&symbol{Type: arithmeticOpType(pop(), pop())})
+ push(&symbol{typ: arithmeticOpType(pop(), pop())})
emit(int64(t.Pos), vm.Mul)
case lang.Sub:
- push(&symbol{Type: arithmeticOpType(pop(), pop())})
+ push(&symbol{typ: arithmeticOpType(pop(), pop())})
emit(int64(t.Pos), vm.Sub)
case lang.Minus:
@@ -94,32 +94,32 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
// Unary '+' is idempotent. Nothing to do.
case lang.Addr:
- push(&symbol{Type: vm.PointerTo(pop().Type)})
+ push(&symbol{typ: vm.PointerTo(pop().typ)})
emit(int64(t.Pos), vm.Addr)
case lang.Deref:
- push(&symbol{Type: pop().Type.Elem()})
+ push(&symbol{typ: pop().typ.Elem()})
emit(int64(t.Pos), vm.Deref)
case lang.Index:
- push(&symbol{Type: pop().Type.Elem()})
+ push(&symbol{typ: pop().typ.Elem()})
emit(int64(t.Pos), vm.Index)
case lang.Greater:
- push(&symbol{Type: booleanOpType(pop(), pop())})
+ push(&symbol{typ: booleanOpType(pop(), pop())})
emit(int64(t.Pos), vm.Greater)
case lang.Less:
- push(&symbol{Type: booleanOpType(pop(), pop())})
+ push(&symbol{typ: booleanOpType(pop(), pop())})
emit(int64(t.Pos), vm.Lower)
case lang.Call:
s := pop()
if s.kind != symValue {
- typ := s.Type
+ typ := s.typ
// TODO: pop input types (careful with variadic function).
for i := 0; i < typ.Rtype.NumOut(); i++ {
- push(&symbol{Type: typ.Out(i)})
+ push(&symbol{typ: typ.Out(i)})
}
emit(int64(t.Pos), vm.Call)
break
@@ -131,7 +131,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
rtyp := pop().value.Data.Type()
// TODO: pop input types (careful with variadic function).
for i := 0; i < rtyp.NumOut(); i++ {
- push(&symbol{Type: &vm.Type{Rtype: rtyp.Out(i)}})
+ push(&symbol{typ: &vm.Type{Rtype: rtyp.Out(i)}})
}
emit(int64(t.Pos), vm.CallX, int64(t.Beg))
@@ -146,7 +146,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
st := tokens[i-1]
l := len(c.Data)
d := pop()
- typ := d.Type
+ typ := d.typ
if typ == nil {
typ = d.value.Type
}
@@ -166,17 +166,17 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
return fmt.Errorf("symbol not found: %s", st.Str)
}
d := pop()
- typ := d.Type
+ typ := d.typ
if typ == nil {
typ = d.value.Type
}
- if s.Type == nil {
- s.Type = typ
+ if s.typ == nil {
+ s.typ = typ
s.value = vm.NewValue(typ)
}
if s.local {
if !s.used {
- emit(int64(st.Pos), vm.New, int64(s.index), int64(c.typeSym(s.Type).index))
+ emit(int64(st.Pos), vm.New, int64(s.index), int64(c.typeSym(s.typ).index))
s.used = true
}
emit(int64(st.Pos), vm.Fassign, int64(s.index))
@@ -189,11 +189,11 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
emit(int64(st.Pos), vm.Assign, int64(s.index))
case lang.Equal:
- push(&symbol{Type: booleanOpType(pop(), pop())})
+ push(&symbol{typ: booleanOpType(pop(), pop())})
emit(int64(t.Pos), vm.Equal)
case lang.EqualSet:
- push(&symbol{Type: booleanOpType(pop(), pop())})
+ push(&symbol{typ: booleanOpType(pop(), pop())})
emit(int64(t.Pos), vm.EqualSet)
case lang.Ident:
@@ -306,7 +306,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
push(sym)
emit(int64(t.Pos), vm.Dup, int64(l))
default:
- if f, ok := s.Type.Rtype.FieldByName("X" + t.Str[1:]); ok {
+ if f, ok := s.typ.Rtype.FieldByName(t.Str[1:]); ok {
emit(append([]int64{int64(t.Pos), vm.Field}, slint64(f.Index)...)...)
break
}
@@ -388,7 +388,7 @@ func (e entry) String() string {
e.symbol.local,
e.symbol.index,
e.symbol.kind,
- e.symbol.Type,
+ e.symbol.typ,
e.symbol.value,
)
}
@@ -448,7 +448,7 @@ func (c *Compiler) Dump() *Dump {
Index: e.index,
Name: e.name,
Kind: int(e.kind),
- Type: e.Type.Name,
+ Type: e.typ.Name,
Value: d.Data.Interface(),
}
}
@@ -467,13 +467,13 @@ func (c *Compiler) ApplyDump(d *Dump) error {
}
if dv.Name != e.name ||
- dv.Type != e.Type.Name ||
+ dv.Type != e.typ.Name ||
dv.Kind != int(e.kind) {
return fmt.Errorf("entry with index %d does not match with provided entry. "+
"dumpValue: %s, %s, %d. memoryValue: %s, %s, %d",
dv.Index,
dv.Name, dv.Type, dv.Kind,
- e.name, e.Type, e.kind)
+ e.name, e.typ, e.kind)
}
if dv.Index >= len(c.Data) {
@@ -493,7 +493,7 @@ func (c *Compiler) ApplyDump(d *Dump) error {
func (c *Compiler) typeSym(t *vm.Type) *symbol {
tsym, ok := c.symbols[t.Rtype.String()]
if !ok {
- tsym = &symbol{index: unsetAddr, kind: symType, Type: t}
+ tsym = &symbol{index: unsetAddr, kind: symType, typ: t}
c.symbols[t.Rtype.String()] = tsym
}
if tsym.index == unsetAddr {
diff --git a/parser/decl.go b/parser/decl.go
index 23b5ed6..094fa9f 100644
--- a/parser/decl.go
+++ b/parser/decl.go
@@ -248,6 +248,20 @@ func (p *Parser) parseImportLine(in Tokens) (out Tokens, err error) {
return out, err
}
+func (p *Parser) parsePackage(in Tokens) (out Tokens, err error) {
+ if len(in) != 2 {
+ return out, errors.New("invalid number of arguments")
+ }
+ if in[1].Tok != lang.Ident {
+ return out, errors.New("not an ident")
+ }
+ if p.pkgName != "" {
+ return out, errors.New("package already set")
+ }
+ p.pkgName = in[1].Str
+ return out, err
+}
+
func (p *Parser) parseType(in Tokens) (out Tokens, err error) {
if len(in) < 2 {
return out, ErrMissingType
diff --git a/parser/parse.go b/parser/parse.go
index 537b68e..2aa5ce7 100644
--- a/parser/parse.go
+++ b/parser/parse.go
@@ -20,6 +20,8 @@ type Parser struct {
function *symbol
scope string
fname string
+ pkgName string // current package name
+ noPkg bool // true if package statement is not mandatory (test, repl).
funcScope string
framelen map[string]int // length of function frames indexed by funcScope
@@ -38,6 +40,17 @@ var (
ErrGoto = errors.New("invalid goto statement")
)
+// NewParser returns a new parser.
+func NewParser(scanner *scanner.Scanner, noPkg bool) *Parser {
+ return &Parser{
+ Scanner: scanner,
+ noPkg: noPkg,
+ symbols: initUniverse(),
+ framelen: map[string]int{},
+ labelCount: map[string]int{},
+ }
+}
+
// Scan performs lexical analysis on s and returns Tokens or an error.
func (p *Parser) Scan(s string, endSemi bool) (Tokens, error) {
return p.Scanner.Scan(s, endSemi)
@@ -84,6 +97,14 @@ func (p *Parser) parseStmt(in Tokens) (out Tokens, err error) {
return nil, nil
}
log.Println("parseStmt in:", in)
+ // Preliminary: make sure that a pkgName is defined (or about to be).
+ if in[0].Tok != lang.Package && p.pkgName == "" {
+ if !p.noPkg {
+ return out, errors.New("no package defined")
+ }
+ p.pkgName = "main"
+ }
+
switch t := in[0]; t.Tok {
case lang.Break:
return p.parseBreak(in)
@@ -104,7 +125,7 @@ func (p *Parser) parseStmt(in Tokens) (out Tokens, err error) {
case lang.Import:
return p.parseImports(in)
case lang.Package:
- // TODO: support packages
+ return p.parsePackage(in)
return out, err
case lang.Return:
return p.parseReturn(in)
@@ -263,7 +284,7 @@ func (p *Parser) parseFunc(in Tokens) (out Tokens, err error) {
return out, err
}
s.kind = symFunc
- s.Type = typ
+ s.typ = typ
p.function = s
toks, err := p.Parse(in[len(in)-1].Block())
@@ -459,8 +480,8 @@ func (p *Parser) parseReturn(in Tokens) (out Tokens, err error) {
// TODO: the function symbol should be already present in the parser context.
// otherwise no way to handle anonymous func.
s := p.function
- in[0].Beg = s.Type.Rtype.NumOut()
- in[0].End = s.Type.Rtype.NumIn()
+ in[0].Beg = s.typ.Rtype.NumOut()
+ in[0].End = s.typ.Rtype.NumIn()
out = append(out, in[0])
return out, err
}
diff --git a/parser/symbol.go b/parser/symbol.go
index 499e121..a491073 100644
--- a/parser/symbol.go
+++ b/parser/symbol.go
@@ -26,7 +26,7 @@ type symbol struct {
kind symKind
index int // address of symbol in frame
pkgPath string //
- Type *vm.Type //
+ typ *vm.Type //
value vm.Value //
cval constant.Value //
local bool // if true address is relative to local frame, otherwise global
@@ -34,8 +34,8 @@ type symbol struct {
}
func symtype(s *symbol) *vm.Type {
- if s.Type != nil {
- return s.Type
+ if s.typ != nil {
+ return s.typ
}
return vm.TypeOf(s.value)
}
@@ -47,7 +47,7 @@ func (p *Parser) AddSym(i int, name string, v vm.Value) {
func (p *Parser) addSym(i int, name string, v vm.Value, k symKind, t *vm.Type, local bool) {
name = strings.TrimPrefix(name, "/")
- p.symbols[name] = &symbol{kind: k, index: i, local: local, value: v, Type: t}
+ p.symbols[name] = &symbol{kind: k, index: i, local: local, value: v, typ: t}
}
// getSym searches for an existing symbol starting from the deepest scope.
@@ -70,16 +70,16 @@ func (p *Parser) getSym(name, scope string) (sym *symbol, sc string, ok bool) {
func initUniverse() map[string]*symbol {
return map[string]*symbol{
- "any": {kind: symType, index: unsetAddr, Type: vm.TypeOf((*any)(nil)).Elem()},
- "bool": {kind: symType, index: unsetAddr, Type: vm.TypeOf((*bool)(nil)).Elem()},
- "error": {kind: symType, index: unsetAddr, Type: vm.TypeOf((*error)(nil)).Elem()},
- "int": {kind: symType, index: unsetAddr, Type: vm.TypeOf((*int)(nil)).Elem()},
- "string": {kind: symType, index: unsetAddr, Type: vm.TypeOf((*string)(nil)).Elem()},
+ "any": {kind: symType, index: unsetAddr, typ: vm.TypeOf((*any)(nil)).Elem()},
+ "bool": {kind: symType, index: unsetAddr, typ: vm.TypeOf((*bool)(nil)).Elem()},
+ "error": {kind: symType, index: unsetAddr, typ: vm.TypeOf((*error)(nil)).Elem()},
+ "int": {kind: symType, index: unsetAddr, typ: vm.TypeOf((*int)(nil)).Elem()},
+ "string": {kind: symType, index: unsetAddr, typ: vm.TypeOf((*string)(nil)).Elem()},
"nil": {index: unsetAddr},
"iota": {kind: symConst, index: unsetAddr},
- "true": {index: unsetAddr, value: vm.ValueOf(true), Type: vm.TypeOf(true)},
- "false": {index: unsetAddr, value: vm.ValueOf(false), Type: vm.TypeOf(false)},
+ "true": {index: unsetAddr, value: vm.ValueOf(true), typ: vm.TypeOf(true)},
+ "false": {index: unsetAddr, value: vm.ValueOf(false), typ: vm.TypeOf(false)},
"println": {index: unsetAddr, value: vm.ValueOf(func(v ...any) { fmt.Println(v...) })},
}
diff --git a/parser/type.go b/parser/type.go
index e25431a..b1b25dc 100644
--- a/parser/type.go
+++ b/parser/type.go
@@ -104,7 +104,7 @@ func (p *Parser) parseTypeExpr(in Tokens) (typ *vm.Type, err error) {
if !ok || s.kind != symType {
return nil, fmt.Errorf("%w: %s", ErrInvalidType, in[0].Str)
}
- return s.Type, nil
+ return s.typ, nil
case lang.Struct:
if len(in) != 2 || in[1].Tok != lang.BraceBlock {
@@ -120,7 +120,7 @@ func (p *Parser) parseTypeExpr(in Tokens) (typ *vm.Type, err error) {
return nil, err
}
for i, name := range names {
- fields = append(fields, &vm.Type{Name: name, Rtype: types[i].Rtype})
+ fields = append(fields, &vm.Type{Name: name, PkgPath: p.pkgName, Rtype: types[i].Rtype})
// TODO: handle embedded fields
}
}
diff --git a/vm/type.go b/vm/type.go
index 49215db..7c33cd2 100644
--- a/vm/type.go
+++ b/vm/type.go
@@ -6,12 +6,16 @@ import "reflect"
// Type is the representation of a runtime type.
type Type struct {
- Name string
- Rtype reflect.Type
+ PkgPath string
+ Name string
+ Rtype reflect.Type
}
func (t *Type) String() string {
if t.Name != "" {
+ if t.PkgPath != "" {
+ return t.PkgPath + "." + t.Name
+ }
return t.Name
}
return t.Rtype.String()
@@ -84,7 +88,8 @@ func FuncOf(arg, ret []*Type, variadic bool) *Type {
func StructOf(fields []*Type) *Type {
rf := make([]reflect.StructField, len(fields))
for i, f := range fields {
- rf[i].Name = "X" + f.Name
+ rf[i].Name = f.Name
+ rf[i].PkgPath = f.PkgPath
rf[i].Type = f.Rtype
}
return &Type{Rtype: reflect.StructOf(rf)}
diff --git a/vm/vm.go b/vm/vm.go
index 408be6e..3cb9358 100644
--- a/vm/vm.go
+++ b/vm/vm.go
@@ -6,6 +6,7 @@ import (
"log" // for tracing only
"reflect" // for optional CallX only
"strconv" // for tracing only
+ "unsafe" // to allow setting unexported struct fields
)
const debug = true
@@ -184,7 +185,12 @@ func (m *Machine) Run() (err error) {
case Fdup:
mem = append(mem, mem[int(op[2])+fp-1])
case Field:
- mem[sp-1].Data = mem[sp-1].Data.FieldByIndex(slint(op[2:]))
+ fv := mem[sp-1].Data.FieldByIndex(slint(op[2:]))
+ 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].Data = fv
case Jump:
ip += int(op[2])
continue