summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2024-07-18 12:56:29 +0200
committerGitHub <noreply@github.com>2024-07-18 12:56:29 +0200
commitdabd9e5eb81bbc9aeaeb32fb3e3ce83eef258a77 (patch)
tree32be6a4cecf83487dd6a91e8a9aa1da709840b0f
parent1a2b2cb565ebf701f43012e1fce5552398d622a9 (diff)
fix (parser): don't panic if assign of define untyped value (#10)
* fix (parser): don't panic if assign of define untyped value In case of defining or assigning to untyped value, the type has to be taken from the source value instead of the target value. The vm test coverage has also been slightly improved. * fix and simplify Token.Name() * improve parser errors
-rw-r--r--.golangci.yaml3
-rw-r--r--main.go2
-rw-r--r--parser/compiler.go25
-rw-r--r--parser/expr.go2
-rw-r--r--parser/interpreter_test.go2
-rw-r--r--parser/parse.go25
-rw-r--r--parser/type.go16
-rw-r--r--scanner/scan.go11
-rw-r--r--vm/vm_test.go104
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
diff --git a/main.go b/main.go
index f9f125b..276f732 100644
--- a/main.go
+++ b/main.go
@@ -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]