Commit b29461f0 authored by Dylan Trotter's avatar Dylan Trotter

Implement code object.

parent c6065be4
......@@ -405,18 +405,19 @@ class ExprVisitor(ast.NodeVisitor):
tmpl = '$args[$i] = πg.FunctionArg{Name: $name, Def: $default}'
self.writer.write_tmpl(tmpl, args=func_args.expr, i=i,
name=util.go_str(a.id), default=default.expr)
vararg = args.vararg if args.vararg else ''
kwarg = args.kwarg if args.kwarg else ''
flags = []
if args.vararg:
flags.append('πg.CodeFlagVarArg')
if args.kwarg:
flags.append('πg.CodeFlagKWArg')
# The function object gets written to a temporary writer because we need
# it as an expression that we subsequently bind to some variable.
self.writer.write_tmpl(
'$result = πg.NewFunction('
'$name, $args, $vararg, $kwarg, func('
'$name, πg.NewCode($name, $args, $flags, func('
'πF *πg.Frame, πArgs []*πg.Object) (*πg.Object, *πg.BaseException) {',
result=result.name, name=util.go_str(node.name), args=func_args.expr,
vararg=util.go_str(vararg), kwarg=util.go_str(kwarg))
flags=' | '.join(flags) if flags else 0)
with self.writer.indent_block():
# Declare the local variables used in this function.
for var in func_block.vars.values():
......@@ -440,7 +441,7 @@ class ExprVisitor(ast.NodeVisitor):
with self.writer.indent_block(-1):
self.writer.write(body)
self.writer.write('return πg.None, nil')
self.writer.write('}).ToObject()')
self.writer.write('})).ToObject()')
return result
def _visit_seq_elts(self, elts):
......
......@@ -14,21 +14,13 @@
package grumpy
import (
"reflect"
)
// FrameType is the object representing the Python 'frame' type.
var BlockType = newBasisType("code", reflect.TypeOf(Block{}), toBlockUnsafe, ObjectType)
// Block is a handle to code that runs in a new scope such as a function, class
// or module.
type Block struct {
Object
// name is the name of the compiled function or class, or "<module>".
name string `attr:"co_name"`
name string
// filename is the path of the file where the Python code originated.
filename string `attr:"co_filename"`
filename string
// fn is a closure that executes the body of the code block. It may be
// re-entered multiple times, e.g. for exception handling.
fn func(*Frame, *Object) (*Object, *BaseException)
......@@ -36,11 +28,7 @@ type Block struct {
// NewBlock creates a Block object.
func NewBlock(name, filename string, fn func(*Frame, *Object) (*Object, *BaseException)) *Block {
return &Block{Object{typ: BlockType}, name, filename, fn}
}
func toBlockUnsafe(o *Object) *Block {
return (*Block)(o.toPointer())
return &Block{name, filename, fn}
}
// Exec runs b in the context of a new child frame of back.
......@@ -80,7 +68,3 @@ func (b *Block) execInternal(f *Frame, sendValue *Object) (*Object, *BaseExcepti
f.state = f.PopCheckpoint()
}
}
func initBlockType(dict map[string]*Object) {
BlockType.flags &= ^(typeFlagInstantiable | typeFlagBasetype)
}
......@@ -82,9 +82,9 @@ var builtinTypes = map[*Type]*builtinTypeInfo{
AttributeErrorType: {global: true},
BaseExceptionType: {init: initBaseExceptionType, global: true},
BaseStringType: {init: initBaseStringType, global: true},
BlockType: {init: initBlockType},
BoolType: {init: initBoolType, global: true},
BytesWarningType: {global: true},
CodeType: {},
DeprecationWarningType: {global: true},
dictItemIteratorType: {init: initDictItemIteratorType},
dictKeyIteratorType: {init: initDictKeyIteratorType},
......
package grumpy
import (
"fmt"
"reflect"
)
// CodeType is the object representing the Python 'code' type.
var CodeType = newBasisType("code", reflect.TypeOf(Code{}), toCodeUnsafe, ObjectType)
type CodeFlag int
const (
CodeFlagVarArg CodeFlag = 4
CodeFlagKWArg CodeFlag = 8
)
type Code struct {
Object
name string
// argc is the number of positional arguments.
argc int `attr:"co_argcount"`
// minArgc is the number of positional non-keyword arguments (i.e. the
// minimum number of positional arguments that must be passed).
minArgc int
flags CodeFlag `attr:"co_flags"`
args []FunctionArg
fn func(*Frame, []*Object) (*Object, *BaseException)
}
func NewCode(name string, args []FunctionArg, flags CodeFlag, fn func(*Frame, []*Object) (*Object, *BaseException)) *Code {
argc := len(args)
minArgc := 0
for ; minArgc < argc; minArgc++ {
if args[minArgc].Def != nil {
break
}
}
for _, arg := range args[minArgc:argc] {
if arg.Def == nil {
format := "%s() non-keyword arg %s after keyword arg"
logFatal(fmt.Sprintf(format, name, arg.Name))
}
}
return &Code{Object{typ: CodeType}, name, argc, minArgc, flags, args, fn}
}
func toCodeUnsafe(o *Object) *Code {
return (*Code)(o.toPointer())
}
func (c *Code) call(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
argc := len(args)
if argc > c.argc && c.flags&CodeFlagVarArg == 0 {
format := "%s() takes %d arguments (%d given)"
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf(format, c.name, c.argc, argc))
}
numBodyArgs := c.argc
varArgIndex, kwArgIndex := -1, -1
if c.flags&CodeFlagVarArg != 0 {
varArgIndex = numBodyArgs
numBodyArgs++
}
if c.flags&CodeFlagKWArg != 0 {
kwArgIndex = numBodyArgs
numBodyArgs++
}
bodyArgs := f.MakeArgs(numBodyArgs)
i := 0
for ; i < argc && i < c.argc; i++ {
bodyArgs[i] = args[i]
}
if varArgIndex != -1 {
bodyArgs[varArgIndex] = NewTuple(args[i:].makeCopy()...).ToObject()
}
var kwargDict *Dict
if kwArgIndex != -1 {
kwargDict = NewDict()
bodyArgs[kwArgIndex] = kwargDict.ToObject()
}
for _, kw := range kwargs {
name := kw.Name
j := 0
for ; j < c.argc; j++ {
if c.args[j].Name == name {
if bodyArgs[j] != nil {
format := "%s() got multiple values for keyword argument '%s'"
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf(format, c.name, name))
}
bodyArgs[j] = kw.Value
break
}
}
if j == c.argc {
if kwargDict == nil {
format := "%s() got an unexpected keyword argument '%s'"
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf(format, c.name, name))
}
if raised := kwargDict.SetItemString(f, name, kw.Value); raised != nil {
return nil, raised
}
}
}
for ; i < c.argc; i++ {
arg := c.args[i]
if bodyArgs[i] == nil {
if arg.Def == nil {
format := "%s() takes at least %d arguments (%d given)"
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf(format, c.name, c.minArgc, argc))
}
bodyArgs[i] = arg.Def
}
}
ret, raised := c.fn(f, bodyArgs)
f.FreeArgs(bodyArgs)
return ret, raised
}
package grumpy
import (
"testing"
)
func TestXxx(t *testing.T) {
}
func TestNewCodeKeywordsCheck(t *testing.T) {
oldLogFatal := logFatal
defer func() { logFatal = oldLogFatal }()
var got string
logFatal = func(msg string) {
got = msg
}
NewCode("foo", []FunctionArg{{"bar", None}, {"baz", nil}}, 0, nil)
if want := "foo() non-keyword arg baz after keyword arg"; got != want {
t.Errorf("NewCode logged %q, want %q", got, want)
}
}
func TestNewCode(t *testing.T) {
testFunc := newBuiltinFunction("TestNewCode", func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
if raised := checkFunctionVarArgs(f, "TestNewCode", args, CodeType); raised != nil {
return nil, raised
}
return toCodeUnsafe(args[0]).call(f, args[1:], kwargs)
})
fn := func(f *Frame, args []*Object) (*Object, *BaseException) {
return NewTuple(Args(args).makeCopy()...).ToObject(), nil
}
cases := []invokeTestCase{
invokeTestCase{args: wrapArgs(NewCode("f1", nil, 0, fn)), want: NewTuple().ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f2", []FunctionArg{{"a", nil}}, 0, fn), 123), want: newTestTuple(123).ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f2", []FunctionArg{{"a", nil}}, 0, fn)), kwargs: wrapKWArgs("a", "apple"), want: newTestTuple("apple").ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f2", []FunctionArg{{"a", nil}}, 0, fn)), kwargs: wrapKWArgs("b", "bear"), wantExc: mustCreateException(TypeErrorType, "f2() got an unexpected keyword argument 'b'")},
invokeTestCase{args: wrapArgs(NewCode("f2", []FunctionArg{{"a", nil}}, 0, fn)), wantExc: mustCreateException(TypeErrorType, "f2() takes at least 1 arguments (0 given)")},
invokeTestCase{args: wrapArgs(NewCode("f2", []FunctionArg{{"a", nil}}, 0, fn), 1, 2, 3), wantExc: mustCreateException(TypeErrorType, "f2() takes 1 arguments (3 given)")},
invokeTestCase{args: wrapArgs(NewCode("f3", []FunctionArg{{"a", nil}, {"b", nil}}, 0, fn), 1, 2), want: newTestTuple(1, 2).ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f3", []FunctionArg{{"a", nil}, {"b", nil}}, 0, fn), 1), kwargs: wrapKWArgs("b", "bear"), want: newTestTuple(1, "bear").ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f3", []FunctionArg{{"a", nil}, {"b", nil}}, 0, fn)), kwargs: wrapKWArgs("b", "bear", "a", "apple"), want: newTestTuple("apple", "bear").ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f3", []FunctionArg{{"a", nil}, {"b", nil}}, 0, fn), 1), kwargs: wrapKWArgs("a", "alpha"), wantExc: mustCreateException(TypeErrorType, "f3() got multiple values for keyword argument 'a'")},
invokeTestCase{args: wrapArgs(NewCode("f4", []FunctionArg{{"a", nil}, {"b", None}}, 0, fn), 123), want: newTestTuple(123, None).ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f4", []FunctionArg{{"a", nil}, {"b", None}}, 0, fn), 123, "bar"), want: newTestTuple(123, "bar").ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f4", []FunctionArg{{"a", nil}, {"b", None}}, 0, fn)), kwargs: wrapKWArgs("a", 123, "b", "bar"), want: newTestTuple(123, "bar").ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f5", []FunctionArg{{"a", nil}}, CodeFlagVarArg, fn), 1), want: newTestTuple(1, NewTuple()).ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f5", []FunctionArg{{"a", nil}}, CodeFlagVarArg, fn), 1, 2, 3), want: newTestTuple(1, newTestTuple(2, 3)).ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f6", []FunctionArg{{"a", nil}}, CodeFlagKWArg, fn), "bar"), want: newTestTuple("bar", NewDict()).ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f6", []FunctionArg{{"a", nil}}, CodeFlagKWArg, fn)), kwargs: wrapKWArgs("a", "apple", "b", "bear"), want: newTestTuple("apple", newTestDict("b", "bear")).ToObject()},
invokeTestCase{args: wrapArgs(NewCode("f6", []FunctionArg{{"a", nil}}, CodeFlagKWArg, fn), "bar"), kwargs: wrapKWArgs("b", "baz", "c", "qux"), want: newTestTuple("bar", newTestDict("b", "baz", "c", "qux")).ToObject()},
}
for _, cas := range cases {
if err := runInvokeTestCase(testFunc.ToObject(), &cas); err != "" {
t.Error(err)
}
}
}
......@@ -76,7 +76,7 @@ type Function struct {
Object
fn Func
name string `attr:"__name__"`
spec *functionSpec
code *Code `attr:"func_code"`
}
// FunctionArg describes a parameter to a Python function.
......@@ -88,61 +88,19 @@ type FunctionArg struct {
Def *Object
}
// functionSpec describes a Python user function.
type functionSpec struct {
args []FunctionArg
fn func(*Frame, []*Object) (*Object, *BaseException)
specArgc int
numBodyArgs int
varArgIndex int
kwArgIndex int
minArgc int
}
// NewFunction creates a function object corresponding to a Python function
// described by spec. When called, the arguments are validated before calling
// spec.Fn. This includes checking that an appropriate number of arguments are
// provided, populating *args and **kwargs if necessary, etc.
func NewFunction(name string, args []FunctionArg, vararg string, kwarg string, fn func(*Frame, []*Object) (*Object, *BaseException)) *Function {
specArgc := len(args)
numBodyArgs := specArgc
varArgIndex, kwArgIndex := -1, -1
if vararg != "" {
varArgIndex = numBodyArgs
numBodyArgs++
}
if kwarg != "" {
kwArgIndex = numBodyArgs
numBodyArgs++
}
minArgc := 0
for ; minArgc < specArgc; minArgc++ {
if args[minArgc].Def != nil {
break
}
}
for i := minArgc; i < specArgc; i++ {
if args[i].Def == nil {
format := "%s() non-keyword arg %s after keyword arg"
logFatal(fmt.Sprintf(format, name, args[i].Name))
}
}
spec := &functionSpec{
args: args,
fn: fn,
specArgc: specArgc,
numBodyArgs: numBodyArgs,
varArgIndex: varArgIndex,
kwArgIndex: kwArgIndex,
minArgc: minArgc,
}
return &Function{Object{typ: FunctionType, dict: NewDict()}, nil, name, spec}
// taking the given args, vararg and kwarg. When called, the arguments are
// validated before calling fn. This includes checking that an appropriate
// number of arguments are provided, populating *args and **kwargs if
// necessary, etc.
func NewFunction(name string, c *Code) *Function {
return &Function{Object{typ: FunctionType, dict: NewDict()}, nil, name, c}
}
// newBuiltinFunction returns a function object with the given name that
// invokes fn when called.
func newBuiltinFunction(name string, fn func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException)) *Function {
return &Function{Object{typ: FunctionType, dict: NewDict()}, fn, name, nil}
func newBuiltinFunction(name string, fn Func) *Function {
return &Function{Object: Object{typ: FunctionType, dict: NewDict()}, fn: fn, name: name}
}
func toFunctionUnsafe(o *Object) *Function {
......@@ -161,64 +119,11 @@ func (f *Function) Name() string {
func functionCall(f *Frame, callable *Object, args Args, kwargs KWArgs) (*Object, *BaseException) {
fun := toFunctionUnsafe(callable)
spec := fun.spec
if spec == nil {
code := fun.code
if code == nil {
return fun.fn(f, args, kwargs)
}
argc := len(args)
if argc > spec.specArgc && spec.varArgIndex == -1 {
format := "%s() takes %d arguments (%d given)"
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf(format, fun.name, spec.specArgc, argc))
}
bodyArgs := f.MakeArgs(spec.numBodyArgs)
i := 0
for ; i < argc && i < spec.specArgc; i++ {
bodyArgs[i] = args[i]
}
if spec.varArgIndex != -1 {
bodyArgs[spec.varArgIndex] = NewTuple(args[i:].makeCopy()...).ToObject()
}
var kwargDict *Dict
if spec.kwArgIndex != -1 {
kwargDict = NewDict()
bodyArgs[spec.kwArgIndex] = kwargDict.ToObject()
}
for _, kw := range kwargs {
name := kw.Name
j := 0
for ; j < spec.specArgc; j++ {
if spec.args[j].Name == name {
if bodyArgs[j] != nil {
format := "%s() got multiple values for keyword argument '%s'"
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf(format, fun.name, name))
}
bodyArgs[j] = kw.Value
break
}
}
if j == spec.specArgc {
if kwargDict == nil {
format := "%s() got an unexpected keyword argument '%s'"
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf(format, fun.name, name))
}
if raised := kwargDict.SetItemString(f, name, kw.Value); raised != nil {
return nil, raised
}
}
}
for ; i < spec.specArgc; i++ {
arg := spec.args[i]
if bodyArgs[i] == nil {
if arg.Def == nil {
format := "%s() takes at least %d arguments (%d given)"
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf(format, fun.name, spec.minArgc, argc))
}
bodyArgs[i] = arg.Def
}
}
ret, raised := spec.fn(f, bodyArgs)
f.FreeArgs(bodyArgs)
return ret, raised
return code.call(f, args, kwargs)
}
func functionGet(_ *Frame, desc, instance *Object, owner *Type) (*Object, *BaseException) {
......
......@@ -19,113 +19,16 @@ import (
"testing"
)
func TestNewFunctionKeywordsCheck(t *testing.T) {
oldLogFatal := logFatal
defer func() { logFatal = oldLogFatal }()
var got string
logFatal = func(msg string) {
got = msg
}
NewFunction("foo", []FunctionArg{{"bar", None}, {"baz", nil}}, "", "", nil)
if want := "foo() non-keyword arg baz after keyword arg"; got != want {
t.Errorf("NewFunction logged %q, want %q", got, want)
}
}
func TestNewFunction(t *testing.T) {
fn := func(f *Frame, args []*Object) (*Object, *BaseException) {
return NewTuple(Args(args).makeCopy()...).ToObject(), nil
}
cases := []struct {
fun *Function
invokeTestCase
}{
{
NewFunction("f1", nil, "", "", fn),
invokeTestCase{want: NewTuple().ToObject()},
},
{
NewFunction("f2", []FunctionArg{{"a", nil}}, "", "", fn),
invokeTestCase{args: wrapArgs(123), want: newTestTuple(123).ToObject()},
},
{
NewFunction("f2", []FunctionArg{{"a", nil}}, "", "", fn),
invokeTestCase{kwargs: wrapKWArgs("a", "apple"), want: newTestTuple("apple").ToObject()},
},
{
NewFunction("f2", []FunctionArg{{"a", nil}}, "", "", fn),
invokeTestCase{kwargs: wrapKWArgs("b", "bear"), wantExc: mustCreateException(TypeErrorType, "f2() got an unexpected keyword argument 'b'")},
},
{
NewFunction("f2", []FunctionArg{{"a", nil}}, "", "", fn),
invokeTestCase{wantExc: mustCreateException(TypeErrorType, "f2() takes at least 1 arguments (0 given)")},
},
{
NewFunction("f2", []FunctionArg{{"a", nil}}, "", "", fn),
invokeTestCase{args: wrapArgs(1, 2, 3), wantExc: mustCreateException(TypeErrorType, "f2() takes 1 arguments (3 given)")},
},
{
NewFunction("f3", []FunctionArg{{"a", nil}, {"b", nil}}, "", "", fn),
invokeTestCase{args: wrapArgs(1, 2), want: newTestTuple(1, 2).ToObject()},
},
{
NewFunction("f3", []FunctionArg{{"a", nil}, {"b", nil}}, "", "", fn),
invokeTestCase{args: wrapArgs(1), kwargs: wrapKWArgs("b", "bear"), want: newTestTuple(1, "bear").ToObject()},
},
{
NewFunction("f3", []FunctionArg{{"a", nil}, {"b", nil}}, "", "", fn),
invokeTestCase{kwargs: wrapKWArgs("b", "bear", "a", "apple"), want: newTestTuple("apple", "bear").ToObject()},
},
{
NewFunction("f3", []FunctionArg{{"a", nil}, {"b", nil}}, "", "", fn),
invokeTestCase{args: wrapArgs(1), kwargs: wrapKWArgs("a", "alpha"), wantExc: mustCreateException(TypeErrorType, "f3() got multiple values for keyword argument 'a'")},
},
{
NewFunction("f4", []FunctionArg{{"a", nil}, {"b", None}}, "", "", fn),
invokeTestCase{args: wrapArgs(123), want: newTestTuple(123, None).ToObject()},
},
{
NewFunction("f4", []FunctionArg{{"a", nil}, {"b", None}}, "", "", fn),
invokeTestCase{args: wrapArgs(123, "bar"), want: newTestTuple(123, "bar").ToObject()},
},
{
NewFunction("f4", []FunctionArg{{"a", nil}, {"b", None}}, "", "", fn),
invokeTestCase{kwargs: wrapKWArgs("a", 123, "b", "bar"), want: newTestTuple(123, "bar").ToObject()},
},
{
NewFunction("f5", []FunctionArg{{"a", nil}}, "args", "", fn),
invokeTestCase{args: wrapArgs(1), want: newTestTuple(1, NewTuple()).ToObject()},
},
{
NewFunction("f5", []FunctionArg{{"a", nil}}, "args", "", fn),
invokeTestCase{args: wrapArgs(1, 2, 3), want: newTestTuple(1, newTestTuple(2, 3)).ToObject()},
},
{
NewFunction("f6", []FunctionArg{{"a", nil}}, "", "kwargs", fn),
invokeTestCase{args: wrapArgs("bar"), want: newTestTuple("bar", NewDict()).ToObject()},
},
{
NewFunction("f6", []FunctionArg{{"a", nil}}, "", "kwargs", fn),
invokeTestCase{kwargs: wrapKWArgs("a", "apple", "b", "bear"), want: newTestTuple("apple", newTestDict("b", "bear")).ToObject()},
},
{
NewFunction("f6", []FunctionArg{{"a", nil}}, "", "kwargs", fn),
invokeTestCase{args: wrapArgs("bar"), kwargs: wrapKWArgs("b", "baz", "c", "qux"), want: newTestTuple("bar", newTestDict("b", "baz", "c", "qux")).ToObject()},
},
}
for _, cas := range cases {
if err := runInvokeTestCase(cas.fun.ToObject(), &cas.invokeTestCase); err != "" {
t.Error(err)
}
}
}
func TestFunctionCall(t *testing.T) {
fun := newBuiltinFunction("fun", func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
foo := newBuiltinFunction("foo", func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
return newTestTuple(NewTuple(args.makeCopy()...), kwargs.makeDict()).ToObject(), nil
}).ToObject()
bar := NewFunction("bar", NewCode("bar", nil, CodeFlagVarArg, func(f *Frame, args []*Object) (*Object, *BaseException) {
return args[0], nil
}))
cases := []invokeTestCase{
{args: wrapArgs(fun, 123, "abc"), kwargs: wrapKWArgs("b", "bear"), want: newTestTuple(newTestTuple(123, "abc"), newTestDict("b", "bear")).ToObject()},
{args: wrapArgs(foo, 123, "abc"), kwargs: wrapKWArgs("b", "bear"), want: newTestTuple(newTestTuple(123, "abc"), newTestDict("b", "bear")).ToObject()},
{args: wrapArgs(bar, "bar", "baz"), want: newTestTuple("bar", "baz").ToObject()},
{wantExc: mustCreateException(TypeErrorType, "unbound method __call__() must be called with function instance as first argument (got nothing instead)")},
{args: wrapArgs(newObject(ObjectType)), wantExc: mustCreateException(TypeErrorType, "unbound method __call__() must be called with function instance as first argument (got object instance instead)")},
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment