[pypy-svn] r33717 - in pypy/dist/pypy/translator/cli: . src test

antocuni at codespeak.net antocuni at codespeak.net
Wed Oct 25 14:05:53 CEST 2006


Author: antocuni
Date: Wed Oct 25 14:05:51 2006
New Revision: 33717

Added:
   pypy/dist/pypy/translator/cli/query.py   (contents, props changed)
   pypy/dist/pypy/translator/cli/src/query.cs
   pypy/dist/pypy/translator/cli/test/test_query.py   (contents, props changed)
Removed:
   pypy/dist/pypy/translator/cli/dotnetdb.py
Modified:
   pypy/dist/pypy/translator/cli/dotnet.py
   pypy/dist/pypy/translator/cli/function.py
   pypy/dist/pypy/translator/cli/gencli.py
   pypy/dist/pypy/translator/cli/rte.py
   pypy/dist/pypy/translator/cli/support.py
   pypy/dist/pypy/translator/cli/test/test_dotnet.py
Log:
Automatically discover .NET type informations when requested by
executing an external .NET utility.

The type descriptions are cached and saved in the
cli/query-descriptions file, so they don't need to be re-queried again
next time. I coudn't set svn:ignore for query-descriptions because of
a strange svn problem.



Modified: pypy/dist/pypy/translator/cli/dotnet.py
==============================================================================
--- pypy/dist/pypy/translator/cli/dotnet.py	(original)
+++ pypy/dist/pypy/translator/cli/dotnet.py	Wed Oct 25 14:05:51 2006
@@ -3,7 +3,7 @@
 from pypy.rpython.ootypesystem.ootype import meth, overload, Meth, StaticMethod
 from pypy.annotation import model as annmodel
 from pypy.rpython.rmodel import Repr
-from pypy.translator.cli.support import CLR
+from pypy.translator.cli.support import PythonNet
 
 ## Annotation model
 
@@ -124,9 +124,8 @@
     def __init__(self, INSTANCE, static_methods):
         self._name = INSTANCE._name
         self._INSTANCE = INSTANCE
-        self._static_methods = static_methods
-        for name, meth in static_methods.iteritems():
-            meth._set_attrs(self, name)
+        self._static_methods = {}
+        self._add_methods(static_methods)
 
     def __repr__(self):
         return '<%s>' % (self,)
@@ -134,6 +133,11 @@
     def __str__(self):
         return '%s(%s)' % (self.__class__.__name__, self._INSTANCE._name)
 
+    def _add_methods(self, methods):
+        self._static_methods.update(methods)
+        for name, meth in methods.iteritems():
+            meth._set_attrs(self, name)
+
     def _lookup(self, meth_name, ARGS):
         meth = self._static_methods[meth_name]
         return meth._get_desc(ARGS)
@@ -145,21 +149,21 @@
     def _load_class(self):
         names = self._INSTANCE._namespace.split('.')
         names.append(self._INSTANCE._classname)
-        obj = CLR
+        obj = PythonNet
         for name in names:
             obj = getattr(obj, name)
-        self._CLR_class = obj
+        self._PythonNet_class = obj
 
     def __getattr__(self, attr):
         if attr in self._static_methods:
             self._load_class()
-            return getattr(self._CLR_class, attr)
+            return getattr(self._PythonNet_class, attr)
         else:
             raise AttributeError
 
     def __call__(self, *args):
         self._load_class()
-        return self._CLR_class(*args)
+        return self._PythonNet_class(*args)
 
 
 class Entry(ExtRegistryEntry):
@@ -169,9 +173,21 @@
         return SomeCliClass()
 
 
-
 class CliNamespace(object):
     def __init__(self, name):
         self._name = name
 
+    def __fullname(self, name):
+        if self._name is None:
+            return name
+        else:
+            return '%s.%s' % (self._name, name)
+
+    def __getattr__(self, attr):
+        from pypy.translator.cli.query import load_class_or_namespace
+        name = self.__fullname(attr)
+        load_class_or_namespace(name)
+        assert attr in self.__dict__
+        return getattr(self, attr)
 
+CLR = CliNamespace(None)

