├── guppy ├── _version.py ├── heapy │ ├── test │ │ ├── __init__.py │ │ ├── test_RetaGraph.py │ │ ├── test_dependencies.py │ │ ├── test_Spec.py │ │ ├── test_gsl.py │ │ ├── test_sf.py │ │ ├── test_all.py │ │ ├── test_UniSet.py │ │ ├── test_OutputHandling.py │ │ ├── test_menuleak.py │ │ └── support.py │ ├── __init__.py │ ├── Target.py │ ├── Console.py │ └── ImpSet.py ├── gsl │ ├── __init__.py │ ├── Exceptions.py │ ├── Help.py │ ├── Gsml.py │ ├── FileIO.py │ └── Filer.py ├── etc │ ├── __init__.py │ ├── Descriptor.py │ ├── Code.py │ ├── etc.py │ ├── IterPermute.py │ ├── tkcursors.py │ ├── xterm.py │ ├── textView.py │ └── Cat.py ├── __init__.py └── sets │ └── __init__.py ├── MANIFEST.in ├── docs ├── __init__.py ├── pbscreen.jpg ├── test_guppy.py ├── docexample.py ├── test_heapy.py ├── heapy_tutorial.html ├── guppy.html ├── css │ └── guppy.css ├── heapy_RootState.html └── gsl.html ├── src ├── heapy │ ├── hpinit.h │ ├── relation.h │ ├── stdtypes.h │ ├── hv_cli_id.c │ ├── hv_cli_idset.c │ ├── nodegraph.h │ ├── classifier.h │ ├── initheapyc.c │ ├── heapy.h │ ├── roundupsize.c │ ├── impsets.c │ ├── hv_cli_indisize.c │ ├── hv_cli.c │ ├── hv_cli_user.c │ ├── heapdef.h │ ├── hv_cli_findex.c │ ├── hv_cli_rcs.c │ ├── hv_cli_prod.c │ ├── horizon.c │ ├── hv_cli_dictof.c │ └── xmemstats.c ├── sets │ ├── sets.h │ ├── nodeset.h │ ├── sets_internal.h │ ├── sets.c │ └── bitset.h └── include │ └── guppy.h ├── .gitignore ├── codecov.yml ├── specs ├── about_Prof.gsl ├── gen.gsl ├── genext.gsl ├── genguppy.gsl ├── heapykinds.gsl ├── kindnames.gsl ├── guppy.gsl ├── genguppydoc.py ├── heapy_tutorial.gsl ├── heapy_RefPat.gsl ├── heapy_RootState.gsl ├── docexample.gsl ├── gsl.gsl └── sets.gsl ├── LICENSE ├── ANNOUNCE ├── .github └── workflows │ └── pages.yml ├── setup.py └── README.md /guppy/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '3.1.5' 2 | -------------------------------------------------------------------------------- /guppy/heapy/test/__init__.py: -------------------------------------------------------------------------------- 1 | # Dummy file to make this directory a package. 2 | -------------------------------------------------------------------------------- /guppy/gsl/__init__.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Guppy Specification Language 3 | # @see {@link ...} 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py 2 | include ANNOUNCE 3 | include ChangeLog 4 | graft src 5 | -------------------------------------------------------------------------------- /docs/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class _GLUECLAMP_: 5 | """""" 6 | -------------------------------------------------------------------------------- /docs/pbscreen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuyifei1999/guppy3/HEAD/docs/pbscreen.jpg -------------------------------------------------------------------------------- /src/heapy/hpinit.h: -------------------------------------------------------------------------------- 1 | int fsb_dx_addmethods(PyObject *m, PyMethodDef *methods, PyObject *passthrough); 2 | 3 | -------------------------------------------------------------------------------- /guppy/etc/__init__.py: -------------------------------------------------------------------------------- 1 | class _GLUECLAMP_: 2 | def _get_iterpermute(self): return self.IterPermute.iterpermute 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /dist/ 3 | /guppy3.egg-info/ 4 | /docs/*,gsl-backuped 5 | 6 | __pycache__/ 7 | *.so 8 | venv/ 9 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_RetaGraph.py: -------------------------------------------------------------------------------- 1 | from guppy.heapy.test import test_support 2 | import sys 3 | import unittest 4 | from pprint import pprint 5 | -------------------------------------------------------------------------------- /src/sets/sets.h: -------------------------------------------------------------------------------- 1 | #ifndef Ny_Sets_H 2 | #define Ny_Sets_H 3 | 4 | #include "bitset.h" 5 | #include "nodeset.h" 6 | 7 | #endif /* Ny_Sets_H */ 8 | 9 | 10 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 1 6 | ignore: 7 | - "docs" 8 | - "specs" 9 | comment: false 10 | -------------------------------------------------------------------------------- /guppy/heapy/__init__.py: -------------------------------------------------------------------------------- 1 | class _GLUECLAMP_: 2 | uniset_imports = ('UniSet', 'View', 'Path', 'RefPat') 3 | 4 | def _get_fa(self): 5 | us = self.UniSet 6 | us.out_reach_module_names = self.uniset_imports 7 | return us.fromargs 8 | -------------------------------------------------------------------------------- /guppy/heapy/Target.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | class Target: 6 | def __init__(self, _hiding_tag_=None): 7 | self.wd = os.getcwd() 8 | self.pid = os.getpid() 9 | self.sys = sys 10 | self._hiding_tag_ = _hiding_tag_ 11 | -------------------------------------------------------------------------------- /src/heapy/relation.h: -------------------------------------------------------------------------------- 1 | #ifndef Ny_RELATION_H 2 | 3 | typedef struct { 4 | PyObject_HEAD 5 | int kind; 6 | PyObject *relator; 7 | } NyRelationObject; 8 | 9 | #define NyRelation_Check(op) PyObject_TypeCheck(op, &NyRelation_Type) 10 | 11 | #endif /* #ifndef Ny_RELATION_H */ 12 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_dependencies.py: -------------------------------------------------------------------------------- 1 | # Test the libraries we are dependent on 2 | # Only sets right now. 3 | 4 | 5 | def test_main(debug=0): 6 | print('Testing sets') 7 | from guppy.sets import test 8 | test.test_main() 9 | 10 | 11 | if __name__ == "__main__": 12 | test_main() 13 | -------------------------------------------------------------------------------- /docs/test_guppy.py: -------------------------------------------------------------------------------- 1 | # Tests generated by: guppy.gsl.Tester 2 | # Date: Mon Oct 20 20:07:59 2025 3 | class Tester: 4 | tests = {} 5 | def test_guppy(self, arg): 6 | t0 = arg.Root() 7 | t1 = arg.hpy() 8 | t2 = arg.hpy(ht=()) 9 | tests['.tgt.kindnames.guppy'] = test_guppy 10 | -------------------------------------------------------------------------------- /src/include/guppy.h: -------------------------------------------------------------------------------- 1 | #ifndef GUPPY_H_INCLUDED 2 | 3 | #define NYFILL(t) { \ 4 | if (!t.tp_new) { \ 5 | t.tp_new = PyType_GenericNew; \ 6 | } \ 7 | if (PyType_Ready(&t) < 0) return -1; \ 8 | } 9 | 10 | #endif /* GUPPY_H_INCLUDED */ 11 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_Spec.py: -------------------------------------------------------------------------------- 1 | from guppy.heapy.test import support 2 | import sys 3 | import unittest 4 | 5 | 6 | class TestCase(support.TestCase): 7 | pass 8 | 9 | 10 | class FirstCase(TestCase): 11 | def test_1(self): 12 | Spec = self.heapy.Spec 13 | TestEnv = Spec.mkTestEnv(Spec._Specification_) 14 | 15 | TestEnv.test_contains(Spec) 16 | 17 | 18 | if __name__ == "__main__": 19 | support.run_unittest(FirstCase, 1) 20 | -------------------------------------------------------------------------------- /specs/about_Prof.gsl: -------------------------------------------------------------------------------- 1 | .title = About Heapy Profile Browser 2 | .tk_geometry = 400x200 3 | .h1: Heapy Profile Browser 4 | 5 | .table 6 | ..tr 7 | ...th: Version 8 | ...td: 0.1 9 | ..tr 10 | ...th: Author 11 | ...td: Sverker Nilsson 12 | ..tr 13 | ...th: Email 14 | ...td: sn@sncs.se 15 | ..tr 16 | ...th: License 17 | ...td: MIT 18 | 19 | .p 20 | ..small 21 | ...em: Copyright (c) 2005--2008 22 | ...span: S. Nilsson Computer System AB 23 | ...span: Linkoping, Sweden 24 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_gsl.py: -------------------------------------------------------------------------------- 1 | # Test the gsl subpackage 2 | # Ideally this should be a top level test. 3 | 4 | 5 | def test_main(debug=0): 6 | from guppy import Root 7 | gsl = Root().guppy.gsl 8 | gsl.Document._test_main_() 9 | gsl.DottedTree.test_main() 10 | gsl.FileIO.set_test_mode() 11 | gsl.Filer._test_main_() 12 | gsl.Gsml._test_main_() 13 | gsl.Main._test_main_() 14 | gsl.SpecNodes.test_main() 15 | # gsl.Text.test() 16 | 17 | 18 | if __name__ == "__main__": 19 | test_main() 20 | -------------------------------------------------------------------------------- /src/heapy/stdtypes.h: -------------------------------------------------------------------------------- 1 | #ifndef NY_STDDEFS_INCLUDED 2 | #define NY_STDDEFS_INCLUDED 3 | 4 | /* 5 | Definitions of type structure(s) that were not exported 6 | but were needed anyway. 7 | XXX dangerous, if Python changes this may break. 8 | Should be made officially exported. 9 | Or pull some from offset in tp_members, but that seems 10 | a too complicated workaround for now. 11 | 12 | */ 13 | 14 | 15 | typedef struct { 16 | PyObject_HEAD 17 | PyObject *mapping; 18 | } mappingproxyobject; 19 | 20 | 21 | #endif /* NY_STDDEFS_INCLUDED */ 22 | -------------------------------------------------------------------------------- /guppy/gsl/Exceptions.py: -------------------------------------------------------------------------------- 1 | class GSL_Error(Exception): 2 | pass 3 | 4 | 5 | class TooManyErrors(GSL_Error): 6 | pass 7 | 8 | 9 | class HadReportedError(GSL_Error): 10 | pass 11 | 12 | 13 | class ReportedError(GSL_Error): 14 | pass 15 | 16 | 17 | class UndefinedError(ReportedError): 18 | pass 19 | 20 | 21 | class DuplicateError(ReportedError): 22 | pass 23 | 24 | 25 | class CompositionError(GSL_Error): 26 | pass 27 | 28 | 29 | class CoverageError(ReportedError): 30 | pass 31 | 32 | 33 | class ConditionError(GSL_Error): 34 | pass 35 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_sf.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | # This is to see that the total memory usage doesn't increase with time 4 | # i.e. no leakage / link between consecutive usages of hsp. 5 | # This will run for ever, to be monitored by the printout and some external monitor. 6 | 7 | 8 | def t(): 9 | from guppy import hsp 10 | while 1: 11 | import guppy.heapy.UniSet 12 | import gc 13 | importlib.reload(guppy.heapy.UniSet) 14 | hp = hsp() 15 | x = None 16 | x = hp.heap() 17 | print(x) 18 | gc.collect() 19 | print(x[0]) 20 | print(x[1]) 21 | print(x[2]) 22 | gc.collect() 23 | print(x & dict) 24 | -------------------------------------------------------------------------------- /specs/gen.gsl: -------------------------------------------------------------------------------- 1 | .import:: module heapyc, HeapView, NodeGraph 2 | ..from: heapyc 3 | 4 | .import:: CommonSet, NodeSet, MutNodeSet, ImmNodeSet, module_sets 5 | ..from: sets 6 | 7 | .document: test_heapyc 8 | ..output: tester 9 | ..c: ..test of: NodeGraph 10 | ..c: ...coverage: length 2 11 | ..test of: HeapView 12 | ..test of: MutNodeSet 13 | ...coverage: length 2 14 | ..test of: ImmNodeSet 15 | ...coverage: length 2 16 | ..test of: module_sets 17 | ...coverage: length 3 18 | 19 | .document: heapyc 20 | ..output: html, latex 21 | ..man page of: module heapyc 22 | ..man page of: HeapView 23 | ..man page of: NodeGraph 24 | 25 | .document: sets 26 | ..output: html, latex 27 | ..man page of: module_sets 28 | ..man page of: CommonSet 29 | ..man page of: NodeSet 30 | ..man page of: MutNodeSet 31 | ..man page of: ImmNodeSet 32 | 33 | -------------------------------------------------------------------------------- /specs/genext.gsl: -------------------------------------------------------------------------------- 1 | .import:: module heapyc, HeapView, NodeGraph 2 | ..from: heapyc 3 | 4 | .import:: CommonSet, NodeSet, MutNodeSet, ImmNodeSet, module_sets 5 | ..from: sets 6 | 7 | .document: test_heapyc 8 | ..output: tester 9 | ..c: ..test of: NodeGraph 10 | ..c: ...coverage: length 2 11 | ..test of: HeapView 12 | ..test of: MutNodeSet 13 | ...coverage: length 2 14 | ..test of: ImmNodeSet 15 | ...coverage: length 2 16 | ..test of: module_sets 17 | ...coverage: length 3 18 | 19 | .document: heapyc 20 | ..output: html, latex 21 | ..man page of: module heapyc 22 | ..man page of: HeapView 23 | ..man page of: NodeGraph 24 | 25 | .document: sets 26 | ..output: html, latex 27 | ..man page of: module_sets 28 | ..man page of: CommonSet 29 | ..man page of: NodeSet 30 | ..man page of: MutNodeSet 31 | ..man page of: ImmNodeSet 32 | 33 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_all.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import sys 3 | 4 | autotests = ( 5 | 'dependencies', 6 | 'gsl', 7 | 'Classifiers', 8 | 'heapyc', 9 | 'ER', 10 | 'OutputHandling', 11 | 'Part', 12 | 'Path', 13 | 'RefPat', 14 | 'UniSet', 15 | 'View', 16 | ) 17 | 18 | # These are not tested here 19 | 20 | others = ( 21 | 'menuleak', 22 | 'sf', 23 | ) 24 | 25 | 26 | def test_main(debug=False): 27 | for name in autotests: 28 | testname = 'guppy.heapy.test.test_'+name 29 | try: 30 | del sys.modules[testname] 31 | except KeyError: 32 | pass 33 | x = importlib.import_module(testname) 34 | print('imported:', testname) 35 | f = x.test_main 36 | f(debug=debug) 37 | del sys.modules[testname] 38 | 39 | 40 | if __name__ == '__main__': 41 | test_main() 42 | -------------------------------------------------------------------------------- /guppy/heapy/Console.py: -------------------------------------------------------------------------------- 1 | import code 2 | 3 | 4 | class Console(code.InteractiveConsole): 5 | EOF_key_sequence = '-' 6 | 7 | def __init__(self, stdin, stdout, locals=None, filename=""): 8 | self.stdin = stdin 9 | self.stdout = stdout 10 | code.InteractiveConsole.__init__(self, locals, filename) 11 | 12 | def raw_input(self, prompt=""): 13 | """Write a prompt and read a line. 14 | 15 | The returned line does not include the trailing newline. 16 | When the user enters the EOF key sequence, EOFError is raised. 17 | 18 | """ 19 | self.write(prompt) 20 | line = self.stdin.readline() 21 | if not line: 22 | raise EOFError 23 | line = line.rstrip() 24 | if line == self.EOF_key_sequence: 25 | raise EOFError 26 | else: 27 | return line 28 | 29 | def write(self, data): 30 | self.stdout.write(data) 31 | self.stdout.flush() 32 | -------------------------------------------------------------------------------- /src/heapy/hv_cli_id.c: -------------------------------------------------------------------------------- 1 | /* Implementation of the identity classifier */ 2 | 3 | PyDoc_STRVAR(hv_cli_id_doc, 4 | "HV.cli_id() -> ObjectClassifier\n\ 5 | \n\ 6 | Return a classifier that classifies by identity.\n\ 7 | \n\ 8 | The classification of an object is the object itself."); 9 | 10 | static PyObject * 11 | hv_cli_id_classify(NyHeapViewObject *self, PyObject *arg) 12 | { 13 | Py_INCREF(arg); 14 | return arg; 15 | } 16 | 17 | static int 18 | hv_cli_id_le(PyObject * self, PyObject *a, PyObject *b) 19 | { 20 | return a <= b; 21 | } 22 | 23 | 24 | static NyObjectClassifierDef hv_cli_id_def = { 25 | 0, 26 | sizeof(NyObjectClassifierDef), 27 | "cli_id", 28 | "classifier returning the object itself", 29 | (binaryfunc)hv_cli_id_classify, 30 | (binaryfunc)0, 31 | hv_cli_id_le, 32 | }; 33 | 34 | static PyObject * 35 | hv_cli_id(NyHeapViewObject *self, PyObject *args) 36 | { 37 | return NyObjectClassifier_New((PyObject *)self, &hv_cli_id_def); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /guppy/heapy/ImpSet.py: -------------------------------------------------------------------------------- 1 | class _GLUECLAMP_: 2 | _imports_ = ( 3 | '_parent.UniSet:IdentitySetMulti', 4 | '_parent.UniSet:IdentitySet', 5 | '_parent.View:_hiding_tag_', 6 | '_root.guppy:sets', 7 | '_root.guppy.sets:NodeSet', 8 | '_root.guppy.sets:ImmNodeSet', 9 | '_root.guppy.sets:MutNodeSet', 10 | '_root.guppy.sets:immbit', 11 | '_root.guppy.sets:immbitrange', 12 | '_root.guppy.sets:immbitset', 13 | '_root.guppy.sets:mutbitset', 14 | ) 15 | 16 | def _get_emptynodeset(self): 17 | return self.immnodeset() 18 | 19 | def immnodeset(self, it=()): 20 | return self.sets.immnodeset(it, self._hiding_tag_) 21 | 22 | def immnodeset_union(self, sets): 23 | return self.sets.immnodeset_union(sets, self._hiding_tag_) 24 | 25 | def mutnodeset(self, *args, **kwds): 26 | s = self.sets.mutnodeset(*args, **kwds) 27 | s._hiding_tag_ = self._hiding_tag_ 28 | return s 29 | -------------------------------------------------------------------------------- /guppy/etc/Descriptor.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import inspect 3 | 4 | 5 | class property_nondata: 6 | '''@property, but using non-data descriptor protocol''' 7 | def __init__(self, fget): 8 | self.fget = fget 9 | functools.update_wrapper(self, fget) 10 | 11 | def __get__(self, instance, owner=None): 12 | return self.fget(instance) 13 | 14 | 15 | class property_exp(property): 16 | '''@property, but blacklist tab completers like rlcompleter from getattr''' 17 | def __init__(self, fget, *, doc=None): 18 | super().__init__(fget) 19 | self.__doc__ = doc 20 | 21 | def __get__(self, instance, owner=None): 22 | try: 23 | frame = inspect.currentframe() 24 | try: 25 | frame = frame.f_back 26 | if frame.f_globals['__name__'] == 'rlcompleter': 27 | return None 28 | finally: 29 | del frame 30 | except Exception: 31 | pass 32 | return super().__get__(instance, owner) 33 | -------------------------------------------------------------------------------- /guppy/gsl/Help.py: -------------------------------------------------------------------------------- 1 | class _GLUECLAMP_: 2 | _imports_ = ( 3 | '_root:os', 4 | '_root:webbrowser', 5 | ) 6 | 7 | default_doc_file = 'guppy.html' 8 | 9 | def doc(self, subject=None, *args, **kwds): 10 | """\ 11 | This doesnt work well or at all 12 | There are painful were where-to-find the files issues 13 | for the distributed and installed package. 14 | for I couldn't have the data among the modules themselves. 15 | 16 | Get documentation about a subject or generally about the Guppy system. 17 | It will show the documentation in the system web browser. 18 | If the subject argument is an object that the documentation system 19 | recognizes, it will bring up the documentation for that kind of object. 20 | Otherwise it will bring up a general documentation page. 21 | """ 22 | 23 | self.doc_default() 24 | 25 | def doc_default(self): 26 | self.open_local_filename(self.default_doc_file) 27 | 28 | def open_local_filename(self, filename): 29 | self.webbrowser.open(self.os.path.join(self.doc_dir, filename)) 30 | -------------------------------------------------------------------------------- /src/heapy/hv_cli_idset.c: -------------------------------------------------------------------------------- 1 | /* Implementation of the identity-set classifier */ 2 | 3 | PyDoc_STRVAR(hv_cli_idset_doc, 4 | "HV.cli_id() -> ObjectClassifier\n\ 5 | \n\ 6 | Return a classifier that classifies by set of identity.\n\ 7 | \n\ 8 | The classification of an object is a singleton immnodeset containing the object itself."); 9 | 10 | 11 | static PyObject * 12 | hv_cli_idset_classify(NyHeapViewObject *self, PyObject *arg) 13 | { 14 | return (PyObject *)NyImmNodeSet_NewSingleton(arg, self->_hiding_tag_); 15 | } 16 | 17 | static int 18 | hv_cli_idset_le(PyObject * self, PyObject *a, PyObject *b) 19 | { 20 | return PyObject_RichCompareBool(a, b, Py_LE); 21 | } 22 | 23 | 24 | static NyObjectClassifierDef hv_cli_idset_def = { 25 | 0, 26 | sizeof(NyObjectClassifierDef), 27 | "cli_idset", 28 | "classifier returning singleton set containing object itself", 29 | (binaryfunc)hv_cli_idset_classify, 30 | (binaryfunc)0, 31 | hv_cli_idset_le, 32 | }; 33 | 34 | static PyObject * 35 | hv_cli_idset(NyHeapViewObject *self, PyObject *args) 36 | { 37 | return NyObjectClassifier_New((PyObject *)self, &hv_cli_idset_def); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/heapy/nodegraph.h: -------------------------------------------------------------------------------- 1 | #ifndef NY_NODEGRAPH_H 2 | #define NY_NODEGRAPH_H 3 | 4 | typedef struct { 5 | PyObject *src, *tgt; 6 | } NyNodeGraphEdge; 7 | 8 | typedef struct { 9 | PyObject_HEAD 10 | PyObject *_hiding_tag_; 11 | NyNodeGraphEdge *edges; 12 | Py_ssize_t used_size; 13 | Py_ssize_t allo_size; 14 | char is_mapping; 15 | char is_sorted; 16 | char is_preserving_duplicates; 17 | } NyNodeGraphObject; 18 | 19 | extern PyTypeObject NyNodeGraph_Type; 20 | 21 | #define NyNodeGraph_Check(op) PyObject_TypeCheck(op, &NyNodeGraph_Type) 22 | 23 | 24 | NyNodeGraphObject *NyNodeGraph_New(void); 25 | int NyNodeGraph_Region(NyNodeGraphObject *rg, PyObject *key, 26 | NyNodeGraphEdge **lop, NyNodeGraphEdge **hip); 27 | int NyNodeGraph_AddEdge(NyNodeGraphObject *rg, PyObject *src, PyObject *tgt); 28 | void NyNodeGraph_Clear(NyNodeGraphObject *rg); 29 | NyNodeGraphObject *NyNodeGraph_Copy(NyNodeGraphObject *rg); 30 | int NyNodeGraph_Invert(NyNodeGraphObject *rg); 31 | NyNodeGraphObject *NyNodeGraph_Inverted(NyNodeGraphObject *rg); 32 | int NyNodeGraph_Update(NyNodeGraphObject *a, PyObject *b); 33 | 34 | #endif /* NY_NODEGRAPH_H */ 35 | -------------------------------------------------------------------------------- /src/heapy/classifier.h: -------------------------------------------------------------------------------- 1 | #ifndef NY_CLASSIFIER_H 2 | #define NY_CLASSIFIER_H 3 | 4 | typedef struct { 5 | int flags; 6 | int size; 7 | char *name; 8 | char *doc; 9 | PyObject * (*classify)(PyObject *self, PyObject *arg); 10 | PyObject * (*memoized_kind)(PyObject *self, PyObject *kind); 11 | int (*cmp_le)(PyObject *self, PyObject *a, PyObject *b); 12 | } NyObjectClassifierDef; 13 | 14 | typedef struct{ 15 | PyObject_HEAD 16 | NyObjectClassifierDef *def; 17 | PyObject *self; 18 | } NyObjectClassifierObject; 19 | 20 | #define NyObjectClassifier_Check(op) PyObject_TypeCheck(op, &NyObjectClassifier_Type) 21 | 22 | int NyObjectClassifier_Compare(NyObjectClassifierObject *cli, PyObject *a, PyObject *b, int cmp); 23 | 24 | /* cmp argument (to select etc) 25 | The first 6 happen to correspond to Py_LT , Py_LE etc 26 | but I didn't want to define them as such to not introduce a dependency. 27 | */ 28 | 29 | #define CLI_LT 0 30 | #define CLI_LE 1 31 | #define CLI_EQ 2 32 | #define CLI_NE 3 33 | #define CLI_GT 4 34 | #define CLI_GE 5 35 | #define CLI_MAX 5 /* Current end of definitions */ 36 | 37 | #endif /* NY_CLASSIFIER_H */ 38 | 39 | -------------------------------------------------------------------------------- /src/heapy/initheapyc.c: -------------------------------------------------------------------------------- 1 | #define PY_SSIZE_T_CLEAN 2 | #include 3 | 4 | #define INITFUNC initheapyc 5 | #define MODNAME "heapyc" 6 | 7 | extern int fsb_dx_nybitset_init(PyObject *m); 8 | 9 | static PyMethodDef module_methods[] = 10 | { 11 | {NULL, NULL} 12 | }; 13 | 14 | int fsb_dx_addmethods(PyObject *m, PyMethodDef *methods, PyObject *passthrough) { 15 | PyObject *d, *v; 16 | PyMethodDef *ml; 17 | d = PyModule_GetDict(m); 18 | for (ml = methods; ml->ml_name != NULL; ml++) { 19 | v = PyCFunction_New(ml, passthrough); 20 | if (v == NULL) 21 | return -1; 22 | if (PyDict_SetItemString(d, ml->ml_name, v) != 0) { 23 | Py_DECREF(v); 24 | return -1; 25 | } 26 | Py_DECREF(v); 27 | } 28 | return 0; 29 | } 30 | 31 | DL_EXPORT (void) 32 | INITFUNC (void) 33 | { 34 | PyObject *m; 35 | m = Py_InitModule(MODNAME, module_methods); 36 | if (!m) 37 | goto Error; 38 | if (fsb_dx_nyhprof_init(m) == -1) 39 | goto Error; 40 | return; 41 | Error: 42 | if (PyErr_Occurred() == NULL) 43 | PyErr_SetString(PyExc_ImportError, "module initialization failed"); 44 | } 45 | -------------------------------------------------------------------------------- /guppy/etc/Code.py: -------------------------------------------------------------------------------- 1 | def co_code_findloadednames(co): 2 | """Find in the code of a code object, all loaded names. 3 | (by LOAD_NAME, LOAD_GLOBAL or LOAD_FAST) """ 4 | 5 | import dis 6 | from opcode import HAVE_ARGUMENT, opmap 7 | hasloadname = (opmap['LOAD_NAME'], 8 | opmap['LOAD_GLOBAL'], opmap['LOAD_FAST']) 9 | insns = dis.get_instructions(co) 10 | len_co_names = len(co.co_names) 11 | indexset = {} 12 | for insn in insns: 13 | if insn.opcode >= HAVE_ARGUMENT: 14 | if insn.opcode in hasloadname: 15 | indexset[insn.argval] = 1 16 | if len(indexset) >= len_co_names: 17 | break 18 | for name in co.co_varnames: 19 | try: 20 | del indexset[name] 21 | except KeyError: 22 | pass 23 | return indexset 24 | 25 | 26 | def co_findloadednames(co): 27 | """Find all loaded names in a code object and all its consts of code type""" 28 | names = {} 29 | names.update(co_code_findloadednames(co)) 30 | for c in co.co_consts: 31 | if isinstance(c, type(co)): 32 | names.update(co_findloadednames(c)) 33 | return names 34 | -------------------------------------------------------------------------------- /guppy/__init__.py: -------------------------------------------------------------------------------- 1 | """\ 2 | Top level package of Guppy, a library and programming environment 3 | currently providing in particular the Heapy subsystem, which supports 4 | object and heap memory sizing, profiling and debugging. 5 | 6 | What is exported is the following: 7 | 8 | hpy() Create an object that provides a Heapy entry point. 9 | Root() Create an object that provides a top level entry point. 10 | 11 | """ 12 | 13 | __all__ = ('hpy', 'Root') 14 | 15 | from guppy._version import __version__ 16 | 17 | from guppy.etc.Glue import Root # Get main Guppy entry point 18 | from guppy import sets as sets 19 | 20 | 21 | def hpy(ht=None): 22 | """\ 23 | Main entry point to the Heapy system. 24 | Returns an object that provides a session context and will import 25 | required modules on demand. Some commononly used methods are: 26 | 27 | .heap() get a view of the current reachable heap 28 | .iso(obj..) get information about specific objects 29 | 30 | The optional argument, useful for debugging heapy itself, is: 31 | 32 | ht an alternative hiding tag 33 | 34 | """ 35 | r = Root() 36 | if ht is not None: 37 | r.guppy.heapy.View._hiding_tag_ = ht 38 | return r.guppy.heapy.Use 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2005-2013 Sverker Nilsson, S. Nilsson Computer System AB 2 | Copyright (C) 2019-2021 YiFei Zhu 3 | Copyright (C) 2021-2022 YiFei Zhu, Google LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ANNOUNCE: -------------------------------------------------------------------------------- 1 | I am happy to announce Guppy 3 3.1.5 2 | 3 | Guppy 3 is a library and programming environment for Python, 4 | currently providing in particular the Heapy subsystem, which supports 5 | object and heap memory sizing, profiling and debugging. It also 6 | includes a prototypical specification language, the Guppy 7 | Specification Language (GSL), which can be used to formally specify 8 | aspects of Python programs and generate tests and documentation from a 9 | common source. 10 | 11 | Guppy 3 is a fork of Guppy-PE, created by Sverker Nilsson for Python 2. 12 | 13 | This release adds support for Python 3.13, and support for Python 3.8 14 | was dropped. 15 | 16 | Code for Remote & Monitor has been removed since it no longer worked since 17 | Python 3.9 (I hope PEP 734 will change this in the future). Other libraries 18 | such as aiomanhole might provide similar functionality with a much simpler 19 | implementation. 20 | 21 | This release also fixes a few bugs, including: 22 | 23 | o Fix -Wmissing-braces compile warning 24 | o Added some error handling code paths to bitset 25 | 26 | License: MIT 27 | 28 | The project homepage is on GitHub: 29 | 30 | https://github.com/zhuyifei1999/guppy3 31 | 32 | Enjoy and Happy New Year, 33 | 34 | YiFei Zhu 35 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Build guppydoc and deploy to Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | concurrency: 15 | group: pages 16 | cancel-in-progress: false 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: Setup Pages 26 | uses: actions/configure-pages@v3 27 | 28 | - name: Set up Python 29 | uses: actions/setup-python@v4 30 | with: 31 | python-version: 3.11 32 | 33 | - name: Build docs 34 | run: | 35 | pip install -ve . 36 | xvfb-run -a python specs/genguppydoc.py 37 | 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | with: 41 | path: docs 42 | 43 | deploy: 44 | environment: 45 | name: github-pages 46 | url: ${{ steps.deployment.outputs.page_url }} 47 | runs-on: ubuntu-latest 48 | needs: build 49 | steps: 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 53 | -------------------------------------------------------------------------------- /src/heapy/heapy.h: -------------------------------------------------------------------------------- 1 | #ifndef Ny_HEAPY_H 2 | 3 | struct ExtraType; 4 | 5 | typedef struct { 6 | PyObject_HEAD 7 | PyObject *root; 8 | PyObject *limitframe; 9 | PyObject *_hiding_tag_; 10 | PyObject *static_types; 11 | PyObject *weak_type_callback; 12 | char is_hiding_calling_interpreter; 13 | struct ExtraType **xt_table; 14 | int xt_mask; 15 | size_t xt_size; 16 | } NyHeapViewObject; 17 | 18 | #define NyHeapView_Check(op) PyObject_TypeCheck(op, &NyHeapView_Type) 19 | 20 | typedef struct ExtraType { 21 | PyTypeObject *xt_type; 22 | size_t (*xt_size)(PyObject *obj); 23 | int (*xt_traverse)(struct ExtraType *, PyObject *, visitproc, void *); 24 | int (*xt_relate)(struct ExtraType *, NyHeapRelate *r); 25 | struct ExtraType *xt_next; 26 | struct ExtraType *xt_base, *xt_he_xt; 27 | int (*xt_he_traverse)(struct ExtraType *, PyObject *, visitproc, void *); 28 | NyHeapViewObject *xt_hv; 29 | PyObject *xt_weak_type; 30 | NyHeapDef *xt_hd; 31 | Py_ssize_t xt_he_offs; 32 | int xt_trav_code; 33 | } ExtraType; 34 | 35 | 36 | 37 | typedef struct NyHeapClassifier{ 38 | PyObject_HEAD 39 | PyObject (*classify)(struct NyHeapClassifier*self, PyObject *obj); 40 | void *extra0, *extra1, *extra2, *extra3; 41 | } NyHeapClassifier; 42 | 43 | extern PyObject _Ny_RootStateStruct; /* Don't use this directly */ 44 | 45 | #define Ny_RootState (&_Ny_RootStateStruct) 46 | 47 | #endif /* Ny_HEAPY_H */ 48 | -------------------------------------------------------------------------------- /src/heapy/roundupsize.c: -------------------------------------------------------------------------------- 1 | /* From listobject.c */ 2 | 3 | static Py_ssize_t 4 | roundupsize(Py_ssize_t n) 5 | { 6 | size_t nbits = 0; 7 | size_t n2 = (size_t)n >> 5; 8 | 9 | /* Round up: 10 | * If n < 256, to a multiple of 8. 11 | * If n < 2048, to a multiple of 64. 12 | * If n < 16384, to a multiple of 512. 13 | * If n < 131072, to a multiple of 4096. 14 | * If n < 1048576, to a multiple of 32768. 15 | * If n < 8388608, to a multiple of 262144. 16 | * If n < 67108864, to a multiple of 2097152. 17 | * If n < 536870912, to a multiple of 16777216. 18 | * ... 19 | * If n < 2**(5+3*i), to a multiple of 2**(3*i). 20 | * 21 | * This over-allocates proportional to the list size, making room 22 | * for additional growth. The over-allocation is mild, but is 23 | * enough to give linear-time amortized behavior over a long 24 | * sequence of appends() in the presence of a poorly-performing 25 | * system realloc() (which is a reality, e.g., across all flavors 26 | * of Windows, with Win9x behavior being particularly bad -- and 27 | * we've still got address space fragmentation problems on Win9x 28 | * even with this scheme, although it requires much longer lists to 29 | * provoke them than it used to). 30 | */ 31 | do { 32 | n2 >>= 3; 33 | nbits += 3; 34 | } while (n2); 35 | return ((n >> nbits) + 1) << nbits; 36 | } 37 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_UniSet.py: -------------------------------------------------------------------------------- 1 | from guppy.heapy.test import support 2 | 3 | 4 | class FirstCase(support.TestCase): 5 | def setUp(self): 6 | support.TestCase.setUp(self) 7 | 8 | def test_1(self): 9 | asrt = self.assertTrue 10 | a = [] 11 | b = [] 12 | c = self.iso(a, b) 13 | asrt(len(c.nodes) == 2) 14 | asrt(a in c) 15 | asrt(b in c) 16 | asrt([] not in c) 17 | asrt(c not in c) 18 | 19 | d = self.idset(c.nodes) 20 | 21 | asrt(c.nodes == d.nodes) 22 | 23 | asrt(c == d) 24 | 25 | def test_2(self): 26 | # Test standard set operations 27 | H = self.idset 28 | e1 = [] 29 | e2 = {} 30 | e3 = () 31 | la = [], [e1], [e1, e2], [e1, e2, e3], [e2], [e2, e3], [e3] 32 | self.guppy.sets.test.test_set_operations( 33 | [H(x) for x in la], [H(x) for x in la], [H(x) for x in la]) 34 | 35 | def test_3(self): 36 | # Test out-reaching 37 | 38 | iso = self.iso 39 | 40 | a = [] 41 | b = [a] 42 | c = [b] 43 | self.View.root = c 44 | 45 | x = iso(b) 46 | 47 | self.assertTrue(x.referrers == iso(c)) 48 | self.aseq(x.referents, iso(a)) 49 | self.aseq(x.referents.referrers, x) 50 | self.aseq(x.dominos, iso(a, b)) 51 | 52 | 53 | def test_main(debug=False): 54 | support.run_unittest(FirstCase, debug) 55 | 56 | 57 | if __name__ == "__main__": 58 | test_main() 59 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_OutputHandling.py: -------------------------------------------------------------------------------- 1 | from guppy.heapy.test import support 2 | 3 | 4 | class FirstCase(support.TestCase): 5 | def setUp(self): 6 | support.TestCase.setUp(self) 7 | self.OH = self.heapy.OutputHandling 8 | 9 | def test_1(self): 10 | class T: 11 | def __init__(self, test, numlines, get_num_lines=None, get_more_msg=None): 12 | mod = test.OH 13 | 14 | def get_line_iter(): 15 | for i in range(numlines): 16 | yield '%d' % i 17 | 18 | self.mod = mod 19 | mod.setup_printing( 20 | self, 21 | get_line_iter=get_line_iter, 22 | max_more_lines=4, 23 | get_num_lines=get_num_lines, 24 | get_more_msg=get_more_msg) 25 | 26 | self.aseq(str(T(self, 4)), '0\n1\n2\n3') 27 | 28 | t = T(self, 6, lambda: 6) 29 | 30 | self.aseq( 31 | str(t), "0\n1\n2\n3\n") 32 | x = t.more 33 | self.aseq(str(x), '4\n5') 34 | self.aseq( 35 | str(x.top), "0\n1\n2\n3\n") 36 | 37 | self.aseq(str(x.all), "0\n1\n2\n3\n4\n5") 38 | 39 | t = T(self, 6, get_more_msg=lambda f, t: '<%d more rows>' % (6-t)) 40 | self.aseq(str(t), '0\n1\n2\n3\n<3 more rows>') 41 | 42 | 43 | def test_main(debug=0): 44 | support.run_unittest(FirstCase, debug) 45 | 46 | 47 | if __name__ == "__main__": 48 | test_main() 49 | -------------------------------------------------------------------------------- /guppy/sets/__init__.py: -------------------------------------------------------------------------------- 1 | from guppy.sets.setsc import BitSet # base bitset type 2 | from guppy.sets.setsc import ImmBitSet # immutable bitset type 3 | from guppy.sets.setsc import immbit # immutable bitset singleton constructor 4 | from guppy.sets.setsc import immbitrange # immutable bitset range constructor 5 | from guppy.sets.setsc import immbitset # immutable bitset constructor 6 | from guppy.sets.setsc import MutBitSet # mutable bitset 7 | from guppy.sets.setsc import NodeSet # base nodeset type 8 | from guppy.sets.setsc import ImmNodeSet # immmutable nodeset type 9 | from guppy.sets.setsc import MutNodeSet # mutable nodeset type 10 | 11 | from .setsc import _bs 12 | _bs.__module__ = 'guppy.sets' # ..to be able to set it. 13 | 14 | 15 | # Define some constructors. 16 | # Constructor names are lower case. 17 | # Some constructors are equal to types. 18 | # But this connection depends on the implementation. 19 | # So one may wish the user to not depend on this. 20 | 21 | mutbitset = MutBitSet 22 | immnodeset = ImmNodeSet 23 | mutnodeset = MutNodeSet 24 | 25 | 26 | def mutnodeset_union(iterable): 27 | "Return a mutable nodeset which is the union of all nodesets in iterable." 28 | set = mutnodeset() 29 | for it in iterable: 30 | set |= it 31 | return set 32 | 33 | 34 | def immnodeset_union(iterable, *args): 35 | "Return an immmutable nodeset which is the union of all nodesets in iterable." 36 | set = mutnodeset_union(iterable) 37 | return immnodeset(set, *args) 38 | 39 | 40 | # Make attributes assignable by reading one; 41 | # this is getting around a bug in Python 2.3.3 42 | # and should be harmless in any version. 43 | 44 | 45 | try: 46 | mutnodeset()._hiding_tag_ 47 | except AttributeError: 48 | pass 49 | -------------------------------------------------------------------------------- /specs/genguppy.gsl: -------------------------------------------------------------------------------- 1 | .import:: guppy 2 | ..from: guppy 3 | 4 | .import:: Use 5 | ..from: heapy_Use 6 | 7 | .import:: UniSet, Kind, KindWithAlt, KindOfProdFamily, KindOfTypeFamily, KindOfSizeFamily, 8 | KindOfRetClaSetFamily,KindOfInViaFamily,IdentitySet,IdentitySetNotEmpty,IdentitySetSingleton 9 | ..from: heapy_UniSet 10 | 11 | .import:: EquivalenceRelation, EquivalenceRelationByDictOwner 12 | ..from: heapy_ER 13 | 14 | .import:: RootStateType 15 | ..from: heapy_RootState 16 | 17 | .c 18 | ..import:: ReferencePattern 19 | ..from: heapy_RefPat 20 | 21 | .document: heapy_UniSet 22 | ..output: html 23 | ..man page of: UniSet 24 | ..man page of: Kind 25 | ..man page of: KindWithAlt 26 | ..man page of: KindOfProdFamily 27 | ..man page of: KindOfTypeFamily 28 | ..man page of: KindOfSizeFamily 29 | ..man page of: KindOfRetClaSetFamily 30 | ..man page of: KindOfInViaFamily 31 | ..man page of: IdentitySet 32 | ..man page of: IdentitySetNotEmpty 33 | ..man page of: IdentitySetSingleton 34 | ..man page of: EquivalenceRelation 35 | ..man page of: EquivalenceRelationByDictOwner 36 | 37 | .document: heapy_Use 38 | ..output: html 39 | ..man page of: Use 40 | 41 | .document: test_heapy 42 | ..output: tester 43 | ..test of: Use 44 | 45 | .document: heapy_RootState 46 | ..output: html 47 | ..man page of: RootStateType 48 | 49 | .c 50 | ..document: heapy_ReferencePattern 51 | ...output: html 52 | ...man page of: ReferencePattern 53 | 54 | .document: guppy 55 | ..output: html 56 | ..man page of: guppy 57 | 58 | .document: test_guppy 59 | ..output: tester 60 | ..test of: guppy 61 | 62 | .c 63 | ..context:: H 64 | ...python: 65 | from guppy import hpy 66 | h=hpy() 67 | 68 | .import:: Kind+ 69 | ..from: heapykinds 70 | 71 | .c 72 | ..and: Kind+ 73 | ...eg: h.Anything 74 | ....in context: H 75 | 76 | .c 77 | ..document: test_Use 78 | ...output: tester 79 | ...test of: Use 80 | -------------------------------------------------------------------------------- /src/heapy/impsets.c: -------------------------------------------------------------------------------- 1 | #define NyNodeSet_TYPE (nodeset_exports->type) 2 | 3 | #define NyNodeSet_Check(op) PyObject_TypeCheck(op, NyNodeSet_TYPE) 4 | 5 | NyNodeSet_Exports *nodeset_exports; 6 | 7 | /* Macro NODESET_EXPORTS where error (NULL) checking can be done */ 8 | #define NODESET_EXPORTS nodeset_exports 9 | 10 | NyNodeSetObject * 11 | NyMutNodeSet_New(void) { 12 | return NODESET_EXPORTS->newMut(); 13 | } 14 | 15 | NyNodeSetObject * 16 | NyMutNodeSet_NewHiding(PyObject *tag) { 17 | return NODESET_EXPORTS->newMutHiding(tag); 18 | } 19 | 20 | NyNodeSetObject * 21 | NyMutNodeSet_NewFlags(int flags) { 22 | return NODESET_EXPORTS->newMutFlags(flags); 23 | } 24 | 25 | int 26 | NyNodeSet_setobj(NyNodeSetObject *v, PyObject *obj) { 27 | return NODESET_EXPORTS->setobj(v, obj); 28 | } 29 | 30 | int 31 | NyNodeSet_clrobj(NyNodeSetObject *v, PyObject *obj) { 32 | return NODESET_EXPORTS->clrobj(v, obj); 33 | } 34 | 35 | 36 | int 37 | NyNodeSet_hasobj(NyNodeSetObject *v, PyObject *obj) { 38 | return NODESET_EXPORTS->hasobj(v, obj); 39 | } 40 | 41 | int NyNodeSet_iterate(NyNodeSetObject *ns, 42 | int (*visit)(PyObject *, void *), 43 | void *arg) { 44 | return NODESET_EXPORTS->iterate(ns, visit, arg);; 45 | } 46 | 47 | 48 | NyNodeSetObject * 49 | NyNodeSet_NewImmCopy(NyNodeSetObject *v) { 50 | return NODESET_EXPORTS->newImmCopy(v); 51 | } 52 | 53 | NyNodeSetObject * 54 | NyImmNodeSet_NewSingleton(PyObject *element, PyObject *hiding_tag) { 55 | return NODESET_EXPORTS->newImmSingleton(element, hiding_tag); 56 | } 57 | 58 | int 59 | NyNodeSet_be_immutable(NyNodeSetObject **nsp) { 60 | return NODESET_EXPORTS->be_immutable(nsp); 61 | } 62 | 63 | static int 64 | import_sets(void) 65 | { 66 | if (!nodeset_exports) { 67 | nodeset_exports = PyCapsule_Import("guppy.sets.setsc.NyNodeSet_Exports", 0); 68 | if (!nodeset_exports) 69 | return -1; 70 | } 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /guppy/etc/etc.py: -------------------------------------------------------------------------------- 1 | from io import StringIO 2 | 3 | 4 | def reptable(tb): 5 | if not tb: 6 | return 0, [] 7 | maxlens = [0]*len(tb[0]) 8 | for r in tb: 9 | if r == '-': 10 | continue 11 | for i, e in enumerate(r): 12 | maxlens[i] = max(maxlens[i], len(str(e))+1) 13 | 14 | sumlens = len(maxlens) 15 | for s in maxlens: 16 | sumlens += s 17 | 18 | out = [] 19 | for r in tb: 20 | if r == '-': 21 | out.append('-'*min(sumlens, 75)) 22 | else: 23 | so = '' 24 | for i, e in enumerate(r): 25 | s = str(e) 26 | if s.startswith('!>'): 27 | s = s[2:] 28 | fillright = 1 29 | elif s.isdigit(): 30 | fillright = 1 31 | else: 32 | fillright = 0 33 | ml = maxlens[i]-1 34 | fill = ' '*(ml - len(s)) 35 | if fillright: 36 | s = fill + s 37 | else: 38 | s = s + fill 39 | so += s + ' ' 40 | out.append(so) 41 | return maxlens, out 42 | 43 | 44 | def ptable(tb, f=None): 45 | if f is None: 46 | import sys 47 | f = sys.stdout 48 | _, lines = reptable(tb) 49 | for line in lines: 50 | line = line.rstrip() 51 | print(line, file=f) 52 | 53 | 54 | def strtable(tb): 55 | f = StringIO() 56 | ptable(tb, f) 57 | return f.getvalue() 58 | 59 | 60 | def str2int(s, msg='Hexadecimal literal in the form [-]0x... expected'): 61 | # xxx clumsy -- there should be a builtin function for this ! 62 | if s.startswith('-'): 63 | sign = -1 64 | s = s[1:] 65 | else: 66 | sign = 1 67 | if not s.startswith('0x'): 68 | raise ValueError(msg) 69 | s = s[2:] 70 | if s.endswith('l') or s.endswith('L'): 71 | s = s[:-1] 72 | return int(s, 16) * sign 73 | -------------------------------------------------------------------------------- /src/sets/nodeset.h: -------------------------------------------------------------------------------- 1 | #ifndef Ny_NODESETOBJECT_H 2 | #define Ny_NODESETOBJECT_H 3 | 4 | /* Flags for NyNodeSetObject */ 5 | 6 | #define NS_HOLDOBJECTS 1 /* Only to be cleared in special case with mutable nodeset. */ 7 | 8 | typedef struct { 9 | PyObject_VAR_HEAD 10 | int flags; 11 | PyObject *_hiding_tag_; 12 | union { 13 | PyObject *bitset; /* If mutable type, a mutable bitset with addresses (divided). */ 14 | PyObject *nodes[1]; /* If immutable type, the start of node array, in address order. */ 15 | } u; 16 | } NyNodeSetObject; 17 | 18 | NyNodeSetObject *NyMutNodeSet_New(void); 19 | NyNodeSetObject *NyMutNodeSet_NewFlags(int flags); 20 | NyNodeSetObject *NyMutNodeSet_NewHiding(PyObject *hiding_tag); 21 | 22 | int NyNodeSet_setobj(NyNodeSetObject *v, PyObject *obj); 23 | int NyNodeSet_clrobj(NyNodeSetObject *v, PyObject *obj); 24 | int NyNodeSet_hasobj(NyNodeSetObject *v, PyObject *obj); 25 | 26 | int NyNodeSet_iterate(NyNodeSetObject *hs, 27 | int (*visit)(PyObject *, void *), 28 | void *arg); 29 | 30 | NyNodeSetObject *NyImmNodeSet_NewCopy(NyNodeSetObject *v); 31 | NyNodeSetObject *NyImmNodeSet_NewSingleton(PyObject *element, PyObject *hiding_tag); 32 | int NyNodeSet_be_immutable(NyNodeSetObject **nsp); 33 | 34 | 35 | typedef struct { 36 | int flags; 37 | int size; 38 | char *ident_and_version; 39 | PyTypeObject *type; 40 | NyNodeSetObject *(*newMut)(void); 41 | NyNodeSetObject *(*newMutHiding)(PyObject *tag); 42 | NyNodeSetObject *(*newMutFlags)(int flags); 43 | NyNodeSetObject *(*newImmCopy)(NyNodeSetObject *v); 44 | NyNodeSetObject *(*newImmSingleton)(PyObject *v, PyObject *hiding_tag); 45 | int (*be_immutable)(NyNodeSetObject **nsp); 46 | int (*setobj)(NyNodeSetObject *v, PyObject *obj); 47 | int (*clrobj)(NyNodeSetObject *v, PyObject *obj); 48 | int (*hasobj)(NyNodeSetObject *v, PyObject *obj); 49 | int (*iterate)(NyNodeSetObject *ns, 50 | int (*visit)(PyObject *, void *), 51 | void *arg); 52 | } NyNodeSet_Exports; 53 | 54 | #endif /* Ny_NODESETOBJECT_H */ 55 | 56 | -------------------------------------------------------------------------------- /specs/heapykinds.gsl: -------------------------------------------------------------------------------- 1 | .kind:: EquivalenceRelation 2 | .kind:: EquivalenceRelationByDictOwner 3 | .kind:: Helper 4 | .kind:: IdentitySet 5 | .kind:: IdentitySetNotEmpty 6 | .kind:: IdentitySetSingleton 7 | .kind:: Kind 8 | .kind:: KindOfRetClaSetFamily 9 | .kind:: KindOfProdFamily 10 | .kind:: KindOfSizeFamily 11 | .kind:: KindOfTypeFamily 12 | .kind:: KindOfInViaFamily 13 | .kind:: KindWithAlt 14 | .kind:: MappingProxy 15 | .kind:: MorePrinter 16 | .kind:: Partition 17 | .kind:: Paths 18 | .kind:: ReferencePattern 19 | .kind:: RootStateType 20 | .kind:: SetOfKind 21 | .kind:: Stat 22 | .kind:: UniSet 23 | .kind:: UniSetAvantGarde 24 | .kind:: Use 25 | 26 | .superkind:: IdentitySet+ 27 | ..eg: self.hp.iso(1) 28 | 29 | .superkind:: AltOperator+ 30 | ..eg: '==' 31 | 32 | .superkind:: EquivalenceRelation+ 33 | 34 | .superkind:: Kind+ 35 | ..eg: self.hp.Anything 36 | 37 | .superkind:: ClodoKind+ 38 | ..eg: self.hp.Clodo(dictof=C) 39 | ...in context: 40 | class C: 41 | pass 42 | 43 | 44 | .superkind:: SetOfKind+ 45 | ..eg: self.hp.Size.sokind(24) 46 | 47 | .superkind:: SetOfClodoKind+ 48 | ..eg: self.hp.Clodo.sokind(int)(dictof=()) 49 | 50 | .superkind:: UniSet+ 51 | ..eg: self.hp.Nothing 52 | 53 | .superkind:: loadablefilenamestring+ 54 | ..eg: os.path.join(os.path.dirname(__file__),'profileexample.hpy') 55 | ...in context: 56 | import os 57 | 58 | .superkind:: loadableiterableofstrings+ 59 | ..eg: open(os.path.join(os.path.dirname(__file__),'profileexample.hpy')) 60 | ...in context: 61 | import os 62 | 63 | 64 | 65 | .superkind:: objectaddress+ 66 | ..eg: id(None) 67 | 68 | .superkind:: profilefilename+ 69 | ..eg: os.path.join(os.path.dirname(__file__),'profileexample.hpy') 70 | ...in context: 71 | import os 72 | 73 | .superkind:: relationname+ 74 | ..eg: '[0]' 75 | ..eg: '.a' 76 | 77 | .superkind:: typeexceptdict+ 78 | ..eg: int 79 | ..eg: C 80 | ...in context: 81 | class C: 82 | pass 83 | 84 | .superkind:: typeoremptytuple+ 85 | ..eg: () 86 | ..eg: C 87 | ...in context: 88 | class C: 89 | pass 90 | 91 | .superkind:: moduleaddress+ 92 | ..eg: id(sys) 93 | ...in context: 94 | import sys 95 | 96 | .superkind:: modulename+ 97 | ..eg: 'sys' 98 | 99 | .superkind:: writeable_filename_or_file+ 100 | 101 | .superkind:: writing_mode_string+ 102 | ..eg: 'a' 103 | -------------------------------------------------------------------------------- /docs/docexample.py: -------------------------------------------------------------------------------- 1 | # Tests generated by: guppy.gsl.Tester 2 | # Date: Mon Oct 20 20:07:59 2025 3 | class Tester: 4 | tests = {} 5 | def test_example_kind(self, arg): 6 | t0 = arg.m_returns() 7 | t1 = t0.range(1) 8 | t2 = t0.range(1, 1) 9 | t3 = t0.range(1, 1, 1) 10 | t4 = t0.a_nokind 11 | t5 = t0.m_alt([]) 12 | t6 = t0.m_repeat('abc', 'abc') 13 | t7 = t0.m_repeat(1, 'abc', 'abc') 14 | t8 = t0.m_repeat('abc', 'abc', 'abc') 15 | t9 = t0.m_repeat(1, 'abc', 'abc', 'abc') 16 | t10 = t0.m_repeat('abc', 'abc', 'abc', 'abc') 17 | t11 = t0.m_repeat(1, 'abc', 'abc', 'abc', 'abc') 18 | t12 = t0.m_draw_keywords() 19 | t13 = t0.m_draw_keywords(a=1) 20 | t14 = t0.m_draw_keywords(b=1) 21 | t15 = t0.m_noargs() 22 | t16 = t0.m_draw_keywords(a=1, b=1) 23 | t17 = t0.m_draw_keywords(c='abc') 24 | t18 = t0.m_draw_keywords(a=1, c='abc') 25 | t19 = t0.m_draw_keywords(b=1, c='abc') 26 | t20 = t0.m_draw_keywords(a=1, b=1, c='abc') 27 | t21 = t0.m_one(1) 28 | t22 = t0.m_opt() 29 | t23 = t0.m_opt(1) 30 | t24 = t0.m_opt(1, 'abc') 31 | t25 = t0.m_alt(1) 32 | t26 = t0.m_alt('abc') 33 | t27 = arg.range(1) 34 | t28 = arg.range(1, 1) 35 | t29 = arg.range(1, 1, 1) 36 | t30 = arg.a_nokind 37 | t31 = arg.m_alt([]) 38 | t32 = arg.m_repeat('abc', 'abc') 39 | t33 = arg.m_repeat(1, 'abc', 'abc') 40 | t34 = arg.m_repeat('abc', 'abc', 'abc') 41 | t35 = arg.m_repeat(1, 'abc', 'abc', 'abc') 42 | t36 = arg.m_repeat('abc', 'abc', 'abc', 'abc') 43 | t37 = arg.m_repeat(1, 'abc', 'abc', 'abc', 'abc') 44 | t38 = arg.m_draw_keywords() 45 | t39 = arg.m_draw_keywords(a=1) 46 | t40 = arg.m_draw_keywords(b=1) 47 | t41 = arg.m_noargs() 48 | t42 = arg.m_draw_keywords(a=1, b=1) 49 | t43 = arg.m_draw_keywords(c='abc') 50 | t44 = arg.m_draw_keywords(a=1, c='abc') 51 | t45 = arg.m_draw_keywords(b=1, c='abc') 52 | t46 = arg.m_draw_keywords(a=1, b=1, c='abc') 53 | t47 = arg.m_one(1) 54 | t48 = arg.m_opt() 55 | t49 = arg.m_opt(1) 56 | t50 = arg.m_opt(1, 'abc') 57 | t51 = arg.m_alt(1) 58 | t52 = arg.m_alt('abc') 59 | tests['.tgt.docexample.example_kind'] = test_example_kind 60 | -------------------------------------------------------------------------------- /src/heapy/hv_cli_indisize.c: -------------------------------------------------------------------------------- 1 | /* Implementation of the 'indisize' classifier */ 2 | 3 | typedef struct { 4 | PyObject_VAR_HEAD 5 | NyHeapViewObject *hv; 6 | PyObject *memo; 7 | } IndisizeObject; 8 | 9 | 10 | 11 | static PyObject * 12 | hv_cli_indisize_memoized_kind(IndisizeObject *self, PyObject *size) 13 | { 14 | PyObject *memoedsize = PyDict_GetItem(self->memo, size); 15 | if (!memoedsize) { 16 | if (PyDict_SetItem(self->memo, size, size) == -1) { 17 | return 0; 18 | } 19 | memoedsize = size; 20 | } 21 | Py_INCREF(memoedsize); 22 | return memoedsize; 23 | } 24 | 25 | static PyObject * 26 | hv_cli_indisize_classify(IndisizeObject *self, PyObject *obj) 27 | { 28 | PyObject *size = PyLong_FromSsize_t(hv_std_size(self->hv, obj)); 29 | PyObject *memoedsize; 30 | if (!size) 31 | return size; 32 | memoedsize = hv_cli_indisize_memoized_kind(self, size); 33 | Py_DECREF(size); 34 | return memoedsize; 35 | } 36 | 37 | static int 38 | hv_cli_indisize_le(PyObject * self, PyObject *a, PyObject *b) 39 | { 40 | return PyObject_RichCompareBool(a, b, Py_LE); 41 | } 42 | 43 | 44 | static NyObjectClassifierDef hv_cli_indisize_def = { 45 | 0, 46 | sizeof(NyObjectClassifierDef), 47 | "cli_type", 48 | "classifier returning object size", 49 | (binaryfunc)hv_cli_indisize_classify, 50 | (binaryfunc)hv_cli_indisize_memoized_kind, 51 | hv_cli_indisize_le, 52 | }; 53 | 54 | static char hv_cli_indisize_doc[] = 55 | "HV.cli_indisize(memo) -> ObjectClassifier\n" 56 | "\n" 57 | "Return a classifier that classifies by \"individual size\".\n" 58 | "\n" 59 | "The classification of each object is an int, containing the\n" 60 | "object's individual memory size. The argument is:\n" 61 | "\n" 62 | " memo A dict used to memoize the classification objects."; 63 | 64 | static PyObject * 65 | hv_cli_indisize(NyHeapViewObject *self, PyObject *args) 66 | { 67 | PyObject *r, *memo; 68 | IndisizeObject *s; 69 | if (!PyArg_ParseTuple(args, "O!:cli_indisize", 70 | &PyDict_Type, &memo)) 71 | return NULL; 72 | s = NYTUPLELIKE_NEW(IndisizeObject); 73 | if (!s) 74 | return 0; 75 | s->hv = self; 76 | Py_INCREF(s->hv); 77 | s->memo = memo; 78 | Py_INCREF(memo); 79 | r = NyObjectClassifier_New((PyObject *)s, &hv_cli_indisize_def); 80 | Py_DECREF(s); 81 | return r; 82 | } 83 | -------------------------------------------------------------------------------- /src/heapy/hv_cli.c: -------------------------------------------------------------------------------- 1 | /* Classifier implementations */ 2 | 3 | #define NYTUPLELIKE_NEW(t) ((t *)PyTuple_New((sizeof(t) - sizeof(PyTupleObject)) / sizeof(PyObject *) + 1)) 4 | 5 | #include "hv_cli_and.c" 6 | #include "hv_cli_dictof.c" 7 | #include "hv_cli_id.c" 8 | #include "hv_cli_idset.c" 9 | #include "hv_cli_prod.c" 10 | #include "hv_cli_rcs.c" 11 | #include "hv_cli_indisize.c" 12 | #include "hv_cli_findex.c" 13 | #include "hv_cli_rel.c" 14 | #include "hv_cli_user.c" 15 | 16 | static PyObject * 17 | hv_cli_none_classify(NyHeapViewObject *self, PyObject *arg) 18 | { 19 | Py_INCREF(Py_None); 20 | return Py_None; 21 | } 22 | 23 | static int 24 | hv_cli_none_le(PyObject * self, PyObject *a, PyObject *b) 25 | { 26 | return 1; 27 | } 28 | 29 | static NyObjectClassifierDef hv_cli_none_def = { 30 | 0, 31 | sizeof(NyObjectClassifierDef), 32 | "cli_none", 33 | "classifier returning None", 34 | (binaryfunc)hv_cli_none_classify, 35 | (binaryfunc)0, 36 | hv_cli_none_le, 37 | }; 38 | 39 | PyDoc_STRVAR(hv_cli_none_doc, 40 | "HV.cli_none() -> ObjectClassifier\n\ 41 | \n\ 42 | Return a classifier that classifies all objects the same.\n\ 43 | \n\ 44 | The classification of each object is None."); 45 | 46 | static PyObject * 47 | hv_cli_none(NyHeapViewObject *self, PyObject *args) 48 | { 49 | return NyObjectClassifier_New((PyObject *)self, &hv_cli_none_def); 50 | } 51 | 52 | static PyObject * 53 | hv_cli_type_classify(NyHeapViewObject *hv, PyObject *obj) 54 | { 55 | Py_INCREF(Py_TYPE(obj)); 56 | return (PyObject *)Py_TYPE(obj); 57 | } 58 | 59 | static int 60 | hv_cli_type_le(PyObject * self, PyObject *a, PyObject *b) 61 | { 62 | return (a == b) || PyType_IsSubtype((PyTypeObject *)a, (PyTypeObject *)b); 63 | 64 | } 65 | 66 | static NyObjectClassifierDef hv_cli_type_def = { 67 | 0, 68 | sizeof(NyObjectClassifierDef), 69 | "cli_type", 70 | "classifier returning object type", 71 | (binaryfunc)hv_cli_type_classify, 72 | (binaryfunc)0, 73 | hv_cli_type_le, 74 | }; 75 | 76 | PyDoc_STRVAR(hv_cli_type_doc, 77 | "HV.cli_type() -> ObjectClassifier\n\ 78 | \n\ 79 | Return a classifier that classifies by type.\n\ 80 | \n\ 81 | The classification of each object is the type, as given by its\n\ 82 | C-level member 'ob_type'. (This is the same as the type returned\n\ 83 | by the Python-level builtin 'type'.)"); 84 | 85 | static PyObject * 86 | hv_cli_type(NyHeapViewObject *self, PyObject *args) 87 | { 88 | return NyObjectClassifier_New((PyObject *)self, &hv_cli_type_def); 89 | } 90 | -------------------------------------------------------------------------------- /guppy/etc/IterPermute.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | 3 | 4 | def iterpermute(*args): 5 | args = [iter(a) for a in args] 6 | la = len(args) 7 | stopped = [0] * la 8 | lens = [0] * la 9 | bufs = [[] for i in range(la)] 10 | nexts = [None] * la 11 | n = 0 12 | while 1: 13 | anynew = 0 14 | for i in range(la): 15 | if stopped[i]: 16 | nextval = bufs[i][n % lens[i]] 17 | else: 18 | try: 19 | nextval = next(args[i]) 20 | except StopIteration: 21 | if lens[i] == 0: 22 | # raise ValueError, 'The iterator passed in arg %d did not return any item'%i 23 | return 24 | stopped[i] = 1 25 | nextval = bufs[i][n % lens[i]] 26 | else: 27 | anynew = 1 28 | bufs[i].append(nextval) 29 | lens[i] += 1 30 | nexts[i] = nextval 31 | if anynew: 32 | n += 1 33 | yield tuple(nexts) 34 | else: 35 | break 36 | 37 | wanted = reduce(lambda x, y: x*y, lens, 1) 38 | if n >= wanted: 39 | assert n == wanted 40 | return 41 | ixs = list(enumerate(lens)) 42 | ixs.sort(key=lambda x: x[1]) 43 | ixs = [ix for (ix, ln) in ixs] 44 | jxs = [0] * la 45 | seen = dict([(tuple([j % lens[i] for i in ixs]), 1) 46 | for j in range(n)]) 47 | 48 | while n < wanted: 49 | t = tuple([jxs[i] for i in ixs]) 50 | if t not in seen: 51 | yield tuple([bufs[i][jxs[i]] for i in range(la)]) 52 | n += 1 53 | 54 | for i in ixs: 55 | j = jxs[i] 56 | j = (j + 1) % lens[i] 57 | jxs[i] = j 58 | if j != 0: 59 | break 60 | 61 | 62 | def test_iterpermute(): 63 | import itertools 64 | repeat = itertools.repeat 65 | assert list(iterpermute()) == [()] 66 | assert list(iterpermute(repeat(1, 2))) == [(1,), (1,)] 67 | assert list(iterpermute(repeat(1, 1), repeat(2, 1))) == [(1, 2)] 68 | assert list(iterpermute(list(range(0, 2)), list(range(2, 3)))) == [ 69 | (0, 2), (1, 2)] 70 | assert list(iterpermute(list(range(0, 2)), list(range(2, 4)))) == [ 71 | (0, 2), (1, 3), (1, 2), (0, 3)] 72 | print(list(iterpermute(list(range(0, 2)), list(range(0, 3))))) 73 | print(list(iterpermute(list(range(0, 3)), list(range(0, 2))))) 74 | 75 | 76 | if __name__ == '__main__': 77 | test_iterpermute() 78 | -------------------------------------------------------------------------------- /src/heapy/hv_cli_user.c: -------------------------------------------------------------------------------- 1 | /* Implementation of user defined classifiers. */ 2 | 3 | PyDoc_STRVAR(hv_cli_user_defined_doc, 4 | "\n" 5 | ); 6 | 7 | typedef struct { 8 | /* Mimics a tuple */ 9 | PyObject_VAR_HEAD 10 | NyObjectClassifierObject *cond_cli; 11 | PyObject *cond_kind; 12 | PyObject *classify; 13 | PyObject *memoized_kind; 14 | NyNodeGraphObject *rg; 15 | NyNodeSetObject *norefer; 16 | PyObject *dict; 17 | 18 | 19 | } UserObject; 20 | 21 | static PyObject * 22 | hv_cli_user_memoized_kind(UserObject * self, PyObject *kind) 23 | { 24 | if (self->memoized_kind != Py_None && kind != Py_None) { 25 | kind = PyObject_CallFunctionObjArgs(self->memoized_kind, kind, 0); 26 | } else { 27 | Py_INCREF(kind); 28 | } 29 | return kind; 30 | } 31 | 32 | static PyObject * 33 | hv_cli_user_classify(UserObject * self, PyObject *obj) 34 | { 35 | PyObject *kind; 36 | kind = self->cond_cli->def->classify(self->cond_cli->self, obj); 37 | if (!kind) 38 | return 0; 39 | if (kind != self->cond_kind) { 40 | Py_DECREF(kind); 41 | kind = Py_None; 42 | Py_INCREF(kind); 43 | return kind; 44 | } else { 45 | Py_DECREF(kind); 46 | return PyObject_CallFunctionObjArgs(self->classify, obj, 0); 47 | } 48 | } 49 | 50 | static NyObjectClassifierDef hv_cli_user_def = { 51 | 0, 52 | sizeof(NyObjectClassifierDef), 53 | "cli_user_defined", 54 | "user defined classifier", 55 | (binaryfunc)hv_cli_user_classify, 56 | (binaryfunc)hv_cli_user_memoized_kind, 57 | }; 58 | 59 | 60 | 61 | static PyObject * 62 | hv_cli_user_defined(NyHeapViewObject *self, PyObject *args, PyObject *kwds) 63 | { 64 | static char *kwlist[] = {"cond_cli", "cond_kind", "classify", "memoized_kind", 0}; 65 | UserObject *s, tmp; 66 | PyObject *r; 67 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!OOO:user_defined", kwlist, 68 | &NyObjectClassifier_Type, &tmp.cond_cli, 69 | &tmp.cond_kind, 70 | &tmp.classify, 71 | &tmp.memoized_kind 72 | )) 73 | return 0; 74 | 75 | s = NYTUPLELIKE_NEW(UserObject); 76 | if (!s) 77 | return 0; 78 | 79 | s->cond_cli = tmp.cond_cli; 80 | Py_INCREF(s->cond_cli); 81 | s->cond_kind = tmp.cond_kind; 82 | Py_INCREF(s->cond_kind); 83 | s->classify = tmp.classify; 84 | Py_INCREF(s->classify); 85 | s->memoized_kind = tmp.memoized_kind; 86 | Py_INCREF(s->memoized_kind); 87 | r = NyObjectClassifier_New((PyObject *)s, &hv_cli_user_def); 88 | Py_DECREF(s); 89 | return r; 90 | } 91 | -------------------------------------------------------------------------------- /guppy/etc/tkcursors.py: -------------------------------------------------------------------------------- 1 | # A Tk window that shows what cursor shapes are available. 2 | # Moving the mouse over the cursor name shows the cursor in that shape. 3 | 4 | from tkinter import * 5 | 6 | 7 | def tkcursors(master=None): 8 | if master is None: 9 | root = Tk() 10 | else: 11 | root = master 12 | for i, cursor in enumerate(( 13 | 'X_cursor', 14 | 'arrow', 15 | 'based_arrow_down', 16 | 'based_arrow_up', 17 | 'boat', 18 | 'bogosity', 19 | 'bottom_left_corner', 20 | 'bottom_right_corner', 21 | 'bottom_side', 22 | 'bottom_tee', 23 | 'box_spiral', 24 | 'center_ptr', 25 | 'circle', 26 | 'clock', 27 | 'coffee_mug', 28 | 'cross', 29 | 'cross_reverse', 30 | 'crosshair', 31 | 'diamond_cross', 32 | 'dot', 33 | 'dotbox', 34 | 'double_arrow', 35 | 'draft_large', 36 | 'draft_small', 37 | 'draped_box', 38 | 'exchange', 39 | 'fleur', 40 | 41 | 'gobbler', 42 | 'gumby', 43 | 'hand1', 44 | 'hand2', 45 | 'heart', 46 | 'icon', 47 | 'iron_cross', 48 | 'left_ptr', 49 | 'left_side', 50 | 'left_tee', 51 | 'leftbutton', 52 | 'll_angle', 53 | 'lr_angle', 54 | 'man', 55 | 'middlebutton', 56 | 'mouse', 57 | 'pencil', 58 | 'pirate', 59 | 'plus', 60 | 'question_arrow', 61 | 'right_ptr', 62 | 'right_side', 63 | 'right_tee', 64 | 'rightbutton', 65 | 'rtl_logo', 66 | 'sailboat', 67 | 'sb_down_arrow', 68 | 69 | 'sb_h_double_arrow', 70 | 'sb_left_arrow', 71 | 'sb_right_arrow', 72 | 'sb_up_arrow', 73 | 'sb_v_double_arrow', 74 | 'shuttle', 75 | 'sizing', 76 | 'spider', 77 | 'spraycan', 78 | 'star', 79 | 'target', 80 | 'tcross', 81 | 'top_left_arrow', 82 | 'top_left_corner', 83 | 'top_right_corner', 84 | 'top_side', 85 | 'top_tee', 86 | 'trek', 87 | 'ul_angle', 88 | 'umbrella', 89 | 'ur_angle', 90 | 'watch', 91 | 'xterm' 92 | 93 | 94 | )): 95 | 96 | w = Label(root, text=cursor, cursor=cursor, 97 | width=20, anchor=W, relief=SUNKEN) 98 | col, row = divmod(i, 27) 99 | w.grid(row=row, column=col) 100 | if master is None: 101 | root.mainloop() 102 | 103 | 104 | if __name__ == '__main__': 105 | tkcursors() 106 | -------------------------------------------------------------------------------- /guppy/etc/xterm.py: -------------------------------------------------------------------------------- 1 | # Run an xterm on current process or a forked process 2 | 3 | # Adapted from pty.py in Python 1.5.2 distribution. 4 | # The pty.fork() couldnt be used because it didn't return 5 | # the pty name needed by xterm 6 | # I couldnt import pty.py to use master_open because it didn't find termios. 7 | 8 | import os 9 | import sys 10 | import fcntl 11 | 12 | # We couldnt find termios 13 | 14 | STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO = 0, 1, 2 15 | 16 | # Open pty master. Returns (master_fd, tty_name). SGI and Linux/BSD version. 17 | # Copied from pty.py from Python 1.5.2. /SN 18 | 19 | 20 | def master_open(): 21 | try: 22 | import sgi 23 | except ImportError: 24 | pass 25 | else: 26 | try: 27 | tty_name, master_fd = sgi._getpty(fcntl.O_RDWR, 0o666, 0) 28 | except IOError as msg: 29 | raise os.error(msg) 30 | return master_fd, tty_name 31 | for x in 'pqrstuvwxyzPQRST': 32 | for y in '0123456789abcdef': 33 | pty_name = '/dev/pty' + x + y 34 | try: 35 | fd = os.open(pty_name, fcntl.O_RDWR) 36 | except os.error: 37 | continue 38 | return (fd, '/dev/tty' + x + y) 39 | raise os.error('out of pty devices') 40 | 41 | # Open the pty slave. Acquire the controlling terminal. 42 | # Returns file descriptor. Linux version. (Should be universal? --Guido) 43 | # Copied from pty.py from Python 1.5.2. /SN 44 | 45 | 46 | def slave_open(tty_name): 47 | return os.open(tty_name, fcntl.O_RDWR) 48 | 49 | 50 | def xterm(prog=None, options=''): 51 | master_fd, tty_name = master_open() 52 | pid = os.fork() 53 | if pid: 54 | # Acquire controlling terminal. 55 | slave_fd = slave_open(tty_name) 56 | 57 | # Slave becomes stdin/stdout/stderr of child. 58 | os.dup2(slave_fd, STDIN_FILENO) 59 | os.dup2(slave_fd, STDOUT_FILENO) 60 | os.dup2(slave_fd, STDERR_FILENO) 61 | if (slave_fd > STDERR_FILENO): 62 | os.close(slave_fd) 63 | os.close(master_fd) 64 | sys.stdin.readline() # Throw away an init string from xterm 65 | if prog is not None: 66 | prog() 67 | else: 68 | os.setsid() 69 | cmd = 'xterm %s -S%s%d' % (options, tty_name[-2:], master_fd) 70 | os.system(cmd) 71 | #os.waitpid(pid, 0) 72 | return pid 73 | 74 | 75 | def forkxterm(prog=None, options=''): 76 | pid = os.fork() 77 | if pid: 78 | return pid 79 | else: 80 | os.setsid() 81 | pid = xterm(prog, options) 82 | if not pid: 83 | os._exit(0) 84 | 85 | 86 | def hello(): 87 | print('hello') 88 | while 1: 89 | pass 90 | -------------------------------------------------------------------------------- /src/sets/sets_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef SETS_INTERNAL_H 2 | #define SETS_INTERNAL_H 3 | 4 | #include "sets.h" 5 | 6 | /* BitSet */ 7 | 8 | extern PyTypeObject NyImmBitSet_Type; 9 | extern PyTypeObject NyImmBitSetIter_Type; 10 | extern PyTypeObject NyCplBitSet_Type; 11 | extern PyTypeObject NyMutBitSet_Type; 12 | extern PyTypeObject NyUnion_Type; 13 | 14 | #define NyImmBitSet_Check(op) PyObject_TypeCheck(op, &NyImmBitSet_Type) 15 | #define NyCplBitSet_Check(op) PyObject_TypeCheck(op, &NyCplBitSet_Type) 16 | #define NyMutBitSet_Check(op) PyObject_TypeCheck(op, &NyMutBitSet_Type) 17 | 18 | NyImmBitSetObject *NyImmBitSet_New(NyBit size); 19 | NyCplBitSetObject *NyCplBitSet_New(NyImmBitSetObject *v); 20 | NyMutBitSetObject *NyMutBitSet_New(void); 21 | 22 | typedef int (*NySetVisitor)(NyBit, void *) ; 23 | 24 | typedef int (*NyIterableVisitor)(PyObject *, void *); 25 | 26 | 27 | extern int NyAnyBitSet_iterate(PyObject *v, 28 | NySetVisitor visit, 29 | void *arg); 30 | 31 | 32 | extern Py_ssize_t NyAnyBitSet_length(PyObject *v); 33 | 34 | /* The predefined empty set */ 35 | 36 | extern NyImmBitSetObject _NyImmBitSet_EmptyStruct; 37 | #define NyImmBitSet_Empty (&_NyImmBitSet_EmptyStruct) 38 | 39 | /* The predefined set of all bits */ 40 | 41 | extern NyCplBitSetObject _NyImmBitSet_OmegaStruct; 42 | #define NyImmBitSet_Omega (&_NyImmBitSet_OmegaStruct) 43 | 44 | 45 | extern PyObject *NyMutBitSet_AsImmBitSet(NyMutBitSetObject *v); 46 | extern int NyMutBitSet_clrbit(NyMutBitSetObject *v, NyBit bit); 47 | extern int NyMutBitSet_setbit(NyMutBitSetObject *v, NyBit bit); 48 | extern int NyMutBitSet_hasbit(NyMutBitSetObject *v, NyBit bit); 49 | extern int NyImmBitSet_hasbit(NyImmBitSetObject *v, NyBit bit); 50 | 51 | extern int NyMutBitSet_clear(NyMutBitSetObject *v); 52 | extern NyBit NyMutBitSet_pop(NyMutBitSetObject *v, NyBit i); 53 | 54 | int cplbitset_traverse(NyHeapTraverse *ta); 55 | size_t mutbitset_indisize(NyMutBitSetObject *v); 56 | size_t anybitset_indisize(PyObject *obj); 57 | size_t generic_indisize(PyObject *v); 58 | 59 | /* NodeSet */ 60 | 61 | size_t nodeset_indisize(PyObject *v); 62 | int nodeset_traverse(NyHeapTraverse *ta); 63 | int nodeset_relate(NyHeapRelate *r); 64 | 65 | extern PyTypeObject NyNodeSet_Type; 66 | extern PyTypeObject NyMutNodeSet_Type; 67 | extern PyTypeObject NyImmNodeSet_Type; 68 | 69 | #define NyNodeSet_Check(op) PyObject_TypeCheck(op, &NyNodeSet_Type) 70 | #define NyMutNodeSet_Check(op) PyObject_TypeCheck(op, &NyMutNodeSet_Type) 71 | #define NyImmNodeSet_Check(op) PyObject_TypeCheck(op, &NyImmNodeSet_Type) 72 | 73 | 74 | extern int fsb_dx_addmethods 75 | (PyObject *m, PyMethodDef *methods, PyObject *passthrough); 76 | 77 | 78 | #endif /* SETS_INTERNAL_H */ 79 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import re 3 | import sys 4 | from setuptools import setup, Extension 5 | from distutils.command.install import INSTALL_SCHEMES 6 | 7 | for scheme in INSTALL_SCHEMES.values(): 8 | scheme['data'] = scheme['purelib'] 9 | 10 | 11 | setsc = Extension("guppy.sets.setsc", [ 12 | "src/sets/sets.c", 13 | "src/sets/bitset.c", 14 | "src/sets/nodeset.c" 15 | ]) 16 | 17 | heapyc = Extension("guppy.heapy.heapyc", [ 18 | 'src/heapy/heapyc.c', 19 | 'src/heapy/stdtypes.c' 20 | ]) 21 | 22 | 23 | def doit(): 24 | if sys.version_info.major < 3: 25 | print('''\ 26 | setup.py: Error: This guppy package only supports Python 3. 27 | You can find the original Python 2 version, Guppy-PE, here: 28 | http://guppy-pe.sourceforge.net/''', file=sys.stderr) 29 | sys.exit(1) 30 | if sys.implementation.name != 'cpython': 31 | print('''\ 32 | setup.py: Warning: This guppy package only supports CPython. 33 | Compilation failure expected, but continuting anyways...''', file=sys.stderr) 34 | 35 | with open(os.path.join(os.path.dirname(__file__), 'README.md')) as f: 36 | long_description = f.read() 37 | 38 | with open('guppy/_version.py', 'r') as versionfile: 39 | version = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]$', 40 | versionfile.read(), re.M) 41 | version = version.group(1) 42 | 43 | setup(name="guppy3", 44 | version=version, 45 | description="Guppy 3 -- Guppy-PE ported to Python 3", 46 | long_description=long_description, 47 | long_description_content_type='text/markdown', 48 | author="YiFei Zhu", 49 | author_email="zhuyifei1999@gmail.com", 50 | url="https://github.com/zhuyifei1999/guppy3/", 51 | license='MIT', 52 | packages=[ 53 | "guppy", 54 | "guppy.etc", 55 | "guppy.gsl", 56 | "guppy.heapy", 57 | "guppy.heapy.test", 58 | "guppy.sets", 59 | ], 60 | ext_modules=[setsc, heapyc], 61 | python_requires='>=3.10', 62 | classifiers=[ 63 | "Programming Language :: Python :: 3", 64 | "Programming Language :: Python :: Implementation :: CPython", 65 | "Programming Language :: C", 66 | "Operating System :: OS Independent", 67 | "Development Status :: 4 - Beta", 68 | "Topic :: Software Development :: Debuggers", 69 | "Environment :: Console", 70 | "Intended Audience :: Developers", 71 | ]) 72 | 73 | 74 | doit() 75 | -------------------------------------------------------------------------------- /specs/kindnames.gsl: -------------------------------------------------------------------------------- 1 | .kind:: ObjectClassifier 2 | .kind:: NodeSet 3 | 4 | .kind:: callable 5 | .kind:: int 6 | .kind:: integer 7 | ..d: When it is not specified if an attribute is an int or long, 8 | it is specified as integer. 9 | 10 | .kind:: iterable 11 | .kind:: iterator 12 | .kind:: list 13 | .kind:: string 14 | 15 | .kind:: notnegative 16 | ..d: This is non-negative integer, int or long. 17 | 18 | .kind:: Any 19 | .kind:: RelationStructure 20 | .kind:: boolean 21 | .kind:: frame 22 | .kind:: None 23 | 24 | .kind:: CommonSet 25 | .kind:: ImmNodeSet 26 | .kind:: MutNodeSet 27 | .kind:: HeapView 28 | .kind:: NodeGraph 29 | 30 | .kind:: guppy 31 | 32 | .kind:: guppy_Root 33 | 34 | .superkind:: Guppy Glue+ 35 | ..eg: _GLUECLAMP_ 36 | ...in context: 37 | class _GLUECLAMP_: 38 | # Example module description class. 39 | # It must be named _GLUECLAMP_ to be recognized by Guppy Glue. 40 | 41 | # Tuple of on-demand imports. 42 | 43 | _imports_ = ( 44 | '_root:os', # Import os as self.os 45 | '_parent:module', # Import a module from the parent of my module, as self.module 46 | '_parent.module.method' # Import a method, as self.method 47 | ) 48 | 49 | # Tuple of changable attributes. 50 | 51 | _chgable_ = ('config',) # Allow self.config to be written 52 | 53 | # A 'normal' attribute. 54 | 55 | config = 'A' 56 | 57 | # Methods beginning with _get_ will be called to automatically 58 | # create the attribute with the name after _get_. 59 | 60 | def _get_table(self): # Create the table attribute on demand 61 | return {} # It is automatically memoized as self.table 62 | 63 | .superkind:: int+ 64 | ..eg: 0 65 | 66 | .superkind:: None+ 67 | ..eg: None 68 | 69 | .superkind:: notnegative+ 70 | ..eg: 0 71 | 72 | .superkind:: positive+ 73 | ..eg: 1 74 | 75 | .superkind:: iterable+ 76 | ..eg: [1] 77 | 78 | .superkind:: Any+ 79 | ..eg: () 80 | 81 | .superkind:: dict+ 82 | ..eg: {} 83 | 84 | .superkind:: Exception+ 85 | ..eg: ValueError 86 | 87 | .superkind:: NodeGraph+ 88 | ..eg: NodeGraph() 89 | ...in context: 90 | from guppy.heapy.heapyc import NodeGraph 91 | 92 | .superkind:: NodeSet+ 93 | ..eg: immnodeset() 94 | ...in context: 95 | from guppy.sets import immnodeset 96 | 97 | .superkind:: string+ 98 | ..eg: "" 99 | 100 | .superkind:: ObjectClassifier+ 101 | ..eg: hv.cli_none() 102 | ...in context: 103 | from guppy.heapy.heapyc import HeapView 104 | hv = HeapView((), ()) 105 | 106 | .superkind:: boolean+ 107 | ..eg: True 108 | 109 | .superkind:: type+ 110 | ..eg: int 111 | ..eg: C 112 | ...in context: 113 | class C: 114 | pass 115 | 116 | .superkind:: type_with_hiding_tag+ 117 | ..eg: MutNodeSet 118 | ...in context: 119 | from guppy.sets import MutNodeSet 120 | -------------------------------------------------------------------------------- /docs/test_heapy.py: -------------------------------------------------------------------------------- 1 | # Tests generated by: guppy.gsl.Tester 2 | # Date: Mon Oct 20 20:07:59 2025 3 | class Tester: 4 | tests = {} 5 | def get_ex_1(self): 6 | class C: 7 | pass 8 | return C 9 | def get_ex_2(self): 10 | class C: 11 | pass 12 | return C 13 | def get_ex_3(self): 14 | import sys 15 | return id(sys) 16 | def get_ex_4(self): 17 | class C: 18 | pass 19 | return self.hp.Clodo(dictof=C) 20 | def get_ex_5(self): 21 | class C: 22 | pass 23 | return C 24 | def get_ex_6(self): 25 | import os 26 | return os.path.join(os.path.dirname(__file__),'profileexample.hpy') 27 | def get_ex_7(self): 28 | import os 29 | return open(os.path.join(os.path.dirname(__file__),'profileexample.hpy')) 30 | def get_ex_8(self): 31 | import os 32 | return os.path.join(os.path.dirname(__file__),'profileexample.hpy') 33 | def test_Use(self, arg): 34 | t0 = arg.Size(0) 35 | t1 = arg.Root 36 | t2 = arg.Rcs() 37 | t3 = arg.Rcs(self.get_ex_4()) 38 | t4 = arg.Rcs(self.hp.Clodo.sokind(int)(dictof=())) 39 | t5 = arg.Prod() 40 | t6 = arg.Prod("", 1) 41 | t7 = arg.Module(at=self.get_ex_3()) 42 | t8 = arg.Module(name='sys', at=self.get_ex_3()) 43 | t9 = arg.Prod("") 44 | t10 = arg.Prod(()) 45 | t11 = arg.Unity() 46 | t12 = arg.Clodo(int) 47 | t13 = arg.Clodo(self.get_ex_1()) 48 | t14 = arg.Clodo(dictof=()) 49 | t15 = arg.Clodo(dictof=self.get_ex_2()) 50 | t16 = arg.Id(id(None)) 51 | t17 = arg.Module() 52 | t18 = arg.Module(name='sys') 53 | t19 = arg.Nothing 54 | t20 = arg.Anything 55 | t21 = arg.heapu() 56 | t22 = arg.load(self.get_ex_6()) 57 | t23 = arg.load(self.get_ex_7()) 58 | t24 = arg.load(self.get_ex_6(), use_readline=True) 59 | t25 = arg.load(self.get_ex_7(), use_readline=True) 60 | t26 = arg.heap() 61 | t27 = arg.heapg() 62 | t28 = arg.idset([1]) 63 | t29 = arg.iso() 64 | t30 = arg.iso(()) 65 | t31 = arg.findex() 66 | t32 = t31(0) 67 | t33 = arg.findex(self.hp.Anything) 68 | t34 = t33(0) 69 | t35 = arg.Via() 70 | t36 = arg.Via('[0]') 71 | t37 = arg.Via('.a') 72 | t38 = arg.Type(int) 73 | t39 = arg.Type(self.get_ex_5()) 74 | t40 = arg.doc 75 | t41 = arg.pb() 76 | t42 = arg.pb(self.get_ex_8()) 77 | t43 = arg.setref() 78 | t44 = arg.setrelheap() 79 | t45 = arg.setrelheap(self.hp.Nothing) 80 | t46 = arg.setrelheapg() 81 | t47 = arg.setrelheapg(self.hp.Nothing) 82 | t48 = arg.setrelheapu() 83 | t49 = arg.setrelheapu(self.hp.Nothing) 84 | tests['.tgt.heapykinds.Use'] = test_Use 85 | -------------------------------------------------------------------------------- /specs/guppy.gsl: -------------------------------------------------------------------------------- 1 | .import:: guppy, guppy_Root 2 | ..from: kindnames 3 | 4 | .import:: Any+, boolean+, Guppy Glue+ 5 | ..from: kindnames 6 | 7 | .import:: Use 8 | ..from: heapykinds 9 | 10 | .and: guppy 11 | ..method:: hpy 12 | 13 | ...d: Create a new heapy object that may be used for accessing all of 14 | the heapy functionality. Methods and modules are imported by this 15 | object on demand when needed. 16 | ....c: Where the methods actually are implemented is an implementation detail. 17 | ....t: Two commonly used methods are heap and iso. 18 | 19 | ...d: An example: 20 | ....pre 21 | >>> from guppy import hpy 22 | >>> hpy().heap() # Show current reachable heap 23 | Partition of a set of 30976 objects. Total size = 3544220 bytes. 24 | Index Count % Size % Cumulative % Kind (class / dict of class) 25 | 0 8292 27 739022 21 739022 21 str 26 | \... 27 | 9 172 1 81712 2 3054020 86 dict (no owner) 28 | <89 more rows. Type e.g. '_.more' to view.> 29 | >>> 30 | ...d: To see more about how the heapy object may be used, follow the 31 | link on the return kind. 32 | 33 | ...returns: Use 34 | ...d: Normally no arguments need to be given. The arguments that may 35 | be given are for special cases. 36 | ...draw 37 | ....key arg: ht: Any+ 38 | .....d: The hiding tag to use. It may be useful to specify this 39 | in some cases when using multiple heapy instances, when you 40 | want to see the data in some of the other instances. 41 | .....default: will be set to the same unique object each 42 | time. In this way, different heapy instances will not see each 43 | other's data. 44 | ....c 45 | .....key arg: gt: boolean+ 46 | ......d: If true, the method will fetch objects from gc.get_objects 47 | before creating the heapy instance. This may have been useful in some 48 | situations but there is other functionality that may be superceding 49 | this option. 50 | .....key arg: rh: boolean+ 51 | ......d: If true, the heapy instance will be initialized to have 52 | a .relheap set that contains the initial view of the heap. Subsequent 53 | calls to .heap() will then only show the new objects allocated later 54 | that are not in .relheap. 55 | 56 | ..method:: Root 57 | ...d: Create a new guppy Root object. 58 | 59 | ....p: All functionality in the system may be accessed from this 60 | object. Modules are imported on demand when accessed. Other objects 61 | may be created or imported on demand using 62 | .....ref: .myfile.Guppy Glue+ 63 | .....t: directives. 64 | 65 | ....p: As this is a general access point, the heapy functionality may 66 | be accessed from here as well as via the hpy() method. How it is done 67 | is beyond the scope of this documentation, and is to be regarded an 68 | implementation detail, but you can of course look at the source code 69 | for the hpy method. 70 | 71 | ....p: There is currently no arguments to this constructor, I may think 72 | of adding some options in the future, but it has not yet been needed. 73 | 74 | ...returns: guppy_Root 75 | -------------------------------------------------------------------------------- /guppy/gsl/Gsml.py: -------------------------------------------------------------------------------- 1 | class GsmlHandler: 2 | # To be mixed in with something like HTMLParser.HTMLParser 3 | 4 | def handle_starttag(self, tag, attrs): 5 | self.stack.append(self.out) 6 | self.out = [] 7 | if attrs: 8 | at = [] 9 | for k, v in attrs: 10 | at.append(self.mod.node_of_taci(k, v)) 11 | self.out.append(self.mod.node_of_taci('with', '', at)) 12 | 13 | def handle_endtag(self, tag): 14 | node = self.mod.node_of_taci(tag, '', self.out) 15 | self.out = self.stack.pop() 16 | self.out.append(node) 17 | 18 | def handle_charref(self, name): 19 | if name[:1] == "x": 20 | char = int(name[1:], 16) 21 | name = '0'+name 22 | else: 23 | char = int(name) 24 | if 0 <= char < 128: 25 | char = chr(char) 26 | self.handle_data(char) 27 | else: 28 | self.out.append(self.mod.node_of_taci('char', name)) 29 | 30 | def handle_entityref(self, name): 31 | if name not in self.mod.entitydefs: 32 | self.unknown_entityref(name) 33 | self.out.append(self.mod.node_of_taci('char', name)) 34 | 35 | def unknown_entityref(self, name): 36 | raise SyntaxError('Unknown entity ref: %r' % name) 37 | 38 | def handle_data(self, data): 39 | # data = data.strip() 40 | if data.strip(): 41 | self.out.extend(self.mod.nodes_of_text(data)) 42 | 43 | def handle_comment(self, data): 44 | self.out.append(self.mod.node_of_taci('comment', data, (), 0)) 45 | 46 | def handle_decl(self, decl): 47 | self.out.append(self.mod.node_of_taci('html_declaration', decl)) 48 | 49 | def handle_pi(self, data): 50 | self.out.append(self.mod.node_of_taci('processing_instruction', data)) 51 | 52 | 53 | class _GLUECLAMP_: 54 | _imports_ = ( 55 | '_root.html.parser:HTMLParser', 56 | '_parent.SpecNodes:node_of_taci', 57 | '_parent.SpecNodes:nodes_of_text', 58 | '_root.htmlentitydefs:entitydefs', 59 | ) 60 | 61 | encoding = "iso-8859-1" 62 | 63 | def node_of_gsml(self, text): 64 | class Parser(GsmlHandler, self.HTMLParser): 65 | def __init__(self, mod): 66 | mod.HTMLParser.__init__(self) 67 | self.mod = mod 68 | self.out = [] 69 | self.stack = [] 70 | 71 | p = Parser(self) 72 | p.feed(text) 73 | p.close() 74 | if p.stack: 75 | raise SyntaxError('Missing end tag') 76 | node = self.node_of_taci('block', '', p.out, 0) 77 | return node 78 | 79 | def _test_main_(self): 80 | x = """ 81 | 82 | This is an emphasized word. 83 | See also Guppy. 84 | Defined as . 85 | 86 | Handle char ref: d. 87 | Handle char ref: <. 88 | 89 | 90 | 91 | 92 | 93 | 94 | """ 95 | 96 | node = self.node_of_gsml(x) 97 | # FIXME: Assert equals to something? 98 | print(node) 99 | -------------------------------------------------------------------------------- /guppy/gsl/FileIO.py: -------------------------------------------------------------------------------- 1 | class TestPath: 2 | _path_using_io = ( 3 | 'abspath', 'curdir', 'exists', 'expanduser', 'expandvars', 4 | 'getatime', 'getctime', 'getmtime', 'getsize', 5 | 'isfile', 'islink', 'ismount', 'realpath', 6 | 'samefile', 'sameopenfile', 'samestat', 7 | 'walk', 8 | 9 | ) 10 | 11 | def __init__(self, os): 12 | for name in dir(os.path): 13 | if (not name.startswith('_') and 14 | name not in self._path_using_io): 15 | setattr(self, name, getattr(os.path, name)) 16 | 17 | 18 | class TestIO: 19 | def __init__(self, mod): 20 | os = mod._root.os 21 | for name in mod.os_common: 22 | setattr(self, name, getattr(os, name)) 23 | self.path = TestPath(os) 24 | self.files = {} 25 | self.tempno = 0 26 | 27 | def access(self, name, mode): 28 | if name in self.files: 29 | return True 30 | return False 31 | 32 | def listdir(self, name): 33 | li = [] 34 | name = self.path.join(name, '') 35 | for k in self.files: 36 | if k.startswith(name): 37 | rest = k[len(name):] 38 | if rest: 39 | li.append(rest) 40 | return li 41 | 42 | def mkdtemp(self): 43 | self.tempno += 1 44 | return '/tmp/xyz%d' % self.tempno 45 | 46 | def read_file(self, name): 47 | return self.files[name] 48 | 49 | def remove(self, name): 50 | try: 51 | del self.files[name] 52 | except KeyError: 53 | raise IOError('No such file: %r' % name) 54 | 55 | def rename(self, src, tgt): 56 | try: 57 | data = self.files[src] 58 | except KeyError: 59 | raise IOError('No such file: %r' % src) 60 | del self.files[src] 61 | self.files[tgt] = data 62 | 63 | def rmdir(self, name): 64 | pass 65 | 66 | def write_file(self, name, text): 67 | self.files[name] = text 68 | 69 | 70 | class RealIO: 71 | def __init__(self, mod): 72 | os = mod._root.os 73 | for name in mod.os_common: 74 | setattr(self, name, getattr(os, name)) 75 | self.path = os.path 76 | self.listdir = os.listdir 77 | self.makedirs = os.makedirs 78 | self.mkdtemp = mod._root.tempfile.mkdtemp 79 | self.rmdir = os.rmdir 80 | self.access = os.access 81 | self.chdir = os.chdir 82 | self.remove = os.remove 83 | self.rename = os.rename 84 | 85 | def read_file(self, name): 86 | with open(name) as f: 87 | return f.read() 88 | 89 | def write_file(self, name, data): 90 | with open(name, 'w') as f: 91 | f.write(data) 92 | 93 | 94 | class _GLUECLAMP_: 95 | 96 | _setable_ = 'IO', 97 | 98 | os_common = ('R_OK', 'W_OK', 'X_OK') 99 | 100 | def _get_IO(self): 101 | return RealIO(self) 102 | 103 | def set_IO(self, IO): 104 | self.IO = IO 105 | 106 | def set_test_mode(self): 107 | self.set_IO(TestIO(self)) 108 | -------------------------------------------------------------------------------- /specs/genguppydoc.py: -------------------------------------------------------------------------------- 1 | import guppy 2 | import os 3 | from os.path import join 4 | 5 | 6 | class GenGuppyDoc: 7 | extemplate = """\ 8 | .document: gslexample 9 | ..output: html 10 | ..h1: GSL Document and Test Example 11 | ..ul 12 | ...li 13 | ....a: Source Code 14 | .....href= #source 15 | ...li 16 | ....a: Generated Test Class 17 | .....href= #test 18 | ...li 19 | ....a: Generated Document 20 | .....href= docexample.html 21 | ..a 22 | ...name=source 23 | ...h2: Source Code 24 | ..pre 25 | %s 26 | ..c: end pre 27 | ..a 28 | ...name=test 29 | ...h2: Generated Test Class 30 | ..pre 31 | %s 32 | ..c: end pre 33 | """ 34 | 35 | def __init__(self, input_dir=None, output_dir=None): 36 | if input_dir is None: 37 | # Default to the script's directory 38 | input_dir = os.path.dirname(os.path.realpath(__file__)) 39 | if output_dir is None: 40 | output_dir = join(input_dir, '..', 'docs') 41 | self.input_dir = input_dir 42 | self.output_dir = output_dir 43 | self.gsl = guppy.Root().guppy.gsl 44 | 45 | def gen(self, gslfile, **kwds): 46 | self.gsl.Main.main(gslfile, input_dir=self.input_dir, 47 | output_dir=self.output_dir, **kwds) 48 | 49 | def genext(self): 50 | self.gen('genext.gsl') 51 | 52 | def genguppy(self): 53 | self.gen('genguppy.gsl') 54 | 55 | def gengsl(self): 56 | self.gen('index.gsl') 57 | self.gen('heapy_tutorial.gsl') 58 | 59 | self.gen('gsl.gsl') 60 | self.gen('docexample.gsl') 61 | gslexample = self.extemplate % ( 62 | ('\n'+open(join(self.input_dir, 'docexample.gsl')).read() 63 | ).replace('\n.', '\n\\.'), 64 | open(join(self.output_dir, 'docexample.py')).read()) 65 | 66 | self.gen('gslexample.gsl', input_string=gslexample) 67 | 68 | def genpb(self): 69 | output_file = join(self.input_dir, '..', 'guppy', 'heapy', 'pbhelp.py') 70 | with open(output_file, 'w') as f: 71 | f.write('# AUTOMATICALLY GENERATED BY GENGUPPY\n\n') 72 | 73 | for item, outhtml in [ 74 | ('about', None), ('help', 'ProfileBrowser.html')]: 75 | input_file = join(self.input_dir, item + '_Prof.gsl') 76 | node = self.gsl.SpecNodes.node_of_file(input_file) 77 | 78 | t = self.gsl.Text.RecordingInter() 79 | self.gsl.Text.node2inter(node, t) 80 | t.prepare_for_pickle() 81 | 82 | import pickle 83 | f.write('{} = {}\n'.format(item, repr(pickle.dumps(t)))) 84 | 85 | if outhtml: 86 | wrapped = self.gsl.SpecNodes.node_of_tatci( 87 | 'document', None, None, (node,)) 88 | 89 | outhtml = join(self.input_dir, '..', 'docs', outhtml) 90 | self.gsl.Html.node2file(wrapped, outhtml) 91 | 92 | 93 | def main(): 94 | g = GenGuppyDoc() 95 | g.genext() 96 | g.genguppy() 97 | g.gengsl() 98 | g.genpb() 99 | 100 | 101 | if __name__ == '__main__': 102 | main() 103 | -------------------------------------------------------------------------------- /specs/heapy_tutorial.gsl: -------------------------------------------------------------------------------- 1 | .macro:: header 2 | ..header 3 | ...link 4 | ....rel=stylesheet 5 | ....href=css/guppy.css 6 | 7 | .document: heapy_tutorial 8 | ..output: html 9 | ..document_title: Getting started with Heapy 10 | ..use: header 11 | ..h2: Getting started with Heapy 12 | 13 | ..h3: Usage example 14 | 15 | ..p: The following example shows 16 | 17 | ..ol 18 | ...li: How to create the session context: hp=hpy() 19 | ...li: How to use the interactive help: hp.doc, hp.doc.doc 20 | ...li: How to show the reachable objects in the heap: hp.heap() 21 | ...li: How to create and show a set of objects: hp.iso(1,[],{}) 22 | ...li: How to show the shortest paths from the root to x: hp.iso(x).sp 23 | ..pre 24 | >>> from guppy import hpy; hp=hpy() 25 | >>> hp 26 | Top level interface to Heapy. 27 | Use eg: hp.doc for more info on hp. 28 | >>> hp.doc 29 | Top level interface to Heapy. Available attributes: 30 | Anything Rcs doc load 31 | Clodo Root findex monitor 32 | Id Size heap pb 33 | Idset Type heapu setref 34 | Module Unity idset test 35 | Nothing Via iso 36 | Use eg: hp.doc. for info on . 37 | >>> hp.doc.doc 38 | Overview documentation for top level Heapy object. 39 | Provides a listing of the available attributes. 40 | Accessing the attribute name on the doc objects gives further info, eg: 41 | 42 | >>> hp.doc.heap 43 | 44 | gives doc for the heap method when hp is the top level Heapy object. 45 | 46 | References may be embedded in the documentations. To access a 47 | reference, opening up a web browser with the doc for it one can do eg: 48 | 49 | >>> hp.doc.heap[1] 50 | 51 | The reference number 0 is special. If it is provided, it is the 52 | reference to the html doc for the described object itself. So to see 53 | in the web browser the doc for the heap method one can do: 54 | 55 | >>> hp.doc.heap[0] 56 | 57 | References 58 | [0] heapy_Use.html#heapykinds.Use.doc 59 | >>> hp.heap() 60 | Partition of a set of 36671 objects. Total size = 4285591 bytes. 61 | Index Count % Size % Cumulative % Kind (class / dict of class) 62 | 0 10234 28 902960 21 902960 21 str 63 | 1 8964 24 727944 17 1630904 38 tuple 64 | 2 2367 6 342184 8 1973088 46 types.CodeType 65 | 3 4723 13 333951 8 2307039 54 bytes 66 | 4 435 1 333064 8 2640103 62 type 67 | 5 2179 6 313776 7 2953879 69 function 68 | 6 435 1 245640 6 3199519 75 dict of type 69 | 7 502 1 193376 5 3392895 79 dict (no owner) 70 | 8 91 0 161352 4 3554247 83 dict of module 71 | 9 1061 3 93368 2 3647615 85 types.WrapperDescriptorType 72 | <133 more rows. Type e.g. '_.more' to view.> 73 | >>> hp.iso(1,[],{}) 74 | Partition of a set of 3 objects. Total size = 348 bytes. 75 | Index Count % Size % Cumulative % Kind (class / dict of class) 76 | 0 1 33 248 71 248 71 dict (no owner) 77 | 1 1 33 72 21 320 92 list 78 | 2 1 33 28 8 348 100 int 79 | >>> x=[] 80 | >>> hp.iso(x).sp 81 | 0: hp.Root.i0_modules['__main__'].__dict__['x'] 82 | >>> 83 | ..c: end pre 84 | -------------------------------------------------------------------------------- /guppy/etc/textView.py: -------------------------------------------------------------------------------- 1 | # Copied from idlelib/textView 2 | # - I copied it rather than imported since I didn't want 3 | # to have a dependency on idlelib, 4 | # and I can change what I may want. 5 | # For example, I removed the transient and wait window things 6 | # so a help window behaves more like a 'normal' window 7 | """Simple text browser 8 | 9 | """ 10 | 11 | from tkinter import * 12 | import tkinter.messagebox 13 | 14 | 15 | class TextViewer(Toplevel): 16 | """ 17 | simple text viewer dialog for idle 18 | """ 19 | 20 | def __init__(self, parent, title, fileName=None, data=None): 21 | """If data exists, load it into viewer, otherwise try to load file. 22 | 23 | fileName - string, should be an absoulute filename 24 | """ 25 | Toplevel.__init__(self, parent) 26 | self.configure(borderwidth=5) 27 | self.geometry("=%dx%d+%d+%d" % (625, 500, 28 | parent.winfo_rootx() + 10, 29 | parent.winfo_rooty() + 10)) 30 | # elguavas - config placeholders til config stuff completed 31 | self.bg = '#ffffff' 32 | self.fg = '#000000' 33 | 34 | self.CreateWidgets() 35 | self.title(title) 36 | # self.transient(parent) 37 | # self.grab_set() 38 | self.protocol("WM_DELETE_WINDOW", self.Ok) 39 | self.parent = parent 40 | self.textView.focus_set() 41 | # key bindings for this dialog 42 | self.bind('', self.Ok) # dismiss dialog 43 | self.bind('', self.Ok) # dismiss dialog 44 | if data is not None: 45 | self.textView.insert(0.0, data) 46 | else: 47 | self.LoadTextFile(fileName) 48 | self.textView.config(state=DISABLED) 49 | # self.wait_window() 50 | 51 | def LoadTextFile(self, fileName): 52 | textFile = None 53 | try: 54 | textFile = open(fileName, 'r') 55 | except IOError: 56 | tkinter.messagebox.showerror(title='File Load Error', 57 | message='Unable to load file '+repr(fileName)+' .') 58 | else: 59 | self.textView.insert(0.0, textFile.read()) 60 | 61 | def CreateWidgets(self): 62 | frameText = Frame(self, relief=SUNKEN, height=700) 63 | frameButtons = Frame(self) 64 | self.buttonOk = Button(frameButtons, text='Close', 65 | command=self.Ok, takefocus=FALSE) 66 | self.scrollbarView = Scrollbar(frameText, orient=VERTICAL, 67 | takefocus=FALSE, highlightthickness=0) 68 | self.textView = Text(frameText, wrap=WORD, highlightthickness=0, 69 | fg=self.fg, bg=self.bg) 70 | self.scrollbarView.config(command=self.textView.yview) 71 | self.textView.config(yscrollcommand=self.scrollbarView.set) 72 | self.buttonOk.pack() 73 | self.scrollbarView.pack(side=RIGHT, fill=Y) 74 | self.textView.pack(side=LEFT, expand=TRUE, fill=BOTH) 75 | frameButtons.pack(side=BOTTOM, fill=X) 76 | frameText.pack(side=TOP, expand=TRUE, fill=BOTH) 77 | 78 | def Ok(self, event=None): 79 | self.destroy() 80 | 81 | 82 | if __name__ == '__main__': 83 | # test the dialog 84 | root = Tk() 85 | Button(root, text='View', 86 | command=lambda: TextViewer(root, 'Text', './textView.py')).pack() 87 | root.mainloop() 88 | -------------------------------------------------------------------------------- /src/heapy/heapdef.h: -------------------------------------------------------------------------------- 1 | #ifndef Ny_HEAPDEF_H 2 | #define Ny_HEAPDEF_H 3 | 4 | /* NyHeapTraverse - argument to traverse 5 | Defined to avoid complicated function defs 6 | */ 7 | 8 | typedef struct { 9 | int flags; 10 | PyObject *hv; /* A HeapView object providing context to the traversal 11 | function, if necessary. It is defined as a PyObject 12 | rather than HeapView to avoid include file dependency. */ 13 | PyObject *obj; /* The object that is to be traversed */ 14 | void *arg; /* the argument to pass when visiting referred objects. */ 15 | visitproc visit; /* The visit procedure to call */ 16 | PyObject *_hiding_tag_; /* The hiding tag in use by current context. */ 17 | } NyHeapTraverse; 18 | 19 | /* NyHeapRelate - argument to relate 20 | Defined to avoid complicated function defs 21 | */ 22 | 23 | typedef struct NyHeapRelate { 24 | int flags; /* As yet unused */ 25 | PyObject *hv; /* Heap view object */ 26 | PyObject *src; /* Source of relation, and which is dispatched on */ 27 | PyObject *tgt; /* Target of relation */ 28 | 29 | /* visit() should be called once for each unique pointer 30 | from src to tgt. 31 | The relation type is indicated by the relatype argument 32 | and defined in the NYHR_ definitions below. 33 | The relator argument is an object describing the relation 34 | and should be newly allocated or INCREFED. 35 | The arg argument should be the arg passed in NyHeapRelate 36 | below. 37 | 38 | Return value: non-zero, means the relate function should 39 | not provide any more relations but should return. A zero 40 | return value means visit may be called again. 41 | */ 42 | 43 | int (*visit)(unsigned int relatype, PyObject *relator, struct NyHeapRelate *arg); 44 | } NyHeapRelate; 45 | 46 | /* Values for 'relatype' argument to be passed to visit callback in NyHeapRelate */ 47 | 48 | 49 | #define NYHR_ATTRIBUTE 1 /* src.relator is tgt */ 50 | #define NYHR_INDEXVAL 2 /* src[relator] is tgt */ 51 | #define NYHR_INDEXKEY 3 /* src has key tgt */ 52 | #define NYHR_INTERATTR 4 /* src->relator == tgt in C only */ 53 | #define NYHR_HASATTR 5 /* src has attribute tgt (stored as string) */ 54 | #define NYHR_LOCAL_VAR 6 /* src (a frame) has local variable named with value tgt */ 55 | #define NYHR_CELL 7 /* src has cell variable named containing value tgt */ 56 | #define NYHR_STACK 8 /* src has a stack entry numbered with value tgt */ 57 | #define NYHR_INSET 9 /* src is a set and includes tgt, with current iterated index */ 58 | #define NYHR_RELSRC 10 /* relator % src is tgt ; tgt is relator % src*/ 59 | #define NYHR_LIMIT 11 /* All others are < NYHR_LIMIT */ 60 | 61 | /* NyHeapDef - structure to define by external type providers to define themselves wrt heapy 62 | */ 63 | 64 | /* Definitions of its function types, useful for casting. */ 65 | 66 | typedef size_t (*NyHeapDef_SizeGetter) (PyObject *obj); 67 | typedef int (*NyHeapDef_Traverser) (NyHeapTraverse *arg); 68 | typedef int (*NyHeapDef_RelationGetter) (NyHeapRelate *r); 69 | 70 | typedef struct { 71 | int flags; /* As yet, only 0 */ 72 | PyTypeObject *type; /* The type it regards */ 73 | NyHeapDef_SizeGetter size; 74 | NyHeapDef_Traverser traverse; 75 | NyHeapDef_RelationGetter relate; 76 | void *resv3, *resv4, *resv5; /* Reserved for future bin. comp. */ 77 | } NyHeapDef; 78 | 79 | #endif /* Ny_HEAPDEF_H */ 80 | -------------------------------------------------------------------------------- /guppy/heapy/test/test_menuleak.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | import sys 3 | import gc 4 | 5 | 6 | class FixedMenu(Menu): 7 | # A fix for the .delete() method in Menu. 8 | # To delete commands defined in the menu items deleted. 9 | # Also changed the comment: INDEX2 is actually INCLUDED. 10 | def delete(self, index1, index2=None): 11 | """Delete menu items between INDEX1 and INDEX2 (included).""" 12 | print(self._tclCommands) 13 | if index2 is None: 14 | index2 = index1 15 | # First find out what entries have defined commands. 16 | cmds = [] 17 | for i in range(self.index(index1), self.index(index2)+1): 18 | c = str(self.entrycget(i, 'command')) 19 | if c in self._tclCommands: 20 | # I don't want to delete the command already, since it 21 | # seems mystical to do that while the entry is not yet deleted. 22 | cmds.append(c) 23 | # Delete the menu entries. 24 | self.tk.call(self._w, 'delete', index1, index2) 25 | # Now that the menu entries have been deleted, we can delete their commands. 26 | for c in cmds: 27 | self.deletecommand(c) 28 | 29 | 30 | def test1(M): 31 | # Test with a single command 32 | gc.collect() 33 | root = Tk() 34 | button = Menubutton(root, text='Window') 35 | menu = M(button) 36 | button['menu'] = menu 37 | 38 | def command(): 39 | print('command button pressed') 40 | rc = sys.getrefcount(command) 41 | menu.add_command(command=command) # or add_radiobutton etc 42 | idx = menu.index(END) 43 | menu.delete(idx) 44 | gc.collect() 45 | rc1 = sys.getrefcount(command) 46 | print('leak test with class', M, end=' ') 47 | if rc1 != rc: 48 | print('failed: command is now hold by', rc1, 'references') 49 | else: 50 | print('succeeded: command is now hold by', rc1, 'references') 51 | 52 | root.destroy() 53 | 54 | 55 | def test2(M): 56 | # Test with 3 commands, especially to see that deleting a range works. 57 | 58 | gc.collect() 59 | root = Tk() 60 | button = Menubutton(root, text='Window') 61 | menu = M(button) 62 | button['menu'] = menu 63 | 64 | def command0(): 65 | print('command 0 button pressed') 66 | 'deleting 0 and 1' 67 | menu.delete(idx0, idx1) 68 | 69 | def command1(): 70 | print('command 1 button pressed') 71 | 72 | def command2(): 73 | print('command 2 button pressed') 74 | print('deleting at END') 75 | menu.delete(END) 76 | root.quit() 77 | rc = [sys.getrefcount(x) for x in (command0, command1, command0)] 78 | button.pack() 79 | # or add_radiobutton etc 80 | menu.add_command(command=command0, label='press first') 81 | idx0 = menu.index(END) 82 | menu.add_radiobutton(command=command1, label='command1') 83 | # to see that delete works even when no command supplied 84 | menu.add_command(label='no Command') 85 | idx1 = menu.index(END) 86 | menu.add_command(command=command2, label='press last') 87 | idx2 = menu.index(END) 88 | root.mainloop() 89 | 90 | gc.collect() 91 | rc1 = [sys.getrefcount(x) for x in (command0, command1, command0)] 92 | print('leak test with class', M, end=' ') 93 | if rc1 != rc: 94 | print('failed: command is now hold by', 95 | rc1, 'references, should be', rc) 96 | else: 97 | print('succeeded: command is now hold by', rc1, 'references') 98 | 99 | root.destroy() 100 | 101 | 102 | for M in (Menu, FixedMenu,): 103 | test1(M) 104 | test2(M) 105 | -------------------------------------------------------------------------------- /docs/heapy_tutorial.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | Getting started with Heapy 8 | 9 | 10 |
11 |

