mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-04 23:56:30 +00:00
708 lines
16 KiB
Go
708 lines
16 KiB
Go
|
package quasigo
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"go/ast"
|
||
|
"go/constant"
|
||
|
"go/token"
|
||
|
"go/types"
|
||
|
|
||
|
"github.com/quasilyte/go-ruleguard/ruleguard/goutil"
|
||
|
"golang.org/x/tools/go/ast/astutil"
|
||
|
)
|
||
|
|
||
|
func compile(ctx *CompileContext, fn *ast.FuncDecl) (compiled *Func, err error) {
|
||
|
defer func() {
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
rv := recover()
|
||
|
if rv == nil {
|
||
|
return
|
||
|
}
|
||
|
if compileErr, ok := rv.(compileError); ok {
|
||
|
err = compileErr
|
||
|
return
|
||
|
}
|
||
|
panic(rv) // not our panic
|
||
|
}()
|
||
|
|
||
|
return compileFunc(ctx, fn), nil
|
||
|
}
|
||
|
|
||
|
func compileFunc(ctx *CompileContext, fn *ast.FuncDecl) *Func {
|
||
|
cl := compiler{
|
||
|
ctx: ctx,
|
||
|
fnType: ctx.Types.ObjectOf(fn.Name).Type().(*types.Signature),
|
||
|
constantsPool: make(map[interface{}]int),
|
||
|
intConstantsPool: make(map[int]int),
|
||
|
locals: make(map[string]int),
|
||
|
}
|
||
|
return cl.compileFunc(fn)
|
||
|
}
|
||
|
|
||
|
type compiler struct {
|
||
|
ctx *CompileContext
|
||
|
|
||
|
fnType *types.Signature
|
||
|
retType types.Type
|
||
|
|
||
|
lastOp opcode
|
||
|
|
||
|
locals map[string]int
|
||
|
constantsPool map[interface{}]int
|
||
|
intConstantsPool map[int]int
|
||
|
params map[string]int
|
||
|
|
||
|
code []byte
|
||
|
constants []interface{}
|
||
|
intConstants []int
|
||
|
|
||
|
breakTarget *label
|
||
|
continueTarget *label
|
||
|
|
||
|
labels []*label
|
||
|
}
|
||
|
|
||
|
type label struct {
|
||
|
targetPos int
|
||
|
sources []int
|
||
|
}
|
||
|
|
||
|
type compileError string
|
||
|
|
||
|
func (e compileError) Error() string { return string(e) }
|
||
|
|
||
|
func (cl *compiler) compileFunc(fn *ast.FuncDecl) *Func {
|
||
|
if cl.fnType.Results().Len() != 1 {
|
||
|
panic(cl.errorf(fn.Name, "only functions with a single non-void results are supported"))
|
||
|
}
|
||
|
cl.retType = cl.fnType.Results().At(0).Type()
|
||
|
|
||
|
if !cl.isSupportedType(cl.retType) {
|
||
|
panic(cl.errorUnsupportedType(fn.Name, cl.retType, "function result"))
|
||
|
}
|
||
|
|
||
|
dbg := funcDebugInfo{
|
||
|
paramNames: make([]string, cl.fnType.Params().Len()),
|
||
|
}
|
||
|
|
||
|
cl.params = make(map[string]int, cl.fnType.Params().Len())
|
||
|
for i := 0; i < cl.fnType.Params().Len(); i++ {
|
||
|
p := cl.fnType.Params().At(i)
|
||
|
paramName := p.Name()
|
||
|
paramType := p.Type()
|
||
|
cl.params[paramName] = i
|
||
|
dbg.paramNames[i] = paramName
|
||
|
if !cl.isSupportedType(paramType) {
|
||
|
panic(cl.errorUnsupportedType(fn.Name, paramType, paramName+" param"))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cl.compileStmt(fn.Body)
|
||
|
compiled := &Func{
|
||
|
code: cl.code,
|
||
|
constants: cl.constants,
|
||
|
intConstants: cl.intConstants,
|
||
|
}
|
||
|
if len(cl.locals) != 0 {
|
||
|
dbg.localNames = make([]string, len(cl.locals))
|
||
|
for localName, localIndex := range cl.locals {
|
||
|
dbg.localNames[localIndex] = localName
|
||
|
}
|
||
|
}
|
||
|
cl.ctx.Env.debug.funcs[compiled] = dbg
|
||
|
cl.linkJumps()
|
||
|
return compiled
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileStmt(stmt ast.Stmt) {
|
||
|
switch stmt := stmt.(type) {
|
||
|
case *ast.ReturnStmt:
|
||
|
cl.compileReturnStmt(stmt)
|
||
|
|
||
|
case *ast.AssignStmt:
|
||
|
cl.compileAssignStmt(stmt)
|
||
|
|
||
|
case *ast.IncDecStmt:
|
||
|
cl.compileIncDecStmt(stmt)
|
||
|
|
||
|
case *ast.IfStmt:
|
||
|
cl.compileIfStmt(stmt)
|
||
|
|
||
|
case *ast.ForStmt:
|
||
|
cl.compileForStmt(stmt)
|
||
|
|
||
|
case *ast.BranchStmt:
|
||
|
cl.compileBranchStmt(stmt)
|
||
|
|
||
|
case *ast.BlockStmt:
|
||
|
for i := range stmt.List {
|
||
|
cl.compileStmt(stmt.List[i])
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
panic(cl.errorf(stmt, "can't compile %T yet", stmt))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileIncDecStmt(stmt *ast.IncDecStmt) {
|
||
|
varname, ok := stmt.X.(*ast.Ident)
|
||
|
if !ok {
|
||
|
panic(cl.errorf(stmt.X, "can assign only to simple variables"))
|
||
|
}
|
||
|
id := cl.getLocal(varname, varname.String())
|
||
|
if stmt.Tok == token.INC {
|
||
|
cl.emit8(opIncLocal, id)
|
||
|
} else {
|
||
|
cl.emit8(opDecLocal, id)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileBranchStmt(branch *ast.BranchStmt) {
|
||
|
if branch.Label != nil {
|
||
|
panic(cl.errorf(branch.Label, "can't compile %s with a label", branch.Tok))
|
||
|
}
|
||
|
|
||
|
switch branch.Tok {
|
||
|
case token.BREAK:
|
||
|
cl.emitJump(opJump, cl.breakTarget)
|
||
|
default:
|
||
|
panic(cl.errorf(branch, "can't compile %s yet", branch.Tok))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileForStmt(stmt *ast.ForStmt) {
|
||
|
labelBreak := cl.newLabel()
|
||
|
labelContinue := cl.newLabel()
|
||
|
prevBreakTarget := cl.breakTarget
|
||
|
prevContinueTarget := cl.continueTarget
|
||
|
cl.breakTarget = labelBreak
|
||
|
cl.continueTarget = labelContinue
|
||
|
|
||
|
switch {
|
||
|
case stmt.Cond != nil && stmt.Init != nil && stmt.Post != nil:
|
||
|
// Will be implemented later; probably when the max number of locals will be lifted.
|
||
|
panic(cl.errorf(stmt, "can't compile C-style for loops yet"))
|
||
|
|
||
|
case stmt.Cond != nil && stmt.Init == nil && stmt.Post == nil:
|
||
|
// `for <cond> { ... }`
|
||
|
labelBody := cl.newLabel()
|
||
|
cl.emitJump(opJump, labelContinue)
|
||
|
cl.bindLabel(labelBody)
|
||
|
cl.compileStmt(stmt.Body)
|
||
|
cl.bindLabel(labelContinue)
|
||
|
cl.compileExpr(stmt.Cond)
|
||
|
cl.emitJump(opJumpTrue, labelBody)
|
||
|
cl.bindLabel(labelBreak)
|
||
|
|
||
|
default:
|
||
|
// `for { ... }`
|
||
|
cl.bindLabel(labelContinue)
|
||
|
cl.compileStmt(stmt.Body)
|
||
|
cl.emitJump(opJump, labelContinue)
|
||
|
cl.bindLabel(labelBreak)
|
||
|
}
|
||
|
|
||
|
cl.breakTarget = prevBreakTarget
|
||
|
cl.continueTarget = prevContinueTarget
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileIfStmt(stmt *ast.IfStmt) {
|
||
|
if stmt.Else == nil {
|
||
|
labelEnd := cl.newLabel()
|
||
|
cl.compileExpr(stmt.Cond)
|
||
|
cl.emitJump(opJumpFalse, labelEnd)
|
||
|
cl.compileStmt(stmt.Body)
|
||
|
cl.bindLabel(labelEnd)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
labelEnd := cl.newLabel()
|
||
|
labelElse := cl.newLabel()
|
||
|
cl.compileExpr(stmt.Cond)
|
||
|
cl.emitJump(opJumpFalse, labelElse)
|
||
|
cl.compileStmt(stmt.Body)
|
||
|
if !cl.isUncondJump(cl.lastOp) {
|
||
|
cl.emitJump(opJump, labelEnd)
|
||
|
}
|
||
|
cl.bindLabel(labelElse)
|
||
|
cl.compileStmt(stmt.Else)
|
||
|
cl.bindLabel(labelEnd)
|
||
|
}
|
||
|
|
||
|
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"))
|
||
|
}
|
||
|
|
||
|
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))
|
||
|
}
|
||
|
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)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) getLocal(v ast.Expr, varname string) int {
|
||
|
id, ok := cl.locals[varname]
|
||
|
if !ok {
|
||
|
if _, ok := cl.params[varname]; ok {
|
||
|
panic(cl.errorf(v, "can't assign to %s, params are readonly", varname))
|
||
|
}
|
||
|
panic(cl.errorf(v, "%s is not a writeable local variable", varname))
|
||
|
}
|
||
|
return id
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileReturnStmt(ret *ast.ReturnStmt) {
|
||
|
if ret.Results == nil {
|
||
|
panic(cl.errorf(ret, "'naked' return statements are not allowed"))
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case identName(ret.Results[0]) == "true":
|
||
|
cl.emit(opReturnTrue)
|
||
|
case identName(ret.Results[0]) == "false":
|
||
|
cl.emit(opReturnFalse)
|
||
|
default:
|
||
|
cl.compileExpr(ret.Results[0])
|
||
|
typ := cl.ctx.Types.TypeOf(ret.Results[0])
|
||
|
cl.emit(pickOp(typeIsInt(typ), opReturnIntTop, opReturnTop))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileExpr(e ast.Expr) {
|
||
|
cv := cl.ctx.Types.Types[e].Value
|
||
|
if cv != nil {
|
||
|
cl.compileConstantValue(e, cv)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch e := e.(type) {
|
||
|
case *ast.ParenExpr:
|
||
|
cl.compileExpr(e.X)
|
||
|
|
||
|
case *ast.Ident:
|
||
|
cl.compileIdent(e)
|
||
|
|
||
|
case *ast.SelectorExpr:
|
||
|
cl.compileSelectorExpr(e)
|
||
|
|
||
|
case *ast.UnaryExpr:
|
||
|
switch e.Op {
|
||
|
case token.NOT:
|
||
|
cl.compileUnaryOp(opNot, e)
|
||
|
default:
|
||
|
panic(cl.errorf(e, "can't compile unary %s yet", e.Op))
|
||
|
}
|
||
|
|
||
|
case *ast.SliceExpr:
|
||
|
cl.compileSliceExpr(e)
|
||
|
|
||
|
case *ast.BinaryExpr:
|
||
|
cl.compileBinaryExpr(e)
|
||
|
|
||
|
case *ast.CallExpr:
|
||
|
cl.compileCallExpr(e)
|
||
|
|
||
|
default:
|
||
|
panic(cl.errorf(e, "can't compile %T yet", e))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileSelectorExpr(e *ast.SelectorExpr) {
|
||
|
typ := cl.ctx.Types.TypeOf(e.X)
|
||
|
key := funcKey{
|
||
|
name: e.Sel.String(),
|
||
|
qualifier: typ.String(),
|
||
|
}
|
||
|
|
||
|
if funcID, ok := cl.ctx.Env.nameToNativeFuncID[key]; ok {
|
||
|
cl.compileExpr(e.X)
|
||
|
cl.emit16(opCallNative, int(funcID))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
panic(cl.errorf(e, "can't compile %s field access", e.Sel))
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileBinaryExpr(e *ast.BinaryExpr) {
|
||
|
typ := cl.ctx.Types.TypeOf(e.X)
|
||
|
|
||
|
switch e.Op {
|
||
|
case token.LOR:
|
||
|
cl.compileOr(e)
|
||
|
case token.LAND:
|
||
|
cl.compileAnd(e)
|
||
|
|
||
|
case token.NEQ:
|
||
|
switch {
|
||
|
case identName(e.X) == "nil":
|
||
|
cl.compileExpr(e.Y)
|
||
|
cl.emit(opIsNotNil)
|
||
|
case identName(e.Y) == "nil":
|
||
|
cl.compileExpr(e.X)
|
||
|
cl.emit(opIsNotNil)
|
||
|
case typeIsString(typ):
|
||
|
cl.compileBinaryOp(opNotEqString, e)
|
||
|
case typeIsInt(typ):
|
||
|
cl.compileBinaryOp(opNotEqInt, e)
|
||
|
default:
|
||
|
panic(cl.errorf(e, "!= is not implemented for %s operands", typ))
|
||
|
}
|
||
|
case token.EQL:
|
||
|
switch {
|
||
|
case identName(e.X) == "nil":
|
||
|
cl.compileExpr(e.Y)
|
||
|
cl.emit(opIsNil)
|
||
|
case identName(e.Y) == "nil":
|
||
|
cl.compileExpr(e.X)
|
||
|
cl.emit(opIsNil)
|
||
|
case typeIsString(cl.ctx.Types.TypeOf(e.X)):
|
||
|
cl.compileBinaryOp(opEqString, e)
|
||
|
case typeIsInt(cl.ctx.Types.TypeOf(e.X)):
|
||
|
cl.compileBinaryOp(opEqInt, e)
|
||
|
default:
|
||
|
panic(cl.errorf(e, "== is not implemented for %s operands", typ))
|
||
|
}
|
||
|
|
||
|
case token.GTR:
|
||
|
cl.compileIntBinaryOp(e, opGtInt, typ)
|
||
|
case token.GEQ:
|
||
|
cl.compileIntBinaryOp(e, opGtEqInt, typ)
|
||
|
case token.LSS:
|
||
|
cl.compileIntBinaryOp(e, opLtInt, typ)
|
||
|
case token.LEQ:
|
||
|
cl.compileIntBinaryOp(e, opLtEqInt, typ)
|
||
|
|
||
|
case token.ADD:
|
||
|
switch {
|
||
|
case typeIsString(typ):
|
||
|
cl.compileBinaryOp(opConcat, e)
|
||
|
case typeIsInt(typ):
|
||
|
cl.compileBinaryOp(opAdd, e)
|
||
|
default:
|
||
|
panic(cl.errorf(e, "+ is not implemented for %s operands", typ))
|
||
|
}
|
||
|
|
||
|
case token.SUB:
|
||
|
cl.compileIntBinaryOp(e, opSub, typ)
|
||
|
|
||
|
default:
|
||
|
panic(cl.errorf(e, "can't compile binary %s yet", e.Op))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileIntBinaryOp(e *ast.BinaryExpr, op opcode, typ types.Type) {
|
||
|
switch {
|
||
|
case typeIsInt(typ):
|
||
|
cl.compileBinaryOp(op, e)
|
||
|
default:
|
||
|
panic(cl.errorf(e, "%s is not implemented for %s operands", e.Op, typ))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileSliceExpr(slice *ast.SliceExpr) {
|
||
|
if slice.Slice3 {
|
||
|
panic(cl.errorf(slice, "can't compile 3-index slicing"))
|
||
|
}
|
||
|
|
||
|
// No need to do slicing, its no-op `s[:]`.
|
||
|
if slice.Low == nil && slice.High == nil {
|
||
|
cl.compileExpr(slice.X)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
sliceOp := opStringSlice
|
||
|
sliceFromOp := opStringSliceFrom
|
||
|
sliceToOp := opStringSliceTo
|
||
|
|
||
|
if !typeIsString(cl.ctx.Types.TypeOf(slice.X)) {
|
||
|
panic(cl.errorf(slice.X, "can't compile slicing of something that is not a string"))
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case slice.Low == nil && slice.High != nil:
|
||
|
cl.compileExpr(slice.X)
|
||
|
cl.compileExpr(slice.High)
|
||
|
cl.emit(sliceToOp)
|
||
|
case slice.Low != nil && slice.High == nil:
|
||
|
cl.compileExpr(slice.X)
|
||
|
cl.compileExpr(slice.Low)
|
||
|
cl.emit(sliceFromOp)
|
||
|
default:
|
||
|
cl.compileExpr(slice.X)
|
||
|
cl.compileExpr(slice.Low)
|
||
|
cl.compileExpr(slice.High)
|
||
|
cl.emit(sliceOp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileBuiltinCall(fn *ast.Ident, call *ast.CallExpr) {
|
||
|
switch fn.Name {
|
||
|
case `len`:
|
||
|
s := call.Args[0]
|
||
|
cl.compileExpr(s)
|
||
|
if !typeIsString(cl.ctx.Types.TypeOf(s)) {
|
||
|
panic(cl.errorf(s, "can't compile len() with non-string argument yet"))
|
||
|
}
|
||
|
cl.emit(opStringLen)
|
||
|
default:
|
||
|
panic(cl.errorf(fn, "can't compile %s() builtin function call yet", fn))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileCallExpr(call *ast.CallExpr) {
|
||
|
if id, ok := astutil.Unparen(call.Fun).(*ast.Ident); ok {
|
||
|
_, isBuiltin := cl.ctx.Types.ObjectOf(id).(*types.Builtin)
|
||
|
if isBuiltin {
|
||
|
cl.compileBuiltinCall(id, call)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
expr, fn := goutil.ResolveFunc(cl.ctx.Types, call.Fun)
|
||
|
if fn == nil {
|
||
|
panic(cl.errorf(call.Fun, "can't resolve the called function"))
|
||
|
}
|
||
|
|
||
|
// TODO: just use Func.FullName as a key?
|
||
|
key := funcKey{name: fn.Name()}
|
||
|
sig := fn.Type().(*types.Signature)
|
||
|
if sig.Recv() != nil {
|
||
|
key.qualifier = sig.Recv().Type().String()
|
||
|
} else {
|
||
|
key.qualifier = fn.Pkg().Path()
|
||
|
}
|
||
|
|
||
|
if funcID, ok := cl.ctx.Env.nameToNativeFuncID[key]; ok {
|
||
|
if expr != nil {
|
||
|
cl.compileExpr(expr)
|
||
|
}
|
||
|
for _, arg := range call.Args {
|
||
|
cl.compileExpr(arg)
|
||
|
}
|
||
|
cl.emit16(opCallNative, int(funcID))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
panic(cl.errorf(call.Fun, "can't compile a call to %s func", key))
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileUnaryOp(op opcode, e *ast.UnaryExpr) {
|
||
|
cl.compileExpr(e.X)
|
||
|
cl.emit(op)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileBinaryOp(op opcode, e *ast.BinaryExpr) {
|
||
|
cl.compileExpr(e.X)
|
||
|
cl.compileExpr(e.Y)
|
||
|
cl.emit(op)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileOr(e *ast.BinaryExpr) {
|
||
|
labelEnd := cl.newLabel()
|
||
|
cl.compileExpr(e.X)
|
||
|
cl.emit(opDup)
|
||
|
cl.emitJump(opJumpTrue, labelEnd)
|
||
|
cl.compileExpr(e.Y)
|
||
|
cl.bindLabel(labelEnd)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileAnd(e *ast.BinaryExpr) {
|
||
|
labelEnd := cl.newLabel()
|
||
|
cl.compileExpr(e.X)
|
||
|
cl.emit(opDup)
|
||
|
cl.emitJump(opJumpFalse, labelEnd)
|
||
|
cl.compileExpr(e.Y)
|
||
|
cl.bindLabel(labelEnd)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileIdent(ident *ast.Ident) {
|
||
|
tv := cl.ctx.Types.Types[ident]
|
||
|
cv := tv.Value
|
||
|
if cv != nil {
|
||
|
cl.compileConstantValue(ident, cv)
|
||
|
return
|
||
|
}
|
||
|
if paramIndex, ok := cl.params[ident.String()]; ok {
|
||
|
cl.emit8(pickOp(typeIsInt(tv.Type), opPushIntParam, opPushParam), paramIndex)
|
||
|
return
|
||
|
}
|
||
|
if localIndex, ok := cl.locals[ident.String()]; ok {
|
||
|
cl.emit8(pickOp(typeIsInt(tv.Type), opPushIntLocal, opPushLocal), localIndex)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
panic(cl.errorf(ident, "can't compile a %s (type %s) variable read", ident.String(), tv.Type))
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) compileConstantValue(source ast.Expr, cv constant.Value) {
|
||
|
switch cv.Kind() {
|
||
|
case constant.Bool:
|
||
|
v := constant.BoolVal(cv)
|
||
|
if v {
|
||
|
cl.emit(opPushTrue)
|
||
|
} else {
|
||
|
cl.emit(opPushFalse)
|
||
|
}
|
||
|
|
||
|
case constant.String:
|
||
|
v := constant.StringVal(cv)
|
||
|
id := cl.internConstant(v)
|
||
|
cl.emit8(opPushConst, id)
|
||
|
|
||
|
case constant.Int:
|
||
|
v, exact := constant.Int64Val(cv)
|
||
|
if !exact {
|
||
|
panic(cl.errorf(source, "non-exact int value"))
|
||
|
}
|
||
|
id := cl.internIntConstant(int(v))
|
||
|
cl.emit8(opPushIntConst, id)
|
||
|
|
||
|
case constant.Complex:
|
||
|
panic(cl.errorf(source, "can't compile complex number constants yet"))
|
||
|
|
||
|
case constant.Float:
|
||
|
panic(cl.errorf(source, "can't compile float constants yet"))
|
||
|
|
||
|
default:
|
||
|
panic(cl.errorf(source, "unexpected constant %v", cv))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) internIntConstant(v int) int {
|
||
|
if id, ok := cl.intConstantsPool[v]; ok {
|
||
|
return id
|
||
|
}
|
||
|
id := len(cl.intConstants)
|
||
|
cl.intConstants = append(cl.intConstants, v)
|
||
|
cl.intConstantsPool[v] = id
|
||
|
return id
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) internConstant(v interface{}) int {
|
||
|
if _, ok := v.(int); ok {
|
||
|
panic("compiler error: int constant interned as interface{}")
|
||
|
}
|
||
|
if id, ok := cl.constantsPool[v]; ok {
|
||
|
return id
|
||
|
}
|
||
|
id := len(cl.constants)
|
||
|
cl.constants = append(cl.constants, v)
|
||
|
cl.constantsPool[v] = id
|
||
|
return id
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) linkJumps() {
|
||
|
for _, l := range cl.labels {
|
||
|
for _, jumpPos := range l.sources {
|
||
|
offset := l.targetPos - jumpPos
|
||
|
patchPos := jumpPos + 1
|
||
|
put16(cl.code, patchPos, offset)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) newLabel() *label {
|
||
|
l := &label{}
|
||
|
cl.labels = append(cl.labels, l)
|
||
|
return l
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) bindLabel(l *label) {
|
||
|
l.targetPos = len(cl.code)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) emit(op opcode) {
|
||
|
cl.lastOp = op
|
||
|
cl.code = append(cl.code, byte(op))
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) emitJump(op opcode, l *label) {
|
||
|
l.sources = append(l.sources, len(cl.code))
|
||
|
cl.emit(op)
|
||
|
cl.code = append(cl.code, 0, 0)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) emit8(op opcode, arg8 int) {
|
||
|
cl.emit(op)
|
||
|
cl.code = append(cl.code, byte(arg8))
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) emit16(op opcode, arg16 int) {
|
||
|
cl.emit(op)
|
||
|
buf := make([]byte, 2)
|
||
|
put16(buf, 0, arg16)
|
||
|
cl.code = append(cl.code, buf...)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) errorUnsupportedType(e ast.Node, typ types.Type, where string) compileError {
|
||
|
return cl.errorf(e, "%s type: %s is not supported, try something simpler", where, typ)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) errorf(n ast.Node, format string, args ...interface{}) compileError {
|
||
|
loc := cl.ctx.Fset.Position(n.Pos())
|
||
|
message := fmt.Sprintf("%s:%d: %s", loc.Filename, loc.Line, fmt.Sprintf(format, args...))
|
||
|
return compileError(message)
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) isUncondJump(op opcode) bool {
|
||
|
switch op {
|
||
|
case opJump, opReturnFalse, opReturnTrue, opReturnTop, opReturnIntTop:
|
||
|
return true
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cl *compiler) isSupportedType(typ types.Type) bool {
|
||
|
switch typ := typ.Underlying().(type) {
|
||
|
case *types.Pointer:
|
||
|
// 1. Pointers to structs are supported.
|
||
|
_, isStruct := typ.Elem().Underlying().(*types.Struct)
|
||
|
return isStruct
|
||
|
|
||
|
case *types.Basic:
|
||
|
// 2. Some of the basic types are supported.
|
||
|
// TODO: support byte/uint8 and maybe float64.
|
||
|
switch typ.Kind() {
|
||
|
case types.Bool, types.Int, types.String:
|
||
|
return true
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
case *types.Interface:
|
||
|
// 3. Interfaces are supported.
|
||
|
return true
|
||
|
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|