summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2023-11-17 16:45:58 +0100
committerMarc Vertes <mvertes@free.fr>2023-11-17 16:45:58 +0100
commitee21c324ce8c41b589e5a39e5715223ffd154315 (patch)
tree9dfe692c787ceb65d85f3a9e04524759fab31200
parenta79e558d825c5b777c95c5e098b01391ee36781e (diff)
parser: support selector expression to get / set struct fields
The structures are reresented by reflect values. New instructions `Field` and `Vassign` have been added to the VM to assign reflect values and access struct fields.
-rw-r--r--parser/README.md2
-rw-r--r--parser/compiler.go47
-rw-r--r--parser/expr.go16
-rw-r--r--parser/interpreter_test.go9
-rw-r--r--parser/symbol.go7
-rw-r--r--parser/type.go5
-rw-r--r--vm/vm.go17
7 files changed, 91 insertions, 12 deletions
diff --git a/parser/README.md b/parser/README.md
index 3fbbf61..6a1a0e0 100644
--- a/parser/README.md
+++ b/parser/README.md
@@ -77,7 +77,7 @@ Go language support:
- [x] parenthesis expressions
- [x] call expressions
- [ ] index expressions
-- [ ] selector expressions
+- [x] selector expressions
- [ ] type convertions
- [ ] type assertions
- [ ] parametric types (generic)
diff --git a/parser/compiler.go b/parser/compiler.go
index ed950cb..36fc9d4 100644
--- a/parser/compiler.go
+++ b/parser/compiler.go
@@ -36,10 +36,13 @@ func (c *Compiler) AddSym(name string, value any) int {
}
func (c *Compiler) Codegen(tokens Tokens) (err error) {
- fixList := Tokens{}
log.Println("Codegen tokens:", tokens)
+ fixList := Tokens{} // list of tokens to fix after we gathered all necessary information
+ stack := []*symbol{} // for symbolic evaluation, type checking, etc
emit := func(op ...int64) { c.Code = append(c.Code, op) }
+ push := func(s *symbol) { stack = append(stack, s) }
+ pop := func() *symbol { l := len(stack) - 1; s := stack[l]; stack = stack[:l]; return s }
for i, t := range tokens {
switch t.Id {
@@ -48,6 +51,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
if err != nil {
return err
}
+ push(&symbol{kind: symConst, value: n})
emit(int64(t.Pos), vm.Push, int64(n))
case lang.String:
@@ -58,15 +62,19 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
c.Data = append(c.Data, s)
c.strings[s] = i
}
+ push(&symbol{kind: symConst, value: s})
emit(int64(t.Pos), vm.Dup, int64(i))
case lang.Add:
+ push(&symbol{Type: arithmeticOpType(pop(), pop())})
emit(int64(t.Pos), vm.Add)
case lang.Mul:
+ push(&symbol{Type: arithmeticOpType(pop(), pop())})
emit(int64(t.Pos), vm.Mul)
case lang.Sub:
+ push(&symbol{Type: arithmeticOpType(pop(), pop())})
emit(int64(t.Pos), vm.Sub)
case lang.Greater:
@@ -95,19 +103,23 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
case lang.Assign:
st := tokens[i-1]
+ if st.Id == lang.Period {
+ emit(int64(t.Pos), vm.Vassign)
+ break
+ }
s, ok := c.symbols[st.Str]
if !ok {
return fmt.Errorf("symbol not found: %s", st.Str)
}
if s.local {
emit(int64(st.Pos), vm.Fassign, int64(s.index))
- } else {
- if s.index == unsetAddr {
- s.index = len(c.Data)
- c.Data = append(c.Data, s.value)
- }
- emit(int64(st.Pos), vm.Assign, int64(s.index))
+ break
+ }
+ if s.index == unsetAddr {
+ s.index = len(c.Data)
+ c.Data = append(c.Data, s.value)
}
+ emit(int64(st.Pos), vm.Assign, int64(s.index))
case lang.Equal:
emit(int64(t.Pos), vm.Equal)
@@ -126,6 +138,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
if !ok {
return fmt.Errorf("symbol not found: %s", t.Str)
}
+ push(s)
if s.local {
emit(int64(t.Pos), vm.Fdup, int64(s.index))
} else {
@@ -199,6 +212,13 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
}
emit(int64(t.Pos), vm.Jump, int64(i))
+ case lang.Period:
+ if f, ok := pop().Type.FieldByName("X" + t.Str[1:]); ok {
+ emit(append([]int64{int64(t.Pos), vm.Field}, slint64(f.Index)...)...)
+ break
+ }
+ return fmt.Errorf("field or method not found: %s", t.Str[1:])
+
case lang.Return:
emit(int64(t.Pos), vm.Return, int64(t.Beg), int64(t.End))
@@ -231,6 +251,11 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
return err
}
+func arithmeticOpType(s1, s2 *symbol) reflect.Type {
+ // TODO: make it complete
+ return symtype(s1)
+}
+
func (c *Compiler) PrintCode() {
labels := map[int][]string{} // labels indexed by code location
data := map[int]string{} // data indexed by frame location
@@ -296,3 +321,11 @@ func (c *Compiler) NumIn(i int) (int, bool) {
}
return -1, false
}
+
+func slint64(a []int) []int64 {
+ r := make([]int64, len(a))
+ for i, v := range a {
+ r[i] = int64(v)
+ }
+ return r
+}
diff --git a/parser/expr.go b/parser/expr.go
index 2cd19c6..c2d5eeb 100644
--- a/parser/expr.go
+++ b/parser/expr.go
@@ -10,8 +10,9 @@ import (
func (p *Parser) ParseExpr(in Tokens) (out Tokens, err error) {
log.Println("ParseExpr in:", in)
- var ops Tokens
+ var ops, selectors Tokens
var vl int
+ var selectorId string
//
// Process tokens from last to first, the goal is to reorder the tokens in
// a stack machine processing order, so it can be directly interpreted.
@@ -21,8 +22,11 @@ func (p *Parser) ParseExpr(in Tokens) (out Tokens, err error) {
// temporary assumptions: binary operators, returning 1 value
switch t.Id {
case lang.Ident:
+ if i > 0 && in[i-1].Id == lang.Period {
+ selectorId = t.Str
+ continue
+ }
// resolve symbol if not a selector rhs.
- // TODO: test for selector expr.
_, sc, ok := p.getSym(t.Str, p.scope)
if ok {
if sc != "" {
@@ -31,6 +35,10 @@ func (p *Parser) ParseExpr(in Tokens) (out Tokens, err error) {
}
out = append(out, t)
vl++
+ case lang.Period:
+ t.Str += selectorId
+ selectors = append(Tokens{t}, selectors...)
+ continue
case lang.Int, lang.String:
out = append(out, t)
vl++
@@ -64,6 +72,10 @@ func (p *Parser) ParseExpr(in Tokens) (out Tokens, err error) {
}
ops = append(ops, scanner.Token{Str: "call", Id: lang.Call, Pos: t.Pos})
}
+ if len(selectors) > 0 {
+ out = append(out, selectors...)
+ selectors = nil
+ }
if lops, lout := len(ops), len(out); lops > 0 && vl > lops {
op := ops[lops-1]
ops = ops[:lops-1]
diff --git a/parser/interpreter_test.go b/parser/interpreter_test.go
index ac8dfc4..255f7c5 100644
--- a/parser/interpreter_test.go
+++ b/parser/interpreter_test.go
@@ -180,6 +180,14 @@ func TestConst(t *testing.T) {
})
}
+func TestStruct(t *testing.T) {
+ run(t, []etest{
+ {src: "type T struct {a string; b, c int}; var t T; t", res: "{ 0 0}"},
+ {src: "type T struct {a int}; var t T; t.a", res: "0"},
+ {src: "type T struct {a int}; var t T; t.a = 1; t.a", res: "1"},
+ })
+}
+
func TestType(t *testing.T) {
src0 := `type(
I int
@@ -190,7 +198,6 @@ func TestType(t *testing.T) {
{src: "type t int; var a t = 1; a", res: "1"},
{src: "type t = int; var a t = 1; a", res: "1"},
{src: src0 + `var s S = "xx"; s`, res: "xx"},
- {src: "type T struct {a string; b, c int}; var t T; t", res: "{ 0 0}"},
})
}
diff --git a/parser/symbol.go b/parser/symbol.go
index f707feb..7edcef3 100644
--- a/parser/symbol.go
+++ b/parser/symbol.go
@@ -30,6 +30,13 @@ type symbol struct {
used bool
}
+func symtype(s *symbol) reflect.Type {
+ if s.Type != nil {
+ return s.Type
+ }
+ return reflect.TypeOf(s.value)
+}
+
func (p *Parser) AddSym(i int, name string, v any) { p.addSym(i, name, v, symValue, nil, false) }
func (p *Parser) addSym(i int, name string, v any, k symKind, t reflect.Type, local bool) {
diff --git a/parser/type.go b/parser/type.go
index f39acea..f1a2642 100644
--- a/parser/type.go
+++ b/parser/type.go
@@ -142,7 +142,10 @@ func (p *Parser) parseParamTypes(in Tokens, flag typeFlag) (types []reflect.Type
}
func (p *Parser) addSymVar(index int, name string, typ reflect.Type, flag typeFlag, local bool) {
- zv := reflect.New(typ).Elem().Interface()
+ var zv any = reflect.New(typ).Elem()
+ if typ.Kind() != reflect.Struct {
+ zv = zv.(reflect.Value).Interface()
+ }
switch flag {
case parseTypeIn:
p.addSym(-index-2, name, zv, symVar, typ, true)
diff --git a/vm/vm.go b/vm/vm.go
index f7f4d78..f6adf18 100644
--- a/vm/vm.go
+++ b/vm/vm.go
@@ -16,6 +16,7 @@ const (
Add // n1 n2 -- sum ; sum = n1+n2
Assign // val -- ; mem[$1] = val
Fassign // val -- ; mem[$1] = val
+ Vassign // val dest -- ; dest.Set(val)
Call // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = prog[f](a1, ...)
Calli // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = prog[f](a1, ...)
CallX // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = mem[f](a1, ...)
@@ -24,6 +25,7 @@ const (
Equal // n1 n2 -- cond ; cond = n1 == n2
EqualSet // n1 n2 -- n1 cond ; cond = n1 == n2
Exit // -- ;
+ Field // s -- f ; f = s.FieldIndex($1, ...)
Greater // n1 n2 -- cond; cond = n1 > n2
Grow // -- ; sp += $1
Jump // -- ; ip += $1
@@ -54,6 +56,7 @@ var strop = [...]string{ // for VM tracing.
Exit: "Exit",
Fassign: "Fassign",
Fdup: "Fdup",
+ Field: "Field",
Greater: "Greater",
Grow: "Grow",
Jump: "Jump",
@@ -69,6 +72,7 @@ var strop = [...]string{ // for VM tracing.
Return: "Return",
Sub: "Sub",
Subi: "Subi",
+ Vassign: "Vassign",
}
type Code [][]int64
@@ -161,6 +165,8 @@ func (m *Machine) Run() (err error) {
return err
case Fdup:
mem = append(mem, mem[int(op[2])+fp-1])
+ case Field:
+ mem[sp-1] = mem[sp-1].(reflect.Value).FieldByIndex(slint(op[2:]))
case Jump:
ip += int(op[2])
continue
@@ -219,6 +225,9 @@ func (m *Machine) Run() (err error) {
mem = mem[:sp-1]
case Subi:
mem[sp-1] = mem[sp-1].(int) - int(op[2])
+ case Vassign:
+ mem[sp-1].(reflect.Value).Set(reflect.ValueOf(mem[sp-2]))
+ mem = mem[:sp-2]
}
ip++
}
@@ -265,3 +274,11 @@ func Disassemble(code [][]int64) (asm string) {
}
return asm
}
+
+func slint(a []int64) []int {
+ r := make([]int, len(a))
+ for i, v := range a {
+ r[i] = int(v)
+ }
+ return r
+}