12 | Getting started with Heapy 13 |

14 |

15 | Usage example 16 |

17 |

18 | The following example shows 19 |

20 |
    21 |
  1. How to create the session context: hp=hpy()
  2. 22 |
  3. How to use the interactive help: hp.doc, hp.doc.doc
  4. 23 |
  5. How to show the reachable objects in the heap: hp.heap()
  6. 24 |
  7. How to create and show a set of objects: hp.iso(1,[],{})
  8. 25 |
  9. How to show the shortest paths from the root to x: hp.iso(x).sp
26 |
27 | >>> from guppy import hpy; hp=hpy()
28 | >>> hp
29 | Top level interface to Heapy.
30 | Use eg: hp.doc for more info on hp.
31 | >>> hp.doc
32 | Top level interface to Heapy. Available attributes:
33 | Anything            Rcs                 doc                 load
34 | Clodo               Root                findex              monitor
35 | Id                  Size                heap                pb
36 | Idset               Type                heapu               setref
37 | Module              Unity               idset               test
38 | Nothing             Via                 iso
39 | Use eg: hp.doc.<attribute> for info on <attribute>.
40 | >>> hp.doc.doc
41 | Overview documentation for top level Heapy object.
42 | Provides a listing of the available attributes.
43 | Accessing the attribute name on the doc objects gives further info, eg:
44 | 
45 |     >>> hp.doc.heap
46 | 
47 | gives doc for the heap method when hp is the top level Heapy object.
48 | 
49 | References may be embedded in the documentations. To access a
50 | reference, opening up a web browser with the doc for it one can do eg:
51 | 
52 |     >>> hp.doc.heap[1]
53 | 
54 | The reference number 0 is special. If it is provided, it is the
55 | reference to the html doc for the described object itself. So to see
56 | in the web browser the doc for the heap method one can do:
57 | 
58 |     >>> hp.doc.heap[0]
59 | 
60 | References
61 |     [0] heapy_Use.html#heapykinds.Use.doc
62 | >>> hp.heap()
63 | Partition of a set of 36671 objects. Total size = 4285591 bytes.
64 |  Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
65 |      0  10234  28   902960  21    902960  21 str
66 |      1   8964  24   727944  17   1630904  38 tuple
67 |      2   2367   6   342184   8   1973088  46 types.CodeType
68 |      3   4723  13   333951   8   2307039  54 bytes
69 |      4    435   1   333064   8   2640103  62 type
70 |      5   2179   6   313776   7   2953879  69 function
71 |      6    435   1   245640   6   3199519  75 dict of type
72 |      7    502   1   193376   5   3392895  79 dict (no owner)
73 |      8     91   0   161352   4   3554247  83 dict of module
74 |      9   1061   3    93368   2   3647615  85 types.WrapperDescriptorType
75 | <133 more rows. Type e.g. '_.more' to view.>
76 | >>> hp.iso(1,[],{})
77 | Partition of a set of 3 objects. Total size = 348 bytes.
78 |  Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
79 |      0      1  33      248  71       248  71 dict (no owner)
80 |      1      1  33       72  21       320  92 list
81 |      2      1  33       28   8       348 100 int
82 | >>> x=[]
83 | >>> hp.iso(x).sp
84 |  0: hp.Root.i0_modules['__main__'].__dict__['x']
85 | >>> 
86 |
Generated by GSL-HTML 3.1.5 on Mon Oct 20 20:07:59 2025
-------------------------------------------------------------------------------- /src/sets/sets.c: -------------------------------------------------------------------------------- 1 | /* Module guppy.sets.setsc */ 2 | 3 | char sets_doc[] = 4 | "This module implements two specialized kinds of sets, 'bitsets' and\n" 5 | "'nodesets'. Bitsets are sets of 'bits' -- here meaning integers in a\n" 6 | "particular range -- and designed to be efficient with dense as well as\n" 7 | "sparse distributions. Nodesets are sets of 'nodes', i.e. objects with\n" 8 | "equality based on their address; this makes inclusion test work with\n" 9 | "any combination of objects independently from how equality or hashing\n" 10 | "has been defined for the objects involved.\n" 11 | "\n" 12 | "Summary of module content.\n" 13 | "\n" 14 | "Classes\n" 15 | " BitSet Abstract bitset base class.\n" 16 | " CplBitSet Complemented immutable bitset.\n" 17 | " ImmBitSet Immutable bitset, non-complemented.\n" 18 | " MutBitSet Mutable bitset, complemented or not.\n" 19 | " NodeSet Abstract nodeset base class.\n" 20 | " ImmNodeSet Immutable nodeset.\n" 21 | " MutNodeSet Mutable nodeset.\n" 22 | " \n" 23 | "Functions\n" 24 | " immbit Immutable bitset singleton constructor.\n" 25 | " immbitrange Immutable bitset range constructor.\n" 26 | " immbitset Immutable bitset constructor.\n" 27 | "\n" 28 | "Data\n" 29 | " NyBitSet_Exports,\n" 30 | " NyNodeSet_Exports C-level exported function tables.\n"; 31 | 32 | #define PY_SSIZE_T_CLEAN 33 | #include 34 | 35 | #include "../heapy/heapdef.h" 36 | #include "../heapy/heapy.h" 37 | #include "sets_internal.h" 38 | 39 | #define INITFUNC PyInit_setsc 40 | #define MODNAME "setsc" 41 | 42 | extern int fsb_dx_nybitset_init(PyObject *m); 43 | extern int fsb_dx_nynodeset_init(PyObject *m); 44 | 45 | static PyMethodDef module_methods[] = { 46 | {NULL, NULL} 47 | }; 48 | 49 | int fsb_dx_addmethods(PyObject *m, PyMethodDef *methods, PyObject *passthrough) { 50 | PyObject *d, *v; 51 | PyMethodDef *ml; 52 | d = PyModule_GetDict(m); 53 | for (ml = methods; ml->ml_name != NULL; ml++) { 54 | v = PyCFunction_New(ml, passthrough); 55 | if (v == NULL) 56 | return -1; 57 | if (PyDict_SetItemString(d, ml->ml_name, v) != 0) { 58 | Py_DECREF(v); 59 | return -1; 60 | } 61 | Py_DECREF(v); 62 | } 63 | return 0; 64 | } 65 | 66 | static NyHeapDef nysets_heapdefs[] = { 67 | {0, 0, (NyHeapDef_SizeGetter) mutbitset_indisize}, 68 | {0, 0, 0, cplbitset_traverse}, 69 | {0, 0, nodeset_indisize, nodeset_traverse, nodeset_relate}, 70 | {0} 71 | }; 72 | 73 | static struct PyModuleDef moduledef = { 74 | PyModuleDef_HEAD_INIT, 75 | MODNAME, 76 | PyDoc_STR(sets_doc), 77 | -1, 78 | module_methods 79 | }; 80 | 81 | PyMODINIT_FUNC 82 | INITFUNC (void) 83 | { 84 | PyObject *m; 85 | PyObject *d; 86 | 87 | nysets_heapdefs[0].type = &NyMutBitSet_Type; 88 | nysets_heapdefs[1].type = &NyCplBitSet_Type; 89 | nysets_heapdefs[2].type = &NyNodeSet_Type; 90 | 91 | m = PyModule_Create(&moduledef); 92 | if (!m) 93 | return NULL; 94 | d = PyModule_GetDict(m); 95 | if (fsb_dx_nybitset_init(m) == -1) 96 | goto Error; 97 | if (fsb_dx_nynodeset_init(m) == -1) 98 | goto Error; 99 | if (PyDict_SetItemString(d, 100 | "_NyHeapDefs_", 101 | PyCapsule_New( 102 | &nysets_heapdefs, 103 | "guppy.sets.setsc._NyHeapDefs_", 104 | 0) 105 | ) == -1) 106 | goto Error; 107 | return m; 108 | Error: 109 | if (PyErr_Occurred() == NULL) 110 | PyErr_SetString(PyExc_ImportError, "module initialization failed"); 111 | Py_DECREF(m); 112 | return NULL; 113 | } 114 | -------------------------------------------------------------------------------- /docs/guppy.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

