diff options
| author | Marc Vertes <mvertes@free.fr> | 2023-11-10 22:08:46 +0100 |
|---|---|---|
| committer | Marc Vertes <mvertes@free.fr> | 2023-11-10 22:08:46 +0100 |
| commit | bec71a19e7d7cd0847d1cfa6ef2110d7301fcdd1 (patch) | |
| tree | 7d977e9ad86c119603db88d3fdbf689a845ff8e5 | |
| parent | 5220ccb741c7f3688731d3b3df6e5e851f50f5c5 (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.md | 2 | ||||
| -rw-r--r-- | parser/compiler.go | 12 | ||||
| -rw-r--r-- | parser/decl.go | 65 | ||||
| -rw-r--r-- | parser/interpreter_test.go | 29 | ||||
| -rw-r--r-- | parser/parse.go | 58 | ||||
| -rw-r--r-- | parser/symbol.go | 23 | ||||
| -rw-r--r-- | parser/tokens.go | 57 | ||||
| -rw-r--r-- | parser/type.go | 67 |
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. |
