diff options
| -rw-r--r-- | .golangci.yaml | 3 | ||||
| -rw-r--r-- | main.go | 2 | ||||
| -rw-r--r-- | parser/compiler.go | 25 | ||||
| -rw-r--r-- | parser/expr.go | 2 | ||||
| -rw-r--r-- | parser/interpreter_test.go | 2 | ||||
| -rw-r--r-- | parser/parse.go | 25 | ||||
| -rw-r--r-- | parser/type.go | 16 | ||||
| -rw-r--r-- | scanner/scan.go | 11 | ||||
| -rw-r--r-- | vm/vm_test.go | 104 |
9 files changed, 149 insertions, 41 deletions
diff --git a/.golangci.yaml b/.golangci.yaml index 64e12a5..f2191fb 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -5,6 +5,9 @@ linters: - gofumpt - gosec - misspell + - perfsprint + - prealloc - predeclared - reassign - revive + - unconvert @@ -46,7 +46,7 @@ func repl(interp Interpreter, in io.Reader) (err error) { res, err := interp.Eval(text + "\n") switch { case err == nil: - if !res.IsNil() { + if res.IsValid() { fmt.Println(": ", res) } text, prompt = "", "> " diff --git a/parser/compiler.go b/parser/compiler.go index 3b9a32d..05462eb 100644 --- a/parser/compiler.go +++ b/parser/compiler.go @@ -61,13 +61,14 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { case lang.String: s := t.Block() + v := vm.Value{Data: reflect.ValueOf(s), Type: vm.TypeOf(s)} i, ok := c.strings[s] if !ok { i = len(c.Data) - c.Data = append(c.Data, vm.ValueOf(s)) + c.Data = append(c.Data, v) c.strings[s] = i } - push(&symbol{kind: symConst, value: vm.ValueOf(s)}) + push(&symbol{kind: symConst, value: v}) emit(int64(t.Pos), vm.Dup, int64(i)) case lang.Add: @@ -144,7 +145,11 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { // TODO: support assignment to local, composite objects. st := tokens[i-1] l := len(c.Data) - typ := pop().Type + d := pop() + typ := d.Type + if typ == nil { + typ = d.value.Type + } v := vm.NewValue(typ) c.addSym(l, st.Str, v, symVar, typ, false) c.Data = append(c.Data, v) @@ -160,7 +165,11 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { if !ok { return fmt.Errorf("symbol not found: %s", st.Str) } - typ := pop().Type + d := pop() + typ := d.Type + if typ == nil { + typ = d.value.Type + } if s.Type == nil { s.Type = typ s.value = vm.NewValue(typ) @@ -259,7 +268,7 @@ func (c *Compiler) Codegen(tokens Tokens) (err error) { } else { i = s.value.Data.Int() - int64(len(c.Code)) } - emit(int64(t.Pos), vm.JumpSetTrue, int64(i)) + emit(int64(t.Pos), vm.JumpSetTrue, i) case lang.Goto: var i int64 @@ -431,17 +440,17 @@ type DumpValue struct { // This design choice allows the Virtual Machine (VM) to evolve its memory management strategies // without compromising backward compatibility with dumps generated by previous versions. func (c *Compiler) Dump() *Dump { - var dv []*DumpValue dict := c.symbolsByIndex() + dv := make([]*DumpValue, len(c.Data)) for i, d := range c.Data { e := dict[i] - dv = append(dv, &DumpValue{ + dv[i] = &DumpValue{ Index: e.index, Name: e.name, Kind: int(e.kind), Type: e.Type.Name, Value: d.Data.Interface(), - }) + } } return &Dump{Values: dv} diff --git a/parser/expr.go b/parser/expr.go index cf6ee74..1efb45e 100644 --- a/parser/expr.go +++ b/parser/expr.go @@ -110,7 +110,7 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) { case lang.Comment: return out, nil default: - return nil, fmt.Errorf("expression not supported yet: %v: %q", t.Tok, t.Str) + return nil, fmt.Errorf("invalid expression: %v: %q", t.Tok, t.Str) } if len(selectors) > 0 { out = append(out, selectors...) diff --git a/parser/interpreter_test.go b/parser/interpreter_test.go index a6ad246..314f35b 100644 --- a/parser/interpreter_test.go +++ b/parser/interpreter_test.go @@ -69,6 +69,7 @@ func TestExpr(t *testing.T) { {src: "-2 + 5", res: "3"}, {src: "5 + -2", res: "3"}, {src: "!false", res: "true"}, + {src: `a := "hello"`, res: "hello"}, }) } @@ -233,6 +234,7 @@ func TestVar(t *testing.T) { {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 = "hello"; a`, res: "hello"}, {src: `var ( a, b int = 4+1, 3 c = 8 diff --git a/parser/parse.go b/parser/parse.go index add1600..537b68e 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -29,6 +29,15 @@ type Parser struct { clonum int // closure instance number } +// Parser errors. +var ( + ErrBody = errors.New("missign body") + ErrBreak = errors.New("invalid break statement") + ErrContinue = errors.New("invalid continue statement") + ErrFor = errors.New("invalid for statement") + ErrGoto = errors.New("invalid goto statement") +) + // Scan performs lexical analysis on s and returns Tokens or an error. func (p *Parser) Scan(s string, endSemi bool) (Tokens, error) { return p.Scanner.Scan(s, endSemi) @@ -36,11 +45,11 @@ func (p *Parser) Scan(s string, endSemi bool) (Tokens, error) { // Parse performs syntax analysis on s and return Tokens or an error. func (p *Parser) Parse(src string) (out Tokens, err error) { - log.Printf("Parse src: %#v\n", src) in, err := p.Scan(src, true) if err != nil { return out, err } + log.Printf("Parse src: %#v\n", src) return p.parseStmts(in) } @@ -122,12 +131,12 @@ func (p *Parser) parseBreak(in Tokens) (out Tokens, err error) { label = p.breakLabel case 2: if in[1].Tok != lang.Ident { - return nil, fmt.Errorf("invalid break statement") + return nil, ErrBreak } // TODO: check validity of user provided label label = in[1].Str default: - return nil, fmt.Errorf("invalid break statement") + return nil, ErrBreak } out = Tokens{{Tok: lang.Goto, Str: label}} return out, err @@ -140,12 +149,12 @@ func (p *Parser) parseContinue(in Tokens) (out Tokens, err error) { label = p.continueLabel case 2: if in[1].Tok != lang.Ident { - return nil, fmt.Errorf("invalid continue statement") + return nil, ErrContinue } // TODO: check validity of user provided label label = in[1].Str default: - return nil, fmt.Errorf("invalid continue statement") + return nil, ErrContinue } out = Tokens{{Tok: lang.Goto, Str: label}} return out, err @@ -153,7 +162,7 @@ func (p *Parser) parseContinue(in Tokens) (out Tokens, err error) { func (p *Parser) parseGoto(in Tokens) (out Tokens, err error) { if len(in) != 2 || in[1].Tok != lang.Ident { - return nil, fmt.Errorf("invalid goto statement") + return nil, ErrGoto } // TODO: check validity of user provided label return Tokens{{Tok: lang.Goto, Str: p.funcScope + "/" + in[1].Str}}, nil @@ -171,7 +180,7 @@ func (p *Parser) parseFor(in Tokens) (out Tokens, err error) { case 3: init, cond, post = pre[0], pre[1], pre[2] default: - return nil, fmt.Errorf("invalild for statement") + return nil, ErrFor } breakLabel, continueLabel := p.breakLabel, p.continueLabel p.pushScope("for" + fc) @@ -247,7 +256,7 @@ func (p *Parser) parseFunc(in Tokens) (out Tokens, err error) { bi := in.Index(lang.BraceBlock) if bi < 0 { - return out, fmt.Errorf("no function body") + return out, ErrBody } typ, err := p.parseTypeExpr(in[:bi]) if err != nil { diff --git a/parser/type.go b/parser/type.go index ba60e8f..e25431a 100644 --- a/parser/type.go +++ b/parser/type.go @@ -20,10 +20,12 @@ const ( // Type parsing error definitions. var ( - ErrInvalidType = errors.New("invalid type") - ErrMissingType = errors.New("missing type") - ErrSyntax = errors.New("syntax error") - ErrTypeNotImplemented = errors.New("not implemented") + ErrFuncType = errors.New("invalid function type") + ErrInvalidType = errors.New("invalid type") + ErrMissingType = errors.New("missing type") + ErrSize = errors.New("invalid size") + ErrSyntax = errors.New("syntax error") + ErrNotImplemented = errors.New("not implemented") ) func (p *Parser) parseTypeExpr(in Tokens) (typ *vm.Type, err error) { @@ -44,7 +46,7 @@ func (p *Parser) parseTypeExpr(in Tokens) (typ *vm.Type, err error) { } size, ok := constValue(cval).(int) if !ok { - return nil, fmt.Errorf("invalid size") + return nil, ErrSize } return vm.ArrayOf(size, typ), nil } @@ -71,7 +73,7 @@ func (p *Parser) parseTypeExpr(in Tokens) (typ *vm.Type, err error) { case l >= 2 && in1.Tok == lang.ParenBlock: indexArgs, out = 1, in[2:] default: - return nil, fmt.Errorf("invalid func signature") + return nil, ErrFuncType } // We can now parse function input and output parameter types. @@ -125,7 +127,7 @@ func (p *Parser) parseTypeExpr(in Tokens) (typ *vm.Type, err error) { return vm.StructOf(fields), nil default: - return nil, fmt.Errorf("%w: %v", ErrTypeNotImplemented, in[0].Name()) + return nil, fmt.Errorf("%w: %v", ErrNotImplemented, in[0].Name()) } } diff --git a/scanner/scan.go b/scanner/scan.go index 6ea99a9..2782788 100644 --- a/scanner/scan.go +++ b/scanner/scan.go @@ -34,14 +34,13 @@ func (t *Token) Prefix() string { return t.Str[:t.Beg] } // Name return the name of t (short string for debugging). func (t *Token) Name() string { - name := t.Str - if t.Beg > 1 { - return name[:t.Beg] + ".." + if len(t.Str) == 0 { + return "" } - if t.Beg > 0 { - return name[:t.Beg] + ".." + name[len(name)-t.End:] + if t.Beg > 1 { + return t.Str[:t.Beg] + ".." } - return name + return t.Str[:t.Beg] + ".." + t.Str[len(t.Str)-t.End:] } func (t *Token) String() string { diff --git a/vm/vm_test.go b/vm/vm_test.go index 8fa8e71..43333ad 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -3,6 +3,7 @@ package vm import ( "fmt" "log" + "reflect" "testing" ) @@ -61,7 +62,90 @@ var tests = []struct { {0, Exit}, }, start: 0, end: 1, mem: "[3]", -}, { // #01 -- Calling a function defined outside the VM. +}, { // #01 -- A simple subtraction. + code: [][]int64{ + {0, Push, 2}, + {0, Push, 3}, + {0, Sub}, + {0, Exit}, + }, + start: 0, end: 1, mem: "[1]", +}, { // #02 -- A simple multiplication. + code: [][]int64{ + {0, Push, 3}, + {0, Push, 2}, + {0, Mul}, + {0, Exit}, + }, + start: 0, end: 1, mem: "[6]", +}, { // #03 -- lower. + code: [][]int64{ + {0, Push, 3}, + {0, Push, 2}, + {0, Lower}, + {0, Exit}, + }, + start: 0, end: 1, mem: "[true]", +}, { // #04 -- greater. + code: [][]int64{ + {0, Push, 2}, + {0, Push, 3}, + {0, Greater}, + {0, Exit}, + }, + start: 0, end: 1, mem: "[true]", +}, { // #05 -- equal. + code: [][]int64{ + {0, Push, 2}, + {0, Push, 3}, + {0, Equal}, + {0, Exit}, + }, + start: 0, end: 1, mem: "[false]", +}, { // #06 -- equalSet. + code: [][]int64{ + {0, Push, 2}, + {0, Push, 3}, + {0, EqualSet}, + {0, Exit}, + }, + start: 0, end: 2, mem: "[2 false]", +}, { // #07 -- equalSet. + code: [][]int64{ + {0, Push, 3}, + {0, Push, 3}, + {0, EqualSet}, + {0, Exit}, + }, + start: 0, end: 1, mem: "[true]", +}, { // #08 not. + code: [][]int64{ + {0, Push, 3}, + {0, Push, 3}, + {0, Equal}, + {0, Not}, + {0, Exit}, + }, + start: 0, end: 1, mem: "[false]", +}, { // #09 pop. + code: [][]int64{ + {0, Push, 3}, + {0, Push, 2}, + {0, Pop, 1}, + {0, Exit}, + }, + start: 0, end: 1, mem: "[3]", +}, { // #10 -- Assign a variable. + sym: []Value{{Type: TypeOf(0), Data: reflect.ValueOf(0)}}, + code: [][]int64{ + {0, Grow, 1}, + {0, New, 2, 0}, + {0, Push, 2}, + {0, Assign, 1}, + {0, Exit}, + }, + start: 1, end: 2, mem: "[2]", +}, { // #11 -- Calling a function defined outside the VM. sym: []Value{ValueOf(fmt.Println), ValueOf("Hello")}, code: [][]int64{ {0, Dup, 0}, @@ -69,7 +153,7 @@ var tests = []struct { {0, Exit}, }, start: 1, end: 3, mem: "[6 <nil>]", -}, { // #02 -- Defining and calling a function in VM. +}, { // #12 -- Defining and calling a function in VM. code: [][]int64{ {0, Jump, 3}, // 0 {0, Push, 3}, // 1 @@ -79,7 +163,7 @@ var tests = []struct { {0, Exit}, // 5 }, start: 0, end: 1, mem: "[3]", -}, { // #03 -- Defining and calling a function in VM. +}, { // #13 -- Defining and calling a function in VM. code: [][]int64{ {0, Jump, 3}, // 0 {0, Push, 3}, // 1 @@ -90,7 +174,7 @@ var tests = []struct { {0, Exit}, // 6 }, start: 0, end: 1, mem: "[3]", -}, { // #04 -- Defining and calling a function in VM. +}, { // #14 -- Defining and calling a function in VM. code: [][]int64{ {0, Jump, 5}, // 0 {0, Push, 3}, // 1 @@ -103,20 +187,20 @@ var tests = []struct { {0, Exit}, // 8 }, start: 0, end: 1, mem: "[3]", -}, { // #05 -- Fibonacci numbers, hand written. Showcase recursivity. +}, { // #15 -- Fibonacci numbers, hand written. Showcase recursivity. code: [][]int64{ {0, Jump, 19}, // 0 {0, Push, 2}, // 2 [2] {0, Fdup, -2}, // 1 [2 i] {0, Lower}, // 3 [true/false] {0, JumpTrue, 13}, // 4 [], goto 17 - {0, Push, 2}, // 6 [i 2] - {0, Fdup, -2}, // 5 [i] + {0, Push, 2}, // 5 [i 2] + {0, Fdup, -2}, // 6 [i] {0, Sub}, // 7 [(i-2)] {0, Push, 1}, // 8 {0, Call}, // 9 [fib(i-2)] - {0, Push, 1}, // 11 [(i-2) i 1] - {0, Fdup, -2}, // 10 [fib(i-2) i] + {0, Push, 1}, // 10 [(i-2) i 1] + {0, Fdup, -2}, // 11 [fib(i-2) i] {0, Sub}, // 12 [(i-2) (i-1)] {0, Push, 1}, // 13 {0, Call}, // 14 [fib(i-2) fib(i-1)] @@ -130,7 +214,7 @@ var tests = []struct { {0, Exit}, // 22 }, start: 0, end: 1, mem: "[8]", -}, { // #06 -- Fibonacci with some immediate instructions. +}, { // #16 -- Fibonacci with some immediate instructions. code: [][]int64{ {0, Jump, 14}, // 0 {0, Fdup, -2}, // 1 [i] |
