diff options
| author | Marc Vertes <mvertes@free.fr> | 2025-03-03 10:49:27 +0100 |
|---|---|---|
| committer | Marc Vertes <mvertes@free.fr> | 2025-03-03 10:49:27 +0100 |
| commit | ab69cd9ba61092650abdff6484b12021182385ce (patch) | |
| tree | 4805a0365fc481df57c8f0b5d45f7f993b9a0ef9 | |
| parent | dabd9e5eb81bbc9aeaeb32fb3e3ce83eef258a77 (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.go | 52 | ||||
| -rw-r--r-- | parser/decl.go | 14 | ||||
| -rw-r--r-- | parser/parse.go | 29 | ||||
| -rw-r--r-- | parser/symbol.go | 22 | ||||
| -rw-r--r-- | parser/type.go | 4 | ||||
| -rw-r--r-- | vm/type.go | 11 | ||||
| -rw-r--r-- | vm/vm.go | 8 |
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 } } @@ -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)} @@ -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 |
