diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/language/python/compile.scm | 1 | ||||
-rw-r--r-- | modules/language/python/exceptions.scm | 29 | ||||
-rw-r--r-- | modules/language/python/module.scm | 10 | ||||
-rw-r--r-- | modules/language/python/module/_python.scm | 1 | ||||
-rw-r--r-- | modules/language/python/module/decimal.scm | 116 | ||||
-rw-r--r-- | modules/language/python/module/fnmatch.py | 110 | ||||
-rw-r--r-- | modules/language/python/module/functools.scm | 3 | ||||
-rw-r--r-- | modules/language/python/module/genericpath.py | 153 | ||||
-rw-r--r-- | modules/language/python/module/glob.py | 173 | ||||
-rw-r--r-- | modules/language/python/module/posixpath.py | 521 | ||||
-rw-r--r-- | modules/language/python/number.scm | 3 | ||||
-rw-r--r-- | modules/language/python/range.scm | 2 |
12 files changed, 1051 insertions, 71 deletions
diff --git a/modules/language/python/compile.scm b/modules/language/python/compile.scm index ba07241..b2f5ea7 100644 --- a/modules/language/python/compile.scm +++ b/modules/language/python/compile.scm @@ -379,6 +379,7 @@ ((__rshift__) (N 'py-rshift)) ((__rlshift__) (N 'py-rlshift)) ((__rrshift__) (N 'py-rrshift)) + ((bit_length) (N 'py-bit-length)) ((as_integer_ratio) (N 'py-as-integer-ratio)) ((conjugate) (N 'py-conjugate)) ((denominator) (N 'py-denominator)) diff --git a/modules/language/python/exceptions.scm b/modules/language/python/exceptions.scm index 4ce744a..990f9c3 100644 --- a/modules/language/python/exceptions.scm +++ b/modules/language/python/exceptions.scm @@ -11,7 +11,8 @@ ModuleNotFoundError BlockingIOError InterruptedError BaseException ZeroDivisionError ArithmeticError - OverflowError RecursionError)) + OverflowError RecursionError + Warning DeprecationWarning BytesWarning)) (define-syntax-rule (aif it p x y) (let ((it p)) (if it x y))) @@ -31,6 +32,22 @@ (format #f "~a" (rawref self '__name__)))))) +(define-python-class Warning () + (define __init__ + (case-lambda + ((self) + (values)) + ((self val . l) + (set self 'value val)))) + + (define __repr__ + (lambda (self) + (aif it (rawref self 'value #f) + (format #f "~a:~a" + (rawref self '__name__) it) + (format #f "~a" + (rawref self '__name__)))))) + (define-syntax define-er (syntax-rules () ((_ nm k) @@ -77,3 +94,13 @@ + +(define-syntax define-wr + (syntax-rules () + ((_ nm k) + (define-python-class nm (Warning))) + ((_ nm w k) + (define-python-class nm w)))) + +(define-wr BytesWarning 'BytesWarning) +(define-wr DepricationWarning 'DeprecationWarning) diff --git a/modules/language/python/module.scm b/modules/language/python/module.scm index 87f9207..52ec88b 100644 --- a/modules/language/python/module.scm +++ b/modules/language/python/module.scm @@ -42,7 +42,7 @@ (define _modules (make-hash-table)) (define __setprivate__ (lambda (self p) - (rawset self '_isprivate p))) + (rawset self '_private p))) (define _cont (lambda (self id pre l nm skip-error?) @@ -123,7 +123,7 @@ (define _make (lambda (self l nm skip-error?) - (rawset self '_private #f) + (rawset self '_private #t) (if (not (rawref self '_module)) (begin (rawset self '__name__ (string-join @@ -133,10 +133,10 @@ (if (and (not skip-error?) (not public-i)) (raise (ModuleNotFoundError (format #f "No module named ~a" - (ref self '__name__))))) + (rawref self '__name__))))) - (rawset self '_export (module-public-interface _module)) - (rawset self '_module _module) + (rawset self '_export (module-public-interface _module)) + (rawset self '_module _module) (hash-set! _modules l self)))))) (define __getattribute__ diff --git a/modules/language/python/module/_python.scm b/modules/language/python/module/_python.scm index 47df9fc..2fac0a1 100644 --- a/modules/language/python/module/_python.scm +++ b/modules/language/python/module/_python.scm @@ -40,6 +40,7 @@ len dir next dict None property range tuple bytes bytearray eval locals globals exec type object open __import__ + Warning BytesWarning DeprecationWarning ) #:export (print repr complex float int str diff --git a/modules/language/python/module/decimal.scm b/modules/language/python/module/decimal.scm index 4f2f6fe..0778da1 100644 --- a/modules/language/python/module/decimal.scm +++ b/modules/language/python/module/decimal.scm @@ -50,7 +50,13 @@ ;; Limits for the C version for compatibility MAX_PREC MAX_EMAX MIN_EMIN MIN_ETINY)) +(define (abool x) + (if (eq? x None) + None + (bool x))) + (define-syntax-rule (aif it p . l) (let ((it p)) (if (bool it) . l))) +(define-syntax-rule (aaif it p . l) (let ((it p)) (if (abool it) . l))) (define-syntax-rule (D x) (lambda () x)) @@ -620,7 +626,7 @@ This is the copyright information of the file ported over to scheme ((_ (when . u) . l) (begin (when . u) (twix . l))) ((_ (a it code ...) . l) - (aif it a (begin code ...) (twix . l))))) + (aaif it a (begin code ...) (twix . l))))) (define-syntax-rule (norm-op self op) (begin @@ -1196,21 +1202,19 @@ This is the copyright information of the file ported over to scheme Rounds, if it has reason. " - (pk '__neg__) (twix ((un-special self context) it it) - (let () (pk 1)) (let* ((context (if (eq? context None) (getcontext) context)) - (ans (if (pk (and (not (bool self)) + (ans (if (and (not (bool self)) (not (eq? (cx-rounding context) - ROUND_FLOOR)))) + ROUND_FLOOR))) ;; -Decimal('0') is Decimal('0'), ;; not Decimal('-0'), except ;; in ROUND_FLOOR rounding mode. - ((pk (ref self 'copy_abs))) - ((pk (ref self 'copy_negate)))))) + ((ref self 'copy_abs)) + ((ref self 'copy_negate))))) ((ref ans '_fix) context))))) (define __pos__ @@ -1473,7 +1477,6 @@ This is the copyright information of the file ported over to scheme (set! exp exp-) coeff-)))))))))) - (pk 'div sign coeff exp) (let ((ans (_dec_from_triple sign (str coeff) exp))) ((ref ans '_fix) context)))))) @@ -1864,7 +1867,7 @@ This is the copyright information of the file ported over to scheme (set! ans ((cx-error context) Overflow "above Emax" (ref self '_sign))) - (set! ans (_dec_from_triple (ref self '_sign) (pk 'c coeff) (pk 'e exp_min)))) + (set! ans (_dec_from_triple (ref self '_sign) coeff exp_min))) ;; raise the appropriate signals, taking care to respect ;; the precedence described in the specification (if (and changed self_is_subnormal) @@ -2122,13 +2125,13 @@ This is the copyright information of the file ported over to scheme ((norm-op self other ) it it) ((norm-op self modulo) it it) (let (get-context context)) - + (let () (pk 1)) ;; deal with NaNs: if there are any sNaNs then first one wins, ;; (i.e. behaviour for NaNs is identical to that of fma) (let ((self_is_nan (ref self '_isnan)) (other_is_nan (ref other '_isnan)) (modulo_is_nan (ref modulo '_isnan)))) - + (let () (pk 2)) ((or (bool self_is_nan) (bool other_is_nan) (bool modulo_is_nan)) it (cond ((= self_is_nan 2) @@ -2143,7 +2146,7 @@ This is the copyright information of the file ported over to scheme (_fix_nan other context)) (else (_fix_nan modulo context)))) - + (let () (pk 3)) ;;check inputs: we apply same restrictions as Python's pow() ((not (and ((ref self '_isinteger)) ((ref other '_isinteger)) @@ -2151,16 +2154,16 @@ This is the copyright information of the file ported over to scheme ((cx-error context) InvalidOperation (+ "pow() 3rd argument not allowed " "unless all arguments are integers"))) - + (let () (pk 4)) ((< other 0) it ((cx-error context) InvalidOperation (+ "pow() 2nd argument cannot be " "negative when 3rd argument specified"))) - + (let () (pk 5)) ((not (bool modulo)) it ((cx-error context) InvalidOperation "pow() 3rd argument cannot be 0")) - + (let () (pk 6)) ;; additional restriction for decimal: the modulus must be less ;; than 10**prec in absolute value ((>= ((ref modulo 'adjusted)) (cx-prec context)) it @@ -2168,7 +2171,7 @@ This is the copyright information of the file ported over to scheme (+ "insufficient precision: pow() 3rd " "argument must not have more than " "precision digits"))) - + (let () (pk 7)) ;; define 0**0 == NaN, for consistency with two-argument pow ;; (even though it hurts!) ((and (not (bool other)) (not (bool self))) it @@ -2176,7 +2179,7 @@ This is the copyright information of the file ported over to scheme (+ "at least one of pow() 1st argument " "and 2nd argument must be nonzero ;" "0**0 is not defined"))) - + (let () (pk 8)) ;; compute sign of result (let ((sign (if ((ref other '_iseven)) 0 @@ -2184,23 +2187,23 @@ This is the copyright information of the file ported over to scheme (base (_WorkRep ((ref self 'to_integral_value)))) (exponent (_WorkRep ((ref other 'to_integral_value))))) - + (let () (pk 9)) ;; convert modulo to a Python integer, and self and other to ;; Decimal integers (i.e. force their exponents to be >= 0) (set! modulo (abs (int modulo))) - + (let () (pk 10)) ;; compute result using integer pow() (set! base (guile:modulo (* (guile:modulo (ref base 'int) modulo) (modulo-expt 10 (ref base 'exp) modulo)) modulo)) - + (let () (pk 11)) (let lp ((i (ref exponent 'exp))) (if (> i 0) (begin (set! base (modulo-expt base 10 modulo)) (lp (- i 1))))) - + (let () (pk 12)) (set! base (modulo-expt base (ref exponent 'int) modulo)) (_dec_from_triple sign (str base) 0))))) @@ -2263,7 +2266,6 @@ This is the copyright information of the file ported over to scheme ;; so |y| < 1/xe and the result is not representable. ;; Similarly, len(str(abs(yc)*xc_bits)) <= -ye implies |y| ;; < 1/nbits(xc). - (twix (let () (define-syntax-rule (clean xc xe n +) @@ -2273,7 +2275,6 @@ This is the copyright information of the file ported over to scheme (set! xc (/ xc n)) (set! xe (+ xe 1)) (lp)))))) - (let* ((x (_WorkRep self)) (xc (ref x 'int)) (xe (ref x 'exp))) @@ -2283,15 +2284,13 @@ This is the copyright information of the file ported over to scheme (yc (ref y 'int)) (ye (ref y 'exp))) (clean yc ye 10 +)) - ;; case where xc == 1: result is 10**(xe*y), with xe*y ;; required to be an integer - ((= xc 1) it - (set! xe (* xe yc)) + ((= xc 1) it + (set! xe (* xe yc)) ;; result is now 10**(xe * 10**ye); xe * 10**ye must be integral (clean xe ye 10 +) - (if (< ye 0) None (let ((exponent (* xe (expt 10 ye))) @@ -2308,8 +2307,8 @@ This is the copyright information of the file ported over to scheme (_dec_from_triple 0 (+ "1" (* "0" zeros)) (- exponent zeros))))) - ;; case where y is negative: xc must be either a power - ;; of 2 or a power of 5. + ;; case where y is negative: xc must be either a power + ;; of 2 or a power of 5. ((= (ref y 'sign) 1) it (let ((last_digit (modulo xc 10))) (twix @@ -2364,7 +2363,7 @@ This is the copyright information of the file ported over to scheme ((> e emax) it None) - + (begin (set! xc (expt 5 e)) #f))) @@ -2381,7 +2380,6 @@ This is the copyright information of the file ported over to scheme ((not (= remainder 0)) it None) - (let () (clean xc e 5 -)) ;; Guard against large values of ye, using the same logic as in @@ -2417,6 +2415,7 @@ This is the copyright information of the file ported over to scheme ;; now y is positive; find m and n such that y = m/n (let ((m #f) (n #f) (xc_bits (_nbits xc)))) + ((if (>= ye 0) (begin (set! m (* yc (expt 10 ye))) @@ -2454,7 +2453,7 @@ This is the copyright information of the file ported over to scheme ((and (not (= xc 1)) (<= xc_bits n)) it None) - ((not (= (modulo xe n) 0)) it + ((not (= (guile:modulo xe n) 0)) it None) (begin @@ -2465,7 +2464,7 @@ This is the copyright information of the file ported over to scheme (let lp () (let* ((x (expt a (- n 1))) (q (quotient xc x)) - (r (modulo xc x))) + (r (guile:modulo xc x))) (if (<= a q) (if (not (and (= a q) (= r 0))) None @@ -2587,7 +2586,7 @@ This is the copyright information of the file ported over to scheme (pylist-ref _SignedInfinity result_sign))) ;; Inf**(+ve or Inf) = Inf; Inf**(-ve or -Inf) = 0 - ((bool ((self '_isinfinity))) it + ((bool ((ref self '_isinfinity))) it (if (= (ref other '_sign) 0) (pylist-ref _SignedInfinity result_sign) (_dec_from_triple result_sign "0" 0))) @@ -2596,7 +2595,7 @@ This is the copyright information of the file ported over to scheme ;; depend on the exponent of self, and on whether other is a ;; positive integer, a negative integer, or neither (let ((prec (cx-prec context)))) - + ((equal? self _One) it (let ((exp #f)) (if ((ref other '_isinteger)) @@ -2666,6 +2665,7 @@ This is the copyright information of the file ported over to scheme ;; try for an exact result with precision +1 (when (eq? ans None) (set! ans ((ref self '_power_exact) other (+ prec 1))) + (let () (pk 2 0)) (when (not (eq? ans None)) (if (= result_sign 1) (set! ans (_dec_from_triple 1 (ref ans '_int) @@ -2695,7 +2695,7 @@ This is the copyright information of the file ported over to scheme (call-with-values (lambda () (_dpower xc xe yc ye (+ p extra))) (lambda (coeff exp) - (if (modulo coeff + (if (guile:modulo coeff (* 5 (expt 10 (- (len (str coeff)) p 1)))) (values coeff exp) (lp (+ extra 3))))))) @@ -3396,10 +3396,8 @@ This is the copyright information of the file ported over to scheme (twix (let (get-context context)) - ;; exp(NaN) = NaN (let ((ans ((ref self '_check_nans) #:context context)))) - (ans it it) ;; exp(-Infinity) = 0 @@ -3472,7 +3470,6 @@ This is the copyright information of the file ported over to scheme ;; at this stage, ans should round correctly with *any* ;; rounding mode, not just with ROUND_HALF_EVEN - (let* ((context ((ref context '_shallow_copy))) (rounding ((ref context '_set_rounding) ROUND_HALF_EVEN)) (ans ((ref ans '_fix) context))) @@ -3585,36 +3582,33 @@ This is the copyright information of the file ported over to scheme (twix (let (get-context context)) - (let () (pk 4 1)) ;; ln(NaN) = NaN (let ((ans ((ref self '_check_nans) #:context context)))) - (let () (pk 4 1.2 ans)) + (ans it it) - (let () (pk 4 2)) ;; ln(0.0) == -Infinity ((not (bool self)) it _NegativeInfinity) - (let () (pk 4)) + ;; ln(Infinity) = Infinity ((= ((ref self '_isinfinity)) 1) it _Infinity) - (let () (pk 4 3)) + ;; ln(1.0) == 0.0 ((equal? self _One) it _Zero) - (let () (pk 4 4)) + ;; ln(negative) raises InvalidOperation (if (= (ref self '_sign) 1) ((cx-error context) InvalidOperation "ln of a negative value")) - (let () (pk 4 5)) + ;; result is irrational, so necessarily inexact (let* ((op (_WorkRep self)) (c (ref op 'int)) (e (ref op 'exp)) (p (cx-prec context)))) - (let () (pk 4 6)) ;; correctly rounded result: repeatedly increase precision by 3 ;; until we get an unambiguously roundable result @@ -3623,10 +3617,8 @@ This is the copyright information of the file ported over to scheme 2)) ;; at least p+3 places (ans #f)) (let lp ((places places)) - (pk 'places places) (let ((coeff (_dlog c e places))) ;; assert len(str(abs(coeff)))-p >= 1 - (pk 'coeff coeff) (if (not (= (modulo coeff (* 5 (expt 10 (- (len (str (abs coeff))) p 1)))) @@ -6228,7 +6220,7 @@ This is the copyright information of the file ported over to scheme ;;##### Integer arithmetic functions used by ln, log10, exp and __pow__ ##### -(define _nbits (ref int 'bit_length)) +(define _nbits py-bit-length) (define _decimal_lshift_exact (lambda (n e) @@ -6322,7 +6314,7 @@ This is the copyright information of the file ported over to scheme ;; is actually an integer approximation to 2**R*y*M, where R is the ;; number of reductions performed so far. - ;; argument reduction; R = number of reductions performed + ;; argument reduction; R = number of reductions performed (call-with-values (lambda () (let lp ((y (- x M)) (R 0)) @@ -6380,7 +6372,6 @@ This is the copyright information of the file ported over to scheme "Given integers c, e and p with c > 0, compute an integer approximation to 10**p * log(c*10**e), with an absolute error of at most 1. Assumes that c*10**e is not exactly 1." - (pk '_dlog) ;; Increase precision by 2. The precision increase is compensated ;; for at the end with a division by 100. (set! p (+ p 2)) @@ -6405,10 +6396,10 @@ This is the copyright information of the file ported over to scheme ;; p <= 0: just approximate the whole thing by 0; error < 2.31 0)) (lambda (log_d) - (pk 'log_d) (call-with-values (lambda () ;; compute approximation to f*10**p*log(10), with error < 11. + (pk 'log_d log_d) (if (not (= f 0)) (let ((extra (- (len (str (abs f))) 1))) (if (>= (+ p extra) 0) @@ -6419,6 +6410,7 @@ This is the copyright information of the file ported over to scheme 0)) (lambda (f_log_ten) ;; error in sum < 11+27 = 38; error after division < 0.38 + 0.5 < 1 + (pk 'log_ten f_log_ten) (_div_nearest (+ f_log_ten log_d) 100)))))))) (define-python-class _Log10Memoize () @@ -6484,11 +6476,13 @@ This is the copyright information of the file ported over to scheme ;; expm1(z/2**(R-1)), ... , exp(z/2), exp(z). ;; Find R such that x/2**R/M <= 2**-L + (let ((R (_nbits (floor-quotient (ash x L) M)))) ;; Taylor series. (2**L)**T > M (let* ((T (- (int (floor-quotient (* -10 (len (str M))) (* 3 L))))) (y1 (let ((Mshift (ash M R))) (for ((i : (range (- T 1) 0 -1))) ((y (_div_nearest x T))) + (pk 'y i y) (_div_nearest (* x (+ Mshift y)) (* Mshift i)) #:final y))) @@ -6498,7 +6492,7 @@ This is the copyright information of the file ported over to scheme (let ((Mshift (ash M (+ k 2)))) (_div_nearest (* y (+ y Mshift)) Mshift)) #:final y))) - + (pk '_iexp x M (+ M y2) R T y1) (+ M y2))))) (define _dexp @@ -6515,14 +6509,13 @@ This is the copyright information of the file ported over to scheme digits of precision and with an error in d of at most 1. This is almost, but not quite, the same as the error being < 1ulp: when d = 10**(p-1) the error could be up to 10 ulp." - ;; we'll call iexp with M = 10**(p+2), giving p+3 digits of precision (set! p (+ p 2)) - + (pk '_dexp c e p) ;; compute log(10) with extra precision = adjusted exponent of c*10**e (let* ((extra (max 0 (+ e (len (str c)) -1))) (q (+ p extra))) - + ;; compute quotient c*10**e/(log(10)) = c*10**(e+q)/(log(10)*10**q), ;; rounding down (let* ((shift (+ e q)) @@ -6555,7 +6548,7 @@ This is the copyright information of the file ported over to scheme We assume that: x is positive and not equal to 1, and y is nonzero. " - + (pk xc xe yc ye p) (let* ;; Find b such that 10**(b-1) <= |y| <= 10**b ((b (+ (len (str (abs yc))) ye)) @@ -6579,10 +6572,9 @@ This is the copyright information of the file ported over to scheme (call-with-values (lambda () (_dexp ps (- (+ p 1)) (+ p 1))) - (lambda (coeff exp) - (values (_div_nearest coeff 10) - (+ exp 1)))))))) - + (lambda (coeff exp) + (values (pk 1 (_div_nearest coeff 10)) + (pk 2 (+ exp 1))))))))) (define _corr (dict '(("1" . 100) ("2" . 70) ("3" . 53) ("4" . 40) ("5" . 31) ("6" . 23 ) ("7" . 16) ("8" . 10) ("9" . 5)))) (define _log10_lb diff --git a/modules/language/python/module/fnmatch.py b/modules/language/python/module/fnmatch.py new file mode 100644 index 0000000..07c9924 --- /dev/null +++ b/modules/language/python/module/fnmatch.py @@ -0,0 +1,110 @@ +module(fnmatch) +"""Filename matching with shell patterns. + +fnmatch(FILENAME, PATTERN) matches according to the local convention. +fnmatchcase(FILENAME, PATTERN) always takes case in account. + +The functions operate by translating the pattern into a regular +expression. They cache the compiled regular expressions for speed. + +The function translate(PATTERN) returns a regular expression +corresponding to PATTERN. (It does not compile it.) +""" +import os +import posixpath +import re +import functools + +__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"] + +def fnmatch(name, pat): + """Test whether FILENAME matches PATTERN. + + Patterns are Unix shell style: + + * matches everything + ? matches any single character + [seq] matches any character in seq + [!seq] matches any char not in seq + + An initial period in FILENAME is not special. + Both FILENAME and PATTERN are first case-normalized + if the operating system requires it. + If you don't want this, use fnmatchcase(FILENAME, PATTERN). + """ + name = os.path.normcase(name) + pat = os.path.normcase(pat) + return fnmatchcase(name, pat) + +@functools.lru_cache(maxsize=256, typed=True) +def _compile_pattern(pat): + if isinstance(pat, bytes): + pat_str = str(pat, 'ISO-8859-1') + res_str = translate(pat_str) + res = bytes(res_str, 'ISO-8859-1') + else: + res = translate(pat) + return re.compile(res).match + +def filter(names, pat): + """Return the subset of the list NAMES that match PAT.""" + result = [] + pat = os.path.normcase(pat) + match = _compile_pattern(pat) + if os.path is posixpath: + # normcase on posix is NOP. Optimize it away from the loop. + for name in names: + if match(name): + result.append(name) + else: + for name in names: + if match(os.path.normcase(name)): + result.append(name) + return result + +def fnmatchcase(name, pat): + """Test whether FILENAME matches PATTERN, including case. + + This is a version of fnmatch() which doesn't case-normalize + its arguments. + """ + match = _compile_pattern(pat) + return match(name) is not None + + +def translate(pat): + """Translate a shell PATTERN to a regular expression. + + There is no way to quote meta-characters. + """ + + i, n = 0, len(pat) + res = '' + while i < n: + c = pat[i] + i = i+1 + if c == '*': + res = res + '.*' + elif c == '?': + res = res + '.' + elif c == '[': + j = i + if j < n and pat[j] == '!': + j = j+1 + if j < n and pat[j] == ']': + j = j+1 + while j < n and pat[j] != ']': + j = j+1 + if j >= n: + res = res + '\\[' + else: + stuff = pat[i:j].replace('\\','\\\\') + i = j+1 + if stuff[0] == '!': + stuff = '^' + stuff[1:] + elif stuff[0] == '^': + stuff = '\\' + stuff + res = '%s[%s]' % (res, stuff) + else: + res = res + re.escape(c) + return r'(?s:%s)\Z' % res diff --git a/modules/language/python/module/functools.scm b/modules/language/python/module/functools.scm index f0ddf29..c065900 100644 --- a/modules/language/python/module/functools.scm +++ b/modules/language/python/module/functools.scm @@ -63,7 +63,8 @@ (lambda () (let ((value (getattr wrapped attr))) (setattr wrapper attr value))) - (#:except AttributeError => values))) + (#:except #t + (setattr wrapper attr None)))) (for ((attr : updated)) () (py-update (getattr wrapper attr) (getattr wrapped attr (dict)))) diff --git a/modules/language/python/module/genericpath.py b/modules/language/python/module/genericpath.py new file mode 100644 index 0000000..226e0b3 --- /dev/null +++ b/modules/language/python/module/genericpath.py @@ -0,0 +1,153 @@ +module(genericpath) + +""" +Path operations common to more than one OS +Do not use directly. The OS specific modules import the appropriate +functions from this module themselves. +""" +import os +import stat + +__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime', + 'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile', + 'samestat'] + + +# Does a path exist? +# This is false for dangling symbolic links on systems that support them. +def exists(path): + """Test whether a path exists. Returns False for broken symbolic links""" + try: + os.stat(path) + except OSError: + return False + return True + + +# This follows symbolic links, so both islink() and isdir() can be true +# for the same path on systems that support symlinks +def isfile(path): + """Test whether a path is a regular file""" + try: + st = os.stat(path) + except OSError: + return False + return stat.S_ISREG(st.st_mode) + + +# Is a path a directory? +# This follows symbolic links, so both islink() and isdir() +# can be true for the same path on systems that support symlinks +def isdir(s): + """Return true if the pathname refers to an existing directory.""" + try: + st = os.stat(s) + except OSError: + return False + return stat.S_ISDIR(st.st_mode) + + +def getsize(filename): + """Return the size of a file, reported by os.stat().""" + return os.stat(filename).st_size + + +def getmtime(filename): + """Return the last modification time of a file, reported by os.stat().""" + return os.stat(filename).st_mtime + + +def getatime(filename): + """Return the last access time of a file, reported by os.stat().""" + return os.stat(filename).st_atime + + +def getctime(filename): + """Return the metadata change time of a file, reported by os.stat().""" + return os.stat(filename).st_ctime + + +# Return the longest prefix of all list elements. +def commonprefix(m): + "Given a list of pathnames, returns the longest common leading component" + if not m: return '' + # Some people pass in a list of pathname parts to operate in an OS-agnostic + # fashion; don't try to translate in that case as that's an abuse of the + # API and they are already doing what they need to be OS-agnostic and so + # they most likely won't be using an os.PathLike object in the sublists. + if not isinstance(m[0], (list, tuple)): + m = tuple(map(os.fspath, m)) + s1 = min(m) + s2 = max(m) + for i, c in enumerate(s1): + if c != s2[i]: + return s1[:i] + return s1 + +# Are two stat buffers (obtained from stat, fstat or lstat) +# describing the same file? +def samestat(s1, s2): + """Test whether two stat buffers reference the same file""" + return (s1.st_ino == s2.st_ino and + s1.st_dev == s2.st_dev) + + +# Are two filenames really pointing to the same file? +def samefile(f1, f2): + """Test whether two pathnames reference the same actual file""" + s1 = os.stat(f1) + s2 = os.stat(f2) + return samestat(s1, s2) + + +# Are two open files really referencing the same file? +# (Not necessarily the same file descriptor!) +def sameopenfile(fp1, fp2): + """Test whether two open file objects reference the same file""" + s1 = os.fstat(fp1) + s2 = os.fstat(fp2) + return samestat(s1, s2) + + +# Split a path in root and extension. +# The extension is everything starting at the last dot in the last +# pathname component; the root is everything before that. +# It is always true that root + ext == p. + +# Generic implementation of splitext, to be parametrized with +# the separators +def _splitext(p, sep, altsep, extsep): + """Split the extension from a pathname. + + Extension is everything from the last dot to the end, ignoring + leading dots. Returns "(root, ext)"; ext may be empty.""" + # NOTE: This code must work for text and bytes strings. + + sepIndex = p.rfind(sep) + if altsep: + altsepIndex = p.rfind(altsep) + sepIndex = max(sepIndex, altsepIndex) + + dotIndex = p.rfind(extsep) + if dotIndex > sepIndex: + # skip all leading dots + filenameIndex = sepIndex + 1 + while filenameIndex < dotIndex: + if p[filenameIndex:filenameIndex+1] != extsep: + return p[:dotIndex], p[dotIndex:] + filenameIndex += 1 + + return p, p[:0] + +def _check_arg_types(funcname, *args): + hasstr = hasbytes = False + for s in args: + if isinstance(s, str): + hasstr = True + elif isinstance(s, bytes): + hasbytes = True + else: + raise TypeError('%s() argument must be str or bytes, not %r' % + (funcname, s.__class__.__name__)) from None + if hasstr and hasbytes: + raise TypeError("Can't mix strings and bytes in path components") from None diff --git a/modules/language/python/module/glob.py b/modules/language/python/module/glob.py new file mode 100644 index 0000000..9b3efe2 --- /dev/null +++ b/modules/language/python/module/glob.py @@ -0,0 +1,173 @@ +module(glob) + +"""Filename globbing utility.""" + +import os +import re +import fnmatch + +__all__ = ["glob", "iglob", "escape"] + +def glob(pathname, *, recursive=False): + """Return a list of paths matching a pathname pattern. + + The pattern may contain simple shell-style wildcards a la + fnmatch. However, unlike fnmatch, filenames starting with a + dot are special cases that are not matched by '*' and '?' + patterns. + + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. + """ + return list(iglob(pathname, recursive=recursive)) + +def iglob(pathname, *, recursive=False): + """Return an iterator which yields the paths matching a pathname pattern. + + The pattern may contain simple shell-style wildcards a la + fnmatch. However, unlike fnmatch, filenames starting with a + dot are special cases that are not matched by '*' and '?' + patterns. + + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. + """ + it = _iglob(pathname, recursive, False) + if recursive and _isrecursive(pathname): + s = next(it) # skip empty string + assert not s + return it + +def _iglob(pathname, recursive, dironly): + dirname, basename = os.path.split(pathname) + if not has_magic(pathname): + assert not dironly + if basename: + if os.path.lexists(pathname): + yield pathname + else: + # Patterns ending with a slash should match only directories + if os.path.isdir(dirname): + yield pathname + return + if not dirname: + if recursive and _isrecursive(basename): + yield from _glob2(dirname, basename, dironly) + else: + yield from _glob1(dirname, basename, dironly) + return + # `os.path.split()` returns the argument itself as a dirname if it is a + # drive or UNC path. Prevent an infinite recursion if a drive or UNC path + # contains magic characters (i.e. r'\\?\C:'). + if dirname != pathname and has_magic(dirname): + dirs = _iglob(dirname, recursive, True) + else: + dirs = [dirname] + if has_magic(basename): + if recursive and _isrecursive(basename): + glob_in_dir = _glob2 + else: + glob_in_dir = _glob1 + else: + glob_in_dir = _glob0 + for dirname in dirs: + for name in glob_in_dir(dirname, basename, dironly): + yield os.path.join(dirname, name) + +# These 2 helper functions non-recursively glob inside a literal directory. +# They return a list of basenames. _glob1 accepts a pattern while _glob0 +# takes a literal basename (so it only has to check for its existence). + +def _glob1(dirname, pattern, dironly): + names = list(_iterdir(dirname, dironly)) + if not _ishidden(pattern): + names = (x for x in names if not _ishidden(x)) + return fnmatch.filter(names, pattern) + +def _glob0(dirname, basename, dironly): + if not basename: + # `os.path.split()` returns an empty basename for paths ending with a + # directory separator. 'q*x/' should match only directories. + if os.path.isdir(dirname): + return [basename] + else: + if os.path.lexists(os.path.join(dirname, basename)): + return [basename] + return [] + +# Following functions are not public but can be used by third-party code. + +def glob0(dirname, pattern): + return _glob0(dirname, pattern, False) + +def glob1(dirname, pattern): + return _glob1(dirname, pattern, False) + +# This helper function recursively yields relative pathnames inside a literal +# directory. + +def _glob2(dirname, pattern, dironly): + assert _isrecursive(pattern) + yield pattern[:0] + yield from _rlistdir(dirname, dironly) + +# If dironly is false, yields all file names inside a directory. +# If dironly is true, yields only directory names. +def _iterdir(dirname, dironly): + if not dirname: + if isinstance(dirname, bytes): + dirname = bytes(os.curdir, 'ASCII') + else: + dirname = os.curdir + try: + with os.scandir(dirname) as it: + for entry in it: + try: + if not dironly or entry.is_dir(): + yield entry.name + except OSError: + pass + except OSError: + return + +# Recursively yields relative pathnames inside a literal directory. +def _rlistdir(dirname, dironly): + names = list(_iterdir(dirname, dironly)) + for x in names: + if not _ishidden(x): + yield x + path = os.path.join(dirname, x) if dirname else x + for y in _rlistdir(path, dironly): + yield os.path.join(x, y) + + +magic_check = re.compile('([*?[])') +magic_check_bytes = re.compile(b'([*?[])') + +def has_magic(s): + if isinstance(s, bytes): + match = magic_check_bytes.search(s) + else: + match = magic_check.search(s) + return match is not None + +def _ishidden(path): + return path[0] in ('.', b'.'[0]) + +def _isrecursive(pattern): + if isinstance(pattern, bytes): + return pattern == b'**' + else: + return pattern == '**' + +def escape(pathname): + """Escape all special characters. + """ + # Escaping is done by wrapping any of "*?[" between square brackets. + # Metacharacters do not work in the drive part and shouldn't be escaped. + drive, pathname = os.path.splitdrive(pathname) + if isinstance(pathname, bytes): + pathname = magic_check_bytes.sub(br'[\1]', pathname) + else: + pathname = magic_check.sub(r'[\1]', pathname) + return drive + pathname diff --git a/modules/language/python/module/posixpath.py b/modules/language/python/module/posixpath.py new file mode 100644 index 0000000..1a3198b --- /dev/null +++ b/modules/language/python/module/posixpath.py @@ -0,0 +1,521 @@ +module(posixpath) +"""Common operations on Posix pathnames. + +Instead of importing this module directly, import os and refer to +this module as os.path. The "os.path" name is an alias for this +module on Posix systems; on other systems (e.g. Mac, Windows), +os.path provides the same operations in a manner specific to that +platform, and is an alias to another module (e.g. macpath, ntpath). + +Some of this can actually be useful on non-Posix systems too, e.g. +for manipulation of the pathname component of URLs. +""" + +import os +import sys +import stat +import genericpath +from genericpath import * + +__all__ = ["normcase","isabs","join","splitdrive","split","splitext", + "basename","dirname","commonprefix","getsize","getmtime", + "getatime","getctime","islink","exists","lexists","isdir","isfile", + "ismount", "expanduser","expandvars","normpath","abspath", + "samefile","sameopenfile","samestat", + "curdir","pardir","sep","pathsep","defpath","altsep","extsep", + "devnull","realpath","supports_unicode_filenames","relpath", + "commonpath"] + +# Strings representing various path-related bits and pieces. +# These are primarily for export; internally, they are hardcoded. +curdir = '.' +pardir = '..' +extsep = '.' +sep = '/' +pathsep = ':' +defpath = ':/bin:/usr/bin' +altsep = None +devnull = '/dev/null' + +def _get_sep(path): + if isinstance(path, bytes): + return b'/' + else: + return '/' + +# Normalize the case of a pathname. Trivial in Posix, string.lower on Mac. +# On MS-DOS this may also turn slashes into backslashes; however, other +# normalizations (such as optimizing '../' away) are not allowed +# (another function should be defined to do that). + +def normcase(s): + """Normalize case of pathname. Has no effect under Posix""" + s = os.fspath(s) + if not isinstance(s, (bytes, str)): + raise TypeError("normcase() argument must be str or bytes, " + "not '{}'".format(s.__class__.__name__)) + return s + + +# Return whether a path is absolute. +# Trivial in Posix, harder on the Mac or MS-DOS. + +def isabs(s): + """Test whether a path is absolute""" + s = os.fspath(s) + sep = _get_sep(s) + return s.startswith(sep) + + +# Join pathnames. +# Ignore the previous parts if a part is absolute. +# Insert a '/' unless the first part is empty or already ends in '/'. + +def join(a, *p): + """Join two or more pathname components, inserting '/' as needed. + If any component is an absolute path, all previous path components + will be discarded. An empty last part will result in a path that + ends with a separator.""" + a = os.fspath(a) + sep = _get_sep(a) + path = a + try: + if not p: + path[:0] + sep #23780: Ensure compatible data type even if p is null. + for b in map(os.fspath, p): + if b.startswith(sep): + path = b + elif not path or path.endswith(sep): + path += b + else: + path += sep + b + except (TypeError, AttributeError, BytesWarning): + genericpath._check_arg_types('join', a, *p) + raise + return path + + +# Split a path in head (everything up to the last '/') and tail (the +# rest). If the path ends in '/', tail will be empty. If there is no +# '/' in the path, head will be empty. +# Trailing '/'es are stripped from head unless it is the root. + +def split(p): + """Split a pathname. Returns tuple "(head, tail)" where "tail" is + everything after the final slash. Either part may be empty.""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + head, tail = p[:i], p[i:] + if head and head != sep*len(head): + head = head.rstrip(sep) + return head, tail + + +# Split a path in root and extension. +# The extension is everything starting at the last dot in the last +# pathname component; the root is everything before that. +# It is always true that root + ext == p. + +def splitext(p): + p = os.fspath(p) + if isinstance(p, bytes): + sep = b'/' + extsep = b'.' + else: + sep = '/' + extsep = '.' + return genericpath._splitext(p, sep, None, extsep) +#splitext.__doc__ = genericpath._splitext.__doc__ + +# Split a pathname into a drive specification and the rest of the +# path. Useful on DOS/Windows/NT; on Unix, the drive is always empty. + +def splitdrive(p): + """Split a pathname into drive and path. On Posix, drive is always + empty.""" + p = os.fspath(p) + return p[:0], p + + +# Return the tail (basename) part of a path, same as split(path)[1]. + +def basename(p): + """Returns the final component of a pathname""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + return p[i:] + + +# Return the head (dirname) part of a path, same as split(path)[0]. + +def dirname(p): + """Returns the directory component of a pathname""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + head = p[:i] + if head and head != sep*len(head): + head = head.rstrip(sep) + return head + + +# Is a path a symbolic link? +# This will always return false on systems where os.lstat doesn't exist. + +def islink(path): + """Test whether a path is a symbolic link""" + try: + st = os.lstat(path) + except (OSError, AttributeError): + return False + return stat.S_ISLNK(st.st_mode) + +# Being true for dangling symbolic links is also useful. + +def lexists(path): + """Test whether a path exists. Returns True for broken symbolic links""" + try: + os.lstat(path) + except OSError: + return False + return True + + +# Is a path a mount point? +# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?) + +def ismount(path): + """Test whether a path is a mount point""" + try: + s1 = os.lstat(path) + except OSError: + # It doesn't exist -- so not a mount point. :-) + return False + else: + # A symlink can never be a mount point + if stat.S_ISLNK(s1.st_mode): + return False + + if isinstance(path, bytes): + parent = join(path, b'..') + else: + parent = join(path, '..') + parent = realpath(parent) + try: + s2 = os.lstat(parent) + except OSError: + return False + + dev1 = s1.st_dev + dev2 = s2.st_dev + if dev1 != dev2: + return True # path/.. on a different device as path + ino1 = s1.st_ino + ino2 = s2.st_ino + if ino1 == ino2: + return True # path/.. is the same i-node as path + return False + + +# Expand paths beginning with '~' or '~user'. +# '~' means $HOME; '~user' means that user's home directory. +# If the path doesn't begin with '~', or if the user or $HOME is unknown, +# the path is returned unchanged (leaving error reporting to whatever +# function is called with the expanded path as argument). +# See also module 'glob' for expansion of *, ? and [...] in pathnames. +# (A function should also be defined to do full *sh-style environment +# variable expansion.) + +def expanduser(path): + """Expand ~ and ~user constructions. If user or $HOME is unknown, + do nothing.""" + path = os.fspath(path) + if isinstance(path, bytes): + tilde = b'~' + else: + tilde = '~' + if not path.startswith(tilde): + return path + sep = _get_sep(path) + i = path.find(sep, 1) + if i < 0: + i = len(path) + if i == 1: + if 'HOME' not in os.environ: + import pwd + userhome = pwd.getpwuid(os.getuid()).pw_dir + else: + userhome = os.environ['HOME'] + else: + import pwd + name = path[1:i] + if isinstance(name, bytes): + name = str(name, 'ASCII') + try: + pwent = pwd.getpwnam(name) + except KeyError: + return path + userhome = pwent.pw_dir + if isinstance(path, bytes): + userhome = os.fsencode(userhome) + root = b'/' + else: + root = '/' + userhome = userhome.rstrip(root) + return (userhome + path[i:]) or root + + +# Expand paths containing shell variable substitutions. +# This expands the forms $variable and ${variable} only. +# Non-existent variables are left unchanged. + +_varprog = None +_varprogb = None + +def expandvars(path): + """Expand shell variables of form $var and ${var}. Unknown variables + are left unchanged.""" + path = os.fspath(path) + global _varprog, _varprogb + if isinstance(path, bytes): + if b'$' not in path: + return path + if not _varprogb: + import re + _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII) + search = _varprogb.search + start = b'{' + end = b'}' + environ = getattr(os, 'environb', None) + else: + if '$' not in path: + return path + if not _varprog: + import re + _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII) + search = _varprog.search + start = '{' + end = '}' + environ = os.environ + i = 0 + while True: + m = search(path, i) + if not m: + break + i, j = m.span(0) + name = m.group(1) + if name.startswith(start) and name.endswith(end): + name = name[1:-1] + try: + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(name)]) + else: + value = environ[name] + except KeyError: + i = j + else: + tail = path[j:] + path = path[:i] + value + i = len(path) + path += tail + return path + + +# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. +# It should be understood that this may change the meaning of the path +# if it contains symbolic links! + +def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + sep = b'/' + empty = b'' + dot = b'.' + dotdot = b'..' + else: + sep = '/' + empty = '' + dot = '.' + dotdot = '..' + if path == empty: + return dot + initial_slashes = path.startswith(sep) + # POSIX allows one or two initial slashes, but treats three or more + # as single slash. + if (initial_slashes and + path.startswith(sep*2) and not path.startswith(sep*3)): + initial_slashes = 2 + comps = path.split(sep) + new_comps = [] + for comp in comps: + if comp in (empty, dot): + continue + if (comp != dotdot or (not initial_slashes and not new_comps) or + (new_comps and new_comps[-1] == dotdot)): + new_comps.append(comp) + elif new_comps: + new_comps.pop() + comps = new_comps + path = sep.join(comps) + if initial_slashes: + path = sep*initial_slashes + path + return path or dot + + +def abspath(path): + """Return an absolute path.""" + path = os.fspath(path) + if not isabs(path): + if isinstance(path, bytes): + cwd = os.getcwdb() + else: + cwd = os.getcwd() + path = join(cwd, path) + return normpath(path) + + +# Return a canonical path (i.e. the absolute location of a file on the +# filesystem). + +def realpath(filename): + """Return the canonical path of the specified filename, eliminating any +symbolic links encountered in the path.""" + filename = os.fspath(filename) + path, ok = _joinrealpath(filename[:0], filename, {}) + return abspath(path) + +# Join two paths, normalizing and eliminating any symbolic links +# encountered in the second path. +def _joinrealpath(path, rest, seen): + if isinstance(path, bytes): + sep = b'/' + curdir = b'.' + pardir = b'..' + else: + sep = '/' + curdir = '.' + pardir = '..' + + if isabs(rest): + rest = rest[1:] + path = sep + + while rest: + name, _, rest = rest.partition(sep) + if not name or name == curdir: + # current dir + continue + if name == pardir: + # parent dir + if path: + path, name = split(path) + if name == pardir: + path = join(path, pardir, pardir) + else: + path = pardir + continue + newpath = join(path, name) + if not islink(newpath): + path = newpath + continue + # Resolve the symbolic link + if newpath in seen: + # Already seen this path + path = seen[newpath] + if path is not None: + # use cached value + continue + # The symlink is not resolved, so we must have a symlink loop. + # Return already resolved part + rest of the path unchanged. + return join(newpath, rest), False + seen[newpath] = None # not resolved symlink + path, ok = _joinrealpath(path, os.readlink(newpath), seen) + if not ok: + return join(path, rest), False + seen[newpath] = path # resolved symlink + + return path, True + + +supports_unicode_filenames = (sys.platform == 'darwin') + +def relpath(path, start=None): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + path = os.fspath(path) + if isinstance(path, bytes): + curdir = b'.' + sep = b'/' + pardir = b'..' + else: + curdir = '.' + sep = '/' + pardir = '..' + + if start is None: + start = curdir + else: + start = os.fspath(start) + + try: + start_list = [x for x in abspath(start).split(sep) if x] + path_list = [x for x in abspath(path).split(sep) if x] + # Work out how much of the filepath is shared by start and path. + i = len(commonprefix([start_list, path_list])) + + rel_list = [pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) + except (TypeError, AttributeError, BytesWarning, DeprecationWarning): + genericpath._check_arg_types('relpath', path, start) + raise + + +# Return the longest common sub-path of the sequence of paths given as input. +# The paths are not normalized before comparing them (this is the +# responsibility of the caller). Any trailing separator is stripped from the +# returned path. + +def commonpath(paths): + """Given a sequence of path names, returns the longest common sub-path.""" + + if not paths: + raise ValueError('commonpath() arg is an empty sequence') + + paths = tuple(map(os.fspath, paths)) + if isinstance(paths[0], bytes): + sep = b'/' + curdir = b'.' + else: + sep = '/' + curdir = '.' + + try: + split_paths = [path.split(sep) for path in paths] + + try: + isabs, = set(p[:1] == sep for p in paths) + except ValueError: + raise ValueError("Can't mix absolute and relative paths") from None + + split_paths = [[c for c in s if c and c != curdir] for s in split_paths] + s1 = min(split_paths) + s2 = max(split_paths) + common = s1 + for i, c in enumerate(s1): + if c != s2[i]: + common = s1[:i] + break + + prefix = sep if isabs else sep[:0] + return prefix + sep.join(common) + except (TypeError, AttributeError): + genericpath._check_arg_types('commonpath', *paths) + raise diff --git a/modules/language/python/number.scm b/modules/language/python/number.scm index 95965b8..b51cd6b 100644 --- a/modules/language/python/number.scm +++ b/modules/language/python/number.scm @@ -15,6 +15,7 @@ py-as-integer-ratio py-conjugate py-fromhex py-hex py-imag py-is-integer py-real hex py-bin py-index py-ifloordiv py-ilshift py-imod py-imul py-imatmul + py-bit-length py-ilogior py-ilogand py-ipow py-isub py-i/ py-irshift py-ilogxor)) @@ -244,7 +245,7 @@ (mk-unop i0 py-lognot __invert__) (define-method (py-bit-length (i <integer>)) - (logcount i)) + (integer-length (abs i))) (define-method (py-conjugate (i <complex>)) (make-rectangular (real-part i) (- (imag-part i)))) diff --git a/modules/language/python/range.scm b/modules/language/python/range.scm index 5941469..8cc2741 100644 --- a/modules/language/python/range.scm +++ b/modules/language/python/range.scm @@ -47,7 +47,7 @@ (let* ((a (ref self '_a)) (b (ref self '_b)) (c (ref self '_c)) - (aa (if (> c 0) a (- a 1))) + (aa (if (> c 0) a a)) (op (if (> c 0) < >))) (let lp ((i aa)) (if (op i b) |