Modified: pypy/dist/pypy/translator/cli/function.py
==============================================================================
--- pypy/dist/pypy/translator/cli/function.py	(original)
+++ pypy/dist/pypy/translator/cli/function.py	Wed Oct 25 14:05:51 2006
@@ -13,11 +13,7 @@
 from pypy.translator.cli.metavm import InstructionList, Generator
 from pypy.translator.cli.node import Node
 from pypy.translator.cli.class_ import Class
-
-from pypy.tool.ansi_print import ansi_log
-import py
-log = py.log.Producer("cli") 
-py.log.setconsumer("cli", ansi_log) 
+from pypy.translator.cli.support import log
 
 class Function(Node, Generator):
     def __init__(self, db, graph, name = None, is_method = False, is_entrypoint = False):

Modified: pypy/dist/pypy/translator/cli/gencli.py
==============================================================================
--- pypy/dist/pypy/translator/cli/gencli.py	(original)
+++ pypy/dist/pypy/translator/cli/gencli.py	Wed Oct 25 14:05:51 2006
@@ -16,6 +16,7 @@
 from pypy.translator.cli.support import Tee
 from pypy.translator.cli.prebuiltnodes import get_prebuilt_nodes
 from pypy.translator.cli.stackopt import StackOptGenerator
+from pypy.translator.cli import query
 
 try:
     set
@@ -66,6 +67,7 @@
         self.db.gen_constants(self.ilasm)
         out.close()
         self.db.const_count.dump(self.const_stat)
+        query.savedesc()
         return self.tmpfile.strpath
 
     def gen_entrypoint(self):

