diff options
| author | Marc Vertes <mvertes@free.fr> | 2024-03-22 16:59:25 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-22 16:59:25 +0100 |
| commit | 362f7c9c45598b429c92e67756f41b690043e0c4 (patch) | |
| tree | 59dd897446880912b4a2ca4a3a8d8c49e3553211 | |
| parent | bf2d6438e95c60946c64a6692e3dae1d836364a6 (diff) | |
feat: add initial support for import, provide minimal fmt (#6)
The `import` statement is now parsed. It only provides minimal
support for the `fmt` package (only `Println` symbol is defined).
This should be sufficient to pass a few tests.
Full support of package namespaces, source and binary imports will be
supported later, based on this work.
| -rw-r--r-- | parser/compiler.go | 59 | ||||
| -rw-r--r-- | parser/decl.go | 59 | ||||
| -rw-r--r-- | parser/expr.go | 12 | ||||
| -rw-r--r-- | parser/interpreter_test.go | 18 | ||||
| -rw-r--r-- | parser/package.go | 15 | ||||
| -rw-r--r-- | parser/parse.go | 6 | ||||
| -rw-r--r-- | parser/symbol.go | 16 | ||||
| -rw-r--r-- | parser/type.go | 1 | ||||
| -rw-r--r-- | scanner/scan.go | 3 | ||||
| -rw-r--r-- | scanner/scan_test.go | 2 |
10 files changed, 155 insertions, 36 deletions
diff --git a/parser/compiler.go b/parser/compiler.go index fb79720..57e176f 100644 --- a/parser/compiler.go +++ b/parser/compiler.go @@ -86,7 +86,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { emit(int64(t.Pos), vm.Not) case lang.Plus: - // Nothing to do. + // Unary '+' is idempotent. Nothing to do. case lang.Addr: push(&symbol{Type: vm.PointerTo(pop().Type)}) @@ -109,16 +109,22 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { emit(int64(t.Pos), vm.Lower) case lang.Call: - typ := pop().Type - // TODO: pop input types (careful with variadic function) - for i := 0; i < typ.Rtype.NumOut(); i++ { - push(&symbol{Type: typ.Out(i)}) + s := pop() + if s.kind != symValue { + typ := s.Type + // TODO: pop input types (careful with variadic function). + for i := 0; i < typ.Rtype.NumOut(); i++ { + push(&symbol{Type: typ.Out(i)}) + } + emit(int64(t.Pos), vm.Call) + break } - emit(int64(t.Pos), vm.Call) + push(s) + fallthrough // A symValue must be called through callX. case lang.CallX: rtyp := pop().value.Data.Type() - // TODO: pop input types (careful with variadic function) + // TODO: pop input types (careful with variadic function). for i := 0; i < rtyp.NumOut(); i++ { push(&symbol{Type: &vm.Type{Rtype: rtyp.Out(i)}}) } @@ -128,7 +134,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { emit(int64(t.Pos), vm.Grow, int64(t.Beg)) case lang.Define: - // TODO: support assignment to local, composite objects + // TODO: support assignment to local, composite objects. st := tokens[i-1] l := len(c.Data) typ := pop().Type @@ -186,6 +192,9 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { return fmt.Errorf("symbol not found: %s", t.Str) } push(s) + if s.kind == symPkg { + break + } if s.local { emit(int64(t.Pos), vm.Fdup, int64(s.index)) } else { @@ -256,11 +265,37 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { emit(int64(t.Pos), vm.Jump, i) case lang.Period: - if f, ok := pop().Type.Rtype.FieldByName("X" + t.Str[1:]); ok { - emit(append([]int64{int64(t.Pos), vm.Field}, slint64(f.Index)...)...) - break + s := pop() + switch s.kind { + case symPkg: + p, ok := packages[s.pkgPath] + if !ok { + return fmt.Errorf("package not found: %s", s.pkgPath) + } + v, ok := p[t.Str[1:]] + if !ok { + return fmt.Errorf("symbol not found in package %s: %s", s.pkgPath, t.Str[1:]) + } + name := s.pkgPath + t.Str + var l int + sym, _, ok := c.getSym(name, "") + if ok { + l = sym.index + } else { + l = len(c.Data) + c.Data = append(c.Data, v) + c.addSym(l, name, v, symValue, v.Type, false) + sym = c.symbols[name] + } + push(sym) + emit(int64(t.Pos), vm.Dup, int64(l)) + default: + if f, ok := s.Type.Rtype.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:]) } - 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)) diff --git a/parser/decl.go b/parser/decl.go index d794131..7638b75 100644 --- a/parser/decl.go +++ b/parser/decl.go @@ -2,8 +2,10 @@ package parser import ( "errors" + "fmt" "go/constant" "go/token" + "path" "strings" "github.com/mvertes/parscan/lang" @@ -189,6 +191,63 @@ var gotok = map[lang.TokenId]token.Token{ lang.Not: token.NOT, } +func (p *Parser) ParseImport(in Tokens) (out Tokens, err error) { + if p.fname != "" { + return out, errors.New("unexpected import") + } + if len(in) < 2 { + return out, errors.New("missing expression") + } + if in[1].Id != lang.ParenBlock { + return p.parseImportLine(in[1:]) + } + if in, err = p.Scan(in[1].Block(), false); err != nil { + return out, err + } + for _, li := range in.Split(lang.Semicolon) { + ot, err := p.parseImportLine(li) + if err != nil { + return out, err + } + out = append(out, ot...) + } + return out, err +} + +func (p *Parser) parseImportLine(in Tokens) (out Tokens, err error) { + l := len(in) + if l != 1 && l != 2 { + return out, errors.New("invalid number of arguments") + } + if in[l-1].Id != lang.String { + return out, fmt.Errorf("invalid argument %v", in[0]) + } + pp := in[l-1].Block() + pkg, ok := packages[pp] + if !ok { + // TODO: try to import source package from here. + return out, fmt.Errorf("package not found: %s", pp) + } + n := in[0].Str + if l == 1 { + // Derive package name from package path. + d, f := path.Split(pp) + n = f + if ok, _ := path.Match(f, "v[0-9]*"); d != "" && ok { + n = path.Base(d) + } + } + if n == "." { + // Import package symbols in the current scope. + for k, v := range pkg { + p.symbols[k] = &symbol{index: unsetAddr, pkgPath: pp, value: v} + } + } else { + p.symbols[n] = &symbol{kind: symPkg, pkgPath: pp, index: unsetAddr} + } + return out, err +} + func (p *Parser) ParseType(in Tokens) (out Tokens, err error) { if len(in) < 2 { return out, MissingTypeErr diff --git a/parser/expr.go b/parser/expr.go index 73b860e..4145240 100644 --- a/parser/expr.go +++ b/parser/expr.go @@ -83,17 +83,7 @@ func (p *Parser) ParseExpr(in Tokens) (out Tokens, err error) { // func call: push args and func address then call out = append(out, t) vl++ - if t2 := in[i-1]; t2.Id == lang.Ident { - if s, sc, ok := p.getSym(t2.Str, p.scope); ok { - log.Println("callExpr:", t2.Str, p.scope, s, ok, sc) - if s.kind == symValue { - // Store the number of input parameters in the token Beg field. - ops = append(ops, scanner.Token{Id: lang.CallX, Pos: t.Pos, Beg: p.numItems(t.Block(), lang.Comma)}) - break - } - } - } - ops = append(ops, scanner.Token{Id: lang.Call, Pos: t.Pos}) + ops = append(ops, scanner.Token{Id: lang.Call, Pos: t.Pos, Beg: p.numItems(t.Block(), lang.Comma)}) case lang.BracketBlock: out = append(out, t) vl++ diff --git a/parser/interpreter_test.go b/parser/interpreter_test.go index df44fae..a459359 100644 --- a/parser/interpreter_test.go +++ b/parser/interpreter_test.go @@ -211,7 +211,7 @@ func TestStruct(t *testing.T) { } func TestType(t *testing.T) { - src0 := `type( + src0 := `type ( I int S string ) @@ -239,3 +239,19 @@ func TestVar(t *testing.T) { ); a+b+c`, res: "16"}, }) } + +func TestImport(t *testing.T) { + src0 := `import ( + "fmt" +) +` + run(t, []etest{ + {src: "fmt.Println(4)", err: "symbol not found: fmt"}, + {src: `import "xxx"`, err: "package not found: xxx"}, + {src: `import "fmt"; fmt.Println(4)`, res: "<nil>"}, + {src: src0 + "fmt.Println(4)", res: "<nil>"}, + {src: `func main() {import "fmt"; fmt.Println("hello")}`, err: "unexpected import"}, + {src: `import m "fmt"; m.Println(4)`, res: "<nil>"}, + {src: `import . "fmt"; Println(4)`, res: "<nil>"}, + }) +} diff --git a/parser/package.go b/parser/package.go new file mode 100644 index 0000000..f03c59f --- /dev/null +++ b/parser/package.go @@ -0,0 +1,15 @@ +package parser + +import ( + "fmt" + + "github.com/mvertes/parscan/vm" +) + +var packages = map[string]map[string]vm.Value{ + "fmt": fmtPkg, +} + +var fmtPkg = map[string]vm.Value{ + "Println": vm.ValueOf(fmt.Println), +} diff --git a/parser/parse.go b/parser/parse.go index 95f9af3..ffcb8e2 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -67,10 +67,10 @@ func (p *Parser) ParseStmts(in Tokens) (out Tokens, err error) { } func (p *Parser) ParseStmt(in Tokens) (out Tokens, err error) { - log.Println("ParseStmt in:", in, len(in)) if len(in) == 0 { return nil, nil } + log.Println("ParseStmt in:", in) switch t := in[0]; t.Id { case lang.Break: return p.ParseBreak(in) @@ -82,12 +82,14 @@ func (p *Parser) ParseStmt(in Tokens) (out Tokens, err error) { return p.ParseFor(in) case lang.Func: return p.ParseFunc(in) - case lang.Defer, lang.Go, lang.Fallthrough, lang.Import, lang.Select: + case lang.Defer, lang.Go, lang.Fallthrough, lang.Select: return out, fmt.Errorf("not yet implemented: %v", t.Id) case lang.Goto: return p.ParseGoto(in) case lang.If: return p.ParseIf(in) + case lang.Import: + return p.ParseImport(in) case lang.Package: // TODO: support packages return out, err diff --git a/parser/symbol.go b/parser/symbol.go index c75f241..c8d89db 100644 --- a/parser/symbol.go +++ b/parser/symbol.go @@ -17,18 +17,20 @@ const ( symConst // a Go constant symVar // a Go variable, located in the VM memory symFunc // a Go function, located in the VM code + symPkg // a Go package ) const unsetAddr = -65535 type symbol struct { - kind symKind - index int // address of symbol in frame - Type *vm.Type // - value vm.Value // - cval constant.Value // - local bool // if true address is relative to local frame, otherwise global - used bool // + kind symKind + index int // address of symbol in frame + pkgPath string // + Type *vm.Type // + value vm.Value // + cval constant.Value // + local bool // if true address is relative to local frame, otherwise global + used bool // } func symtype(s *symbol) *vm.Type { diff --git a/parser/type.go b/parser/type.go index cd63a46..16390e9 100644 --- a/parser/type.go +++ b/parser/type.go @@ -115,7 +115,6 @@ func (p *Parser) ParseTypeExpr(in Tokens) (typ *vm.Type, err error) { var fields []*vm.Type for _, lt := range in.Split(lang.Semicolon) { types, names, err := p.parseParamTypes(lt, parseTypeType) - fmt.Println("### Names:", names, types) if err != nil { return nil, err } diff --git a/scanner/scan.go b/scanner/scan.go index ea7fc37..4c787fb 100644 --- a/scanner/scan.go +++ b/scanner/scan.go @@ -40,7 +40,8 @@ func (t *Token) Name() string { func (t *Token) String() string { s := t.Id.String() - if t.Id.IsLiteral() || t.Id.IsBlock() || t.Id == lang.Ident || t.Id == lang.Comment { + if t.Id.IsLiteral() || t.Id.IsBlock() || t.Id == lang.Ident || t.Id == lang.Comment || + t.Id == lang.Period || t.Id == lang.Label || t.Id == lang.Goto { s += strconv.Quote(t.Str) } return s diff --git a/scanner/scan_test.go b/scanner/scan_test.go index 257b1ab..c3f62a8 100644 --- a/scanner/scan_test.go +++ b/scanner/scan_test.go @@ -130,7 +130,7 @@ def"`, tok: `Ident"f" ParenBlock"(4)" Semicolon Return Semicolon `, }, { // #28 src: "f(3).\nfield", - tok: `Ident"f" ParenBlock"(3)" Period Ident"field" Semicolon `, + tok: `Ident"f" ParenBlock"(3)" Period"." Ident"field" Semicolon `, }, { // #29 src: "\n\n\tif i < 1 {return 0}", tok: `If Ident"i" Less Int"1" BraceBlock"{return 0}" Semicolon `, |