.tgt.kindnames.guppy

11 |
12 |
13 |

Name

14 |
15 |

guppy

16 |
17 |

Synopsis

18 |
19 |
20 |
Methods
21 |
hpy( [ht = Any+]) -> Use
22 |
Root() -> guppy_Root
23 |
24 |

Methods

25 |
hpy( [ht = Any+]) -> Use 26 |
27 |
28 | Create a new heapy object that may be used for accessing all of 29 | the heapy functionality. Methods and modules are imported by this 30 | object on demand when needed. 31 | Two commonly used methods are heap and iso. 32 |
33 |
An example: 34 |
35 | >>> from guppy import hpy
36 | >>> hpy().heap() # Show current reachable heap
37 | Partition of a set of 30976 objects. Total size = 3544220 bytes.
38 |  Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
39 |      0   8292  27   739022  21    739022  21 str
40 | ...
41 |      9    172   1    81712   2   3054020  86 dict (no owner)
42 | <89 more rows. Type e.g. '_.more' to view.>
43 | >>> 
44 |
45 | To see more about how the heapy object may be used, follow the 46 | link on the return kind. 47 |
48 |
49 | Normally no arguments need to be given. The arguments that may 50 | be given are for special cases.
51 |
52 |
53 |
Argument
54 |
55 |
56 |
ht = Any+
57 |
58 | The hiding tag to use. It may be useful to specify this 59 | in some cases when using multiple heapy instances, when you 60 | want to see the data in some of the other instances.
61 |
62 |
63 |
Default: will be set to the same unique object each
64 |
65 | time. In this way, different heapy instances will not see each 66 | other's data.
67 |
Root() -> guppy_Root 68 |
69 |
70 | Create a new guppy Root object. 71 | 72 |