Added: pypy/dist/pypy/translator/cli/query.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/translator/cli/query.py	Wed Oct 25 14:05:51 2006
@@ -0,0 +1,163 @@
+import cPickle as pickle
+import os.path
+from py.compat import subprocess
+from pypy.rpython.ootypesystem import ootype
+from pypy.translator.cli.rte import Query
+from pypy.translator.cli.sdk import SDK
+from pypy.translator.cli.support import log
+from pypy.translator.cli.dotnet import CLR, CliNamespace, CliClass,\
+     NativeInstance, _overloaded_static_meth, _static_meth
+    
+ClassCache = {}
+OOTypeCache = {}
+Descriptions = {}
+
+class Dummy: pass
+fake_root = Dummy()
+fake_root._INSTANCE = ootype.ROOT
+ClassCache['ROOT'] = fake_root
+ClassCache['System.Array'] = fake_root
+del fake_root
+del Dummy
+
+def _descfilename(filename):
+    if filename is None:
+        curdir = os.path.dirname(__file__)
+        return os.path.join(curdir, 'query-descriptions')
+    else:
+        return filename
+
+def savedesc(filename=None):
+    f = open(_descfilename(filename), 'wb')
+    pickle.dump(Descriptions, f, protocol=-1)
+    f.close()
+
+def loaddesc(filename=None):
+    filename = _descfilename(filename)
+    if not os.path.exists(filename):
+        return
+    f = open(filename, 'rb')
+    try:
+        newdesc = pickle.load(f)        
+    except pickle.UnpicklingError:
+        log.WARNING('query-descriptions file exits, but failed to unpickle')
+    else:
+        Descriptions.clear()
+        Descriptions.update(newdesc)
+
+def getattr_ex(target, attr):
+    parts = attr.split('.')
+    for part in parts:
+        target = getattr(target, part)
+    return target
+
+def setattr_ex(target, attr, value):
+    if '.' in attr:
+        namespace, attr = attr.rsplit('.', 1)
+        target = getattr_ex(target, namespace)
+    setattr(target, attr, value)
+
+def load_class_or_namespace(name):
+    try:
+        desc = Descriptions[name]
+    except KeyError:
+        desc = query_description(name)
+        Descriptions[name] = desc
+    setattr_ex(CLR, name, desc.build())
+
+def query_description(name):
+    log.query('Loading description for %s' % name)
+    arglist = SDK.runtime() + [Query.get(), name]
+    query = subprocess.Popen(arglist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    stdout, stderr = query.communicate()
+    retval = query.wait()
+    if retval == 0:
+        cls = ClassDesc()
+        exec stdout in cls.__dict__
+        del cls.__dict__['__builtins__']
+        return cls
+    elif retval == 1:
+        raise RuntimeError, 'query.exe failed with this message:\n%s' % stderr
+    elif retval == 2:
+        # can't load type, assume it's a namespace
+        return NamespaceDesc(name)
+
+def load_class_maybe(name):
+    if name not in ClassCache:
+        load_class_or_namespace(name)
+
+
+class Desc:
+    def build(self):
+        raise NotImplementedError
+    
+    def __eq__(self, other):
+        return self.__dict__ == other.__dict__
+
+    def __hash__(self):
+        raise TypeError
+    
+class NamespaceDesc(Desc):
+    def __init__(self, name):
+        self.name = name
+
+    def build(self):
+        return CliNamespace(self.name)
+
+class ClassDesc(Desc):
+    def build(self):
+        assert self.Assembly.startswith('mscorlib') # TODO: support external assemblies
+        namespace, name = self.FullName.rsplit('.', 1)
+
+        # construct OOTYPE and CliClass
+        load_class_maybe(self.BaseType)
+        BASETYPE = ClassCache[self.BaseType]._INSTANCE
+        TYPE = NativeInstance('[mscorlib]', namespace, name, BASETYPE, {}, {})
+        Class = CliClass(TYPE, {})
+        OOTypeCache[self.OOType] = TYPE
+        ClassCache[self.FullName] = Class
+
+        # render dependencies
+        for name in self.Depend:
+            load_class_maybe(name)
+
+        # add both static and instance methods
+        static_meths = self.group_methods(self.StaticMethods, _overloaded_static_meth,
+                                          _static_meth, ootype.StaticMethod)
+        meths = self.group_methods(self.Methods, ootype.overload, ootype.meth, ootype.Meth)
+        Class._add_methods(static_meths)
+        TYPE._add_methods(meths)
+        return Class
+
+    def group_methods(self, methods, overload, meth, Meth):
+        groups = {}
+        for name, args, result in methods:
+            groups.setdefault(name, []).append((args, result))
+
+        res = {}
+        for name, methlist in groups.iteritems():
+            if len(methlist) == 1:
+                args, result = methlist[0]
+                TYPE = self.get_method_type(Meth, args, result)
+                res[name] = meth(TYPE)
+            else:
+                TYPES = [self.get_method_type(Meth, args, result) for (args, result) in methlist]
+                meths = [meth(TYPE) for TYPE in TYPES]
+                res[name] = overload(*meths)
+        return res
+
+    def get_method_type(self, Meth, args, result):
+        ARGS = [self.get_ootype(arg) for arg in args]
+        RESULT = self.get_ootype(result)
+        return Meth(ARGS, RESULT)
+
+    def get_ootype(self, t):
+        # a bit messy, but works
+        if t.startswith('ootype.'):
+            _, name = t.split('.')
+            return getattr(ootype, name)
+        else:
+            return OOTypeCache[t]
+
+
+loaddesc() ## automatically loads the cached Dependencies

Modified: pypy/dist/pypy/translator/cli/rte.py
==============================================================================
--- pypy/dist/pypy/translator/cli/rte.py	(original)
+++ pypy/dist/pypy/translator/cli/rte.py	Wed Oct 25 14:05:51 2006
@@ -84,6 +84,10 @@
     FLAGS = ['/t:library', '/unsafe', '/r:Mono.Posix', '/r:main.exe']
     DEPENDENCIES = [MainStub]
 
+class Query(Target):
+    SOURCES = ['query.cs']
+    OUTPUT = 'query.exe'
+
 def get_pypy_dll():
     if os.environ.get('PYPYLIB', '').lower() == 'unix':
         DLL = UnixDLL
@@ -91,5 +95,6 @@
         DLL = FrameworkDLL
     return DLL.get()
 
+
 if __name__ == '__main__':
     get_pypy_dll()

Added: pypy/dist/pypy/translator/cli/src/query.cs
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/translator/cli/src/query.cs	Wed Oct 25 14:05:51 2006
@@ -0,0 +1,136 @@
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+
+public class Query
+{
+    public static Dictionary<Type, bool> PendingTypes = new Dictionary<Type, bool>();
+
+    public static int Main(string[] argv)
+    { 
+        if (argv.Length != 1) {
+            Console.Error.WriteLine("Usage: query full-qualified-name");
+            return 1;
+        }
+        
+        string name = argv[0];
+        Type t = Type.GetType(name);
+        if (t == null) {
+            Console.Error.WriteLine("Cannot load type {0}", name);
+            return 2;
+        }
+
+        if (!t.IsPublic) {
+            Console.Error.WriteLine("Cannot load a non-public type");
+            return 2;
+        }
+
+        PrintType(t);
+        return 0;
+    }
+
+    private static void PrintType(Type t)
+    {
+        Console.WriteLine("Assembly = '{0}'", t.Assembly.FullName);
+        Console.WriteLine("FullName = '{0}'", t.FullName);
+        Console.WriteLine("BaseType = '{0}'", GetBaseType(t));
+        Console.WriteLine("OOType = '{0}'", GetOOType(t));
+        PrintMethods("StaticMethods", t.GetMethods(BindingFlags.Static|BindingFlags.Public|BindingFlags.DeclaredOnly));
+        PrintMethods("Methods", t.GetMethods(BindingFlags.Instance|BindingFlags.Public|BindingFlags.DeclaredOnly));
+        PendingTypes.Remove(t);
+        PrintDepend();
+    }
+
+    private static string GetBaseType(Type t)
+    {
+        if (t == typeof(object))
+            return "ROOT"; // special case for System.Object to avoid circular dependencies
+        else if (t.BaseType == null)
+             return "System.Object"; // the only known case is the BaseType of an interface
+        else
+            return t.BaseType.FullName;
+    }
+
+    private static string GetOOType(Type t)
+    {
+        if (t == typeof(void))
+            return "ootype.Void";
+        else if (t == typeof(int))
+            return "ootype.Signed";
+        else if (t == typeof(uint))
+            return "ootype.Unsigned";
+        else if (t == typeof(long))
+            return "ootype.SignedLongLong";
+        else if (t == typeof(ulong))
+            return "ootype.UnsignedLongLong";
+        else if (t == typeof(bool))
+            return "ootype.Bool";
+        else if (t == typeof(double))
+            return "ootype.Float";
+        else if (t == typeof(char))
+            return "ootype.Char"; // maybe it should be unichar?
+        else if (t == typeof(string))
+            return "ootype.String";
+        else {
+            PendingTypes[t] = true;
+            string name = t.FullName.Replace(".", "_"); // TODO: ensure unicity
+            if (t.IsArray)
+                name = name.Replace("[]", "___array___");
+            return name;
+        }
+    }
+
+    private static void PrintMethods(string varname, MethodInfo[] methods)
+    {
+        Console.WriteLine("{0} = [", varname);
+        // MethodName, [ARGS], RESULT
+        foreach(MethodInfo meth in methods) {
+            if (IgnoreMethod(meth))
+                continue;
+            Console.Write("    ('{0}', [", meth.Name);
+            foreach(ParameterInfo par in meth.GetParameters()) {
+                Console.Write("'{0}'", GetOOType(par.ParameterType));
+                Console.Write(", ");
+            }
+            Console.WriteLine("], '{0}'),", GetOOType(meth.ReturnType));
+        }
+        Console.WriteLine("  ]");
+    }
+
+    private static bool IgnoreMethod(MethodInfo meth)
+    {
+        if (!meth.IsPublic)
+            return true;
+
+        // ignore all SpecialName but properties getter/setter
+        if (meth.IsSpecialName && !meth.Name.StartsWith("get_") && !meth.Name.StartsWith("set_"))
+            return true;
+
+        if (IgnoreType(meth.ReturnType))
+            return true;
+        foreach(ParameterInfo par in meth.GetParameters())
+            if (IgnoreType(par.ParameterType))
+                return true;
+
+        return false;
+    }
+
+    private static bool IgnoreType(Type t)
+    {
+        return !t.IsPrimitive && (t == typeof(System.ValueType) ||
+                                  t == typeof(System.Array) ||
+                                  t.IsValueType ||
+                                  t.IsByRef ||
+                                  t.IsPointer ||
+                                  t.IsGenericType ||
+                                  t.IsGenericTypeDefinition);
+    }
+
+    private static void PrintDepend()
+    {
+        Console.Write("Depend = [");
+        foreach(Type t in PendingTypes.Keys)
+            Console.Write("'{0}', ", t.FullName);
+        Console.WriteLine("]");
+    }
+}

Modified: pypy/dist/pypy/translator/cli/support.py
==============================================================================
--- pypy/dist/pypy/translator/cli/support.py	(original)
+++ pypy/dist/pypy/translator/cli/support.py	Wed Oct 25 14:05:51 2006
@@ -1,14 +1,18 @@
 import py
 from pypy.rpython.ootypesystem import ootype
 
+from pypy.tool.ansi_print import ansi_log
+log = py.log.Producer("cli") 
+py.log.setconsumer("cli", ansi_log) 
+
 try:
-    import CLR
+    import CLR as PythonNet
 except ImportError:
-    class _CLR:
+    class _PythonNet:
         def __getattr__(self, attr):
             py.test.skip('Must use pythonnet for being able to access .NET libraries')
-    CLR = _CLR()
-    del _CLR
+    PythonNet = _PythonNet()
+    del _PythonNet
 
 # some code has been stolen from genc
 def string_literal(s):

Modified: pypy/dist/pypy/translator/cli/test/test_dotnet.py
==============================================================================
--- pypy/dist/pypy/translator/cli/test/test_dotnet.py	(original)
+++ pypy/dist/pypy/translator/cli/test/test_dotnet.py	Wed Oct 25 14:05:51 2006
@@ -2,11 +2,11 @@
 from pypy.annotation import model as annmodel
 from pypy.translator.cli.test.runtest import CliTest
 from pypy.translator.cli.dotnet import SomeCliClass, SomeCliStaticMethod,\
-     NativeInstance
-from pypy.translator.cli.dotnetdb import System
-Math = System.Math
-ArrayList = System.Collections.ArrayList
-StringBuilder = System.Text.StringBuilder
+     NativeInstance, CLR
+
+Math = CLR.System.Math
+ArrayList = CLR.System.Collections.ArrayList
+StringBuilder = CLR.System.Text.StringBuilder
 
 class TestDotnet(CliTest):
 

Added: pypy/dist/pypy/translator/cli/test/test_query.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/translator/cli/test/test_query.py	Wed Oct 25 14:05:51 2006
@@ -0,0 +1,35 @@
+from pypy.translator.cli import query
+from pypy.translator.cli.dotnet import CLR, CliNamespace
+
+def test_load_namespace_simple():
+    query.load_class_or_namespace('System')
+    assert isinstance(CLR.System, CliNamespace)
+    assert CLR.System._name == 'System'
+
+def test_load_namespace_complex():
+    query.load_class_or_namespace('System.Collections')
+    assert isinstance(CLR.System, CliNamespace)
+    assert isinstance(CLR.System.Collections, CliNamespace)
+    assert CLR.System.Collections._name == 'System.Collections'
+
+def test_CLR_getattr():
+    System = CLR.System
+    assert isinstance(System, CliNamespace)
+    assert System._name == 'System'
+    assert hasattr(CLR, 'System')
+
+def test_System_Object():
+    Object = CLR.System.Object
+    assert Object._name == '[mscorlib]System.Object'
+    assert 'Equals' in Object._static_methods
+    assert 'ToString' in Object._INSTANCE._methods
+
+def test_savedesc():
+    from pypy.tool.udir import udir
+    CLR.System.Object # force System.Object to be loaded
+    olddesc = query.Descriptions.copy()
+    tmpfile = str(udir.join('descriptions'))
+    query.savedesc(tmpfile)
+    query.Descriptions.clear()
+    query.loaddesc(tmpfile)
+    assert query.Descriptions == olddesc



More information about the Pypy-commit mailing list