Skip to content

Commit

Permalink
ruleguard/quasigo: enable multi-return + add strconv lib support (#311)
Browse files Browse the repository at this point in the history
  • Loading branch information
quasilyte authored Nov 20, 2021
1 parent d5cecbe commit a927a7c
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 27 deletions.
59 changes: 37 additions & 22 deletions ruleguard/quasigo/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,38 +258,43 @@ func (cl *compiler) compileIfStmt(stmt *ast.IfStmt) {
}

func (cl *compiler) compileAssignStmt(assign *ast.AssignStmt) {
if len(assign.Lhs) != 1 {
panic(cl.errorf(assign, "only single left operand is allowed in assignments"))
}
if len(assign.Rhs) != 1 {
panic(cl.errorf(assign, "only single right operand is allowed in assignments"))
}
lhs := assign.Lhs[0]
rhs := assign.Rhs[0]
varname, ok := lhs.(*ast.Ident)
if !ok {
panic(cl.errorf(lhs, "can assign only to simple variables"))
for _, lhs := range assign.Lhs {
_, ok := lhs.(*ast.Ident)
if !ok {
panic(cl.errorf(lhs, "can assign only to simple variables"))
}
}

rhs := assign.Rhs[0]
cl.compileExpr(rhs)

typ := cl.ctx.Types.TypeOf(varname)
if assign.Tok == token.DEFINE {
if _, ok := cl.locals[varname.String()]; ok {
panic(cl.errorf(lhs, "%s variable shadowing is not allowed", varname))
for i := len(assign.Lhs) - 1; i >= 0; i-- {
varname := assign.Lhs[i].(*ast.Ident)
typ := cl.ctx.Types.TypeOf(varname)
if _, ok := cl.locals[varname.String()]; ok {
panic(cl.errorf(varname, "%s variable shadowing is not allowed", varname))
}
if !cl.isSupportedType(typ) {
panic(cl.errorUnsupportedType(varname, typ, varname.String()+" local variable"))
}
if len(cl.locals) == maxFuncLocals {
panic(cl.errorf(varname, "can't define %s: too many locals", varname))
}
id := len(cl.locals)
cl.locals[varname.String()] = id
cl.emit8(pickOp(typeIsInt(typ), opSetIntLocal, opSetLocal), id)
}
if !cl.isSupportedType(typ) {
panic(cl.errorUnsupportedType(varname, typ, varname.String()+" local variable"))
}
if len(cl.locals) == maxFuncLocals {
panic(cl.errorf(lhs, "can't define %s: too many locals", varname))
}
id := len(cl.locals)
cl.locals[varname.String()] = id
cl.emit8(pickOp(typeIsInt(typ), opSetIntLocal, opSetLocal), id)
} else {
id := cl.getLocal(varname, varname.String())
cl.emit8(pickOp(typeIsInt(typ), opSetIntLocal, opSetLocal), id)
for i := len(assign.Lhs) - 1; i >= 0; i-- {
varname := assign.Lhs[i].(*ast.Ident)
typ := cl.ctx.Types.TypeOf(varname)
id := cl.getLocal(varname, varname.String())
cl.emit8(pickOp(typeIsInt(typ), opSetIntLocal, opSetLocal), id)
}
}
}

Expand Down Expand Up @@ -557,6 +562,16 @@ func (cl *compiler) compileNativeCall(key funcKey, expr ast.Expr, args []ast.Exp
if expr != nil {
cl.compileExpr(expr)
}
if len(args) == 1 {
// Check that it's not a f(g()) call, where g() returns
// a multi-value result; we can't compile that yet.
if call, ok := args[0].(*ast.CallExpr); ok {
results := cl.ctx.Types.TypeOf(call.Fun).(*types.Signature).Results()
if results != nil && results.Len() > 1 {
panic(cl.errorf(args[0], "can't pass tuple as a func argument"))
}
}
}
for _, arg := range args {
cl.compileExpr(arg)
}
Expand Down
26 changes: 25 additions & 1 deletion ruleguard/quasigo/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,16 +323,31 @@ func TestCompile(t *testing.T) {
` PushIntLocal 0 # j`,
` ReturnIntTop`,
},

`v, err := atoi("foo"); println(v); println(err == nil); return err`: {
` PushConst 0 # value="foo"`,
` CallNative 2 # testpkg.atoi`,
` SetLocal 0 # err`,
` SetIntLocal 1 # v`,
` PushIntLocal 1 # v`,
` CallNative 3 # builtin.PrintInt`,
` PushLocal 0 # err`,
` IsNil`,
` CallNative 4 # builtin.Print`,
` PushLocal 0 # err`,
` ReturnTop`,
},
}

makePackageSource := func(body string) string {
return `
package test
package testpkg
func f(i int, s string, b bool, eface interface{}) interface{} {
` + body + `
}
func imul(x, y int) int
func idiv(x, y int) int
func atoi(s string) (int, error)
`
}

Expand All @@ -343,6 +358,15 @@ func TestCompile(t *testing.T) {
env.AddNativeFunc(testPackage, "idiv", func(stack *quasigo.ValueStack) {
panic("should not be called")
})
env.AddNativeFunc(testPackage, "atoi", func(stack *quasigo.ValueStack) {
panic("should not be called")
})
env.AddNativeFunc("builtin", "PrintInt", func(stack *quasigo.ValueStack) {
panic("should not be called")
})
env.AddNativeFunc("builtin", "Print", func(stack *quasigo.ValueStack) {
panic("should not be called")
})

for testSrc, disasmLines := range tests {
src := makePackageSource(testSrc)
Expand Down
12 changes: 9 additions & 3 deletions ruleguard/quasigo/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/quasilyte/go-ruleguard/ruleguard/quasigo"
"github.com/quasilyte/go-ruleguard/ruleguard/quasigo/internal/evaltest"
"github.com/quasilyte/go-ruleguard/ruleguard/quasigo/stdlib/qstrconv"
"github.com/quasilyte/go-ruleguard/ruleguard/quasigo/stdlib/qstrings"
)

Expand Down Expand Up @@ -260,15 +261,21 @@ func TestEvalFile(t *testing.T) {
}

var stdout bytes.Buffer
env.AddNativeFunc("builtin", "Print", func(stack *quasigo.ValueStack) {
env.AddNativeFunc(`builtin`, `Print`, func(stack *quasigo.ValueStack) {
arg := stack.Pop()
fmt.Fprintln(&stdout, arg)
})
env.AddNativeFunc("builtin", "PrintInt", func(stack *quasigo.ValueStack) {
env.AddNativeFunc(`builtin`, `PrintInt`, func(stack *quasigo.ValueStack) {
fmt.Fprintln(&stdout, stack.PopInt())
})

env.AddNativeMethod(`error`, `Error`, func(stack *quasigo.ValueStack) {
err := stack.Pop().(error)
stack.Push(err.Error())
})

qstrings.ImportAll(env)
qstrconv.ImportAll(env)

var mainFunc *quasigo.Func
for _, decl := range parsed.ast.Decls {
Expand Down Expand Up @@ -320,5 +327,4 @@ func TestEvalFile(t *testing.T) {
runTest(t, mainFile)
})
}

}
24 changes: 24 additions & 0 deletions ruleguard/quasigo/stdlib/qstrconv/qstrconv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package qstrconv

import (
"strconv"

"github.com/quasilyte/go-ruleguard/ruleguard/quasigo"
)

func ImportAll(env *quasigo.Env) {
env.AddNativeFunc(`strconv`, `Atoi`, Atoi)
env.AddNativeFunc(`strconv`, `Itoa`, Itoa)
}

func Atoi(stack *quasigo.ValueStack) {
s := stack.Pop().(string)
v, err := strconv.Atoi(s)
stack.PushInt(v)
stack.Push(err)
}

func Itoa(stack *quasigo.ValueStack) {
i := stack.PopInt()
stack.Push(strconv.Itoa(i))
}
28 changes: 28 additions & 0 deletions ruleguard/quasigo/testdata/qstrconv/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import "strconv"

func main() {
s := "16"

i, err := strconv.Atoi(s)
println(i)
println(err == nil)

i2, err2 := strconv.Atoi("bad")
println(i2)
println(err2.Error())

println(strconv.Itoa(140))
println(strconv.Itoa(i) == s)

i, err2 = strconv.Atoi("foo")
println(i)
println(err2.Error())

i, err2 = strconv.Atoi("-349")
println(i)
if err2 == nil {
println("err2 is nil")
}
}
2 changes: 1 addition & 1 deletion ruleguard/ruleguard_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestParseFilterFuncError(t *testing.T) {
// Assignment errors.
{
`x, y := 1, 2; return x == y`,
`only single left operand is allowed in assignments`,
`only single right operand is allowed in assignments`,
},
{
`x := 0; { x := 1; return x == 1 }; return x == 0`,
Expand Down

0 comments on commit a927a7c

Please sign in to comment.