├── .gitignore ├── README.md ├── lec01 └── lec01-computer-science.pdf ├── lec02 └── lec02-functions.pdf ├── lec03 ├── demo.py └── lec03-control.pdf ├── lec04 ├── demo.py └── lec04-higher-order-functions.pdf ├── lec05 └── lec05-environments.pdf ├── lec06 └── lec06-design.pdf ├── lec07 ├── demo │ └── demo.py └── lec07-recursion.pdf ├── lec08 ├── demo │ └── demo.py └── lec08-tree-recursion.pdf ├── lec09 ├── demo │ └── demo.py └── lec09-containers.pdf ├── lec10 ├── demo │ ├── demo.py │ └── demo2.py └── lec10-data-abstraction.pdf ├── lec11 ├── demo │ └── demo.py └── lec11-trees.pdf ├── lec12 ├── demo │ └── dog.c └── lec12-mutable-values.pdf ├── lec13 ├── demo │ └── demo.py └── lec13-mutable-functions.pdf ├── lec14 ├── demo │ └── demo.py └── lec14-iterators.pdf ├── lec15 ├── demo │ └── demo.py └── lec15-objects.pdf ├── lec16 ├── demo │ └── demo.py └── lec16-inheritance.pdf ├── lec17 ├── demo │ └── demo.py └── lec17-representation.pdf ├── lec18 ├── demo │ └── demo.py └── lec18-composition.pdf ├── lec19 ├── demo │ └── demo.py └── lec19-efficiency.pdf ├── lec20 ├── demo │ └── demo.py └── lec20-decomposition.pdf ├── lec21 ├── demo │ ├── demo.scm │ └── scheme └── lec21-scheme.pdf ├── lec22 ├── demo │ └── demo.py └── lec22-exceptions.pdf ├── lec23 ├── demo │ ├── buffer.py │ ├── scalc.py │ ├── scheme_reader.py │ ├── scheme_tokens.py │ └── ucb.py └── lec23-calculator.pdf ├── lec24 └── lec24-interpreters.pdf ├── lec25 ├── demo │ ├── demo.sql │ └── sqlite_shell.py └── lec25-declarative-programming.pdf ├── lec26 ├── demo │ ├── demo.sql │ └── sqlite_shell.py └── lec26-tables.pdf ├── lec27 ├── demo │ ├── demo.sql │ └── sqlite_shell.py └── lec27-aggregation.pdf ├── lec28 └── lec28-database.pdf ├── lec29 └── lec29-tail-calls.pdf └── lec30 └── lec30-macros.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pptx 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SICP系列更新日志 2 | 3 | 4 | 5 | 知识共享许可协议 6 | 7 | 8 | 9 | | 条目 | 主题 | 链接 | 10 | | ------ | ------------------------------------------------------------ | ------------------------------------------- | 11 | | lec01 | Computer Science(计算机科学) | https://www.bilibili.com/video/BV1ML4y1P7KN | 12 | | lab00 | Start Up(环境配置和测试评分系统) | https://www.bilibili.com/video/BV1aY411N7ZQ | 13 | | lec02 | Functions(函数) | https://www.bilibili.com/video/BV1rv4y1M7GP | 14 | | hw01 | Variables&Functions,Control(变量、函数和控制) | https://www.bilibili.com/video/BV13Z4y1i7Jw | 15 | | lec03 | Control(控制) | https://www.bilibili.com/video/BV1QT411g7dp | 16 | | lab01 | Variables&Functions,Control(变量、函数和控制) | https://www.bilibili.com/video/BV1yB4y1v7F7 | 17 | | lec04 | Higher-Order Functions(高阶函数) | https://www.bilibili.com/video/BV1MB4y1B7GD | 18 | | proj01 | Hog(野猪掷骰子) | https://www.bilibili.com/video/BV1mY4y1n7i5 | 19 | | lec05 | Environments(各种场景下的程序环境) | https://www.bilibili.com/video/BV1Jr4y1u7Pf | 20 | | lab02 | Higher-Order Function and Lambda Expression(高阶函数和匿名函数) | https://www.bilibili.com/video/BV1zf4y1Z7MQ | 21 | | lec06 | Design(函数设计) | https://www.bilibili.com/video/BV1RS4y1n7CX | 22 | | lab03 | Midterm Review(阶段性复习) | https://www.bilibili.com/video/BV1U34y1n79U | 23 | | lec07 | Recursion(递归) | https://www.bilibili.com/video/BV1Ca411p7j5 | 24 | | lec08 | Tree Recursion(树形递归) | https://www.bilibili.com/video/BV1FT411J7Tq | 25 | | hw02 | Recursion(递归) | https://www.bilibili.com/video/BV18Z4y1Y7Wo | 26 | | lec09 | Containers(容器) | https://www.bilibili.com/video/BV1ie4y1R77y | 27 | | lab04 | Recursion, Tree Recursion, Python Lists(递归、树形递归、Python列表) | https://www.bilibili.com/video/BV1gT411J7Tr | 28 | | lec10 | Data Abstraction(数据抽象) | https://www.bilibili.com/video/BV1da411n76i | 29 | | proj02 | Cats(猫猫打字通) | https://www.bilibili.com/video/BV1Dg411f7nj | 30 | | lec11 | Tree(树) | https://www.bilibili.com/video/BV1JB4y1Y73A | 31 | | lab05 | Data Abstraction, Trees(数据抽象,树) | https://www.bilibili.com/video/BV1Ae4y197Lh | 32 | | lec12 | Mutable Values(可修改的值) | https://www.bilibili.com/video/BV1PG4y1i7ps | 33 | | hw03 | Trees, Data Abstraction(树,数据抽象) | https://www.bilibili.com/video/BV11U4y1i7dv | 34 | | lec13 | Mutable Functions(可修性的函数) | https://www.bilibili.com/video/BV1og411Z7wS | 35 | | lab06 | Nonlocal, Mutability(非本地和修改性) | https://www.bilibili.com/video/BV1xB4y187bu | 36 | | lec14 | Iterators(迭代器) | https://www.bilibili.com/video/BV1rW4y127FT | 37 | | lec15 | Objects(对象) | https://www.bilibili.com/video/BV1cU4y1v7cw | 38 | | hw04 | Nonlocal, Iterators(非本地变量,迭代器) | https://www.bilibili.com/video/BV1se4y1Q73F | 39 | | lec16 | Inheritance(继承) | https://www.bilibili.com/video/BV1ZB4y1t7JC | 40 | | lab07 | Iterators, Generators, OOP(迭代器,生成子和面向对象) | https://www.bilibili.com/video/BV18W4y1y7TB | 41 | | proj03 | Ants(蚂蚁塔防) | https://www.bilibili.com/video/BV11g41117zh | 42 | | lec17 | Representation(表示) | https://www.bilibili.com/video/BV1Dt4y137UN | 43 | | lec18 | Composition(组合) | https://www.bilibili.com/video/BV1QF411A7py | 44 | | hw05 | OOP, Linked Lists, Trees(面向对象,链表,树) | https://www.bilibili.com/video/BV1mt4y137pP | 45 | | lec19 | Efficiency(效率) | https://www.bilibili.com/video/BV1Vg411y7WQ | 46 | | lab08 | Linked Lists, Trees(链表,树) | https://www.bilibili.com/video/BV1Ag411k7vn | 47 | | lec20 | Decomposition(解构) | https://www.bilibili.com/video/BV1zv4y1F7vR | 48 | | lab09 | Midterm Review(阶段性复习) | https://www.bilibili.com/video/BV1nS4y1s7pr | 49 | | lec21 | Scheme(Scheme语言基础) | https://www.bilibili.com/video/BV1cd4y1P7Th | 50 | | lab10 | Scheme(Scheme语言基础) | https://www.bilibili.com/video/BV1Fd4y1o7Vt | 51 | | hw06 | Scheme(Scheme语言基础) | https://www.bilibili.com/video/BV1Aa411Z7Ed | 52 | | lec22 | Exceptions(异常) | https://www.bilibili.com/video/BV1R14y1b7Cv | 53 | | lec23 | Calculator(计算器) | https://www.bilibili.com/video/BV1FB4y1z7W3 | 54 | | hw07 | Scheme Lists(Scheme链表) | https://www.bilibili.com/video/BV1vG4y1a7Mb | 55 | | lec24 | Interpreters(解释器) | https://www.bilibili.com/video/BV14G411b7sr | 56 | | lab11 | Interpreters(解释器) | https://www.bilibili.com/video/BV1aB4y1V73q | 57 | | proj04 | Scheme(Scheme解释器) | https://www.bilibili.com/video/BV1WY4y1F74V | 58 | | lec25 | Declarative Programming(申述式编程) | https://www.bilibili.com/video/BV17P411V71J | 59 | | hw08 | More Scheme(Scheme 练习) | https://www.bilibili.com/video/BV1nD4y1B7uN | 60 | | lec26 | Table(表格) | https://www.bilibili.com/video/BV1tP411V75a | 61 | | lab12 | SQL(SQL基础) | https://www.bilibili.com/video/BV1iW4y1t7om | 62 | | lec27 | Aggregation(聚合) | https://www.bilibili.com/video/BV1nU4y1r7EG | 63 | | hw09 | SQL(SQL 基础) | https://www.bilibili.com/video/BV1914y1W7Rk | 64 | | lab13 | More SQL(SQL练习) | https://www.bilibili.com/video/BV1xe4y1d7Lb | 65 | | lab14 | Final Review(最终复习) | https://www.bilibili.com/video/BV1Sg411U7cW | 66 | 67 | -------------------------------------------------------------------------------- /lec01/lec01-computer-science.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec01/lec01-computer-science.pdf -------------------------------------------------------------------------------- /lec02/lec02-functions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec02/lec02-functions.pdf -------------------------------------------------------------------------------- /lec03/demo.py: -------------------------------------------------------------------------------- 1 | # source file is an ascii file 2 | print("Hello! I'm a python source file!") 3 | 4 | # multiple return values 5 | 6 | def multiple(a, b): 7 | return a, b 8 | x, y = multiple(1, 2) 9 | print(x, y) 10 | 11 | 12 | # default arguments 13 | 14 | def sum(a, b, c = 2): 15 | return a + b + c 16 | 17 | 18 | # compound statements 19 | 20 | def add(a, b): 21 | return a + b 22 | 23 | 24 | # simple statements 25 | from math import pi 26 | a = 2 27 | 28 | 29 | 30 | # prime fractorization 31 | 32 | def prime_fractorization(n): 33 | """print the prime fractorization of positive integer n 34 | 35 | Args: 36 | n (int): a positive integer 37 | 38 | >>> prime_fractorization(-1) 39 | >>> prime_fractorization(1) 40 | >>> prime_fractorization(8) 41 | 2 42 | 2 43 | 2 44 | >>> prime_fractorization(9) 45 | 3 46 | 3 47 | >>> prime_fractorization(858) 48 | 2 49 | 3 50 | 11 51 | 13 52 | """ 53 | if n <= 1: 54 | return 55 | i = 2 56 | while n != 1: 57 | if n % i == 0: 58 | print(i) 59 | n = n // i 60 | i = 2 61 | else: 62 | i = i + 1 63 | 64 | -------------------------------------------------------------------------------- /lec03/lec03-control.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec03/lec03-control.pdf -------------------------------------------------------------------------------- /lec04/demo.py: -------------------------------------------------------------------------------- 1 | # Iteration 2 | 3 | def fib(n): 4 | """return the nth fibonacii number 5 | 6 | Args: 7 | n (int): n >= 1 8 | 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ... 9 | 10 | >>> fib(1) 11 | 1 12 | >>> fib(3) 13 | 2 14 | >>> fib(7) 15 | 13 16 | >>> fib(9) 17 | 34 18 | """ 19 | pred, curr = 0, 1 20 | while n > 1: 21 | # temp = curr 22 | # curr = pred + curr 23 | # pred = temp 24 | pred, curr = curr, pred + curr 25 | n -= 1 26 | return curr 27 | 28 | 29 | 30 | 31 | # Generalization with arguments 32 | 33 | from math import pi, sqrt 34 | 35 | ## before generalization 36 | 37 | def area_square(r): 38 | """ Return the area of a square with side length r. """ 39 | return r * r * 1 40 | 41 | def area_circle(r): 42 | """ Return the area of a circle with radius r. """ 43 | return r * r * pi 44 | 45 | def area_hexagon(r): 46 | """ Return the area of a regular hexagon with side length r. """ 47 | return r * r * 3 * sqrt(3) / 2 48 | 49 | ## after generalization with arguments 50 | 51 | def area(r, shape_constant): 52 | """ Return the area of a shale from length measurement r. """ 53 | assert r > 0, 'A length must be positive' 54 | return r * r * shape_constant 55 | 56 | def square(r): 57 | return area(r, 1) 58 | 59 | def circle(r): 60 | return area(r, pi) 61 | 62 | def hexagon(r): 63 | return area(r, 3 * sqrt(3) / 2) 64 | 65 | 66 | 67 | 68 | # Generalization over a computational process 69 | 70 | ## Before generalization 71 | 72 | def sum_naturals(n): 73 | """ Sum the first n natural numbers 74 | 75 | Args: 76 | n (int): n >= 0 77 | 78 | >>> sum_naturals(5) 79 | 15 80 | """ 81 | res, i = 0, 1 82 | while i <= n: 83 | res += i 84 | i += 1 85 | return res 86 | 87 | def sum_cubes(n): 88 | """ Sum the first n cube of natural numbers 89 | 90 | Args: 91 | n (int): n >= 0 92 | 93 | >>> sum_cubes(5) 94 | 225 95 | """ 96 | res, i = 0, 1 97 | while i <= n: 98 | res += i * i * i 99 | i += 1 100 | return res 101 | 102 | # After generalization 103 | 104 | from operator import mul 105 | 106 | def natural(i): 107 | return i 108 | 109 | def cube(i): 110 | return i * i * i 111 | 112 | def split_term(i): 113 | return 8 / mul(4 * i - 3, 4 * i - 1) 114 | 115 | def summation(n, term): 116 | """Sum the first n terms of a sequence 117 | 118 | Args: 119 | n (int): n >= 0 120 | term (function): a function describing how to compute each term 121 | 122 | >>> summation(5, cube) 123 | 225 124 | >>> summation(5, natural) 125 | 15 126 | >>> summation(5, split_term) 127 | 3.041839618929402 128 | """ 129 | res, i = 0, 1 130 | while i <= n: 131 | res += term(i) 132 | i += 1 133 | return res 134 | 135 | 136 | # function as return values 137 | 138 | def make_adder(n): 139 | """returns a function that will add n to its argument 140 | 141 | Args: 142 | n (number): a number 143 | 144 | >>> add_three = make_adder(3) 145 | >>> add_three(4) 146 | 7 147 | >>> make_adder(4)(8) 148 | 12 149 | """ 150 | def adder(k): 151 | return n + k 152 | return adder 153 | 154 | 155 | # if statement and if function 156 | 157 | def condition(): 158 | print("This is condition.") 159 | return True 160 | 161 | def if_suite(): 162 | print("This is if suite.") 163 | 164 | def else_suite(): 165 | print("This is else suite.") 166 | 167 | def if_(condition_value, if_value, else_value): 168 | if condition_value: 169 | return if_value 170 | else: 171 | return else_value 172 | 173 | def if_statement(): 174 | if condition(): 175 | if_suite() 176 | else: 177 | else_suite() 178 | 179 | def if_function(): 180 | if_(condition(), if_suite(), else_suite()) 181 | 182 | 183 | # conditional expression 184 | 185 | from math import abs 186 | 187 | def frac_abs(x): 188 | if x == 0: 189 | return 0 190 | else: 191 | return abs(1 / x) 192 | 193 | # return 0 if x == 0 else abs(1 / x) 194 | 195 | 196 | -------------------------------------------------------------------------------- /lec04/lec04-higher-order-functions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec04/lec04-higher-order-functions.pdf -------------------------------------------------------------------------------- /lec05/lec05-environments.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec05/lec05-environments.pdf -------------------------------------------------------------------------------- /lec06/lec06-design.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec06/lec06-design.pdf -------------------------------------------------------------------------------- /lec07/demo/demo.py: -------------------------------------------------------------------------------- 1 | def sum_digits_itr(n): 2 | """ Calculate the summation of all digits in n iteratively 3 | 4 | >>> sum_digits_itr(2019) 5 | 12 6 | >>> sum_digits_itr(2022) 7 | 6 8 | """ 9 | sum = 0 10 | while n > 0: 11 | sum += n % 10 12 | n //= 10 13 | # iteration invariant 14 | return sum 15 | 16 | def sum_digits_rec(n): 17 | """ Calculate the summation of all digits in n recursively 18 | 19 | >>> sum_digits_rec(2019) 20 | 12 21 | >>> sum_digits_rec(2022) 22 | 6 23 | """ 24 | if n < 10: 25 | return n 26 | former, last = n // 10, n % 10 27 | return sum_digits_rec(former) + last 28 | 29 | def sum_digits_rec2(n, digit_sum): 30 | """ Calculate the summation of all digits in n recursively 31 | 32 | >>> sum_digits_rec2(2019, 0) 33 | 12 34 | >>> sum_digits_rec2(2022, 0) 35 | 6 36 | """ 37 | if n == 0: 38 | return digit_sum 39 | former, last = n // 10, n % 10 40 | return sum_digits_rec2(former, digit_sum + last) 41 | 42 | 43 | def factor(n): 44 | """ Return n * (n - 1) * ... * 2 * 1, n >= 1 45 | 46 | >>> factor(3) 47 | 6 48 | >>> factor(4) 49 | 24 50 | >>> factor(5) 51 | 120 52 | >>> factor(6) 53 | 720 54 | """ 55 | if n == 1: 56 | return 1 57 | return n * factor(n - 1) 58 | 59 | 60 | def is_even(n): 61 | """ Returns whether n is an even integer without using mod(%). (n >= 0) 62 | 63 | >>> is_even(0) 64 | True 65 | >>> is_even(1) 66 | False 67 | >>> is_even(128) 68 | True 69 | >>> is_even(63) 70 | False 71 | """ 72 | if n == 0: 73 | return True 74 | return is_odd(n - 1) 75 | 76 | 77 | def is_odd(n): 78 | """ Returns whether n is an odd integer without using mod(%). (n >= 0) 79 | 80 | >>> is_odd(0) 81 | False 82 | >>> is_odd(1) 83 | True 84 | >>> is_odd(128) 85 | False 86 | >>> is_odd(63) 87 | True 88 | """ 89 | if n == 0: 90 | return False 91 | return is_even(n - 1) 92 | -------------------------------------------------------------------------------- /lec07/lec07-recursion.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec07/lec07-recursion.pdf -------------------------------------------------------------------------------- /lec08/demo/demo.py: -------------------------------------------------------------------------------- 1 | def fact(n): 2 | """ Return the factorial of N. 3 | 4 | >>> fact(0) 5 | 1 6 | >>> fact(1) 7 | 1 8 | >>> fact(3) 9 | 6 10 | >>> fact(4) 11 | 24 12 | >>> fact(5) 13 | 120 14 | """ 15 | # Base Case 16 | if n == 0: 17 | return 1 18 | # Recursive Case 19 | return n * fact(n - 1) 20 | 21 | def cascade(n): 22 | """ Print a cascade prefixes of N. 23 | 24 | >>> cascade(1) 25 | 1 26 | >>> cascade(234) 27 | 234 28 | 23 29 | 2 30 | 23 31 | 234 32 | >>> cascade(123) 33 | 123 34 | 12 35 | 1 36 | 12 37 | 123 38 | >>> cascade(1234) 39 | 1234 40 | 123 41 | 12 42 | 1 43 | 12 44 | 123 45 | 1234 46 | """ 47 | # Base Case 48 | if n < 10: 49 | print(n) 50 | else: 51 | print(n) 52 | # Recursive Case 53 | cascade(n // 10) 54 | print(n) 55 | 56 | 57 | def cascade2(n): 58 | """ Print a cascade prefixes of N. 59 | 60 | >>> cascade2(1) 61 | 1 62 | >>> cascade2(234) 63 | 234 64 | 23 65 | 2 66 | 23 67 | 234 68 | >>> cascade2(123) 69 | 123 70 | 12 71 | 1 72 | 12 73 | 123 74 | >>> cascade2(1234) 75 | 1234 76 | 123 77 | 12 78 | 1 79 | 12 80 | 123 81 | 1234 82 | """ 83 | print(n) 84 | if n >= 10: 85 | cascade2(n // 10) 86 | print(n) 87 | 88 | 89 | # TDD: Test-Driven Development 90 | 91 | def inverse_cascade(n): 92 | """ Print an inverse cascade of prefixes of N. 93 | 94 | >>> inverse_cascade(12) 95 | 1 96 | 12 97 | 1 98 | >>> inverse_cascade(123) 99 | 1 100 | 12 101 | 123 102 | 12 103 | 1 104 | >>> inverse_cascade(1234) 105 | 1 106 | 12 107 | 123 108 | 1234 109 | 123 110 | 12 111 | 1 112 | """ 113 | # print the grow order of n // 10 114 | grow(n // 10) 115 | # print n 116 | print(n) 117 | # print the shrink order of n // 10 118 | shrink(n // 10) 119 | 120 | 121 | # Functional Abstraction 122 | def grow(n): 123 | # Base Case 124 | if n == 0: 125 | return 126 | # Recursive Call 127 | grow(n // 10) 128 | print(n) 129 | 130 | 131 | def shrink(n): 132 | # Base Case 133 | if n == 0: 134 | return 135 | # Recursive Call 136 | print(n) 137 | shrink(n // 10) 138 | 139 | 140 | # Tree Recursion 141 | 142 | def fib(n): 143 | """ Return the Nth fibonacci number. 144 | 145 | >>> fib(0) 146 | 0 147 | >>> fib(1) 148 | 1 149 | >>> fib(2) 150 | 1 151 | >>> fib(3) 152 | 2 153 | >>> fib(4) 154 | 3 155 | >>> fib(5) 156 | 5 157 | >>> fib(6) 158 | 8 159 | """ 160 | # Base Case 161 | if n == 0: 162 | return 0 163 | if n == 1: 164 | return 1 165 | # Recursive Call 166 | return fib(n - 2) + fib(n - 1) 167 | 168 | 169 | 170 | # Hanoi 171 | 172 | def print_move(origin, destination): 173 | """ Print instructions to move a disk. """ 174 | print("Move the top disk from rod", origin, "to rod", destination) 175 | 176 | 177 | def move_stack(n, start, end): 178 | """Print the moves required to move N disks on the start pole to the end 179 | pole without violating the rules of Towers of Hanoi. 180 | 181 | Args: 182 | n (int): number of disks 183 | start (int): a pole position, either 1, 2 or 3 184 | end (int): a pole position, either 1, 2, or 3 185 | 186 | There are exactly three poles, and start and end must be different. Assume 187 | that the start pole has at least n disks of increasing size, and the end 188 | pole is either empty or has a top disk larger than the top n start disks. 189 | 190 | >>> move_stack(1, 1, 3) 191 | Move the top disk from rod 1 to rod 3 192 | >>> move_stack(2, 1, 3) 193 | Move the top disk from rod 1 to rod 2 194 | Move the top disk from rod 1 to rod 3 195 | Move the top disk from rod 2 to rod 3 196 | >>> move_stack(3, 1, 3) 197 | Move the top disk from rod 1 to rod 3 198 | Move the top disk from rod 1 to rod 2 199 | Move the top disk from rod 3 to rod 2 200 | Move the top disk from rod 1 to rod 3 201 | Move the top disk from rod 2 to rod 1 202 | Move the top disk from rod 2 to rod 3 203 | Move the top disk from rod 1 to rod 3 204 | """ 205 | # Contract 206 | assert 1 <= start <= 3 and 1 <= end <= 3 and start != end, "Illegal Input!" 207 | # Base Case 208 | if n == 1: 209 | print_move(start, end) 210 | else: 211 | bridge = 6 - start - end 212 | move_stack(n - 1, start, bridge) 213 | print_move(start, end) 214 | move_stack(n - 1, bridge, end) 215 | 216 | 217 | def count_partitions(n, m): 218 | """ Count the partitions of N using parts up to(no more than) size m. 219 | 220 | >>> count_partitions(6, 4) 221 | 9 222 | >>> count_partitions(2, 4) 223 | 2 224 | >>> count_partitions(6, 3) 225 | 7 226 | """ 227 | # Base Case 228 | if n == 0: 229 | return 1 230 | if n < 0: 231 | return 0 232 | if m == 0: 233 | return 0 234 | # Recursive Call 235 | with_at_least_one_m = count_partitions(n - m, m) 236 | without_m = count_partitions(n, m - 1) 237 | return with_at_least_one_m + without_m 238 | 239 | 240 | -------------------------------------------------------------------------------- /lec08/lec08-tree-recursion.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec08/lec08-tree-recursion.pdf -------------------------------------------------------------------------------- /lec09/demo/demo.py: -------------------------------------------------------------------------------- 1 | def count_occurance(lst, ele): 2 | """ Count the occurance of ele in lst. 3 | 4 | >>> count_occurance([1, 2, 2, 3, 3], 2) 5 | 2 6 | >>> count_occurance([1, 1, 2, 1, 1], 1) 7 | 4 8 | >>> count_occurance([], 2) 9 | 0 10 | """ 11 | count = 0 12 | for e in lst: 13 | if e == ele: 14 | count += 1 15 | return count 16 | 17 | def sum_itr(lst): 18 | """ Return the summation of elements in lst iteratively. 19 | 20 | >>> sum_itr([1, 2, 3]) 21 | 6 22 | >>> sum_itr([4, 5, 6]) 23 | 15 24 | """ 25 | total = 0 26 | for ele in lst: 27 | total += ele 28 | return total 29 | 30 | def sum_rec(lst): 31 | """ Return the summation of elements in lst recursively. 32 | 33 | >>> sum_rec([1, 2, 3]) 34 | 6 35 | >>> sum_rec([4, 5, 6]) 36 | 15 37 | """ 38 | if not lst: 39 | return 0 40 | return lst[0] + sum_rec(lst[1:]) 41 | 42 | def reverse_string_itr(s): 43 | """ Return the reversed string of s iteratively. 44 | 45 | >>> reverse_string_itr('draw') 46 | 'ward' 47 | >>> reverse_string_itr('abaaba') 48 | 'abaaba' 49 | """ 50 | res = '' 51 | for i in s: 52 | res = i + res 53 | return res 54 | 55 | def reverse_string_rec(s): 56 | """ Return the reversed string of s recursively. 57 | 58 | >>> reverse_string_rec('draw') 59 | 'ward' 60 | >>> reverse_string_rec('abaaba') 61 | 'abaaba' 62 | """ 63 | if len(s) == 0: 64 | return s 65 | return reverse_string_rec(s[1:]) + s[0] 66 | -------------------------------------------------------------------------------- /lec09/lec09-containers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec09/lec09-containers.pdf -------------------------------------------------------------------------------- /lec10/demo/demo.py: -------------------------------------------------------------------------------- 1 | # Rational Arithmetic 2 | 3 | def add_rational(x, y): 4 | """ Return the sum of rational numbers X and Y, 5 | which is also a rational number. 6 | 7 | >>> x = rational(3, 2) 8 | >>> y = rational(4, 3) 9 | >>> z = add_rational(x, y) 10 | >>> numer(z) 11 | 17 12 | >>> denom(z) 13 | 6 14 | """ 15 | nx, dx = numer(x), denom(x) 16 | ny, dy = numer(y), denom(y) 17 | return rational(nx * dy + ny * dx, dx * dy) 18 | 19 | def mul_rational(x, y): 20 | """ Return the product of rational numbers X and Y, 21 | Which is also a rational number. 22 | 23 | >>> x = rational(3, 2) 24 | >>> y = rational(4, 3) 25 | >>> z = mul_rational(x, y) 26 | >>> numer(z) 27 | 2 28 | >>> denom(z) 29 | 1 30 | """ 31 | return rational(numer(x) * numer(y), denom(x) * denom(y)) 32 | 33 | def equal_rational(x, y): 34 | """ Return whether two rational numbers X and Y are equal. 35 | 36 | >>> x = rational(3, 2) 37 | >>> y = rational(6, 4) 38 | >>> z = rational(2, 3) 39 | >>> equal_rational(x, y) 40 | True 41 | >>> equal_rational(x, z) 42 | False 43 | """ 44 | return numer(x) * denom(y) == numer(y) * denom(x) 45 | 46 | def print_rational(x): 47 | """ Print the rational number X. 48 | 49 | >>> x = rational(3, 2) 50 | >>> print_rational(x) 51 | 3 / 2 52 | """ 53 | print(numer(x), '/', denom(x)) 54 | 55 | 56 | 57 | # Constructor and Selectors 58 | 59 | # gcd = greates common divisor 60 | from math import gcd 61 | 62 | def rational(n, d): 63 | """ Return a representation of the rational number N / D, 64 | which is a compound value. 65 | """ 66 | g = gcd(n, d) 67 | return [n // g, d // g] 68 | 69 | def numer(x): 70 | """ Return the numerator of rational number X. 71 | 72 | >>> x = rational(3, 2) 73 | >>> numer(x) 74 | 3 75 | """ 76 | return x[0] 77 | 78 | def denom(x): 79 | """ Return the denominator of rational number X. 80 | 81 | >>> x = rational(3, 2) 82 | >>> denom(x) 83 | 2 84 | """ 85 | return x[1] 86 | 87 | 88 | # dictionary 89 | 90 | def dict_demo(): 91 | # A dictionary stores a bunch of key-value pairs 92 | me = {'name': 'bearSir', 'age': 21, 'gender': 'male'} 93 | # we can access the value through key 94 | me['name'] 95 | me['name'] = ['bearSir', 'BigBearSir'] 96 | # we can add a new key-value pair like this 97 | me['hobby'] = 'coding' 98 | # we can delete a key-value pair like this 99 | del me['hobby'] 100 | 101 | -------------------------------------------------------------------------------- /lec10/demo/demo2.py: -------------------------------------------------------------------------------- 1 | # Another representation of pair 2 | 3 | def pair(first, second): 4 | """ Return a pair. """ 5 | def pair_func(selector): 6 | if selector == 'first': 7 | return first 8 | if selector == 'second': 9 | return second 10 | return pair_func 11 | 12 | def first(p): 13 | """ Rerurn the first element of a pair. 14 | 15 | >>> p = pair(2, 3) 16 | >>> first(p) 17 | 2 18 | """ 19 | return p('first') 20 | 21 | 22 | def second(p): 23 | """ Return the second element of a pair. 24 | 25 | >>> p = pair(2, 3) 26 | >>> second(p) 27 | 3 28 | """ 29 | return p('second') 30 | 31 | 32 | # Rational Arithmetic 33 | 34 | def add_rational(x, y): 35 | """ Return the sum of rational numbers X and Y, 36 | which is also a rational number. 37 | 38 | >>> x = rational(3, 2) 39 | >>> y = rational(4, 3) 40 | >>> z = add_rational(x, y) 41 | >>> numer(z) 42 | 17 43 | >>> denom(z) 44 | 6 45 | """ 46 | nx, dx = numer(x), denom(x) 47 | ny, dy = numer(y), denom(y) 48 | return rational(nx * dy + ny * dx, dx * dy) 49 | 50 | def mul_rational(x, y): 51 | """ Return the product of rational numbers X and Y, 52 | Which is also a rational number. 53 | 54 | >>> x = rational(3, 2) 55 | >>> y = rational(4, 3) 56 | >>> z = mul_rational(x, y) 57 | >>> numer(z) 58 | 2 59 | >>> denom(z) 60 | 1 61 | """ 62 | return rational(numer(x) * numer(y), denom(x) * denom(y)) 63 | 64 | def equal_rational(x, y): 65 | """ Return whether two rational numbers X and Y are equal. 66 | 67 | >>> x = rational(3, 2) 68 | >>> y = rational(6, 4) 69 | >>> z = rational(2, 3) 70 | >>> equal_rational(x, y) 71 | True 72 | >>> equal_rational(x, z) 73 | False 74 | """ 75 | return numer(x) * denom(y) == numer(y) * denom(x) 76 | 77 | def print_rational(x): 78 | """ Print the rational number X. 79 | 80 | >>> x = rational(3, 2) 81 | >>> print_rational(x) 82 | 3 / 2 83 | """ 84 | print(numer(x), '/', denom(x)) 85 | 86 | 87 | 88 | # Constructor and Selectors 89 | 90 | # gcd = greates common divisor 91 | from math import gcd 92 | 93 | def rational(n, d): 94 | """ Return a representation of the rational number N / D, 95 | which is a compound value. 96 | """ 97 | g = gcd(n, d) 98 | n, d = n // g, d // g 99 | def rational_func(selector): 100 | if selector == 'n': 101 | return n 102 | if selector == 'd': 103 | return d 104 | return rational_func 105 | 106 | def numer(x): 107 | """ Return the numerator of rational number X. 108 | 109 | >>> x = rational(3, 2) 110 | >>> numer(x) 111 | 3 112 | """ 113 | return x('n') 114 | 115 | def denom(x): 116 | """ Return the denominator of rational number X. 117 | 118 | >>> x = rational(3, 2) 119 | >>> denom(x) 120 | 2 121 | """ 122 | return x('d') 123 | -------------------------------------------------------------------------------- /lec10/lec10-data-abstraction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec10/lec10-data-abstraction.pdf -------------------------------------------------------------------------------- /lec11/demo/demo.py: -------------------------------------------------------------------------------- 1 | def count_leaves(t): 2 | """ Count the leaves in a tree T. 3 | 4 | >>> t = tree(3, [tree(1), tree(2, [tree(1), tree(1)])]) 5 | >>> count_leaves(t) 6 | 3 7 | """ 8 | if is_leaf(t): 9 | return 1 10 | return sum([count_leaves(b) for b in branches(t)]) 11 | 12 | def leaves(t): 13 | """ Return a list of all leaves in tree T. 14 | 15 | >>> t = tree(3, [tree(1), tree(2, [tree(1), tree(1)])]) 16 | >>> leaves(t) 17 | [1, 1, 1] 18 | """ 19 | if is_leaf(t): 20 | return [label(t)] 21 | return sum([leaves(b) for b in branches(t)], []) 22 | 23 | def copy_tree(t): 24 | """ Return another tree which is the same as T but is not T. 25 | 26 | >>> t1 = tree(3, [tree(1), tree(2, [tree(1), tree(1)])]) 27 | >>> t2 = copy_tree(t1) 28 | >>> t2 == t1 29 | True 30 | >>> t2 is t1 31 | False 32 | """ 33 | # Base case is optional 34 | if is_leaf(t): 35 | return tree(label(t)) 36 | return tree(label(t), [copy_tree(b) for b in branches(t)]) 37 | 38 | def print_tree(t, indent=0): 39 | """ Print a tree. 40 | 41 | >>> print_tree(tree(1)) 42 | 1 43 | >>> print_tree(tree(1, [tree(2)])) 44 | 1 45 | 2 46 | >>> print_tree(tree(3, [tree(1), tree(2, [tree(1), tree(1)])])) 47 | 3 48 | 1 49 | 2 50 | 1 51 | 1 52 | >>> print_tree(tree(3, [tree(1, [tree(4), tree(5)]), tree(2, [tree(1), tree(1)])])) 53 | 3 54 | 1 55 | 4 56 | 5 57 | 2 58 | 1 59 | 1 60 | """ 61 | # less indent 62 | print(' ' * indent + str(label(t))) 63 | for b in branches(t): 64 | # more indent 65 | print_tree(b, indent + 1) 66 | 67 | 68 | def sum_paths(t): 69 | """ Return a list of all paths in T. 70 | 71 | >>> t1 = tree(3, [tree(1), tree(2, [tree(1), tree(1)])]) 72 | >>> sum_paths(t1) 73 | [[3, 1], [3, 2, 1], [3, 2, 1]] 74 | >>> t2 = tree(3, [tree(1, [tree(4), tree(5)]), tree(2, [tree(1), tree(1)])]) 75 | >>> sum_paths(t2) 76 | [[3, 1, 4], [3, 1, 5], [3, 2, 1], [3, 2, 1]] 77 | """ 78 | if is_leaf(t): 79 | return [[label(t)]] 80 | res = [] 81 | for b in branches(t): 82 | paths = sum_paths(b) 83 | for p in paths: 84 | res.append([label(t)] + p) 85 | return res 86 | 87 | 88 | # Implementation of Tree Abstraction 89 | 90 | def tree(label, branches=[]): 91 | """ Constructor of tree. 92 | 93 | >>> t = tree(3, [tree(1), tree(2, [tree(1), tree(1)])]) 94 | >>> t 95 | [3, [1], [2, [1], [1]]] 96 | >>> label(t) 97 | 3 98 | >>> branches(t) 99 | [[1], [2, [1], [1]]] 100 | >>> is_leaf(t) 101 | False 102 | >>> is_tree(t) 103 | True 104 | """ 105 | for b in branches: 106 | assert is_tree(b) 107 | return [label] + list(branches) 108 | 109 | def is_tree(t): 110 | if type(t) != list or len(t) < 1: 111 | return False 112 | for b in branches(t): 113 | if not is_tree(b): 114 | return False 115 | return True 116 | 117 | def branches(t): 118 | """ Branch selector of tree. """ 119 | return t[1:] 120 | 121 | def label(t): 122 | """ Label selector of tree. """ 123 | return t[0] 124 | 125 | def is_leaf(t): 126 | return not branches(t) 127 | 128 | -------------------------------------------------------------------------------- /lec11/lec11-trees.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec11/lec11-trees.pdf -------------------------------------------------------------------------------- /lec12/demo/dog.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | typedef struct { 7 | int size; 8 | char color[20]; 9 | } Dog; 10 | 11 | void bark(Dog* this) { 12 | if (this->size < 20) { 13 | printf("%s Dog: Wuwuwu!\n", this->color); 14 | } else { 15 | printf("%s Dog: Wooooooooof!\n", this->color); 16 | } 17 | } 18 | 19 | Dog* makeDog(int size, const char* color) { 20 | Dog* this = (Dog*)malloc(sizeof(Dog)); 21 | this->size = size; 22 | strcpy(this->color, color); 23 | return this; 24 | } 25 | 26 | void delete(Dog* this) { 27 | free(this); 28 | } 29 | 30 | int main() { 31 | Dog* smallDog = makeDog(5, "Dark"); 32 | Dog* bigDog = makeDog(20, "White"); 33 | bark(smallDog); 34 | bark(bigDog); 35 | delete(smallDog); 36 | delete(bigDog); 37 | return 0; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /lec12/lec12-mutable-values.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec12/lec12-mutable-values.pdf -------------------------------------------------------------------------------- /lec13/demo/demo.py: -------------------------------------------------------------------------------- 1 | def make_withdraw1(balance): 2 | """ Return a Withdarw function that withdraws money 3 | from an account with initial balance of BALANCE. 4 | 5 | >>> withdraw = make_withdraw1(100) 6 | >>> withdraw(25) 7 | 75 8 | >>> withdraw(25) 9 | 50 10 | >>> withdraw(60) 11 | 'Insufficient funds!' 12 | >>> withdraw(15) 13 | 35 14 | """ 15 | def withdraw(amount): 16 | nonlocal balance 17 | if amount > balance: 18 | return 'Insufficient funds!' 19 | balance -= amount 20 | return balance 21 | return withdraw 22 | 23 | 24 | def make_withdraw2(balance): 25 | """ Return a Withdarw function that withdraws money 26 | from an account with initial balance of BALANCE. 27 | 28 | >>> withdraw = make_withdraw2(100) 29 | >>> withdraw(25) 30 | 75 31 | >>> withdraw(25) 32 | 50 33 | >>> withdraw(60) 34 | 'Insufficient funds!' 35 | >>> withdraw(15) 36 | 35 37 | """ 38 | b = [balance] 39 | def withdraw(amount): 40 | if amount > b[0]: 41 | return 'Insufficient funds!' 42 | b[0] -= amount 43 | return b[0] 44 | return withdraw 45 | 46 | def f(x): 47 | x = 4 48 | def g(y): 49 | def h(z): 50 | nonlocal x 51 | x = x + 1 52 | return x + y + z 53 | return h 54 | return g 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /lec13/lec13-mutable-functions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec13/lec13-mutable-functions.pdf -------------------------------------------------------------------------------- /lec14/demo/demo.py: -------------------------------------------------------------------------------- 1 | class Countdown: 2 | """ Count down to zero. 3 | 4 | >>> list(Countdown(5)) 5 | [5, 4, 3, 2, 1] 6 | >>> for x in Countdown(3): 7 | ... print(x) 8 | 3 9 | 2 10 | 1 11 | """ 12 | def __init__(self, start): 13 | self.start = start 14 | 15 | def __iter__(self): 16 | v = self.start 17 | while v > 0: 18 | yield v 19 | v -= 1 20 | 21 | 22 | def countdown(n): 23 | """ Lazily return n ~ 1. 24 | 25 | >>> list(countdown(4)) 26 | [4, 3, 2, 1] 27 | >>> list(countdown(8)) 28 | [8, 7, 6, 5, 4, 3, 2, 1] 29 | """ 30 | if n > 0: 31 | yield n 32 | yield from countdown(n - 1) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /lec14/lec14-iterators.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec14/lec14-iterators.pdf -------------------------------------------------------------------------------- /lec15/demo/demo.py: -------------------------------------------------------------------------------- 1 | class Clown: 2 | """ 3 | 4 | >>> Clown.nose 5 | 'big and red' 6 | >>> Clown.dance() 7 | 'No thanks' 8 | """ 9 | nose = 'big and red' 10 | def dance(): 11 | return 'No thanks' 12 | 13 | 14 | class Account: 15 | """ An account has a balance and a holder. 16 | All accounts share common behaviours. 17 | 18 | >>> a = Account('John') 19 | >>> a.holder 20 | 'John' 21 | >>> a.balance 22 | 0 23 | >>> a.deposit(100) 24 | 100 25 | >>> a.balance 26 | 100 27 | >>> a.withdraw(10) 28 | 90 29 | >>> a.withdraw(100) 30 | 'Insufficient funds' 31 | """ 32 | 33 | interest = 0.02 # A Class Attribute 34 | 35 | def __init__(self, account_holder): 36 | """ Constructor of Account class. """ 37 | self.holder = account_holder # An object/instance Attribute 38 | self.balance = 0 39 | 40 | def deposit(self, amount): 41 | self.balance += amount 42 | return self.balance 43 | 44 | def withdraw(self, amount): 45 | if amount > self.balance: 46 | return 'Insufficient funds' 47 | self.balance -= amount 48 | return self.balance 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lec15/lec15-objects.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec15/lec15-objects.pdf -------------------------------------------------------------------------------- /lec16/demo/demo.py: -------------------------------------------------------------------------------- 1 | class Account: 2 | """ An Account has a balance and a holder. 3 | 4 | >>> a = Account('John') # an instance of class Account 5 | >>> a.holder # attribute lookup 6 | 'John' 7 | >>> a.balance 8 | 0 9 | >>> a.deposit(100) # bound method invoking 10 | 100 11 | >>> a.withdraw(90) 12 | 10 13 | >>> a.withdraw(90) 14 | 'Insufficient funds.' 15 | >>> a.balance 16 | 10 17 | >>> a.interest # instance attribute -> class attribute 18 | 0.02 19 | """ 20 | interest = 0.02 # A class attribute 21 | 22 | def __init__(self, account_holder): 23 | """ Constructor for class Account. """ 24 | # attribute assignment of an instance will create a new attribute 25 | self.holder = account_holder # an instance attribute 26 | self.balance = 0 27 | 28 | def deposit(self, amount): 29 | """ Add amount to balance. """ 30 | self.balance += amount 31 | return self.balance 32 | 33 | def withdraw(self, amount): 34 | """ Subtract amount from balance if funds are avaliable. """ 35 | if amount > self.balance: 36 | return 'Insufficient funds.' 37 | self.balance -= amount 38 | return self.balance 39 | 40 | 41 | class CheckingAccount(Account): 42 | """ A bank account that charges for deposits. 43 | >>> ch = CheckingAccount('Tom') 44 | >>> ch.interest 45 | 0.01 46 | >>> ch.deposit(20) 47 | 20 48 | >>> ch.withdraw(5) 49 | 14 50 | """ 51 | interest = 0.01 52 | withdraw_fee = 1 53 | 54 | def withdraw(self, amount): 55 | return Account.withdraw(self, amount + self.withdraw_fee) 56 | # return super().withdraw(amount + self.withdraw_fee) 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /lec16/lec16-inheritance.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec16/lec16-inheritance.pdf -------------------------------------------------------------------------------- /lec17/demo/demo.py: -------------------------------------------------------------------------------- 1 | def _repr(x): 2 | """ Return the repr string of object x 3 | 4 | >>> from fractions import Fraction 5 | >>> half = Fraction(1, 2) 6 | >>> _repr(half) == repr(half) 7 | True 8 | """ 9 | s = type(x).__repr__(x) 10 | if not isinstance(s, str): 11 | raise TypeError 12 | return s 13 | 14 | 15 | 16 | def _str(x): 17 | """ Return the str string of object x 18 | 19 | >>> from fractions import Fraction 20 | >>> half = Fraction(1, 2) 21 | >>> _str(half) == str(half) 22 | True 23 | """ 24 | s = type(x).__str__(x) 25 | if not isinstance(s, str): 26 | raise TypeError 27 | return s 28 | 29 | 30 | class Bear: 31 | """ A Bear. 32 | 33 | >>> bearSir = Bear() 34 | >>> bearSir 35 | Bear() 36 | >>> print(bearSir) 37 | a bear 38 | >>> repr(bearSir) 39 | 'Bear()' 40 | >>> str(bearSir) 41 | 'a bear' 42 | >>> bearSir.__repr__() 43 | 'instance bear repr' 44 | >>> bearSir.__str__() 45 | 'instance bear str' 46 | >>> bool(bearSir) 47 | False 48 | >>> float(bearSir) 49 | 1.1 50 | """ 51 | 52 | def __init__(self): 53 | self.__repr__ = lambda: 'instance bear repr' # instance attribute 54 | self.__str__ = lambda: 'instance bear str' 55 | 56 | def __repr__(self): # class attribute 57 | return 'Bear()' 58 | 59 | def __str__(self): 60 | return 'a bear' 61 | 62 | def __bool__(self): 63 | return False 64 | 65 | def __float__(self): 66 | return 1.1 67 | 68 | 69 | # Ratio numbers 70 | 71 | from math import gcd 72 | 73 | class Ratio: 74 | """ A mutable ratio. 75 | 76 | >>> f = Ratio(9, 15) 77 | >>> f 78 | Ratio(9, 15) 79 | >>> print(f) 80 | 9/15 81 | 82 | >>> Ratio(1, 3) + Ratio(1, 6) 83 | Ratio(1, 2) 84 | >>> Ratio(1, 3) + 1 85 | Ratio(4, 3) 86 | >>> 1 + f 87 | Ratio(8, 5) 88 | >>> 1.4 + f 89 | 2.0 90 | """ 91 | def __init__(self, n, d): 92 | self.numer = n 93 | self.denom = d 94 | 95 | def __repr__(self): 96 | return 'Ratio({0}, {1})'.format(self.numer, self.denom) 97 | 98 | def __str__(self): 99 | return '{0}/{1}'.format(self.numer, self.denom) 100 | 101 | def __float__(self): 102 | return self.numer / self.denom 103 | 104 | def __add__(self, other): 105 | if isinstance(other, Ratio): 106 | n = self.numer * other.denom + other.numer * self.denom 107 | d = self.denom * other.denom 108 | elif isinstance(other, int): 109 | n = self.numer + self.denom * other 110 | d = self.denom 111 | else: 112 | return float(self) + other 113 | g = gcd(n, d) 114 | return Ratio(n // g, d // g) 115 | 116 | __radd__ = __add__ 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /lec17/lec17-representation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec17/lec17-representation.pdf -------------------------------------------------------------------------------- /lec18/demo/demo.py: -------------------------------------------------------------------------------- 1 | class Link: 2 | """ A Linked List. 3 | 4 | >>> s = Link(3, Link(4, Link(5))) 5 | >>> s 6 | Link(3, Link(4, Link(5))) 7 | >>> print(s) 8 | <3, 4, 5> 9 | >>> s.first 10 | 3 11 | >>> s.rest 12 | Link(4, Link(5)) 13 | >>> s.rest.first 14 | 4 15 | >>> s.rest.first = 7 16 | >>> s 17 | Link(3, Link(7, Link(5))) 18 | >>> print(s) 19 | <3, 7, 5> 20 | >>> s.rest.rest = Link.empty 21 | >>> s 22 | Link(3, Link(7)) 23 | >>> s.first = 6 24 | >>> print(s) 25 | <6, 7> 26 | >>> t = Link(1, Link(Link(2, Link(3)), Link(4))) 27 | >>> t 28 | Link(1, Link(Link(2, Link(3)), Link(4))) 29 | >>> print(t) 30 | <1, <2, 3>, 4> 31 | """ 32 | empty = () 33 | 34 | def __init__(self, first, rest=empty): 35 | assert rest is Link.empty or isinstance(rest, Link) 36 | self.first = first 37 | self.rest = rest 38 | 39 | def __repr__(self): 40 | if self.rest: 41 | rest_repr = ', ' + repr(self.rest) 42 | else: 43 | rest_repr = '' 44 | return 'Link(' + repr(self.first) + rest_repr + ')' 45 | 46 | def __str__(self): 47 | string = '<' 48 | while self.rest is not Link.empty: 49 | string += str(self.first) + ', ' 50 | self = self.rest 51 | return string + str(self.first) + '>' 52 | 53 | 54 | # Linked List Processing 55 | 56 | square, odd = lambda x: x * x, lambda x: x % 2 == 1 57 | 58 | def range_link(start, end): 59 | """ Return a Linked List containing consecutive integers from start to end. 60 | 61 | >>> range_link(3, 6) 62 | Link(3, Link(4, Link(5))) 63 | """ 64 | if start >= end: 65 | return Link.empty 66 | return Link(start, range_link(start + 1, end)) 67 | 68 | def map_link(f, s): 69 | """ Return a Linked List that contains f(x) for each x in Linked List s. 70 | 71 | >>> map_link(square, range_link(1, 5)) 72 | Link(1, Link(4, Link(9, Link(16)))) 73 | """ 74 | if s is Link.empty: 75 | return s 76 | return Link(f(s.first), map_link(f, s.rest)) 77 | 78 | def filter_link(f, s): 79 | """ Return a Linked List that contains only elements x of 80 | Link List s for which f(x) is a true value. 81 | 82 | >>> filter_link(odd, range_link(1, 6)) 83 | Link(1, Link(3, Link(5))) 84 | >>> map_link(square, filter_link(odd, range_link(1, 6))) 85 | Link(1, Link(9, Link(25))) 86 | """ 87 | if s is Link.empty: 88 | return s 89 | return Link(s.first, filter_link(f, s.rest)) if f(s.first) else filter_link(f, s.rest) 90 | 91 | 92 | # Linked List Mutation 93 | 94 | def add(s, v): 95 | """ Add v to s, return modified s. 96 | 97 | >>> s = Link(1, Link(3, Link(5))) 98 | >>> add(s, 0) 99 | Link(0, Link(1, Link(3, Link(5)))) 100 | >>> add(s, 3) 101 | Link(0, Link(1, Link(3, Link(5)))) 102 | >>> add(s, 4) 103 | Link(0, Link(1, Link(3, Link(4, Link(5))))) 104 | >>> add(s, 6) 105 | Link(0, Link(1, Link(3, Link(4, Link(5, Link(6)))))) 106 | """ 107 | assert s is not Link.empty 108 | if v < s.first: 109 | s.rest = Link(s.first, s.rest) 110 | s.first = v 111 | elif v > s.first: 112 | s.rest = add(s.rest, v) if s.rest is not Link.empty else Link(v) 113 | return s 114 | 115 | 116 | class Tree: 117 | """ A Tree is a label and a list of branches. 118 | 119 | >>> t = Tree(3, [Tree(1, [Tree(0), Tree(1)]), Tree(2, [Tree(1), Tree(1, [Tree(0), Tree(1)])])]) 120 | >>> t 121 | Tree(3, [Tree(1, [Tree(0), Tree(1)]), Tree(2, [Tree(1), Tree(1, [Tree(0), Tree(1)])])]) 122 | >>> print(t) 123 | 3 124 | 1 125 | 0 126 | 1 127 | 2 128 | 1 129 | 1 130 | 0 131 | 1 132 | """ 133 | def __init__(self, label, branches=[]): 134 | self.label = label 135 | for branch in branches: 136 | assert isinstance(branch, Tree) 137 | self.branches = list(branches) 138 | 139 | def __repr__(self): 140 | if self.branches: 141 | branch_str = ', ' + repr(self.branches) 142 | else: 143 | branch_str = '' 144 | return 'Tree({0}{1})'.format(repr(self.label), branch_str) 145 | 146 | def __str__(self): 147 | return '\n'.join(self.indented()) 148 | 149 | def indented(self): 150 | lines = [] 151 | for b in self.branches: 152 | for line in b.indented(): 153 | lines.append(' ' + line) 154 | return [str(self.label)] + lines 155 | 156 | def is_leaf(self): 157 | return not self.branches 158 | 159 | 160 | # Tree Creation 161 | 162 | def fib_tree(n): 163 | """ A Fibonacci tree. 164 | 165 | >>> print(fib_tree(4)) 166 | 3 167 | 1 168 | 0 169 | 1 170 | 2 171 | 1 172 | 1 173 | 0 174 | 1 175 | """ 176 | if n == 0 or n == 1: 177 | return Tree(n) 178 | left = fib_tree(n - 2) 179 | right = fib_tree(n - 1) 180 | fib_n = left.label + right.label 181 | return Tree(fib_n, [left, right]) 182 | 183 | 184 | # Tree Mutation 185 | 186 | def prune(t, n): 187 | """ Prune sub-trees whose label value is n. 188 | 189 | >>> t1 = fib_tree(4) 190 | >>> prune(t1, 1) 191 | >>> print(t1) 192 | 3 193 | 2 194 | >>> t2 = fib_tree(5) 195 | >>> prune(t2, 1) 196 | >>> print(t2) 197 | 5 198 | 2 199 | 3 200 | 2 201 | """ 202 | assert n != t.label 203 | t.branches = [b for b in t.branches if b.label != n] 204 | for b in t.branches: 205 | prune(b, n) 206 | 207 | 208 | # Tree Processing 209 | 210 | def leaves(tree): 211 | """ Return the list of leaf values of a tree. 212 | 213 | >>> leaves(fib_tree(4)) 214 | [0, 1, 1, 0, 1] 215 | """ 216 | if tree.is_leaf(): 217 | return [tree.label] 218 | return sum([leaves(b) for b in tree.branches], []) 219 | 220 | 221 | def height(tree): 222 | """ The height of a tree. 223 | 224 | >>> height(fib_tree(4)) 225 | 3 226 | """ 227 | if tree.is_leaf(): 228 | return 0 229 | return 1 + max([height(b) for b in tree.branches]) 230 | 231 | -------------------------------------------------------------------------------- /lec18/lec18-composition.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec18/lec18-composition.pdf -------------------------------------------------------------------------------- /lec19/demo/demo.py: -------------------------------------------------------------------------------- 1 | def fib(n): 2 | """ Return the Nth fibonacci number. 3 | 4 | >>> fib(0) 5 | 0 6 | >>> fib(1) 7 | 1 8 | >>> fib(2) 9 | 1 10 | >>> fib(3) 11 | 2 12 | >>> fib(4) 13 | 3 14 | >>> fib(5) 15 | 5 16 | """ 17 | if n == 0: 18 | return 0 19 | if n == 1: 20 | return 1 21 | return fib(n - 2) + fib(n - 1) 22 | 23 | def memo(f): 24 | """ Reconstruct a single argument function into a memoized version.""" 25 | cache = {} 26 | def memoized(n): 27 | if n not in cache: 28 | cache[n] = f(n) 29 | return cache[n] 30 | return memoized 31 | 32 | def make_memo_fib(): 33 | """ Make a memoized fib function that works efficiently. 34 | 35 | >>> memo_fib = make_memo_fib() 36 | >>> memo_fib(0) 37 | 0 38 | >>> memo_fib(1) 39 | 1 40 | >>> memo_fib(2) 41 | 1 42 | >>> memo_fib(3) 43 | 2 44 | >>> memo_fib(4) 45 | 3 46 | >>> memo_fib(5) 47 | 5 48 | >>> memo_fib(35) 49 | 9227465 50 | """ 51 | cache = {0: 0, 1: 1} 52 | def memo_fib(n): 53 | if n in cache: 54 | return cache[n] 55 | res = memo_fib(n - 2) + memo_fib(n - 1) 56 | cache[n] = res 57 | return cache[n] 58 | return memo_fib 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /lec19/lec19-efficiency.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec19/lec19-efficiency.pdf -------------------------------------------------------------------------------- /lec20/demo/demo.py: -------------------------------------------------------------------------------- 1 | def brute_overlap(s, t): 2 | """ Quartic Time Brute Overlap. 3 | 4 | >>> brute_overlap([3, 4, 6, 7, 9, 10], [1, 3, 5, 7, 8]) 5 | 2 6 | """ 7 | count = 0 8 | for i in s: 9 | for j in t: 10 | if i == j: 11 | count += 1 12 | return count 13 | 14 | 15 | def fast_overlap(s, t): 16 | """ Linear Time Fast Overlap for Sorted Lists. 17 | 18 | >>> fast_overlap([3, 4, 6, 7, 9, 10], [1, 3, 5, 7, 8]) 19 | 2 20 | """ 21 | count, i, j = 0, 0, 0 22 | while i < len(s) and j < len(t): 23 | if s[i] < t[j]: 24 | i = i + 1 25 | elif s[i] > t[j]: 26 | j = j + 1 27 | else: 28 | count, i, j = count + 1, i + 1, j + 1 29 | return count 30 | 31 | -------------------------------------------------------------------------------- /lec20/lec20-decomposition.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec20/lec20-decomposition.pdf -------------------------------------------------------------------------------- /lec21/demo/demo.scm: -------------------------------------------------------------------------------- 1 | ; Definitions 2 | (define (square x) (* x x)) 3 | 4 | (define (average x y) (/ (+ x y) 2)) 5 | 6 | (define pi 3.14) 7 | 8 | (define (abs x) 9 | (if (< x 0) (- x) x ) 10 | ) 11 | 12 | (define sum-square 13 | (lambda (x y) (+ (square x) (square y))) 14 | ) 15 | 16 | (define (fib n) 17 | (cond ((= n 0) 0) 18 | ((= n 1) 1) 19 | (else (+ (fib (- n 1)) (fib (- n 2)))) 20 | ) 21 | ) 22 | 23 | ; cons = construction 24 | (define a (cons 1 (cons 2 (cons 3 (cons 4 nil))))) 25 | 26 | ; car = content of the address register 27 | ; get the first element of a linked list 28 | (define a1 (car a)) 29 | 30 | ; cdr = content of the decrement register 31 | ; get the rest of a linked list 32 | (define b (cdr a)) 33 | 34 | ; the first of a linked list can be a linked list 35 | (define c (cons 0 (cons (cons 1 (cons 2 (cons 3 nil))) (cons 4 (cons 5 nil))))) 36 | 37 | (define (sum-while starting-x while-condition add-to-total update-x) 38 | `(begin 39 | (define (f x total) 40 | (if ,while-condition 41 | (f ,update-x (+ total ,add-to-total)) 42 | total 43 | ) 44 | ) 45 | (f ,starting-x 0) 46 | ) 47 | ) 48 | ; (eval (sum-while 2 '(< x 10) '(* x x) '(+ x 2))) => 120 49 | ; (eval (sum-while 1 '(< (* x x) 50) 'x '(+ x 1))) => 28 50 | 51 | -------------------------------------------------------------------------------- /lec21/demo/scheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec21/demo/scheme -------------------------------------------------------------------------------- /lec21/lec21-scheme.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec21/lec21-scheme.pdf -------------------------------------------------------------------------------- /lec22/demo/demo.py: -------------------------------------------------------------------------------- 1 | def reduce(f, s, initial): 2 | """ Combine elements of s pairwise using combiner f and starter value initial 3 | 4 | >>> from operator import mul 5 | >>> reduce(mul, [2, 4, 8], 1) 6 | 64 7 | >>> reduce(pow, [1, 2, 3, 4], 2) 8 | 16777216 9 | """ 10 | if not s: 11 | return initial 12 | return reduce(f, s[1:], f(initial, s[0])) 13 | 14 | 15 | def divide_all(n, ds): 16 | """ Divide n by every d in ds. 17 | 18 | >>> divide_all(1024, [2, 4, 8]) 19 | 16.0 20 | >>> divide_all(1024, [2, 4, 0, 8]) 21 | inf 22 | """ 23 | from operator import truediv 24 | try: 25 | return reduce(truediv, ds, n) 26 | except ZeroDivisionError: 27 | return float('inf') 28 | 29 | 30 | -------------------------------------------------------------------------------- /lec22/lec22-exceptions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec22/lec22-exceptions.pdf -------------------------------------------------------------------------------- /lec23/demo/buffer.py: -------------------------------------------------------------------------------- 1 | """The buffer module assists in iterating through lines and tokens.""" 2 | 3 | import math 4 | 5 | class Buffer(object): 6 | """A Buffer provides a way of accessing a sequence of tokens across lines. 7 | 8 | Its constructor takes an iterator, called "the source", that returns the 9 | next line of tokens as a list each time it is queried, or None to indicate 10 | the end of data. 11 | 12 | The Buffer in effect concatenates the sequences returned from its source 13 | and then supplies the items from them one at a time through its pop() 14 | method, calling the source for more sequences of items only when needed. 15 | 16 | In addition, Buffer provides a current method to look at the 17 | next item to be supplied, without sequencing past it. 18 | 19 | The __str__ method prints all tokens read so far, up to the end of the 20 | current line, and marks the current token with >>. 21 | 22 | >>> buf = Buffer(iter([['(', '+'], [15], [12, ')']])) 23 | >>> buf.pop() 24 | '(' 25 | >>> buf.pop() 26 | '+' 27 | >>> buf.current() 28 | 15 29 | >>> print(buf) 30 | 1: ( + 31 | 2: >> 15 32 | >>> buf.pop() 33 | 15 34 | >>> buf.current() 35 | 12 36 | >>> buf.pop() 37 | 12 38 | >>> print(buf) 39 | 1: ( + 40 | 2: 15 41 | 3: 12 >> ) 42 | >>> buf.pop() 43 | ')' 44 | >>> print(buf) 45 | 1: ( + 46 | 2: 15 47 | 3: 12 ) >> 48 | >>> buf.pop() # returns None 49 | """ 50 | def __init__(self, source): 51 | self.index = 0 52 | self.lines = [] 53 | self.source = source 54 | self.current_line = () 55 | self.current() 56 | 57 | def pop(self): 58 | """Remove the next item from self and return it. If self has 59 | exhausted its source, returns None.""" 60 | current = self.current() 61 | self.index += 1 62 | return current 63 | 64 | @property 65 | def more_on_line(self): 66 | return self.index < len(self.current_line) 67 | 68 | def current(self): 69 | """Return the current element, or None if none exists.""" 70 | while not self.more_on_line: 71 | self.index = 0 72 | try: 73 | self.current_line = next(self.source) 74 | self.lines.append(self.current_line) 75 | except StopIteration: 76 | self.current_line = () 77 | return None 78 | return self.current_line[self.index] 79 | 80 | def __str__(self): 81 | """Return recently read contents; current element marked with >>.""" 82 | # Format string for right-justified line numbers 83 | n = len(self.lines) 84 | msg = '{0:>' + str(math.floor(math.log10(n))+1) + "}: " 85 | 86 | # Up to three previous lines and current line are included in output 87 | s = '' 88 | for i in range(max(0, n-4), n-1): 89 | s += msg.format(i+1) + ' '.join(map(str, self.lines[i])) + '\n' 90 | s += msg.format(n) 91 | s += ' '.join(map(str, self.current_line[:self.index])) 92 | s += ' >> ' 93 | s += ' '.join(map(str, self.current_line[self.index:])) 94 | return s.strip() 95 | 96 | # Try to import readline for interactive history 97 | try: 98 | import readline 99 | except: 100 | pass 101 | 102 | class InputReader(object): 103 | """An InputReader is an iterable that prompts the user for input.""" 104 | def __init__(self, prompt): 105 | self.prompt = prompt 106 | 107 | def __iter__(self): 108 | while True: 109 | yield input(self.prompt) 110 | self.prompt = ' ' * len(self.prompt) 111 | -------------------------------------------------------------------------------- /lec23/demo/scalc.py: -------------------------------------------------------------------------------- 1 | """An interpreter for the Scheme-Syntax Calculator Language 2 | 3 | An interpreter for a calculator language that uses prefix-order call syntax. 4 | Operator expressions must be operator symbols. Operand expressions are 5 | separated by spaces. 6 | 7 | Examples: 8 | > (* 1 2 3) 9 | 6 10 | > (+) 11 | 0 12 | > (+ 2 (/ 4 8)) 13 | 2.5 14 | > (+ 2 2) (* 3 3) 15 | 4 16 | 9 17 | > (+ 1 18 | (- 23) 19 | (* 4 2.5)) 20 | -12 21 | > ) 22 | SyntaxError: unexpected token: ) 23 | > 2.3.4 24 | ValueError: invalid numeral: 2.3.4 25 | > + 26 | TypeError: + is not a number or call expression 27 | > (/ 5) 28 | TypeError: / requires exactly 2 arguments 29 | > (/ 1 0) 30 | ZeroDivisionError: division by zero 31 | """ 32 | 33 | from ucb import trace, main, interact 34 | from operator import add, sub, mul, truediv 35 | from scheme_reader import Pair, nil, scheme_read, buffer_input 36 | 37 | 38 | # Eval & Apply 39 | 40 | def calc_eval(exp): 41 | """Evaluate a Calculator expression. 42 | 43 | >>> calc_eval(as_scheme_list('+', 2, as_scheme_list('*', 4, 6))) 44 | 26 45 | >>> calc_eval(as_scheme_list('+', 2, as_scheme_list('/', 40, 5))) 46 | 10 47 | """ 48 | if type(exp) in (int, float): 49 | return simplify(exp) 50 | elif isinstance(exp, Pair): 51 | arguments = exp.second.map(calc_eval) 52 | return simplify(calc_apply(exp.first, arguments)) 53 | else: 54 | raise TypeError(str(exp) + ' is not a number or call expression') 55 | 56 | def calc_apply(operator, args): 57 | """Apply the named operator to a list of args. 58 | 59 | >>> calc_apply('+', as_scheme_list(1, 2, 3)) 60 | 6 61 | >>> calc_apply('-', as_scheme_list(10, 1, 2, 3)) 62 | 4 63 | >>> calc_apply('-', as_scheme_list(10)) 64 | -10 65 | >>> calc_apply('*', nil) 66 | 1 67 | >>> calc_apply('*', as_scheme_list(1, 2, 3, 4, 5)) 68 | 120 69 | >>> calc_apply('/', as_scheme_list(40, 5)) 70 | 8.0 71 | >>> calc_apply('/', as_scheme_list(10)) 72 | 0.1 73 | """ 74 | if not isinstance(operator, str): 75 | raise TypeError(str(operator) + ' is not a symbol') 76 | if operator == '+': 77 | return reduce(add, args, 0) 78 | elif operator == '-': 79 | if len(args) == 0: 80 | raise TypeError(operator + ' requires at least 1 argument') 81 | elif len(args) == 1: 82 | return -args.first 83 | else: 84 | return reduce(sub, args.second, args.first) 85 | elif operator == '*': 86 | return reduce(mul, args, 1) 87 | elif operator == '/': 88 | if len(args) == 0: 89 | raise TypeError(operator + ' requires at least 1 argument') 90 | elif len(args) == 1: 91 | return 1/args.first 92 | else: 93 | return reduce(truediv, args.second, args.first) 94 | else: 95 | raise TypeError(operator + ' is an unknown operator') 96 | 97 | def simplify(value): 98 | """Return an int if value is an integer, or value otherwise. 99 | 100 | >>> simplify(8.0) 101 | 8 102 | >>> simplify(2.3) 103 | 2.3 104 | >>> simplify('+') 105 | '+' 106 | """ 107 | if isinstance(value, float) and int(value) == value: 108 | return int(value) 109 | return value 110 | 111 | def reduce(fn, scheme_list, start): 112 | """Reduce a recursive list of Pairs using fn and a start value. 113 | 114 | >>> reduce(add, as_scheme_list(1, 2, 3), 0) 115 | 6 116 | """ 117 | if scheme_list is nil: 118 | return start 119 | return reduce(fn, scheme_list.second, fn(start, scheme_list.first)) 120 | 121 | def as_scheme_list(*args): 122 | """Return a recursive list of Pairs that contains the elements of args. 123 | 124 | >>> as_scheme_list(1, 2, 3) 125 | Pair(1, Pair(2, Pair(3, nil))) 126 | """ 127 | if len(args) == 0: 128 | return nil 129 | return Pair(args[0], as_scheme_list(*args[1:])) 130 | 131 | @main 132 | def read_eval_print_loop(): 133 | """Run a read-eval-print loop for Calculator.""" 134 | while True: 135 | try: 136 | src = buffer_input() 137 | while src.more_on_line: 138 | expression = scheme_read(src) 139 | print(calc_eval(expression)) 140 | except (SyntaxError, TypeError, ValueError, ZeroDivisionError) as err: 141 | print(type(err).__name__ + ':', err) 142 | except (KeyboardInterrupt, EOFError): # -D, etc. 143 | print('Calculation completed.') 144 | return 145 | -------------------------------------------------------------------------------- /lec23/demo/scheme_reader.py: -------------------------------------------------------------------------------- 1 | from ucb import main, trace, interact 2 | from scheme_tokens import tokenize_lines, DELIMITERS 3 | from buffer import Buffer, InputReader 4 | 5 | # Pairs and Scheme lists 6 | 7 | class Pair(object): 8 | """A pair has two instance attributes: first and second. For a Pair to be 9 | a well-formed list, second is either a well-formed list or nil. Some 10 | methods only apply to well-formed lists. 11 | 12 | >>> s = Pair(1, Pair(2, nil)) 13 | >>> s 14 | Pair(1, Pair(2, nil)) 15 | >>> print(s) 16 | (1 2) 17 | >>> len(s) 18 | 2 19 | >>> s[1] 20 | 2 21 | >>> print(s.map(lambda x: x+4)) 22 | (5 6) 23 | """ 24 | def __init__(self, first, second): 25 | self.first = first 26 | self.second = second 27 | 28 | def __repr__(self): 29 | return "Pair({0}, {1})".format(repr(self.first), repr(self.second)) 30 | 31 | def __str__(self): 32 | s = "(" + str(self.first) 33 | second = self.second 34 | while isinstance(second, Pair): 35 | s += " " + str(second.first) 36 | second = second.second 37 | if second is not nil: 38 | s += " . " + str(second) 39 | return s + ")" 40 | 41 | def __len__(self): 42 | n, second = 1, self.second 43 | while isinstance(second, Pair): 44 | n += 1 45 | second = second.second 46 | if second is not nil: 47 | raise TypeError("length attempted on improper list") 48 | return n 49 | 50 | def __getitem__(self, k): 51 | if k < 0: 52 | raise IndexError("negative index into list") 53 | y = self 54 | for _ in range(k): 55 | if y.second is nil: 56 | raise IndexError("list index out of bounds") 57 | elif not isinstance(y.second, Pair): 58 | raise TypeError("ill-formed list") 59 | y = y.second 60 | return y.first 61 | 62 | def map(self, fn): 63 | """Return a Scheme list after mapping Python function FN to SELF.""" 64 | mapped = fn(self.first) 65 | if self.second is nil or isinstance(self.second, Pair): 66 | return Pair(mapped, self.second.map(fn)) 67 | else: 68 | raise TypeError("ill-formed list") 69 | 70 | class nil(object): 71 | """The empty list""" 72 | 73 | def __repr__(self): 74 | return "nil" 75 | 76 | def __str__(self): 77 | return "()" 78 | 79 | def __len__(self): 80 | return 0 81 | 82 | def __getitem__(self, k): 83 | if k < 0: 84 | raise IndexError("negative index into list") 85 | raise IndexError("list index out of bounds") 86 | 87 | def map(self, fn): 88 | return self 89 | 90 | nil = nil() # Assignment hides the nil class; there is only one instance 91 | 92 | 93 | # Scheme list parser, without quotation or dotted lists. 94 | 95 | def scheme_read(src): 96 | """Read the next expression from src, a Buffer of tokens. 97 | 98 | >>> lines = ['(+ 1 ', '(+ 23 4)) ('] 99 | >>> src = Buffer(tokenize_lines(lines)) 100 | >>> print(scheme_read(src)) 101 | (+ 1 (+ 23 4)) 102 | """ 103 | if src.current() is None: 104 | raise EOFError 105 | val = src.pop() 106 | if val == 'nil': 107 | return nil 108 | elif val not in DELIMITERS: # ( ) ' . 109 | return val 110 | elif val == "(": 111 | return read_tail(src) 112 | else: 113 | raise SyntaxError("unexpected token: {0}".format(val)) 114 | 115 | def read_tail(src): 116 | """Return the remainder of a list in src, starting before an element or ). 117 | 118 | >>> read_tail(Buffer(tokenize_lines([')']))) 119 | nil 120 | >>> read_tail(Buffer(tokenize_lines(['2 3)']))) 121 | Pair(2, Pair(3, nil)) 122 | >>> read_tail(Buffer(tokenize_lines(['2 (3 4))']))) 123 | Pair(2, Pair(Pair(3, Pair(4, nil)), nil)) 124 | """ 125 | if src.current() is None: 126 | raise SyntaxError("unexpected end of file") 127 | if src.current() == ")": 128 | src.pop() 129 | return nil 130 | first = scheme_read(src) 131 | rest = read_tail(src) 132 | return Pair(first, rest) 133 | 134 | 135 | # Interactive loop 136 | 137 | def buffer_input(): 138 | return Buffer(tokenize_lines(InputReader('> '))) 139 | 140 | @main 141 | def read_print_loop(): 142 | """Run a read-print loop for Scheme expressions.""" 143 | while True: 144 | try: 145 | src = buffer_input() 146 | while src.more_on_line: 147 | expression = scheme_read(src) 148 | print(repr(expression)) 149 | except (SyntaxError, ValueError) as err: 150 | print(type(err).__name__ + ':', err) 151 | except (KeyboardInterrupt, EOFError): # -D, etc. 152 | return 153 | -------------------------------------------------------------------------------- /lec23/demo/scheme_tokens.py: -------------------------------------------------------------------------------- 1 | """The scheme_tokens module provides functions tokenize_line and tokenize_lines 2 | for converting (iterators producing) strings into (iterators producing) lists 3 | of tokens. A token may be: 4 | 5 | * A number (represented as an int or float) 6 | * A boolean (represented as a bool) 7 | * A symbol (represented as a string) 8 | * A delimiter, including parentheses, dots, and single quotes 9 | """ 10 | 11 | import string 12 | import sys 13 | 14 | _SYMBOL_STARTS = set('!$%&*/:<=>?@^_~') | set(string.ascii_lowercase) 15 | _SYMBOL_INNERS = _SYMBOL_STARTS | set(string.digits) | set('+-.') 16 | _NUMERAL_STARTS = set(string.digits) | set('+-.') 17 | _WHITESPACE = set(' \t\n\r') 18 | _SINGLE_CHAR_TOKENS = set("()'") 19 | _TOKEN_END = _WHITESPACE | _SINGLE_CHAR_TOKENS 20 | DELIMITERS = _SINGLE_CHAR_TOKENS | {'.'} 21 | 22 | def valid_symbol(s): 23 | """Returns whether s is not a well-formed value.""" 24 | if len(s) == 0 or s[0] not in _SYMBOL_STARTS: 25 | return False 26 | for c in s[1:]: 27 | if c not in _SYMBOL_INNERS: 28 | return False 29 | return True 30 | 31 | def next_candidate_token(line, k): 32 | """A tuple (tok, k'), where tok is the next substring of line at or 33 | after position k that could be a token (assuming it passes a validity 34 | check), and k' is the position in line following that token. Returns 35 | (None, len(line)) when there are no more tokens.""" 36 | while k < len(line): 37 | c = line[k] 38 | if c == ';': 39 | return None, len(line) 40 | elif c in _WHITESPACE: 41 | k += 1 42 | elif c in _SINGLE_CHAR_TOKENS: 43 | return c, k+1 44 | elif c == '#': # Boolean values #t and #f 45 | return line[k:k+2], min(k+2, len(line)) 46 | else: 47 | j = k 48 | while j < len(line) and line[j] not in _TOKEN_END: 49 | j += 1 50 | return line[k:j], min(j, len(line)) 51 | return None, len(line) 52 | 53 | def tokenize_line(line): 54 | """The list of Scheme tokens on line. Excludes comments and whitespace.""" 55 | result = [] 56 | text, i = next_candidate_token(line, 0) 57 | while text is not None: 58 | if text in DELIMITERS: 59 | result.append(text) 60 | elif text == '+' or text == '-': 61 | result.append(text) 62 | elif text == '#t' or text.lower() == 'true': 63 | result.append(True) 64 | elif text == '#f' or text.lower() == 'false': 65 | result.append(False) 66 | elif text == 'nil': 67 | result.append(text) 68 | elif text[0] in _NUMERAL_STARTS: 69 | try: 70 | result.append(int(text)) 71 | except ValueError: 72 | try: 73 | result.append(float(text)) 74 | except ValueError: 75 | raise ValueError("invalid numeral: {0}".format(text)) 76 | elif text[0] in _SYMBOL_STARTS and valid_symbol(text): 77 | result.append(text) 78 | else: 79 | print("warning: invalid token: {0}".format(text), file=sys.stderr) 80 | print(" ", line, file=sys.stderr) 81 | print(" " * (i+3), "^", file=sys.stderr) 82 | text, i = next_candidate_token(line, i) 83 | return result 84 | 85 | def tokenize_lines(input): 86 | """An iterator that returns lists of tokens, one for each line of the 87 | iterable input sequence.""" 88 | return map(tokenize_line, input) 89 | -------------------------------------------------------------------------------- /lec23/demo/ucb.py: -------------------------------------------------------------------------------- 1 | """The ucb module contains functions specific to 61A at UC Berkeley.""" 2 | 3 | import code 4 | import functools 5 | import inspect 6 | import re 7 | import signal 8 | import sys 9 | 10 | 11 | def main(fn): 12 | """Call fn with command line arguments. Used as a decorator. 13 | 14 | The main decorator marks the function that starts a program. For example, 15 | 16 | @main 17 | def my_run_function(): 18 | # function body 19 | 20 | Use this instead of the typical __name__ == "__main__" predicate. 21 | """ 22 | if inspect.stack()[1][0].f_locals['__name__'] == '__main__': 23 | args = sys.argv[1:] # Discard the script name from command line 24 | fn(*args) # Call the main function 25 | return fn 26 | 27 | _PREFIX = '' 28 | def trace(fn): 29 | """A decorator that prints a function's name, its arguments, and its return 30 | values each time the function is called. For example, 31 | 32 | @trace 33 | def compute_something(x, y): 34 | # function body 35 | """ 36 | @functools.wraps(fn) 37 | def wrapped(*args, **kwds): 38 | global _PREFIX 39 | reprs = [repr(e) for e in args] 40 | reprs += [repr(k) + '=' + repr(v) for k, v in kwds.items()] 41 | log('{0}({1})'.format(fn.__name__, ', '.join(reprs)) + ':') 42 | _PREFIX += ' ' 43 | try: 44 | result = fn(*args, **kwds) 45 | _PREFIX = _PREFIX[:-4] 46 | except Exception as e: 47 | log(fn.__name__ + ' exited via exception') 48 | _PREFIX = _PREFIX[:-4] 49 | raise 50 | # Here, print out the return value. 51 | log('{0}({1}) -> {2}'.format(fn.__name__, ', '.join(reprs), result)) 52 | return result 53 | return wrapped 54 | 55 | 56 | def log(message): 57 | """Print an indented message (used with trace).""" 58 | if type(message) is not str: 59 | message = str(message) 60 | print(_PREFIX + re.sub('\n', '\n' + _PREFIX, message)) 61 | 62 | 63 | def log_current_line(): 64 | """Print information about the current line of code.""" 65 | frame = inspect.stack()[1] 66 | log('Current line: File "{f[1]}", line {f[2]}, in {f[3]}'.format(f=frame)) 67 | 68 | 69 | def interact(msg=None): 70 | """Start an interactive interpreter session in the current environment. 71 | 72 | On Unix: 73 | -D exits the interactive session and returns to normal execution. 74 | In Windows: 75 | -Z exists the interactive session and returns to normal 76 | execution. 77 | """ 78 | # use exception trick to pick up the current frame 79 | try: 80 | raise None 81 | except: 82 | frame = sys.exc_info()[2].tb_frame.f_back 83 | 84 | # evaluate commands in current namespace 85 | namespace = frame.f_globals.copy() 86 | namespace.update(frame.f_locals) 87 | 88 | # exit on interrupt 89 | def handler(signum, frame): 90 | print() 91 | exit(0) 92 | signal.signal(signal.SIGINT, handler) 93 | 94 | if not msg: 95 | _, filename, line, _, _, _ = inspect.stack()[1] 96 | msg = 'Interacting at File "{0}", line {1} \n'.format(filename, line) 97 | msg += ' Unix: -D continues the program; \n' 98 | msg += ' Windows: -Z continues the program; \n' 99 | msg += ' exit() or -C exits the program' 100 | 101 | code.interact(msg, None, namespace) 102 | -------------------------------------------------------------------------------- /lec23/lec23-calculator.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec23/lec23-calculator.pdf -------------------------------------------------------------------------------- /lec24/lec24-interpreters.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec24/lec24-interpreters.pdf -------------------------------------------------------------------------------- /lec25/demo/demo.sql: -------------------------------------------------------------------------------- 1 | .open 2 | 3 | --new 4 | 5 | -- Cities 6 | create table cities as 7 | select 38 as latitude, 122 as longitude, "Berkeley" as name union 8 | select 42, 71, "Cambridge" union 9 | select 45, 93, "Minneapolis"; 10 | 11 | -- Parents 12 | create table parents as 13 | select "abraham" as parent, "barack" as child union 14 | select "abraham" , "clinton" union 15 | select "delano" , "herbert" union 16 | select "fillmore" , "abraham" union 17 | select "fillmore" , "delano" union 18 | select "fillmore" , "grover" union 19 | select "eisenhower" , "fillmore"; 20 | 21 | -- Arithmetic 22 | 23 | create table lift as 24 | select 101 as chair, 2 as single, 2 as couple union 25 | select 102 , 0 , 3 union 26 | select 103 , 4 , 1; 27 | 28 | -- Ints 29 | create table ints as 30 | select "zero" as word, 0 as one, 0 as two, 0 as four, 0 as eight union 31 | select "one" , 1 , 0 , 0 , 0 union 32 | select "two" , 0 , 2 , 0 , 0 union 33 | select "three" , 1 , 2 , 0 , 0 union 34 | select "four" , 0 , 0 , 4 , 0 union 35 | select "five" , 1 , 0 , 4 , 0 union 36 | select "six" , 0 , 2 , 4 , 0 union 37 | select "seven" , 1 , 2 , 4 , 0 union 38 | select "eight" , 0 , 0 , 0 , 8 union 39 | select "nine" , 1 , 0 , 0 , 8; 40 | 41 | -------------------------------------------------------------------------------- /lec25/demo/sqlite_shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Licensed under the MIT license 4 | 5 | # A simple SQLite shell that uses the built-in Python adapter. 6 | 7 | import codecs 8 | import io 9 | import os 10 | import sys 11 | import sqlite3 12 | import time 13 | import warnings 14 | 15 | try: FileNotFoundError 16 | except NameError: FileNotFoundError = OSError 17 | 18 | if str != bytes: buffer = bytes 19 | if str != bytes: unicode = str 20 | 21 | try: import msvcrt 22 | except ImportError: msvcrt = None 23 | 24 | CP_UTF8 = 65001 25 | pythonapi = None 26 | if msvcrt: 27 | import ctypes 28 | (BOOL, DWORD, HANDLE, UINT) = (ctypes.c_long, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_uint) 29 | GetConsoleCP = ctypes.WINFUNCTYPE(UINT)(('GetConsoleCP', ctypes.windll.kernel32)) 30 | SetConsoleCP = ctypes.WINFUNCTYPE(BOOL, UINT)(('SetConsoleCP', ctypes.windll.kernel32)) 31 | GetConsoleOutputCP = ctypes.WINFUNCTYPE(UINT)(('GetConsoleOutputCP', ctypes.windll.kernel32)) 32 | SetConsoleOutputCP = ctypes.WINFUNCTYPE(BOOL, UINT)(('SetConsoleOutputCP', ctypes.windll.kernel32)) 33 | GetConsoleMode = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(DWORD), use_last_error=True)(('GetConsoleMode', ctypes.windll.kernel32)) 34 | GetNumberOfConsoleInputEvents = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(DWORD), use_last_error=True)(('GetNumberOfConsoleInputEvents', ctypes.windll.kernel32)) 35 | ReadConsoleW = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.c_void_p, DWORD, ctypes.POINTER(DWORD), ctypes.c_void_p, use_last_error=True)(('ReadConsoleW', ctypes.windll.kernel32)) 36 | WriteConsoleW = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.c_void_p, DWORD, ctypes.POINTER(DWORD), ctypes.c_void_p, use_last_error=True)(('WriteConsoleW', ctypes.windll.kernel32)) 37 | class Py_buffer(ctypes.Structure): _fields_ = [('buf', ctypes.c_void_p), ('obj', ctypes.py_object), ('len', ctypes.c_ssize_t), ('itemsize', ctypes.c_ssize_t), ('readonly', ctypes.c_int), ('ndim', ctypes.c_int), ('format', ctypes.c_char_p), ('shape', ctypes.POINTER(ctypes.c_ssize_t)), ('strides', ctypes.POINTER(ctypes.c_ssize_t)), ('suboffsets', ctypes.POINTER(ctypes.c_ssize_t))] + ([('smalltable', ctypes.c_ssize_t * 2)] if sys.version_info[0] <= 2 else []) + [('internal', ctypes.c_void_p)] 38 | try: from ctypes import pythonapi 39 | except ImportError: pass 40 | if pythonapi: 41 | def getbuffer(b, writable): 42 | arr = Py_buffer() 43 | pythonapi.PyObject_GetBuffer(ctypes.py_object(b), ctypes.byref(arr), ctypes.c_int(writable)) 44 | try: buf = (ctypes.c_ubyte * arr.len).from_address(arr.buf) 45 | finally: pythonapi.PyBuffer_Release(ctypes.byref(arr)) 46 | return buf 47 | 48 | ENCODING = 'utf-8' 49 | 50 | if sys.version_info[0] < 3: 51 | class NotASurrogateError(Exception): pass 52 | def surrogateescape_handler(exc): 53 | # Source: https://github.com/PythonCharmers/python-future/blob/aef57391c0cd58bf840dff5e2bc2c8c0f5b0a1b4/src/future/utils/surrogateescape.py 54 | mystring = exc.object[exc.start:exc.end] 55 | try: 56 | if isinstance(exc, UnicodeDecodeError): 57 | decoded = [] 58 | for ch in mystring: 59 | if isinstance(ch, int): 60 | code = ch 61 | else: 62 | code = ord(ch) 63 | if 0x80 <= code <= 0xFF: 64 | decoded.append(unichr(0xDC00 + code)) 65 | elif code <= 0x7F: 66 | decoded.append(unichr(code)) 67 | else: 68 | raise NotASurrogateError() 69 | decoded = str().join(decoded) 70 | elif isinstance(exc, UnicodeEncodeError): 71 | decoded = [] 72 | for ch in mystring: 73 | code = ord(ch) 74 | if not 0xD800 <= code <= 0xDCFF: 75 | raise NotASurrogateError() 76 | if 0xDC00 <= code <= 0xDC7F: 77 | decoded.append(unichr(code - 0xDC00)) 78 | elif code <= 0xDCFF: 79 | decoded.append(unichr(code - 0xDC00)) 80 | else: 81 | raise NotASurrogateError() 82 | decoded = str().join(decoded) 83 | else: 84 | raise exc 85 | except NotASurrogateError: 86 | raise exc 87 | return (decoded, exc.end) 88 | codecs.register_error('surrogateescape', surrogateescape_handler) 89 | 90 | def exception_encode(ex, codec): 91 | if str == bytes: 92 | reduced = ex.__reduce__() 93 | ex = reduced[0](*tuple(map(lambda arg: codec.decode(arg)[0] if isinstance(arg, bytes) else arg, reduced[1]))) 94 | return ex 95 | 96 | def sql_commands(read_line): 97 | delims = ['"', "'", ';', '--'] 98 | counter = 0 99 | in_string = None 100 | j = i = 0 101 | prev_line = None 102 | line = None 103 | concat = [] 104 | while True: 105 | if line is None: 106 | while True: # process preprocessor directives 107 | counter += 1 108 | not_in_the_middle_of_any_input = not in_string and i == j and all(map(lambda chunk_: len(chunk_) == 0, concat)) 109 | line = read_line(counter - 1, not_in_the_middle_of_any_input, prev_line) 110 | empty_string = line[:0] if line is not None else line 111 | prev_line = line 112 | if not line: 113 | break 114 | if not_in_the_middle_of_any_input and line.startswith("."): 115 | yield line 116 | line = None 117 | else: 118 | break 119 | if not line: 120 | break 121 | j = i = 0 122 | if j < len(line): 123 | (j, delim) = min(map(lambda pair: pair if pair[0] >= 0 else (len(line), pair[1]), map(lambda d: (line.find(d, j), d), in_string or delims if in_string != '--' else "\n"))) 124 | if i < j: concat.append(line[i:j]); i = j 125 | if not in_string: 126 | if j < len(line): 127 | j += len(delim) 128 | if delim == ';': 129 | i = j 130 | concat.append(line[j : j + len(delim)]) # ensure delimeter is the same type as the string (it may not be due to implicit conversion) 131 | # Eat up any further spaces until a newline 132 | while j < len(line): 133 | delim = line[j:j+1] 134 | if not delim.isspace(): break 135 | j += 1 136 | if delim == "\n": break 137 | if i < j: concat.append(line[i:j]); i = j 138 | yield empty_string.join(concat) 139 | del concat[:] 140 | else: 141 | in_string = delim 142 | else: 143 | if j < len(line): 144 | ch = line[j:j+1] 145 | assert ch == in_string or in_string == '--' 146 | j += 1 147 | i = j 148 | concat.append(ch) 149 | in_string = None 150 | else: 151 | if i < j: concat.append(line[i:j]); i = j 152 | line = None 153 | 154 | class WindowsConsoleIOMixin(object): 155 | # Ctrl+C handling with ReadFile() is messed up on Windows starting on Windows 8... here's some background reading: 156 | # https://stackoverflow.com/a/43260436 157 | # https://github.com/microsoft/terminal/issues/334 158 | # We use ReadConsole when we can, so it doesn't affect us, but it's good info to know regardless. 159 | def __init__(self, fd): 160 | assert isatty(fd), "file descriptor must refer to a console (note that on Windows, NUL satisfies isatty(), but is not a console)" 161 | self.fd = fd 162 | self.handle = msvcrt.get_osfhandle(fd) 163 | def fileno(self): return self.fd 164 | def isatty(self): return isatty(self.fd) 165 | def seekable(self): return False 166 | def readable(self): return GetNumberOfConsoleInputEvents(self.handle, ctypes.byref(DWORD(0))) != 0 167 | def writable(self): n = DWORD(0); return WriteConsoleW(self.handle, ctypes.c_void_p(), n, ctypes.byref(n), ctypes.c_void_p()) != 0 168 | def readwcharsinto(self, buf, n): 169 | nr = DWORD(n) 170 | old_error = ctypes.get_last_error() 171 | ctypes.set_last_error(0) 172 | success = ReadConsoleW(self.handle, buf, nr, ctypes.byref(nr), ctypes.c_void_p()) 173 | error = ctypes.get_last_error() 174 | ctypes.set_last_error(old_error) 175 | if not success: raise ctypes.WinError(error) 176 | ERROR_OPERATION_ABORTED = 995 177 | if nr.value == 0 and error == ERROR_OPERATION_ABORTED: 178 | # Apparently this can trigger pending KeyboardInterrupts? 179 | time.sleep(1.0 / (1 << 64)) 180 | raise KeyboardInterrupt() # If Python doesn't raise it, we can 181 | return nr.value 182 | def writewchars(self, buf, n): 183 | nw = DWORD(n) 184 | if not WriteConsoleW(self.handle, buf, nw, ctypes.byref(nw), ctypes.c_void_p()): 185 | raise ctypes.WinError() 186 | return nw.value 187 | 188 | class WindowsConsoleRawIO(WindowsConsoleIOMixin, io.RawIOBase): 189 | def readinto(self, b): 190 | wordsize = ctypes.sizeof(ctypes.c_wchar) 191 | return self.readwcharsinto(getbuffer(b, True), len(b) // wordsize) * wordsize 192 | def write(self, b): 193 | wordsize = ctypes.sizeof(ctypes.c_wchar) 194 | return self.writewchars(getbuffer(b, False), len(b) // wordsize) * wordsize 195 | 196 | class WindowsConsoleTextIO(WindowsConsoleIOMixin, io.TextIOBase): 197 | buf = None 198 | buffered = unicode() 199 | translate = True 200 | def getbuf(self, ncodeunits): 201 | buf = self.buf 202 | if buf is None or len(buf) < ncodeunits: 203 | self.buf = buf = ctypes.create_unicode_buffer(ncodeunits) 204 | return buf 205 | @staticmethod # Don't let classes override this... they can override the caller instead 206 | def do_read(self, nchars, translate_newlines): 207 | prenewline = os.linesep[:-1] 208 | newline = os.linesep[-1:] 209 | empty = os.linesep[:0] 210 | if nchars is None or nchars < -1: nchars = -1 211 | ncodeunits = nchars if nchars >= 0 else io.DEFAULT_BUFFER_SIZE # Unit mismatch, but doesn't matter; we'll loop 212 | buf = None 213 | istart = 0 214 | while True: 215 | iend = self.buffered.find(newline, istart, min(istart + nchars, len(self.buffered)) if nchars >= 0 else None) if newline is not None else nchars 216 | if iend >= 0: iend += len(newline) if newline is not None else 0 217 | if 0 <= iend <= len(self.buffered): 218 | break 219 | if buf is None: buf = self.getbuf(ncodeunits) 220 | istart = len(self.buffered) 221 | chunk = buf[:self.readwcharsinto(buf, ncodeunits)] 222 | if translate_newlines: chunk = chunk.replace(prenewline, empty) 223 | if chunk.startswith('\x1A'): # EOF on Windows (Ctrl+Z) at the beginning of a line results in the entire rest of the buffer being discarded 224 | iend = istart 225 | break 226 | # Python 2 and Python 3 behaviors differ on Windows... Python 2's sys.stdin.readline() just deletes the next character if it sees EOF in the middle of a string! I won't emulate that here. 227 | self.buffered += chunk # We're relying on Python's concatenation optimization here... we don't do it ourselves, since we want self.buffered to be valid every iteration in case there is an exception raised 228 | result = self.buffered[:iend] 229 | self.buffered = self.buffered[iend:] 230 | return result 231 | def read(self, nchars=-1): return WindowsConsoleTextIO.do_read(self, nchars, None, self.translate) 232 | def readline(self, nchars=-1): return WindowsConsoleTextIO.do_read(self, nchars, self.translate) 233 | def write(self, text): buf = ctypes.create_unicode_buffer(text); return self.writewchars(buf, max(len(buf) - 1, 0)) 234 | 235 | def wrap_windows_console_io(stream, is_output): 236 | fd = None 237 | if stream is not None and sys.version_info[0] < 3 and msvcrt and (is_output or pythonapi) and isatty(stream): 238 | try: fd = stream.fileno() 239 | except io.UnsupportedOperation: pass 240 | result = stream 241 | if fd is not None: 242 | f = GetConsoleOutputCP if is_output else GetConsoleCP 243 | if not f or f() != CP_UTF8: 244 | try: 245 | if True or is_output: 246 | result = WindowsConsoleTextIO(fd) 247 | else: 248 | result = io.TextIOWrapper((io.BufferedWriter if is_output else io.BufferedReader)(WindowsConsoleRawIO(fd)), 'utf-16-le', 'strict', line_buffering=True) 249 | except IOError: pass 250 | return result 251 | 252 | class NonOwningTextIOWrapper(io.TextIOWrapper): 253 | def __init__(self, base_textiowrapper, **kwargs): 254 | assert isinstance(base_textiowrapper, io.TextIOWrapper) 255 | self.base = base_textiowrapper # must keep a reference to this alive so it doesn't get closed 256 | super(NonOwningTextIOWrapper, self).__init__(base_textiowrapper.buffer, **kwargs) 257 | def close(self): 258 | super(NonOwningTextIOWrapper, self).flush() 259 | 260 | def wrap_unicode_stdio(stream, is_writer, encoding): # The underlying stream must NOT be used directly until the stream returned by this function is disposed of 261 | if isinstance(stream, io.TextIOWrapper): 262 | stream.flush() # Make sure nothing is left in the buffer before we re-wrap it 263 | none = object() 264 | kwargs = {} 265 | for key in ['encoding', 'errors', 'newline', 'line_buffering', 'write_through']: 266 | value = getattr(stream, 'newlines' if key == 'newline' else key, none) 267 | if value is not none: 268 | kwargs[key] = value 269 | kwargs['encoding'] = encoding 270 | result = NonOwningTextIOWrapper(stream, **kwargs) 271 | elif 'PYTHONIOENCODING' not in os.environ and str == bytes and stream in (sys.stdin, sys.stdout, sys.stderr): 272 | result = (codecs.getwriter if is_writer else codecs.getreader)(encoding)(stream) 273 | else: 274 | result = stream 275 | return result 276 | 277 | class StringEscapeParser(object): 278 | def __init__(self): 279 | import re 280 | self.pattern = re.compile("\"((?:[^\"\\n]+|\\\\.)*)(?:\"|$)|\'([^\'\\n]*)(?:\'|$)|(\\S+)") 281 | self.escape_pattern = re.compile("\\\\(.)", re.DOTALL) 282 | @staticmethod 283 | def escape_replacement(m): 284 | text = m.group(1) 285 | if text == "\\": text = "\\" 286 | elif text == "/": text = "\n" 287 | elif text == "n": text = "\n" 288 | elif text == "r": text = "\r" 289 | elif text == "t": text = "\t" 290 | elif text == "v": text = "\v" 291 | elif text == "f": text = "\f" 292 | elif text == "a": text = "\a" 293 | elif text == "b": text = "\b" 294 | return text 295 | def __call__(self, s): 296 | escape_pattern = self.escape_pattern 297 | escape_replacement = self.escape_replacement 298 | result = [] 299 | for match in self.pattern.finditer(s): 300 | [m1, m2, m3] = match.groups() 301 | if m1 is not None: result.append(escape_pattern.sub(escape_replacement, m1)) 302 | if m2 is not None: result.append(m2) 303 | if m3 is not None: result.append(escape_pattern.sub(escape_replacement, m3)) 304 | return result 305 | 306 | class Database(object): 307 | def __init__(self, name, *args, **kwargs): 308 | self.connection = sqlite3.connect(name, *args, **kwargs) 309 | self.cursor = self.connection.cursor() 310 | self.name = name # assign name only AFTER cursor is created 311 | 312 | def isatty(file_or_fd): 313 | result = True 314 | method = getattr(file_or_fd, 'isatty', None) if not isinstance(file_or_fd, int) else None # this check is just an optimization 315 | if method is not None: 316 | try: tty = method() 317 | except io.UnsupportedOperation: tty = None 318 | result = result and tty is not None and tty 319 | method = getattr(file_or_fd, 'fileno', None) if not isinstance(file_or_fd, int) else None # this check is just an optimization 320 | if method is not None: 321 | try: fd = method() 322 | except io.UnsupportedOperation: fd = None 323 | result = result and fd is not None and os.isatty(fd) and (not msvcrt or GetConsoleMode(msvcrt.get_osfhandle(fd), ctypes.byref(DWORD(0))) != 0) 324 | return result 325 | 326 | def can_call_input_for_stdio(stream): 327 | return stream == sys.stdin and sys.version_info[0] >= 3 328 | 329 | class StdIOProxy(object): 330 | # Maybe useful later: codecs.StreamRecoder(bytesIO, codec.decode, codec.encode, codec.streamwriter, codec.streamreader, errors='surrogateescape') 331 | def __init__(self, stdin, stdout, stderr, codec, allow_set_code_page): 332 | self.codec = codec 333 | streams = (stdin, stdout, stderr) 334 | for stream in streams: 335 | assert isinstance(stream, io.IOBase) or sys.version_info[0] < 3 and isinstance(stream, file) or hasattr(stream, 'mode'), "unable to determine stream type" 336 | assert not isinstance(stream, io.RawIOBase), "RAW I/O APIs are different and not supported" 337 | self.streaminfos = tuple(map(lambda stream: 338 | ( 339 | stream, 340 | isinstance(stream, io.BufferedIOBase) or isinstance(stream, io.RawIOBase) or not isinstance(stream, io.TextIOBase) and 'b' in stream.mode, 341 | isinstance(stream, io.TextIOBase) or not (isinstance(stream, io.BufferedIOBase) or isinstance(stream, io.RawIOBase)) and 'b' not in stream.mode, 342 | allow_set_code_page 343 | ), 344 | streams)) 345 | @property 346 | def stdin(self): return self.streaminfos[0][0] 347 | @property 348 | def stdout(self): return self.streaminfos[1][0] 349 | @property 350 | def stderr(self): return self.streaminfos[2][0] 351 | def _coerce(self, streaminfo, codec, arg): 352 | stream = streaminfo[0] 353 | can_binary = streaminfo[1] 354 | can_text = streaminfo[2] 355 | if not isinstance(arg, bytes) and not isinstance(arg, buffer) and not isinstance(arg, unicode): 356 | arg = unicode(arg) 357 | if isinstance(arg, bytes) or isinstance(arg, buffer): 358 | if not can_binary: 359 | arg = codec.decode(arg, 'surrogateescape')[0] 360 | elif isinstance(arg, unicode): 361 | if not can_text: 362 | arg = codec.encode(unicode(arg), 'strict')[0] 363 | return arg 364 | @staticmethod 365 | def _do_readline(stream, allow_set_code_page, *args): 366 | new_code_page = CP_UTF8 367 | old_code_page = GetConsoleCP() if msvcrt and GetConsoleCP and isatty(stream) else None 368 | if old_code_page == new_code_page: old_code_page = None # Don't change code page if it's already correct... 369 | if old_code_page is not None: 370 | if not SetConsoleCP(new_code_page): 371 | old_code_page = None 372 | try: 373 | result = stream.readline(*args) 374 | finally: 375 | if old_code_page is not None: 376 | SetConsoleCP(old_code_page) 377 | return result 378 | @staticmethod 379 | def _do_write(stream, allow_set_code_page, *args): 380 | new_code_page = CP_UTF8 381 | old_code_page = GetConsoleOutputCP() if msvcrt and GetConsoleOutputCP and isatty(stream) else None 382 | if old_code_page == new_code_page: old_code_page = None # Don't change code page if it's already correct... 383 | if old_code_page is not None: 384 | if not SetConsoleOutputCP(new_code_page): 385 | old_code_page = None 386 | try: 387 | result = stream.write(*args) 388 | finally: 389 | if old_code_page is not None: 390 | SetConsoleCP(old_code_page) 391 | return result 392 | def _readln(self, streaminfo, codec, prompt): 393 | stream = streaminfo[0] 394 | can_binary = streaminfo[1] 395 | allow_set_code_page = streaminfo[3] 396 | if can_call_input_for_stdio(stream) and not can_binary: # input() can't work with binary data 397 | result = self._coerce(streaminfo, codec, "") 398 | try: 399 | result = input(*((self._coerce(streaminfo, codec, prompt),) if prompt is not None else ())) 400 | result += self._coerce(streaminfo, codec, "\n") 401 | except EOFError: pass 402 | else: 403 | self.output(*((prompt,) if prompt is not None else ())) 404 | self.error() 405 | result = StdIOProxy._do_readline(stream, allow_set_code_page) 406 | return result 407 | def _writeln(self, streaminfo, codec, *args, **kwargs): 408 | stream = streaminfo[0] 409 | allow_set_code_page = streaminfo[3] 410 | flush = kwargs.pop('flush', True) 411 | kwargs.setdefault('end', '\n') 412 | kwargs.setdefault('sep', ' ') 413 | end = kwargs.get('end') 414 | sep = kwargs.get('sep') 415 | first = True 416 | for arg in args: 417 | if first: first = False 418 | elif sep is not None: 419 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, sep)) 420 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, arg)) 421 | if end is not None: 422 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, end)) 423 | if flush: stream.flush() 424 | def inputln(self, prompt=None): return self._readln(self.streaminfos[0], self.codec, prompt) 425 | def output(self, *args, **kwargs): kwargs.setdefault('end', None); return self._writeln(self.streaminfos[1], self.codec, *args, **kwargs) 426 | def outputln(self, *args, **kwargs): return self._writeln(self.streaminfos[1], self.codec, *args, **kwargs) 427 | def error(self, *args, **kwargs): kwargs.setdefault('end', None); return self._writeln(self.streaminfos[2], self.codec, *args, **kwargs) 428 | def errorln(self, *args, **kwargs): return self._writeln(self.streaminfos[2], self.codec, *args, **kwargs) 429 | 430 | class bytes_comparable_with_unicode(bytes): # For Python 2/3 compatibility, to allow implicit conversion between strings and bytes when it is safe. (Used for strings like literals which we know be safe.) 431 | codec = codecs.lookup('ascii') # MUST be a safe encoding 432 | @classmethod 433 | def coerce(cls, other, for_output=False): 434 | return cls.codec.encode(other)[0] if not isinstance(other, bytes) else bytes_comparable_with_unicode(other) if for_output else other 435 | @classmethod 436 | def translate_if_bytes(cls, value): 437 | if value is not None and isinstance(value, bytes): value = cls(value) 438 | return value 439 | def __hash__(self): return super(bytes_comparable_with_unicode, self).__hash__() # To avoid warning 440 | def __eq__(self, other): return super(bytes_comparable_with_unicode, self).__eq__(self.coerce(other)) 441 | def __ne__(self, other): return super(bytes_comparable_with_unicode, self).__ne__(self.coerce(other)) 442 | def __lt__(self, other): return super(bytes_comparable_with_unicode, self).__lt__(self.coerce(other)) 443 | def __gt__(self, other): return super(bytes_comparable_with_unicode, self).__gt__(self.coerce(other)) 444 | def __le__(self, other): return super(bytes_comparable_with_unicode, self).__le__(self.coerce(other)) 445 | def __ge__(self, other): return super(bytes_comparable_with_unicode, self).__ge__(self.coerce(other)) 446 | def __getitem__(self, index): return self.coerce(super(bytes_comparable_with_unicode, self).__getitem__(index), True) 447 | def __add__(self, other): return self.coerce(super(bytes_comparable_with_unicode, self).__add__(self.coerce(other)), True) 448 | def __iadd__(self, other): return self.coerce(super(bytes_comparable_with_unicode, self).__iadd__(self.coerce(other)), True) 449 | def __radd__(self, other): return self.coerce(self.coerce(other).__add__(self), True) 450 | def find(self, other, *args): return super(bytes_comparable_with_unicode, self).find(self.coerce(other), *args) 451 | def join(self, others): return self.coerce(super(bytes_comparable_with_unicode, self).join(map(self.coerce, others)), True) 452 | def startswith(self, other): return super(bytes_comparable_with_unicode, self).startswith(self.coerce(other)) 453 | def __str__(self): return self.codec.decode(self)[0] 454 | if str == bytes: 455 | __unicode__ = __str__ 456 | def __str__(self): raise NotImplementedError() 457 | 458 | def wrap_bytes_comparable_with_unicode_readline(readline): 459 | def callback(*args): 460 | line = readline(*args) 461 | line = bytes_comparable_with_unicode.translate_if_bytes(line) 462 | return line 463 | return callback 464 | 465 | def main(program, *args, **kwargs): # **kwargs = dict(stdin=file, stdout=file, stderr=file); useful for callers who import this module 466 | import argparse # slow import (compiles regexes etc.), so don't import it until needed 467 | argparser = argparse.ArgumentParser( 468 | prog=os.path.basename(program), 469 | usage=None, 470 | description=None, 471 | epilog=None, 472 | parents=[], 473 | formatter_class=argparse.RawTextHelpFormatter) 474 | argparser.add_argument('-version', '--version', action='store_true', help="show SQLite version") 475 | argparser.add_argument('-batch', '--batch', action='store_true', help="force batch I/O") 476 | argparser.add_argument('-init', '--init', metavar="FILE", help="read/process named file") 477 | argparser.add_argument('filename', nargs='?', metavar="FILENAME", help="is the name of an SQLite database.\nA new database is created if the file does not previously exist.") 478 | argparser.add_argument('sql', nargs='*', metavar="SQL", help="SQL commnds to execute after opening database") 479 | argparser.add_argument('--readline', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to import readline if available (default: %(default)s)") 480 | argparser.add_argument('--self-test', action='store_true', help="perform a basic self-test") 481 | argparser.add_argument('--cross-test', action='store_true', help="perform a basic test against the official executable") 482 | argparser.add_argument('--unicode-stdio', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to enable Unicode wrapper for standard I/O (default: %(default)s)") 483 | argparser.add_argument('--console', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to auto-detect and use console window APIs (default: %(default)s)") 484 | argparser.add_argument('--encoding', default=ENCODING, help="the default encoding to use (default: %(default)s)") 485 | (stdin, stdout, stderr) = (kwargs.pop('stdin', sys.stdin), kwargs.pop('stdout', sys.stdout), kwargs.pop('stderr', sys.stderr)) 486 | parsed_args = argparser.parse_args(args) 487 | codec = codecs.lookup(parsed_args.encoding or argparser.get_default('encoding')) 488 | if parsed_args.self_test: self_test(codec) 489 | if parsed_args.cross_test: cross_test("sqlite3", codec) 490 | parse_escaped_strings = StringEscapeParser() 491 | if parsed_args.unicode_stdio == "true": 492 | stdin = wrap_unicode_stdio(stdin, False, codec.name) 493 | stdout = wrap_unicode_stdio(stdout, True, codec.name) 494 | stderr = wrap_unicode_stdio(stderr, True, codec.name) 495 | if parsed_args.console == "true": 496 | stdin = wrap_windows_console_io(stdin, False) 497 | stdout = wrap_windows_console_io(stdout, True) 498 | stderr = wrap_windows_console_io(stderr, True) 499 | allow_set_code_page = sys.version_info[0] < 3 and False # This is only necessary on Python 2 if we use the default I/O functions instead of bypassing to ReadConsole()/WriteConsole() 500 | stdio = StdIOProxy(stdin, stdout, stderr, codec, allow_set_code_page) 501 | db = None 502 | no_args = len(args) == 0 503 | init_sql = parsed_args.sql 504 | is_nonpipe_input = stdin.isatty() # NOT the same thing as TTY! (NUL and /dev/null are the difference) 505 | init_show_prompt = not parsed_args.batch and is_nonpipe_input 506 | if not parsed_args.batch and isatty(stdin) and (parsed_args.readline == "true" or __name__ == '__main__') and parsed_args.readline != "false": 507 | try: 508 | with warnings.catch_warnings(): 509 | warnings.filterwarnings('ignore', category=DeprecationWarning) 510 | import readline 511 | except ImportError: pass 512 | if parsed_args and parsed_args.version: 513 | stdio.outputln(sqlite3.sqlite_version); 514 | else: 515 | filename = parsed_args.filename 516 | if filename is None: filename = ":memory:" 517 | db = Database(filename, isolation_level=None) 518 | def exec_script(db, filename, ignore_io_errors): 519 | try: 520 | with io.open(filename, 'r', encoding=codec.name) as f: # Assume .sql files are text -- any binary data inside them should be X'' encoded, not embedded directly 521 | for command in sql_commands(wrap_bytes_comparable_with_unicode_readline(lambda *args: (lambda s: (s) or None)(f.readline()))): 522 | result = exec_command(db, command, False and ignore_io_errors) 523 | if result is not None: 524 | return result 525 | except IOError as ex: 526 | stdio.errorln(ex) 527 | if not ignore_io_errors: return ex.errno 528 | def raise_invalid_command_error(command): 529 | if isinstance(command, bytes): command = codec.decode(command)[0] 530 | if command.startswith("."): command = command[1:] 531 | raise RuntimeError("Error: unknown command or invalid arguments: \"%s\". Enter \".help\" for help" % (command.rstrip().replace("\\", "\\\\").replace("\"", "\\\""),)) 532 | def exec_command(db, command, ignore_io_errors): 533 | results = None 534 | query = None 535 | query_parameters = {} 536 | try: 537 | if command.startswith("."): 538 | args = list(parse_escaped_strings(command)) 539 | if args[0] in (".quit", ".exit"): 540 | return 0 541 | elif args[0] == ".help": 542 | stdio.error(""" 543 | .cd DIRECTORY Change the working directory to DIRECTORY 544 | .dump Dump the database in an SQL text format 545 | .exit Exit this program 546 | .help Show this message 547 | .open FILE Close existing database and reopen FILE 548 | .print STRING... Print literal STRING 549 | .quit Exit this program 550 | .read FILENAME Execute SQL in FILENAME 551 | .schema ?PATTERN? Show the CREATE statements matching PATTERN 552 | .show Show the current values for various settings 553 | .tables ?TABLE? List names of tables 554 | """.lstrip()) 555 | elif args[0] == ".cd": 556 | if len(args) != 2: raise_invalid_command_error(command) 557 | os.chdir(args[1]) 558 | elif args[0] == ".dump": 559 | if len(args) != 1: raise_invalid_command_error(command) 560 | foreign_keys = db.cursor.execute("PRAGMA foreign_keys;").fetchone()[0] 561 | if foreign_keys in (0, "0", "off", "OFF"): 562 | stdio.outputln("PRAGMA foreign_keys=OFF;", flush=False) 563 | for line in db.connection.iterdump(): 564 | stdio.outputln(line, flush=False) 565 | stdio.output() 566 | elif args[0] == ".open": 567 | if len(args) <= 1: raise_invalid_command_error(command) 568 | filename = args[-1] 569 | for option in args[+1:-1]: 570 | raise ValueError("option %s not supported" % (repr(option),)) 571 | try: db.__init__(filename) 572 | except sqlite3.OperationalError as ex: 573 | ex.args = ex.args[:0] + ("Error: unable to open database \"%s\": %s" % (filename, ex.args[0]),) + ex.args[1:] 574 | raise 575 | elif args[0] == ".print": 576 | stdio.outputln(*args[1:]) 577 | elif args[0] == ".read": 578 | if len(args) != 2: raise_invalid_command_error(command) 579 | exec_script(db, args[1], ignore_io_errors) 580 | elif args[0] == ".schema": 581 | if len(args) > 2: raise_invalid_command_error(command) 582 | pattern = args[1] if len(args) > 1 else None 583 | query_parameters['type'] = 'table' 584 | if pattern is not None: 585 | query_parameters['pattern'] = pattern 586 | query = "SELECT sql || ';' FROM sqlite_master WHERE type = :type" + (" AND name LIKE :pattern" if pattern is not None else "") + ";" 587 | elif args[0] == ".show": 588 | if len(args) > 2: raise_invalid_command_error(command) 589 | stdio.errorln(" filename:", db.name) 590 | elif args[0] == ".tables": 591 | if len(args) > 2: raise_invalid_command_error(command) 592 | pattern = args[1] if len(args) > 1 else None 593 | query_parameters['type'] = 'table' 594 | if pattern is not None: 595 | query_parameters['pattern'] = pattern 596 | query = "SELECT name FROM sqlite_master WHERE type = :type" + (" AND name LIKE :pattern" if pattern is not None else "") + ";" 597 | else: 598 | raise_invalid_command_error(args[0]) 599 | else: 600 | query = command 601 | if query is not None: 602 | results = db.cursor.execute(query if isinstance(query, unicode) else codec.decode(query, 'surrogatereplace')[0], query_parameters) 603 | except (RuntimeError, OSError, FileNotFoundError, sqlite3.OperationalError) as ex: 604 | stdio.errorln(exception_encode(ex, codec)) 605 | if results is not None: 606 | for row in results: 607 | stdio.outputln(*tuple(map(lambda item: item if item is not None else "", row)), sep="|", flush=False) 608 | stdio.output() 609 | if db: 610 | if parsed_args and parsed_args.init: 611 | if is_nonpipe_input: stdio.errorln("-- Loading resources from", parsed_args.init) 612 | exec_script(db, parsed_args.init, False) 613 | def read_stdin(index, not_in_the_middle_of_any_input, prev_line): 614 | show_prompt = init_show_prompt 615 | to_write = [] 616 | if index < len(init_sql): 617 | line = init_sql[index] 618 | if not line.startswith(".") and not line.rstrip().endswith(";"): 619 | line += ";" 620 | elif index == len(init_sql) and len(init_sql) > 0: 621 | line = None 622 | else: 623 | if show_prompt: 624 | if not_in_the_middle_of_any_input: 625 | show_prompt = False 626 | if index == 0: 627 | to_write.append("SQLite version %s (adapter version %s)\nEnter \".help\" for usage hints.\n" % (sqlite3.sqlite_version, sqlite3.version)) 628 | if no_args: 629 | to_write.append("Connected to a transient in-memory database.\nUse \".open FILENAME\" to reopen on a persistent database.\n") 630 | if index > 0 and not prev_line: 631 | to_write.append("\n") 632 | to_write.append("%7s " % ("sqlite%s>" % ("",) if not_in_the_middle_of_any_input else "...>",)) 633 | try: 634 | line = stdio.inputln("".join(to_write)) 635 | except KeyboardInterrupt: 636 | line = "" 637 | raise # just kidding, don't handle it for now... 638 | return line 639 | for command in sql_commands(wrap_bytes_comparable_with_unicode_readline(read_stdin)): 640 | result = exec_command(db, command, True) 641 | if result is not None: 642 | return result 643 | if init_show_prompt and len(init_sql) == 0: 644 | stdio.outputln() 645 | 646 | def call_program(cmdline, input_text): 647 | import subprocess 648 | return subprocess.Popen(cmdline, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=False).communicate(input_text) 649 | 650 | def test_query(): 651 | hexcodec = codecs.lookup('hex_codec') 652 | ascii = 'ascii' 653 | data1 = b"\xD8\xA2" 654 | data2 = b"\x01\x02\xFF\x01\xFF\xFE\xFD" 655 | values = [data1, data2] 656 | query_bytes = b'SELECT %s;' % (b", ".join(map(lambda b: b"X'%s'" % (hexcodec.encode(b)[0].upper(),), values)),) 657 | expected_bytes = b"%s\n" % (b"|".join(values),) 658 | return query_bytes, expected_bytes 659 | 660 | def cross_test(sqlite_cmdline, codec): 661 | (query_bytes, expected_bytes) = test_query() 662 | (official_output, official_error) = call_program(sqlite_cmdline, query_bytes) 663 | # We can't use os.linesep here since binaries may belong to different platforms (Win32/MinGW vs. MSYS/Cygwin vs. WSL...) 664 | official_output = official_output.replace(b"\r\n", b"\n") 665 | official_error = official_error.replace(b"\r\n", b"\n") 666 | if official_output != expected_bytes: 667 | raise sqlite3.ProgrammingError("expected bytes are wrong: official %s != expected %s" % (repr(official_output), repr(expected_bytes))) 668 | if official_error: 669 | raise sqlite3.ProgrammingError("did not expect errors from official binary") 670 | 671 | def self_test(codec): 672 | (query_bytes, expected_bytes) = test_query() 673 | if not (lambda stdin, stdout, stderr: not main(sys.argv[0], stdin=stdin, stdout=stdout, stderr=stderr) and stdout.getvalue() == expected_bytes)(io.BytesIO(query_bytes), io.BytesIO(), io.BytesIO()): 674 | raise sqlite3.ProgrammingError("byte I/O is broken") 675 | if not (lambda stdin, stdout, stderr: not main(sys.argv[0], stdin=stdin, stdout=stdout, stderr=stderr) and stdout.getvalue() == codec.decode(expected_bytes, 'surrogateescape'))(io.StringIO(query_bytes.decode(ascii)), io.StringIO(), io.StringIO()): 676 | raise sqlite3.ProgrammingError("string I/O is broken") 677 | 678 | if __name__ == '__main__': 679 | import sys 680 | exit_code = main(*sys.argv) 681 | if exit_code not in (None, 0): raise SystemExit(exit_code) 682 | -------------------------------------------------------------------------------- /lec25/lec25-declarative-programming.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec25/lec25-declarative-programming.pdf -------------------------------------------------------------------------------- /lec26/demo/demo.sql: -------------------------------------------------------------------------------- 1 | .open 2 | 3 | --new 4 | 5 | ---------- 6 | -- Dogs -- 7 | ---------- 8 | 9 | -- Parents 10 | CREATE TABLE parents AS 11 | SELECT "abraham" AS parent, "barack" AS child UNION 12 | SELECT "abraham" , "clinton" UNION 13 | SELECT "delano" , "herbert" UNION 14 | SELECT "fillmore" , "abraham" UNION 15 | SELECT "fillmore" , "delano" UNION 16 | SELECT "fillmore" , "grover" UNION 17 | SELECT "eisenhower" , "fillmore"; 18 | 19 | -- Fur 20 | CREATE TABLE dogs AS 21 | SELECT "abraham" AS name, "long" AS fur UNION 22 | SELECT "barack" , "short" UNION 23 | SELECT "clinton" , "long" UNION 24 | SELECT "delano" , "long" UNION 25 | SELECT "eisenhower" , "short" UNION 26 | SELECT "fillmore" , "curly" UNION 27 | SELECT "grover" , "short" UNION 28 | SELECT "herbert" , "curly"; 29 | 30 | -- Granddog 31 | CREATE TABLE grandparents AS 32 | SELECT a.parent AS grandog, b.child AS granpup 33 | FROM parents AS a, parents AS b 34 | WHERE a.child = b.parent; 35 | 36 | ------------ 37 | -- Cities -- 38 | ------------ 39 | 40 | CREATE TABLE cities AS 41 | SELECT 38 AS latitude, 122 AS longitude, "Berkeley" AS name UNION 42 | SELECT 42, 71, "Cambridge" UNION 43 | SELECT 45, 93, "Minneapolis" UNION 44 | SELECT 33, 117, "San Diego" UNION 45 | SELECT 26, 80, "Miami" UNION 46 | SELECT 90, 0, "North Pole"; 47 | 48 | 49 | --------------- 50 | -- Sentences -- 51 | --------------- 52 | 53 | CREATE TABLE nouns AS 54 | SELECT "the dog" AS phrase UNION 55 | SELECT "the cat" UNION 56 | SELECT "the bird"; 57 | -------------------------------------------------------------------------------- /lec26/demo/sqlite_shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Licensed under the MIT license 4 | 5 | # A simple SQLite shell that uses the built-in Python adapter. 6 | 7 | import codecs 8 | import io 9 | import os 10 | import sys 11 | import sqlite3 12 | import time 13 | import warnings 14 | 15 | try: FileNotFoundError 16 | except NameError: FileNotFoundError = OSError 17 | 18 | if str != bytes: buffer = bytes 19 | if str != bytes: unicode = str 20 | 21 | try: import msvcrt 22 | except ImportError: msvcrt = None 23 | 24 | CP_UTF8 = 65001 25 | pythonapi = None 26 | if msvcrt: 27 | import ctypes 28 | (BOOL, DWORD, HANDLE, UINT) = (ctypes.c_long, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_uint) 29 | GetConsoleCP = ctypes.WINFUNCTYPE(UINT)(('GetConsoleCP', ctypes.windll.kernel32)) 30 | SetConsoleCP = ctypes.WINFUNCTYPE(BOOL, UINT)(('SetConsoleCP', ctypes.windll.kernel32)) 31 | GetConsoleOutputCP = ctypes.WINFUNCTYPE(UINT)(('GetConsoleOutputCP', ctypes.windll.kernel32)) 32 | SetConsoleOutputCP = ctypes.WINFUNCTYPE(BOOL, UINT)(('SetConsoleOutputCP', ctypes.windll.kernel32)) 33 | GetConsoleMode = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(DWORD), use_last_error=True)(('GetConsoleMode', ctypes.windll.kernel32)) 34 | GetNumberOfConsoleInputEvents = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(DWORD), use_last_error=True)(('GetNumberOfConsoleInputEvents', ctypes.windll.kernel32)) 35 | ReadConsoleW = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.c_void_p, DWORD, ctypes.POINTER(DWORD), ctypes.c_void_p, use_last_error=True)(('ReadConsoleW', ctypes.windll.kernel32)) 36 | WriteConsoleW = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.c_void_p, DWORD, ctypes.POINTER(DWORD), ctypes.c_void_p, use_last_error=True)(('WriteConsoleW', ctypes.windll.kernel32)) 37 | class Py_buffer(ctypes.Structure): _fields_ = [('buf', ctypes.c_void_p), ('obj', ctypes.py_object), ('len', ctypes.c_ssize_t), ('itemsize', ctypes.c_ssize_t), ('readonly', ctypes.c_int), ('ndim', ctypes.c_int), ('format', ctypes.c_char_p), ('shape', ctypes.POINTER(ctypes.c_ssize_t)), ('strides', ctypes.POINTER(ctypes.c_ssize_t)), ('suboffsets', ctypes.POINTER(ctypes.c_ssize_t))] + ([('smalltable', ctypes.c_ssize_t * 2)] if sys.version_info[0] <= 2 else []) + [('internal', ctypes.c_void_p)] 38 | try: from ctypes import pythonapi 39 | except ImportError: pass 40 | if pythonapi: 41 | def getbuffer(b, writable): 42 | arr = Py_buffer() 43 | pythonapi.PyObject_GetBuffer(ctypes.py_object(b), ctypes.byref(arr), ctypes.c_int(writable)) 44 | try: buf = (ctypes.c_ubyte * arr.len).from_address(arr.buf) 45 | finally: pythonapi.PyBuffer_Release(ctypes.byref(arr)) 46 | return buf 47 | 48 | ENCODING = 'utf-8' 49 | 50 | if sys.version_info[0] < 3: 51 | class NotASurrogateError(Exception): pass 52 | def surrogateescape_handler(exc): 53 | # Source: https://github.com/PythonCharmers/python-future/blob/aef57391c0cd58bf840dff5e2bc2c8c0f5b0a1b4/src/future/utils/surrogateescape.py 54 | mystring = exc.object[exc.start:exc.end] 55 | try: 56 | if isinstance(exc, UnicodeDecodeError): 57 | decoded = [] 58 | for ch in mystring: 59 | if isinstance(ch, int): 60 | code = ch 61 | else: 62 | code = ord(ch) 63 | if 0x80 <= code <= 0xFF: 64 | decoded.append(unichr(0xDC00 + code)) 65 | elif code <= 0x7F: 66 | decoded.append(unichr(code)) 67 | else: 68 | raise NotASurrogateError() 69 | decoded = str().join(decoded) 70 | elif isinstance(exc, UnicodeEncodeError): 71 | decoded = [] 72 | for ch in mystring: 73 | code = ord(ch) 74 | if not 0xD800 <= code <= 0xDCFF: 75 | raise NotASurrogateError() 76 | if 0xDC00 <= code <= 0xDC7F: 77 | decoded.append(unichr(code - 0xDC00)) 78 | elif code <= 0xDCFF: 79 | decoded.append(unichr(code - 0xDC00)) 80 | else: 81 | raise NotASurrogateError() 82 | decoded = str().join(decoded) 83 | else: 84 | raise exc 85 | except NotASurrogateError: 86 | raise exc 87 | return (decoded, exc.end) 88 | codecs.register_error('surrogateescape', surrogateescape_handler) 89 | 90 | def exception_encode(ex, codec): 91 | if str == bytes: 92 | reduced = ex.__reduce__() 93 | ex = reduced[0](*tuple(map(lambda arg: codec.decode(arg)[0] if isinstance(arg, bytes) else arg, reduced[1]))) 94 | return ex 95 | 96 | def sql_commands(read_line): 97 | delims = ['"', "'", ';', '--'] 98 | counter = 0 99 | in_string = None 100 | j = i = 0 101 | prev_line = None 102 | line = None 103 | concat = [] 104 | while True: 105 | if line is None: 106 | while True: # process preprocessor directives 107 | counter += 1 108 | not_in_the_middle_of_any_input = not in_string and i == j and all(map(lambda chunk_: len(chunk_) == 0, concat)) 109 | line = read_line(counter - 1, not_in_the_middle_of_any_input, prev_line) 110 | empty_string = line[:0] if line is not None else line 111 | prev_line = line 112 | if not line: 113 | break 114 | if not_in_the_middle_of_any_input and line.startswith("."): 115 | yield line 116 | line = None 117 | else: 118 | break 119 | if not line: 120 | break 121 | j = i = 0 122 | if j < len(line): 123 | (j, delim) = min(map(lambda pair: pair if pair[0] >= 0 else (len(line), pair[1]), map(lambda d: (line.find(d, j), d), in_string or delims if in_string != '--' else "\n"))) 124 | if i < j: concat.append(line[i:j]); i = j 125 | if not in_string: 126 | if j < len(line): 127 | j += len(delim) 128 | if delim == ';': 129 | i = j 130 | concat.append(line[j : j + len(delim)]) # ensure delimeter is the same type as the string (it may not be due to implicit conversion) 131 | # Eat up any further spaces until a newline 132 | while j < len(line): 133 | delim = line[j:j+1] 134 | if not delim.isspace(): break 135 | j += 1 136 | if delim == "\n": break 137 | if i < j: concat.append(line[i:j]); i = j 138 | yield empty_string.join(concat) 139 | del concat[:] 140 | else: 141 | in_string = delim 142 | else: 143 | if j < len(line): 144 | ch = line[j:j+1] 145 | assert ch == in_string or in_string == '--' 146 | j += 1 147 | i = j 148 | concat.append(ch) 149 | in_string = None 150 | else: 151 | if i < j: concat.append(line[i:j]); i = j 152 | line = None 153 | 154 | class WindowsConsoleIOMixin(object): 155 | # Ctrl+C handling with ReadFile() is messed up on Windows starting on Windows 8... here's some background reading: 156 | # https://stackoverflow.com/a/43260436 157 | # https://github.com/microsoft/terminal/issues/334 158 | # We use ReadConsole when we can, so it doesn't affect us, but it's good info to know regardless. 159 | def __init__(self, fd): 160 | assert isatty(fd), "file descriptor must refer to a console (note that on Windows, NUL satisfies isatty(), but is not a console)" 161 | self.fd = fd 162 | self.handle = msvcrt.get_osfhandle(fd) 163 | def fileno(self): return self.fd 164 | def isatty(self): return isatty(self.fd) 165 | def seekable(self): return False 166 | def readable(self): return GetNumberOfConsoleInputEvents(self.handle, ctypes.byref(DWORD(0))) != 0 167 | def writable(self): n = DWORD(0); return WriteConsoleW(self.handle, ctypes.c_void_p(), n, ctypes.byref(n), ctypes.c_void_p()) != 0 168 | def readwcharsinto(self, buf, n): 169 | nr = DWORD(n) 170 | old_error = ctypes.get_last_error() 171 | ctypes.set_last_error(0) 172 | success = ReadConsoleW(self.handle, buf, nr, ctypes.byref(nr), ctypes.c_void_p()) 173 | error = ctypes.get_last_error() 174 | ctypes.set_last_error(old_error) 175 | if not success: raise ctypes.WinError(error) 176 | ERROR_OPERATION_ABORTED = 995 177 | if nr.value == 0 and error == ERROR_OPERATION_ABORTED: 178 | # Apparently this can trigger pending KeyboardInterrupts? 179 | time.sleep(1.0 / (1 << 64)) 180 | raise KeyboardInterrupt() # If Python doesn't raise it, we can 181 | return nr.value 182 | def writewchars(self, buf, n): 183 | nw = DWORD(n) 184 | if not WriteConsoleW(self.handle, buf, nw, ctypes.byref(nw), ctypes.c_void_p()): 185 | raise ctypes.WinError() 186 | return nw.value 187 | 188 | class WindowsConsoleRawIO(WindowsConsoleIOMixin, io.RawIOBase): 189 | def readinto(self, b): 190 | wordsize = ctypes.sizeof(ctypes.c_wchar) 191 | return self.readwcharsinto(getbuffer(b, True), len(b) // wordsize) * wordsize 192 | def write(self, b): 193 | wordsize = ctypes.sizeof(ctypes.c_wchar) 194 | return self.writewchars(getbuffer(b, False), len(b) // wordsize) * wordsize 195 | 196 | class WindowsConsoleTextIO(WindowsConsoleIOMixin, io.TextIOBase): 197 | buf = None 198 | buffered = unicode() 199 | translate = True 200 | def getbuf(self, ncodeunits): 201 | buf = self.buf 202 | if buf is None or len(buf) < ncodeunits: 203 | self.buf = buf = ctypes.create_unicode_buffer(ncodeunits) 204 | return buf 205 | @staticmethod # Don't let classes override this... they can override the caller instead 206 | def do_read(self, nchars, translate_newlines): 207 | prenewline = os.linesep[:-1] 208 | newline = os.linesep[-1:] 209 | empty = os.linesep[:0] 210 | if nchars is None or nchars < -1: nchars = -1 211 | ncodeunits = nchars if nchars >= 0 else io.DEFAULT_BUFFER_SIZE # Unit mismatch, but doesn't matter; we'll loop 212 | buf = None 213 | istart = 0 214 | while True: 215 | iend = self.buffered.find(newline, istart, min(istart + nchars, len(self.buffered)) if nchars >= 0 else None) if newline is not None else nchars 216 | if iend >= 0: iend += len(newline) if newline is not None else 0 217 | if 0 <= iend <= len(self.buffered): 218 | break 219 | if buf is None: buf = self.getbuf(ncodeunits) 220 | istart = len(self.buffered) 221 | chunk = buf[:self.readwcharsinto(buf, ncodeunits)] 222 | if translate_newlines: chunk = chunk.replace(prenewline, empty) 223 | if chunk.startswith('\x1A'): # EOF on Windows (Ctrl+Z) at the beginning of a line results in the entire rest of the buffer being discarded 224 | iend = istart 225 | break 226 | # Python 2 and Python 3 behaviors differ on Windows... Python 2's sys.stdin.readline() just deletes the next character if it sees EOF in the middle of a string! I won't emulate that here. 227 | self.buffered += chunk # We're relying on Python's concatenation optimization here... we don't do it ourselves, since we want self.buffered to be valid every iteration in case there is an exception raised 228 | result = self.buffered[:iend] 229 | self.buffered = self.buffered[iend:] 230 | return result 231 | def read(self, nchars=-1): return WindowsConsoleTextIO.do_read(self, nchars, None, self.translate) 232 | def readline(self, nchars=-1): return WindowsConsoleTextIO.do_read(self, nchars, self.translate) 233 | def write(self, text): buf = ctypes.create_unicode_buffer(text); return self.writewchars(buf, max(len(buf) - 1, 0)) 234 | 235 | def wrap_windows_console_io(stream, is_output): 236 | fd = None 237 | if stream is not None and sys.version_info[0] < 3 and msvcrt and (is_output or pythonapi) and isatty(stream): 238 | try: fd = stream.fileno() 239 | except io.UnsupportedOperation: pass 240 | result = stream 241 | if fd is not None: 242 | f = GetConsoleOutputCP if is_output else GetConsoleCP 243 | if not f or f() != CP_UTF8: 244 | try: 245 | if True or is_output: 246 | result = WindowsConsoleTextIO(fd) 247 | else: 248 | result = io.TextIOWrapper((io.BufferedWriter if is_output else io.BufferedReader)(WindowsConsoleRawIO(fd)), 'utf-16-le', 'strict', line_buffering=True) 249 | except IOError: pass 250 | return result 251 | 252 | class NonOwningTextIOWrapper(io.TextIOWrapper): 253 | def __init__(self, base_textiowrapper, **kwargs): 254 | assert isinstance(base_textiowrapper, io.TextIOWrapper) 255 | self.base = base_textiowrapper # must keep a reference to this alive so it doesn't get closed 256 | super(NonOwningTextIOWrapper, self).__init__(base_textiowrapper.buffer, **kwargs) 257 | def close(self): 258 | super(NonOwningTextIOWrapper, self).flush() 259 | 260 | def wrap_unicode_stdio(stream, is_writer, encoding): # The underlying stream must NOT be used directly until the stream returned by this function is disposed of 261 | if isinstance(stream, io.TextIOWrapper): 262 | stream.flush() # Make sure nothing is left in the buffer before we re-wrap it 263 | none = object() 264 | kwargs = {} 265 | for key in ['encoding', 'errors', 'newline', 'line_buffering', 'write_through']: 266 | value = getattr(stream, 'newlines' if key == 'newline' else key, none) 267 | if value is not none: 268 | kwargs[key] = value 269 | kwargs['encoding'] = encoding 270 | result = NonOwningTextIOWrapper(stream, **kwargs) 271 | elif 'PYTHONIOENCODING' not in os.environ and str == bytes and stream in (sys.stdin, sys.stdout, sys.stderr): 272 | result = (codecs.getwriter if is_writer else codecs.getreader)(encoding)(stream) 273 | else: 274 | result = stream 275 | return result 276 | 277 | class StringEscapeParser(object): 278 | def __init__(self): 279 | import re 280 | self.pattern = re.compile("\"((?:[^\"\\n]+|\\\\.)*)(?:\"|$)|\'([^\'\\n]*)(?:\'|$)|(\\S+)") 281 | self.escape_pattern = re.compile("\\\\(.)", re.DOTALL) 282 | @staticmethod 283 | def escape_replacement(m): 284 | text = m.group(1) 285 | if text == "\\": text = "\\" 286 | elif text == "/": text = "\n" 287 | elif text == "n": text = "\n" 288 | elif text == "r": text = "\r" 289 | elif text == "t": text = "\t" 290 | elif text == "v": text = "\v" 291 | elif text == "f": text = "\f" 292 | elif text == "a": text = "\a" 293 | elif text == "b": text = "\b" 294 | return text 295 | def __call__(self, s): 296 | escape_pattern = self.escape_pattern 297 | escape_replacement = self.escape_replacement 298 | result = [] 299 | for match in self.pattern.finditer(s): 300 | [m1, m2, m3] = match.groups() 301 | if m1 is not None: result.append(escape_pattern.sub(escape_replacement, m1)) 302 | if m2 is not None: result.append(m2) 303 | if m3 is not None: result.append(escape_pattern.sub(escape_replacement, m3)) 304 | return result 305 | 306 | class Database(object): 307 | def __init__(self, name, *args, **kwargs): 308 | self.connection = sqlite3.connect(name, *args, **kwargs) 309 | self.cursor = self.connection.cursor() 310 | self.name = name # assign name only AFTER cursor is created 311 | 312 | def isatty(file_or_fd): 313 | result = True 314 | method = getattr(file_or_fd, 'isatty', None) if not isinstance(file_or_fd, int) else None # this check is just an optimization 315 | if method is not None: 316 | try: tty = method() 317 | except io.UnsupportedOperation: tty = None 318 | result = result and tty is not None and tty 319 | method = getattr(file_or_fd, 'fileno', None) if not isinstance(file_or_fd, int) else None # this check is just an optimization 320 | if method is not None: 321 | try: fd = method() 322 | except io.UnsupportedOperation: fd = None 323 | result = result and fd is not None and os.isatty(fd) and (not msvcrt or GetConsoleMode(msvcrt.get_osfhandle(fd), ctypes.byref(DWORD(0))) != 0) 324 | return result 325 | 326 | def can_call_input_for_stdio(stream): 327 | return stream == sys.stdin and sys.version_info[0] >= 3 328 | 329 | class StdIOProxy(object): 330 | # Maybe useful later: codecs.StreamRecoder(bytesIO, codec.decode, codec.encode, codec.streamwriter, codec.streamreader, errors='surrogateescape') 331 | def __init__(self, stdin, stdout, stderr, codec, allow_set_code_page): 332 | self.codec = codec 333 | streams = (stdin, stdout, stderr) 334 | for stream in streams: 335 | assert isinstance(stream, io.IOBase) or sys.version_info[0] < 3 and isinstance(stream, file) or hasattr(stream, 'mode'), "unable to determine stream type" 336 | assert not isinstance(stream, io.RawIOBase), "RAW I/O APIs are different and not supported" 337 | self.streaminfos = tuple(map(lambda stream: 338 | ( 339 | stream, 340 | isinstance(stream, io.BufferedIOBase) or isinstance(stream, io.RawIOBase) or not isinstance(stream, io.TextIOBase) and 'b' in stream.mode, 341 | isinstance(stream, io.TextIOBase) or not (isinstance(stream, io.BufferedIOBase) or isinstance(stream, io.RawIOBase)) and 'b' not in stream.mode, 342 | allow_set_code_page 343 | ), 344 | streams)) 345 | @property 346 | def stdin(self): return self.streaminfos[0][0] 347 | @property 348 | def stdout(self): return self.streaminfos[1][0] 349 | @property 350 | def stderr(self): return self.streaminfos[2][0] 351 | def _coerce(self, streaminfo, codec, arg): 352 | stream = streaminfo[0] 353 | can_binary = streaminfo[1] 354 | can_text = streaminfo[2] 355 | if not isinstance(arg, bytes) and not isinstance(arg, buffer) and not isinstance(arg, unicode): 356 | arg = unicode(arg) 357 | if isinstance(arg, bytes) or isinstance(arg, buffer): 358 | if not can_binary: 359 | arg = codec.decode(arg, 'surrogateescape')[0] 360 | elif isinstance(arg, unicode): 361 | if not can_text: 362 | arg = codec.encode(unicode(arg), 'strict')[0] 363 | return arg 364 | @staticmethod 365 | def _do_readline(stream, allow_set_code_page, *args): 366 | new_code_page = CP_UTF8 367 | old_code_page = GetConsoleCP() if msvcrt and GetConsoleCP and isatty(stream) else None 368 | if old_code_page == new_code_page: old_code_page = None # Don't change code page if it's already correct... 369 | if old_code_page is not None: 370 | if not SetConsoleCP(new_code_page): 371 | old_code_page = None 372 | try: 373 | result = stream.readline(*args) 374 | finally: 375 | if old_code_page is not None: 376 | SetConsoleCP(old_code_page) 377 | return result 378 | @staticmethod 379 | def _do_write(stream, allow_set_code_page, *args): 380 | new_code_page = CP_UTF8 381 | old_code_page = GetConsoleOutputCP() if msvcrt and GetConsoleOutputCP and isatty(stream) else None 382 | if old_code_page == new_code_page: old_code_page = None # Don't change code page if it's already correct... 383 | if old_code_page is not None: 384 | if not SetConsoleOutputCP(new_code_page): 385 | old_code_page = None 386 | try: 387 | result = stream.write(*args) 388 | finally: 389 | if old_code_page is not None: 390 | SetConsoleCP(old_code_page) 391 | return result 392 | def _readln(self, streaminfo, codec, prompt): 393 | stream = streaminfo[0] 394 | can_binary = streaminfo[1] 395 | allow_set_code_page = streaminfo[3] 396 | if can_call_input_for_stdio(stream) and not can_binary: # input() can't work with binary data 397 | result = self._coerce(streaminfo, codec, "") 398 | try: 399 | result = input(*((self._coerce(streaminfo, codec, prompt),) if prompt is not None else ())) 400 | result += self._coerce(streaminfo, codec, "\n") 401 | except EOFError: pass 402 | else: 403 | self.output(*((prompt,) if prompt is not None else ())) 404 | self.error() 405 | result = StdIOProxy._do_readline(stream, allow_set_code_page) 406 | return result 407 | def _writeln(self, streaminfo, codec, *args, **kwargs): 408 | stream = streaminfo[0] 409 | allow_set_code_page = streaminfo[3] 410 | flush = kwargs.pop('flush', True) 411 | kwargs.setdefault('end', '\n') 412 | kwargs.setdefault('sep', ' ') 413 | end = kwargs.get('end') 414 | sep = kwargs.get('sep') 415 | first = True 416 | for arg in args: 417 | if first: first = False 418 | elif sep is not None: 419 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, sep)) 420 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, arg)) 421 | if end is not None: 422 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, end)) 423 | if flush: stream.flush() 424 | def inputln(self, prompt=None): return self._readln(self.streaminfos[0], self.codec, prompt) 425 | def output(self, *args, **kwargs): kwargs.setdefault('end', None); return self._writeln(self.streaminfos[1], self.codec, *args, **kwargs) 426 | def outputln(self, *args, **kwargs): return self._writeln(self.streaminfos[1], self.codec, *args, **kwargs) 427 | def error(self, *args, **kwargs): kwargs.setdefault('end', None); return self._writeln(self.streaminfos[2], self.codec, *args, **kwargs) 428 | def errorln(self, *args, **kwargs): return self._writeln(self.streaminfos[2], self.codec, *args, **kwargs) 429 | 430 | class bytes_comparable_with_unicode(bytes): # For Python 2/3 compatibility, to allow implicit conversion between strings and bytes when it is safe. (Used for strings like literals which we know be safe.) 431 | codec = codecs.lookup('ascii') # MUST be a safe encoding 432 | @classmethod 433 | def coerce(cls, other, for_output=False): 434 | return cls.codec.encode(other)[0] if not isinstance(other, bytes) else bytes_comparable_with_unicode(other) if for_output else other 435 | @classmethod 436 | def translate_if_bytes(cls, value): 437 | if value is not None and isinstance(value, bytes): value = cls(value) 438 | return value 439 | def __hash__(self): return super(bytes_comparable_with_unicode, self).__hash__() # To avoid warning 440 | def __eq__(self, other): return super(bytes_comparable_with_unicode, self).__eq__(self.coerce(other)) 441 | def __ne__(self, other): return super(bytes_comparable_with_unicode, self).__ne__(self.coerce(other)) 442 | def __lt__(self, other): return super(bytes_comparable_with_unicode, self).__lt__(self.coerce(other)) 443 | def __gt__(self, other): return super(bytes_comparable_with_unicode, self).__gt__(self.coerce(other)) 444 | def __le__(self, other): return super(bytes_comparable_with_unicode, self).__le__(self.coerce(other)) 445 | def __ge__(self, other): return super(bytes_comparable_with_unicode, self).__ge__(self.coerce(other)) 446 | def __getitem__(self, index): return self.coerce(super(bytes_comparable_with_unicode, self).__getitem__(index), True) 447 | def __add__(self, other): return self.coerce(super(bytes_comparable_with_unicode, self).__add__(self.coerce(other)), True) 448 | def __iadd__(self, other): return self.coerce(super(bytes_comparable_with_unicode, self).__iadd__(self.coerce(other)), True) 449 | def __radd__(self, other): return self.coerce(self.coerce(other).__add__(self), True) 450 | def find(self, other, *args): return super(bytes_comparable_with_unicode, self).find(self.coerce(other), *args) 451 | def join(self, others): return self.coerce(super(bytes_comparable_with_unicode, self).join(map(self.coerce, others)), True) 452 | def startswith(self, other): return super(bytes_comparable_with_unicode, self).startswith(self.coerce(other)) 453 | def __str__(self): return self.codec.decode(self)[0] 454 | if str == bytes: 455 | __unicode__ = __str__ 456 | def __str__(self): raise NotImplementedError() 457 | 458 | def wrap_bytes_comparable_with_unicode_readline(readline): 459 | def callback(*args): 460 | line = readline(*args) 461 | line = bytes_comparable_with_unicode.translate_if_bytes(line) 462 | return line 463 | return callback 464 | 465 | def main(program, *args, **kwargs): # **kwargs = dict(stdin=file, stdout=file, stderr=file); useful for callers who import this module 466 | import argparse # slow import (compiles regexes etc.), so don't import it until needed 467 | argparser = argparse.ArgumentParser( 468 | prog=os.path.basename(program), 469 | usage=None, 470 | description=None, 471 | epilog=None, 472 | parents=[], 473 | formatter_class=argparse.RawTextHelpFormatter) 474 | argparser.add_argument('-version', '--version', action='store_true', help="show SQLite version") 475 | argparser.add_argument('-batch', '--batch', action='store_true', help="force batch I/O") 476 | argparser.add_argument('-init', '--init', metavar="FILE", help="read/process named file") 477 | argparser.add_argument('filename', nargs='?', metavar="FILENAME", help="is the name of an SQLite database.\nA new database is created if the file does not previously exist.") 478 | argparser.add_argument('sql', nargs='*', metavar="SQL", help="SQL commnds to execute after opening database") 479 | argparser.add_argument('--readline', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to import readline if available (default: %(default)s)") 480 | argparser.add_argument('--self-test', action='store_true', help="perform a basic self-test") 481 | argparser.add_argument('--cross-test', action='store_true', help="perform a basic test against the official executable") 482 | argparser.add_argument('--unicode-stdio', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to enable Unicode wrapper for standard I/O (default: %(default)s)") 483 | argparser.add_argument('--console', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to auto-detect and use console window APIs (default: %(default)s)") 484 | argparser.add_argument('--encoding', default=ENCODING, help="the default encoding to use (default: %(default)s)") 485 | (stdin, stdout, stderr) = (kwargs.pop('stdin', sys.stdin), kwargs.pop('stdout', sys.stdout), kwargs.pop('stderr', sys.stderr)) 486 | parsed_args = argparser.parse_args(args) 487 | codec = codecs.lookup(parsed_args.encoding or argparser.get_default('encoding')) 488 | if parsed_args.self_test: self_test(codec) 489 | if parsed_args.cross_test: cross_test("sqlite3", codec) 490 | parse_escaped_strings = StringEscapeParser() 491 | if parsed_args.unicode_stdio == "true": 492 | stdin = wrap_unicode_stdio(stdin, False, codec.name) 493 | stdout = wrap_unicode_stdio(stdout, True, codec.name) 494 | stderr = wrap_unicode_stdio(stderr, True, codec.name) 495 | if parsed_args.console == "true": 496 | stdin = wrap_windows_console_io(stdin, False) 497 | stdout = wrap_windows_console_io(stdout, True) 498 | stderr = wrap_windows_console_io(stderr, True) 499 | allow_set_code_page = sys.version_info[0] < 3 and False # This is only necessary on Python 2 if we use the default I/O functions instead of bypassing to ReadConsole()/WriteConsole() 500 | stdio = StdIOProxy(stdin, stdout, stderr, codec, allow_set_code_page) 501 | db = None 502 | no_args = len(args) == 0 503 | init_sql = parsed_args.sql 504 | is_nonpipe_input = stdin.isatty() # NOT the same thing as TTY! (NUL and /dev/null are the difference) 505 | init_show_prompt = not parsed_args.batch and is_nonpipe_input 506 | if not parsed_args.batch and isatty(stdin) and (parsed_args.readline == "true" or __name__ == '__main__') and parsed_args.readline != "false": 507 | try: 508 | with warnings.catch_warnings(): 509 | warnings.filterwarnings('ignore', category=DeprecationWarning) 510 | import readline 511 | except ImportError: pass 512 | if parsed_args and parsed_args.version: 513 | stdio.outputln(sqlite3.sqlite_version); 514 | else: 515 | filename = parsed_args.filename 516 | if filename is None: filename = ":memory:" 517 | db = Database(filename, isolation_level=None) 518 | def exec_script(db, filename, ignore_io_errors): 519 | try: 520 | with io.open(filename, 'r', encoding=codec.name) as f: # Assume .sql files are text -- any binary data inside them should be X'' encoded, not embedded directly 521 | for command in sql_commands(wrap_bytes_comparable_with_unicode_readline(lambda *args: (lambda s: (s) or None)(f.readline()))): 522 | result = exec_command(db, command, False and ignore_io_errors) 523 | if result is not None: 524 | return result 525 | except IOError as ex: 526 | stdio.errorln(ex) 527 | if not ignore_io_errors: return ex.errno 528 | def raise_invalid_command_error(command): 529 | if isinstance(command, bytes): command = codec.decode(command)[0] 530 | if command.startswith("."): command = command[1:] 531 | raise RuntimeError("Error: unknown command or invalid arguments: \"%s\". Enter \".help\" for help" % (command.rstrip().replace("\\", "\\\\").replace("\"", "\\\""),)) 532 | def exec_command(db, command, ignore_io_errors): 533 | results = None 534 | query = None 535 | query_parameters = {} 536 | try: 537 | if command.startswith("."): 538 | args = list(parse_escaped_strings(command)) 539 | if args[0] in (".quit", ".exit"): 540 | return 0 541 | elif args[0] == ".help": 542 | stdio.error(""" 543 | .cd DIRECTORY Change the working directory to DIRECTORY 544 | .dump Dump the database in an SQL text format 545 | .exit Exit this program 546 | .help Show this message 547 | .open FILE Close existing database and reopen FILE 548 | .print STRING... Print literal STRING 549 | .quit Exit this program 550 | .read FILENAME Execute SQL in FILENAME 551 | .schema ?PATTERN? Show the CREATE statements matching PATTERN 552 | .show Show the current values for various settings 553 | .tables ?TABLE? List names of tables 554 | """.lstrip()) 555 | elif args[0] == ".cd": 556 | if len(args) != 2: raise_invalid_command_error(command) 557 | os.chdir(args[1]) 558 | elif args[0] == ".dump": 559 | if len(args) != 1: raise_invalid_command_error(command) 560 | foreign_keys = db.cursor.execute("PRAGMA foreign_keys;").fetchone()[0] 561 | if foreign_keys in (0, "0", "off", "OFF"): 562 | stdio.outputln("PRAGMA foreign_keys=OFF;", flush=False) 563 | for line in db.connection.iterdump(): 564 | stdio.outputln(line, flush=False) 565 | stdio.output() 566 | elif args[0] == ".open": 567 | if len(args) <= 1: raise_invalid_command_error(command) 568 | filename = args[-1] 569 | for option in args[+1:-1]: 570 | raise ValueError("option %s not supported" % (repr(option),)) 571 | try: db.__init__(filename) 572 | except sqlite3.OperationalError as ex: 573 | ex.args = ex.args[:0] + ("Error: unable to open database \"%s\": %s" % (filename, ex.args[0]),) + ex.args[1:] 574 | raise 575 | elif args[0] == ".print": 576 | stdio.outputln(*args[1:]) 577 | elif args[0] == ".read": 578 | if len(args) != 2: raise_invalid_command_error(command) 579 | exec_script(db, args[1], ignore_io_errors) 580 | elif args[0] == ".schema": 581 | if len(args) > 2: raise_invalid_command_error(command) 582 | pattern = args[1] if len(args) > 1 else None 583 | query_parameters['type'] = 'table' 584 | if pattern is not None: 585 | query_parameters['pattern'] = pattern 586 | query = "SELECT sql || ';' FROM sqlite_master WHERE type = :type" + (" AND name LIKE :pattern" if pattern is not None else "") + ";" 587 | elif args[0] == ".show": 588 | if len(args) > 2: raise_invalid_command_error(command) 589 | stdio.errorln(" filename:", db.name) 590 | elif args[0] == ".tables": 591 | if len(args) > 2: raise_invalid_command_error(command) 592 | pattern = args[1] if len(args) > 1 else None 593 | query_parameters['type'] = 'table' 594 | if pattern is not None: 595 | query_parameters['pattern'] = pattern 596 | query = "SELECT name FROM sqlite_master WHERE type = :type" + (" AND name LIKE :pattern" if pattern is not None else "") + ";" 597 | else: 598 | raise_invalid_command_error(args[0]) 599 | else: 600 | query = command 601 | if query is not None: 602 | results = db.cursor.execute(query if isinstance(query, unicode) else codec.decode(query, 'surrogatereplace')[0], query_parameters) 603 | except (RuntimeError, OSError, FileNotFoundError, sqlite3.OperationalError) as ex: 604 | stdio.errorln(exception_encode(ex, codec)) 605 | if results is not None: 606 | for row in results: 607 | stdio.outputln(*tuple(map(lambda item: item if item is not None else "", row)), sep="|", flush=False) 608 | stdio.output() 609 | if db: 610 | if parsed_args and parsed_args.init: 611 | if is_nonpipe_input: stdio.errorln("-- Loading resources from", parsed_args.init) 612 | exec_script(db, parsed_args.init, False) 613 | def read_stdin(index, not_in_the_middle_of_any_input, prev_line): 614 | show_prompt = init_show_prompt 615 | to_write = [] 616 | if index < len(init_sql): 617 | line = init_sql[index] 618 | if not line.startswith(".") and not line.rstrip().endswith(";"): 619 | line += ";" 620 | elif index == len(init_sql) and len(init_sql) > 0: 621 | line = None 622 | else: 623 | if show_prompt: 624 | if not_in_the_middle_of_any_input: 625 | show_prompt = False 626 | if index == 0: 627 | to_write.append("SQLite version %s (adapter version %s)\nEnter \".help\" for usage hints.\n" % (sqlite3.sqlite_version, sqlite3.version)) 628 | if no_args: 629 | to_write.append("Connected to a transient in-memory database.\nUse \".open FILENAME\" to reopen on a persistent database.\n") 630 | if index > 0 and not prev_line: 631 | to_write.append("\n") 632 | to_write.append("%7s " % ("sqlite%s>" % ("",) if not_in_the_middle_of_any_input else "...>",)) 633 | try: 634 | line = stdio.inputln("".join(to_write)) 635 | except KeyboardInterrupt: 636 | line = "" 637 | raise # just kidding, don't handle it for now... 638 | return line 639 | for command in sql_commands(wrap_bytes_comparable_with_unicode_readline(read_stdin)): 640 | result = exec_command(db, command, True) 641 | if result is not None: 642 | return result 643 | if init_show_prompt and len(init_sql) == 0: 644 | stdio.outputln() 645 | 646 | def call_program(cmdline, input_text): 647 | import subprocess 648 | return subprocess.Popen(cmdline, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=False).communicate(input_text) 649 | 650 | def test_query(): 651 | hexcodec = codecs.lookup('hex_codec') 652 | ascii = 'ascii' 653 | data1 = b"\xD8\xA2" 654 | data2 = b"\x01\x02\xFF\x01\xFF\xFE\xFD" 655 | values = [data1, data2] 656 | query_bytes = b'SELECT %s;' % (b", ".join(map(lambda b: b"X'%s'" % (hexcodec.encode(b)[0].upper(),), values)),) 657 | expected_bytes = b"%s\n" % (b"|".join(values),) 658 | return query_bytes, expected_bytes 659 | 660 | def cross_test(sqlite_cmdline, codec): 661 | (query_bytes, expected_bytes) = test_query() 662 | (official_output, official_error) = call_program(sqlite_cmdline, query_bytes) 663 | # We can't use os.linesep here since binaries may belong to different platforms (Win32/MinGW vs. MSYS/Cygwin vs. WSL...) 664 | official_output = official_output.replace(b"\r\n", b"\n") 665 | official_error = official_error.replace(b"\r\n", b"\n") 666 | if official_output != expected_bytes: 667 | raise sqlite3.ProgrammingError("expected bytes are wrong: official %s != expected %s" % (repr(official_output), repr(expected_bytes))) 668 | if official_error: 669 | raise sqlite3.ProgrammingError("did not expect errors from official binary") 670 | 671 | def self_test(codec): 672 | (query_bytes, expected_bytes) = test_query() 673 | if not (lambda stdin, stdout, stderr: not main(sys.argv[0], stdin=stdin, stdout=stdout, stderr=stderr) and stdout.getvalue() == expected_bytes)(io.BytesIO(query_bytes), io.BytesIO(), io.BytesIO()): 674 | raise sqlite3.ProgrammingError("byte I/O is broken") 675 | if not (lambda stdin, stdout, stderr: not main(sys.argv[0], stdin=stdin, stdout=stdout, stderr=stderr) and stdout.getvalue() == codec.decode(expected_bytes, 'surrogateescape'))(io.StringIO(query_bytes.decode(ascii)), io.StringIO(), io.StringIO()): 676 | raise sqlite3.ProgrammingError("string I/O is broken") 677 | 678 | if __name__ == '__main__': 679 | import sys 680 | exit_code = main(*sys.argv) 681 | if exit_code not in (None, 0): raise SystemExit(exit_code) 682 | -------------------------------------------------------------------------------- /lec26/lec26-tables.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec26/lec26-tables.pdf -------------------------------------------------------------------------------- /lec27/demo/demo.sql: -------------------------------------------------------------------------------- 1 | .open 2 | 3 | --new 4 | 5 | create table animals as 6 | select "dog" as kind, 4 as legs, 20 as weight union 7 | select "cat" , 4 , 10 union 8 | select "ferret" , 4 , 10 union 9 | select "parrot" , 2 , 6 union 10 | select "penguin" , 2 , 10 union 11 | select "t-rex" , 2 , 12000; 12 | -------------------------------------------------------------------------------- /lec27/demo/sqlite_shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Licensed under the MIT license 4 | 5 | # A simple SQLite shell that uses the built-in Python adapter. 6 | 7 | import codecs 8 | import io 9 | import os 10 | import sys 11 | import sqlite3 12 | import time 13 | import warnings 14 | 15 | try: FileNotFoundError 16 | except NameError: FileNotFoundError = OSError 17 | 18 | if str != bytes: buffer = bytes 19 | if str != bytes: unicode = str 20 | 21 | try: import msvcrt 22 | except ImportError: msvcrt = None 23 | 24 | CP_UTF8 = 65001 25 | pythonapi = None 26 | if msvcrt: 27 | import ctypes 28 | (BOOL, DWORD, HANDLE, UINT) = (ctypes.c_long, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_uint) 29 | GetConsoleCP = ctypes.WINFUNCTYPE(UINT)(('GetConsoleCP', ctypes.windll.kernel32)) 30 | SetConsoleCP = ctypes.WINFUNCTYPE(BOOL, UINT)(('SetConsoleCP', ctypes.windll.kernel32)) 31 | GetConsoleOutputCP = ctypes.WINFUNCTYPE(UINT)(('GetConsoleOutputCP', ctypes.windll.kernel32)) 32 | SetConsoleOutputCP = ctypes.WINFUNCTYPE(BOOL, UINT)(('SetConsoleOutputCP', ctypes.windll.kernel32)) 33 | GetConsoleMode = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(DWORD), use_last_error=True)(('GetConsoleMode', ctypes.windll.kernel32)) 34 | GetNumberOfConsoleInputEvents = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(DWORD), use_last_error=True)(('GetNumberOfConsoleInputEvents', ctypes.windll.kernel32)) 35 | ReadConsoleW = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.c_void_p, DWORD, ctypes.POINTER(DWORD), ctypes.c_void_p, use_last_error=True)(('ReadConsoleW', ctypes.windll.kernel32)) 36 | WriteConsoleW = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.c_void_p, DWORD, ctypes.POINTER(DWORD), ctypes.c_void_p, use_last_error=True)(('WriteConsoleW', ctypes.windll.kernel32)) 37 | class Py_buffer(ctypes.Structure): _fields_ = [('buf', ctypes.c_void_p), ('obj', ctypes.py_object), ('len', ctypes.c_ssize_t), ('itemsize', ctypes.c_ssize_t), ('readonly', ctypes.c_int), ('ndim', ctypes.c_int), ('format', ctypes.c_char_p), ('shape', ctypes.POINTER(ctypes.c_ssize_t)), ('strides', ctypes.POINTER(ctypes.c_ssize_t)), ('suboffsets', ctypes.POINTER(ctypes.c_ssize_t))] + ([('smalltable', ctypes.c_ssize_t * 2)] if sys.version_info[0] <= 2 else []) + [('internal', ctypes.c_void_p)] 38 | try: from ctypes import pythonapi 39 | except ImportError: pass 40 | if pythonapi: 41 | def getbuffer(b, writable): 42 | arr = Py_buffer() 43 | pythonapi.PyObject_GetBuffer(ctypes.py_object(b), ctypes.byref(arr), ctypes.c_int(writable)) 44 | try: buf = (ctypes.c_ubyte * arr.len).from_address(arr.buf) 45 | finally: pythonapi.PyBuffer_Release(ctypes.byref(arr)) 46 | return buf 47 | 48 | ENCODING = 'utf-8' 49 | 50 | if sys.version_info[0] < 3: 51 | class NotASurrogateError(Exception): pass 52 | def surrogateescape_handler(exc): 53 | # Source: https://github.com/PythonCharmers/python-future/blob/aef57391c0cd58bf840dff5e2bc2c8c0f5b0a1b4/src/future/utils/surrogateescape.py 54 | mystring = exc.object[exc.start:exc.end] 55 | try: 56 | if isinstance(exc, UnicodeDecodeError): 57 | decoded = [] 58 | for ch in mystring: 59 | if isinstance(ch, int): 60 | code = ch 61 | else: 62 | code = ord(ch) 63 | if 0x80 <= code <= 0xFF: 64 | decoded.append(unichr(0xDC00 + code)) 65 | elif code <= 0x7F: 66 | decoded.append(unichr(code)) 67 | else: 68 | raise NotASurrogateError() 69 | decoded = str().join(decoded) 70 | elif isinstance(exc, UnicodeEncodeError): 71 | decoded = [] 72 | for ch in mystring: 73 | code = ord(ch) 74 | if not 0xD800 <= code <= 0xDCFF: 75 | raise NotASurrogateError() 76 | if 0xDC00 <= code <= 0xDC7F: 77 | decoded.append(unichr(code - 0xDC00)) 78 | elif code <= 0xDCFF: 79 | decoded.append(unichr(code - 0xDC00)) 80 | else: 81 | raise NotASurrogateError() 82 | decoded = str().join(decoded) 83 | else: 84 | raise exc 85 | except NotASurrogateError: 86 | raise exc 87 | return (decoded, exc.end) 88 | codecs.register_error('surrogateescape', surrogateescape_handler) 89 | 90 | def exception_encode(ex, codec): 91 | if str == bytes: 92 | reduced = ex.__reduce__() 93 | ex = reduced[0](*tuple(map(lambda arg: codec.decode(arg)[0] if isinstance(arg, bytes) else arg, reduced[1]))) 94 | return ex 95 | 96 | def sql_commands(read_line): 97 | delims = ['"', "'", ';', '--'] 98 | counter = 0 99 | in_string = None 100 | j = i = 0 101 | prev_line = None 102 | line = None 103 | concat = [] 104 | while True: 105 | if line is None: 106 | while True: # process preprocessor directives 107 | counter += 1 108 | not_in_the_middle_of_any_input = not in_string and i == j and all(map(lambda chunk_: len(chunk_) == 0, concat)) 109 | line = read_line(counter - 1, not_in_the_middle_of_any_input, prev_line) 110 | empty_string = line[:0] if line is not None else line 111 | prev_line = line 112 | if not line: 113 | break 114 | if not_in_the_middle_of_any_input and line.startswith("."): 115 | yield line 116 | line = None 117 | else: 118 | break 119 | if not line: 120 | break 121 | j = i = 0 122 | if j < len(line): 123 | (j, delim) = min(map(lambda pair: pair if pair[0] >= 0 else (len(line), pair[1]), map(lambda d: (line.find(d, j), d), in_string or delims if in_string != '--' else "\n"))) 124 | if i < j: concat.append(line[i:j]); i = j 125 | if not in_string: 126 | if j < len(line): 127 | j += len(delim) 128 | if delim == ';': 129 | i = j 130 | concat.append(line[j : j + len(delim)]) # ensure delimeter is the same type as the string (it may not be due to implicit conversion) 131 | # Eat up any further spaces until a newline 132 | while j < len(line): 133 | delim = line[j:j+1] 134 | if not delim.isspace(): break 135 | j += 1 136 | if delim == "\n": break 137 | if i < j: concat.append(line[i:j]); i = j 138 | yield empty_string.join(concat) 139 | del concat[:] 140 | else: 141 | in_string = delim 142 | else: 143 | if j < len(line): 144 | ch = line[j:j+1] 145 | assert ch == in_string or in_string == '--' 146 | j += 1 147 | i = j 148 | concat.append(ch) 149 | in_string = None 150 | else: 151 | if i < j: concat.append(line[i:j]); i = j 152 | line = None 153 | 154 | class WindowsConsoleIOMixin(object): 155 | # Ctrl+C handling with ReadFile() is messed up on Windows starting on Windows 8... here's some background reading: 156 | # https://stackoverflow.com/a/43260436 157 | # https://github.com/microsoft/terminal/issues/334 158 | # We use ReadConsole when we can, so it doesn't affect us, but it's good info to know regardless. 159 | def __init__(self, fd): 160 | assert isatty(fd), "file descriptor must refer to a console (note that on Windows, NUL satisfies isatty(), but is not a console)" 161 | self.fd = fd 162 | self.handle = msvcrt.get_osfhandle(fd) 163 | def fileno(self): return self.fd 164 | def isatty(self): return isatty(self.fd) 165 | def seekable(self): return False 166 | def readable(self): return GetNumberOfConsoleInputEvents(self.handle, ctypes.byref(DWORD(0))) != 0 167 | def writable(self): n = DWORD(0); return WriteConsoleW(self.handle, ctypes.c_void_p(), n, ctypes.byref(n), ctypes.c_void_p()) != 0 168 | def readwcharsinto(self, buf, n): 169 | nr = DWORD(n) 170 | old_error = ctypes.get_last_error() 171 | ctypes.set_last_error(0) 172 | success = ReadConsoleW(self.handle, buf, nr, ctypes.byref(nr), ctypes.c_void_p()) 173 | error = ctypes.get_last_error() 174 | ctypes.set_last_error(old_error) 175 | if not success: raise ctypes.WinError(error) 176 | ERROR_OPERATION_ABORTED = 995 177 | if nr.value == 0 and error == ERROR_OPERATION_ABORTED: 178 | # Apparently this can trigger pending KeyboardInterrupts? 179 | time.sleep(1.0 / (1 << 64)) 180 | raise KeyboardInterrupt() # If Python doesn't raise it, we can 181 | return nr.value 182 | def writewchars(self, buf, n): 183 | nw = DWORD(n) 184 | if not WriteConsoleW(self.handle, buf, nw, ctypes.byref(nw), ctypes.c_void_p()): 185 | raise ctypes.WinError() 186 | return nw.value 187 | 188 | class WindowsConsoleRawIO(WindowsConsoleIOMixin, io.RawIOBase): 189 | def readinto(self, b): 190 | wordsize = ctypes.sizeof(ctypes.c_wchar) 191 | return self.readwcharsinto(getbuffer(b, True), len(b) // wordsize) * wordsize 192 | def write(self, b): 193 | wordsize = ctypes.sizeof(ctypes.c_wchar) 194 | return self.writewchars(getbuffer(b, False), len(b) // wordsize) * wordsize 195 | 196 | class WindowsConsoleTextIO(WindowsConsoleIOMixin, io.TextIOBase): 197 | buf = None 198 | buffered = unicode() 199 | translate = True 200 | def getbuf(self, ncodeunits): 201 | buf = self.buf 202 | if buf is None or len(buf) < ncodeunits: 203 | self.buf = buf = ctypes.create_unicode_buffer(ncodeunits) 204 | return buf 205 | @staticmethod # Don't let classes override this... they can override the caller instead 206 | def do_read(self, nchars, translate_newlines): 207 | prenewline = os.linesep[:-1] 208 | newline = os.linesep[-1:] 209 | empty = os.linesep[:0] 210 | if nchars is None or nchars < -1: nchars = -1 211 | ncodeunits = nchars if nchars >= 0 else io.DEFAULT_BUFFER_SIZE # Unit mismatch, but doesn't matter; we'll loop 212 | buf = None 213 | istart = 0 214 | while True: 215 | iend = self.buffered.find(newline, istart, min(istart + nchars, len(self.buffered)) if nchars >= 0 else None) if newline is not None else nchars 216 | if iend >= 0: iend += len(newline) if newline is not None else 0 217 | if 0 <= iend <= len(self.buffered): 218 | break 219 | if buf is None: buf = self.getbuf(ncodeunits) 220 | istart = len(self.buffered) 221 | chunk = buf[:self.readwcharsinto(buf, ncodeunits)] 222 | if translate_newlines: chunk = chunk.replace(prenewline, empty) 223 | if chunk.startswith('\x1A'): # EOF on Windows (Ctrl+Z) at the beginning of a line results in the entire rest of the buffer being discarded 224 | iend = istart 225 | break 226 | # Python 2 and Python 3 behaviors differ on Windows... Python 2's sys.stdin.readline() just deletes the next character if it sees EOF in the middle of a string! I won't emulate that here. 227 | self.buffered += chunk # We're relying on Python's concatenation optimization here... we don't do it ourselves, since we want self.buffered to be valid every iteration in case there is an exception raised 228 | result = self.buffered[:iend] 229 | self.buffered = self.buffered[iend:] 230 | return result 231 | def read(self, nchars=-1): return WindowsConsoleTextIO.do_read(self, nchars, None, self.translate) 232 | def readline(self, nchars=-1): return WindowsConsoleTextIO.do_read(self, nchars, self.translate) 233 | def write(self, text): buf = ctypes.create_unicode_buffer(text); return self.writewchars(buf, max(len(buf) - 1, 0)) 234 | 235 | def wrap_windows_console_io(stream, is_output): 236 | fd = None 237 | if stream is not None and sys.version_info[0] < 3 and msvcrt and (is_output or pythonapi) and isatty(stream): 238 | try: fd = stream.fileno() 239 | except io.UnsupportedOperation: pass 240 | result = stream 241 | if fd is not None: 242 | f = GetConsoleOutputCP if is_output else GetConsoleCP 243 | if not f or f() != CP_UTF8: 244 | try: 245 | if True or is_output: 246 | result = WindowsConsoleTextIO(fd) 247 | else: 248 | result = io.TextIOWrapper((io.BufferedWriter if is_output else io.BufferedReader)(WindowsConsoleRawIO(fd)), 'utf-16-le', 'strict', line_buffering=True) 249 | except IOError: pass 250 | return result 251 | 252 | class NonOwningTextIOWrapper(io.TextIOWrapper): 253 | def __init__(self, base_textiowrapper, **kwargs): 254 | assert isinstance(base_textiowrapper, io.TextIOWrapper) 255 | self.base = base_textiowrapper # must keep a reference to this alive so it doesn't get closed 256 | super(NonOwningTextIOWrapper, self).__init__(base_textiowrapper.buffer, **kwargs) 257 | def close(self): 258 | super(NonOwningTextIOWrapper, self).flush() 259 | 260 | def wrap_unicode_stdio(stream, is_writer, encoding): # The underlying stream must NOT be used directly until the stream returned by this function is disposed of 261 | if isinstance(stream, io.TextIOWrapper): 262 | stream.flush() # Make sure nothing is left in the buffer before we re-wrap it 263 | none = object() 264 | kwargs = {} 265 | for key in ['encoding', 'errors', 'newline', 'line_buffering', 'write_through']: 266 | value = getattr(stream, 'newlines' if key == 'newline' else key, none) 267 | if value is not none: 268 | kwargs[key] = value 269 | kwargs['encoding'] = encoding 270 | result = NonOwningTextIOWrapper(stream, **kwargs) 271 | elif 'PYTHONIOENCODING' not in os.environ and str == bytes and stream in (sys.stdin, sys.stdout, sys.stderr): 272 | result = (codecs.getwriter if is_writer else codecs.getreader)(encoding)(stream) 273 | else: 274 | result = stream 275 | return result 276 | 277 | class StringEscapeParser(object): 278 | def __init__(self): 279 | import re 280 | self.pattern = re.compile("\"((?:[^\"\\n]+|\\\\.)*)(?:\"|$)|\'([^\'\\n]*)(?:\'|$)|(\\S+)") 281 | self.escape_pattern = re.compile("\\\\(.)", re.DOTALL) 282 | @staticmethod 283 | def escape_replacement(m): 284 | text = m.group(1) 285 | if text == "\\": text = "\\" 286 | elif text == "/": text = "\n" 287 | elif text == "n": text = "\n" 288 | elif text == "r": text = "\r" 289 | elif text == "t": text = "\t" 290 | elif text == "v": text = "\v" 291 | elif text == "f": text = "\f" 292 | elif text == "a": text = "\a" 293 | elif text == "b": text = "\b" 294 | return text 295 | def __call__(self, s): 296 | escape_pattern = self.escape_pattern 297 | escape_replacement = self.escape_replacement 298 | result = [] 299 | for match in self.pattern.finditer(s): 300 | [m1, m2, m3] = match.groups() 301 | if m1 is not None: result.append(escape_pattern.sub(escape_replacement, m1)) 302 | if m2 is not None: result.append(m2) 303 | if m3 is not None: result.append(escape_pattern.sub(escape_replacement, m3)) 304 | return result 305 | 306 | class Database(object): 307 | def __init__(self, name, *args, **kwargs): 308 | self.connection = sqlite3.connect(name, *args, **kwargs) 309 | self.cursor = self.connection.cursor() 310 | self.name = name # assign name only AFTER cursor is created 311 | 312 | def isatty(file_or_fd): 313 | result = True 314 | method = getattr(file_or_fd, 'isatty', None) if not isinstance(file_or_fd, int) else None # this check is just an optimization 315 | if method is not None: 316 | try: tty = method() 317 | except io.UnsupportedOperation: tty = None 318 | result = result and tty is not None and tty 319 | method = getattr(file_or_fd, 'fileno', None) if not isinstance(file_or_fd, int) else None # this check is just an optimization 320 | if method is not None: 321 | try: fd = method() 322 | except io.UnsupportedOperation: fd = None 323 | result = result and fd is not None and os.isatty(fd) and (not msvcrt or GetConsoleMode(msvcrt.get_osfhandle(fd), ctypes.byref(DWORD(0))) != 0) 324 | return result 325 | 326 | def can_call_input_for_stdio(stream): 327 | return stream == sys.stdin and sys.version_info[0] >= 3 328 | 329 | class StdIOProxy(object): 330 | # Maybe useful later: codecs.StreamRecoder(bytesIO, codec.decode, codec.encode, codec.streamwriter, codec.streamreader, errors='surrogateescape') 331 | def __init__(self, stdin, stdout, stderr, codec, allow_set_code_page): 332 | self.codec = codec 333 | streams = (stdin, stdout, stderr) 334 | for stream in streams: 335 | assert isinstance(stream, io.IOBase) or sys.version_info[0] < 3 and isinstance(stream, file) or hasattr(stream, 'mode'), "unable to determine stream type" 336 | assert not isinstance(stream, io.RawIOBase), "RAW I/O APIs are different and not supported" 337 | self.streaminfos = tuple(map(lambda stream: 338 | ( 339 | stream, 340 | isinstance(stream, io.BufferedIOBase) or isinstance(stream, io.RawIOBase) or not isinstance(stream, io.TextIOBase) and 'b' in stream.mode, 341 | isinstance(stream, io.TextIOBase) or not (isinstance(stream, io.BufferedIOBase) or isinstance(stream, io.RawIOBase)) and 'b' not in stream.mode, 342 | allow_set_code_page 343 | ), 344 | streams)) 345 | @property 346 | def stdin(self): return self.streaminfos[0][0] 347 | @property 348 | def stdout(self): return self.streaminfos[1][0] 349 | @property 350 | def stderr(self): return self.streaminfos[2][0] 351 | def _coerce(self, streaminfo, codec, arg): 352 | stream = streaminfo[0] 353 | can_binary = streaminfo[1] 354 | can_text = streaminfo[2] 355 | if not isinstance(arg, bytes) and not isinstance(arg, buffer) and not isinstance(arg, unicode): 356 | arg = unicode(arg) 357 | if isinstance(arg, bytes) or isinstance(arg, buffer): 358 | if not can_binary: 359 | arg = codec.decode(arg, 'surrogateescape')[0] 360 | elif isinstance(arg, unicode): 361 | if not can_text: 362 | arg = codec.encode(unicode(arg), 'strict')[0] 363 | return arg 364 | @staticmethod 365 | def _do_readline(stream, allow_set_code_page, *args): 366 | new_code_page = CP_UTF8 367 | old_code_page = GetConsoleCP() if msvcrt and GetConsoleCP and isatty(stream) else None 368 | if old_code_page == new_code_page: old_code_page = None # Don't change code page if it's already correct... 369 | if old_code_page is not None: 370 | if not SetConsoleCP(new_code_page): 371 | old_code_page = None 372 | try: 373 | result = stream.readline(*args) 374 | finally: 375 | if old_code_page is not None: 376 | SetConsoleCP(old_code_page) 377 | return result 378 | @staticmethod 379 | def _do_write(stream, allow_set_code_page, *args): 380 | new_code_page = CP_UTF8 381 | old_code_page = GetConsoleOutputCP() if msvcrt and GetConsoleOutputCP and isatty(stream) else None 382 | if old_code_page == new_code_page: old_code_page = None # Don't change code page if it's already correct... 383 | if old_code_page is not None: 384 | if not SetConsoleOutputCP(new_code_page): 385 | old_code_page = None 386 | try: 387 | result = stream.write(*args) 388 | finally: 389 | if old_code_page is not None: 390 | SetConsoleCP(old_code_page) 391 | return result 392 | def _readln(self, streaminfo, codec, prompt): 393 | stream = streaminfo[0] 394 | can_binary = streaminfo[1] 395 | allow_set_code_page = streaminfo[3] 396 | if can_call_input_for_stdio(stream) and not can_binary: # input() can't work with binary data 397 | result = self._coerce(streaminfo, codec, "") 398 | try: 399 | result = input(*((self._coerce(streaminfo, codec, prompt),) if prompt is not None else ())) 400 | result += self._coerce(streaminfo, codec, "\n") 401 | except EOFError: pass 402 | else: 403 | self.output(*((prompt,) if prompt is not None else ())) 404 | self.error() 405 | result = StdIOProxy._do_readline(stream, allow_set_code_page) 406 | return result 407 | def _writeln(self, streaminfo, codec, *args, **kwargs): 408 | stream = streaminfo[0] 409 | allow_set_code_page = streaminfo[3] 410 | flush = kwargs.pop('flush', True) 411 | kwargs.setdefault('end', '\n') 412 | kwargs.setdefault('sep', ' ') 413 | end = kwargs.get('end') 414 | sep = kwargs.get('sep') 415 | first = True 416 | for arg in args: 417 | if first: first = False 418 | elif sep is not None: 419 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, sep)) 420 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, arg)) 421 | if end is not None: 422 | StdIOProxy._do_write(stream, allow_set_code_page, self._coerce(streaminfo, codec, end)) 423 | if flush: stream.flush() 424 | def inputln(self, prompt=None): return self._readln(self.streaminfos[0], self.codec, prompt) 425 | def output(self, *args, **kwargs): kwargs.setdefault('end', None); return self._writeln(self.streaminfos[1], self.codec, *args, **kwargs) 426 | def outputln(self, *args, **kwargs): return self._writeln(self.streaminfos[1], self.codec, *args, **kwargs) 427 | def error(self, *args, **kwargs): kwargs.setdefault('end', None); return self._writeln(self.streaminfos[2], self.codec, *args, **kwargs) 428 | def errorln(self, *args, **kwargs): return self._writeln(self.streaminfos[2], self.codec, *args, **kwargs) 429 | 430 | class bytes_comparable_with_unicode(bytes): # For Python 2/3 compatibility, to allow implicit conversion between strings and bytes when it is safe. (Used for strings like literals which we know be safe.) 431 | codec = codecs.lookup('ascii') # MUST be a safe encoding 432 | @classmethod 433 | def coerce(cls, other, for_output=False): 434 | return cls.codec.encode(other)[0] if not isinstance(other, bytes) else bytes_comparable_with_unicode(other) if for_output else other 435 | @classmethod 436 | def translate_if_bytes(cls, value): 437 | if value is not None and isinstance(value, bytes): value = cls(value) 438 | return value 439 | def __hash__(self): return super(bytes_comparable_with_unicode, self).__hash__() # To avoid warning 440 | def __eq__(self, other): return super(bytes_comparable_with_unicode, self).__eq__(self.coerce(other)) 441 | def __ne__(self, other): return super(bytes_comparable_with_unicode, self).__ne__(self.coerce(other)) 442 | def __lt__(self, other): return super(bytes_comparable_with_unicode, self).__lt__(self.coerce(other)) 443 | def __gt__(self, other): return super(bytes_comparable_with_unicode, self).__gt__(self.coerce(other)) 444 | def __le__(self, other): return super(bytes_comparable_with_unicode, self).__le__(self.coerce(other)) 445 | def __ge__(self, other): return super(bytes_comparable_with_unicode, self).__ge__(self.coerce(other)) 446 | def __getitem__(self, index): return self.coerce(super(bytes_comparable_with_unicode, self).__getitem__(index), True) 447 | def __add__(self, other): return self.coerce(super(bytes_comparable_with_unicode, self).__add__(self.coerce(other)), True) 448 | def __iadd__(self, other): return self.coerce(super(bytes_comparable_with_unicode, self).__iadd__(self.coerce(other)), True) 449 | def __radd__(self, other): return self.coerce(self.coerce(other).__add__(self), True) 450 | def find(self, other, *args): return super(bytes_comparable_with_unicode, self).find(self.coerce(other), *args) 451 | def join(self, others): return self.coerce(super(bytes_comparable_with_unicode, self).join(map(self.coerce, others)), True) 452 | def startswith(self, other): return super(bytes_comparable_with_unicode, self).startswith(self.coerce(other)) 453 | def __str__(self): return self.codec.decode(self)[0] 454 | if str == bytes: 455 | __unicode__ = __str__ 456 | def __str__(self): raise NotImplementedError() 457 | 458 | def wrap_bytes_comparable_with_unicode_readline(readline): 459 | def callback(*args): 460 | line = readline(*args) 461 | line = bytes_comparable_with_unicode.translate_if_bytes(line) 462 | return line 463 | return callback 464 | 465 | def main(program, *args, **kwargs): # **kwargs = dict(stdin=file, stdout=file, stderr=file); useful for callers who import this module 466 | import argparse # slow import (compiles regexes etc.), so don't import it until needed 467 | argparser = argparse.ArgumentParser( 468 | prog=os.path.basename(program), 469 | usage=None, 470 | description=None, 471 | epilog=None, 472 | parents=[], 473 | formatter_class=argparse.RawTextHelpFormatter) 474 | argparser.add_argument('-version', '--version', action='store_true', help="show SQLite version") 475 | argparser.add_argument('-batch', '--batch', action='store_true', help="force batch I/O") 476 | argparser.add_argument('-init', '--init', metavar="FILE", help="read/process named file") 477 | argparser.add_argument('filename', nargs='?', metavar="FILENAME", help="is the name of an SQLite database.\nA new database is created if the file does not previously exist.") 478 | argparser.add_argument('sql', nargs='*', metavar="SQL", help="SQL commnds to execute after opening database") 479 | argparser.add_argument('--readline', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to import readline if available (default: %(default)s)") 480 | argparser.add_argument('--self-test', action='store_true', help="perform a basic self-test") 481 | argparser.add_argument('--cross-test', action='store_true', help="perform a basic test against the official executable") 482 | argparser.add_argument('--unicode-stdio', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to enable Unicode wrapper for standard I/O (default: %(default)s)") 483 | argparser.add_argument('--console', action='store', metavar="(true|false)", default="true", choices=("true", "false"), help="whether to auto-detect and use console window APIs (default: %(default)s)") 484 | argparser.add_argument('--encoding', default=ENCODING, help="the default encoding to use (default: %(default)s)") 485 | (stdin, stdout, stderr) = (kwargs.pop('stdin', sys.stdin), kwargs.pop('stdout', sys.stdout), kwargs.pop('stderr', sys.stderr)) 486 | parsed_args = argparser.parse_args(args) 487 | codec = codecs.lookup(parsed_args.encoding or argparser.get_default('encoding')) 488 | if parsed_args.self_test: self_test(codec) 489 | if parsed_args.cross_test: cross_test("sqlite3", codec) 490 | parse_escaped_strings = StringEscapeParser() 491 | if parsed_args.unicode_stdio == "true": 492 | stdin = wrap_unicode_stdio(stdin, False, codec.name) 493 | stdout = wrap_unicode_stdio(stdout, True, codec.name) 494 | stderr = wrap_unicode_stdio(stderr, True, codec.name) 495 | if parsed_args.console == "true": 496 | stdin = wrap_windows_console_io(stdin, False) 497 | stdout = wrap_windows_console_io(stdout, True) 498 | stderr = wrap_windows_console_io(stderr, True) 499 | allow_set_code_page = sys.version_info[0] < 3 and False # This is only necessary on Python 2 if we use the default I/O functions instead of bypassing to ReadConsole()/WriteConsole() 500 | stdio = StdIOProxy(stdin, stdout, stderr, codec, allow_set_code_page) 501 | db = None 502 | no_args = len(args) == 0 503 | init_sql = parsed_args.sql 504 | is_nonpipe_input = stdin.isatty() # NOT the same thing as TTY! (NUL and /dev/null are the difference) 505 | init_show_prompt = not parsed_args.batch and is_nonpipe_input 506 | if not parsed_args.batch and isatty(stdin) and (parsed_args.readline == "true" or __name__ == '__main__') and parsed_args.readline != "false": 507 | try: 508 | with warnings.catch_warnings(): 509 | warnings.filterwarnings('ignore', category=DeprecationWarning) 510 | import readline 511 | except ImportError: pass 512 | if parsed_args and parsed_args.version: 513 | stdio.outputln(sqlite3.sqlite_version); 514 | else: 515 | filename = parsed_args.filename 516 | if filename is None: filename = ":memory:" 517 | db = Database(filename, isolation_level=None) 518 | def exec_script(db, filename, ignore_io_errors): 519 | try: 520 | with io.open(filename, 'r', encoding=codec.name) as f: # Assume .sql files are text -- any binary data inside them should be X'' encoded, not embedded directly 521 | for command in sql_commands(wrap_bytes_comparable_with_unicode_readline(lambda *args: (lambda s: (s) or None)(f.readline()))): 522 | result = exec_command(db, command, False and ignore_io_errors) 523 | if result is not None: 524 | return result 525 | except IOError as ex: 526 | stdio.errorln(ex) 527 | if not ignore_io_errors: return ex.errno 528 | def raise_invalid_command_error(command): 529 | if isinstance(command, bytes): command = codec.decode(command)[0] 530 | if command.startswith("."): command = command[1:] 531 | raise RuntimeError("Error: unknown command or invalid arguments: \"%s\". Enter \".help\" for help" % (command.rstrip().replace("\\", "\\\\").replace("\"", "\\\""),)) 532 | def exec_command(db, command, ignore_io_errors): 533 | results = None 534 | query = None 535 | query_parameters = {} 536 | try: 537 | if command.startswith("."): 538 | args = list(parse_escaped_strings(command)) 539 | if args[0] in (".quit", ".exit"): 540 | return 0 541 | elif args[0] == ".help": 542 | stdio.error(""" 543 | .cd DIRECTORY Change the working directory to DIRECTORY 544 | .dump Dump the database in an SQL text format 545 | .exit Exit this program 546 | .help Show this message 547 | .open FILE Close existing database and reopen FILE 548 | .print STRING... Print literal STRING 549 | .quit Exit this program 550 | .read FILENAME Execute SQL in FILENAME 551 | .schema ?PATTERN? Show the CREATE statements matching PATTERN 552 | .show Show the current values for various settings 553 | .tables ?TABLE? List names of tables 554 | """.lstrip()) 555 | elif args[0] == ".cd": 556 | if len(args) != 2: raise_invalid_command_error(command) 557 | os.chdir(args[1]) 558 | elif args[0] == ".dump": 559 | if len(args) != 1: raise_invalid_command_error(command) 560 | foreign_keys = db.cursor.execute("PRAGMA foreign_keys;").fetchone()[0] 561 | if foreign_keys in (0, "0", "off", "OFF"): 562 | stdio.outputln("PRAGMA foreign_keys=OFF;", flush=False) 563 | for line in db.connection.iterdump(): 564 | stdio.outputln(line, flush=False) 565 | stdio.output() 566 | elif args[0] == ".open": 567 | if len(args) <= 1: raise_invalid_command_error(command) 568 | filename = args[-1] 569 | for option in args[+1:-1]: 570 | raise ValueError("option %s not supported" % (repr(option),)) 571 | try: db.__init__(filename) 572 | except sqlite3.OperationalError as ex: 573 | ex.args = ex.args[:0] + ("Error: unable to open database \"%s\": %s" % (filename, ex.args[0]),) + ex.args[1:] 574 | raise 575 | elif args[0] == ".print": 576 | stdio.outputln(*args[1:]) 577 | elif args[0] == ".read": 578 | if len(args) != 2: raise_invalid_command_error(command) 579 | exec_script(db, args[1], ignore_io_errors) 580 | elif args[0] == ".schema": 581 | if len(args) > 2: raise_invalid_command_error(command) 582 | pattern = args[1] if len(args) > 1 else None 583 | query_parameters['type'] = 'table' 584 | if pattern is not None: 585 | query_parameters['pattern'] = pattern 586 | query = "SELECT sql || ';' FROM sqlite_master WHERE type = :type" + (" AND name LIKE :pattern" if pattern is not None else "") + ";" 587 | elif args[0] == ".show": 588 | if len(args) > 2: raise_invalid_command_error(command) 589 | stdio.errorln(" filename:", db.name) 590 | elif args[0] == ".tables": 591 | if len(args) > 2: raise_invalid_command_error(command) 592 | pattern = args[1] if len(args) > 1 else None 593 | query_parameters['type'] = 'table' 594 | if pattern is not None: 595 | query_parameters['pattern'] = pattern 596 | query = "SELECT name FROM sqlite_master WHERE type = :type" + (" AND name LIKE :pattern" if pattern is not None else "") + ";" 597 | else: 598 | raise_invalid_command_error(args[0]) 599 | else: 600 | query = command 601 | if query is not None: 602 | results = db.cursor.execute(query if isinstance(query, unicode) else codec.decode(query, 'surrogatereplace')[0], query_parameters) 603 | except (RuntimeError, OSError, FileNotFoundError, sqlite3.OperationalError) as ex: 604 | stdio.errorln(exception_encode(ex, codec)) 605 | if results is not None: 606 | for row in results: 607 | stdio.outputln(*tuple(map(lambda item: item if item is not None else "", row)), sep="|", flush=False) 608 | stdio.output() 609 | if db: 610 | if parsed_args and parsed_args.init: 611 | if is_nonpipe_input: stdio.errorln("-- Loading resources from", parsed_args.init) 612 | exec_script(db, parsed_args.init, False) 613 | def read_stdin(index, not_in_the_middle_of_any_input, prev_line): 614 | show_prompt = init_show_prompt 615 | to_write = [] 616 | if index < len(init_sql): 617 | line = init_sql[index] 618 | if not line.startswith(".") and not line.rstrip().endswith(";"): 619 | line += ";" 620 | elif index == len(init_sql) and len(init_sql) > 0: 621 | line = None 622 | else: 623 | if show_prompt: 624 | if not_in_the_middle_of_any_input: 625 | show_prompt = False 626 | if index == 0: 627 | to_write.append("SQLite version %s (adapter version %s)\nEnter \".help\" for usage hints.\n" % (sqlite3.sqlite_version, sqlite3.version)) 628 | if no_args: 629 | to_write.append("Connected to a transient in-memory database.\nUse \".open FILENAME\" to reopen on a persistent database.\n") 630 | if index > 0 and not prev_line: 631 | to_write.append("\n") 632 | to_write.append("%7s " % ("sqlite%s>" % ("",) if not_in_the_middle_of_any_input else "...>",)) 633 | try: 634 | line = stdio.inputln("".join(to_write)) 635 | except KeyboardInterrupt: 636 | line = "" 637 | raise # just kidding, don't handle it for now... 638 | return line 639 | for command in sql_commands(wrap_bytes_comparable_with_unicode_readline(read_stdin)): 640 | result = exec_command(db, command, True) 641 | if result is not None: 642 | return result 643 | if init_show_prompt and len(init_sql) == 0: 644 | stdio.outputln() 645 | 646 | def call_program(cmdline, input_text): 647 | import subprocess 648 | return subprocess.Popen(cmdline, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=False).communicate(input_text) 649 | 650 | def test_query(): 651 | hexcodec = codecs.lookup('hex_codec') 652 | ascii = 'ascii' 653 | data1 = b"\xD8\xA2" 654 | data2 = b"\x01\x02\xFF\x01\xFF\xFE\xFD" 655 | values = [data1, data2] 656 | query_bytes = b'SELECT %s;' % (b", ".join(map(lambda b: b"X'%s'" % (hexcodec.encode(b)[0].upper(),), values)),) 657 | expected_bytes = b"%s\n" % (b"|".join(values),) 658 | return query_bytes, expected_bytes 659 | 660 | def cross_test(sqlite_cmdline, codec): 661 | (query_bytes, expected_bytes) = test_query() 662 | (official_output, official_error) = call_program(sqlite_cmdline, query_bytes) 663 | # We can't use os.linesep here since binaries may belong to different platforms (Win32/MinGW vs. MSYS/Cygwin vs. WSL...) 664 | official_output = official_output.replace(b"\r\n", b"\n") 665 | official_error = official_error.replace(b"\r\n", b"\n") 666 | if official_output != expected_bytes: 667 | raise sqlite3.ProgrammingError("expected bytes are wrong: official %s != expected %s" % (repr(official_output), repr(expected_bytes))) 668 | if official_error: 669 | raise sqlite3.ProgrammingError("did not expect errors from official binary") 670 | 671 | def self_test(codec): 672 | (query_bytes, expected_bytes) = test_query() 673 | if not (lambda stdin, stdout, stderr: not main(sys.argv[0], stdin=stdin, stdout=stdout, stderr=stderr) and stdout.getvalue() == expected_bytes)(io.BytesIO(query_bytes), io.BytesIO(), io.BytesIO()): 674 | raise sqlite3.ProgrammingError("byte I/O is broken") 675 | if not (lambda stdin, stdout, stderr: not main(sys.argv[0], stdin=stdin, stdout=stdout, stderr=stderr) and stdout.getvalue() == codec.decode(expected_bytes, 'surrogateescape'))(io.StringIO(query_bytes.decode(ascii)), io.StringIO(), io.StringIO()): 676 | raise sqlite3.ProgrammingError("string I/O is broken") 677 | 678 | if __name__ == '__main__': 679 | import sys 680 | exit_code = main(*sys.argv) 681 | if exit_code not in (None, 0): raise SystemExit(exit_code) 682 | -------------------------------------------------------------------------------- /lec27/lec27-aggregation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec27/lec27-aggregation.pdf -------------------------------------------------------------------------------- /lec28/lec28-database.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec28/lec28-database.pdf -------------------------------------------------------------------------------- /lec29/lec29-tail-calls.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec29/lec29-tail-calls.pdf -------------------------------------------------------------------------------- /lec30/lec30-macros.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacyCui/sicp-lectures/0d8e574f3886ec8807937b482e97779fb8918b8b/lec30/lec30-macros.pdf --------------------------------------------------------------------------------