summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2023-11-10 22:08:46 +0100
committerMarc Vertes <mvertes@free.fr>2023-11-10 22:08:46 +0100
commitbec71a19e7d7cd0847d1cfa6ef2110d7301fcdd1 (patch)
tree7d977e9ad86c119603db88d3fdbf689a845ff8e5
parent5220ccb741c7f3688731d3b3df6e5e851f50f5c5 (diff)
parser: implement support for var declarations
The full Go syntax is supported, blocks or line, mutiple comma separated variables, assignments. In local and global frame.
-rw-r--r--parser/README.md2
-rw-r--r--parser/compiler.go12
-rw-r--r--parser/decl.go65
-rw-r--r--parser/interpreter_test.go29
-rw-r--r--parser/parse.go58
-rw-r--r--parser/symbol.go23
-rw-r--r--parser/tokens.go57
-rw-r--r--parser/type.go67
8 files changed, 229 insertions, 84 deletions
diff --git a/parser/README.md b/parser/README.md
index 0503e28..ecb7c41 100644
--- a/parser/README.md
+++ b/parser/README.md
@@ -51,7 +51,7 @@ Go language support:
- [ ] channel operations
- [x] var defined by assign :=
- [x] var assign =
-- [ ] var declaration
+- [x] var declaration
- [ ] type declaration
- [x] func declaration
- [ ] const declaration
diff --git a/parser/compiler.go b/parser/compiler.go
index e2b7823..bce83d5 100644
--- a/parser/compiler.go
+++ b/parser/compiler.go
@@ -22,7 +22,7 @@ type Compiler struct {
func NewCompiler(scanner *scanner.Scanner) *Compiler {
return &Compiler{
- Parser: &Parser{Scanner: scanner, symbols: initUniverse(), labelCount: map[string]int{}},
+ Parser: &Parser{Scanner: scanner, symbols: initUniverse(), framelen: map[string]int{}, labelCount: map[string]int{}},
Entry: -1,
strings: map[string]int{},
}
@@ -86,6 +86,9 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
case lang.CallX:
c.Emit(int64(t.Pos), vm.CallX, int64(t.Beg))
+ case lang.Grow:
+ c.Emit(int64(t.Pos), vm.Grow, int64(t.Beg))
+
case lang.Define:
// TODO: support assignment to local, composite objects
st := tokens[i-1]
@@ -104,6 +107,10 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
if s.local {
c.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)
+ }
c.Emit(int64(st.Pos), vm.Assign, int64(s.index))
}
@@ -127,8 +134,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) {
if s.local {
c.Emit(int64(t.Pos), vm.Fdup, int64(s.index))
} else {
- if s.index < 0 {
- // This global symbol is defined but not yet used. Add it to data.
+ if s.index == unsetAddr {
s.index = len(c.Data)
c.Data = append(c.Data, s.value)
}
diff --git a/parser/decl.go b/parser/decl.go
new file mode 100644
index 0000000..fa07c17
--- /dev/null
+++ b/parser/decl.go
@@ -0,0 +1,65 @@
+package parser
+
+import (
+ "errors"
+ "log"
+ "strings"
+
+ "github.com/gnolang/parscan/lang"
+ "github.com/gnolang/parscan/scanner"
+)
+
+func (p *Parser) ParseVar(in Tokens) (out Tokens, err error) {
+ if len(in) < 1 {
+ return out, errors.New("missing expression")
+ }
+ if in[1].Id != lang.ParenBlock {
+ return p.parseVarLine(in[1:])
+ }
+ if in, err = p.Scan(in[1].Block(), false); err != nil {
+ return out, err
+ }
+ for _, lt := range in.Split(lang.Semicolon) {
+ if lt, err = p.parseVarLine(lt); err != nil {
+ return out, err
+ }
+ out = append(out, lt...)
+ }
+ return out, err
+}
+
+func (p *Parser) parseVarLine(in Tokens) (out Tokens, err error) {
+ decl := in
+ var assign Tokens
+ if i := decl.Index(lang.Assign); i >= 0 {
+ assign = decl[i+1:]
+ decl = decl[:i]
+ }
+ var vars []string
+ if _, vars, err = p.parseParamTypes(decl, parseTypeVar); err != nil {
+ if errors.Is(err, missingTypeError) {
+ for _, lt := range decl.Split(lang.Comma) {
+ vars = append(vars, lt[0].Str)
+ // TODO: compute type from rhs
+ p.addSym(unsetAddr, strings.TrimPrefix(p.scope+"/"+lt[0].Str, "/"), nil, symVar, nil, false)
+ }
+ } else {
+ return out, err
+ }
+ }
+ values := assign.Split(lang.Comma)
+ if len(values) == 1 && len(values[0]) == 0 {
+ values = nil
+ }
+ log.Println("ParseVar:", vars, values, len(values))
+ for i, v := range values {
+ if v, err = p.ParseExpr(v); err != nil {
+ return out, err
+ }
+ out = append(out, v...)
+ out = append(out,
+ scanner.Token{Id: lang.Ident, Str: vars[i]},
+ scanner.Token{Id: lang.Assign})
+ }
+ return out, err
+}
diff --git a/parser/interpreter_test.go b/parser/interpreter_test.go
index 42223d4..edb6b0d 100644
--- a/parser/interpreter_test.go
+++ b/parser/interpreter_test.go
@@ -10,7 +10,10 @@ import (
"github.com/gnolang/parscan/scanner"
)
-type etest struct{ src, res, err string }
+type etest struct {
+ src, res, err string
+ skip bool
+}
var GoScanner *scanner.Scanner
@@ -21,6 +24,9 @@ func init() {
func gen(test etest) func(*testing.T) {
return func(t *testing.T) {
+ if test.skip {
+ t.Skip()
+ }
interp := parser.NewInterpreter(GoScanner)
errStr := ""
r, e := interp.Eval(test.src)
@@ -112,8 +118,8 @@ func TestFor(t *testing.T) {
}
func TestGoto(t *testing.T) {
- run(t, []etest{{
- src: `
+ run(t, []etest{
+ {src: `
func f(a int) int {
a = a+1
goto end
@@ -157,3 +163,20 @@ func TestSwitch(t *testing.T) {
{src: src1 + "f(6)", res: "0"},
})
}
+
+func TestVar(t *testing.T) {
+ run(t, []etest{
+ {src: "var a int; a", res: "0"},
+ {src: "var a, b, c int; a", res: "0"},
+ {src: "var a, b, c int; a + b", res: "0"},
+ {src: "var a, b, c int; a + b + c", res: "0"},
+ {src: "var a int = 2+1; a", res: "3"},
+ {src: "var a, b int = 2, 5; a+b", res: "7"},
+ {src: "var x = 5; x", res: "5"},
+ {src: "var a = 1; func f() int { var a, b int = 3, 4; return a+b}; a+f()", res: "8"},
+ {src: `var (
+ a, b int = 4+1, 3
+ c = 8
+); a+b+c`, res: "16"},
+ })
+}
diff --git a/parser/parse.go b/parser/parse.go
index 40cec5b..a7675b4 100644
--- a/parser/parse.go
+++ b/parser/parse.go
@@ -20,6 +20,7 @@ type Parser struct {
fname string
funcScope string
+ framelen map[string]int // length of function frames indexed by funcScope
labelCount map[string]int
breakLabel string
continueLabel string
@@ -29,55 +30,6 @@ func (p *Parser) Scan(s string, endSemi bool) (Tokens, error) {
return p.Scanner.Scan(s, endSemi)
}
-type Tokens []scanner.Token
-
-func (toks Tokens) String() (s string) {
- for _, t := range toks {
- s += fmt.Sprintf("%#v ", t.Str)
- }
- return s
-}
-
-func (toks Tokens) Index(id lang.TokenId) int {
- for i, t := range toks {
- if t.Id == id {
- return i
- }
- }
- return -1
-}
-
-func (toks Tokens) LastIndex(id lang.TokenId) int {
- for i := len(toks) - 1; i >= 0; i-- {
- if toks[i].Id == id {
- return i
- }
- }
- return -1
-}
-
-func (toks Tokens) Split(id lang.TokenId) (result []Tokens) {
- for {
- i := toks.Index(id)
- if i < 0 {
- return append(result, toks)
- }
- result = append(result, toks[:i])
- toks = toks[i+1:]
- }
-}
-
-func (toks Tokens) SplitStart(id lang.TokenId) (result []Tokens) {
- for {
- i := toks[1:].Index(id)
- if i < 0 {
- return append(result, toks)
- }
- result = append(result, toks[:i])
- toks = toks[i+1:]
- }
-}
-
func (p *Parser) Parse(src string) (out Tokens, err error) {
log.Printf("Parse src: %#v\n", src)
in, err := p.Scan(src, true)
@@ -135,6 +87,8 @@ func (p *Parser) ParseStmt(in Tokens) (out Tokens, err error) {
return p.ParseReturn(in)
case lang.Switch:
return p.ParseSwitch(in)
+ case lang.Var:
+ return p.ParseVar(in)
case lang.Ident:
if len(in) == 2 && in[1].Id == lang.Colon {
return p.ParseLabel(in)
@@ -280,14 +234,15 @@ func (p *Parser) ParseFunc(in Tokens) (out Tokens, err error) {
s.Type = typ
p.function = s
- log.Println("body:", in[len(in)-1].Block())
toks, err := p.Parse(in[len(in)-1].Block())
if err != nil {
return out, err
}
+ if l := p.framelen[p.funcScope] - 1; l > 0 {
+ out = append(out, scanner.Token{Id: lang.Grow, Beg: l})
+ }
out = append(out, toks...)
out = append(out, scanner.Token{Id: lang.Label, Str: fname + "_end"})
- log.Println("symbols", p.symbols)
return out, err
}
@@ -463,7 +418,6 @@ func (p *Parser) ParseReturn(in Tokens) (out Tokens, err error) {
s := p.function
in[0].Beg = s.Type.NumOut()
in[0].End = s.Type.NumIn()
- log.Println("ParseReturn:", p.fname, in[0])
out = append(out, in[0])
return out, err
}
diff --git a/parser/symbol.go b/parser/symbol.go
index d32bb59..d7c05f1 100644
--- a/parser/symbol.go
+++ b/parser/symbol.go
@@ -17,6 +17,8 @@ const (
symFunc // a Go function, located in the VM code
)
+const unsetAddr = -65535
+
type symbol struct {
kind symKind
index int // address of symbol in frame
@@ -29,6 +31,7 @@ type symbol struct {
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) {
+ name = strings.TrimPrefix(name, "/")
p.symbols[name] = &symbol{kind: k, index: i, local: local, value: v, Type: t, used: true}
}
@@ -52,17 +55,17 @@ 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: -1, Type: reflect.TypeOf((*any)(nil)).Elem()},
- "bool": {kind: symType, index: -1, Type: reflect.TypeOf((*bool)(nil)).Elem()},
- "error": {kind: symType, index: -1, Type: reflect.TypeOf((*error)(nil)).Elem()},
- "int": {kind: symType, index: -1, Type: reflect.TypeOf((*int)(nil)).Elem()},
- "string": {kind: symType, index: -1, Type: reflect.TypeOf((*string)(nil)).Elem()},
+ "any": {kind: symType, index: unsetAddr, Type: reflect.TypeOf((*any)(nil)).Elem()},
+ "bool": {kind: symType, index: unsetAddr, Type: reflect.TypeOf((*bool)(nil)).Elem()},
+ "error": {kind: symType, index: unsetAddr, Type: reflect.TypeOf((*error)(nil)).Elem()},
+ "int": {kind: symType, index: unsetAddr, Type: reflect.TypeOf((*int)(nil)).Elem()},
+ "string": {kind: symType, index: unsetAddr, Type: reflect.TypeOf((*string)(nil)).Elem()},
- "nil": {index: -1},
- "iota": {index: -1, value: 0},
- "true": {index: -1, value: true, Type: reflect.TypeOf(true)},
- "false": {index: -1, value: false, Type: reflect.TypeOf(false)},
+ "nil": {index: unsetAddr},
+ "iota": {index: unsetAddr, value: 0},
+ "true": {index: unsetAddr, value: true, Type: reflect.TypeOf(true)},
+ "false": {index: unsetAddr, value: false, Type: reflect.TypeOf(false)},
- "println": {index: -1, value: func(v ...any) { fmt.Println(v...) }},
+ "println": {index: unsetAddr, value: func(v ...any) { fmt.Println(v...) }},
}
}
diff --git a/parser/tokens.go b/parser/tokens.go
new file mode 100644
index 0000000..acffe58
--- /dev/null
+++ b/parser/tokens.go
@@ -0,0 +1,57 @@
+package parser
+
+import (
+ "fmt"
+
+ "github.com/gnolang/parscan/lang"
+ "github.com/gnolang/parscan/scanner"
+)
+
+type Tokens []scanner.Token
+
+func (toks Tokens) String() (s string) {
+ for _, t := range toks {
+ s += fmt.Sprintf("%#v ", t.Str)
+ }
+ return s
+}
+
+func (toks Tokens) Index(id lang.TokenId) int {
+ for i, t := range toks {
+ if t.Id == id {
+ return i
+ }
+ }
+ return -1
+}
+
+func (toks Tokens) LastIndex(id lang.TokenId) int {
+ for i := len(toks) - 1; i >= 0; i-- {
+ if toks[i].Id == id {
+ return i
+ }
+ }
+ return -1
+}
+
+func (toks Tokens) Split(id lang.TokenId) (result []Tokens) {
+ for {
+ i := toks.Index(id)
+ if i < 0 {
+ return append(result, toks)
+ }
+ result = append(result, toks[:i])
+ toks = toks[i+1:]
+ }
+}
+
+func (toks Tokens) SplitStart(id lang.TokenId) (result []Tokens) {
+ for {
+ i := toks[1:].Index(id)
+ if i < 0 {
+ return append(result, toks)
+ }
+ result = append(result, toks[:i])
+ toks = toks[i+1:]
+ }
+}
diff --git a/parser/type.go b/parser/type.go
index 4858d75..7d7d3ed 100644
--- a/parser/type.go
+++ b/parser/type.go
@@ -1,9 +1,11 @@
package parser
import (
+ "errors"
"fmt"
"log"
"reflect"
+ "strings"
"github.com/gnolang/parscan/lang"
)
@@ -36,7 +38,7 @@ func (p *Parser) ParseType(in Tokens) (typ reflect.Type, err error) {
if err != nil {
return nil, err
}
- arg, err := p.parseParamTypes(iargs, true)
+ arg, _, err := p.parseParamTypes(iargs, parseTypeIn)
if err != nil {
return nil, err
}
@@ -46,7 +48,7 @@ func (p *Parser) ParseType(in Tokens) (typ reflect.Type, err error) {
return nil, err
}
}
- ret, err := p.parseParamTypes(out, false)
+ ret, _, err := p.parseParamTypes(out, parseTypeOut)
if err != nil {
return nil, err
}
@@ -63,9 +65,19 @@ func (p *Parser) ParseType(in Tokens) (typ reflect.Type, err error) {
return typ, err
}
+type typeFlag int
+
+const (
+ parseTypeIn typeFlag = iota
+ parseTypeOut
+ parseTypeVar
+)
+
+var missingTypeError = errors.New("Missing type")
+
// parseParamTypes parses a list of comma separated typed parameters and returns a list of
// runtime types. Implicit parameter names and types are supported.
-func (p *Parser) parseParamTypes(in Tokens, arg bool) (types []reflect.Type, err error) {
+func (p *Parser) parseParamTypes(in Tokens, flag typeFlag) (types []reflect.Type, vars []string, err error) {
// Parse from right to left, to allow multiple comma separated parameters of the same type.
list := in.Split(lang.Comma)
for i := len(list) - 1; i >= 0; i-- {
@@ -74,37 +86,62 @@ func (p *Parser) parseParamTypes(in Tokens, arg bool) (types []reflect.Type, err
continue
}
param := ""
+ local := p.funcScope != ""
if p.hasFirstParam(t) {
- param = t[0].Str
+ param = strings.TrimPrefix(p.scope+"/"+t[0].Str, "/")
t = t[1:]
if len(t) == 0 {
if len(types) == 0 {
- return nil, fmt.Errorf("Invalid type %v", t[0])
+ return nil, nil, missingTypeError
}
// Type was ommitted, apply the previous one from the right.
types = append([]reflect.Type{types[0]}, types...)
- if arg {
- p.addSym(-i-2, p.scope+"/"+param, nil, symVar, types[0], true)
- } else {
- p.addSym(i, p.scope+"/"+param, nil, symVar, types[0], true)
+ zv := reflect.New(types[0]).Elem().Interface()
+ switch flag {
+ case parseTypeIn:
+ p.addSym(-i-2, param, zv, symVar, types[0], true)
+ case parseTypeOut:
+ p.addSym(p.framelen[p.funcScope], param, zv, symVar, types[0], true)
+ p.framelen[p.funcScope]++
+ case parseTypeVar:
+ if local {
+ p.addSym(p.framelen[p.funcScope], param, zv, symVar, types[0], local)
+ p.framelen[p.funcScope]++
+ } else {
+ p.addSym(unsetAddr, param, zv, symVar, types[0], local)
+ }
}
+ vars = append(vars, param)
continue
}
}
typ, err := p.ParseType(t)
if err != nil {
- return nil, err
+ return nil, nil, err
}
if param != "" {
- if arg {
- p.addSym(-i-2, p.scope+"/"+param, nil, symVar, typ, true)
- } else {
- p.addSym(i, p.scope+"/"+param, nil, symVar, typ, true)
+ zv := reflect.New(typ).Elem().Interface()
+ switch flag {
+ case parseTypeIn:
+ p.addSym(-i-2, param, zv, symVar, typ, true)
+ case parseTypeOut:
+ p.addSym(p.framelen[p.funcScope], param, zv, symVar, typ, true)
+ p.framelen[p.funcScope]++
+ case parseTypeVar:
+ if local {
+ p.addSym(p.framelen[p.funcScope], param, zv, symVar, typ, local)
+ p.framelen[p.funcScope]++
+ } else {
+ p.addSym(unsetAddr, param, zv, symVar, typ, local)
+ }
}
+ } else if flag == parseTypeOut {
+ p.framelen[p.funcScope]++
}
types = append([]reflect.Type{typ}, types...)
+ vars = append(vars, param)
}
- return types, err
+ return types, vars, err
}
// hasFirstParam returns true if the first token of a list is a parameter name.