├── .gitignore
├── Makefile
├── README.md
├── Small.md
├── logo.svg
├── logo_sml.png
├── requirements.txt
├── setup.py
└── subpy
├── __init__.py
├── features.py
├── stdlib.py
├── tests
├── __init__.py
└── test_features.py
└── validate.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | *.sw[pon]
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | env/
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | python -m unittest discover subpy/tests
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Subpy
6 | =====
7 |
8 | Subpy is a library for defining subsets of the Python language
9 | and querying ASTs for language-level properties that are
10 | specified as sets of features.
11 |
12 | Many projects aim to work with specific subsets of Python that
13 | are amenable to *static analysis* and *type inference*, subpy is
14 | simply a static analysis library for checking for subsets with
15 | the intention of providing more informative error reporting for
16 | end-users.
17 |
18 | Usage
19 | -----
20 |
21 | The input to the ``checker`` can be either a Module, Function or
22 | source code as string. It returns a dictionary of lists keyed by
23 | the ``feature`` enumeration code and the values with the line
24 | numbers where the feature is detected.
25 |
26 | ```python
27 | >>> from subpy import checker
28 | >>> import io
29 |
30 | >>> print checker(io)
31 |
32 | {3: [98],
33 | 5: [78, 81, 84, 87],
34 | 9: [78, 81, 84, 87],
35 | 10: [81, 84, 87],
36 | 32: [92, 96],
37 | 34: [79]}
38 | ```
39 |
40 | Matching the feature codes with the keys in the dictionary we see
41 | the information this is telling us that in the ``io`` module in
42 | the standard library:
43 |
44 | * A *delete* is used on line 98.
45 | * A *class* is used on line 78, 81, 84, and 87.
46 | * *Inheritance* is used on line 78, 81, 84, and 87.
47 | * *Multiple inheritance* is used on line 81, 84, and 87.
48 | * A *custom iterator* is used on line 92 and 96.
49 | * A *metaclass* is used on line 79.
50 |
51 | A example using the function level checker:
52 |
53 | ```python
54 | from subpy import checker
55 | from subpy.features import ListComp
56 |
57 | def example1():
58 | return [x**2 for x in range(25)]
59 |
60 | def example2():
61 | return 'hello'
62 |
63 | features = checker(example1)
64 |
65 | if ListComp in features:
66 | print 'You used a list comprehension on lines %r' % (features[ListComp])
67 |
68 | features = checker(example2)
69 |
70 | if ListComp not in features:
71 | print 'You did not use any list comprehensions!'
72 |
73 | ```
74 |
75 | Defining Subsets
76 | ----------------
77 |
78 | For example if we want to exclude the use of *List Comprehensions*
79 | and *Set Comprehensions* we could define a subset of Python that
80 | excludes these features.
81 |
82 | ```python
83 | MyPythonSubset = FullPython - { ListComp, SetComp }
84 | ```
85 |
86 | The ``validator`` command can be used to raise when unsupported
87 | features are detected in the given source. For example, we'll
88 | support the python feature set excluding list comprehensions and
89 | set comprehensions.
90 |
91 | ```python
92 | from subpy import validator, FullPython, FeatureNotSupported
93 | from subpy.features import ListComp, SetComp
94 |
95 | def example():
96 | return [x**2 for x in range(25)]
97 |
98 | my_features = FullPython - { ListComp, SetComp }
99 |
100 | validator(example, features=my_features)
101 | ```
102 |
103 | ```python
104 | File "", line 2
105 | return [x**2 for x in range(25)]
106 | ^
107 | subpy.validate.FeatureNotSupported: ListComp
108 | ```
109 |
110 | Subpy is currently able to parse the entire standard library and
111 | can be used to query some interesting trivia facts.
112 |
113 | ```python
114 | from subpy import detect
115 | from subpy.stdlib import libraries
116 | from subpy.features import Metaclasses, MInheritance, Exec
117 |
118 | import importlib
119 |
120 | print('Libraries with Multiple Inheritance and Metaclasses:')
121 | for lib in libraries:
122 | mod = importlib.import_module(lib)
123 | features = detect(mod)
124 |
125 | if Metaclasses in features and MInheritance in features:
126 | print(lib)
127 |
128 | ```
129 |
130 | ```
131 | Libraries with Multiple Inheritance and Metaclasses:
132 | io
133 | ```
134 |
135 | Or to query for potentially unsafe code execution:
136 |
137 | ```python
138 | print('Libraries with Exec')
139 | for lib in libraries:
140 | mod = importlib.import_module(lib)
141 | features = detect(mod)
142 |
143 | if Exec in features:
144 | print(lib)
145 | ```
146 |
147 | ```
148 | Libraries with Exec
149 | ihooks
150 | site
151 | cgi
152 | rexec
153 | Bastion
154 | imputil
155 | trace
156 | timeit
157 | cProfile
158 | doctest
159 | code
160 | bdb
161 | runpy
162 | profile
163 | collections
164 | ```
165 |
166 | Feature Codes
167 | -------------
168 |
169 | Currently supported features are an enumeration with values given
170 | below:
171 |
172 | 1. ImplicitCasts
173 | 1. Generators
174 | 1. DelVar
175 | 1. Closures
176 | 1. Classes
177 | 1. Decorators
178 | 1. VarArgs
179 | 1. KeywordArgs
180 | 1. Inheritance
181 | 1. MInheritance
182 | 1. ClassDecorators
183 | 1. Assertions
184 | 1. ChainComparison
185 | 1. Exceptions
186 | 1. Lambda
187 | 1. RelativeImports
188 | 1. ImportStar
189 | 1. HeteroList
190 | 1. Continue
191 | 1. MultipleReturn
192 | 1. DictComp
193 | 1. Ellipsi
194 | 1. TupleUnpacking
195 | 1. Exec
196 | 1. FancyIndexing
197 | 1. Globals
198 | 1. ContextManagers
199 | 1. GeneratorExp
200 | 1. Ternary
201 | 1. ListComp
202 | 1. SetComp
203 | 1. CustomIterators
204 | 1. Printing
205 | 1. Metaclasses
206 |
207 | Testing
208 | -------
209 |
210 | To test run:
211 |
212 | ```bash
213 | $ python -m unittest discover subpy/tests
214 | ```
215 |
216 | Copying
217 | -------
218 |
219 | The core logic is self-contained in ``features.py`` and
220 | ``validate.py`` which will function as standalone modules without
221 | subpy package, which includes the test suite. There are no
222 | dependencies other than the standard library.
223 |
224 | License
225 | -------
226 |
227 | Copyright (c) 2013, Continuum Analytics, Inc.
228 | All rights reserved.
229 |
230 | Redistribution and use in source and binary forms, with or without
231 | modification, are permitted provided that the following conditions are
232 | met:
233 |
234 | Redistributions of source code must retain the above copyright notice,
235 | this list of conditions and the following disclaimer.
236 |
237 | Redistributions in binary form must reproduce the above copyright
238 | notice, this list of conditions and the following disclaimer in the
239 | documentation and/or other materials provided with the distribution.
240 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
241 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
242 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
243 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
244 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
245 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
246 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
247 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
248 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
249 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
250 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
251 |
--------------------------------------------------------------------------------
/Small.md:
--------------------------------------------------------------------------------
1 | SmallPy Language Specification
2 | ------------------------------
3 |
4 | Motivation:
5 |
6 | - Establish a "Numeric Recipies in C" subset of Python. Just
7 | unambiguously make numeric loops fast, do this well because it
8 | generates revenue for Continuum. Then keep the "clever path" as
9 | an ongoing project into how to type larger sets of Python.
10 |
11 | - Allow perfect reasoning about performance by demarcating a
12 | subset of Python that either lowers into LLVM or fails to
13 | compile.
14 |
15 | - If code compiles it is guaranteed to be faster than the
16 | equivalent CPython.
17 |
18 | - Improve the overall user experience of error reporting so that
19 | the end-user knows how to change their code to match the
20 | subset.
21 |
22 | - Explicit is better than implicit. If the end user wants to go
23 | through the Object Layer make them say so.
24 |
25 | Types
26 | -----
27 |
28 | - int
29 | - float
30 | - bool
31 | - string
32 | - unicode
33 | - complex
34 | - list
35 | - tuple
36 | - NoneType
37 | - FunctionType
38 |
39 | Unsupported Types:
40 |
41 | - object
42 | - dict
43 | - set
44 | - long
45 | - frozenset
46 | - buffer
47 | - bytearray
48 | - bytes
49 | - basestring
50 | - memoryview
51 |
52 | Values
53 | ------
54 |
55 | Tuple unpacking is not supported.
56 |
57 | Assignment to tuples is syntactic sugar for multiple assignment
58 | only when r-values are literals.
59 |
60 | Supported:
61 |
62 | ```
63 | a, b = 1, True
64 | ```
65 |
66 | Not Supported:
67 |
68 | ```
69 | a, b = f()
70 | ```
71 |
72 | Control Flow
73 | ------------
74 |
75 | Supported:
76 |
77 | - If
78 | - If/Else
79 | - If/ElseIf/Else
80 | - For
81 | - While
82 |
83 | Not Supported:
84 |
85 | - Generators
86 | - Exceptions
87 | - Try
88 | - Try/Finally
89 | - Try/Except
90 | - Try/Except/Finally
91 | - While/Else
92 | - For/Else
93 |
94 | Data Model
95 | ----------
96 |
97 | - Lists are stricly homogeneus containers of data. The
98 | storage is compiler specific.
99 | - Tuples are heteroegneous immutable types. The storage is
100 | compiler specific.
101 | - Strings are variable immutable arrays of ascii characters. The
102 | storage is compiler specific.
103 | - Unicode strings are immutable arrays of UTF-8 characters. The
104 | storage is compiler specific.
105 |
106 | The ``list`` constructor polymorphically lifts elements into
107 | singleton containers.
108 |
109 | ```
110 | list :: a -> [a]
111 | ```
112 |
113 | Introspection
114 | -------------
115 |
116 | Runtime introspection with ``type``, ``isinstance``,
117 | ``issubclass``, ``id``, ``globals``, ``locals``, ``dir``,
118 | ``callable``, ``getattr``, ``hash``, ``hasattr``, ``super``,
119 | ``vars`` is not supported.
120 |
121 | The ``intern`` function is not supported.
122 |
123 | Length
124 | ------
125 |
126 | The implementation of the ``len`` function is polymorphic and
127 | container specific.
128 |
129 | ```python
130 | len :: [a] -> int
131 | ```
132 |
133 | Destruction
134 | -----------
135 |
136 | Variable and element destruction is not supported. The ``del``
137 | operator is not part of the syntax and ``delattr` is not
138 | supported.
139 |
140 | Metaprogramming
141 | ---------------
142 |
143 | ``compile``, ``eval`` and ``exec``, ``execfile`` are not supported
144 |
145 | Pass
146 | ----
147 |
148 | Pass is a syntactic construct that translates into a compiler
149 | specific noop.
150 |
151 | System IO
152 | ---------
153 |
154 | ``file``, ``open`` and ``quit``, ``raw_input``, ``reload``,
155 | ``help`` and ``input`` are not supported.
156 |
157 | ``print`` is optionally supported. The implementation is specific
158 | to the compiler. For compilers that do not choose to implement
159 | print it should translate to noop.
160 |
161 | The ``repr`` function is not supported.
162 |
163 | Formatting
164 | ----------
165 |
166 | Printf style formatting is not supported. The ``Mod`` operator
167 | exclusively maps to numeric modulus.
168 |
169 | Iterators
170 | ---------
171 |
172 | Generators are not supported.
173 |
174 | Range iterators are syntactic sugar for looping constructs. Custom
175 | iterators are not supported. The ``iter`` and ``next`` functions
176 | are not supported.
177 |
178 | ```
179 | for i in xrange(start, stop, step)
180 | foo()
181 | ```
182 |
183 | Is lowered into some equivalent low-level looping construct that
184 | roughly corresponds to the following C code:
185 |
186 | ```
187 | for (i = start; i < stop; i += step) {
188 | foo();
189 | }
190 | ```
191 |
192 | The value of ``i`` after the loop block follows the Python
193 | semantics and is set to the last value in the iterator instead of
194 | the C semantics.
195 |
196 | ``xrange`` and `range`` are lowered into the same constructs.
197 |
198 | ``enumerate`` is not supported
199 |
200 | Comprehensions
201 | --------------
202 |
203 | TODO
204 |
205 | Builtins
206 | --------
207 |
208 | * abs - Supported
209 | * all - Supported
210 | * any - Supported
211 | * apply - Not Supported
212 | * basestring - Not Supported
213 | * bin - Not Supported
214 | * bool - Supported
215 | * buffer - Not Supported
216 | * bytearray - Not Supported
217 | * bytes - Not Supported
218 | * callable - Not Supported
219 | * chr - Not Supported
220 | * classmethod - Not Supported
221 | * cmp - Supported
222 | * coerce - Not Supported
223 | * compile - Not Supported
224 | * complex - Supported
225 | * copyright - Not Supported
226 | * credits - Not Supported
227 | * delattr - Not Supported
228 | * dict - Not Supported
229 | * dir - Not Supported
230 | * divmod - Supported
231 | * enumerate - Not Supported
232 | * eval - Not Supported
233 | * execfile - Not Supported
234 | * exit - Not Supported
235 | * file - Not Supported
236 | * filter - Supported
237 | * float - Supported
238 | * format - Not Supported
239 | * frozenset - Not Supported
240 | * getattr - Not Supported
241 | * globals - Not Supported
242 | * hasattr - Not Supported
243 | * hash - Not Supported
244 | * help - Not Supported
245 | * hex - Not Supported
246 | * id - Not Supported
247 | * input - Not Supported
248 | * int - Supported
249 | * intern - Not Supported
250 | * isinstance - Not Supported
251 | * issubclass - Not Supported
252 | * iter - Not Supported
253 | * len - Supported
254 | * license - Not Supported
255 | * list - Supported
256 | * locals - Not Supported
257 | * long - Not Supported
258 | * map - Supported
259 | * max - Supported
260 | * memoryview - Not Supported
261 | * min - Supported
262 | * next - Not Supported
263 | * object - Not Supported
264 | * oct - Not Supported
265 | * open - Not Supported
266 | * ord - Not Supported
267 | * pow - Supported
268 | * print - Not Supported
269 | * property - Not Supported
270 | * quit - Not Supported
271 | * range - Supported
272 | * raw_input - Not Supported
273 | * reduce - Supported
274 | * reload - Not Supported
275 | * repr - Not Supported
276 | * reversed - Not Supported
277 | * round - Supported
278 | * set - Not Supported
279 | * setattr - Not Supported
280 | * slice - Not Supported
281 | * sorted - Supported
282 | * staticmethod - Not Supported
283 | * str - Supported
284 | * sum - Supported
285 | * super - Not Supported
286 | * tuple - Not Supported
287 | * type - Not Supported
288 | * unichr - Not Supported
289 | * unicode - Supported
290 | * vars - Not Supported
291 | * xrange - Supported
292 | * zip - Supported
293 |
294 |
295 | Filter
296 | ------
297 |
298 | ```python
299 | filter :: (a -> bool) -> [a] -> [a]
300 | def filter(p, xs):
301 | return [x for x in xs if p(x)]
302 | ```
303 |
304 | All
305 | ---
306 |
307 |
308 | ```python
309 | all :: (a -> bool) -> [a] -> bool
310 | def all(p, xs):
311 | for i in xs:
312 | if not p(i):
313 | return False
314 | return True
315 | ```
316 |
317 | Any
318 | ---
319 |
320 | ```python
321 | any :: (a -> bool) -> [a] -> bool
322 | def any(p, xs):
323 | for i in xs:
324 | if not p(i):
325 | return True
326 | return False
327 | ```
328 |
329 | Map
330 | ---
331 |
332 | ```python
333 | map :: (a -> b) -> [a] -> [b]
334 | def map(f, xs):
335 | return [f(x) for x in xs]
336 | ```
337 |
338 | Reduce
339 | ------
340 |
341 | ```python
342 | reduce :: (a -> b -> a) -> a -> [b] -> a
343 | def reduce(p, xs, x0):
344 | acc = x0
345 | for x in xs:
346 | acc = f(acc)
347 | return acc
348 | ```
349 |
350 | Max
351 | ---
352 |
353 | ```python
354 |
355 | max :: [a] -> a
356 | def max(xs):
357 | assert len(xs) > 0
358 | x0 = xs[0]
359 | for x in xs:
360 | if x > x0:
361 | x0 = x
362 | return x0
363 | ```
364 |
365 | Min
366 | ---
367 |
368 | ```python
369 |
370 | min :: [a] -> a
371 | def min(xs):
372 | assert len(xs) > 0
373 | x0 = xs[0]
374 | for x in xs:
375 | if x < x0:
376 | x0 = x
377 | return x0
378 | ```
379 |
380 | Zip
381 | ---
382 |
383 | ```python
384 | min :: [a] -> [b] -> [(a, b)]
385 | def min(xs, ys):
386 | out = []
387 | for i in min(len(xs), len(ys)):
388 | out.append((xs[i], ys[i]))
389 | return out
390 | ```
391 |
392 | Reverse
393 | -------
394 |
395 | ```
396 | reverse :: [a] -> [a]
397 | def reverse(xs):
398 | out = []
399 | for i in len(xs):
400 | out.append(xs[len(xs)-i])
401 | return out
402 | ```
403 |
404 | Sorted
405 | ------
406 |
407 | Sorted sorts the underlying list data structure. The
408 | implementation is compiler specific.
409 |
410 | ```python
411 | sorted :: [a] -> [a]
412 | ```
413 |
414 | Slice
415 | -----
416 |
417 | Named slicing is not supported. Slice types are not supported.
418 | Slicing as an indexing operation is supported.
419 |
420 | ```
421 | a = slice(0, 1, 2)
422 | ```
423 |
424 | Classes
425 | -------
426 |
427 | Classes are not supported. The corresponding descriptor methods
428 | are not implemented.
429 |
430 | - property
431 | - classmethod
432 | - staticmethod
433 |
434 | Casts
435 | -----
436 |
437 | ```
438 | int :: a -> int
439 | bool :: a -> bool
440 | complex :: a -> bool
441 | ```
442 |
443 | The coerce function is not supported.
444 |
445 | The ``str``, ``list`` and ``tuple`` casts are not supported.
446 |
447 | Characters
448 | ----------
449 |
450 | The ``chr``, ``ord`` and ``unichr``, ``hex``, ``bin``, ``oct``
451 | functions are not supported.
452 |
453 | Closures
454 | --------
455 |
456 | Nested functions and closures are not supported.
457 |
458 | Globals
459 | -------
460 |
461 | Global variables are not supported.
462 |
463 | Arguments
464 | ---------
465 |
466 | Variadic and keyword arguments are not supported.
467 |
468 | Assertions
469 | ----------
470 |
471 | Assertions are not supported.
472 |
473 | Syntax
474 | ------
475 |
476 | Language Features
477 |
478 | ```
479 |
480 | module SmallPython
481 | {
482 | mod = Module(stmt* body)
483 | | Expression(expr body)
484 |
485 | stmt = FunctionDef(identifier name, arguments args,
486 | stmt* body, expr? returns)
487 | | Return(expr? value)
488 |
489 | | Assign(expr* targets, expr value)
490 | | AugAssign(expr target, operator op, expr value)
491 |
492 | | For(expr target, expr iter, stmt* body, stmt* orelse)
493 | | While(expr test, stmt* body, stmt* orelse)
494 | | If(expr test, stmt* body, stmt* orelse)
495 |
496 | | Expr(expr value)
497 | | Pass | Break | Continue
498 |
499 | attributes (int lineno, int col_offset)
500 |
501 | expr = BoolOp(boolop op, expr* values)
502 | | BinOp(expr left, operator op, expr right)
503 | | UnaryOp(unaryop op, expr operand)
504 | | IfExp(expr test, expr body, expr orelse)
505 | | ListComp(expr elt, comprehension* generators)
506 | | Compare(expr left, cmpop* ops, expr* comparators)
507 | | Call(expr func, expr* args, keyword* keywords,
508 | expr? starargs, expr? kwargs)
509 | | Num(object n) -- a number as a PyObject.
510 | | Str(string s) -- need to specify raw, unicode, etc?
511 | | Bytes(string s)
512 |
513 | | Attribute(expr value, identifier attr, expr_context ctx)
514 | | Subscript(expr value, slice slice, expr_context ctx)
515 | | Name(identifier id, expr_context ctx)
516 | | List(expr* elts, expr_context ctx)
517 | | Tuple(expr* elts, expr_context ctx)
518 |
519 | attributes (int lineno, int col_offset)
520 |
521 | expr_context = Load | Store | AugLoad | AugStore | Param
522 |
523 | slice = Slice(expr? lower, expr? upper, expr? step)
524 | | ExtSlice(slice* dims)
525 | | Index(expr value)
526 |
527 | boolop = And | Or
528 |
529 | operator = Add | Sub | Mult | Div | Mod | Pow | LShift
530 | | RShift | BitOr | BitXor | BitAnd | FloorDiv
531 |
532 | unaryop = Invert | Not | UAdd | USub
533 |
534 | cmpop = Eq | NotEq | Lt | LtE | Gt | GtE
535 |
536 | comprehension = (expr target, expr iter, expr* ifs)
537 |
538 | arguments = (arg* args, identifier? vararg, expr? varargannotation,
539 | arg* kwonlyargs, identifier? kwarg,
540 | expr? kwargannotation, expr* defaults,
541 | expr* kw_defaults)
542 | arg = (identifier arg, expr? annotation)
543 |
544 | keyword = (identifier arg, expr value)
545 | alias = (identifier name, identifier? asname)
546 | }
547 |
548 | ```
549 |
550 | Operators
551 | ---------
552 |
553 | - And
554 | - Or
555 | - Add
556 | - Sub
557 | - Mult
558 | - Div
559 | - Mod
560 | - Pow
561 | - LShift
562 | - RShift
563 | - BitOr
564 | - BitXor
565 | - BitAnd
566 | - FloorDiv
567 | - Invert
568 | - Not
569 | - UAdd
570 | - USub
571 | - Eq
572 | - NotEq
573 | - Lt
574 | - LtE
575 | - Gt
576 | - GtE
577 |
578 | Comparison operator chaining is supported and is desugared into
579 | boolean conjunctions of the comparison operators.
580 |
581 | ```
582 | (x > y > z)
583 | ```
584 |
585 | ```
586 | (x > y) and (y > z)
587 | ```
588 |
589 | Smallpy explictly does not support the following operators.
590 |
591 | - Is
592 | - IsNot
593 | - In
594 | - NotIn
595 |
596 | Division
597 | --------
598 |
599 | Division follows the Python semantics for distinction between
600 | ``floordiv`` and ``truediv`` but operates over unboxed types
601 | with no error checking.
602 |
603 | Math Functions
604 | --------------
605 |
606 | - abs
607 | - cmp
608 | - divmod
609 | - pow
610 | - round
611 |
612 | sorted
613 |
614 | Floating Point Math
615 | -------------------
616 |
617 | ```
618 | acos
619 | acosh
620 | asin
621 | asinh
622 | atan
623 | atan2
624 | atanh
625 | ceil
626 | copysign
627 | cos
628 | cosh
629 | degrees
630 | e
631 | erf
632 | erfc
633 | exp
634 | expm1
635 | fabs
636 | factorial
637 | floor
638 | fmod
639 | frexp
640 | fsum
641 | gamma
642 | hypot
643 | isinf
644 | isnan
645 | ldexp
646 | lgamma
647 | log
648 | log10
649 | log1p
650 | modf
651 | pi
652 | pow
653 | radians
654 | sin
655 | sinh
656 | sqrt
657 | tan
658 | tanh
659 | trunc
660 | ```
661 |
662 | Complex Math
663 |
664 | ```
665 | acos
666 | acosh
667 | asin
668 | asinh
669 | atan
670 | atanh
671 | cos
672 | cosh
673 | exp
674 | isinf
675 | isnan
676 | log
677 | log10
678 | phase
679 | polar
680 | rect
681 | sin
682 | sinh
683 | sqrt
684 | tan
685 | tanh
686 | ```
687 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
391 |
--------------------------------------------------------------------------------
/logo_sml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdiehl/subpy/60e2bb9c9e7f542a121999cc324c4569ff2d23b2/logo_sml.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdiehl/subpy/60e2bb9c9e7f542a121999cc324c4569ff2d23b2/requirements.txt
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 |
3 | setup(
4 | name = "subpy",
5 | license = "BSD",
6 | description = "Python subsets",
7 | packages = ['subpy',
8 | 'subpy.tests',],
9 | version = "0.1",
10 | )
11 |
--------------------------------------------------------------------------------
/subpy/__init__.py:
--------------------------------------------------------------------------------
1 | from .features import *
2 | from .validate import detect, fd, checker, validator, \
3 | FeatureNotSupported, FullPython
4 |
5 |
6 | from .tests.test_features import run
7 |
8 | test = run
9 |
--------------------------------------------------------------------------------
/subpy/features.py:
--------------------------------------------------------------------------------
1 | #------------------------------------------------------------------------
2 | # Feature Set
3 | #------------------------------------------------------------------------
4 |
5 | ImplicitCasts = 1
6 | Generators = 2
7 | DelVar = 3
8 | Closures = 4
9 | Classes = 5
10 | Decorators = 6
11 | VarArgs = 7
12 | KeywordArgs = 8
13 | Inheritance = 9
14 | MInheritance = 10
15 | ClassDecorators = 11
16 | Assertions = 12
17 | ChainComparison = 13
18 | Exceptions = 14
19 | Lambda = 15
20 | RelativeImports = 16
21 | ImportStar = 17
22 | HeteroList = 18
23 | Continue = 19
24 | MultipleReturn = 20
25 | DictComp = 21
26 | Ellipsi = 22
27 | TupleUnpacking = 23
28 | Exec = 24
29 | FancyIndexing = 25
30 | Globals = 26
31 | ContextManagers = 27
32 | GeneratorExp = 28
33 | Ternary = 29
34 | ListComp = 30
35 | SetComp = 31
36 | CustomIterators = 32
37 | Printing = 33
38 | Metaclasses = 34
39 |
--------------------------------------------------------------------------------
/subpy/stdlib.py:
--------------------------------------------------------------------------------
1 | import os
2 | import distutils.sysconfig as sysconfig
3 |
4 | jokes = ['antigravity', 'this', 'dbhash', 'bsddb']
5 |
6 | def standard_library():
7 | std_lib = sysconfig.get_python_lib(standard_lib=True)
8 |
9 | for top, dirs, files in os.walk(std_lib):
10 | for nm in files:
11 | prefix = top[len(std_lib)+1:]
12 | if prefix[:13] == 'site-packages':
13 | continue
14 |
15 | if nm == '__init__.py':
16 | pack = top[len(std_lib)+1:].replace(os.path.sep,'.')
17 |
18 | if ('.' not in pack) and\
19 | (not pack.startswith('_')) and\
20 | (pack not in jokes):
21 | yield pack
22 |
23 | elif nm[-3:] == '.py':
24 | pack = os.path.join(prefix, nm)[:-3].replace(os.path.sep,'.')
25 |
26 | if ('.' not in pack) and\
27 | (not pack.startswith('_')) and\
28 | (pack not in jokes):
29 | yield pack
30 |
31 | # Python 3.x, todo, this has changed
32 | libraries = [
33 | 'ihooks',
34 | 'quopri',
35 | 'DocXMLRPCServer',
36 | 'shelve',
37 | 'tarfile',
38 | 'CGIHTTPServer',
39 | 'linecache',
40 | 'pdb',
41 | 'sre_parse',
42 | 'user',
43 | 'site',
44 | 'pty',
45 | 'commands',
46 | 'opcode',
47 | 'uu',
48 | 'multifile',
49 | 'pkgutil',
50 | 'dumbdbm',
51 | 'cgi',
52 | 'telnetlib',
53 | 'copy',
54 | 'copy_reg',
55 | 'SimpleXMLRPCServer',
56 | 'textwrap',
57 | 'xmlrpclib',
58 | 'MimeWriter',
59 | 'threading',
60 | 'mailbox',
61 | 'gettext',
62 | 'contextlib',
63 | 'sndhdr',
64 | 'HTMLParser',
65 | 'hashlib',
66 | 'BaseHTTPServer',
67 | 'posixfile',
68 | 'csv',
69 | 'struct',
70 | 'urlparse',
71 | 'rexec',
72 | 'htmllib',
73 | 'asynchat',
74 | 'sha',
75 | 'dis',
76 | 'warnings',
77 | 'heapq',
78 | 'types',
79 | 'token',
80 | 'netrc',
81 | 'mimetypes',
82 | 'anydbm',
83 | 'Bastion',
84 | 'subprocess',
85 | 'imputil',
86 | 'imghdr',
87 | 'macurl2path',
88 | 'trace',
89 | 'atexit',
90 | 'rfc822',
91 | 'imaplib',
92 | 'getpass',
93 | 'md5',
94 | 'base64',
95 | 'sre',
96 | 'cookielib',
97 | 'fileinput',
98 | 'keyword',
99 | 'tty',
100 | 'mutex',
101 | 'timeit',
102 | 'markupbase',
103 | 'stat',
104 | 'mimetools',
105 | 'fpformat',
106 | 'pprint',
107 | 'httplib',
108 | 'fnmatch',
109 | 'zipfile',
110 | 'mhlib',
111 | 'ssl',
112 | 'cProfile',
113 | 'aifc',
114 | 'ftplib',
115 | 'pstats',
116 | 'optparse',
117 | 'dummy_threading',
118 | 'sre_compile',
119 | 'py_compile',
120 | 'uuid',
121 | 'stringold',
122 | 'sunau',
123 | 'Cookie',
124 | 'asyncore',
125 | 'wave',
126 | 'sgmllib',
127 | 'new',
128 | 'getopt',
129 | 'audiodev',
130 | 'fractions',
131 | 'inspect',
132 | 'ast',
133 | 'statvfs',
134 | 'UserString',
135 | 'decimal',
136 | 'codecs',
137 | 'os2emxpath',
138 | 'hmac',
139 | 'genericpath',
140 | 'sunaudio',
141 | 'Queue',
142 | 'sched',
143 | 'cgitb',
144 | 'mailcap',
145 | 'pipes',
146 | 'smtplib',
147 | 'doctest',
148 | 'xmllib',
149 | 'numbers',
150 | 'os',
151 | 'dummy_thread',
152 | 'code',
153 | 'sre_constants',
154 | 'colorsys',
155 | 'modulefinder',
156 | 'bdb',
157 | 'calendar',
158 | 'urllib',
159 | 'abc',
160 | 'SocketServer',
161 | 'UserList',
162 | 'dircache',
163 | 'pydoc',
164 | 'weakref',
165 | 'ConfigParser',
166 | 'pickletools',
167 | 're',
168 | 'posixpath',
169 | 'chunk',
170 | 'mimify',
171 | 'binhex',
172 | 'tabnanny',
173 | 'rlcompleter',
174 | 'runpy',
175 | 'pyclbr',
176 | 'stringprep',
177 | 'glob',
178 | 'nntplib',
179 | 'popen2',
180 | 'formatter',
181 | 'functools',
182 | 'symtable',
183 | 'repr',
184 | 'smtpd',
185 | 'macpath',
186 | 'pickle',
187 | 'sets',
188 | 'string',
189 | 'urllib2',
190 | 'shutil',
191 | 'shlex',
192 | 'xdrlib',
193 | 'sysconfig',
194 | 'profile',
195 | 'gzip',
196 | 'tokenize',
197 | 'robotparser',
198 | 'socket',
199 | 'difflib',
200 | 'UserDict',
201 | 'platform',
202 | 'argparse',
203 | 'plistlib',
204 | 'cmd',
205 | 'nturl2path',
206 | 'locale',
207 | 'tempfile',
208 | 'random',
209 | 'toaiff',
210 | 'webbrowser',
211 | 'SimpleHTTPServer',
212 | 'collections',
213 | 'bisect',
214 | 'symbol',
215 | 'htmlentitydefs',
216 | 'io',
217 | 'whichdb',
218 | 'traceback',
219 | 'filecmp',
220 | 'compileall',
221 | 'ntpath',
222 | 'codeop',
223 | 'StringIO',
224 | 'poplib',
225 | 'email',
226 | 'json',
227 | 'multiprocessing',
228 | 'hotshot',
229 | 'xml',
230 | 'unittest',
231 | 'lib2to3',
232 | 'encodings',
233 | 'distutils',
234 | 'idlelib',
235 | 'wsgiref',
236 | 'curses',
237 | 'importlib',
238 | 'compiler',
239 | 'sqlite3',
240 | 'ctypes',
241 | 'logging'
242 | ]
243 |
--------------------------------------------------------------------------------
/subpy/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdiehl/subpy/60e2bb9c9e7f542a121999cc324c4569ff2d23b2/subpy/tests/__init__.py
--------------------------------------------------------------------------------
/subpy/tests/test_features.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import unittest
3 | import importlib
4 |
5 | is_py3k = bool(sys.version_info[0] == 3)
6 |
7 | from subpy import detect
8 | from subpy import features as f
9 |
10 | tests = []
11 |
12 | #------------------------------------------------------------------------
13 |
14 | class TestFeatureDetect(unittest.TestCase):
15 |
16 | def has(self, feature, fn):
17 | self.assertTrue(feature in detect(fn))
18 |
19 | def not_has(self, feature, fn):
20 | self.assertTrue(feature not in detect(fn))
21 |
22 | def test_implicit_casts(self):
23 |
24 | def fn():
25 | return 1+2.0
26 |
27 | self.has(f.ImplicitCasts, fn)
28 |
29 | def test_generators(self):
30 |
31 | def fn():
32 | yield 1
33 |
34 | self.has(f.Generators, fn)
35 |
36 | def test_delvar(self):
37 |
38 | def fn():
39 | x = 3
40 | del x
41 |
42 | self.has(f.DelVar, fn)
43 |
44 | def test_closure(self):
45 |
46 | def fn():
47 | x = 3
48 | def closure():
49 | print(x)
50 |
51 | self.has(f.Closures, fn)
52 |
53 | def test_classes(self):
54 |
55 | def fn():
56 | class Class:
57 | pass
58 |
59 | self.has(f.Classes, fn)
60 |
61 | def test_decorators(self):
62 |
63 | def fn():
64 | @f
65 | def g():
66 | pass
67 |
68 | self.has(f.Decorators, fn)
69 |
70 | def test_varargs1(self):
71 |
72 | def fn():
73 | def foo(*xs):
74 | pass
75 |
76 | self.has(f.VarArgs, fn)
77 |
78 | def test_varargs2(self):
79 |
80 | def fn():
81 | list(map(id, *xs))
82 |
83 | self.has(f.VarArgs, fn)
84 |
85 | def test_kwargs1(self):
86 |
87 | def fn():
88 | def foo(**kw):
89 | pass
90 |
91 | self.has(f.KeywordArgs, fn)
92 |
93 | def test_kwargs2(self):
94 |
95 | def fn():
96 | foo(x=3)
97 |
98 | self.has(f.KeywordArgs, fn)
99 |
100 | def test_kwargs3(self):
101 |
102 | def fn():
103 | foo(**kw)
104 |
105 | self.has(f.KeywordArgs, fn)
106 |
107 | def test_inheritance(self):
108 |
109 | def fn():
110 | class foo(int):
111 | pass
112 |
113 | self.has(f.Inheritance, fn)
114 | self.not_has(f.MInheritance, fn)
115 |
116 | def test_minheritance(self):
117 |
118 | def fn():
119 | class foo(int, bool):
120 | pass
121 |
122 | self.has(f.MInheritance, fn)
123 | self.has(f.Inheritance, fn)
124 |
125 | def test_class_decorators(self):
126 |
127 | def fn():
128 | @foo
129 | class foo(object):
130 | pass
131 |
132 | self.has(f.ClassDecorators, fn)
133 |
134 | def test_assertions(self):
135 |
136 | def fn():
137 | assert True
138 |
139 | self.has(f.Assertions, fn)
140 |
141 | def test_chain_comparisons(self):
142 |
143 | def fn():
144 | x > y > z
145 |
146 | self.has(f.ChainComparison, fn)
147 |
148 | def test_exceptions1(self):
149 |
150 | def fn():
151 | raise Exception
152 |
153 | self.has(f.Exceptions, fn)
154 |
155 | def test_exceptions2(self):
156 |
157 | def fn():
158 | try:
159 | foo()
160 | except Exception as e:
161 | pass
162 |
163 | self.has(f.Exceptions, fn)
164 |
165 | def test_exceptions3(self):
166 |
167 | def fn():
168 | try:
169 | foo()
170 | finally:
171 | pass
172 |
173 | self.has(f.Exceptions, fn)
174 |
175 | def test_lambda(self):
176 |
177 | def fn():
178 | S = lambda x: lambda y: lambda z: x(z)(y(z))
179 | K = lambda x: lambda y: x
180 | I = lambda x: x
181 |
182 | self.has(f.Lambda, fn)
183 |
184 | def test_relative_imports(self):
185 |
186 | def fn():
187 | from . import foo
188 |
189 | self.has(f.RelativeImports, fn)
190 |
191 | def test_importstar(self):
192 |
193 | fn = """from sys import *"""
194 |
195 | self.has(f.ImportStar, fn)
196 |
197 | def test_heterolist(self):
198 |
199 | def fn():
200 | [1, 2.0, None, 'guido']
201 |
202 | self.has(f.HeteroList, fn)
203 |
204 | def test_continue(self):
205 |
206 | def fn():
207 | while True:
208 | continue
209 |
210 | self.has(f.Continue, fn)
211 |
212 | def test_multiple_return(self):
213 |
214 | def fn():
215 | return 1, None
216 |
217 | self.has(f.MultipleReturn, fn)
218 |
219 | def test_dict_comp(self):
220 |
221 | def fn():
222 | {a : b for a,b in xs}
223 |
224 | self.has(f.DictComp, fn)
225 |
226 | def test_ellipsis1(self):
227 |
228 | def fn():
229 | xs[1:2, ..., 0]
230 |
231 | self.has(f.Ellipsi, fn)
232 |
233 | def test_ellipsis2(self):
234 |
235 | def fn():
236 | xs[...]
237 |
238 | self.has(f.Ellipsi, fn)
239 |
240 | def test_tuple_unpacking(self):
241 |
242 | def fn():
243 | x, y = [1,2]
244 |
245 | self.has(f.TupleUnpacking, fn)
246 |
247 | def test_exec(self):
248 |
249 | def fn():
250 | exec('foo', {})
251 |
252 | self.has(f.Exec, fn)
253 |
254 | def test_fancy_indexing(self):
255 |
256 | def fn():
257 | A[1:, 20:10:-2, ...]
258 |
259 | self.has(f.FancyIndexing, fn)
260 |
261 | def test_globals(self):
262 |
263 | def fn():
264 | global x
265 |
266 | self.has(f.Globals, fn)
267 |
268 | def test_context_managers(self):
269 |
270 | def fn():
271 | with foo:
272 | pass
273 |
274 | self.has(f.ContextManagers, fn)
275 |
276 | def test_generator_exp1(self):
277 |
278 | def fn():
279 | (a for a in x)
280 |
281 | self.has(f.GeneratorExp, fn)
282 |
283 | def test_generator_exp2(self):
284 |
285 | def fn():
286 | f(a for a in x)
287 |
288 | self.has(f.GeneratorExp, fn)
289 |
290 | def test_ternary(self):
291 |
292 | def fn():
293 | a if True else b
294 |
295 | self.has(f.Ternary, fn)
296 |
297 | def test_listcomp(self):
298 |
299 | def fn():
300 | [a for a in x]
301 |
302 | self.has(f.ListComp, fn)
303 |
304 | def test_setcomp(self):
305 |
306 | def fn():
307 | {a for a in x}
308 |
309 | self.has(f.SetComp, fn)
310 |
311 | def test_custom_iterators1(self):
312 |
313 | def fn():
314 | for a in foo:
315 | pass
316 |
317 | self.has(f.CustomIterators, fn)
318 |
319 | def test_custom_iterators2(self):
320 |
321 | def fn():
322 | for a in set([1,2,3]):
323 | pass
324 |
325 | self.has(f.CustomIterators, fn)
326 |
327 | def test_custom_iterators3(self):
328 |
329 | def fn():
330 | for a in range(25):
331 | pass
332 | for a in range(25):
333 | pass
334 |
335 | self.not_has(f.CustomIterators, fn)
336 |
337 | def test_printing(self):
338 |
339 | def fn():
340 | print('hello world')
341 |
342 | self.has(f.Printing, fn)
343 |
344 | def test_metaclass(self):
345 |
346 | def fn():
347 | class Foo(object, metaclass=Bar):
348 | pass
349 |
350 | self.has(f.Metaclasses, fn)
351 |
352 | tests.append(TestFeatureDetect)
353 |
354 | #------------------------------------------------------------------------
355 |
356 | class TestStandardLibrary(unittest.TestCase):
357 |
358 | def test_fullstdlib(self):
359 | from subpy.stdlib import libraries
360 |
361 | for lib in libraries:
362 | mod = importlib.import_module(lib)
363 | detect(mod)
364 |
365 | tests.append(TestStandardLibrary)
366 |
367 | #------------------------------------------------------------------------
368 |
369 | class TestToplevel(unittest.TestCase):
370 |
371 | def test_detect(self):
372 | from subpy import detect, features
373 |
374 | def foo():
375 | lambda x:x
376 | x, y = (1,2)
377 |
378 | t1 = features.TupleUnpacking in detect(foo)
379 | t2 = features.Lambda in detect(foo)
380 |
381 | self.assertTrue(t1)
382 | self.assertTrue(t2)
383 |
384 | def test_checker(self):
385 | from subpy import checker
386 | from subpy.features import ListComp, SetComp
387 |
388 | def comps():
389 | return [x**2 for x in range(25)]
390 |
391 | my_subset = set([
392 | ListComp,
393 | SetComp,
394 | ])
395 |
396 | features = checker(comps)
397 |
398 | self.assertTrue(ListComp in features)
399 |
400 | def test_validator(self):
401 |
402 | from subpy import validator, FullPython, FeatureNotSupported
403 | from subpy.features import ListComp, SetComp
404 |
405 | def comps():
406 | return [x**2 for x in range(25)]
407 |
408 | my_features = FullPython - set([
409 | ListComp,
410 | SetComp,
411 | ])
412 |
413 | with self.assertRaises(FeatureNotSupported):
414 | validator(comps, features=my_features)
415 |
416 | tests.append(TestToplevel)
417 |
418 | #------------------------------------------------------------------------
419 |
420 | def run(verbosity=1, repeat=1):
421 | suite = unittest.TestSuite()
422 | for cls in tests:
423 | for _ in range(repeat):
424 | suite.addTest(unittest.makeSuite(cls))
425 |
426 | runner = unittest.TextTestRunner(verbosity=verbosity)
427 | return runner.run(suite)
428 |
429 | if __name__ == '__main__':
430 | run()
431 |
--------------------------------------------------------------------------------
/subpy/validate.py:
--------------------------------------------------------------------------------
1 | import re
2 | import ast
3 | import types
4 | import inspect
5 | from textwrap import dedent
6 | from collections import deque, defaultdict
7 |
8 | from .features import *
9 |
10 | FullPython = set([
11 | ImplicitCasts,
12 | Generators,
13 | DelVar,
14 | Closures,
15 | Classes,
16 | Decorators,
17 | VarArgs,
18 | KeywordArgs,
19 | Inheritance,
20 | MInheritance,
21 | ClassDecorators,
22 | Assertions,
23 | ChainComparison,
24 | Exceptions,
25 | Lambda,
26 | RelativeImports,
27 | ImportStar,
28 | HeteroList,
29 | Continue,
30 | MultipleReturn,
31 | DictComp,
32 | Ellipsi,
33 | TupleUnpacking,
34 | Exec,
35 | FancyIndexing,
36 | Globals,
37 | ContextManagers,
38 | GeneratorExp,
39 | Ternary,
40 | ListComp,
41 | SetComp,
42 | CustomIterators,
43 | Printing,
44 | Metaclasses
45 | ])
46 |
47 | def _compile_lib_matcher(libs):
48 | matches = []
49 | for allowed in libs:
50 | matches.append(allowed.replace('.', '\\.')\
51 | .replace('*', '.*$'))
52 | return r'|'.join(matches)
53 |
54 | #------------------------------------------------------------------------
55 | # AST Traversal
56 | #------------------------------------------------------------------------
57 |
58 | GLOBAL = 0
59 |
60 | class PythonVisitor(ast.NodeVisitor):
61 |
62 | def __init__(self, features, libs):
63 | self.scope = deque([('global', 0)])
64 | self.features = features
65 |
66 | if libs:
67 | self.libs = _compile_lib_matcher(libs)
68 | else:
69 | self.libs = None
70 |
71 | def __call__(self, source):
72 | if isinstance(source, types.ModuleType):
73 | source = dedent(inspect.getsource(source))
74 | if isinstance(source, types.FunctionType):
75 | source = dedent(inspect.getsource(source))
76 | if isinstance(source, types.LambdaType):
77 | source = dedent(inspect.getsource(source))
78 | elif isinstance(source, str):
79 | source = source
80 | else:
81 | raise NotImplementedError
82 |
83 | self._source = source
84 | self._ast = ast.parse(source)
85 | self.visit(self._ast)
86 |
87 | def nolib(self, node, library):
88 | #print 'NO SUPPORT! %s' % library
89 | #print self._source.split('\n')[node.lineno-1]
90 | raise SystemExit()
91 |
92 | def action(self, node, feature):
93 | raise NotImplementedError
94 |
95 | # -------------------------------------------------
96 |
97 | def visit_comprehension(self, node):
98 | if node.ifs:
99 | ifs = list(map(self.visit, node.ifs))
100 | target = self.visit(node.target)
101 | iter = self.visit(node.iter)
102 |
103 | def visit_keyword(self, node):
104 | value = self.visit(node.value)
105 |
106 | def check_arguments(self, node):
107 | args = node.args
108 |
109 | ### Check for variadic arguments
110 | if VarArgs not in self.features:
111 | if args.vararg:
112 | self.action(node, VarArgs)
113 |
114 | ### Check for keyword arguments
115 | if KeywordArgs not in self.features:
116 | if args.kwarg:
117 | self.action(node, KeywordArgs)
118 | if args.defaults:
119 | self.action(node, KeywordArgs)
120 |
121 | # -------------------------------------------------
122 |
123 | def visit_Assert(self, node):
124 | ## Check for assertions
125 | if Assertions not in self.features:
126 | self.action(node, Assertions)
127 |
128 | test = self.visit(node.test)
129 |
130 | def visit_Assign(self, node):
131 | targets = node.targets
132 |
133 | ## Check for tuple unpacking
134 | if TupleUnpacking not in self.features:
135 | if len(node.targets) > 1:
136 | self.action(node, TupleUnpacking)
137 |
138 | if any(isinstance(x, ast.Tuple) for x in node.targets):
139 | self.action(node, TupleUnpacking)
140 |
141 | for target in targets:
142 | self.visit(target)
143 | self.visit(node.value)
144 |
145 | ## Check for metaclasses
146 | if Metaclasses not in self.features:
147 | if isinstance(target, ast.Name) and target.id == '__metaclass__':
148 | self.action(node, Metaclasses)
149 |
150 | def visit_Attribute(self, node):
151 | value = self.visit(node.value)
152 |
153 | def visit_AugAssign(self, node):
154 | target = self.visit(node.target)
155 | value = self.visit(node.value)
156 | op = node.op.__class__
157 |
158 | def visit_BinOp(self, node):
159 | lhs = self.visit(node.left)
160 | rhs = self.visit(node.right)
161 | op_str = node.op.__class__
162 |
163 | ## Check for implicit coercions between numeric types
164 | if ImplicitCasts not in self.features:
165 | if isinstance(node.left, ast.Num) and isinstance(node.right, ast.Num):
166 | a = node.left.n
167 | b = node.right.n
168 | if type(a) != type(b) and ImplicitCasts not in self.features:
169 | self.action(node, ImplicitCasts)
170 |
171 | def visit_Break(self, node):
172 | pass
173 |
174 | def visit_BoolOp(self, node):
175 | operands = list(map(self.visit, node.values))
176 | operator = node.op.__class__
177 |
178 | ## Check for implicit coercions between numeric types
179 | if ImplicitCasts not in self.features:
180 | for operand in node.values:
181 | if isinstance(operand, ast.Num):
182 | self.action(node, ImplicitCasts)
183 |
184 | # PY3
185 | def visit_Bytes(self, node):
186 | pass
187 |
188 | def visit_Call(self, node):
189 | name = self.visit(node.func)
190 | args = list(map(self.visit, node.args))
191 | keywords = list(map(self.visit, node.keywords))
192 |
193 | # Python 2.x - 3.4
194 | if hasattr(node,"starargs") and node.starargs:
195 | ## Check for variadic arguments
196 | starargs = self.visit(node.starargs)
197 |
198 | if VarArgs not in self.features:
199 | self.action(node, VarArgs)
200 |
201 | if (hasattr(node,'keywords') and node.keywords) or \
202 | (hasattr(node,'kwargs') and node.kwargs):
203 | ## Check for keyword arguments
204 | kwargs = list(map(self.visit, node.keywords))
205 |
206 | if KeywordArgs not in self.features:
207 | self.action(node, KeywordArgs)
208 |
209 | # Python 3.5+
210 | # TODO
211 |
212 |
213 | def visit_ClassDef(self, node):
214 |
215 | if Classes not in self.features:
216 | self.action(node, Classes)
217 |
218 | if node.bases:
219 | bases = list(map(self.visit, node.bases))
220 |
221 | ## Check for single inheritance
222 | if len(bases) >= 1 and Inheritance not in self.features:
223 | self.action(node, Inheritance)
224 |
225 | ## Check for multiple inheritance
226 | if len(bases) > 1 and MInheritance not in self.features:
227 | self.action(node, MInheritance)
228 |
229 | if node.decorator_list:
230 | decorators = list(map(self.visit, node.decorator_list))
231 |
232 | ## Check for class decorators
233 | if decorators and ClassDecorators not in self.features:
234 | self.action(node, ClassDecorators)
235 |
236 |
237 | self.scope.append(('class', node))
238 | body = list(map(self.visit, node.body))
239 | self.scope.pop()
240 |
241 | def visit_Compare(self, node):
242 | ## Check for chained comparisons
243 | if len(node.comparators) > 1 and ChainComparison not in self.features:
244 | self.action(node, ChainComparison)
245 |
246 | operands = list(map(self.visit, [node.left] + node.comparators))
247 | operators = [op.__class__ for op in node.ops]
248 |
249 | def visit_Continue(self, node):
250 | ## Check for continue
251 | if Continue not in self.features:
252 | self.action(node, Continue)
253 |
254 | def visit_Delete(self, node):
255 | target = list(map(self.visit, node.targets))
256 | if DelVar not in self.features:
257 | self.action(node, DelVar)
258 |
259 | def visit_Dict(self, node):
260 | keys = list(map(self.visit, node.keys))
261 | values = list(map(self.visit, node.values))
262 |
263 | def visit_DictComp(self, node):
264 | key = self.visit(node.key)
265 | value = self.visit(node.value)
266 | gens = list(map(self.visit, node.generators))
267 |
268 | ## Check for dictionary comprehensions
269 | if DictComp not in self.features:
270 | self.action(node, DictComp)
271 |
272 | def visit_Ellipsis(self, node):
273 | pass
274 |
275 | def visit_ExtSlice(self, node):
276 | list(map(self.visit, node.dims))
277 |
278 | def visit_ExceptHandler(self, node):
279 |
280 | if node.name:
281 | name = node.name.id
282 | #if node.type:
283 | #type = node.type.id
284 | body = list(map(self.visit, node.body))
285 |
286 | if Exceptions not in self.features:
287 | self.action(node, Exceptions)
288 |
289 | def visit_Exec(self, node):
290 | ## Check for dynamic exec
291 | if Exec not in self.features:
292 | self.action(node, Exec)
293 |
294 | body = self.visit(node.body)
295 | if node.globals:
296 | globals = self.visit(node.globals)
297 | if node.locals:
298 | locals = self.visit(node.locals)
299 |
300 | def visit_Expr(self, node):
301 | value = self.visit(node.value)
302 |
303 | def visit_For(self, node):
304 | target = self.visit(node.target)
305 | iter = self.visit(node.iter)
306 | body = list(map(self.visit, node.body))
307 | orelse = list(map(self.visit, node.orelse))
308 |
309 | ## Check for custom iterators
310 | if CustomIterators not in self.features:
311 | if not isinstance(node.iter, ast.Call):
312 | self.action(node, CustomIterators)
313 | elif isinstance(node.iter.func, ast.Name):
314 | if node.iter.func.id not in ['xrange', 'range']:
315 | self.action(node, CustomIterators)
316 | else:
317 | self.action(node, CustomIterators)
318 |
319 | ## Check for tuple unpacking
320 | if TupleUnpacking not in self.features:
321 | if isinstance(node.target, ast.Tuple):
322 | self.action(node.target, TupleUnpacking)
323 |
324 | def visit_FunctionDef(self, node):
325 | self.check_arguments(node)
326 |
327 | if node.decorator_list:
328 | decorators = list(map(self.visit, node.decorator_list))
329 |
330 | ## Check for class decorators
331 | if decorators and Decorators not in self.features:
332 | self.action(node, Decorators)
333 |
334 | ## Check for closures
335 | scope_ty, scope = self.scope[-1]
336 |
337 | if scope_ty == 'function' and scope != GLOBAL:
338 | if Closures not in self.features:
339 | self.action(node, Closures)
340 |
341 | self.scope.append(('function', node))
342 |
343 | for defn in node.body:
344 | self.visit(defn)
345 | self.scope.pop()
346 |
347 | def visit_Global(self, node):
348 | ## Check for globals
349 | if Globals not in self.features:
350 | self.action(node, Globals)
351 |
352 | def visit_GeneratorExp(self, node):
353 | self.visit(node.elt)
354 | list(map(self.visit, node.generators))
355 |
356 | ## Check for dictionary comprehensions
357 | if GeneratorExp not in self.features:
358 | self.action(node, GeneratorExp)
359 |
360 | def visit_If(self, node):
361 | test = self.visit(node.test)
362 | body = list(map(self.visit, node.body))
363 | orelse = list(map(self.visit, node.orelse))
364 |
365 | def visit_IfExp(self, node):
366 | test = self.visit(node.test)
367 | body = self.visit(node.body)
368 | orelse = self.visit(node.orelse)
369 |
370 | if Ternary not in self.features:
371 | self.action(node, Ternary)
372 |
373 | def visit_Import(self, node):
374 | matcher = self.libs
375 |
376 | ## Check for unsupported libraries
377 | def check_import(name):
378 | if name.startswith('.') and RelativeImports not in self.features:
379 | self.action(node, RelativeImports)
380 |
381 | if not re.match(matcher, name):
382 | self.nolib(node, name)
383 |
384 | for package in node.names:
385 | if self.libs:
386 | check_import(package.name)
387 |
388 | def visit_ImportFrom(self, node):
389 | matcher = self.libs
390 |
391 | ## Check for unsupported libraries
392 | def check_import(name):
393 |
394 | if not re.match(matcher, name):
395 | self.nolib(node, name)
396 |
397 | for package in node.names:
398 | ## Check for "import *"
399 | if package.name == '*' and ImportStar not in self.features:
400 | self.action(node, ImportStar)
401 |
402 | if node.module == None:
403 | if RelativeImports not in self.features:
404 | self.action(node, RelativeImports)
405 |
406 | if node.module and self.libs:
407 | munged = node.module + '.' + package.name
408 | check_import(munged)
409 |
410 | def visit_Index(self, node):
411 | self.visit(node.value)
412 |
413 | def visit_Lambda(self, node):
414 | ## Check for lambdas
415 | if Lambda not in self.features:
416 | self.action(node, Lambda)
417 |
418 | self.check_arguments(node)
419 |
420 | #args = self.visit(node.args)
421 | body = self.visit(node.body)
422 |
423 | def visit_List(self, node):
424 | elts = list(map(self.visit, node.elts))
425 |
426 | ## Check for hetereogenous lists
427 | if node.elts and not HeteroList in self.features:
428 | ty = type(node.elts[0])
429 | for el in node.elts[1:]:
430 | if type(el) != ty:
431 | self.action(node, HeteroList)
432 |
433 | def visit_ListComp(self, node):
434 | elt = self.visit(node.elt)
435 | gens = list(map(self.visit, node.generators))
436 |
437 | ## Check for list comprehensions
438 | if ListComp not in self.features:
439 | self.action(node, ListComp)
440 |
441 | def visit_Name(self, node):
442 | pass
443 |
444 | def visit_Num(self, node):
445 | pass
446 |
447 | def visit_Module(self, node):
448 | body = list(map(self.visit, node.body))
449 |
450 | def visit_Pass(self, node):
451 | pass
452 |
453 | def visit_Print(self, node):
454 | ## Check for printing
455 | if Printing not in self.features:
456 | self.action(node, Printing)
457 |
458 | if node.dest:
459 | dest = self.visit(node.dest)
460 |
461 | values = list(map(self.visit, node.values))
462 |
463 | def visit_Raise(self, node):
464 | if node.type:
465 | self.visit(node.type)
466 |
467 | ## Check for exceptions
468 | if Exceptions not in self.features:
469 | self.action(node, Exceptions)
470 |
471 | def visit_Return(self, node):
472 | if node.value:
473 | self.visit(node.value)
474 |
475 | ## Check for multiple returns
476 | if isinstance(node.value, ast.Tuple) and MultipleReturn not in self.features:
477 | self.action(node, MultipleReturn)
478 |
479 | def visit_Set(self, node):
480 | elts = list(map(self.visit, node.elts))
481 |
482 | def visit_SetComp(self, node):
483 | elt = self.visit(node.elt)
484 | gens = list(map(self.visit, node.generators))
485 |
486 | ## Check for set comprehensions
487 | if SetComp not in self.features:
488 | self.action(node, SetComp)
489 |
490 | def visit_Slice(self, node):
491 | lower = node.lower
492 | if lower is not None:
493 | lower = self.visit(lower)
494 |
495 | upper = node.upper
496 | if upper is not None:
497 | upper = self.visit(upper)
498 |
499 | step = node.step
500 | if step is not None:
501 | step = self.visit(step)
502 |
503 | def visit_Str(self, node):
504 | pass
505 |
506 | def visit_Starred(self, node):
507 | self.visit(node.value)
508 |
509 | def visit_Subscript(self, node):
510 | value = self.visit(node.value)
511 | slice = self.visit(node.slice)
512 |
513 | ## Check for fancy indexing
514 | if isinstance(node.slice, ast.ExtSlice) and FancyIndexing not in self.features:
515 | self.action(node, FancyIndexing)
516 |
517 | ## Check for ellipsis
518 | if Ellipsi not in self.features:
519 | if any(type(a) == ast.Ellipsis for a in node.slice.dims):
520 | self.action(node, Ellipsi)
521 |
522 | if isinstance(node.slice, ast.Ellipsis) and Ellipsi not in self.features:
523 | self.action(node, Ellipsi)
524 |
525 | def visit_TryExcept(self, node):
526 | body = list(map(self.visit, node.body))
527 | if node.handlers:
528 | handlers = list(map(self.visit, node.handlers))
529 |
530 | if node.orelse:
531 | orelse = list(map(self.visit, node.orelse))
532 |
533 | ## Check for exceptions
534 | if Exceptions not in self.features:
535 | self.action(node, Exceptions)
536 |
537 | def visit_TryFinally(self, node):
538 | body = list(map(self.visit, node.body))
539 | finalbody = list(map(self.visit, node.finalbody))
540 |
541 | ## Check for exceptions
542 | if Exceptions not in self.features:
543 | self.action(node, Exceptions)
544 |
545 | def visit_Tuple(self, node):
546 | return list(map(self.visit, node.elts))
547 |
548 | def visit_UnaryOp(self, node):
549 | operand = self.visit(node.operand)
550 | op = node.op.__class__
551 |
552 | def visit_While(self, node):
553 | test = self.visit(node.test)
554 | body = list(map(self.visit, node.body))
555 | if node.orelse:
556 | orelse = list(map(self.visit, node.orelse))
557 |
558 | def visit_With(self, node):
559 | ## Check for context managers
560 | if ContextManagers not in self.features:
561 | self.action(node, ContextManagers)
562 |
563 | exp = self.visit(node.context_expr)
564 | if node.optional_vars:
565 | var = node.optional_vars and self.visit(node.optional_vars)
566 | body = list(map(self.visit, node.body))
567 |
568 | def visit_Yield(self, node):
569 | ## Check for generators
570 | if Generators not in self.features:
571 | self.action(node, Generators)
572 |
573 | if node.value:
574 | value = self.visit(node.value)
575 |
576 | # PY3
577 | def visit_YieldFrom(self, node):
578 | if Generators not in self.features:
579 | self.action(node, Generators)
580 |
581 | if node.value:
582 | value = self.visit(node.value)
583 |
584 | def generic_visit(self, node):
585 | assert 0
586 |
587 | #------------------------------------------------------------------------
588 | # Validator
589 | #------------------------------------------------------------------------
590 |
591 | class FeatureNotSupported(SyntaxError): pass
592 |
593 | class Validator(PythonVisitor):
594 | """ Check if the given source conforms to the feature set or
595 | raise an Exception. """
596 |
597 | def action(self, node, feature):
598 | line = self._source.splitlines()[node.lineno-1]
599 | lineno = node.lineno
600 | offset = node.col_offset
601 | raise FeatureNotSupported(feature, ('', lineno, offset + 1, line))
602 |
603 | class Checker(PythonVisitor):
604 | """ Aggregate sites for features that don't conform to the
605 | given feature set """
606 |
607 | def __init__(self, features, libraries):
608 | super(Checker, self).__init__(features, libraries)
609 | self.detected = None
610 |
611 | def action(self, node, feature):
612 | self.detected[feature].append(node.lineno)
613 |
614 | def __call__(self, source):
615 | self.detected = defaultdict(list)
616 | super(Checker, self).__call__(source)
617 | return dict(self.detected)
618 |
619 | class Detect(PythonVisitor):
620 | """ Aggregate sites for conform to the given feature set """
621 |
622 | def __init__(self):
623 | super(Detect, self).__init__(set(), [])
624 | self.detected = None
625 |
626 | def action(self, node, feature):
627 | self.detected[feature].append(node.lineno)
628 |
629 | def __call__(self, source):
630 | self.detected = defaultdict(list)
631 | super(Detect, self).__call__(source)
632 | return dict(self.detected)
633 |
634 | def detect(source):
635 | d = Detect()
636 | return d(source)
637 |
638 | def checker(source, features=None, libraries=None):
639 | d = Checker(features or set(), libraries or list())
640 | return d(source)
641 |
642 | def validator(source, features=None, libraries=None):
643 | d = Validator(features or set(), libraries or list())
644 | return d(source)
645 |
646 | fd = detect
647 |
--------------------------------------------------------------------------------