73 | All functionality in the system may be accessed from this 74 | object. Modules are imported on demand when accessed. Other objects 75 | may be created or imported on demand using Guppy Glue+ 76 | directives. 77 |

78 |

79 | As this is a general access point, the heapy functionality may 80 | be accessed from here as well as via the hpy() method. How it is done 81 | is beyond the scope of this documentation, and is to be regarded an 82 | implementation detail, but you can of course look at the source code 83 | for the hpy method. 84 |

85 |

86 | There is currently no arguments to this constructor, I may think 87 | of adding some options in the future, but it has not yet been needed. 88 |

89 |
Generated by GSL-HTML 3.1.5 on Mon Oct 20 20:07:59 2025
-------------------------------------------------------------------------------- /specs/heapy_RefPat.gsl: -------------------------------------------------------------------------------- 1 | .import:: int+ 2 | ..from: kindnames 3 | 4 | .import:: ReferencePattern, IdentitySetNotEmpty 5 | ..from: heapykinds 6 | 7 | .import:: IdentitySet 8 | ..from: heapy_UniSet 9 | 10 | .and: ReferencePattern 11 | ..constructor: IdentitySet.rp 12 | ..constructor: IdentitySet.get_rp 13 | 14 | ..d 15 | 16 | This is an object representing a reference pattern. It is a condensed 17 | form of the graph formed by all direct and indirect referrer 18 | objects. Given a starting set of objects, a reference pattern groups 19 | the direct referrers by similar kind by a given classifier, and 20 | continues recursively with the referrers of the direct referrers and 21 | so on until a certain depth is reached, an already seen set of objects 22 | is reached or the objects of a set are all of some kind defined in a 23 | stop criterium. The result is generally a graph which is per default 24 | presented in depth first form. 25 | 26 | ...p: 27 | 28 | The string representation of a reference pattern is designed to be 29 | displayed in textual form and also to be like what would be shown 30 | graphically. It is printed as a depth first spanning tree. Each node 31 | of the printed tree represents a set of objects of the same kind, as 32 | defined by the classifier used. A [-] sign means the node has referrer 33 | nodes, which are printed on subsequent lines, indented one step. A [+] 34 | sign means the node has referrers that are not printed because the 35 | maximum depth is reached. Some kinds of objects may be specified to 36 | not be followed to their referrers, because they are standard kinds of 37 | objects which are referred to in known ways. For example, the 38 | referrers of modules are not followed by default. Such nodes are 39 | indicated by a [S] sign. Some nodes may have already been seen in the 40 | spanning tree, and this is indicated by [^ x] where x is the index of 41 | the node as previously printed. 42 | 43 | ...p: 44 | 45 | The contents of the set of objects representing a node, is printed in 46 | a condensed form designed to usually fit on a single line. It is 47 | possible to select particular nodes of the reference pattern, to get 48 | the underlying set of objects for further examination. Selecting such 49 | a node may be done either by the numeric index printed at the start of 50 | each node line or by the tree selection node name which is printed 51 | after the index. Selecting by index is done by indexing 'x[index]' 52 | while the selection by node name is done by getting the attribute 53 | 'x.abc'. 54 | 55 | ...dwh: Example 56 | ....pre 57 | >>> from guppy import hpy 58 | >>> h=hpy() 59 | >>> x=[] 60 | >>> y=[[x] for i in range(1000)] 61 | >>> z=[(x,) for i in range(1000)] 62 | >>> p=h.iso(x).rp 63 | >>> p 64 | Reference Pattern by <[dict of] class>. 65 | 0: _ --- [-] 1 list: 0xb7a3ac4c*0 66 | 1: a [-] 1000 list: 0xb79d06ec*1, 0xb79d070c*1, 0xb79d072c*1... 67 | 2: aa ---- [-] 1 list: 0xb7a3a38c*1000 68 | 3: a3 [S] 1 dict of module: __main__ 69 | 4: b ---- [-] 1000 tuple: 0xb77b270c*1, 0xb77b27ac*1, 0xb77b2b8c*1... 70 | 5: ba [-] 1 list: 0xb7d67a0c*1000 71 | 6: baa ---- [^ 3] 1 dict of module: __main__ 72 | 7: c [^ 3] 1 dict of module: __main__ 73 | >>> p._ == p[0] == h.iso(x) 74 | True 75 | >>> p._.referrers == p.a | p.b | p.c 76 | True 77 | ...c: end pre 78 | 79 | ..dwh: See also 80 | ...t: Section XXX. 81 | 82 | ..fop: repr 83 | ...d: Convert to a printable string, the same as by 84 | ....ref: .mykind.str 85 | ....t: . See the general reference pattern description for an 86 | explanation and example. 87 | 88 | ..fop: str 89 | 90 | ...d: Convert to a printable string, the same as by 91 | ....ref: .mykind.repr 92 | ....t: . See the general reference pattern description for an 93 | explanation and example. 94 | 95 | ..getitem 96 | ...arg: index:int+ 97 | ...returns: IdentitySetNotEmpty 98 | ...d 99 | 100 | Indexes the reference pattern by a numerical index. See the general 101 | reference pattern description for an explanation and example. 102 | -------------------------------------------------------------------------------- /docs/css/guppy.css: -------------------------------------------------------------------------------- 1 | /* 2 | Style information specific to the Guppy-PE project home page. 3 | 4 | Adapted/copied/based/stolen from: 5 | 6 | Style information specific to the Cheese Shop web interface. 7 | 8 | $id$ 9 | */ 10 | td.content { 11 | padding: 2px; 12 | } 13 | p.ok { 14 | background-color: #22bb22; 15 | padding: 5 5 5 5; 16 | color: white; 17 | font-weight: bold; 18 | } 19 | p.error { 20 | background-color: #bb2222; 21 | padding: 5 5 5 5; 22 | color: white; 23 | font-weight: bold; 24 | } 25 | 26 | /* style for forms */ 27 | table.form { 28 | padding: 2; 29 | border-spacing: 0px; 30 | border-collapse: separate; 31 | } 32 | 33 | table.form th { 34 | color: #333388; 35 | text-align: right; 36 | vertical-align: top; 37 | font-weight: normal; 38 | padding-right: 0.5em; 39 | } 40 | 41 | table.form th.header { 42 | font-weight: bold; 43 | text-align: left; 44 | } 45 | 46 | table.form th.required { 47 | font-weight: bold; 48 | } 49 | 50 | table.form td { 51 | color: #333333; 52 | empty-cells: show; 53 | vertical-align: top; 54 | } 55 | 56 | table.form td.optional { 57 | font-weight: bold; 58 | font-style: italic; 59 | } 60 | 61 | /* style for lists */ 62 | table.list { 63 | border-spacing: 0px; 64 | border-collapse: separate; 65 | width: 100%; 66 | } 67 | 68 | table.list th { 69 | text-align: left; 70 | padding: 2px 4px 2px 4px; 71 | color: #333; 72 | background-color: #ccc; 73 | border-bottom: 1px solid #ccc; 74 | vertical-align: top; 75 | empty-cells: show; 76 | } 77 | table.list th a[href]:hover { color: #404070 } 78 | table.list th a[href]:link { color: #404070 } 79 | table.list th a[href] { color: #404070 } 80 | table.list th.group { 81 | background-color: #f4f4ff; 82 | text-align: center; 83 | } 84 | 85 | table.list td { 86 | padding: 2px 4px 2px 4px; 87 | border: 0px 2px 0px 2px; 88 | border-right: 1px solid #ccc; 89 | color: #333; 90 | background-color: white; 91 | vertical-align: top; 92 | empty-cells: show; 93 | } 94 | 95 | table.list tr.normal td { 96 | background-color: white; 97 | } 98 | 99 | table.list tr.odd td { 100 | background-color: #efefef; 101 | } 102 | 103 | table.list td#last { 104 | border-top: 1px solid #ccc; 105 | border-left: none; 106 | border-right: none; 107 | } 108 | 109 | table.list tr:first-child td, 110 | table.list tr:first-child th { 111 | border-top: 1px solid #ccc; 112 | } 113 | table.list td:first-child { 114 | border-left: 1px solid #ccc; 115 | border-right: 1px solid #ccc; 116 | } 117 | 118 | table.list tr.navigation th { 119 | text-align: right; 120 | } 121 | table.list tr.navigation th:first-child { 122 | border-right: none; 123 | text-align: left; 124 | } 125 | 126 | /* style for role lists */ 127 | table.roles { 128 | border-spacing: 0px; 129 | border-collapse: separate; 130 | width: 100%; 131 | } 132 | 133 | table.roles th.header{ 134 | padding-top: 10px; 135 | border-bottom: 1px solid gray; 136 | font-weight: bold; 137 | background-color: white; 138 | color: #707040; 139 | } 140 | 141 | table.roles th { 142 | border-bottom: 1px solid #afafaf; 143 | font-weight: bold; 144 | text-align: left; 145 | } 146 | 147 | table.roles td { 148 | vertical-align: top; 149 | empty-cells: show; 150 | } 151 | 152 | 153 | /* style for history displays */ 154 | table.history { 155 | border-spacing: 0px; 156 | border-collapse: separate; 157 | } 158 | 159 | table.history th.header{ 160 | padding-top: 10px; 161 | border-bottom: 1px solid gray; 162 | font-weight: bold; 163 | background-color: white; 164 | color: #707040; 165 | font-size: 100%; 166 | } 167 | 168 | table.history th { 169 | border-bottom: 1px solid #afafaf; 170 | font-weight: bold; 171 | text-align: left; 172 | font-size: 90%; 173 | padding-right: 1em; 174 | } 175 | 176 | table.history td { 177 | font-size: 90%; 178 | vertical-align: top; 179 | empty-cells: show; 180 | padding-right: 1em; 181 | } 182 | 183 | table.history tr.last td { 184 | border-bottom: 1px solid gray; 185 | } 186 | 187 | ul.nodot { 188 | list-style-type: none; 189 | } 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/sets/bitset.h: -------------------------------------------------------------------------------- 1 | #ifndef Ny_BITSET_H 2 | #define Ny_BITSET_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | typedef Py_uintptr_t NyBits; 9 | 10 | 11 | /* Number of bits in a NyBits field 12 | We don't use sizeof since it can't be used in preprocessor #if directive 13 | 14 | Not: #define NyBits_N ((long)(sizeof(NyBits) * 8)) 15 | */ 16 | 17 | 18 | #if SIZE_MAX==4294967295UL 19 | #define NyBits_N 32 20 | #define ONE_LONG 1l 21 | #elif SIZE_MAX==18446744073709551615ULL 22 | #define NyBits_N 64 23 | #define ONE_LONG 1ll 24 | #else 25 | #error "Unsupported size of size_t" 26 | #endif 27 | 28 | 29 | /* Assume __BYTE_ORDER is defined on all big-endian archs and that they 30 | have byteswap.h. Little-endian archs don't include byteswap.h, so 31 | it should still work with eg MSC. 32 | */ 33 | 34 | #ifdef __BYTE_ORDER 35 | #if __BYTE_ORDER==__BIG_ENDIAN 36 | 37 | #define NyBits_IS_BIG_ENDIAN 1 38 | 39 | #include "byteswap.h" 40 | 41 | #if (NyBits_N==64) 42 | #define NyBits_BSWAP(x) bswap_64(x) 43 | #elif (NyBits_N==32) 44 | #define NyBits_BSWAP(x) bswap_32(x) 45 | #else 46 | #error "Unsupported NyBits_N" 47 | #endif 48 | 49 | #endif 50 | #endif 51 | 52 | 53 | 54 | 55 | typedef Py_intptr_t NyBit; 56 | 57 | /* Largest positive value of type NyBit. */ 58 | #define NyBit_MAX ((NyBit)(((Py_uintptr_t)-1)>>1)) 59 | /* Smallest negative value of type NyBit. */ 60 | #define NyBit_MIN (-NyBit_MAX-1) 61 | 62 | 63 | #define NyPos_MAX (NyBit_MAX/NyBits_N) 64 | #define NyPos_MIN (NyBit_MIN/NyBits_N) 65 | 66 | 67 | typedef struct { 68 | NyBit pos; /* The position of the first bit / NyBits_N */ 69 | NyBits bits; /* The bits as a mask */ 70 | } NyBitField; 71 | 72 | /* Immutable bitset */ 73 | 74 | typedef struct { 75 | PyObject_VAR_HEAD 76 | Py_ssize_t ob_length; /* Result for len(), -1 if not yet calculated */ 77 | NyBitField ob_field[1]; /* The bit fields, ob_size of these */ 78 | } NyImmBitSetObject; 79 | 80 | typedef struct { 81 | PyObject_HEAD 82 | NyImmBitSetObject *ob_val; 83 | } NyCplBitSetObject; 84 | 85 | typedef struct { 86 | NyBit pos; 87 | NyBitField *lo, *hi; 88 | NyImmBitSetObject *set; 89 | } NySetField; 90 | 91 | #define NyUnion_MINSIZE 1 92 | 93 | typedef struct { 94 | PyObject_VAR_HEAD 95 | NyBit cur_size; 96 | NySetField ob_field[NyUnion_MINSIZE]; 97 | } NyUnionObject; 98 | 99 | /* Mutable bitset */ 100 | 101 | typedef struct { 102 | PyObject_HEAD 103 | int cpl; 104 | NyBit splitting_size; 105 | NyBitField *cur_field; 106 | NyUnionObject *root; 107 | NyUnionObject fst_root; 108 | } NyMutBitSetObject; 109 | 110 | 111 | #define NyBits_EMPTY 0 112 | #define NyBits_AND 1 /* a & b */ 113 | #define NyBits_OR 2 /* a | b */ 114 | #define NyBits_XOR 3 /* a ^ b */ 115 | #define NyBits_SUB 4 /* a & ~b */ 116 | #define NyBits_SUBR 5 /* ~a & b */ 117 | #define NyBits_FALSE 6 /* ~a & a */ 118 | #define NyBits_TRUE 7 /* ~a | a */ 119 | 120 | /* Table for import of C objects & functions via Python's cobject mechanism 121 | in the module at name 'NyBitSet_Exports' 122 | */ 123 | 124 | typedef struct { 125 | int flags; 126 | int size; 127 | char *ident_and_version; 128 | NyMutBitSetObject *(*mbs_new)(void); 129 | /* setbit & clrbit sets or clears bit bitno 130 | set_or_clr sets or clears it depending on set_or_clr parameter 131 | All 3 functions return previous bit: 0 (clr) or 1 (set) 132 | On error, -1 is returned. 133 | */ 134 | int (*mbs_setbit)(NyMutBitSetObject *v, NyBit bitno); 135 | int (*mbs_clrbit)(NyMutBitSetObject *v, NyBit bitno); 136 | int (*mbs_set_or_clr)(NyMutBitSetObject *v, NyBit bitno, int set_or_clr); 137 | PyObject *(*mbs_as_immutable)(NyMutBitSetObject *v); 138 | int (*iterate)(PyObject *v, 139 | int (*visit)(NyBit, void *), 140 | void *arg 141 | ); 142 | 143 | int (*mbs_hasbit)(NyMutBitSetObject *v, NyBit bitno); 144 | int (*ibs_hasbit)(NyImmBitSetObject *v, NyBit bitno); 145 | int (*cpl_hasbit)(NyCplBitSetObject *v, NyBit bitno); 146 | int (*mbs_clear)(NyMutBitSetObject *v); 147 | } NyBitSet_Exports; 148 | 149 | 150 | 151 | #ifdef __cplusplus 152 | } 153 | #endif 154 | 155 | #endif /* Ny_BITSET_H */ 156 | -------------------------------------------------------------------------------- /guppy/heapy/test/support.py: -------------------------------------------------------------------------------- 1 | """Supporting definitions for the Heapy regression test. 2 | Addapted from Python standard module test_support. 3 | """ 4 | 5 | import contextlib 6 | import unittest 7 | import pdb 8 | import sys 9 | import tracemalloc 10 | 11 | 12 | class Error(Exception): 13 | """Base class for regression test exceptions.""" 14 | 15 | 16 | class TestFailed(Error): 17 | """Test failed.""" 18 | 19 | 20 | class TestSkipped(Error): 21 | """Test skipped. 22 | 23 | This can be raised to indicate that a test was deliberatly 24 | skipped, but not because a feature wasn't available. For 25 | example, if some resource can't be used, such as the network 26 | appears to be unavailable, this should be raised instead of 27 | TestFailed. 28 | """ 29 | 30 | 31 | verbose = 1 # Flag set to 0 by regrtest.py 32 | use_resources = None # Flag set to [] by regrtest.py 33 | 34 | 35 | # ======================================================================= 36 | # Preliminary PyUNIT integration. 37 | 38 | 39 | class BasicTestRunner: 40 | def run(self, test): 41 | result = unittest.TestResult() 42 | test(result) 43 | return result 44 | 45 | 46 | def run_suite(suite, testclass=None): 47 | """Run tests from a unittest.TestSuite-derived class.""" 48 | if verbose: 49 | runner = unittest.TextTestRunner(sys.stdout, verbosity=2) 50 | else: 51 | runner = BasicTestRunner() 52 | 53 | result = runner.run(suite) 54 | if not result.wasSuccessful(): 55 | if len(result.errors) == 1 and not result.failures: 56 | err = result.errors[0][1] 57 | elif len(result.failures) == 1 and not result.errors: 58 | err = result.failures[0][1] 59 | else: 60 | if testclass is None: 61 | msg = "errors occurred; run in verbose mode for details" 62 | else: 63 | msg = "errors occurred in %s.%s" \ 64 | % (testclass.__module__, testclass.__name__) 65 | raise TestFailed(msg) 66 | raise TestFailed(err) 67 | 68 | 69 | def run_unittest(testclass, debug=0): 70 | """Run tests from a unittest.TestCase-derived class.""" 71 | suite = unittest.defaultTestLoader.loadTestsFromTestCase(testclass) 72 | if debug: 73 | suite.debug() 74 | else: 75 | run_suite(suite, testclass) 76 | 77 | 78 | def debug_unittest(testclass): 79 | """ Debug tests from a unittest.TestCase-derived class.""" 80 | run_unittest(testclass, debug=1) 81 | 82 | 83 | # Base test case, tailored for heapy 84 | class TestCase(unittest.TestCase): 85 | def setUp(self): 86 | from guppy import Root 87 | self.python = Root() 88 | self.guppy = self.python.guppy 89 | self.heapy = self.guppy.heapy 90 | self.Part = self.heapy.Part 91 | self.ImpSet = self.heapy.ImpSet 92 | self.Use = self.heapy.Use 93 | self.View = self.heapy.View 94 | self.iso = self.Use.iso 95 | self.idset = self.Use.idset 96 | self.version_info = sys.version_info 97 | 98 | def aseq(self, a, b, cont=0): 99 | if a != b: 100 | print("aseq: Expected: b = ", b) 101 | print("Got actually : a = ", a) 102 | if cont <= 0: 103 | if cont < 0: 104 | pdb.set_trace() 105 | else: 106 | self.assertTrue(0) 107 | 108 | def asis(self, a, b, cont=0): 109 | if a is not b: 110 | print("asis: Expected: b = ", b) 111 | print("Got actually : a = ", a) 112 | if cont <= 0: 113 | if cont < 0: 114 | pdb.set_trace() 115 | else: 116 | self.assertTrue(0) 117 | 118 | def tearDown(self): 119 | pass 120 | 121 | @contextlib.contextmanager 122 | def tracemalloc_state(enabled=True): 123 | orig_enabled = tracemalloc.is_tracing() 124 | 125 | def set_enabled(new_enabled): 126 | cur_enabled = tracemalloc.is_tracing() 127 | if cur_enabled == new_enabled: 128 | return 129 | 130 | if new_enabled: 131 | tracemalloc.start() 132 | else: 133 | tracemalloc.stop() 134 | 135 | set_enabled(enabled) 136 | 137 | try: 138 | yield 139 | finally: 140 | set_enabled(orig_enabled) 141 | -------------------------------------------------------------------------------- /specs/heapy_RootState.gsl: -------------------------------------------------------------------------------- 1 | .import:: RootStateType 2 | ..from: heapykinds 3 | 4 | .and:: RootStateType 5 | ..d 6 | ...p 7 | The type of an object with special functionality that gives access to 8 | internals of the Python interpreter and thread structures. It is used 9 | as a top level root when traversing the heap to make sure to find 10 | some special objects that may otherwise be hidden. 11 | ...p 12 | There are no references from the RootState object to the special 13 | objects. But the heap traversal and related functions defined for 14 | RootStateType look into the Python interpreter and thread structures. 15 | The visibility is controlled by options set in the HeapView object 16 | which is passed to the traversal function. This makes it possible to 17 | hide an interpreter and/or some frames referring to system objects 18 | that should not be traversed. (See the attributes 19 | 'is_hiding_calling_interpreter' and 'limitframe' in HeapView.) 20 | ...p 21 | The objects found in interpreter and thread structures are related to 22 | the RootState object via attributes with special names. These names 23 | have a special form which will be described below. The name starts 24 | with either an interpreter designator or a thread designator. It is 25 | then followed by the name of a member in the corresponding interpreter 26 | or thread structure. These names are the same as the names of the 27 | members in the C structures defining them. Some of the names may be 28 | dependent on the Python interpreter version used. 29 | ...p 30 | The attribute names are used for two purposes: 31 | 32 | ...ul 33 | ....li 34 | To be the name used in the result of the 'relate' operation between 35 | the RootState object and some object that is referred to via an 36 | internal Python interpreter or thread structure. 37 | ....li 38 | To be used as attribute names when selecting objects 39 | from the RootState object. This may be used to get at such 40 | an object knowing only its attribute name. 41 | 42 | ...p 43 | An attribute name is of one of the following three forms. 44 | ...p 45 | i_ 46 | ...p 47 | i_t_ 48 | ...p 49 | i_t_f 50 | 51 | ...h3: 52 | ...p 53 | The interpreter number identifies a particular interpreter structure. 54 | Often there is only one interpreter used, in which case the number is 55 | 0. It is possible to use more than one interpreter. The interpreters 56 | are then numbered from 0 and up in the order they were started. [This 57 | applies as long as no interpreter is terminated while there is still a 58 | newer interpreter running. Then the newer interpreters will be 59 | renumbered. If this is found to be a problem, a solution may be 60 | devised for a newer release.] 61 | 62 | ...h3: 63 | ...p 64 | The interpreter attribute is a member with PyObject pointer type 65 | in the PyInterpreterState structure and can be, but not limited to, 66 | one of the following: 67 | 68 | ...ul 69 | ....li: modules 70 | ....li: sysdict 71 | ....li: builtins 72 | ....li: codec_search_path 73 | ....li: codec_search_cache 74 | ....li: codec_error_registry 75 | 76 | ...h3: 77 | ...p 78 | The thread numbers are taken from the thread identity number assigned 79 | by Python. [ In older versions without thread identity numbers the hex 80 | address will be used.] 81 | ...h3: 82 | ...p 83 | The thread attribute is a member with PyObject pointer type 84 | in the PyThreadState structure and can be, but not limited to, 85 | one of the following: 86 | 87 | ...ul 88 | ....li: c_profileobj 89 | ....li: c_traceobj 90 | ....li: curexc_type 91 | ....li: curexc_value 92 | ....li: curexc_traceback 93 | ....li: exc_type 94 | ....li: exc_value 95 | ....li: exc_traceback 96 | ....li: dict 97 | ....li: async_exc 98 | 99 | ...h3: 100 | ...p 101 | The frame list is treated specially. The frame list is continually 102 | changed and the object that the frame member points to is not valid 103 | for long enough to be useful. Therefore frames are referred to by a 104 | special designator using the format shown above with a frame 105 | number. The frame number is the number of the frame starting from 0 106 | but counting in the reversed order of the frame list. Thus the first 107 | started frame is 0, and in general the most recent frame has a number 108 | that is the number of frames it has before it in call order. 109 | -------------------------------------------------------------------------------- /guppy/gsl/Filer.py: -------------------------------------------------------------------------------- 1 | """ Handles filing of data from low-level gsl filing and data records. 2 | """ 3 | 4 | 5 | class Filer: 6 | def __init__(self, mod, node): 7 | self.mod = mod 8 | self.writefile_envs = [] 9 | self.writefile_names = {} 10 | 11 | node.accept(self) 12 | 13 | def visit_file(self, node): 14 | node.children_accept(self) 15 | 16 | visit_string = visit_file 17 | 18 | def visit_write_file(self, node): 19 | name = node.arg 20 | if name in self.writefile_names: 21 | raise SyntaxError('Duplicate file name: %r' % name) 22 | self.writefile_names[name] = node 23 | self.writefile_envs.append(WriteFile(self, node)) 24 | 25 | def get_info(self): 26 | infos = [] 27 | for e in self.writefile_envs: 28 | infos.append('write file: %s' % e.file_name) 29 | return '\n'.join(infos) 30 | 31 | def write(self): 32 | for e in self.writefile_envs: 33 | e.write() 34 | 35 | 36 | class WriteFile: 37 | node_data = None 38 | node_mode = None 39 | 40 | def __init__(self, filer, node): 41 | self.filer = filer 42 | self.mod = mod = filer.mod 43 | self.node_file = node 44 | self.file_name = node.arg 45 | 46 | node.children_accept(self) 47 | if self.node_data is None: 48 | data = '' 49 | else: 50 | data = self.node_data.arg 51 | self.data = data 52 | if self.node_mode is None: 53 | mode = '' 54 | else: 55 | mode = self.node_mode.arg 56 | self.mode = mode 57 | 58 | def visit_text(self, node): 59 | self.set_single('node_data', node) 60 | 61 | def visit_end(self, node): 62 | self.set_single('node_end', node) 63 | 64 | def visit_mode(self, node): 65 | self.set_single('node_mode', node) 66 | 67 | def set_single(self, name, node): 68 | if getattr(self, name, None) is not None: 69 | raise SyntaxError('Duplicate %r at index %r' % (name, node.index)) 70 | setattr(self, name, node) 71 | node.children_accept(self, 'no_node_expected') 72 | 73 | def write(self): 74 | IO = self.mod.IO 75 | if self.mod.backup_suffix: 76 | backup_name = self.file_name + self.mod.backup_suffix 77 | if IO.access(self.file_name, IO.R_OK | IO.W_OK): 78 | IO.rename(self.file_name, backup_name) 79 | 80 | IO.write_file(self.file_name, self.data) 81 | 82 | 83 | class _GLUECLAMP_: 84 | _imports_ = ( 85 | '_parent.FileIO:IO', 86 | ) 87 | 88 | _setable_ = 'backup_suffix', 89 | 90 | # Files that are to be overwritten are renamed by 91 | # adding backup_suffix to the name. This is no substitute for a 92 | # versioning system but a last precaution, especially while I am 93 | # developing the system. 94 | # Set this to None to disable backuping. 95 | 96 | backup_suffix = ',gsl-backuped' 97 | 98 | syntax_gsl = ''' 99 | .tag writefile 100 | 101 | ''' 102 | 103 | def filer(self, node): 104 | return Filer(self, node) 105 | 106 | def _test_main_(self): 107 | IO = self.IO 108 | N = self._parent.SpecNodes 109 | tempdir = IO.mkdtemp() 110 | tempname = IO.path.join(tempdir, 'x') 111 | data = 'hello' 112 | try: 113 | X = ''' 114 | .write file: %s 115 | ..text 116 | %s 117 | ..end 118 | ''' % (tempname, data) 119 | node = N.node_of_string(X) 120 | f = self.filer(node) 121 | assert f.get_info() == 'write file: %s' % tempname 122 | f.write() 123 | d = IO.read_file(tempname) 124 | assert d == data 125 | 126 | # Test multiple files and backup 127 | # And that we can do without ..data / ..end 128 | 129 | data2 = 'hello2\n' 130 | data3 = '\nhello3' 131 | X = ''' 132 | .write file: %s 133 | ..text 134 | %s 135 | .write file: %s 136 | ..text 137 | %s 138 | ..end 139 | ''' % (tempname, data2, tempname+'.3', data3) 140 | 141 | node = N.node_of_string(X) 142 | f = self.filer(node) 143 | f.write() 144 | 145 | assert IO.read_file(tempname+self.backup_suffix) == data 146 | d = IO.read_file(tempname) 147 | assert d == data2 148 | assert IO.read_file(tempname+'.3') == data3 149 | 150 | finally: 151 | for name in IO.listdir(tempdir): 152 | IO.remove(IO.path.join(tempdir, name)) 153 | IO.rmdir(tempdir) 154 | -------------------------------------------------------------------------------- /guppy/etc/Cat.py: -------------------------------------------------------------------------------- 1 | class Graph: 2 | def __init__(self, objects, arrows): 3 | self.objects = objects # Sequence of objects 4 | self.arrows = arrows # Map[name] ->pair(object, object) 5 | 6 | def source(self, x): 7 | return self.arrows[x][0] 8 | 9 | def target(self, x): 10 | return self.arrows[x][1] 11 | 12 | def get_dual(self): 13 | objects = self.objects 14 | arrows = dict([(arrow, (tgt, src)) 15 | for (arrow, (src, tgt)) in list(self.arrows.items())]) 16 | return self.__class__(objects, arrows) 17 | 18 | 19 | class Cat: 20 | # Category presented by a graph (with objects and generators) and relations. 21 | def __init__(self, graph, relations): 22 | # category is defined by the parameters: 23 | # graph.objects: sequenceof(O) 24 | # graph.arrows: dict mapping(A, pairof(O in objects)) 25 | # relations: sequence(pairof(sequence(A), sequence(A))) 26 | self.graph = graph 27 | self.relations = relations 28 | 29 | def get_dual(self): 30 | graph = self.graph.get_dual() 31 | relations = dual_relations(self.relations) 32 | return self.__class__(graph, relations) 33 | 34 | 35 | class Functor: 36 | def __init__(self, fo, fa, src=None, tgt=None): 37 | self.fo = adapt_function(fo) 38 | self.fa = adapt_function(fa) 39 | self.src = src 40 | self.tgt = tgt 41 | 42 | 43 | class Function: 44 | def __init__(self, map, src, tgt): 45 | f = getattr(map, '__getitem__', None) 46 | if callable(f): 47 | pass 48 | else: 49 | f = map 50 | if not callable(f): 51 | raise TypeError( 52 | 'Function: map is neither callable or indexable') 53 | self.f = f 54 | self.src = src 55 | self.tgt = tgt 56 | 57 | def __getitem__(self, *args): 58 | return self.f(*args) 59 | 60 | def __call__(self, *args, **kwargs): 61 | return self.f(*args, **kwargs) 62 | 63 | def __str__(self): 64 | return '%s(%s, %s, %s)' % (self.__class__, self.src, self.tgt, self.f) 65 | 66 | def asdict(self): 67 | return dict([(x, self[x]) for x in self.src]) 68 | 69 | def items(self): 70 | return [(x, self[x]) for x in self.src] 71 | 72 | def keys(self): 73 | return list(self.src) 74 | 75 | def values(self): 76 | return [v for (k, v) in list(self.items())] 77 | 78 | 79 | class Identity(Function): 80 | def __init__(self, src): 81 | Function.__init__(lambda x: x, src, src) 82 | 83 | 84 | def check_graph(G): 85 | # Check that G is a valid graph object 86 | # with arrows that have all source and target in G.objects 87 | 88 | Gob = G.objects 89 | for a in G.arrows: 90 | if not G.source(a) in Gob: 91 | raise ValueError( 92 | 'Arrow %r has source %r not in graph objects' % (a, G.source(a))) 93 | if not G.target(a) in Gob: 94 | raise ValueError( 95 | 'Arrow %r has target %r not in graph objects' % (a, G.target(a))) 96 | 97 | 98 | def check_rules(R, G): 99 | # Check that the rules in R contain valid composing arrows in graph G 100 | 101 | coms = [] 102 | for (left, right) in R: 103 | coms.append(left) 104 | coms.append(right) 105 | 106 | for com in coms: 107 | a0 = None 108 | for a in com: 109 | if a not in G.arrows: 110 | raise ValueError( 111 | 'Arrow %r, used in a rule, is not a valid arrow' % (a,)) 112 | if a0 is not None: 113 | if G.source(a) != G.target(a0): 114 | raise ValueError('''\ 115 | Source of arrow %r (%r) does not match target of arrow %r (%r)''' % ( 116 | a, G.source(a), a0, G.target(a0))) 117 | a0 = a 118 | 119 | 120 | def check_cat(C): 121 | check_graph(C.graph) 122 | check_rules(C.relations, C.graph) 123 | 124 | 125 | def oarcat(objects, arrows, relations): 126 | return Cat(Graph(objects, arrows), relations) 127 | 128 | 129 | def adapt_function(f): 130 | if not isinstance(f, Function): 131 | if isinstance(f, dict): 132 | src = list(f.keys()) 133 | tgt = list(f.values()) 134 | else: 135 | src = None 136 | tgt = None 137 | f = Function(f, src, tgt) 138 | return f 139 | 140 | 141 | def dual_relations(relations): 142 | dual = [] 143 | for (a, b) in relations: 144 | a = list(a) 145 | b = list(b) 146 | a.reverse() 147 | b.reverse() 148 | dual.append((tuple(a), tuple(b))) 149 | return dual 150 | -------------------------------------------------------------------------------- /src/heapy/hv_cli_findex.c: -------------------------------------------------------------------------------- 1 | /* Implementation of the "findex" classifier (for lack of a better name) 2 | a generalization of biper (bipartitioner) 3 | as discussed in Notes Sep 21 2005. 4 | 5 | */ 6 | 7 | PyDoc_STRVAR(hv_cli_findex_doc, 8 | "HV.cli_findex(tuple, memo) -> ObjectClassifier\n\ 9 | "); 10 | 11 | 12 | typedef struct { 13 | PyObject_VAR_HEAD 14 | PyObject *alts; 15 | PyObject *memo; 16 | PyObject *kinds; 17 | PyObject *cmps; 18 | } FindexObject; 19 | 20 | static PyObject * 21 | hv_cli_findex_memoized_kind(FindexObject * self, PyObject *kind) 22 | { 23 | PyObject *result = PyDict_GetItem(self->memo, kind); 24 | if (!result) { 25 | if (PyErr_Occurred()) 26 | goto Err; 27 | if (PyDict_SetItem(self->memo, kind, kind) == -1) 28 | goto Err; 29 | result = kind; 30 | } 31 | Py_INCREF(result); 32 | return result; 33 | Err: 34 | return 0; 35 | } 36 | 37 | 38 | static PyObject * 39 | hv_cli_findex_classify(FindexObject * self, PyObject *obj) 40 | { 41 | Py_ssize_t i, numalts; 42 | PyObject *kind, *ret, *index; 43 | numalts = PyTuple_GET_SIZE(self->alts); 44 | for (i = 0; i < numalts; i++) { 45 | PyObject *ckc = PyTuple_GET_ITEM(self->alts, i); 46 | NyObjectClassifierObject *cli = (void *)PyTuple_GET_ITEM(ckc, 0); 47 | PyObject *cmpkind = PyTuple_GET_ITEM(self->kinds, i); 48 | long cmp = PyLong_AS_LONG(PyTuple_GET_ITEM(self->cmps, i)); 49 | kind = cli->def->classify(cli->self, obj); 50 | if (!kind) 51 | return 0; 52 | cmp = NyObjectClassifier_Compare(cli, kind, cmpkind, cmp); 53 | Py_DECREF(kind); 54 | if (cmp == -1) 55 | return 0; 56 | if (cmp) 57 | break; 58 | } 59 | index = PyLong_FromSsize_t(i); 60 | if (!index) 61 | return 0; 62 | ret = hv_cli_findex_memoized_kind(self, index); 63 | Py_DECREF(index); 64 | return ret; 65 | } 66 | 67 | static int 68 | hv_cli_findex_le(PyObject * self, PyObject *a, PyObject *b) 69 | { 70 | return PyObject_RichCompareBool(a, b, Py_LE); 71 | } 72 | 73 | static NyObjectClassifierDef hv_cli_findex_def = { 74 | 0, 75 | sizeof(NyObjectClassifierDef), 76 | "cli_findex", 77 | "classifier returning index of matching kind", 78 | (binaryfunc)hv_cli_findex_classify, 79 | (binaryfunc)hv_cli_findex_memoized_kind, 80 | hv_cli_findex_le, 81 | }; 82 | 83 | static PyObject * 84 | hv_cli_findex(NyHeapViewObject *hv, PyObject *args) 85 | { 86 | PyObject *r; 87 | FindexObject *s, tmp; 88 | Py_ssize_t numalts; 89 | Py_ssize_t i; 90 | if (!PyArg_ParseTuple(args, "O!O!:cli_findex", 91 | &PyTuple_Type, &tmp.alts, 92 | &PyDict_Type, &tmp.memo)) { 93 | return 0; 94 | } 95 | numalts = PyTuple_GET_SIZE(tmp.alts); 96 | for (i = 0; i < numalts; i++) { 97 | PyObject *ckc = PyTuple_GET_ITEM(tmp.alts, i); 98 | if (!PyTuple_Check(ckc)) { 99 | PyErr_SetString(PyExc_TypeError, "Tuple of TUPLES expected."); 100 | return 0; 101 | } 102 | if (PyTuple_GET_SIZE(ckc) != 3) { 103 | PyErr_SetString(PyExc_TypeError, "Tuple of TRIPLES expected."); 104 | return 0; 105 | } 106 | if (!NyObjectClassifier_Check(PyTuple_GET_ITEM(ckc, 0))) { 107 | PyErr_SetString(PyExc_TypeError, "Tuple of triples with [0] a CLASSIFIER expected."); 108 | return 0; 109 | } 110 | if (!PyUnicode_Check(PyTuple_GET_ITEM(ckc, 2))) { 111 | PyErr_SetString(PyExc_TypeError, "Tuple of triples with [2] a STRING expected."); 112 | return 0; 113 | } 114 | if (cli_cmp_as_int(PyTuple_GET_ITEM(ckc, 2)) == -1) { 115 | return 0; 116 | } 117 | } 118 | s = NYTUPLELIKE_NEW(FindexObject); 119 | if (!s) 120 | return 0; 121 | s->alts = tmp.alts; 122 | Py_INCREF(tmp.alts); 123 | s->memo = tmp.memo; 124 | Py_INCREF(tmp.memo); 125 | s->kinds = PyTuple_New(numalts); 126 | s->cmps = PyTuple_New(numalts); 127 | if (!s->kinds) 128 | goto Err; 129 | for (i = 0; i < numalts; i++) { 130 | PyObject *ckc = PyTuple_GET_ITEM(tmp.alts, i); 131 | NyObjectClassifierObject *cli = (void *)PyTuple_GET_ITEM(ckc, 0); 132 | PyObject *mk = PyTuple_GET_ITEM(ckc, 1); 133 | if (cli->def->memoized_kind) { 134 | mk = cli->def->memoized_kind(cli->self, mk); 135 | if (!mk) 136 | goto Err; 137 | } else { 138 | Py_INCREF(mk); 139 | } 140 | PyTuple_SET_ITEM(s->kinds, i, mk); 141 | mk = PyLong_FromLong(cli_cmp_as_int(PyTuple_GET_ITEM(ckc, 2))); 142 | if (!mk) 143 | goto Err; 144 | PyTuple_SET_ITEM(s->cmps, i, mk); 145 | 146 | } 147 | r = NyObjectClassifier_New((PyObject *)s, &hv_cli_findex_def); 148 | Py_DECREF(s); 149 | return r; 150 | Err: 151 | Py_DECREF(s); 152 | return 0; 153 | } 154 | -------------------------------------------------------------------------------- /src/heapy/hv_cli_rcs.c: -------------------------------------------------------------------------------- 1 | /* Implementation of the 'rcs' classifier */ 2 | 3 | PyDoc_STRVAR(hv_cli_rcs_doc, 4 | "HV.cli_rcs(referrers, classifier, memo) -> ObjectClassifier\n\ 5 | \n\ 6 | Return a classifier that classifies by \"Referrer Classification Set\".\n\ 7 | \n\ 8 | The classification of an object is the classifications of its\n\ 9 | referrers, collected in an immutable NodeSet object. Arguments:\n\ 10 | \n\ 11 | referrers A NodeGraph object used to\n\ 12 | map each object to its referrers.\n\ 13 | \n\ 14 | classifier A ObjectClassifier object used to\n\ 15 | classify each referrer.\n\ 16 | \n\ 17 | memo A dict object used to\n\ 18 | memoize the classification sets.\n\ 19 | "); 20 | 21 | 22 | typedef struct { 23 | /* Mimics a tuple - xxx should perhaps make a proper object/use tuple macros?! */ 24 | PyObject_VAR_HEAD 25 | NyHeapViewObject *hv; 26 | NyObjectClassifierObject *cli; 27 | NyNodeGraphObject *rg; 28 | NyNodeSetObject *norefer; 29 | PyObject *memo; 30 | } RetclasetObject; 31 | 32 | static PyObject * 33 | hv_cli_rcs_fast_memoized_kind(RetclasetObject * self, PyObject *kind) 34 | { 35 | PyObject *result = PyDict_GetItem(self->memo, kind); 36 | if (!result) { 37 | if (PyErr_Occurred()) 38 | goto Err; 39 | if (PyDict_SetItem(self->memo, kind, kind) == -1) 40 | goto Err; 41 | result = kind; 42 | } 43 | Py_INCREF(result); 44 | return result; 45 | Err: 46 | return 0; 47 | } 48 | 49 | typedef struct { 50 | NyObjectClassifierObject *cli; 51 | NyNodeSetObject *ns; 52 | } MemoRcsArg; 53 | 54 | static int 55 | rcs_visit_memoize_sub(PyObject *obj, MemoRcsArg *arg) 56 | { 57 | obj = arg->cli->def->memoized_kind(arg->cli->self, obj); 58 | if (!obj) 59 | return -1; 60 | if (NyNodeSet_setobj(arg->ns, obj) == -1) { 61 | Py_DECREF(obj); 62 | return -1; 63 | } 64 | Py_DECREF(obj); 65 | return 0; 66 | } 67 | 68 | static PyObject * 69 | hv_cli_rcs_memoized_kind(RetclasetObject * self, PyObject *kind) 70 | { 71 | if (!NyNodeSet_Check(kind)) { 72 | PyErr_SetString(PyExc_TypeError, 73 | "hv_cli_rcs_memoized_kind: nodeset object (immutable) expected."); 74 | return 0; 75 | } 76 | if (!self->cli->def->memoized_kind) { 77 | return hv_cli_rcs_fast_memoized_kind(self, kind); 78 | } else { 79 | MemoRcsArg arg; 80 | PyObject *result; 81 | arg.cli = self->cli; 82 | arg.ns = hv_mutnodeset_new(self->hv); 83 | if (!arg.ns) 84 | return 0; 85 | if (iterable_iterate(kind, (visitproc)rcs_visit_memoize_sub, &arg) == -1) 86 | goto Err; 87 | if (NyNodeSet_be_immutable(&arg.ns) == -1) 88 | goto Err; 89 | result = hv_cli_rcs_fast_memoized_kind(self, (PyObject *)arg.ns); 90 | Ret: 91 | Py_DECREF(arg.ns); 92 | return result; 93 | Err: 94 | result = 0; 95 | goto Ret; 96 | } 97 | } 98 | 99 | 100 | 101 | 102 | static PyObject * 103 | hv_cli_rcs_classify(RetclasetObject * self, PyObject *obj) 104 | { 105 | NyNodeGraphEdge *lo, *hi, *cur; 106 | PyObject *kind = 0; 107 | NyNodeSetObject *Ri = hv_mutnodeset_new(self->hv); 108 | if (!Ri) 109 | goto Err; 110 | if (NyNodeGraph_Region(self->rg, obj, &lo, &hi) == -1) { 111 | goto Err; 112 | } 113 | for (cur = lo; cur < hi; cur++) { 114 | if (cur->tgt == Py_None) 115 | continue; 116 | kind = self->cli->def->classify(self->cli->self, cur->tgt); 117 | if (!kind) 118 | goto Err; 119 | if (NyNodeSet_setobj(Ri, kind) == -1) 120 | goto Err; 121 | Py_DECREF(kind); 122 | } 123 | if (NyNodeSet_be_immutable(&Ri) == -1) 124 | goto Err; 125 | kind = hv_cli_rcs_fast_memoized_kind(self, (PyObject *)Ri); 126 | Py_DECREF(Ri); 127 | return kind; 128 | 129 | Err: 130 | Py_XDECREF(kind); 131 | Py_XDECREF(Ri); 132 | return 0; 133 | } 134 | 135 | static int 136 | hv_cli_rcs_le(PyObject * self, PyObject *a, PyObject *b) 137 | { 138 | return PyObject_RichCompareBool(a, b, Py_LE); 139 | } 140 | 141 | static NyObjectClassifierDef hv_cli_rcs_def = { 142 | 0, 143 | sizeof(NyObjectClassifierDef), 144 | "hv_cli_rcs", 145 | "classifier returning ...", 146 | (binaryfunc)hv_cli_rcs_classify, 147 | (binaryfunc)hv_cli_rcs_memoized_kind, 148 | hv_cli_rcs_le 149 | }; 150 | 151 | 152 | static PyObject * 153 | hv_cli_rcs(NyHeapViewObject *hv, PyObject *args) 154 | { 155 | PyObject *r; 156 | RetclasetObject *s, tmp; 157 | if (!PyArg_ParseTuple(args, "O!O!O!:cli_rcs", 158 | &NyNodeGraph_Type, &tmp.rg, 159 | &NyObjectClassifier_Type, &tmp.cli, 160 | &PyDict_Type, &tmp.memo)) { 161 | return 0; 162 | } 163 | s = NYTUPLELIKE_NEW(RetclasetObject); 164 | if (!s) 165 | return 0; 166 | 167 | s->hv = hv; 168 | Py_INCREF(hv); 169 | s->rg = tmp.rg; 170 | Py_INCREF(tmp.rg); 171 | s->cli = tmp.cli; 172 | Py_INCREF(tmp.cli); 173 | s->memo = tmp.memo; 174 | Py_INCREF(tmp.memo); 175 | r = NyObjectClassifier_New((PyObject *)s, &hv_cli_rcs_def); 176 | Py_DECREF(s); 177 | return r; 178 | } 179 | -------------------------------------------------------------------------------- /docs/heapy_RootState.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

.tgt.heapykinds.RootStateType

11 |
12 |
13 |

Name

14 |
15 |

RootStateType

16 |
17 |

Description

18 |
19 |

20 | The type of an object with special functionality that gives access to 21 | internals of the Python interpreter and thread structures. It is used 22 | as a top level root when traversing the heap to make sure to find 23 | some special objects that may otherwise be hidden.

24 |

25 | There are no references from the RootState object to the special 26 | objects. But the heap traversal and related functions defined for 27 | RootStateType look into the Python interpreter and thread structures. 28 | The visibility is controlled by options set in the HeapView object 29 | which is passed to the traversal function. This makes it possible to 30 | hide an interpreter and/or some frames referring to system objects 31 | that should not be traversed. (See the attributes 32 | 'is_hiding_calling_interpreter' and 'limitframe' in HeapView.)

33 |

34 | The objects found in interpreter and thread structures are related to 35 | the RootState object via attributes with special names. These names 36 | have a special form which will be described below. The name starts 37 | with either an interpreter designator or a thread designator. It is 38 | then followed by the name of a member in the corresponding interpreter 39 | or thread structure. These names are the same as the names of the 40 | members in the C structures defining them. Some of the names may be 41 | dependent on the Python interpreter version used.

42 |

43 | The attribute names are used for two purposes: 44 |

45 |
    46 |
  • 47 | To be the name used in the result of the 'relate' operation between 48 | the RootState object and some object that is referred to via an 49 | internal Python interpreter or thread structure.
  • 50 |
  • 51 | To be used as attribute names when selecting objects 52 | from the RootState object. This may be used to get at such 53 | an object knowing only its attribute name. 54 |
55 |

An attribute name is of one of the following three forms.

56 |

i<interpreter number>_<interpreter attribute>

57 |

i<interpreter number>_t<thread number>_<thread attribute>

58 |

59 | i<interpreter number>_t<thread number>_f<frame number> 60 |

61 |

<interpreter number>

62 |

63 | The interpreter number identifies a particular interpreter structure. 64 | Often there is only one interpreter used, in which case the number is 65 | 0. It is possible to use more than one interpreter. The interpreters 66 | are then numbered from 0 and up in the order they were started. [This 67 | applies as long as no interpreter is terminated while there is still a 68 | newer interpreter running. Then the newer interpreters will be 69 | renumbered. If this is found to be a problem, a solution may be 70 | devised for a newer release.] 71 |

72 |

<interpreter attribute>

73 |

74 | The interpreter attribute is a member with PyObject pointer type 75 | in the PyInterpreterState structure and can be, but not limited to, 76 | one of the following: 77 |

78 |
    79 |
  • modules
  • 80 |
  • sysdict
  • 81 |
  • builtins
  • 82 |
  • codec_search_path
  • 83 |
  • codec_search_cache
  • 84 |
  • 85 | codec_error_registry 86 |
87 |

<thread number>

88 |

89 | The thread numbers are taken from the thread identity number assigned 90 | by Python. [ In older versions without thread identity numbers the hex 91 | address will be used.]

92 |

<thread attribute>

93 |

94 | The thread attribute is a member with PyObject pointer type 95 | in the PyThreadState structure and can be, but not limited to, 96 | one of the following: 97 |

98 |
    99 |
  • c_profileobj
  • 100 |
  • c_traceobj
  • 101 |
  • curexc_type
  • 102 |
  • curexc_value
  • 103 |
  • curexc_traceback
  • 104 |
  • exc_type
  • 105 |
  • exc_value
  • 106 |
  • exc_traceback
  • 107 |
  • dict
  • 108 |
  • 109 | async_exc 110 |
111 |

<frame number>

112 |

113 | The frame list is treated specially. The frame list is continually 114 | changed and the object that the frame member points to is not valid 115 | for long enough to be useful. Therefore frames are referred to by a 116 | special designator using the format shown above with a frame 117 | number. The frame number is the number of the frame starting from 0 118 | but counting in the reversed order of the frame list. Thus the first 119 | started frame is 0, and in general the most recent frame has a number 120 | that is the number of frames it has before it in call order. 121 |

122 |
Generated by GSL-HTML 3.1.5 on Mon Oct 20 20:07:59 2025
-------------------------------------------------------------------------------- /specs/docexample.gsl: -------------------------------------------------------------------------------- 1 | .kind:: example_kind 2 | ..d: A kind specifying some example attributes and methods. 3 | 4 | .c: This is a comment on the outer level. The comment extends to the 5 | next line beginning with a dot. 6 | 7 | We have left the scope of example_kind for now. 8 | 9 | The superkinds below are used for argument specification and provide 10 | values to the generated tests. 11 | 12 | They are not (necessarily) included in the generated document. 13 | 14 | .superkind:: int+ 15 | ..eg: 1 16 | 17 | .superkind:: string+ 18 | ..eg: 'abc' 19 | 20 | .superkind:: list+ 21 | ..eg: [] 22 | 23 | .kind:: list_of_integers 24 | 25 | ..d: A kind with no formal properties. In this example, I am not 26 | specifying anything about this kind. It is to be understood from its 27 | name and context what it means. More specifications can be added later 28 | as a system description evolves. 29 | 30 | .and: example_kind 31 | ..c: Here continues the specification of example_kind. 32 | ..c: It can be split in several parts, the first one must 33 | ..c: be a .kind, and the others .and. 34 | ..c: This can be useful to specify recursive kinds. 35 | 36 | ..attr:: a_nokind 37 | ...d: An attribute named a_nokind, with unspecified kind. 38 | 39 | ..method:: m_noargs 40 | ...d: A method that takes no arguments, and returns an unspecified kind. 41 | 42 | ..method:: m_returns 43 | ...d: A method which returns objects of kind 44 | ....ref: .myfile.example_kind 45 | ....t: again. 46 | ...returns: example_kind 47 | 48 | ..method:: m_one 49 | ...d: A method with one argument. 50 | ...arg: a: int+ 51 | ....d: A positional argument of kind 52 | .....ref: .myfile.int+ 53 | .....t:. The 54 | .....ref: .mykind.int+ 55 | .....t: kind is a so called 56 | .....em: superkind 57 | .....t: because an API with this specification, 58 | is expected to accept values according to the specification of 59 | 60 | .....ref: .mykind.int+ 61 | .....t:, but it is allowed to accept other kinds of arguments as well. A 62 | .....em: compatible 63 | 64 | .....t: new specification could add these alternative kinds of 65 | arguments as allowed arguments, but it would still have to accept the 66 | 67 | .....ref: .mykind.int+ 68 | .....t: kind of argument. 69 | ...dwh: Note 70 | The + in the int+ name is a convention to indicate that it is a 71 | ....em: superkind 72 | ....t:. 73 | ...dwh: Note 74 | The name of the argument, a, does 75 | ....em: not 76 | 77 | ....t: mean that it can be specified as a keyword argument with that 78 | name. It is only when keyword arguments are specifically specified 79 | that they are actually keyword arguments. 80 | 81 | ..method:: m_opt 82 | 83 | ...d: A method with optional arguments. Square brackets without a 84 | preceding modifier means that the contents is optional. So in this 85 | case, either no argument must be given, or if one argument is given it 86 | is a, or if two arguments are given, it is a and b in that order. 87 | 88 | ...optionals 89 | ....arg: a: int+ 90 | ....arg: b: string+ 91 | 92 | ..method:: m_alt 93 | ...d: A method with alternative arguments. 94 | An 95 | ....sup: 96 | .....strong: alt: 97 | 98 | ....t: before the bracket is a modifier that means that there is a 99 | choice of alternatives. The argument is required and should be either 100 | an int+, string+ or list+. 101 | 102 | ...alt 103 | ....arg: a: int+ 104 | .....d: Description for an int argument. 105 | ....arg: a: string+ 106 | .....d: Description for a string argument. 107 | ....arg: a: list+ 108 | .....d: Description for a list argument. 109 | 110 | ..method:: m_repeat 111 | ...d: A method with repeated arguments. 112 | A modifier 113 | ....sup: 114 | .....strong: m..n: 115 | 116 | ....t: before the argument, where m and n are integers, means an 117 | argument that may be repeated at least m times and at most n 118 | times. Instead of n, * may be specified and means any number of times. 119 | 120 | ...repeat: 0..* 121 | ....arg: a: int+ 122 | 123 | .....d: This argument may be repeated any number of times as long as 124 | it is of of kind int+. 125 | 126 | ...repeat: 2..4 127 | ....arg: b: string+ 128 | .....d: The final arguments must be of kind string+ and be repeated 2, 129 | 3 or 4 times. 130 | 131 | ..method:: m_draw_keywords 132 | ...d: A method with optional keyword arguments. 133 | ...d: The modifier 134 | ....sup 135 | .....strong: draw: 136 | 137 | ....t: means to 'draw' any combination of arguments from within the 138 | brackets. Keyword arguments by them self would not be optional, but 139 | would be mandatory, in the current specification system. 140 | 141 | ...draw 142 | ....key arg: a : int+ 143 | ....key arg: b : int+ 144 | ....key arg: c : string+ 145 | 146 | ..method:: range 147 | ...d: A method combining different argument specifications. 148 | 149 | ...d: The modifier 150 | ....sup 151 | .....strong: seq 152 | 153 | ....t: means arguments that are specified by the 154 | ....em: sequence 155 | ....t: of arguments within the following brackets. 156 | 157 | ....p: Create a range of numbers. 158 | ...alt 159 | ....arg: stop: int+ 160 | ....seq 161 | .....arg: start: int+ 162 | ......d: The first value of the range. 163 | ......default: 0 164 | Starts with first value. 165 | .....arg: stop: int+ 166 | ......d: The value just beyond the last value of the range. 167 | .....optionals 168 | ......arg: step: int+ 169 | .......d: Positive or negative, steps values up or down. 170 | .......default: 1 171 | ...returns: list_of_integers 172 | ....d: a list containing an arithmetic progression of integers. 173 | 174 | .document: docexample 175 | ..output: html, tester 176 | ..man page of: list_of_integers 177 | ..man page of: example_kind 178 | ..test of: example_kind 179 | 180 | 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guppy 3 2 | [![Build Status](https://img.shields.io/github/actions/workflow/status/zhuyifei1999/guppy3/workflow.yml?branch=master)](https://github.com/zhuyifei1999/guppy3/actions/workflows/workflow.yml) [![Codecov](https://img.shields.io/codecov/c/github/zhuyifei1999/guppy3)](https://codecov.io/gh/zhuyifei1999/guppy3) [![PyPI version](https://img.shields.io/pypi/v/guppy3)](https://pypi.org/project/guppy3/) [![Repology - Repositories](https://img.shields.io/repology/repositories/python:guppy3)](https://repology.org/project/python:guppy3/versions) [![PyPI - Downloads](https://img.shields.io/pypi/dm/guppy3)](https://pypistats.org/packages/guppy3) 3 | [![PyPI - Implementation](https://img.shields.io/pypi/implementation/guppy3)](https://pypi.org/project/guppy3/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/guppy3)](https://pypi.org/project/guppy3/) [![PyPI - License](https://img.shields.io/pypi/l/guppy3)](https://github.com/zhuyifei1999/guppy3/blob/master/LICENSE) [![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](https://github.com/zhuyifei1999/guppy3/issues) 4 | 5 | A Python Programming Environment & Heap analysis toolset. 6 | 7 | This package contains the following subpackages: 8 | * etc - Support modules. Contains especially the Glue protocol module. 9 | * gsl - The Guppy Specification Language implementation. This can be used 10 | to create documents and tests from a common source. 11 | * heapy - The heap analysis toolset. It can be used to find information about 12 | the objects in the heap and display the information in various ways. 13 | * sets - Bitsets and 'nodesets' implemented in C. 14 | 15 | Guppy 3 is a fork of Guppy-PE, created by Sverker Nilsson for Python 2. 16 | 17 | ## Requirements 18 | 19 | You should have Python 3.10, 3.11, 3.12, 3.13, or 3.14. This package is CPython 20 | only; PyPy and other Python implementations are not supported. Python 2 support 21 | can be obtained from [guppy-pe](http://guppy-pe.sourceforge.net/) by 22 | Sverker Nilsson, from which this package is forked. 23 | 24 | To use the graphical browser, Tkinter is needed. 25 | 26 | Free-threaded CPython is not supported, due to the excessive complexity and 27 | overhead to lock and unlock every object in the interpreter, including objects 28 | that are CPython implementation details. (Maybe `_PyEval_EnableGILTransient` 29 | could be a solution to this problem, but it is currently not exposed.) 30 | 31 | ## Installation 32 | 33 | Install with pip by: 34 | 35 | ``` 36 | pip install guppy3 37 | ``` 38 | 39 | Install with conda by: 40 | ``` 41 | conda install -c conda-forge guppy3 42 | ``` 43 | 44 | ## Usage 45 | 46 | The following example shows 47 | 48 | 1. How to create the session context: `h=hpy()` 49 | 2. How to show the reachable objects in the heap: `h.heap()` 50 | 4. How to show the shortest paths from the root to the single largest object: `h.heap().byid[0].sp` 51 | 3. How to create and show a set of objects: `h.iso(1,[],{})` 52 | 53 | ```python 54 | >>> from guppy import hpy; h=hpy() 55 | >>> h.heap() 56 | Partition of a set of 30976 objects. Total size = 3544220 bytes. 57 | Index Count % Size % Cumulative % Kind (class / dict of class) 58 | 0 8292 27 739022 21 739022 21 str 59 | 1 7834 25 625624 18 1364646 39 tuple 60 | 2 2079 7 300624 8 1665270 47 types.CodeType 61 | 3 400 1 297088 8 1962358 55 type 62 | 4 4168 13 279278 8 2241636 63 bytes 63 | 5 1869 6 269136 8 2510772 71 function 64 | 6 400 1 228464 6 2739236 77 dict of type 65 | 7 79 0 139704 4 2878940 81 dict of module 66 | 8 1061 3 93368 3 2972308 84 types.WrapperDescriptorType 67 | 9 172 1 81712 2 3054020 86 dict (no owner) 68 | <89 more rows. Type e.g. '_.more' to view.> 69 | >>> h.heap().byid[0].sp 70 | 0: h.Root.i0_modules['os'].__dict__ 71 | >>> h.iso(1,[],{}) 72 | Partition of a set of 3 objects. Total size = 348 bytes. 73 | Index Count % Size % Cumulative % Kind (class / dict of class) 74 | 0 1 33 248 71 248 71 dict (no owner) 75 | 1 1 33 72 21 320 92 list 76 | 2 1 33 28 8 348 100 int 77 | >>> 78 | ``` 79 | 80 | People have written awesome posts on how to use this toolset, including: 81 | * [How to use guppy/heapy for tracking down memory usage](https://smira.ru/wp-content/uploads/2011/08/heapy.html) 82 | * [Debugging Django memory leak with TrackRefs and Guppy](https://opensourcehacker.com/2008/03/07/debugging-django-memory-leak-with-trackrefs-and-guppy/) 83 | * [Diagnosing Memory "Leaks" in Python](https://chase-seibert.github.io/blog/2013/08/03/diagnosing-memory-leaks-python.html) 84 | * [Digging into python memory issues in ckan with heapy](https://joetsoi.github.io/debugging-python-ckan-memory-issues/) 85 | * [Optimizing memory usage in Python: a case study](https://web.archive.org/web/20230614181724/https://guillaume.segu.in/blog/code/487/optimizing-memory-usage-in-python-a-case-study/) 86 | * [Memory profiling in python](https://quantlane.com/blog/python-memory-profiling/) 87 | * [guppy/heapy - Profile Memory Usage in Python](https://coderzcolumn.com/tutorials/python/guppy-heapy-profile-memory-usage-in-python) 88 | 89 | Formal and API documentation are [also available](https://zhuyifei1999.github.io/guppy3/). 90 | 91 | ## Contributing 92 | 93 | Issues and pull requests are welcome. You may also ask for help on using this 94 | toolset; however, in such cases, we will only provide guidance, and not profile 95 | your code for you. 96 | 97 | Please make sure to update tests as appropriate. 98 | 99 | ### Testing 100 | 101 | To test if the heapy build and installation was ok, you can do: 102 | 103 | ```python 104 | >>> from guppy import hpy 105 | >>> hpy().test() 106 | Testing sets 107 | Test #0 108 | Test #1 109 | Test #2 110 | ... 111 | ``` 112 | 113 | There will be several more tests. Some tests may take a while. 114 | 115 | ## License 116 | 117 | Copyright (C) 2005-2013 Sverker Nilsson, S. Nilsson Computer System AB 118 | Copyright (C) 2019-2021 YiFei Zhu 119 | Copyright (C) 2021-2025 YiFei Zhu, Google LLC 120 | 121 | The right is granted to copy, use, modify and redistribute this code 122 | according to the rules in what is commonly referred to as an MIT 123 | license. 124 | 125 | This is not an official Google product. 126 | 127 | *** USE AT YOUR OWN RISK AND BE AWARE THAT THIS IS AN EARLY RELEASE *** 128 | -------------------------------------------------------------------------------- /src/heapy/hv_cli_prod.c: -------------------------------------------------------------------------------- 1 | /* Implementation of the 'prod' classifier */ 2 | 3 | PyDoc_STRVAR(hv_cli_prod_doc, 4 | "HV.cli_prod(memo) -> ObjectClassifier\n" 5 | "\n" 6 | "Return a classifier that classifes by \"producer\".\n" 7 | "\n" 8 | "The classification of an object is the file name and line number\n" 9 | "in which the object is produced (allocated).\n" 10 | "\n" 11 | " memo A dict object used to\n" 12 | " memoize the classification sets.\n" 13 | ); 14 | 15 | #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 11 16 | # define Py_BUILD_CORE 17 | # undef _PyObject_LookupSpecial 18 | /* _PyType_PreHeaderSize */ 19 | # include 20 | # undef Py_BUILD_CORE 21 | #endif 22 | 23 | // The sizeof of PyGC_Head is not to be trusted upon even across Python minor 24 | // releases. Eg: python/cpython@8766cb7 25 | static Py_ssize_t sizeof_PyGC_Head; 26 | 27 | #define INTERNAL_MODULE "_testinternalcapi" 28 | 29 | static void lazy_init_hv_cli_prod(void) 30 | { 31 | if (sizeof_PyGC_Head) 32 | return; 33 | 34 | if (PyLong_AsLong(PySys_GetObject("hexversion")) == PY_VERSION_HEX) { 35 | sizeof_PyGC_Head = sizeof(PyGC_Head); 36 | return; 37 | } 38 | 39 | PyObject *_testcapimodule, *_testcapi_SIZEOF_PYGC_HEAD = NULL; 40 | 41 | _testcapimodule = PyImport_ImportModule(INTERNAL_MODULE); 42 | if (!_testcapimodule) 43 | goto Err; 44 | 45 | _testcapi_SIZEOF_PYGC_HEAD = PyObject_GetAttrString( 46 | _testcapimodule, "SIZEOF_PYGC_HEAD"); 47 | if (!_testcapi_SIZEOF_PYGC_HEAD) 48 | goto Err; 49 | 50 | sizeof_PyGC_Head = PyLong_AsSsize_t(_testcapi_SIZEOF_PYGC_HEAD); 51 | if (sizeof_PyGC_Head < 0) 52 | goto Err; 53 | 54 | Py_DECREF(_testcapimodule); 55 | Py_DECREF(_testcapi_SIZEOF_PYGC_HEAD); 56 | return; 57 | 58 | Err: 59 | Py_XDECREF(_testcapimodule); 60 | Py_XDECREF(_testcapi_SIZEOF_PYGC_HEAD); 61 | 62 | PyErr_Clear(); 63 | sizeof_PyGC_Head = sizeof(PyGC_Head); 64 | PyErr_WarnFormat(PyExc_UserWarning, 1, 65 | "Unable to determine sizeof(PyGC_Head) from " 66 | INTERNAL_MODULE ".SIZEOF_PYGC_HEAD, assuming %zd", 67 | sizeof_PyGC_Head); 68 | } 69 | 70 | typedef struct { 71 | PyObject_VAR_HEAD 72 | NyHeapViewObject *hv; 73 | PyObject *memo; 74 | } ProdObject; 75 | 76 | static PyObject * 77 | hv_cli_prod_memoized_kind(ProdObject * self, PyObject *kind) 78 | { 79 | PyObject *result = PyDict_GetItem(self->memo, kind); 80 | if (!result) { 81 | if (PyErr_Occurred()) 82 | goto Err; 83 | if (PyDict_SetItem(self->memo, kind, kind) == -1) 84 | goto Err; 85 | result = kind; 86 | } 87 | Py_INCREF(result); 88 | return result; 89 | Err: 90 | return 0; 91 | } 92 | 93 | static PyObject * 94 | hv_cli_prod_classify(ProdObject *self, PyObject *obj) 95 | { 96 | PyObject *result; 97 | PyObject *kind = NULL, *tb = NULL; 98 | Py_uintptr_t ptr; 99 | 100 | // Refer to _tracemalloc.c:_tracemalloc__get_object_traceback 101 | if (PyType_IS_GC(Py_TYPE(obj))) { 102 | ptr = (Py_uintptr_t)((char *)obj - sizeof_PyGC_Head); 103 | } else { 104 | ptr = (Py_uintptr_t)obj; 105 | } 106 | 107 | #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 11 108 | // https://github.com/python/cpython/issues/101430 109 | ptr -= _PyType_PreHeaderSize(Py_TYPE(obj)); 110 | // _PyType_PreHeaderSize would add an extra compile-time sizeof(PyGC_Head), 111 | // which may be different from the true value in sizeof_PyGC_Head 112 | if (PyType_IS_GC(Py_TYPE(obj))) 113 | ptr += sizeof(PyGC_Head); 114 | #endif 115 | 116 | tb = _PyTraceMalloc_GetTraceback(0, (Py_uintptr_t)ptr); 117 | 118 | if (!tb) 119 | goto Err; 120 | 121 | if (PySequence_Check(tb) && PySequence_Length(tb)) { 122 | kind = PySequence_GetItem(tb, 0); 123 | } else { 124 | kind = Py_None; 125 | Py_INCREF(Py_None); 126 | } 127 | 128 | if (!kind) 129 | goto Err; 130 | 131 | result = hv_cli_prod_memoized_kind(self, kind); 132 | Py_DECREF(tb); 133 | Py_DECREF(kind); 134 | return result; 135 | 136 | Err: 137 | Py_XDECREF(tb); 138 | Py_XDECREF(kind); 139 | return 0; 140 | } 141 | 142 | static int 143 | hv_cli_prod_le(PyObject * self, PyObject *a, PyObject *b) 144 | { 145 | if (a == Py_None || b == Py_None) 146 | return a == Py_None && b == Py_None; 147 | 148 | if (!PyTuple_Check(a) || !PyTuple_Check(b)) 149 | return 0; 150 | 151 | Py_ssize_t i; 152 | PyObject *a_elem, *b_elem; 153 | for (i = 0; i < 2; i++) { 154 | a_elem = PyTuple_GetItem(a, i); 155 | b_elem = PyTuple_GetItem(b, i); 156 | if (!a_elem || !b_elem) 157 | return -1; 158 | 159 | if (a_elem == Py_None || b_elem == Py_None) 160 | continue; 161 | 162 | int k = PyObject_RichCompareBool(a_elem, b_elem, Py_EQ); 163 | if (k < 0) 164 | return k; 165 | if (k) 166 | continue; 167 | 168 | switch (i) { 169 | case 0: 170 | // filename: a.startswith(b) 171 | if (!PySequence_Check(a_elem) || !PySequence_Check(b_elem)) 172 | return 0; 173 | 174 | Py_ssize_t len = PySequence_Length(b_elem); 175 | if (len < 0) 176 | return len; 177 | PyObject *substr = PySequence_GetSlice(a_elem, 0, len); 178 | if (!substr) 179 | return -1; 180 | k = PyObject_RichCompareBool(substr, b_elem, Py_EQ); 181 | Py_DECREF(substr); 182 | break; 183 | case 1: 184 | // lineno 185 | k = PyObject_RichCompareBool(a_elem, b_elem, Py_LE); 186 | break; 187 | } 188 | 189 | if (k <= 0) 190 | return k; 191 | } 192 | 193 | return 1; 194 | } 195 | 196 | static NyObjectClassifierDef hv_cli_prod_def = { 197 | 0, 198 | sizeof(NyObjectClassifierDef), 199 | "hv_cli_prod", 200 | "classifier returning object producer", 201 | (binaryfunc)hv_cli_prod_classify, 202 | (binaryfunc)hv_cli_prod_memoized_kind, 203 | hv_cli_prod_le 204 | }; 205 | 206 | static PyObject * 207 | hv_cli_prod(NyHeapViewObject *self, PyObject *args) 208 | { 209 | PyObject *r, *memo; 210 | ProdObject *s; 211 | if (!PyArg_ParseTuple(args, "O!:cli_prod", 212 | &PyDict_Type, &memo)) 213 | return NULL; 214 | 215 | lazy_init_hv_cli_prod(); 216 | 217 | s = NYTUPLELIKE_NEW(ProdObject); 218 | if (!s) 219 | return 0; 220 | s->hv = self; 221 | Py_INCREF(s->hv); 222 | s->memo = memo; 223 | Py_INCREF(memo); 224 | r = NyObjectClassifier_New((PyObject *)s, &hv_cli_prod_def); 225 | Py_DECREF(s); 226 | return r; 227 | } 228 | -------------------------------------------------------------------------------- /src/heapy/horizon.c: -------------------------------------------------------------------------------- 1 | /* Implementation of the Horizon type */ 2 | 3 | char horizon_doc[]= 4 | "Horizon(X:iterable)\n" 5 | "\n" 6 | "Create a new Horizon object from X. \n" 7 | "\n" 8 | "The objects in X will be used to initialize a set of objects within\n" 9 | "the Horizon object. There are no official references to these objects,\n" 10 | "but as some of these objects become deallocated, they will be removed\n" 11 | "from the set of objects within the Horizon object. The objects within\n" 12 | "the set of objects within the Horizon object can be compared to\n" 13 | "another set of objects via the news() method. This can be used to see\n" 14 | "what objects have been allocated but not deallocated since the Horizon\n" 15 | "object was created.\n" 16 | ; 17 | 18 | 19 | 20 | typedef struct _NyHorizonObject { 21 | PyObject_HEAD 22 | struct _NyHorizonObject *next; 23 | NyNodeSetObject *hs; 24 | } NyHorizonObject; 25 | 26 | /* Horizon Management 27 | The struct rm must be a static/global singleton, since it is intimately bound to patching 28 | */ 29 | 30 | static struct { 31 | NyHorizonObject *horizons; 32 | PyObject *types; 33 | } rm; 34 | 35 | static void horizon_patched_dealloc(PyObject *v); 36 | 37 | static destructor 38 | horizon_get_org_dealloc(PyTypeObject *t) 39 | { 40 | if (!rm.types && t->tp_dealloc != horizon_patched_dealloc) 41 | return t->tp_dealloc; 42 | 43 | PyObject *d = PyDict_GetItem(rm.types, (PyObject *)t); 44 | if (d) 45 | return (destructor)PyLong_AsSsize_t(d); 46 | 47 | Py_FatalError("horizon_get_org_dealloc: no original destructor found"); 48 | } 49 | 50 | static void 51 | horizon_remove(NyHorizonObject *v) 52 | { 53 | NyHorizonObject **p; 54 | for (p = &rm.horizons; *p != v; p = &((*p)->next)) { 55 | if (!*p) 56 | Py_FatalError("horizon_remove: no such horizon found"); 57 | } 58 | *p = v->next; 59 | if (!rm.horizons && rm.types) { 60 | Py_ssize_t i = 0; 61 | PyObject *pk, *pv; 62 | while (PyDict_Next(rm.types, &i, &pk, &pv)) { 63 | ((PyTypeObject *)pk)->tp_dealloc = (destructor)PyLong_AsSsize_t(pv); 64 | } 65 | Py_DECREF(rm.types); 66 | rm.types = 0; 67 | } 68 | } 69 | 70 | 71 | static void 72 | horizon_dealloc(NyHorizonObject *rg) 73 | { 74 | horizon_remove(rg); 75 | Py_XDECREF(rg->hs); 76 | Py_TYPE(rg)->tp_free((PyObject *)rg); 77 | } 78 | 79 | 80 | static PyTypeObject * 81 | horizon_base(PyObject *v) 82 | { 83 | PyTypeObject *t = Py_TYPE(v); 84 | while (t->tp_flags & Py_TPFLAGS_HEAPTYPE) { 85 | assert(t->tp_base); 86 | assert(Py_TYPE(t) == Py_TYPE(t->tp_base) || 87 | PyObject_IsSubclass((PyObject *)Py_TYPE(t), (PyObject *)Py_TYPE(t->tp_base))); 88 | t = t->tp_base; 89 | } 90 | return t; 91 | } 92 | 93 | 94 | static void 95 | horizon_patched_dealloc(PyObject *v) 96 | { 97 | NyHorizonObject *r; 98 | for (r = rm.horizons; r; r = r->next) { 99 | if (NyNodeSet_clrobj(r->hs, v) == -1) 100 | Py_FatalError("horizon_patched_dealloc: could not clear object in nodeset"); 101 | } 102 | horizon_get_org_dealloc(horizon_base(v))(v); 103 | } 104 | 105 | static int 106 | horizon_patch_dealloc(PyTypeObject *t) 107 | { 108 | PyObject *org; 109 | if (!rm.types) { 110 | rm.types = PyDict_New(); 111 | if (!rm.types) 112 | return -1; 113 | } 114 | if (!(org = PyLong_FromSsize_t((Py_ssize_t)t->tp_dealloc))) 115 | return -1; 116 | if (PyDict_SetItem(rm.types, (PyObject *)t, org) == -1) { 117 | Py_DECREF(org); 118 | return -1; 119 | } 120 | t->tp_dealloc = horizon_patched_dealloc; 121 | Py_DECREF(org); 122 | return 0; 123 | } 124 | 125 | static int 126 | horizon_update_trav(PyObject *obj, NyHorizonObject *ta) { 127 | int r; 128 | r = NyNodeSet_setobj(ta->hs, obj); 129 | if (!r) { 130 | PyTypeObject *t = horizon_base(obj); 131 | if (t->tp_dealloc != horizon_patched_dealloc) { 132 | if (horizon_patch_dealloc(t) == -1) { 133 | return -1; 134 | } 135 | } 136 | } 137 | if (r == -1) 138 | return -1; 139 | return 0; 140 | } 141 | 142 | PyObject * 143 | horizon_new(PyTypeObject *type, PyObject *args, PyObject *kwds) 144 | { 145 | 146 | PyObject *X; 147 | NyHorizonObject *hz = 0; 148 | static char *kwlist[] = {"X", 0}; 149 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:Horizon.__new__", 150 | kwlist, 151 | &X)) 152 | goto err; 153 | hz = (NyHorizonObject *)type->tp_alloc(type, 1); 154 | if (!hz) 155 | goto err; 156 | hz->next = rm.horizons; 157 | rm.horizons = hz; 158 | hz->hs = NyMutNodeSet_NewFlags(0); /* I.E. not NS_HOLDOBJECTS */ 159 | if (!hz->hs) 160 | goto err; 161 | if (iterable_iterate((PyObject *)X, (visitproc)horizon_update_trav, hz) == -1 || 162 | horizon_update_trav((PyObject *)hz, hz) == -1) 163 | goto err; 164 | return (PyObject *)hz; 165 | err: 166 | Py_XDECREF(hz); 167 | return 0; 168 | 169 | } 170 | 171 | typedef struct { 172 | NyHorizonObject *rg; 173 | NyNodeSetObject *result; 174 | } NewsTravArg; 175 | 176 | 177 | static int 178 | horizon_news_trav(PyObject *obj, NewsTravArg *ta) 179 | { 180 | if (!(NyNodeSet_hasobj(ta->rg->hs, obj))) 181 | if (NyNodeSet_setobj(ta->result, obj) == -1) 182 | return -1; 183 | return 0; 184 | } 185 | 186 | static char news_doc[] = 187 | "H.news(X:iterable) -> NodeSet\n" 188 | "\n" 189 | "Return the set of objects in X that is not in the set of objects of H.\n" 190 | "\n" 191 | "If H was created from the contents of the heap at a particular time,\n" 192 | "H.news(X) will return the set of objects in X that were allocated\n" 193 | "after H was created.\n" 194 | ; 195 | 196 | 197 | static PyObject * 198 | horizon_news(NyHorizonObject *self, PyObject *arg) 199 | { 200 | NewsTravArg ta; 201 | ta.rg = self; 202 | ta.result = NyMutNodeSet_New(); 203 | if (!(ta.result)) 204 | goto err; 205 | if (iterable_iterate(arg, (visitproc)horizon_news_trav, &ta) == -1) 206 | goto err; 207 | return (PyObject *)ta.result; 208 | err: 209 | Py_XDECREF(ta.result); 210 | return 0; 211 | } 212 | 213 | 214 | static PyMethodDef horizon_methods[] = { 215 | {"news", (PyCFunction)horizon_news, METH_O, news_doc}, 216 | {0} /* sentinel */ 217 | }; 218 | 219 | PyTypeObject NyHorizon_Type = { 220 | PyVarObject_HEAD_INIT(NULL, 0) 221 | .tp_name = "guppy.heapy.heapyc.Horizon", 222 | .tp_basicsize = sizeof(NyHorizonObject), 223 | .tp_dealloc = (destructor)horizon_dealloc, 224 | .tp_getattro = PyObject_GenericGetAttr, 225 | .tp_flags = Py_TPFLAGS_DEFAULT, 226 | .tp_doc = horizon_doc, 227 | .tp_methods = horizon_methods, 228 | .tp_alloc = PyType_GenericAlloc, 229 | .tp_new = horizon_new, 230 | .tp_free = PyObject_Del, 231 | }; 232 | -------------------------------------------------------------------------------- /specs/gsl.gsl: -------------------------------------------------------------------------------- 1 | .document: gsl 2 | ..output: html 3 | 4 | ..h1: Guppy Specification Language 5 | 6 | ..ul 7 | ...li 8 | ....a: Generated document example 9 | .....href=docexample.html 10 | ...li 11 | ....a: Kinds 12 | .....href=#kinds 13 | ...li 14 | ....a: Example 15 | .....href=#example 16 | ...li 17 | ....a: Emacs editing mode 18 | .....href=#emacsmode 19 | 20 | ..p: GSL is an evolving specification language, which is a part of the 21 | ...a: Guppy-PE 22 | ....href= index.html 23 | ...t: programming environment. I started experimenting with this 24 | language because I felt the need to have a way to specify 25 | documentation and tests from the same source. GSL can describe aspects 26 | of a system, especially its API, in a way that can be automatically 27 | converted to tests as well as to documents. The documents generated 28 | have a formal structure for describing the formal aspects of the 29 | specification, complemented with descriptive text from the same source 30 | documents. A language that is similar in intent to GSL, is the 31 | 32 | ...a: http://adl.opengroup.org 33 | ....href=http://adl.opengroup.org/ 34 | ...t: Assertion Definition Language. 35 | 36 | ..p: Generating tests automatically is a quite hard problem 37 | generally. The programmer only may know best what should be tested. 38 | At the very least, however, GSL can check that some aspects of the 39 | documentation are in fact correct. If an object is specified to have 40 | an attribute, it can be tested. If the kind (type) of the attribute is 41 | also specified, it can also be tested, as far as the kind of the 42 | attribute is specifed, and as far as it can be done within physical 43 | limits when there are circularities and combinatorial explosions. 44 | 45 | ..p: It is possible to use GSL to write documents that have no formal 46 | connection to program semantics. It supplies a syntax that can proivde 47 | a 1-1 correspondence with HTML (and XML), but is often easier to read 48 | and write (IMHO). The syntax is a simple syntax based on dotted 49 | indentation. There is a well defined 1-1 correspondence with a tree 50 | structure. 51 | 52 | ..p: This release of Guppy is not primarily concerned with GSL which 53 | is still in a very prototypical state, it awaits probably the first 54 | major refactoring. However, since the documentation of Heapy is 55 | generated by GSL, something deserves to be said about the kind of 56 | documents it has generated. 57 | 58 | ..p: I think what wants to be explained at this point is the following. 59 | 60 | ..a 61 | ...name=kinds 62 | ...h3: Kinds 63 | 64 | ..p: I am generally using the word 'kind' to stand for something that is 65 | not a type but is specified in some other way. This is 66 | because I don't want to say type and class when I am not exactly 67 | referring to a type, since it would be confusing if some 68 | people think it means a Python type, someone else think it 69 | means something else. The word 'kind' means just a kind, it is not 70 | defined what a kind is, except for what you might think it means in 71 | ordinary language. 72 | 73 | ..p: Maybe a 'kind' is quite the same as what is otherwise often 74 | called an 'interface'. Well, I am using the word 'kind' in that case 75 | anyway since it is shorter and easier to read and write. 76 | 77 | ..p: In GSL, a 'kind' starts as just a name for something without 78 | any properties at all. You can however add properties to a kind 79 | by specifying something about it. You may for example say 80 | 81 | ..pre 82 | \.and: mykind 83 | \..attr:: attributename 84 | ..c: end pre 85 | 86 | ..p: This means you have added an aspect to mykind. It now means that it is 87 | a (Python) object having an attribute named attributename. A test can 88 | be generated from this specification. It will check an object claimed 89 | to be of kind mykind, to make sure it really has an attribute named 90 | attributename. 91 | 92 | ..p: You can also add properties saying that a kind of objects or 93 | attributes is callable, what kind of parameters it can take, and what 94 | kind of return value it will deliver. The parameters can be specified 95 | to be optional, named, or repeated in varous ways. 96 | 97 | ..a 98 | ...name=example 99 | ...h3: Example 100 | 101 | ..p: This 102 | ...a: GSL example 103 | ....href=gslexample.html 104 | ...t: contains the source code and the generated test code for the 105 | ....a: generated document 106 | .....href=docexample.html 107 | ...t:example. The generated document intends to illustrate different kinds 108 | of parameter specifications. 109 | 110 | 111 | ..a 112 | ...h3: Emacs mode 113 | ...name=emacsmode 114 | 115 | ..p: There is an Emacs mode that supports editing of GSL files. It is 116 | based on the Python mode. It indents with dots instead of 117 | spaces. The mode file "gsl-mode-0.1.el" is in the distribution top 118 | level directory and is not automatically installed. 119 | 120 | ..p: The following is taken from its builtin help texts. 121 | ..pre 122 | GSL mode: 123 | Major mode for editing GSL files. 124 | To submit a problem report, enter `C-c C-b' from a 125 | `gsl-mode' buffer. Do `C-c ?' for detailed documentation. 126 | 127 | This mode knows about GSL dotted indentation. 128 | Paragraphs are separated by blank lines only. 129 | 130 | COMMANDS 131 | key binding 132 | --- ------- 133 | 134 | C-c Prefix Command 135 | 136 | C-c C-v gsl-mode-version 137 | C-c C-b gsl-submit-bug-report 138 | C-c ? gsl-describe-mode 139 | C-c C-r gsl-shift-region-right 140 | C-c > gsl-shift-region-right 141 | C-c C-l gsl-shift-region-left 142 | C-c < gsl-shift-region-left 143 | 144 | 145 | KINDS OF LINES 146 | 147 | Each physical line in the file is either a `markup line' 148 | (the line starts with a dot character '.') or a `text line' 149 | (the line starts with some other character). Text lines starting 150 | with a dot may be entered by quoting by a backslash ('\')). 151 | 152 | INDENTATION 153 | 154 | Unlike most programming languages, GSL uses indentation, and only 155 | indentation, to specify block structure. Unlike other programming 156 | languages the indentation is not based on blanks but on another 157 | special character; currently this is fixed to be the '.' character. 158 | The indentation that can be supplied automatically by GSL-mode is 159 | just a guess: only you know the block structure you intend, so only 160 | you can supply correct indentation. 161 | 162 | Primarily for entering new code: 163 | 164 | TAB indent line appropriately 165 | LFD insert newline, then indent 166 | 167 | The TAB and LFD keys will indent the current line to reproduce 168 | the same indentation as the closest preceding markup line. 169 | 170 | Primarily for reindenting existing code: 171 | 172 | C-c C-l shift region left 173 | C-c C-r shift region right 174 | 175 | The indentation of the markup lines in the region is changed by +/- 1 176 | or the argument given. Text lines in the region will not be changed. 177 | 178 | OTHER COMMANDS 179 | 180 | Use C-c C-v to see the current version of gsl-mode. 181 | 182 | Use C-c C-b to submit a bug report or enhancement proposal. 183 | 184 | This text is displayed via the C-c ? command. 185 | 186 | HOOKS 187 | 188 | Entering GSL mode calls with no arguments the value of the variable 189 | `gsl-mode-hook', if that value exists and is not nil; see the `Hooks' 190 | section of the Elisp manual for details. 191 | ..c: end pre 192 | -------------------------------------------------------------------------------- /src/heapy/hv_cli_dictof.c: -------------------------------------------------------------------------------- 1 | /* Implementation of the 'dictof' classifier 2 | It is like clodo but classifies non-dicts to None. 3 | And has an argument that classifies the owners. 4 | */ 5 | 6 | PyDoc_STRVAR(hv_cli_dictof_doc, 7 | "HV.cli_dictof(owners, ownerclassifier, notdictkind, notownedkind) -> ObjectClassifier\n" 8 | "\n" 9 | "Return a classifier, that classifies by \"Dict Owner\".\n" 10 | "\n" 11 | "The classification of an object is the notdictkind,\n" 12 | "unless the object is a dict object. If the dict is 'owned' by some owner,\n" 13 | "the classification will be \n" 14 | "the class (as by the ownerclass argument ) of its owner.\n" 15 | "If it is not owned, the returned kind will be notowned argument.\n" 16 | "Arguments:\n" 17 | "\n" 18 | " owners A NodeGraph object used to map each dict object to\n" 19 | " its owner, or to None if it has no owner. The\n" 20 | " graph will be automatically updated, from heap\n" 21 | " information defined by HV, whenever an attempt\n" 22 | " is made to classify a dict that maps to nothing.\n" 23 | "\n" 24 | "\n" 25 | " ownerclassifier\n" 26 | " notdictkind\n" 27 | " notownedkind\n" 28 | ); 29 | 30 | 31 | /* This macro defines the definition of a 'dict' as far as 32 | the dictof classifier is concerned. So we don't bother about 33 | subtypes - they can't be 'owned' in any standard way can they (?) 34 | */ 35 | # define DictofDict_Check(obj) (Py_TYPE(obj) == &PyDict_Type) 36 | 37 | typedef struct { 38 | PyObject_VAR_HEAD 39 | NyHeapViewObject *hv; 40 | NyNodeGraphObject *owners; 41 | NyObjectClassifierObject *ownerclassifier; 42 | PyObject *notdictkind; 43 | PyObject *notownedkind; 44 | } DictofObject; 45 | 46 | /* Code for new dict-owner update method. Notes Apr 7 2005. */ 47 | 48 | static PyObject * 49 | hv_cli_dictof_get_static_types_list(NyHeapViewObject *hv) { 50 | if (PyObject_Length(hv->static_types) == 0) { 51 | PyObject *h = hv_heap(hv, Py_None, Py_None); /* It updates static_types */ 52 | if (!h) 53 | return 0; 54 | Py_DECREF(h); 55 | } 56 | return PySequence_List(hv->static_types); 57 | } 58 | 59 | typedef struct { 60 | NyHeapViewObject *hv; 61 | NyNodeSetObject *dictsowned; 62 | NyNodeGraphObject *rg; 63 | } DictofTravArg; 64 | 65 | static int 66 | hv_cli_dictof_update_rec(PyObject *obj, DictofTravArg *ta) { 67 | if (DictofDict_Check(obj)) { 68 | int setobj = NyNodeSet_setobj(ta->dictsowned, obj); 69 | if (setobj == -1) 70 | return -1; 71 | else if (setobj == 0) 72 | if (NyNodeGraph_AddEdge(ta->rg, obj, Py_None) == -1) 73 | return -1; 74 | } 75 | return 0; 76 | } 77 | 78 | static int 79 | hv_cli_dictof_update(NyHeapViewObject *hv, NyNodeGraphObject *rg) 80 | { 81 | DictofTravArg ta; 82 | ta.hv = hv; 83 | ta.rg = rg; 84 | 85 | PyObject **dp; 86 | Py_ssize_t i, len; 87 | int k; 88 | int result = -1; 89 | PyObject *lists[2] = {0, 0}; 90 | 91 | if (!(ta.dictsowned = NyMutNodeSet_New())) goto err; 92 | if (!(lists[0] = hv_cli_dictof_get_static_types_list(hv))) goto err; 93 | if (!(lists[1] = gc_get_objects())) goto err; 94 | for (k = 0; k < 2; k++) { 95 | PyObject *objects = lists[k]; 96 | len = PyList_Size(objects); 97 | if (len == -1) /* catches eg type error */ 98 | goto err; 99 | for (i = 0; i < len; i++) { 100 | PyObject *obj = PyList_GET_ITEM(objects, i); 101 | dp = _PyObject_GetDictPtr(obj); 102 | if (dp && *dp) { 103 | if (NyNodeGraph_AddEdge(ta.rg, *dp, obj) == -1) 104 | goto err; 105 | if (NyNodeSet_setobj(ta.dictsowned, *dp) == -1) 106 | goto err; 107 | } 108 | } 109 | } 110 | for (k = 0; k < 2; k++) { 111 | PyObject *objects = lists[k]; 112 | len = PyList_Size(objects); 113 | for (i = 0; i < len; i++) { 114 | PyObject *obj = PyList_GET_ITEM(objects, i); 115 | if (DictofDict_Check(obj)) { 116 | int setobj = NyNodeSet_setobj(ta.dictsowned, obj); 117 | if (setobj == -1) 118 | goto err; 119 | else if (setobj == 0) 120 | if (NyNodeGraph_AddEdge(ta.rg, obj, Py_None) == -1) 121 | goto err; 122 | } 123 | 124 | if (PyObject_IS_GC(obj)) { 125 | if (Py_TYPE(obj)->tp_traverse( 126 | obj, (visitproc)hv_cli_dictof_update_rec, &ta) == -1) 127 | goto err; 128 | } 129 | } 130 | } 131 | result = 0; 132 | err: 133 | Py_XDECREF(ta.dictsowned); 134 | Py_XDECREF(lists[0]); 135 | Py_XDECREF(lists[1]); 136 | return result; 137 | } 138 | 139 | 140 | static PyObject * 141 | hv_cli_dictof_classify(DictofObject *self, PyObject *obj) 142 | { 143 | if (!DictofDict_Check(obj)) { 144 | Py_INCREF(self->notdictkind); 145 | return self->notdictkind; 146 | } else { 147 | NyNodeGraphEdge *lo, *hi; 148 | if (NyNodeGraph_Region(self->owners, obj, &lo, &hi) == -1) { 149 | return 0; 150 | } 151 | if (!(lo < hi)) { 152 | NyNodeGraph_Clear(self->owners); 153 | if (hv_cli_dictof_update(self->hv, self->owners) == -1) 154 | return 0; 155 | if (NyNodeGraph_Region(self->owners, obj, &lo, &hi) == -1) { 156 | return 0; 157 | } 158 | } 159 | if (lo < hi && lo->tgt != Py_None) { 160 | PyObject *ownerkind = self->ownerclassifier->def->classify 161 | (self->ownerclassifier->self, lo->tgt); 162 | return ownerkind; 163 | } else { 164 | Py_INCREF(self->notownedkind); 165 | return self->notownedkind; 166 | } 167 | } 168 | 169 | } 170 | 171 | static PyObject * 172 | hv_cli_dictof_memoized_kind(DictofObject *self, PyObject *obj) 173 | { 174 | if (self->ownerclassifier->def->memoized_kind) 175 | return self->ownerclassifier->def->memoized_kind(self->ownerclassifier->self, obj); 176 | else { 177 | Py_INCREF(obj); 178 | return obj; 179 | } 180 | } 181 | 182 | static NyObjectClassifierDef hv_cli_dictof_def = { 183 | 0, 184 | sizeof(NyObjectClassifierDef), 185 | "cli_dictof", 186 | "classifier returning ...", 187 | (binaryfunc)hv_cli_dictof_classify, 188 | (binaryfunc)hv_cli_dictof_memoized_kind, 189 | }; 190 | 191 | static PyObject * 192 | hv_cli_dictof(NyHeapViewObject *self, PyObject *args) 193 | { 194 | PyObject *r; 195 | DictofObject *s, tmp; 196 | if (!PyArg_ParseTuple(args, "O!O!OO:cli_dictof", 197 | &NyNodeGraph_Type, &tmp.owners, 198 | &NyObjectClassifier_Type,&tmp.ownerclassifier, 199 | &tmp.notdictkind, 200 | &tmp.notownedkind 201 | )) 202 | return 0; 203 | 204 | s = NYTUPLELIKE_NEW(DictofObject); 205 | if (!s) 206 | return 0; 207 | s->hv = self; 208 | Py_INCREF(s->hv); 209 | 210 | s->owners = tmp.owners; 211 | Py_INCREF(s->owners); 212 | 213 | s->ownerclassifier = tmp.ownerclassifier; 214 | Py_INCREF(s->ownerclassifier); 215 | 216 | s->notdictkind = tmp.notdictkind; 217 | Py_INCREF(s->notdictkind); 218 | 219 | s->notownedkind = tmp.notownedkind; 220 | Py_INCREF(s->notownedkind); 221 | 222 | r = NyObjectClassifier_New((PyObject *)s, &hv_cli_dictof_def); 223 | Py_DECREF(s); 224 | return r; 225 | } 226 | -------------------------------------------------------------------------------- /docs/gsl.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

11 | Guppy Specification Language 12 |

13 | 18 |

GSL is an evolving specification language, which is a part of the Guppy-PE 19 | programming environment. I started experimenting with this 20 | language because I felt the need to have a way to specify 21 | documentation and tests from the same source. GSL can describe aspects 22 | of a system, especially its API, in a way that can be automatically 23 | converted to tests as well as to documents. The documents generated 24 | have a formal structure for describing the formal aspects of the 25 | specification, complemented with descriptive text from the same source 26 | documents. A language that is similar in intent to GSL, is the 27 | http://adl.opengroup.org 28 | Assertion Definition Language. 29 |

30 |

31 | Generating tests automatically is a quite hard problem 32 | generally. The programmer only may know best what should be tested. 33 | At the very least, however, GSL can check that some aspects of the 34 | documentation are in fact correct. If an object is specified to have 35 | an attribute, it can be tested. If the kind (type) of the attribute is 36 | also specified, it can also be tested, as far as the kind of the 37 | attribute is specifed, and as far as it can be done within physical 38 | limits when there are circularities and combinatorial explosions. 39 |

40 |

41 | It is possible to use GSL to write documents that have no formal 42 | connection to program semantics. It supplies a syntax that can proivde 43 | a 1-1 correspondence with HTML (and XML), but is often easier to read 44 | and write (IMHO). The syntax is a simple syntax based on dotted 45 | indentation. There is a well defined 1-1 correspondence with a tree 46 | structure. 47 |

48 |

49 | This release of Guppy is not primarily concerned with GSL which 50 | is still in a very prototypical state, it awaits probably the first 51 | major refactoring. However, since the documentation of Heapy is 52 | generated by GSL, something deserves to be said about the kind of 53 | documents it has generated. 54 |

55 |

56 | I think what wants to be explained at this point is the following. 57 |

58 |

59 | Kinds 60 |

61 |

62 | I am generally using the word 'kind' to stand for something that is 63 | not a type but is specified in some other way. This is 64 | because I don't want to say type and class when I am not exactly 65 | referring to a type, since it would be confusing if some 66 | people think it means a Python type, someone else think it 67 | means something else. The word 'kind' means just a kind, it is not 68 | defined what a kind is, except for what you might think it means in 69 | ordinary language. 70 |

71 |

72 | Maybe a 'kind' is quite the same as what is otherwise often 73 | called an 'interface'. Well, I am using the word 'kind' in that case 74 | anyway since it is shorter and easier to read and write. 75 |

76 |

77 | In GSL, a 'kind' starts as just a name for something without 78 | any properties at all. You can however add properties to a kind 79 | by specifying something about it. You may for example say 80 |

81 |
 82 | .and: mykind
 83 | ..attr:: attributename
84 |

85 | This means you have added an aspect to mykind. It now means that it is 86 | a (Python) object having an attribute named attributename. A test can 87 | be generated from this specification. It will check an object claimed 88 | to be of kind mykind, to make sure it really has an attribute named 89 | attributename. 90 |

91 |

92 | You can also add properties saying that a kind of objects or 93 | attributes is callable, what kind of parameters it can take, and what 94 | kind of return value it will deliver. The parameters can be specified 95 | to be optional, named, or repeated in varous ways. 96 |

97 |

98 | Example 99 |

100 |

This GSL example contains the source code and the generated test code for the generated document 101 | example. The generated document intends to illustrate different kinds 102 | of parameter specifications. 103 | 104 |

105 |

Emacs mode

106 |

107 | There is an Emacs mode that supports editing of GSL files. It is 108 | based on the Python mode. It indents with dots instead of 109 | spaces. The mode file "gsl-mode-0.1.el" is in the distribution top 110 | level directory and is not automatically installed. 111 |

112 |

The following is taken from its builtin help texts.

113 |
114 | GSL mode:
115 | Major mode for editing GSL files.
116 | To submit a problem report, enter `C-c C-b' from a
117 | `gsl-mode' buffer.  Do `C-c ?' for detailed documentation.
118 | 
119 | This mode knows about GSL dotted indentation.
120 | Paragraphs are separated by blank lines only.
121 | 
122 | COMMANDS
123 | key             binding
124 | ---             -------
125 | 
126 | C-c		Prefix Command
127 | 
128 | C-c C-v		gsl-mode-version
129 | C-c C-b		gsl-submit-bug-report
130 | C-c ?		gsl-describe-mode
131 | C-c C-r		gsl-shift-region-right
132 | C-c >		gsl-shift-region-right
133 | C-c C-l		gsl-shift-region-left
134 | C-c <		gsl-shift-region-left
135 | 
136 | 
137 | KINDS OF LINES
138 | 
139 | Each physical line in the file is either a `markup line'
140 | (the line starts with a dot character '.') or a `text line'
141 | (the line starts with some other character). Text lines starting
142 | with a dot may be entered by quoting by a backslash ('\')).
143 | 
144 | INDENTATION
145 | 
146 | Unlike most programming languages, GSL uses indentation, and only
147 | indentation, to specify block structure. Unlike other programming
148 | languages the indentation is not based on blanks but on another
149 | special character; currently this is fixed to be the '.' character.
150 | The indentation that can be supplied automatically by GSL-mode is
151 | just a guess: only you know the block structure you intend, so only
152 | you can supply correct indentation.
153 | 
154 | Primarily for entering new code:
155 | 
156 | 	TAB	 indent line appropriately
157 | 	LFD	 insert newline, then indent
158 | 
159 | The TAB and LFD keys will indent the current line to reproduce
160 | the same indentation as the closest preceding markup line.
161 | 
162 | Primarily for reindenting existing code:
163 | 
164 | 	C-c C-l	 shift region left
165 | 	C-c C-r	 shift region right
166 | 
167 | The indentation of the markup lines in the region is changed by +/- 1
168 | or the argument given. Text lines in the region will not be changed.
169 | 
170 | OTHER COMMANDS
171 | 
172 | Use C-c C-v to see the current version of gsl-mode.
173 | 
174 | Use C-c C-b to submit a bug report or enhancement proposal.
175 | 
176 | This text is displayed via the C-c ? command.
177 | 
178 | HOOKS
179 | 
180 | Entering GSL mode calls with no arguments the value of the variable
181 | `gsl-mode-hook', if that value exists and is not nil; see the `Hooks'
182 | section of the Elisp manual for details.
183 |
Generated by GSL-HTML 3.1.5 on Mon Oct 20 20:07:59 2025
-------------------------------------------------------------------------------- /specs/sets.gsl: -------------------------------------------------------------------------------- 1 | .import:: CommonSet, NodeSet, NodeSet+, ImmNodeSet, MutNodeSet, iterable+, Any+, boolean, 2 | iterator, int 3 | ..from: kindnames 4 | 5 | .kind:: module_sets 6 | ..method:: mutnodeset 7 | ...optionals 8 | ....arg: elements:iterable+ 9 | ...returns: MutNodeSet 10 | ....d: a new mutable nodeset with specified elements. 11 | 12 | ..method:: immnodeset 13 | ...optionals 14 | ....arg: elements:iterable+ 15 | ...returns: ImmNodeSet 16 | ....d: a new immutable nodeset with specified elements. 17 | 18 | .and: CommonSet 19 | 20 | ..condition:: contains 21 | ...arg: x 22 | ...arg: y 23 | ...d: True if the set x contains the element y. 24 | ...python code: y in x 25 | 26 | ..condition:: empty 27 | ...self: x 28 | ...d: True if the set x is empty. 29 | ...python code: not x 30 | 31 | ..condition:: equalset 32 | ...arg: x 33 | ...arg: y 34 | ...d: True if x contains the same elements as y. 35 | ...python code: immnodeset(x) == immnodeset(y) 36 | ....in context: from guppy.sets import immnodeset 37 | 38 | ..condition:: istrue 39 | ...arg: x 40 | ...d: True if the argument is true in the Python sense. 41 | ...python code: bool(x) 42 | 43 | ..condition:: subset 44 | ...arg: x 45 | ...arg: y 46 | ...d: True if x represents a non-strict subset of y: 47 | ...d: all elements in x are also in y. 48 | ...python code: immnodeset(x) <= immnodeset(y) 49 | ....in context: from guppy.sets import immnodeset 50 | 51 | .and: NodeSet 52 | ..d 53 | A nodeset is a set of objects with equality based on heap address. 54 | 55 | ..self: x 56 | 57 | ..op: & 58 | ...d: 59 | Intersection: the set of objects that are in both x and y. 60 | ...arg: y: iterable+ 61 | ...returns: ImmNodeSet 62 | ...postcondition: CommonSet.subset(, x) 63 | ...postcondition: CommonSet.subset(, y) 64 | 65 | ..op: | 66 | ...d: 67 | Union: the set of objects that are in either x or y. 68 | ...arg: y: iterable+ 69 | ...returns: ImmNodeSet 70 | ...postcondition: CommonSet.subset(x, ) 71 | ...postcondition: CommonSet.subset(y, ) 72 | 73 | ..op: ^ 74 | ...d: 75 | Symmetric set difference: the set of objects that are in exactly one of x and y. 76 | ...arg: y: iterable+ 77 | ...returns: ImmNodeSet 78 | 79 | ..op: - 80 | ...d: 81 | Set difference: the set of objects that are in x but not in y. 82 | ...arg: y: iterable+ 83 | ...returns: ImmNodeSet 84 | 85 | ..iop: &= 86 | ...d: 87 | In-place intersection. 88 | ...arg: y: iterable+ 89 | ...returns: NodeSet 90 | ...postcondition: CommonSet.subset(, x) 91 | ...postcondition: CommonSet.subset(, y) 92 | 93 | ..iop: |= 94 | ...d: 95 | In-place union. 96 | ...arg: y: iterable+ 97 | ...returns: NodeSet 98 | ...postcondition: CommonSet.subset(x, ) 99 | ...postcondition: CommonSet.subset(y, ) 100 | 101 | ..iop: ^= 102 | ...d: 103 | In-place symmetric set difference. 104 | ...arg: y: iterable+ 105 | ...returns: NodeSet 106 | 107 | ..iop: -= 108 | ...d: 109 | In-place set difference. 110 | ...arg: y: iterable+ 111 | ...returns: NodeSet 112 | 113 | ..rop: in 114 | ...d: 115 | Inclusion test. 116 | ...arg: y: Any+ 117 | ...returns: boolean 118 | 119 | 120 | ..op: == 121 | ...d: 122 | Equal: 123 | x and y contain the same elements. 124 | 125 | ...arg: y: NodeSet+ 126 | ...returns: boolean 127 | 128 | ..op: != 129 | ...d: 130 | Not equal: 131 | x and y do not contain the same elements. 132 | 133 | ...arg: y: NodeSet+ 134 | ...returns: boolean 135 | 136 | ..op: <= 137 | ...d: 138 | Subset, non-strict: 139 | all elements in x are also in y. 140 | ...arg: y: NodeSet+ 141 | ...returns: boolean 142 | 143 | ..op: < 144 | ...d: 145 | Subset, strict: 146 | all elements in x are also in y, 147 | and y contains some element not in x. 148 | 149 | ...arg: y: NodeSet+ 150 | ...returns: boolean 151 | 152 | ..op: >= 153 | ...d: 154 | Superset, non-strict: 155 | all elements in y are also in x. 156 | 157 | ...arg: y: NodeSet+ 158 | ...returns: boolean 159 | 160 | ..op: > 161 | ...d: 162 | Superset, strict: 163 | all elements in y are also in x, 164 | and x contains some element not in y. 165 | 166 | ...arg: y: NodeSet+ 167 | ...returns: boolean 168 | 169 | ..fop: iter 170 | ...d: Iteration 171 | ...returns: iterator 172 | ....d:an iterator yielding the elements of x. 173 | ....d:(The order is implementation dependent.) 174 | ...postcondition: CommonSet.equalset(, x) 175 | 176 | ..fop: len 177 | ...d: Length 178 | ...returns: int 179 | ....d:the number of elements in x. 180 | 181 | .and: MutNodeSet 182 | ..d: A mutable nodeset is a nodeset object that can be updated in place. 183 | ..subkind of: NodeSet 184 | ...d: All operations from the NodeSet kind are inherited. 185 | ...d: The in-place operators (&=, |= etc) update the target set in place 186 | and return the same object. 187 | ...d: It is unspecified what happens when trying to update a mutable nodeset 188 | for which an iterator object (from the iter() function) is active. 189 | 190 | ..self: S 191 | 192 | ..constructor: module_sets.mutnodeset 193 | 194 | ..attr:: add 195 | ...mapping 196 | ....d: Add e to S; no effect if e was already in S. 197 | ....arg: e:Any+ 198 | ....postcondition: CommonSet.contains(S, e) 199 | ....postcondition: not CommonSet.empty(S) 200 | 201 | ..attr:: append 202 | ...mapping 203 | ....d: Add e to S, or raise ValueError if e was already in S. 204 | ....arg: e:Any+ 205 | ....precondition: not CommonSet.contains(S, e) 206 | ....postcondition: CommonSet.contains(S, e) 207 | ....postcondition: not CommonSet.empty(S) 208 | 209 | ..attr:: clear 210 | ...mapping 211 | ....d: Remove all elements from S, and compact its storage. 212 | ....postcondition: CommonSet.empty(S) 213 | 214 | ..attr:: discard 215 | ...mapping 216 | ....d: Remove e from S; no effect if e was not in S. 217 | ....arg: e:Any+ 218 | ....postcondition: not CommonSet.contains(S, e) 219 | 220 | ..attr:: pop 221 | ...mapping 222 | ....d: Remove and return some object from S, or raise ValueError if S was empty. 223 | ....precondition: not CommonSet.empty(S) 224 | ....postcondition: not CommonSet.contains(S, ) 225 | 226 | ..attr:: remove 227 | ...mapping 228 | ....d: Remove e from S, or raise ValueError if e was not in S. 229 | ....arg: e:Any+ 230 | ....precondition: CommonSet.contains(S, e) 231 | ....postcondition: not CommonSet.contains(S, e) 232 | 233 | ..attr:: tas 234 | ...mapping 235 | ....d: Test and Set. 236 | ....d: If e is in S return True, 237 | ....d: else add e to S and return False. 238 | ....arg: e:Any+ 239 | ....returns: boolean 240 | ....postcondition: CommonSet.contains(S, e) 241 | ....postcondition: not CommonSet.empty(S) 242 | ....equation: 243 | .....precondition: CommonSet.contains(S, e) 244 | .....postcondition: CommonSet.istrue() 245 | 246 | ..attr:: tac 247 | ...mapping 248 | ....d: Test and Clear. 249 | ....d: If e is in S, remove e from S and return True, 250 | ....d: else return False. 251 | ....arg: e:Any+ 252 | ....returns: boolean 253 | ....postcondition: not CommonSet.contains(S, e) 254 | ....equation: 255 | .....precondition: CommonSet.contains(S, e) 256 | .....postcondition: CommonSet.istrue() 257 | 258 | 259 | .and: ImmNodeSet 260 | ..d: An immutable nodeset is a nodeset object that is guaranteed to always 261 | contain the same elements after it has been created. 262 | 263 | ..subkind of: NodeSet 264 | ...d: An immutable nodeset inherits the operations defined for NodeSet. 265 | ...d: The in-place operations (&=, |= etc) will not really update the 266 | target set in place, but will return an updated copy. It is yet formally 267 | unspecified whether this returned copy is mutable or immutable. 268 | 269 | ..self: x 270 | 271 | ..constructor: module_sets.immnodeset 272 | 273 | ..fop: hash 274 | ...d: Hashing 275 | ...returns: int 276 | ....d: a hash value based on the addresses of the elements. 277 | 278 | -------------------------------------------------------------------------------- /src/heapy/xmemstats.c: -------------------------------------------------------------------------------- 1 | /* Extra, low-level memory statistics functions. 2 | Some is system dependent, 3 | some require special Python compilation. 4 | */ 5 | 6 | static char hp_xmemstats_doc[] = 7 | "xmemstats()\n" 8 | "\n" 9 | "Print extra memory statistics. What is printed depends on the system\n" 10 | "configuration. "; 11 | 12 | #ifndef MS_WIN32 13 | #include 14 | #else 15 | #include 16 | #endif 17 | 18 | 19 | static size_t (*dlptr_malloc_usable_size)(void *ptr); 20 | static void (*dlptr_malloc_stats)(void); 21 | static int (*dlptr__PyObject_DebugMallocStats)(FILE *out); 22 | static Py_ssize_t *dlptr__Py_RefTotal; 23 | 24 | #ifdef HEAPY_BREAK_THREAD_SAFETY 25 | static void **dlptr___malloc_hook, **dlptr___realloc_hook, **dlptr___free_hook; 26 | static void *org___malloc_hook, *org___realloc_hook, *org___free_hook; 27 | 28 | Py_ssize_t totalloc, totfree, numalloc, numfree; 29 | 30 | static int has_malloc_hooks; 31 | #endif 32 | 33 | static void *addr_of_symbol(const char *symbol) { 34 | #ifndef MS_WIN32 35 | return dlsym(RTLD_DEFAULT, symbol); 36 | #else 37 | void *addr; 38 | HMODULE hMod; 39 | 40 | hMod = GetModuleHandle(NULL); 41 | if (hMod) { 42 | addr = GetProcAddress(hMod, symbol); 43 | if (addr) 44 | return addr; 45 | } 46 | 47 | hMod = GetModuleHandle( 48 | // Why isn't this in some CPython header file? 49 | #ifdef _DEBUG 50 | "python" Py_STRINGIFY(PY_MAJOR_VERSION) Py_STRINGIFY(PY_MINOR_VERSION) "_d.dll" 51 | #else 52 | "python" Py_STRINGIFY(PY_MAJOR_VERSION) Py_STRINGIFY(PY_MINOR_VERSION) ".dll" 53 | #endif 54 | ); 55 | if (hMod) { 56 | addr = GetProcAddress(hMod, symbol); 57 | if (addr) 58 | return addr; 59 | } 60 | 61 | hMod = GetModuleHandle("MSVCRT.dll"); 62 | if (hMod) { 63 | addr = GetProcAddress(hMod, symbol); 64 | if (addr) 65 | return addr; 66 | } 67 | 68 | return 0; 69 | #endif 70 | } 71 | 72 | #ifdef HEAPY_BREAK_THREAD_SAFETY 73 | static void 74 | breakit(void *p, char c) 75 | { 76 | // fprintf(stderr, "breakit %p %c %d\n", p, c, dlptr_malloc_usable_size(p)); 77 | } 78 | 79 | static void *mallochook(size_t size); 80 | static void *reallochook(void *p, size_t size); 81 | static void freehook(void *p); 82 | 83 | #define HOOK_RESTORE do { \ 84 | *dlptr___malloc_hook = org___malloc_hook; \ 85 | *dlptr___realloc_hook = org___realloc_hook; \ 86 | *dlptr___free_hook = org___free_hook; \ 87 | } while (0) 88 | 89 | #define HOOK_SAVE do { \ 90 | org___malloc_hook = *dlptr___malloc_hook; \ 91 | org___realloc_hook = *dlptr___realloc_hook; \ 92 | org___free_hook = *dlptr___free_hook; \ 93 | } while (0) 94 | 95 | #define HOOK_SET do { \ 96 | *dlptr___malloc_hook = &mallochook; \ 97 | *dlptr___realloc_hook = &reallochook; \ 98 | *dlptr___free_hook = &freehook; \ 99 | } while (0) 100 | 101 | 102 | static void * 103 | mallochook(size_t size) { 104 | void *p; 105 | 106 | HOOK_RESTORE; 107 | p = malloc(size); 108 | HOOK_SAVE; 109 | 110 | if (p) { 111 | totalloc += dlptr_malloc_usable_size(p); 112 | numalloc += 1; 113 | } 114 | breakit(p, 'm'); 115 | 116 | HOOK_SET; 117 | return p; 118 | } 119 | 120 | static void * 121 | reallochook(void *p, size_t size) { 122 | void *q; 123 | Py_ssize_t f; 124 | 125 | if (p) 126 | f = dlptr_malloc_usable_size(p); 127 | else 128 | f = 0; 129 | 130 | HOOK_RESTORE; 131 | q = realloc(p, size); 132 | HOOK_SAVE; 133 | 134 | if (p) { 135 | if (q) { 136 | if (q != p) { 137 | totfree += f; 138 | f = dlptr_malloc_usable_size(q); 139 | totalloc += f; 140 | } else { 141 | f = dlptr_malloc_usable_size(q) - f; 142 | if (f > 0) { 143 | totalloc += f; 144 | } else { 145 | totfree -= f; 146 | } 147 | } 148 | } else if (!size) { 149 | totfree += f; 150 | numfree += 1; 151 | } 152 | } else { 153 | if (q) { 154 | totalloc += dlptr_malloc_usable_size(q); 155 | numalloc += 1; 156 | } 157 | } 158 | 159 | breakit(q, 'r'); 160 | 161 | HOOK_SET; 162 | return q; 163 | } 164 | 165 | static void 166 | freehook(void *p) { 167 | totfree += dlptr_malloc_usable_size(p); 168 | HOOK_RESTORE; 169 | free(p); 170 | HOOK_SAVE; 171 | 172 | if (p) 173 | numfree += 1; 174 | 175 | HOOK_SET; 176 | } 177 | #endif 178 | 179 | static void 180 | xmemstats_init(void) { 181 | #ifdef HEAPY_BREAK_THREAD_SAFETY 182 | dlptr___malloc_hook = addr_of_symbol("__malloc_hook"); 183 | dlptr___realloc_hook = addr_of_symbol("__realloc_hook"); 184 | dlptr___free_hook = addr_of_symbol("__free_hook"); 185 | #endif 186 | dlptr_malloc_usable_size = addr_of_symbol("malloc_usable_size"); 187 | dlptr_malloc_stats = addr_of_symbol("malloc_stats"); 188 | dlptr__PyObject_DebugMallocStats = addr_of_symbol("_PyObject_DebugMallocStats"); 189 | dlptr__Py_RefTotal = addr_of_symbol("_Py_RefTotal"); 190 | 191 | #ifdef HEAPY_BREAK_THREAD_SAFETY 192 | has_malloc_hooks = dlptr___malloc_hook && dlptr___realloc_hook && 193 | dlptr___free_hook && dlptr_malloc_usable_size; 194 | if (has_malloc_hooks) { 195 | HOOK_SAVE; 196 | HOOK_SET; 197 | } 198 | #endif 199 | } 200 | 201 | static PyObject * 202 | hp_xmemstats(PyObject *self, PyObject *args) 203 | { 204 | if (dlptr__PyObject_DebugMallocStats) { 205 | fprintf(stderr, "======================================================================\n"); 206 | fprintf(stderr, "Output from _PyObject_DebugMallocStats()\n\n"); 207 | dlptr__PyObject_DebugMallocStats(stderr); 208 | } 209 | 210 | if (dlptr_malloc_stats) { 211 | fprintf(stderr, "======================================================================\n"); 212 | fprintf(stderr, "Output from malloc_stats\n\n"); 213 | dlptr_malloc_stats(); 214 | } 215 | 216 | #ifdef HEAPY_BREAK_THREAD_SAFETY 217 | if (has_malloc_hooks) { 218 | fprintf(stderr, "======================================================================\n"); 219 | fprintf(stderr, "Statistics gathered from hooks into malloc, realloc and free\n\n"); 220 | 221 | fprintf(stderr, "Allocated bytes = %12zd\n", totalloc); 222 | fprintf(stderr, "Allocated - freed bytes = %12zd\n", totalloc-totfree); 223 | fprintf(stderr, "Calls to malloc = %12zd\n", numalloc); 224 | fprintf(stderr, "Calls to malloc - calls to free = %12zd\n", numalloc-numfree); 225 | } 226 | #endif 227 | 228 | #ifndef Py_TRACE_REFS 229 | if (dlptr__Py_RefTotal) { 230 | #endif 231 | fprintf(stderr, "======================================================================\n"); 232 | fprintf(stderr, "Other statistics\n\n"); 233 | #ifndef Py_TRACE_REFS 234 | } 235 | #endif 236 | 237 | if (dlptr__Py_RefTotal) { 238 | fprintf(stderr, "Total reference count = %12zd\n", *dlptr__Py_RefTotal); 239 | } 240 | 241 | #ifdef Py_TRACE_REFS 242 | { 243 | PyObject *x; int i; 244 | for (i = 0, x = this_module->_ob_next; x != this_module; x = x->_ob_next, i++); 245 | fprintf(stderr, "Total heap objects = %12d\n", i); 246 | } 247 | #endif 248 | fprintf(stderr, "======================================================================\n"); 249 | 250 | Py_INCREF(Py_None); 251 | return Py_None; 252 | } 253 | --------------------------------------------------------------------------------