summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2024-03-22 16:59:25 +0100
committerGitHub <noreply@github.com>2024-03-22 16:59:25 +0100
commit362f7c9c45598b429c92e67756f41b690043e0c4 (patch)
tree59dd897446880912b4a2ca4a3a8d8c49e3553211
parentbf2d6438e95c60946c64a6692e3dae1d836364a6 (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.go59
-rw-r--r--parser/decl.go59
-rw-r--r--parser/expr.go12
-rw-r--r--parser/interpreter_test.go18
-rw-r--r--parser/package.go15
-rw-r--r--parser/parse.go6
-rw-r--r--parser/symbol.go16
-rw-r--r--parser/type.go1
-rw-r--r--scanner/scan.go3
-rw-r--r--scanner/scan_test.go2
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 `,