├── 02_ts_tutorial.ipynb ├── 03_pure_functions.ipynb ├── 04_first_class_functions.ipynb ├── 05_closures.ipynb ├── 06_sum_types.ipynb ├── 07_recursion.ipynb ├── 08_algebraic_data_types.ipynb ├── 09_problem_solving.ipynb ├── 10_code_org.ipynb ├── 11_react_intro.ipynb ├── 12_regex.ipynb ├── 13_sql ├── 13_sql.ipynb ├── README.md ├── images │ ├── costco.png │ ├── items.png │ ├── lists-with-counts.png │ ├── lists.png │ ├── safeway.png │ └── xkcd_exploits.png ├── pyvenv.cfg └── requirements.txt ├── 14_concurrency.ipynb ├── 15_go.ipynb ├── 16_streams_generators.ipynb ├── 17_haskell.ipynb ├── 17_ihaskell_install.md ├── 18_lexing_parsing.ipynb ├── 19_interp.ipynb ├── 20_transpilers_compilers.ipynb ├── 21_metaprogramming.ipynb ├── LICENSE ├── README.md ├── lib ├── draw.ts ├── introspect.ts ├── lambdats │ ├── README.md │ ├── cloInterp.ts │ ├── expr.ts │ ├── lexer.ts │ ├── main.ts │ ├── package.json │ ├── parser.ts │ ├── substInterp.ts │ ├── token.ts │ ├── transpile.ts │ └── tsconfig.json ├── list.ts └── tree.ts ├── media ├── cat-no-meme.jpg ├── concur_vs_parallel.png ├── context.png ├── expression-problem-function.svg ├── expression-problem-type.svg ├── expression-problem.svg ├── heap-oop.svg ├── heap-procedural.svg ├── heap-shapes.svg ├── heap-square.svg ├── making_burger.png ├── monad_burrito.png └── thread_vs_process.png ├── package.json └── tmp ├── fib_worker.js ├── haskell.txt ├── hello_world.txt ├── notes.txt └── worker.js /12_regex.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "952d8d77", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "data": { 11 | "text/html": [ 12 | "\n", 13 | " \n", 14 | " " 15 | ] 16 | }, 17 | "metadata": {}, 18 | "output_type": "display_data" 19 | }, 20 | { 21 | "data": { 22 | "text/html": [ 23 | "\n", 24 | "\n", 30 | " " 31 | ] 32 | }, 33 | "metadata": {}, 34 | "output_type": "display_data" 35 | } 36 | ], 37 | "source": [ 38 | "import { requireCytoscape, requireCarbon } from \"./lib/draw\";\n", 39 | "\n", 40 | "requireCarbon();\n", 41 | "requireCytoscape();" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "6cc67c69", 47 | "metadata": {}, 48 | "source": [ 49 | "# Domain Specific Languages (DSL) and Regular Expressions" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "id": "ca96d89c", 55 | "metadata": {}, 56 | "source": [ 57 | "## Where Were We?\n", 58 | "\n", 59 | "1. Language primitives (i.e., building blocks of languages)\n", 60 | "2. **Language paradigms** (i.e., combinations of language primitives)\n", 61 | " - Last time: React\n", 62 | " - This time: **domain specific languages** (DSL)\n", 63 | "3. Building a language (i.e., designing your own language)" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "id": "b840148a", 69 | "metadata": {}, 70 | "source": [ 71 | "## Goal\n", 72 | "\n", 73 | "1. Introduce programming with **domain specific languages** (DSLs).\n", 74 | "2. Introduce **regular expressions** as an example of a DSL." 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "c710bc65", 80 | "metadata": {}, 81 | "source": [ 82 | "## Why DSLs?\n", 83 | "\n", 84 | "1. Unlike a general purpose programming language, a DSL is designed to solve a class of problems in a specific domain. Consequently, a DSL is not necessarily Turing complete.\n", 85 | "2. The downside is that there are some programs that you will not be able to write in a DSL.\n", 86 | "3. The upside is that your programs can have special properties that may be useful for your specific setting." 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "id": "5fcc4f24", 92 | "metadata": {}, 93 | "source": [ 94 | "## Examples of DSLs\n", 95 | "\n", 96 | "1. Mathematica: solving mathematical equations\n", 97 | "2. Matlab: scientific computing\n", 98 | "3. Gradle: build system\n", 99 | "4. YACC: parser generator\n", 100 | "5. SQL: relational database query language" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "id": "a492680c", 106 | "metadata": {}, 107 | "source": [ 108 | "### Mathematica\n", 109 | "\n", 110 | "Example code:\n", 111 | "```\n", 112 | "solve x^2 + 4x + 6 = 0\n", 113 | "```\n", 114 | "\n", 115 | "[https://www.wolframalpha.com/](https://www.wolframalpha.com/)" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "id": "3af1d23d", 121 | "metadata": {}, 122 | "source": [ 123 | "### Matlab\n", 124 | "\n", 125 | "\n", 126 | "Example code:\n", 127 | "\n", 128 | "```\n", 129 | "Ts = 1/50;\n", 130 | "t = 0:Ts:10-Ts; \n", 131 | "x = sin(2*pi*15*t) + sin(2*pi*20*t);\n", 132 | "plot(t,x)\n", 133 | "xlabel('Time (seconds)')\n", 134 | "ylabel('Amplitude')\n", 135 | "```\n", 136 | "\n", 137 | "[https://www.mathworks.com/products/matlab.html](https://www.mathworks.com/products/matlab.html)" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "id": "006fad85", 143 | "metadata": {}, 144 | "source": [ 145 | "### Gradle\n", 146 | "\n", 147 | "Example code:\n", 148 | "```\n", 149 | "dependencies { \n", 150 | " api(\"junit:junit:4.13\")\n", 151 | " implementation(\"junit:junit:4.13\")\n", 152 | " testImplementation(\"junit:junit:4.13\")\n", 153 | "}\n", 154 | "```\n", 155 | "\n", 156 | "[https://docs.gradle.org/current/userguide/userguide.html](https://docs.gradle.org/current/userguide/userguide.html)" 157 | ] 158 | }, 159 | { 160 | "cell_type": "markdown", 161 | "id": "4e17a1e7", 162 | "metadata": {}, 163 | "source": [ 164 | "### SQL (Structured Query Language)\n", 165 | "\n", 166 | "Example code:\n", 167 | "\n", 168 | "```\n", 169 | "SELECT column1, column2 FROM table1, table2 WHERE column2='value';\n", 170 | "```\n", 171 | "\n", 172 | "[https://www.w3schools.com/sql/](https://www.w3schools.com/sql/)" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "id": "b8f0e31d", 178 | "metadata": {}, 179 | "source": [ 180 | "### YACC (Yet Another Compiler Compiler)\n", 181 | "\n", 182 | "Example code:\n", 183 | "```\n", 184 | "input :\n", 185 | " | input line\n", 186 | ";\n", 187 | "line : '\\n'\n", 188 | " | exp '\\n' { printf (\"\\t%.10g\\n\", $1); }\n", 189 | ";\n", 190 | "exp : NUM { $$ = $1; }\n", 191 | " | exp exp '+' { $$ = $1 + $2; }\n", 192 | " | exp exp '-' { $$ = $1 - $2; }\n", 193 | " | exp exp '*' { $$ = $1 * $2; }\n", 194 | " | exp exp '/' { $$ = $1 / $2; }\n", 195 | " /* Exponentiation */\n", 196 | " | exp exp '^' { $$ = pow ($1, $2); }\n", 197 | " /* Unary minus */\n", 198 | " | exp 'n' { $$ = -$1; }\n", 199 | "```\n", 200 | "\n", 201 | "[https://www.cs.ccu.edu.tw/~naiwei/cs5605/YaccBison.html](https://www.cs.ccu.edu.tw/~naiwei/cs5605/YaccBison.html)\n", 202 | "\n", 203 | "Question: does YACC code remind you of something that you have seen in class?" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "id": "61365733", 209 | "metadata": {}, 210 | "source": [ 211 | "## Common Problem: Pattern Matching on Strings" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "id": "d86ae0a2", 217 | "metadata": {}, 218 | "source": [ 219 | "### Let's start with simple patterns" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": 2, 225 | "id": "71c0082a", 226 | "metadata": {}, 227 | "outputs": [], 228 | "source": [ 229 | "const files = [\"hw1.ts\", \"hw1.js\", \"hw2.ts\", \"hw2.js\"];" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 3, 235 | "id": "939d2a16", 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "name": "stdout", 240 | "output_type": "stream", 241 | "text": [ 242 | "[ \u001b[32m'hw1.ts'\u001b[39m, \u001b[32m'hw2.ts'\u001b[39m ]\n" 243 | ] 244 | } 245 | ], 246 | "source": [ 247 | "// Get all strings that end with \".ts\"\n", 248 | "files.filter((x) => x.endsWith(\".ts\"));" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": 4, 254 | "id": "193c5468", 255 | "metadata": {}, 256 | "outputs": [ 257 | { 258 | "name": "stdout", 259 | "output_type": "stream", 260 | "text": [ 261 | "[ \u001b[32m'hw1.js'\u001b[39m, \u001b[32m'hw2.js'\u001b[39m ]\n" 262 | ] 263 | } 264 | ], 265 | "source": [ 266 | "// Get all strings that end with \".js\"\n", 267 | "files.filter((x) => x.endsWith(\".js\"));" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": 5, 273 | "id": "796251ff", 274 | "metadata": {}, 275 | "outputs": [ 276 | { 277 | "name": "stdout", 278 | "output_type": "stream", 279 | "text": [ 280 | "[ \u001b[32m'hw1.ts'\u001b[39m, \u001b[32m'hw1.js'\u001b[39m ]\n" 281 | ] 282 | } 283 | ], 284 | "source": [ 285 | "// Get all strings that begin with \"hw1\"\n", 286 | "files.filter((x) => x.startsWith(\"hw1\"));" 287 | ] 288 | }, 289 | { 290 | "cell_type": "code", 291 | "execution_count": 6, 292 | "id": "5c8dfe7f", 293 | "metadata": {}, 294 | "outputs": [ 295 | { 296 | "name": "stdout", 297 | "output_type": "stream", 298 | "text": [ 299 | "[ \u001b[32m'hw1.ts'\u001b[39m ]\n" 300 | ] 301 | } 302 | ], 303 | "source": [ 304 | "// Get all strings that begin with \"hw1\" and endsWith \".ts\"\n", 305 | "files.filter((x) => x.startsWith(\"hw1\") && x.endsWith(\".ts\"));" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "id": "eb665b3d", 311 | "metadata": {}, 312 | "source": [ 313 | "### More complex patterns\n", 314 | "\n", 315 | "Suppose you want to check if phone numbers are valid." 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": 7, 321 | "id": "cfe20d3e", 322 | "metadata": {}, 323 | "outputs": [], 324 | "source": [ 325 | "const phoneNumbers = [\"123-456-7890\", \"(123) 456-7890\", \"1234567890\", \"+1 1234567890\"]; // phone numbers" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": 8, 331 | "id": "9bb7db4c", 332 | "metadata": {}, 333 | "outputs": [ 334 | { 335 | "name": "stdout", 336 | "output_type": "stream", 337 | "text": [ 338 | "1234567890\n" 339 | ] 340 | } 341 | ], 342 | "source": [ 343 | "function replaceAll(s: string, find: string, replace: string): string {\n", 344 | " let prev = s;\n", 345 | " let curr = s.replace(find, replace);\n", 346 | " while (prev !== curr) {\n", 347 | " prev = curr;\n", 348 | " curr = curr.replace(find, replace);\n", 349 | " }\n", 350 | " return curr;\n", 351 | "}\n", 352 | "replaceAll(\"123-456-7890\", \"-\", \"\")" 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": 9, 358 | "id": "7a6aceec", 359 | "metadata": {}, 360 | "outputs": [ 361 | { 362 | "name": "stdout", 363 | "output_type": "stream", 364 | "text": [ 365 | "[ \u001b[32m'1234567890'\u001b[39m, \u001b[32m'1234567890'\u001b[39m, \u001b[32m'1234567890'\u001b[39m, \u001b[32m'11234567890'\u001b[39m ]\n" 366 | ] 367 | } 368 | ], 369 | "source": [ 370 | "const phoneNumbers2 = phoneNumbers.map((x) => replaceAll(x, \"-\", \"\"))\n", 371 | " .map((x) => replaceAll(x, \" \", \"\"))\n", 372 | " .map((x) => replaceAll(x, \"(\", \"\"))\n", 373 | " .map((x) => replaceAll(x, \")\", \"\"))\n", 374 | " .map((x) => replaceAll(x, \"+\", \"\"))\n", 375 | "phoneNumbers2" 376 | ] 377 | }, 378 | { 379 | "cell_type": "code", 380 | "execution_count": 10, 381 | "id": "74e1f53c", 382 | "metadata": {}, 383 | "outputs": [], 384 | "source": [ 385 | "function isNumber(s: string): boolean {\n", 386 | " for (const c of s) {\n", 387 | " if (! (c === \"0\" || c === \"1\" || c === \"2\" || c === \"3\" || c === \"4\" ||\n", 388 | " c === \"5\" || c === \"6\" || c === \"7\" || c === \"8\" || c === \"9\") ) {\n", 389 | " return false;\n", 390 | " }\n", 391 | " }\n", 392 | " return true\n", 393 | "}" 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": 11, 399 | "id": "0dd17c6d", 400 | "metadata": {}, 401 | "outputs": [ 402 | { 403 | "name": "stdout", 404 | "output_type": "stream", 405 | "text": [ 406 | "[ \u001b[32m'1234567890'\u001b[39m, \u001b[32m'1234567890'\u001b[39m, \u001b[32m'1234567890'\u001b[39m, \u001b[32m'11234567890'\u001b[39m ]\n" 407 | ] 408 | } 409 | ], 410 | "source": [ 411 | "phoneNumbers2.filter(isNumber)" 412 | ] 413 | }, 414 | { 415 | "cell_type": "markdown", 416 | "id": "a4bd709f", 417 | "metadata": {}, 418 | "source": [ 419 | "### Problems with Solution\n", 420 | "\n", 421 | "1. Loses information:\n", 422 | " * +1 signifies country code\n", 423 | " * (123) signifies area code\n", 424 | "2. This information may be useful for checking the validity of phone numbers, e.g., not all 10 digit numbers are valid phone numbers. The grouping of the numbers gives geographic information.\n", 425 | "3. Does not enforce that phone numbers have a certain number of digits. For example, is \"12389348762342134\" an area code?" 426 | ] 427 | }, 428 | { 429 | "cell_type": "markdown", 430 | "id": "749f56b1", 431 | "metadata": {}, 432 | "source": [ 433 | "### Other examples of common patterns\n", 434 | "\n", 435 | "1. URLs\n", 436 | " * https://www.google.com, www.google.com\n", 437 | "2. ZIP codes\n", 438 | " * 12345 vs. 12345-678\n", 439 | "3. Valid variable names in a programming language\n", 440 | " * Cannot start variables with a number in TypeScript\n", 441 | "4. Extract emails and links from text" 442 | ] 443 | }, 444 | { 445 | "cell_type": "markdown", 446 | "id": "4f5c9a42", 447 | "metadata": {}, 448 | "source": [ 449 | "## What is the idea of a DSL?\n", 450 | "\n", 451 | "Claim: Using string functions + general-purpose code is a no go for several reasons.\n", 452 | "1. It requires a non-programmer to know how to program in a general-purpose language.\n", 453 | "2. The non-programmer may find a more familar syntax easier to understand.\n", 454 | "3. Therefore, we should design a language that is more familiar and easier to use." 455 | ] 456 | }, 457 | { 458 | "cell_type": "markdown", 459 | "id": "30cae047", 460 | "metadata": {}, 461 | "source": [ 462 | "## Regular Expressions\n", 463 | "\n", 464 | "1. Addresses the string matching problem, thus useful.\n", 465 | "2. Rich connections to formal language theory.\n", 466 | " * Take CSC 520.\n", 467 | " * [https://en.wikipedia.org/wiki/Chomsky_hierarchy](https://en.wikipedia.org/wiki/Chomsky_hierarchy)\n", 468 | " * Example of a DSL designed by computer scientists for computer scientists." 469 | ] 470 | }, 471 | { 472 | "cell_type": "markdown", 473 | "id": "81aa6695", 474 | "metadata": {}, 475 | "source": [ 476 | "### Regular Expressions" 477 | ] 478 | }, 479 | { 480 | "cell_type": "markdown", 481 | "id": "c2547fee", 482 | "metadata": {}, 483 | "source": [ 484 | "### Regular Expressions for File Extensions" 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": 12, 490 | "id": "cdf18f97", 491 | "metadata": {}, 492 | "outputs": [], 493 | "source": [ 494 | "let regexpFileExt: RegExp = /.*\\.ts$/;" 495 | ] 496 | }, 497 | { 498 | "cell_type": "code", 499 | "execution_count": 13, 500 | "id": "68d7bcd7", 501 | "metadata": {}, 502 | "outputs": [ 503 | { 504 | "name": "stdout", 505 | "output_type": "stream", 506 | "text": [ 507 | "[ \u001b[32m'.ts'\u001b[39m, \u001b[32m'hw2.ts'\u001b[39m ]\n" 508 | ] 509 | } 510 | ], 511 | "source": [ 512 | "const files = [\".ts\", \"hw1.js\", \"hw2.ts\", \"hw2.js\"];\n", 513 | "files.filter((x) => regexpFileExt.test(x))" 514 | ] 515 | }, 516 | { 517 | "cell_type": "markdown", 518 | "id": "49e3b163", 519 | "metadata": {}, 520 | "source": [ 521 | "Key\n", 522 | "\n", 523 | "1. `/` and `/` signify the start and end of a regular expression similar to \"\" for strings.\n", 524 | "2. `.` is a **wildcard**, i.e., it matches any character.\n", 525 | "3. `\\.` escapes `.` so that it matches a literal period similar to escaping characters in a string.\n", 526 | "4. `t` and `s` stand for literal characters to match.\n", 527 | "5. `$` means end of string." 528 | ] 529 | }, 530 | { 531 | "cell_type": "markdown", 532 | "id": "8f71216c", 533 | "metadata": {}, 534 | "source": [ 535 | "### Regular Expressions for Phone Numbers" 536 | ] 537 | }, 538 | { 539 | "cell_type": "markdown", 540 | "id": "59d9e916", 541 | "metadata": {}, 542 | "source": [ 543 | "#### Phone numbers 1" 544 | ] 545 | }, 546 | { 547 | "cell_type": "code", 548 | "execution_count": 14, 549 | "id": "d45dfb32", 550 | "metadata": {}, 551 | "outputs": [], 552 | "source": [ 553 | "const phoneNumbers = [\"123-456-7890\", \"(123) 456-7890\", \"1234567890\", \"+1 1234567890\"]; // phone numbers" 554 | ] 555 | }, 556 | { 557 | "cell_type": "code", 558 | "execution_count": 15, 559 | "id": "84d7ae34", 560 | "metadata": {}, 561 | "outputs": [ 562 | { 563 | "name": "stdout", 564 | "output_type": "stream", 565 | "text": [ 566 | "[ \u001b[32m'1234567890'\u001b[39m ]\n" 567 | ] 568 | } 569 | ], 570 | "source": [ 571 | "let regexpPhone: RegExp = /^[0-9]{3}[0-9]{3}[0-9]{4}$/;\n", 572 | "phoneNumbers.filter((x) => regexpPhone.test(x));" 573 | ] 574 | }, 575 | { 576 | "cell_type": "markdown", 577 | "id": "9fe9da81", 578 | "metadata": {}, 579 | "source": [ 580 | "Key\n", 581 | "\n", 582 | "1. `^` means start of string.\n", 583 | "3. `[0-9]` means every character between `0` and `9`.\n", 584 | "4. `{x}` means exactly x matches of the preceeding expression." 585 | ] 586 | }, 587 | { 588 | "cell_type": "markdown", 589 | "id": "83142a8a", 590 | "metadata": {}, 591 | "source": [ 592 | "#### Phone numbers 2" 593 | ] 594 | }, 595 | { 596 | "cell_type": "code", 597 | "execution_count": 16, 598 | "id": "4ed78b45", 599 | "metadata": {}, 600 | "outputs": [ 601 | { 602 | "name": "stdout", 603 | "output_type": "stream", 604 | "text": [ 605 | "[ \u001b[32m'123-456-7890'\u001b[39m, \u001b[32m'1234567890'\u001b[39m ]\n" 606 | ] 607 | } 608 | ], 609 | "source": [ 610 | "let regexpPhone2: RegExp = /^[0-9]{3}\\s*-?\\s*[0-9]{3}\\s*-?\\s*[0-9]{4}$/;\n", 611 | "phoneNumbers.filter((x) => regexpPhone2.test(x));" 612 | ] 613 | }, 614 | { 615 | "cell_type": "markdown", 616 | "id": "b86e8aa8", 617 | "metadata": {}, 618 | "source": [ 619 | "Key\n", 620 | "\n", 621 | "1. `\\s` means any white space character.\n", 622 | "2. `*` means 0 or more occurrences of previous character.\n", 623 | "3. `?` means 0 or 1 occurrences of previous character." 624 | ] 625 | }, 626 | { 627 | "cell_type": "markdown", 628 | "id": "d953d94e", 629 | "metadata": {}, 630 | "source": [ 631 | "#### Phone numbers 3" 632 | ] 633 | }, 634 | { 635 | "cell_type": "code", 636 | "execution_count": 17, 637 | "id": "78a4687f", 638 | "metadata": {}, 639 | "outputs": [], 640 | "source": [ 641 | "let regexpPhone3: RegExp = /^(\\d{3})|(\\(\\d{3}\\))\\s*-?\\s*\\d{3}\\s*-?\\s*\\d{4}$/;" 642 | ] 643 | }, 644 | { 645 | "cell_type": "code", 646 | "execution_count": 18, 647 | "id": "b3bab47e", 648 | "metadata": {}, 649 | "outputs": [ 650 | { 651 | "name": "stdout", 652 | "output_type": "stream", 653 | "text": [ 654 | "[ \u001b[32m'123-456-7890'\u001b[39m, \u001b[32m'(123) 456-7890'\u001b[39m, \u001b[32m'1234567890'\u001b[39m ]\n" 655 | ] 656 | } 657 | ], 658 | "source": [ 659 | "phoneNumbers.filter((x) => regexpPhone3.test(x))" 660 | ] 661 | }, 662 | { 663 | "cell_type": "markdown", 664 | "id": "a2b02e34", 665 | "metadata": {}, 666 | "source": [ 667 | "Key\n", 668 | "\n", 669 | "1. `|` means either the left or the right should match.\n", 670 | "2. `\\d` = `[0-9]`\n", 671 | "3. `\\(` means match the literal `(` because it's part of the regular expression language." 672 | ] 673 | }, 674 | { 675 | "cell_type": "markdown", 676 | "id": "26ce1828", 677 | "metadata": {}, 678 | "source": [ 679 | "#### Phone numbers 4" 680 | ] 681 | }, 682 | { 683 | "cell_type": "code", 684 | "execution_count": 19, 685 | "id": "97d4ea81", 686 | "metadata": {}, 687 | "outputs": [], 688 | "source": [ 689 | "let regexpPhone4: RegExp = /^(\\+\\d+)?\\s*(\\d{3})|(\\(\\d{3}\\))\\s*-?\\s*\\d{3}\\s*-?\\s*\\d{4}$/;" 690 | ] 691 | }, 692 | { 693 | "cell_type": "code", 694 | "execution_count": 20, 695 | "id": "47105add", 696 | "metadata": {}, 697 | "outputs": [ 698 | { 699 | "name": "stdout", 700 | "output_type": "stream", 701 | "text": [ 702 | "[ \u001b[32m'123-456-7890'\u001b[39m, \u001b[32m'(123) 456-7890'\u001b[39m, \u001b[32m'1234567890'\u001b[39m, \u001b[32m'+1 1234567890'\u001b[39m ]\n" 703 | ] 704 | } 705 | ], 706 | "source": [ 707 | "phoneNumbers.filter((x) => regexpPhone4.test(x))" 708 | ] 709 | }, 710 | { 711 | "cell_type": "markdown", 712 | "id": "18e79c67", 713 | "metadata": {}, 714 | "source": [ 715 | "Key\n", 716 | "\n", 717 | "1. `+` means 1 or more occurrences" 718 | ] 719 | }, 720 | { 721 | "cell_type": "markdown", 722 | "id": "e055d0e8", 723 | "metadata": {}, 724 | "source": [ 725 | "### Regular Expressions for Emails" 726 | ] 727 | }, 728 | { 729 | "cell_type": "code", 730 | "execution_count": 21, 731 | "id": "140e0d76", 732 | "metadata": {}, 733 | "outputs": [ 734 | { 735 | "name": "stdout", 736 | "output_type": "stream", 737 | "text": [ 738 | "\u001b[33mtrue\u001b[39m\n", 739 | "\u001b[33mfalse\u001b[39m\n", 740 | "\u001b[33mtrue\u001b[39m\n" 741 | ] 742 | } 743 | ], 744 | "source": [ 745 | "let regexpEmail: RegExp = /^[\\w.-]+@[\\w.-]+$/;\n", 746 | "console.log(regexpEmail.test(\"bob@sfsu.edu\"));\n", 747 | "console.log(regexpEmail.test(\"bobsfsu.edu\"));\n", 748 | "console.log(regexpEmail.test(\"bob@sfsu\"));" 749 | ] 750 | }, 751 | { 752 | "cell_type": "markdown", 753 | "id": "b6952fcc", 754 | "metadata": {}, 755 | "source": [ 756 | "### Regular Expression Summary\n", 757 | "\n", 758 | "1. `/` and `/` signify the start and end of a regular expression similar to \"\" for strings.\n", 759 | "2. `$` means end of string.\n", 760 | "3. `^` means start of string.\n", 761 | "\n", 762 | "4. `t` and `s` stand for literal characters to match.\n", 763 | "5. `.` is a **wildcard**, i.e., it matches any character.\n", 764 | "6. `\\.` escapes `.` so that it matches a literal period similar to escaping characters in a string.\n", 765 | "\n", 766 | "7. `\\s` means any white space character.\n", 767 | "8. `\\d` = `[0-9]`\n", 768 | "9. `[0-9]` means every character between `0` and `9`.\n", 769 | "\n", 770 | "10. `|` means either the left or the right should match.\n", 771 | "11. `{x}` means exactly x matches of the preceeding expression.\n", 772 | "12. `*` means 0 or more occurrences of previous character.\n", 773 | "13. `?` means 0 or 1 occurrences of previous character.\n", 774 | "14. `+` means 1 or more occurrences" 775 | ] 776 | }, 777 | { 778 | "cell_type": "markdown", 779 | "id": "3ba99999", 780 | "metadata": {}, 781 | "source": [ 782 | "### Implementing Regular Expressions" 783 | ] 784 | }, 785 | { 786 | "cell_type": "code", 787 | "execution_count": 22, 788 | "id": "ed99d297", 789 | "metadata": {}, 790 | "outputs": [], 791 | "source": [ 792 | "type RegExp =\n", 793 | " | { tag: \"VOID\" }\n", 794 | " | { tag: \"EMPTY\" } // \"\"\n", 795 | " | { tag: \"CHAR\", char: string } // match specific character, i.e., character case\n", 796 | " | { tag: \"STAR\", re: RegExp } // match any number, i.e., *\n", 797 | " | { tag: \"CONCAT\", re1: RegExp, re2: RegExp } // match re1 followed by re2, i.e., (re1)(re2)\n", 798 | " | { tag: \"OR\", re1: RegExp, re2: RegExp } // match re1 or match re2, i.e., |" 799 | ] 800 | }, 801 | { 802 | "cell_type": "code", 803 | "execution_count": 23, 804 | "id": "416757ae", 805 | "metadata": {}, 806 | "outputs": [], 807 | "source": [ 808 | "function newVoid(): RegExp { return { tag: \"VOID\" } }\n", 809 | "function newEmpty(): RegExp { return { tag: \"EMPTY\" } }\n", 810 | "function newChar(char: string): RegExp { return { tag: \"CHAR\", char: char } }\n", 811 | "function newStar(re: RegExp): RegExp { return { tag: \"STAR\", re: re } }\n", 812 | "function newConcat(re1: RegExp, re2: RegExp): RegExp { return { tag: \"CONCAT\", re1: re1, re2: re2 } }\n", 813 | "function newOr(re1: RegExp, re2: RegExp): RegExp { return { tag: \"OR\", re1: re1, re2: re2 } }" 814 | ] 815 | }, 816 | { 817 | "cell_type": "code", 818 | "execution_count": 24, 819 | "id": "22b487ca", 820 | "metadata": {}, 821 | "outputs": [], 822 | "source": [ 823 | "// \"asdfasdfasdf\"\n", 824 | "// [a, s, d, f, a, s, d, f, a, s, d, f]\n", 825 | "\n", 826 | "function regexpTest(arr: string[], re: RegExp): boolean {\n", 827 | " switch (re.tag) {\n", 828 | " case \"VOID\": {\n", 829 | " return false;\n", 830 | " }\n", 831 | " case \"EMPTY\": {\n", 832 | " return arr.length === 0;\n", 833 | " }\n", 834 | " case \"CHAR\": {\n", 835 | " return arr.length === 1 ? arr[0] === re.char : false;\n", 836 | " }\n", 837 | " case \"STAR\": {\n", 838 | " if (arr.length === 0) {\n", 839 | " return true;\n", 840 | " }\n", 841 | " for (let i = 1; i <= arr.length; i++) {\n", 842 | " let arr2 = arr.slice(0, i);\n", 843 | " let count = 1;\n", 844 | " while (regexpTest(arr2, re.re) && arr2.length === i) {\n", 845 | " arr2 = arr.slice(count*i, (count+1)*i);\n", 846 | " count += 1;\n", 847 | " }\n", 848 | " if (arr2.length === 0) {\n", 849 | " return true;\n", 850 | " }\n", 851 | " }\n", 852 | " return false;\n", 853 | " }\n", 854 | " case \"CONCAT\": { \n", 855 | " for (let i = 0; i <= arr.length; i++) {\n", 856 | " const left = arr.slice(0, i);\n", 857 | " const right = arr.slice(i);\n", 858 | " if (regexpTest(left, re.re1) && regexpTest(right, re.re2)) {\n", 859 | " return true;\n", 860 | " }\n", 861 | " }\n", 862 | " return false;\n", 863 | " }\n", 864 | " case \"OR\": {\n", 865 | " return regexpTest(arr, re.re1) || regexpTest(arr, re.re2);\n", 866 | " }\n", 867 | " }\n", 868 | "}" 869 | ] 870 | }, 871 | { 872 | "cell_type": "code", 873 | "execution_count": 25, 874 | "id": "b57c29f6", 875 | "metadata": {}, 876 | "outputs": [ 877 | { 878 | "name": "stdout", 879 | "output_type": "stream", 880 | "text": [ 881 | "\u001b[33mtrue\u001b[39m\n", 882 | "\u001b[33mfalse\u001b[39m\n", 883 | "\u001b[33mfalse\u001b[39m\n" 884 | ] 885 | } 886 | ], 887 | "source": [ 888 | "const re1 = newConcat(newChar('a'), newConcat(newChar('b'), newChar('c')));\n", 889 | "console.log(regexpTest(['a', 'b', 'c'], re1))\n", 890 | "console.log(regexpTest(['a', 'b'], re1))\n", 891 | "console.log(regexpTest(['a', 'a', 'b', 'c'], re1))" 892 | ] 893 | }, 894 | { 895 | "cell_type": "code", 896 | "execution_count": 26, 897 | "id": "b1f5df18", 898 | "metadata": {}, 899 | "outputs": [ 900 | { 901 | "name": "stdout", 902 | "output_type": "stream", 903 | "text": [ 904 | "\u001b[33mtrue\u001b[39m\n", 905 | "\u001b[33mtrue\u001b[39m\n", 906 | "\u001b[33mfalse\u001b[39m\n" 907 | ] 908 | } 909 | ], 910 | "source": [ 911 | "const re2_ = newConcat(newChar('a'), newChar('b'));\n", 912 | "const re2 = newOr(re1, re2_);\n", 913 | "console.log(regexpTest(['a', 'b', 'c'], re2))\n", 914 | "console.log(regexpTest(['a', 'b'], re2))\n", 915 | "console.log(regexpTest(['a', 'a', 'b', 'c'], re2))" 916 | ] 917 | }, 918 | { 919 | "cell_type": "code", 920 | "execution_count": 27, 921 | "id": "82c07435", 922 | "metadata": {}, 923 | "outputs": [ 924 | { 925 | "name": "stdout", 926 | "output_type": "stream", 927 | "text": [ 928 | "\u001b[33mtrue\u001b[39m\n", 929 | "\u001b[33mtrue\u001b[39m\n", 930 | "\u001b[33mtrue\u001b[39m\n", 931 | "\u001b[33mtrue\u001b[39m\n", 932 | "\u001b[33mfalse\u001b[39m\n" 933 | ] 934 | } 935 | ], 936 | "source": [ 937 | "const re3 = newStar(re1);\n", 938 | "console.log(regexpTest([], re3))\n", 939 | "console.log(regexpTest(['a', 'b', 'c'], re3))\n", 940 | "console.log(regexpTest(['a', 'b', 'c', 'a', 'b', 'c'], re3))\n", 941 | "console.log(regexpTest(['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'], re3))\n", 942 | "console.log(regexpTest(['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a'], re3))" 943 | ] 944 | }, 945 | { 946 | "cell_type": "markdown", 947 | "id": "7c39fe72", 948 | "metadata": {}, 949 | "source": [ 950 | "## Summary\n", 951 | "\n", 952 | "1. We introduced the idea of a DSL and saw many examples of DSLs in different domains.\n", 953 | "2. We focused on **regular expressions** as a DSL central to computer science.\n", 954 | "3. Regular expressions can be used for matching patterns on strings." 955 | ] 956 | }, 957 | { 958 | "cell_type": "code", 959 | "execution_count": null, 960 | "id": "5f89a8ac", 961 | "metadata": {}, 962 | "outputs": [], 963 | "source": [] 964 | } 965 | ], 966 | "metadata": { 967 | "kernelspec": { 968 | "display_name": "TypeScript", 969 | "language": "typescript", 970 | "name": "typescript" 971 | }, 972 | "language_info": { 973 | "codemirror_mode": { 974 | "mode": "typescript", 975 | "name": "javascript", 976 | "typescript": true 977 | }, 978 | "file_extension": ".ts", 979 | "mimetype": "text/typescript", 980 | "name": "typescript", 981 | "version": "3.7.2" 982 | } 983 | }, 984 | "nbformat": 4, 985 | "nbformat_minor": 5 986 | } 987 | -------------------------------------------------------------------------------- /13_sql/README.md: -------------------------------------------------------------------------------- 1 | 2 | This directory has the SQL lesson. It lets you run SQL commands in Jupyter Lab. 3 | These commands hit an in-memory SQLite database that's constructed by the code 4 | in the notebook. If you wreck the database, run all the notebook code again 5 | from the top. 6 | 7 | To set up this directory after cloning the repo: 8 | 9 | % python3 -m venv venv 10 | % source venv/bin/activate 11 | (sql) % rehash 12 | (sql) % pip install -r requirements.txt 13 | (sql) % rehash 14 | 15 | To run the notebook: 16 | 17 | % source venv/bin/activate 18 | (sql) % rehash 19 | (sql) % jupyter lab 20 | 21 | To create this directory from scratch (from the `csc600` directory): 22 | 23 | % mkdir sql 24 | % cd sql 25 | % python3 -m venv venv 26 | % source venv/bin/activate 27 | (sql) % rehash 28 | (sql) % pip install jupyterlab ipython-sql 29 | (sql) % pip freeze > requirements.txt 30 | 31 | -------------------------------------------------------------------------------- /13_sql/images/costco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/13_sql/images/costco.png -------------------------------------------------------------------------------- /13_sql/images/items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/13_sql/images/items.png -------------------------------------------------------------------------------- /13_sql/images/lists-with-counts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/13_sql/images/lists-with-counts.png -------------------------------------------------------------------------------- /13_sql/images/lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/13_sql/images/lists.png -------------------------------------------------------------------------------- /13_sql/images/safeway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/13_sql/images/safeway.png -------------------------------------------------------------------------------- /13_sql/images/xkcd_exploits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/13_sql/images/xkcd_exploits.png -------------------------------------------------------------------------------- /13_sql/pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = /usr/local/opt/python@3.9/bin 2 | include-system-site-packages = false 3 | version = 3.9.4 4 | -------------------------------------------------------------------------------- /13_sql/requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==3.3.4 2 | appnope==0.1.2 3 | argon2-cffi==21.1.0 4 | attrs==21.2.0 5 | Babel==2.9.1 6 | backcall==0.2.0 7 | bleach==4.1.0 8 | certifi==2021.10.8 9 | cffi==1.15.0 10 | charset-normalizer==2.0.7 11 | debugpy==1.5.1 12 | decorator==5.1.0 13 | defusedxml==0.7.1 14 | entrypoints==0.3 15 | greenlet==1.1.2 16 | idna==3.3 17 | ipykernel==6.4.2 18 | ipython==7.28.0 19 | ipython-genutils==0.2.0 20 | ipython-sql==0.4.0 21 | jedi==0.18.0 22 | Jinja2==3.0.2 23 | json5==0.9.6 24 | jsonschema==4.1.2 25 | jupyter-client==7.0.6 26 | jupyter-core==4.8.1 27 | jupyter-server==1.11.1 28 | jupyterlab==3.2.1 29 | jupyterlab-pygments==0.1.2 30 | jupyterlab-server==2.8.2 31 | MarkupSafe==2.0.1 32 | matplotlib-inline==0.1.3 33 | mistune==0.8.4 34 | nbclassic==0.3.3 35 | nbclient==0.5.4 36 | nbconvert==6.2.0 37 | nbformat==5.1.3 38 | nest-asyncio==1.5.1 39 | notebook==6.4.5 40 | packaging==21.0 41 | pandocfilters==1.5.0 42 | parso==0.8.2 43 | pexpect==4.8.0 44 | pickleshare==0.7.5 45 | prettytable==0.7.2 46 | prometheus-client==0.11.0 47 | prompt-toolkit==3.0.21 48 | ptyprocess==0.7.0 49 | pycparser==2.20 50 | Pygments==2.10.0 51 | pyparsing==3.0.0 52 | pyrsistent==0.18.0 53 | python-dateutil==2.8.2 54 | pytz==2021.3 55 | pyzmq==22.3.0 56 | requests==2.26.0 57 | requests-unixsocket==0.2.0 58 | Send2Trash==1.8.0 59 | six==1.16.0 60 | sniffio==1.2.0 61 | SQLAlchemy==1.4.26 62 | sqlparse==0.4.2 63 | terminado==0.12.1 64 | testpath==0.5.0 65 | tornado==6.1 66 | traitlets==5.1.0 67 | urllib3==1.26.7 68 | wcwidth==0.2.5 69 | webencodings==0.5.1 70 | websocket-client==1.2.1 71 | -------------------------------------------------------------------------------- /17_ihaskell_install.md: -------------------------------------------------------------------------------- 1 | Install instructions 2 | 1. Only works with x86_64 architecture (used homebrew in x86_64 mode) 3 | 2. Works with ghc8.7.10 4 | 3. Change `stack.yaml` to comment out `cairo` dependencies 5 | ``` 6 | packages: 7 | - . 8 | - ./ipython-kernel 9 | - ./ghc-parser 10 | - ./ihaskell-display/ihaskell-aeson 11 | - ./ihaskell-display/ihaskell-blaze 12 | # - ./ihaskell-display/ihaskell-charts 13 | # - ./ihaskell-display/ihaskell-diagrams 14 | - ./ihaskell-display/ihaskell-gnuplot 15 | - ./ihaskell-display/ihaskell-graphviz 16 | - ./ihaskell-display/ihaskell-hatex 17 | - ./ihaskell-display/ihaskell-juicypixels 18 | - ./ihaskell-display/ihaskell-magic 19 | # - ./ihaskell-display/ihaskell-plot 20 | # - ./ihaskell-display/ihaskell-static-canvas 21 | - ./ihaskell-display/ihaskell-widgets 22 | ``` 23 | 4. Use IHaskell stack instructions 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Daniel E Huang and Others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Programming and Programming Languages 2 | 3 | This repository contains lecture notes ([TypeScript](https://www.typescriptlang.org/) Jupyter notebooks) for a 1 semester upper-level course on programming and programming languages. The basis for the lecture notes is a course taught at San Francisco State University (SFSU) in Fall 2021 and Spring 2022. 4 | 5 | 6 | ## Why this Class? 7 | 8 | The hope of this class is to expose you to different ways of programming and how different programming languages support these ways of programming. Such a class is often taught using a non-main-stream langauge (e.g.,Scheme). Instead, we're using TypeScript, a superset of JavaScript. The advantage is that you'll come away learning a popular language that is widely used in web programming. 9 | 10 | 11 | ### Contents 12 | 13 | 1. Language primitives (i.e., building blocks of languages): lecture 02 - lecture 10. 14 | - pure functions and first-class functions 15 | - recursion 16 | - algebraic data-types 17 | - functional vs. object-oriented programming 18 | 2. Language paradigms (i.e., combinations of language primitives): lecture 11 - lecture 17. 19 | - domain-specific languages and declarative programming 20 | - concurrency 21 | - laziness 22 | 3. Building a language (i.e., designing your own language): lecture 18 - lecture 21. 23 | - interpreters and transpilers 24 | - meta-programming 25 | 26 | 27 | #### Special Lectures 28 | 29 | 1. Lecture 13 explores [Sql](https://www.w3schools.com/sql/) 30 | 2. Lecture 15 explores [Go](https://go.dev/) 31 | 3. Lecture 17 explores [Haskell](https://www.haskell.org/) 32 | 33 | 34 | ### Why Jupyter + TypeScript? 35 | 36 | Why not slides? Simply put, slides are a polished product that are not interactive and often do not show the thought proces and trial/error that goes into coming up with a piece of code that works. Jupyter + TypeScript enables you to explore concepts with actual code that you can modify. 37 | 38 | 39 | ## Requirements 40 | 41 | 1. [Jupyter Notebook](https://jupyter.org/) 42 | 2. [Node/JS](https://nodejs.org/en/) 43 | 44 | 45 | ## Usage 46 | 47 | 1. `npm install` (first time) 48 | 2. `jupyter notebook` 49 | 50 | 51 | ## Acknowledgements 52 | 53 | 1. Lawrence Kesteloot, Becker Polverini, and Aaron Bembenek. 54 | 2. The students at SFSU. 55 | 56 | -------------------------------------------------------------------------------- /lib/draw.ts: -------------------------------------------------------------------------------- 1 | import * as tslab from 'tslab'; 2 | import * as util from 'util'; 3 | import { v4 as uuidv4 } from 'uuid'; 4 | 5 | import * as list from './list'; 6 | import * as tree from './tree'; 7 | import * as introspect from './introspect'; 8 | 9 | 10 | 11 | /* ************************************************************************** */ 12 | /* Requiring CSS */ 13 | /* ************************************************************************** */ 14 | 15 | export function requireCarbon() { 16 | tslab.display.html(` 17 | 18 | `); 19 | } 20 | 21 | export function requireCytoscape() { 22 | tslab.display.html(` 23 | 29 | `); 30 | } 31 | 32 | 33 | /* ************************************************************************** */ 34 | /* Draw Data-Structure Functions */ 35 | /* ************************************************************************** */ 36 | 37 | export const nodeStyle = ` 38 | { 39 | selector: 'node', 40 | css: { 41 | 'class': ".bx--tree", 42 | 'label': 'data(label)', 43 | 'height': '10px', 44 | 'width': '10px', 45 | } 46 | }, 47 | { 48 | selector: 'edge', 49 | css: { 50 | 'width': 3, 51 | // 'line-color': '#ccc123', 52 | 'curve-style': 'bezier', 53 | 'target-arrow-shape': 'triangle', 54 | 'target-arrow-fill': 'filled', 55 | 'arrow-scale': 1, 56 | } 57 | } 58 | `; 59 | 60 | export function draw(elems, width=800, height=350, layout=listLayout) { 61 | const divId = "mydiv" + uuidv4(); 62 | tslab.display.html(` 63 | 71 |
72 | `); 73 | tslab.display.html(` 74 | 86 | `); 87 | } 88 | 89 | 90 | /* -------------------------------------------------------- */ 91 | /* Draw List */ 92 | /* -------------------------------------------------------- */ 93 | 94 | export const listLayout = `{}`; 95 | 96 | export function drawList(ls: list.List, width=800, height=350) { 97 | return draw(list.cytoscapify(ls), width, height, listLayout); 98 | } 99 | 100 | 101 | /* -------------------------------------------------------- */ 102 | /* Draw Tree */ 103 | /* -------------------------------------------------------- */ 104 | 105 | export const treeLayout = ` 106 | { 107 | name: 'preset' 108 | } 109 | `; 110 | 111 | export function drawTree(t: tree.Tree, width=800, height=350) { 112 | return draw(tree.cytoscapify(t), width, height, treeLayout); 113 | } 114 | 115 | 116 | /* -------------------------------------------------------- */ 117 | /* Draw Memory */ 118 | /* -------------------------------------------------------- */ 119 | 120 | export const memLayout = ` 121 | { 122 | name: 'breadthfirst', 123 | directed: true, 124 | padding: 10 125 | } 126 | `; 127 | 128 | export function drawMemTrace(memTrace: introspect.MemoryTrace, i, width=800, height=350) { 129 | return draw(introspect.cytoscapifyMemTrace(memTrace.memory[i], memTrace.refId), width, height, memLayout); 130 | } 131 | 132 | 133 | /* -------------------------------------------------------- */ 134 | /* Draw Call Stack */ 135 | /* -------------------------------------------------------- */ 136 | 137 | export const callStackLayout = ` 138 | { 139 | name: 'breadthfirst', 140 | directed: true, 141 | padding: 10 142 | } 143 | `; 144 | 145 | export function drawCallStack(stk: [string, number, any][], width=800, height=350) { 146 | draw(introspect.cytoscapifyCallStack(stk), width, height, callStackLayout); 147 | } 148 | 149 | export function drawStackTrace(stk: [string, number, any][], drawFn, width=800, height=550) { 150 | let iter = 0; 151 | for (const x of stk) { 152 | if (x[0] == "CALL") { 153 | console.log(`CALL[${iter}]` ); 154 | drawFn(x[2][0][1]) 155 | } else { 156 | console.log(`RET[${iter}]: ${x[2]}`) 157 | } 158 | iter += 1; 159 | } 160 | } 161 | 162 | 163 | /* ************************************************************************** */ 164 | /* Display line plot */ 165 | /* ************************************************************************** */ 166 | 167 | export function linePlot(xs, ys, chartTitles=[], width=800, height=350): void { 168 | const divId = "mydiv" + uuidv4(); 169 | 170 | const colors = [ 171 | "rgb(44, 62, 80)", 172 | "rgb(231, 76, 60)", 173 | "rgb(236, 240, 241)", 174 | "rgb(52, 152, 219)" 175 | ]; 176 | 177 | let datasets = ""; 178 | if (Array.isArray(ys)) { 179 | if (Array.isArray(ys[0])) { 180 | datasets = ys.map((x, i) => { 181 | let title = `${i}` 182 | if (i in chartTitles) { 183 | title = chartTitles[i]; 184 | } 185 | return `{ 186 | label: "${title}", 187 | backgroundColor: '${colors[i % 4]}', 188 | borderColor: '${colors[i % 4]}', 189 | data: ${util.inspect(x)}, 190 | }`}).join(", "); 191 | } else { 192 | datasets = `{ 193 | label: "${"data"}", 194 | backgroundColor: '${colors[0]}', 195 | borderColor: '${colors[0]}', 196 | data: ${util.inspect(ys)}, 197 | }` 198 | } 199 | } 200 | 201 | tslab.display.html(` 202 | 210 | 211 |
212 | 213 |
214 | 215 | 242 | `); 243 | } 244 | 245 | 246 | /* ************************************************************************** */ 247 | /* Display React */ 248 | /* ************************************************************************** */ 249 | 250 | export function displayReact(components): void { 251 | const divId = "mydiv" + uuidv4(); 252 | tslab.display.html(` 253 |
254 | 275 | `) 276 | } -------------------------------------------------------------------------------- /lib/introspect.ts: -------------------------------------------------------------------------------- 1 | import ts, { idText, isExpressionStatement, SyntaxKind } from "typescript"; 2 | import * as util from "util"; 3 | import { Map } from 'immutable'; 4 | 5 | 6 | /* ************************************************************************** */ 7 | /* Call-Stack Tracing 2 */ 8 | /* ************************************************************************** */ 9 | 10 | export interface CallStackTrace { 11 | func: Function, 12 | stack: [string, number, any][] 13 | }; 14 | 15 | const _traceFunction3 = (f: Function, 16 | transformer: (context: ts.TransformationContext) => (rootNode: T) => T, 17 | exports? /* for Jupyter */, 18 | verbose=false) => { 19 | // Function --> string --> ts.SourceFile --> string (instrumented) 20 | const source = f.toString(); 21 | const sourceFile: ts.SourceFile = ts.createSourceFile('foobar.ts', source, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TS); 22 | const result: ts.TransformationResult = ts.transform(sourceFile, [transformer]); 23 | const transformedSourceFile: ts.SourceFile = result.transformed[0]; 24 | const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 25 | const transformedString = printer.printFile(transformedSourceFile); 26 | if (verbose) { 27 | console.log(transformedString); 28 | } 29 | 30 | const instrumentTemplate = ` 31 | return (exports) => { 32 | let stack = []; 33 | let count = 0; 34 | let lvl = 0 35 | 36 | const csc600_RecordEnter = (params: [string, any][]): void => { 37 | stack.push(["CALL", lvl, params]); 38 | //for (let param of params) { 39 | // const [name, val] = param; 40 | // stack.push(["CALL", lvl, param]); 41 | //} 42 | lvl += 2; 43 | }; 44 | 45 | const csc600_RecordExit = (x) => { 46 | lvl -= 2; 47 | stack.push(["RET", lvl, x]); 48 | return x; 49 | }; 50 | 51 | return {func: ${transformedString}), stack: stack}; 52 | } 53 | ` 54 | return Function(`${ts.transpile(instrumentTemplate)}`)()(exports); 55 | }; 56 | 57 | 58 | export function traceCallStack(f: Function, exports? /* for Jupyter */, verbose=false): CallStackTrace { 59 | function traceParam(param: ts.ParameterDeclaration): ts.ArrayLiteralExpression { 60 | const paramStr = ts.factory.createStringLiteral(param.name.getText()); 61 | const paramName = ts.factory.createIdentifier(param.name.getText()); 62 | return ts.factory.createArrayLiteralExpression([paramStr, paramName]); 63 | } 64 | 65 | // The transformation 66 | const transformer = (context: ts.TransformationContext) => (rootNode: T) => { 67 | function visit(node: ts.Node): ts.Node { 68 | switch (node.kind) { 69 | case ts.SyntaxKind.ArrowFunction: { 70 | const arr = node as ts.ArrowFunction; 71 | const body2 = ts.visitEachChild(arr.body, visit, context); 72 | const params = ts.factory.createArrayLiteralExpression(arr.parameters.map(traceParam)); 73 | const functionName = ts.factory.createIdentifier("csc600_RecordEnter"); 74 | const instrumented = ts.factory.createCallExpression(functionName, undefined, [params]) 75 | const tuple = ts.factory.createArrayLiteralExpression([body2 as ts.Expression].concat(params).concat(instrumented)); 76 | const body3 = ts.factory.createElementAccessExpression(tuple, 0); 77 | return ts.factory.createArrowFunction(arr.modifiers, arr.typeParameters, arr.parameters, arr.type, arr.equalsGreaterThanToken, body3); 78 | } 79 | case ts.SyntaxKind.FunctionDeclaration: { 80 | const func = node as ts.FunctionDeclaration; 81 | const body2 = ts.visitEachChild(func.body, visit, context); 82 | const params = ts.factory.createArrayLiteralExpression(func.parameters.map(traceParam)); 83 | const functionName = ts.factory.createIdentifier("csc600_RecordEnter"); 84 | const prelude: ts.Statement[] = [ts.factory.createExpressionStatement(ts.factory.createCallExpression(functionName, undefined, [params]))]; 85 | const body3 = ts.factory.createBlock(prelude.concat(body2.statements), true); 86 | // const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 87 | return ts.factory.createFunctionDeclaration(func.decorators, func.modifiers, func.asteriskToken, func.name, func.typeParameters, func.parameters, func.type, body3); 88 | } 89 | case ts.SyntaxKind.ReturnStatement: { 90 | const ret = node as ts.ReturnStatement; 91 | if (ret.expression) { 92 | const functionName = ts.factory.createIdentifier("csc600_RecordExit"); 93 | const instrumented = ts.factory.createCallExpression(functionName, undefined, [ts.visitEachChild(ret.expression, visit, context)]); 94 | return ts.factory.createReturnStatement(instrumented); 95 | } else { 96 | return ts.visitEachChild(node, visit, context); 97 | } 98 | } 99 | default: { 100 | return ts.visitEachChild(node, visit, context); 101 | } 102 | } 103 | }; 104 | return ts.visitNode(rootNode, visit); 105 | }; 106 | 107 | return _traceFunction3(f, transformer, exports, verbose); 108 | } 109 | 110 | export function cytoscapifyCallStack(stk: [string, number, any][]) { 111 | let count = 0; 112 | function fresh(prefix: string): string { 113 | count += 1; 114 | return prefix + count; 115 | } 116 | 117 | let acc = []; 118 | for (let i = 0; i < stk.length; i++) { 119 | const x = stk[i]; 120 | acc.push({ data: {id: i, label: `${x[0]}[${i}](${JSON.stringify(x[2])})`}}); 121 | const lvl = x[1]; 122 | if (x[0] == "CALL") { 123 | for (let j = i - 1; j >= 0; j--) { 124 | if (stk[j][1] == lvl - 2) { 125 | acc.push({ data: {id: fresh("edge"), source: j, target: i}}); 126 | break; 127 | } 128 | } 129 | } else if (x[0] == "RET") { 130 | if (stk[i-1][0] == "CALL" && stk[i-1][1] == lvl) { 131 | acc.push({ data: {id: fresh("edge"), source: i-1, target: i}}); 132 | } else { 133 | for (let j = i - 1; j >= 0; j--) { 134 | if (stk[j][0] == "CALL" && stk[j][1] == lvl) { 135 | break; 136 | } else if (stk[j][0] == "RET" && stk[j][1] - 2 == lvl) { 137 | acc.push({ data: {id: fresh("edge"), source: j, target: i}}); 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | return util.inspect(acc); 145 | } 146 | 147 | 148 | /* ************************************************************************** */ 149 | /* Call-Stack Tracing */ 150 | /* ************************************************************************** */ 151 | 152 | export interface CallStackTrace { 153 | func: Function, 154 | trace: { [id: string]: any }, 155 | refId: WeakMap 156 | }; 157 | 158 | const _traceFunction = (f: Function, 159 | transformer: (context: ts.TransformationContext) => (rootNode: T) => T, 160 | exports? /* for Jupyter */, 161 | verbose=false) => { 162 | // Function --> string --> ts.SourceFile --> string (instrumented) 163 | const source = f.toString(); 164 | const sourceFile: ts.SourceFile = ts.createSourceFile('foobar.ts', source, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TS); 165 | const result: ts.TransformationResult = ts.transform(sourceFile, [transformer]); 166 | const transformedSourceFile: ts.SourceFile = result.transformed[0]; 167 | const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 168 | const transformedString = printer.printFile(transformedSourceFile); 169 | if (verbose) { 170 | console.log(transformedString); 171 | } 172 | 173 | const instrumentTemplate = ` 174 | return (exports) => { 175 | let trace: { [id: string]: any } = {}; 176 | let count = 0; 177 | let lvl = 0 178 | 179 | const csc600_RecordEnter = (x) => { 180 | lvl += 2; 181 | }; 182 | 183 | const csc600_RecordParam = (name, x) => { 184 | const id = "t" + count; 185 | console.log(" ".repeat(lvl) + "Argument[" + id + "]: " + name + " ->", x); 186 | trace[id] = [lvl, x]; 187 | count += 1; 188 | return x; 189 | }; 190 | 191 | const csc600_RecordExit = (x) => { 192 | lvl -= 2; 193 | const id = "t" + count; 194 | console.log(" ".repeat(lvl) + "Return[" + id + "]: ", x); 195 | trace[id] = [lvl, x]; 196 | count += 1; 197 | return x; 198 | }; 199 | 200 | const csc600_RecordValue = (x) => { 201 | const id = "t" + count; 202 | console.log("Creating: ", id, x); 203 | trace[id] = ["value", x]; 204 | count += 1; 205 | return x; 206 | }; 207 | 208 | 209 | let refId = new WeakMap; 210 | let objectCount: number = 0; 211 | const objectId = (object) => { 212 | if (!refId.has(object)) { 213 | refId.set(object,++objectCount); 214 | } 215 | return refId.get(object); 216 | }; 217 | 218 | const csc600_RecordReference = (x) => { 219 | const id = "t" + count; 220 | const id2 = objectId(x); 221 | console.log("Creating: ", id, id2, x); 222 | trace[id] = ["reference", id2, x]; 223 | count += 1; 224 | return x; 225 | }; 226 | 227 | const csc600_Record = (x) => { 228 | const id = "t" + count; 229 | console.log("Encountering: ", id, x); 230 | trace[id] = x; 231 | count += 1; 232 | return x; 233 | }; 234 | 235 | return {func: ${transformedString}), trace: trace, refId: refId}; 236 | } 237 | ` 238 | return Function(`${ts.transpile(instrumentTemplate)}`)()(exports); 239 | }; 240 | 241 | export function traceCall(f: Function, exports? /* for Jupyter */, verbose=false): CallStackTrace { 242 | function traceEnter() { 243 | const functionName = ts.factory.createIdentifier("csc600_RecordEnter"); 244 | const instrumented = ts.factory.createCallExpression(functionName, undefined, []); 245 | return instrumented; 246 | } 247 | 248 | function traceArrParam(param: ts.ParameterDeclaration) { 249 | const functionName = ts.factory.createIdentifier("csc600_RecordParam"); 250 | const paramStr = ts.factory.createStringLiteral(param.name.getText()); 251 | const paramName = ts.factory.createIdentifier(param.name.getText()); 252 | const instrumented = ts.factory.createCallExpression(functionName, undefined, [paramStr, paramName]); 253 | return instrumented; 254 | } 255 | 256 | function traceFunParam(param: ts.ParameterDeclaration) { 257 | const functionName = ts.factory.createIdentifier("csc600_RecordParam"); 258 | const paramStr = ts.factory.createStringLiteral(param.name.getText()); 259 | const paramName = ts.factory.createIdentifier(param.name.getText()); 260 | const instrumented = ts.factory.createCallExpression(functionName, undefined, [paramStr, paramName]); 261 | return ts.factory.createExpressionStatement(instrumented); 262 | } 263 | 264 | // The transformation 265 | const transformer = (context: ts.TransformationContext) => (rootNode: T) => { 266 | function visit(node: ts.Node): ts.Node { 267 | switch (node.kind) { 268 | case ts.SyntaxKind.ArrowFunction: { 269 | const arr = node as ts.ArrowFunction; 270 | const body2 = ts.visitEachChild(arr.body, visit, context); 271 | const params: ts.Expression[] = arr.parameters.map(traceArrParam); 272 | const tuple = ts.factory.createArrayLiteralExpression([body2 as ts.Expression].concat(params).concat(traceEnter())); 273 | const body3 = ts.factory.createElementAccessExpression(tuple, 0); 274 | return ts.factory.createArrowFunction(arr.modifiers, arr.typeParameters, arr.parameters, arr.type, arr.equalsGreaterThanToken, body3); 275 | } 276 | case ts.SyntaxKind.FunctionDeclaration: { 277 | const func = node as ts.FunctionDeclaration; 278 | const body2 = ts.visitEachChild(func.body, visit, context); 279 | const prelude: ts.Statement[] = func.parameters.map(traceFunParam).concat([ts.factory.createExpressionStatement(traceEnter())]); 280 | const body3 = ts.factory.createBlock(prelude.concat(body2.statements), true); 281 | const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 282 | return ts.factory.createFunctionDeclaration(func.decorators, func.modifiers, func.asteriskToken, func.name, func.typeParameters, func.parameters, func.type, body3); 283 | } 284 | case ts.SyntaxKind.ReturnStatement: { 285 | const ret = node as ts.ReturnStatement; 286 | if (ret.expression) { 287 | const functionName = ts.factory.createIdentifier("csc600_RecordExit"); 288 | const instrumented = ts.factory.createCallExpression(functionName, undefined, [ts.visitEachChild(ret.expression, visit, context)]); 289 | return ts.factory.createReturnStatement(instrumented); 290 | } else { 291 | return ts.visitEachChild(node, visit, context); 292 | } 293 | } 294 | default: { 295 | return ts.visitEachChild(node, visit, context); 296 | } 297 | } 298 | }; 299 | return ts.visitNode(rootNode, visit); 300 | }; 301 | 302 | return _traceFunction(f, transformer, exports, verbose); 303 | } 304 | 305 | 306 | /* ************************************************************************** */ 307 | /* Memory Tracing */ 308 | /* ************************************************************************** */ 309 | 310 | export interface MemoryTrace { 311 | func: Function, // Instrumented function 312 | memory: Map[], // Keeps timeline of memory 313 | refId: WeakMap // Maps objects to integers 314 | }; 315 | 316 | const _traceMemory = (f: Function, 317 | transformer: (context: ts.TransformationContext) => (rootNode: T) => T, 318 | exports? /* for Jupyter */, 319 | verbose=false) => { 320 | // Function --> string --> ts.SourceFile --> string (instrumented) 321 | const source = f.toString(); 322 | const sourceFile: ts.SourceFile = ts.createSourceFile('foobar.ts', source, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TS); 323 | const result: ts.TransformationResult = ts.transform(sourceFile, [transformer]); 324 | const transformedSourceFile: ts.SourceFile = result.transformed[0]; 325 | const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 326 | const transformedString = printer.printFile(transformedSourceFile); 327 | if (verbose) { 328 | console.log(transformedString); 329 | } 330 | 331 | const instrumentTemplate = ` 332 | return (exports, Map) => { 333 | let trace: Map[] = []; 334 | 335 | let refId = new WeakMap; 336 | let objectCount: number = 0; 337 | const objectId = (object) => { 338 | if (object === null) { 339 | return "null"; 340 | } else { 341 | if (!refId.has(object)) { 342 | refId.set(object,++objectCount); 343 | } 344 | return refId.get(object); 345 | } 346 | }; 347 | 348 | const csc600_RecordValue = (x, v) => { 349 | if (trace.length > 0) { 350 | trace.push(trace[trace.length - 1].set(x, ["val", v])); 351 | } else { 352 | trace.push(Map().set(x, ["val", v])); 353 | } 354 | return v; 355 | } 356 | 357 | const csc600_RecordReference = (x, v) => { 358 | if (trace.length > 0) { 359 | trace.push(trace[trace.length - 1].set(x, ["ref", objectId(v), v])); 360 | } else { 361 | trace.push(Map().set(x, ["ref", objectId(v), v])); 362 | } 363 | return v; 364 | } 365 | 366 | return {func: ${transformedString}), memory: trace, refId: refId}; 367 | } 368 | ` 369 | return Function(`${ts.transpile(instrumentTemplate)}`)()(exports, Map); 370 | }; 371 | 372 | export function traceMemory(f: Function, exports? /* for Jupyter */, verbose=false): MemoryTrace { 373 | function traceValRef(visit: (node: ts.Node) => ts.Node, context: ts.TransformationContext, x: ts.StringLiteral, node: ts.Expression) { 374 | switch (node.kind) { 375 | case ts.SyntaxKind.NullKeyword: { 376 | const functionName = ts.factory.createIdentifier("csc600_RecordReference"); 377 | return ts.factory.createCallExpression(functionName, /*typeArgs*/ undefined, [x, node]); 378 | } 379 | case ts.SyntaxKind.Identifier: { 380 | const ident = node as ts.Identifier; 381 | const functionName = ts.factory.createIdentifier("csc600_RecordValue"); 382 | return ts.factory.createCallExpression(functionName, /*typeArgs*/ undefined, [x, ident]); 383 | } 384 | case ts.SyntaxKind.NumericLiteral: { 385 | const num = node as ts.NumericLiteral; 386 | const functionName = ts.factory.createIdentifier("csc600_RecordValue"); 387 | return ts.factory.createCallExpression(functionName, /*typeArgs*/ undefined, [x, num]); 388 | } 389 | case ts.SyntaxKind.StringLiteral: { 390 | const str = node as ts.StringLiteral; 391 | const functionName = ts.factory.createIdentifier("csc600_RecordValue"); 392 | return ts.factory.createCallExpression(functionName, /*typeArgs*/ undefined, [x, str]); 393 | } 394 | case ts.SyntaxKind.ArrayLiteralExpression: { 395 | const arrLit = node as ts.ArrayLiteralExpression; 396 | const functionName = ts.factory.createIdentifier("csc600_RecordReference"); 397 | const instArrLit = ts.factory.createArrayLiteralExpression(arrLit.elements.map((elem) => ts.visitEachChild(elem, visit, context)), false); 398 | return ts.factory.createCallExpression(functionName, /*typeArgs*/ undefined, [x, instArrLit]); 399 | } 400 | case ts.SyntaxKind.ObjectLiteralExpression: { 401 | const objLit = node as ts.ObjectLiteralExpression; 402 | const f = (objLitElem: ts.ObjectLiteralElementLike) => { 403 | const propAssign = objLitElem as ts.PropertyAssignment; 404 | return ts.factory.createPropertyAssignment(propAssign.name, ts.visitEachChild(propAssign.initializer, visit, context)); 405 | }; 406 | const functionName = ts.factory.createIdentifier("csc600_RecordReference"); 407 | const instObjLit = ts.factory.createObjectLiteralExpression(objLit.properties.map(f), false); 408 | return ts.factory.createCallExpression(functionName, /*typeArgs*/ undefined, [x, instObjLit]); 409 | } 410 | case ts.SyntaxKind.BinaryExpression: { 411 | // TODO(deh): Should only do this really for a subset of binary expressions. 412 | const binExp = node as ts.BinaryExpression; 413 | const functionName = ts.factory.createIdentifier("csc600_RecordValue"); 414 | return ts.factory.createCallExpression(functionName, /*typeArgs*/ undefined, [x, binExp]); 415 | } 416 | default: { 417 | return ts.visitEachChild(node, visit, context); 418 | } 419 | } 420 | } 421 | 422 | // The transformation 423 | const transformer = (context: ts.TransformationContext) => (rootNode: T) => { 424 | const visit = (node: ts.Node): ts.Node => { 425 | switch (node.kind) { 426 | case ts.SyntaxKind.ExpressionStatement: { 427 | const expStmt = node as ts.ExpressionStatement; 428 | switch (expStmt.expression.kind) { 429 | case ts.SyntaxKind.BinaryExpression: { 430 | const binExp = expStmt.expression as ts.BinaryExpression; 431 | const tok = binExp.operatorToken as ts.Token; 432 | switch (tok.kind) { 433 | case ts.SyntaxKind.EqualsToken: { 434 | switch (binExp.left.kind) { 435 | case ts.SyntaxKind.Identifier: { 436 | const x = binExp.left as ts.Identifier; 437 | const id = ts.factory.createStringLiteral(x.getText()); 438 | return ts.factory.createBinaryExpression(ts.visitEachChild(binExp.left, visit, context), 439 | binExp.operatorToken, 440 | traceValRef(visit, context, id, binExp.right)); 441 | } 442 | default: { 443 | return ts.factory.createBinaryExpression(ts.visitEachChild(binExp.left, visit, context), 444 | binExp.operatorToken, 445 | ts.visitEachChild(binExp.right, visit, context)); 446 | } 447 | } 448 | } 449 | default: { 450 | return ts.visitEachChild(expStmt, visit, context); 451 | } 452 | } 453 | } 454 | default: { 455 | return ts.visitEachChild(expStmt, visit, context); 456 | } 457 | } 458 | } 459 | case ts.SyntaxKind.VariableStatement: { 460 | const varStmt = node as ts.VariableStatement; 461 | const varDecls = varStmt.declarationList as ts.VariableDeclarationList; 462 | const f = (varDecl: ts.VariableDeclaration) => { 463 | const id = ts.factory.createStringLiteral(varDecl.name.getText()); 464 | return ts.factory.createVariableDeclaration(varDecl.name, varDecl.exclamationToken, varDecl.type, traceValRef(visit, context, id, varDecl.initializer)); 465 | }; 466 | // TODO(deh): why are the modifiers messed up? 467 | return ts.factory.createVariableStatement(varStmt.modifiers, ts.factory.createVariableDeclarationList(varDecls.declarations.map(f))); 468 | } 469 | default: { 470 | return ts.visitEachChild(node, visit, context); 471 | } 472 | } 473 | }; 474 | return ts.visitNode(rootNode, visit); 475 | }; 476 | 477 | return _traceMemory(f, transformer, exports, verbose); 478 | }; 479 | 480 | export function cytoscapifyMemTrace(valRefTrace: Map, refToLoc: WeakMap): string { 481 | let count = 0; 482 | function fresh(prefix: string): string { 483 | count += 1; 484 | return prefix + count; 485 | } 486 | 487 | function reifyArr(arr: any[]) { 488 | if (arr === null) { 489 | return "null"; 490 | } else { 491 | return `[${arr.map((x) => { 492 | if (x === null) { 493 | return "location(null)"; 494 | } else if (refToLoc.has(x)) { 495 | return "location(" + refToLoc.get(x) + ")"; 496 | } else { 497 | return util.inspect(x); 498 | } 499 | }).join(", ")}]`; 500 | } 501 | } 502 | 503 | const nullId = fresh("null"); 504 | const undefinedId = fresh("undefined"); 505 | 506 | let acc = []; 507 | acc.push({ data: {id: nullId, label: "location(null)"}}); 508 | acc.push({ data: {id: undefinedId, label: "undefined"}}); 509 | for (let [key, value] of valRefTrace) { 510 | const kind = value[0] 511 | if (kind == "val") { 512 | const varId = fresh("var"); 513 | acc.push({ data: {id: varId, label: "variable(" + key + ")"}}); 514 | if (value[1] === undefined) { 515 | const varToVal = fresh("edge"); 516 | acc.push({ data: {id: varToVal, source: varId, target: undefinedId}}); 517 | } else { 518 | const valId = fresh("val"); 519 | acc.push({ data: {id: valId, label: util.inspect(value[1])}}); 520 | const varToVal = fresh("edge"); 521 | acc.push({ data: {id: varToVal, source: varId, target: valId}}); 522 | } 523 | } else if (kind == "ref") { 524 | const varId = fresh("var"); 525 | acc.push({ data: {id: varId, label: "variable(" + key + ")"}}); 526 | if (value[2] === null) { 527 | const varToVal = fresh("edge"); 528 | acc.push({ data: {id: varToVal, source: varId, target: nullId}}); 529 | } else { 530 | const refId = fresh("ref"); 531 | acc.push({ data: {id: refId, label: "location(" + value[1] + ")"}}); 532 | const objId = fresh("obj"); 533 | acc.push({ data: {id: objId, label: reifyArr(value[2])}}); 534 | const edgeId = fresh("edge"); 535 | acc.push({ data: {id: edgeId, source: refId, target: objId}}); 536 | const varToVal = fresh("edge"); 537 | acc.push({ data: {id: varToVal, source: varId, target: refId}}); 538 | } 539 | } 540 | } 541 | 542 | return util.inspect(acc); 543 | } 544 | -------------------------------------------------------------------------------- /lib/lambdats/README.md: -------------------------------------------------------------------------------- 1 | 2 | # LambdaTS 3 | 4 | Mini language with just basic arithmetic and one-parameter functions. 5 | 6 | # Installing 7 | 8 | Run: 9 | 10 | % npm install 11 | 12 | # Running 13 | 14 | Run: 15 | 16 | % npm start 17 | 18 | # Language 19 | 20 | ``` 21 | binop ::= + | - | * | / 22 | e ::= x | e(e) | λx => e | n | e binop e | e ? e : e | (e) | let x = e in e 23 | 24 | Precedence, highest to lowest, and associativity: 25 | 26 | e(e) left to right 27 | e * e | e / e left to right 28 | e + e | e - e left to right 29 | e ? e : e right to left 30 | λx => e right to left 31 | let x = e in e right to left 32 | ``` 33 | -------------------------------------------------------------------------------- /lib/lambdats/cloInterp.ts: -------------------------------------------------------------------------------- 1 | import * as T from "./token"; 2 | import * as E from "./expr"; 3 | import * as immutable from "immutable"; 4 | 5 | 6 | /* ************************************************************************** */ 7 | /* Values based on closures */ 8 | /* ************************************************************************** */ 9 | 10 | /* -------------------------------------------------------- */ 11 | /* Number values */ 12 | /* -------------------------------------------------------- */ 13 | 14 | export type NumericalValue = { 15 | tag: "NUMERICAL_VALUE", // A number has no more computation left to be run 16 | val: number, 17 | }; 18 | 19 | export function mkNumericalValue(n: number): NumericalValue { 20 | return { 21 | tag: "NUMERICAL_VALUE", 22 | val: n 23 | }; 24 | } 25 | 26 | /* -------------------------------------------------------- */ 27 | /* Function Values */ 28 | /* -------------------------------------------------------- */ 29 | 30 | export type Environment = immutable.Map; 31 | 32 | export type FunctionValue = { 33 | tag: "FUNCTION_VALUE", // A function has no more computation left to be run. 34 | func: E.FunctionExpr, // Of course, this function can be called in the future. 35 | env: Environment, // Ignore for now 36 | }; 37 | 38 | export function mkFunctionValue(e: E.FunctionExpr, env: Environment): FunctionValue { 39 | return { tag: "FUNCTION_VALUE", func: e, env: env }; 40 | } 41 | 42 | 43 | /* -------------------------------------------------------- */ 44 | /* Any Value */ 45 | /* -------------------------------------------------------- */ 46 | 47 | export type Value = 48 | NumericalValue 49 | | FunctionValue; 50 | 51 | export function valueToString(val: Value) { 52 | switch (val.tag) { 53 | case "NUMERICAL_VALUE": { 54 | return `${val.val}`; 55 | } 56 | case "FUNCTION_VALUE": { 57 | return `${E.exprToString(val.func)}`; 58 | } 59 | } 60 | } 61 | 62 | 63 | /* ************************************************************************** */ 64 | /* Interpreter Based on Closures */ 65 | /* ************************************************************************** */ 66 | 67 | export function interpretBinop(leftVal: Value, op: T.BinaryOperator, rightVal: Value): Value { 68 | if (leftVal.tag === "NUMERICAL_VALUE" && rightVal.tag === "NUMERICAL_VALUE") { 69 | switch (op) { 70 | case "+": { 71 | return mkNumericalValue(leftVal.val + rightVal.val); 72 | } 73 | case "-": { 74 | return mkNumericalValue(leftVal.val - rightVal.val); 75 | } 76 | case "*": { 77 | return mkNumericalValue(leftVal.val * rightVal.val); 78 | } 79 | case "/": { 80 | return mkNumericalValue(leftVal.val / rightVal.val); 81 | } 82 | } 83 | } else { 84 | throw Error(`Attempting ${leftVal} ${op} ${rightVal}`); 85 | } 86 | } 87 | 88 | export function openInterpret(env: Environment, e: E.Expr): Value { 89 | switch (e.tag) { 90 | case "NUMBER": { 91 | return mkNumericalValue(e.value); 92 | } 93 | case "BINARY": { 94 | return interpretBinop(openInterpret(env, e.left), e.operator, openInterpret(env, e.right)); 95 | } 96 | case "CONDITIONAL": { 97 | const condVal = openInterpret(env, e.condExpr); 98 | if (condVal.tag === "NUMERICAL_VALUE" && condVal.val !== 0) { 99 | return openInterpret(env, e.thenExpr); 100 | } else { 101 | return openInterpret(env, e.elseExpr); 102 | } 103 | } 104 | case "FUNCTION": { // New case 1 105 | // A function is already a value. 106 | // Question: What is a function value? (hint: first-class functions + state) 107 | return mkFunctionValue(e, env); 108 | } 109 | case "IDENTIFIER": { // New case 2 110 | // Lookup the variable in the environment. 111 | // Lexical scope reduces to dictionary lookup 112 | const v = env.get(e.name); 113 | if (v !== undefined) { 114 | return v; 115 | } else { 116 | throw Error(`Cannot find name ${e.name} in scope}`); 117 | } 118 | } 119 | case "CALL": { // New case 3 120 | // ((x) => x)(1) 121 | // 122 | // 1. Evaluate the left-hand-side. 123 | const funcVal = openInterpret(env, e.func); // Evaluate to the closure ((x) => x) 124 | if (funcVal.tag === "FUNCTION_VALUE") { // 2. Check that we have a function 125 | const argVal = openInterpret(env, e.argument); // 3. Evaluate the argument, 1 126 | // 4. Evaluate the function body under an extended environment 127 | // 5. Question: is interp pure? 128 | // 6. Note that we use funcVal.env as opposed to env! 129 | return openInterpret(funcVal.env.set(funcVal.func.parameter, argVal), funcVal.func.body); 130 | } else { 131 | throw Error(`Cannot apply ${funcVal}.`); 132 | } 133 | } 134 | default: { 135 | throw Error("Shouldn't happen"); 136 | } 137 | } 138 | } 139 | 140 | export function interpret(e: E.Expr): Value { 141 | // Start with empty environment, i.e., no variables in scope 142 | return openInterpret(immutable.Map(), e); 143 | } 144 | -------------------------------------------------------------------------------- /lib/lambdats/expr.ts: -------------------------------------------------------------------------------- 1 | import {BinaryOperator, Identifier, NumericConstant} from "./token"; 2 | import * as util from "util"; 3 | 4 | 5 | /* ************************************************************************** */ 6 | /* AST */ 7 | /* ************************************************************************** */ 8 | 9 | /* -------------------------------------------------------- */ 10 | /* Any binary (two-argument) expression. */ 11 | /* -------------------------------------------------------- */ 12 | 13 | export type BinaryExpr = { 14 | tag: "BINARY"; 15 | operator: BinaryOperator; 16 | left: Expr; 17 | right: Expr; 18 | }; 19 | 20 | export function mkBinaryExpr(left: Expr, operator: BinaryOperator, right: Expr): BinaryExpr { 21 | return { 22 | tag: "BINARY", 23 | operator: operator, 24 | left: left, 25 | right: right, 26 | }; 27 | } 28 | 29 | 30 | /* -------------------------------------------------------- */ 31 | /* Ternary conditional expression. */ 32 | /* -------------------------------------------------------- */ 33 | 34 | export type ConditionalExpr = { 35 | tag: "CONDITIONAL"; 36 | condExpr: Expr; 37 | thenExpr: Expr; 38 | elseExpr: Expr; 39 | }; 40 | 41 | export function mkConditionalExpr(condExpr: Expr, thenExpr: Expr, elseExpr: Expr): ConditionalExpr { 42 | return { 43 | tag: "CONDITIONAL", 44 | condExpr: condExpr, 45 | thenExpr: thenExpr, 46 | elseExpr: elseExpr, 47 | }; 48 | } 49 | 50 | 51 | /* -------------------------------------------------------- */ 52 | /* A function expression. (Not a call, just the function.) */ 53 | /* -------------------------------------------------------- */ 54 | 55 | export type FunctionExpr = { 56 | tag: "FUNCTION"; 57 | parameter: string; 58 | body: Expr; 59 | }; 60 | 61 | export function mkFunctionExpr(parameter: string, body: Expr): FunctionExpr { 62 | return { 63 | tag: "FUNCTION", 64 | parameter: parameter, 65 | body: body, 66 | }; 67 | } 68 | 69 | 70 | /* -------------------------------------------------------- */ 71 | /* A function call expression. */ 72 | /* -------------------------------------------------------- */ 73 | 74 | export type CallExpr = { 75 | tag: "CALL"; 76 | func: Expr; 77 | argument: Expr; 78 | }; 79 | 80 | export function mkCallExpr(func: Expr, argument: Expr): CallExpr { 81 | return { 82 | tag: "CALL", 83 | func: func, 84 | argument: argument, 85 | }; 86 | } 87 | 88 | 89 | /* -------------------------------------------------------- */ 90 | /* Local binding */ 91 | /* -------------------------------------------------------- */ 92 | 93 | export type LetExpr = { 94 | tag: "LET"; // let x = left in right 95 | name: string; 96 | left: Expr; 97 | right: Expr; 98 | }; 99 | 100 | export function mkLetExpr(name: string, left: Expr, right: Expr): LetExpr { 101 | return { 102 | tag: "LET", 103 | name: name, 104 | left: left, 105 | right: right 106 | }; 107 | } 108 | 109 | 110 | /* -------------------------------------------------------- */ 111 | /* Any expression. */ 112 | /* -------------------------------------------------------- */ 113 | 114 | export type Expr = 115 | BinaryExpr 116 | | ConditionalExpr 117 | | FunctionExpr 118 | | CallExpr 119 | | Identifier 120 | | NumericConstant 121 | | LetExpr; 122 | 123 | 124 | /* ************************************************************************** */ 125 | /* Utility */ 126 | /* ************************************************************************** */ 127 | 128 | /** 129 | * Returns the string representation of the expression, for debugging. 130 | */ 131 | export function exprToString(expr: Expr): string { 132 | switch (expr.tag) { 133 | case "IDENTIFIER": 134 | return expr.name; 135 | case "BINARY": 136 | return "(" + exprToString(expr.left) + expr.operator + exprToString(expr.right) + ")"; 137 | case "CONDITIONAL": 138 | return "(" + exprToString(expr.condExpr) + "?" + exprToString(expr.thenExpr) + ":" + exprToString(expr.elseExpr) + ")"; 139 | case "FUNCTION": 140 | return "(λ" + expr.parameter + "=>" + exprToString(expr.body) + ")"; 141 | case "CALL": 142 | return exprToString(expr.func) + "(" + exprToString(expr.argument) + ")"; 143 | case "NUMBER": 144 | return expr.value.toString(); 145 | case "LET": 146 | return `let ${expr.name} = ${exprToString(expr.left)} in ${exprToString(expr.right)}`; 147 | } 148 | } 149 | 150 | export function cytoscapify(e: Expr): string { 151 | let count = 0; 152 | function fresh(prefix: string): string { 153 | count += 1; 154 | return prefix + count; 155 | } 156 | 157 | function createChild(lvl: number, x: number, y: number, parentId: string, e: Expr) { 158 | const node = go(lvl, x, y, e); 159 | const edgeId = fresh("edge"); 160 | const edge = { 161 | "data": { 162 | "id": edgeId, 163 | "source": parentId, 164 | "target": node[0].data.id 165 | } 166 | }; 167 | return { 168 | "node": node, 169 | "edge": edge 170 | }; 171 | } 172 | 173 | function go(lvl: number, x: number, y: number, e: Expr): any { 174 | switch (e.tag) { 175 | case "IDENTIFIER": { 176 | return [{ 177 | "data": { 178 | "id": fresh("identifier"), 179 | "label": e.name 180 | }, 181 | "position": { 182 | "x": x, 183 | "y": y 184 | } 185 | }]; 186 | } 187 | case "NUMBER": { 188 | return [{ 189 | "data": { 190 | "id": fresh("number"), 191 | "label": e.value 192 | }, 193 | "position": { 194 | "x": x, 195 | "y": y 196 | } 197 | }]; 198 | } 199 | case "BINARY": { 200 | const nodeId = fresh("binary"); 201 | const left = createChild(lvl + 1, x - 100 / lvl, y + 50, nodeId, e.left); 202 | const right = createChild(lvl + 1, x + 100 / lvl, y + 50, nodeId, e.right); 203 | 204 | return [{ 205 | "data": { 206 | "id": nodeId, 207 | "label": e.operator 208 | }, 209 | "position": { 210 | "x": x, 211 | "y": y 212 | } 213 | }, left.edge, right.edge].concat(left.node).concat(right.node); 214 | } 215 | case "CONDITIONAL": { 216 | const nodeId = fresh("conditional"); 217 | const condExpr = createChild(lvl + 1, x - 150 / lvl, y + 50, nodeId, e.condExpr); 218 | const thenExpr = createChild(lvl + 1, x, y + 50, nodeId, e.thenExpr); 219 | const elseExpr = createChild(lvl + 1, x + 150 / lvl, y + 50, nodeId, e.elseExpr); 220 | 221 | return [{ 222 | "data": { 223 | "id": nodeId, 224 | "label": "if" 225 | }, 226 | "position": { 227 | "x": x, 228 | "y": y 229 | } 230 | }, condExpr.edge, thenExpr.edge, elseExpr.edge].concat(condExpr.node).concat(thenExpr.node).concat(elseExpr.node); 231 | } 232 | case "FUNCTION": { 233 | const nodeId = fresh("function"); 234 | const body = createChild(lvl + 1, x, y + 50, nodeId, e.body); 235 | return [{ 236 | "data": { 237 | "id": nodeId, 238 | "label": `λ(${e.parameter})` 239 | }, 240 | "position": { 241 | "x": x, 242 | "y": y 243 | } 244 | }, body.edge].concat(body.node); 245 | } 246 | case "CALL": { 247 | const nodeId = fresh("call"); 248 | const func = createChild(lvl + 1, x - 100 / lvl, y + 50, nodeId, e.func); 249 | const arg = createChild(lvl + 1, x + 100 / lvl, y + 50, nodeId, e.argument); 250 | 251 | return [{ 252 | "data": { 253 | "id": nodeId, 254 | "label": "call" 255 | }, 256 | "position": { 257 | "x": x, 258 | "y": y 259 | } 260 | }, func.edge, arg.edge].concat(func.node).concat(arg.node); 261 | } 262 | case "LET": { 263 | const nodeId = fresh("let"); 264 | const left = createChild(lvl + 1, x - 100 / lvl, y + 50, nodeId, e.left); 265 | const right = createChild(lvl + 1, x + 100 / lvl, y + 50, nodeId, e.right); 266 | 267 | return [{ 268 | "data": { 269 | "id": nodeId, 270 | "label": `let(${e.name})` 271 | }, 272 | "position": { 273 | "x": x, 274 | "y": y 275 | } 276 | }, left.edge, right.edge].concat(left.node).concat(right.node); 277 | } 278 | } 279 | }; 280 | 281 | return util.inspect(go(1, 0, 0, e)); 282 | }; 283 | -------------------------------------------------------------------------------- /lib/lambdats/lexer.ts: -------------------------------------------------------------------------------- 1 | import {Token} from "./token"; 2 | 3 | const CODE_POINT_ZERO = "0".codePointAt(0) as number; 4 | const CODE_POINT_NINE = "9".codePointAt(0) as number; 5 | 6 | /** 7 | * If "ch" is a digit, return its value. Otherwise return undefined. 8 | */ 9 | function getDigit(ch: string): number | undefined { 10 | const code = ch.codePointAt(0); 11 | if (code === undefined || code < CODE_POINT_ZERO || code > CODE_POINT_NINE) { 12 | return undefined; 13 | } else { 14 | return code - CODE_POINT_ZERO; 15 | } 16 | } 17 | 18 | /** 19 | * Return whether "ch" is a letter (A-Z in either case). 20 | */ 21 | function isLetter(ch: string): boolean { 22 | return ch.match(/^[A-Za-z]/) !== null; 23 | } 24 | 25 | /** 26 | * Generate tokens from the input file. 27 | */ 28 | export function* getTokens(input: string): Generator { 29 | let i = 0; 30 | 31 | while (i < input.length) { 32 | const ch = input[i]; 33 | if (ch === " ") { 34 | // Skip whitespace. 35 | i += 1; 36 | } else if (ch === "+" || ch === "-" || ch === "*" || ch === "/" || 37 | ch === "(" || ch === ")" || ch === "?" || ch === ":" || ch === "λ") { 38 | 39 | yield ch; 40 | i += 1; 41 | } else if (ch === "=" && i + 1 < input.length && input[i + 1] === ">") { 42 | yield "=>"; 43 | i += 2; 44 | } else if (ch === "=") { 45 | yield ch; 46 | i += 1; 47 | } else if (getDigit(ch) !== undefined) { 48 | let value = 0; 49 | while (i < input.length) { 50 | const digitValue = getDigit(input[i]); 51 | if (digitValue === undefined) { 52 | break; 53 | } else { 54 | value = value*10 + digitValue; 55 | i += 1; 56 | } 57 | } 58 | 59 | yield { 60 | tag: "NUMBER", 61 | value: value, 62 | }; 63 | } else if (isLetter(ch)) { 64 | let name = ""; 65 | while (i < input.length && isLetter(input[i])) { 66 | name += input[i]; 67 | i += 1; 68 | } 69 | yield { 70 | tag: "IDENTIFIER", 71 | name: name, 72 | }; 73 | } else { 74 | yield { 75 | tag: "ERROR", 76 | ch: ch, 77 | } 78 | // End lexing. 79 | break; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/lambdats/main.ts: -------------------------------------------------------------------------------- 1 | import {getTokens} from "./lexer"; 2 | import {parse} from "./parser"; 3 | import {interpret} from "./substInterp"; 4 | import {exprToString} from "./expr"; 5 | 6 | const yCombinator = "(λf => (λx => λy => f(x(x))(y)) (λa => λb => f(a(a))(b)))"; 7 | const addTwo = "(λa => λb => a + b)(5)(6)"; 8 | const factorial = "(λfact => λn => n ? n*fact(n - 1) : 1)"; 9 | const fullFactorial = yCombinator + factorial + "(5)" 10 | 11 | function tokenTest() { 12 | for (const token of getTokens(addTwo)) { 13 | console.log(token); 14 | } 15 | } 16 | 17 | function parseTest() { 18 | try { 19 | const input = fullFactorial; 20 | console.log("Input: " + input); 21 | const expr = parse(input); 22 | console.log(JSON.stringify(expr, null, 4)); 23 | console.log(exprToString(expr)); 24 | console.log(exprToString(interpret(expr))); 25 | } catch (e: any) { 26 | console.log(e.message); 27 | } 28 | } 29 | 30 | parseTest(); 31 | -------------------------------------------------------------------------------- /lib/lambdats/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambdats", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npx ts-node main.ts" 8 | }, 9 | "keywords": [], 10 | "author": "Lawrence Kesteloot (https://www.teamten.com/lawrence/)", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "ts-node": "^10.4.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/lambdats/parser.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Expr} from "./expr"; 3 | import {getTokens} from "./lexer"; 4 | import {Token} from "./token"; 5 | 6 | /** 7 | * Converts a generated stream of tokens into one that can be peeked ahead. 8 | */ 9 | class TokenStream { 10 | public readonly stream: Generator; 11 | public token: Token | undefined; 12 | 13 | constructor(stream: Generator) { 14 | this.stream = stream; 15 | this.token = this.getNext(); 16 | } 17 | 18 | /** 19 | * Fetch the next token. Does not update the "token" field. 20 | */ 21 | private getNext(): Token | undefined { 22 | const next = this.stream.next(); 23 | return next.done ? undefined : next.value; 24 | } 25 | 26 | /** 27 | * Peeks at the next token. Does not advance the stream. 28 | */ 29 | public peek(): Token | undefined { 30 | return this.token; 31 | } 32 | 33 | /** 34 | * Gets the next token and advances the stream. 35 | */ 36 | public next(): Token | undefined { 37 | const oldToken = this.token; 38 | this.token = this.getNext(); 39 | return oldToken; 40 | } 41 | } 42 | 43 | /** 44 | * Parse the input string, returning the AST. 45 | */ 46 | export function parse(input: string): Expr { 47 | const lexer = new TokenStream(getTokens(input)); 48 | return parseExpr(lexer); 49 | } 50 | 51 | /** 52 | * Parse a full expression from the stream. 53 | * @param lexer 54 | */ 55 | function parseExpr(lexer: TokenStream): Expr { 56 | return parseLet(lexer); 57 | } 58 | 59 | function parseLet(lexer: TokenStream): Expr { 60 | const token = lexer.peek(); 61 | 62 | if (typeof token === "object" && token.tag === "IDENTIFIER" && token.name === "let") { 63 | lexer.next(); 64 | const name = lexer.next(); 65 | if (typeof name === "object" && name.tag === "IDENTIFIER") { 66 | const eq = lexer.peek(); 67 | if (eq === "=") { 68 | lexer.next(); 69 | const left = parseLet(lexer); 70 | 71 | const token2 = lexer.peek(); 72 | if (typeof token2 === "object" && token2.tag === "IDENTIFIER" && token2.name === "in") { 73 | lexer.next(); 74 | const right = parseLet(lexer); 75 | return { 76 | tag: "LET", 77 | name: name.name, 78 | left: left, 79 | right: right 80 | }; 81 | } else { 82 | throw Error(`Expected in but found ${token2}`); 83 | } 84 | } else { 85 | throw Error(`Expected = but found ${eq}`); 86 | } 87 | } else { 88 | throw Error(`Expected identifier but found ${name}`); 89 | } 90 | } else { 91 | return parseFunction(lexer); 92 | } 93 | } 94 | 95 | /** 96 | * Parse a function definition: 97 | * 98 | * λx => e 99 | */ 100 | function parseFunction(lexer: TokenStream): Expr { 101 | const token = lexer.peek(); 102 | 103 | if (token === "λ") { 104 | lexer.next(); 105 | const parameter = lexer.next(); 106 | if (typeof parameter === "object" && parameter.tag === "IDENTIFIER") { 107 | if (lexer.next() !== "=>") { 108 | throw new Error("Missing arrow"); 109 | } 110 | 111 | const body = parseExpr(lexer); 112 | 113 | return { 114 | tag: "FUNCTION", 115 | parameter: parameter.name, 116 | body: body, 117 | }; 118 | } else { 119 | throw new Error("Parameter must be an identifier: " + parameter); 120 | } 121 | } else { 122 | return parseConditional(lexer); 123 | } 124 | } 125 | 126 | /** 127 | * Parse a ternary conditional expression: 128 | * 129 | * e ? e : e 130 | * 131 | * The "then" clause is used if the "conditional" clause is non-zero. Otherwise the "else" clause is used. 132 | */ 133 | function parseConditional(lexer: TokenStream): Expr { 134 | const condExpr = parseSum(lexer); 135 | 136 | if (lexer.peek() === "?") { 137 | lexer.next(); 138 | 139 | const thenExpr = parseExpr(lexer); 140 | 141 | if (lexer.next() !== ":") { 142 | throw new Error("Colon not found in conditional"); 143 | } 144 | 145 | const elseExpr = parseExpr(lexer); 146 | 147 | return { 148 | tag: "CONDITIONAL", 149 | condExpr: condExpr, 150 | thenExpr: thenExpr, 151 | elseExpr: elseExpr, 152 | }; 153 | } 154 | 155 | return condExpr; 156 | } 157 | 158 | /** 159 | * Parse a sum expression: 160 | * 161 | * e + e 162 | * e - e 163 | */ 164 | function parseSum(lexer: TokenStream): Expr { 165 | let left = parseProduct(lexer); 166 | 167 | // Sums are left-associative, we can't recurse on the right. Just keep getting more 168 | // sum expressions and grouping them on the left. 169 | while (true) { 170 | const token = lexer.peek(); 171 | if (token === "+" || token === "-") { 172 | lexer.next(); 173 | const right = parseProduct(lexer); 174 | left = { 175 | tag: "BINARY", 176 | operator: token, 177 | left: left, 178 | right: right, 179 | }; 180 | } else { 181 | break; 182 | } 183 | } 184 | 185 | return left; 186 | } 187 | 188 | /** 189 | * Parse a product expression: 190 | * 191 | * e * e 192 | * e / e 193 | * 194 | * The division is truncated toward zero. 195 | */ 196 | function parseProduct(lexer: TokenStream): Expr { 197 | let left = parseCall(lexer); 198 | 199 | // Products are left-associative, we can't recurse on the right. Just keep getting more 200 | // product expressions and grouping them on the left. 201 | while (true) { 202 | const token = lexer.peek(); 203 | if (token === "*" || token === "/") { 204 | lexer.next(); 205 | const right = parseCall(lexer); 206 | left = { 207 | tag: "BINARY", 208 | operator: token, 209 | left: left, 210 | right: right, 211 | }; 212 | } else { 213 | break; 214 | } 215 | } 216 | 217 | return left; 218 | } 219 | 220 | /** 221 | * Parse a function call: 222 | * 223 | * e(e) 224 | */ 225 | function parseCall(lexer: TokenStream): Expr { 226 | let func = parseAtom(lexer); 227 | 228 | // Calls are left-associative, we can't recurse on the right. Just keep getting more 229 | // call expressions and grouping them on the left. 230 | while (true) { 231 | if (lexer.peek() === "(") { 232 | lexer.next(); 233 | const argument = parseExpr(lexer); 234 | if (lexer.next() !== ")") { 235 | throw new Error("Missing close parenthesis"); 236 | } 237 | 238 | func = { 239 | tag: "CALL", 240 | func: func, 241 | argument: argument, 242 | }; 243 | } else { 244 | break; 245 | } 246 | } 247 | 248 | return func; 249 | } 250 | 251 | /** 252 | * Parse an atom: 253 | * 254 | * n 255 | * x 256 | * (e) 257 | */ 258 | function parseAtom(lexer: TokenStream): Expr { 259 | const token = lexer.peek(); 260 | if (typeof token === "object") { 261 | if (token.tag === "NUMBER") { 262 | lexer.next(); 263 | return token; 264 | } 265 | if (token.tag === "IDENTIFIER") { 266 | lexer.next(); 267 | return token; 268 | } 269 | } 270 | 271 | if (token === "(") { 272 | lexer.next(); 273 | const expr = parseExpr(lexer); 274 | if (lexer.next() !== ")") { 275 | throw new Error("Missing close parenthesis"); 276 | } 277 | 278 | return expr; 279 | } 280 | 281 | throw new Error("Can't parse token: " + JSON.stringify(token)); 282 | } 283 | -------------------------------------------------------------------------------- /lib/lambdats/substInterp.ts: -------------------------------------------------------------------------------- 1 | import * as T from "./token"; 2 | import * as E from "./expr"; 3 | 4 | 5 | /* ************************************************************************** */ 6 | /* Values */ 7 | /* ************************************************************************** */ 8 | 9 | export type Value = 10 | T.NumericConstant 11 | | E.FunctionExpr 12 | 13 | 14 | /* ************************************************************************** */ 15 | /* Substitution */ 16 | /* ************************************************************************** */ 17 | 18 | export function substitute(orig: E.Expr, x: string, other: E.Expr): E.Expr { 19 | switch (orig.tag) { 20 | case "NUMBER": { 21 | return T.mkNumericConstant(orig.value); 22 | } 23 | case "BINARY": { 24 | return E.mkBinaryExpr(substitute(orig.left, x, other), 25 | orig.operator, 26 | substitute(orig.right, x, other)); 27 | } 28 | case "CONDITIONAL": { 29 | return E.mkConditionalExpr(substitute(orig.condExpr, x, other), 30 | substitute(orig.thenExpr, x, other), 31 | substitute(orig.elseExpr, x, other)); 32 | } 33 | case "FUNCTION": { 34 | return orig.parameter === x ? orig : E.mkFunctionExpr(orig.parameter, substitute(orig.body, x, other)); 35 | } 36 | case "IDENTIFIER": { 37 | return orig.name === x ? other : orig; 38 | } 39 | case "CALL": { 40 | return E.mkCallExpr(substitute(orig.func, x, other), 41 | substitute(orig.argument, x, other)); 42 | } 43 | default: { 44 | throw Error("Shouldn't happen"); 45 | } 46 | } 47 | } 48 | 49 | 50 | /* ************************************************************************** */ 51 | /* Interpreter */ 52 | /* ************************************************************************** */ 53 | 54 | export function interpretBinop(leftVal: Value, op: T.BinaryOperator, rightVal: Value): Value { 55 | if (leftVal.tag === "NUMBER" && rightVal.tag === "NUMBER") { 56 | switch (op) { 57 | case "+": { 58 | return T.mkNumericConstant(leftVal.value + rightVal.value); 59 | } 60 | case "-": { 61 | return T.mkNumericConstant(leftVal.value - rightVal.value); 62 | } 63 | case "*": { 64 | return T.mkNumericConstant(leftVal.value * rightVal.value); 65 | } 66 | case "/": { 67 | return T.mkNumericConstant(leftVal.value / rightVal.value); 68 | } 69 | } 70 | } else { 71 | throw Error(`Attempting ${leftVal} ${op} ${rightVal}`); 72 | } 73 | } 74 | 75 | export function interpret(e: E.Expr): Value { 76 | switch (e.tag) { 77 | case "NUMBER": { 78 | return T.mkNumericConstant(e.value); 79 | } 80 | case "BINARY": { 81 | return interpretBinop(interpret(e.left), e.operator, interpret(e.right)); 82 | } 83 | case "CONDITIONAL": { 84 | const condVal = interpret(e.condExpr); 85 | if (condVal.tag === "NUMBER" && condVal.value !== 0) { 86 | return interpret(e.thenExpr); 87 | } else { 88 | return interpret(e.elseExpr); 89 | } 90 | } 91 | case "FUNCTION": { // New case 1, a function is already a value. 92 | return E.mkFunctionExpr(e.parameter, e.body); 93 | } 94 | case "IDENTIFIER": { // New case 2, all variables should have already been substituted away 95 | throw Error(`Cannot find name ${e.name} in scope}`); 96 | } 97 | case "CALL": { // New case 3 98 | const func = interpret(e.func); 99 | if (func.tag === "FUNCTION") { 100 | const arg = interpret(e.argument); 101 | return interpret(substitute(func.body, func.parameter, arg)); 102 | } else { 103 | throw Error(`Cannot apply ${func} to ${e.argument}`); 104 | } 105 | } 106 | 107 | default: { 108 | throw Error("Shouldn't happen"); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/lambdats/token.ts: -------------------------------------------------------------------------------- 1 | 2 | export type BinaryOperator = "+" | "-" | "*" | "/"; 3 | 4 | export type Symbol = | "(" | ")" | "=>" | "?" | ":" | "λ" | "let" | "in" | "="; 5 | 6 | export type Identifier = { 7 | tag: "IDENTIFIER"; 8 | name: string; 9 | }; 10 | 11 | export function mkIdentifier(name: string): Identifier { 12 | return { 13 | tag: "IDENTIFIER", 14 | name: name, 15 | }; 16 | } 17 | 18 | export type NumericConstant = { 19 | tag: "NUMBER"; 20 | value: number; 21 | } 22 | 23 | export function mkNumericConstant(value: number): NumericConstant { 24 | return { 25 | tag: "NUMBER", 26 | value: value, 27 | }; 28 | } 29 | 30 | 31 | export type Error = { 32 | tag: "ERROR"; 33 | ch: string; 34 | } 35 | 36 | export type Token = Identifier | BinaryOperator | Symbol | NumericConstant | Error; 37 | -------------------------------------------------------------------------------- /lib/lambdats/transpile.ts: -------------------------------------------------------------------------------- 1 | import * as T from "./token"; 2 | import * as E from "./expr"; 3 | 4 | 5 | export function transpile(e: E.Expr): E.Expr { 6 | switch (e.tag) { 7 | case "LET": { 8 | // We don't have "LET" in LambdaTS 9 | // We translate let x = left in right into 10 | // (λx => right)(left) 11 | return E.mkCallExpr(E.mkFunctionExpr(e.name, transpile(e.right)), transpile(e.left)); 12 | } 13 | 14 | // All of theses cases are not interesting 15 | case "NUMBER": { 16 | return T.mkNumericConstant(e.value); 17 | } 18 | case "BINARY": { 19 | return E.mkBinaryExpr(transpile(e.left), e.operator, transpile(e.right)); 20 | } 21 | case "CONDITIONAL": { 22 | return E.mkConditionalExpr(transpile(e.condExpr), transpile(e.thenExpr), transpile(e.elseExpr)); 23 | } 24 | case "FUNCTION": { 25 | return E.mkFunctionExpr(e.parameter, transpile(e.body)); 26 | } 27 | case "IDENTIFIER": { 28 | return T.mkIdentifier(e.name); 29 | } 30 | case "CALL": { 31 | return E.mkCallExpr(transpile(e.func), transpile(e.argument)); 32 | } 33 | default: { 34 | throw Error("Shouldn't happen"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/lambdats/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noImplicitReturns": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/list.ts: -------------------------------------------------------------------------------- 1 | import * as util from "util"; 2 | 3 | 4 | /* ************************************************************************** */ 5 | /* Data-Type */ 6 | /* ************************************************************************** */ 7 | 8 | export type List = 9 | { 10 | tag: "NIL" 11 | } 12 | | { 13 | tag: "CONS", 14 | contents: T, 15 | rest: List 16 | }; 17 | 18 | export function mkNil(): List { 19 | return { 20 | tag: "NIL" 21 | }; 22 | } 23 | 24 | export function mkCons(x: T, ls: List): List { 25 | return { 26 | tag: "CONS", 27 | contents: x, 28 | rest: ls 29 | }; 30 | } 31 | 32 | 33 | /* ************************************************************************** */ 34 | /* Example lists */ 35 | /* ************************************************************************** */ 36 | 37 | export const ls0 = mkNil(); 38 | export const ls1 = mkCons(1, mkNil()); 39 | export const ls2 = mkCons(2, ls1); 40 | export const ls3 = mkCons(3, ls2); 41 | export const ls4 = mkCons(4, ls3); 42 | export const ls5 = mkCons(5, ls4); 43 | 44 | 45 | 46 | /* ************************************************************************** */ 47 | /* List Functions */ 48 | /* ************************************************************************** */ 49 | 50 | export function head(ls: List): T { 51 | switch (ls.tag) { 52 | case "NIL": { 53 | throw Error("Empty list ..."); 54 | } 55 | case "CONS": { 56 | return ls.contents; 57 | } 58 | } 59 | } 60 | 61 | export function tail(ls: List): List { 62 | switch (ls.tag) { 63 | case "NIL": { 64 | throw Error("Empty list ..."); 65 | } 66 | case "CONS": { 67 | return ls.rest; 68 | } 69 | } 70 | } 71 | 72 | export function length(ls: List): number { 73 | switch (ls.tag) { 74 | case "NIL": { 75 | return 0; 76 | } 77 | case "CONS": { 78 | return 1 + length(ls.rest); 79 | } 80 | } 81 | } 82 | 83 | export function listToString(ls: List): string { 84 | switch (ls.tag) { 85 | case "NIL": { 86 | return "()"; 87 | } 88 | case "CONS": { 89 | return "(" + ls.contents + " " + listToString(ls.rest) + ")"; 90 | } 91 | } 92 | } 93 | 94 | export function emit(ls: List): string { 95 | switch (ls.tag) { 96 | case "NIL": { 97 | return "mkNil()"; 98 | } 99 | case "CONS": { 100 | return `mkCons(${ls.contents}, ${emit(ls.rest)})`; 101 | } 102 | } 103 | } 104 | 105 | export function append(ls1: List, ls2: List): List { 106 | switch (ls1.tag) { 107 | case "NIL": { 108 | return ls2; 109 | } 110 | case "CONS": { 111 | return mkCons(ls1.contents, append(ls1.rest, ls2)); 112 | } 113 | } 114 | } 115 | 116 | export function map(f: (elem: T) => U, ls: List): List { 117 | switch(ls.tag) { 118 | case "NIL": { 119 | return mkNil(); 120 | } 121 | case "CONS": { 122 | return mkCons(f(ls.contents), map(f, ls.rest)); 123 | } 124 | } 125 | } 126 | 127 | export function filter(f: (elem: T) => boolean, ls: List): List { 128 | switch(ls.tag) { 129 | case "NIL": { 130 | return mkNil(); 131 | } 132 | case "CONS": { 133 | if (f(ls.contents)) { 134 | return mkCons(ls.contents, filter(f, ls.rest)); 135 | } else { 136 | return filter(f, ls.rest); 137 | } 138 | } 139 | } 140 | } 141 | 142 | export function reduce(f: (elem: T, acc: U) => U, initial: U, ls: List): U { 143 | switch (ls.tag) { 144 | case "NIL": { 145 | return initial; 146 | } 147 | case "CONS": { 148 | return f(ls.contents, reduce(f, initial, ls.rest)); 149 | } 150 | } 151 | } 152 | 153 | export function reverse(ls: List): List { 154 | function go(orig: List, acc: List): List { 155 | switch (orig.tag) { 156 | case "NIL": { 157 | return acc; 158 | } 159 | case "CONS": { 160 | return go(orig.rest, mkCons(orig.contents, acc)); 161 | } 162 | } 163 | } 164 | 165 | return go(ls, mkNil()); 166 | } 167 | 168 | export function foldl(f: (elem: T, acc: U) => U, acc: U, ls: List): U { 169 | switch (ls.tag) { 170 | case "NIL": { 171 | return acc; 172 | } 173 | case "CONS": { 174 | return f(ls.contents, reduce(f, acc, ls.rest)); 175 | } 176 | } 177 | } 178 | 179 | export function foldr(f: (elem: T, acc: U) => U, acc: U, ls: List): U { 180 | switch (ls.tag) { 181 | case "NIL": { 182 | return acc; 183 | } 184 | case "CONS": { 185 | return f(ls.contents, foldr(f, acc, ls.rest)); 186 | } 187 | } 188 | } 189 | 190 | 191 | /* ************************************************************************** */ 192 | /* Utility */ 193 | /* ************************************************************************** */ 194 | 195 | export function arrToList(xs: T[]): List { 196 | if (xs.length == 0) { 197 | return mkNil(); 198 | } else { 199 | return mkCons(xs[0], arrToList(xs.slice(1))); 200 | } 201 | } 202 | 203 | export function cytoscapify(ls: List): string { 204 | let count = 0; 205 | function fresh(prefix: string): string { 206 | count += 1; 207 | return prefix + count; 208 | } 209 | 210 | function go(ls: List): any { 211 | switch (ls.tag) { 212 | case "NIL": { 213 | return [ 214 | { 215 | "data": { 216 | "id": fresh("nil") 217 | } 218 | } 219 | ]; 220 | } 221 | case "CONS": { 222 | const rest = go(ls.rest); 223 | const nodeId = fresh("cons"); 224 | const edgeId = fresh("edge"); 225 | const edge = { 226 | "data": { 227 | "id": edgeId, 228 | "source": nodeId, 229 | "target": rest[0].data.id 230 | } 231 | }; 232 | return [ 233 | { 234 | "data": { 235 | "id": nodeId, 236 | "label": ls.contents 237 | } 238 | }, 239 | edge 240 | ].concat(rest); 241 | } 242 | } 243 | }; 244 | 245 | return util.inspect(go(ls)); 246 | }; 247 | -------------------------------------------------------------------------------- /lib/tree.ts: -------------------------------------------------------------------------------- 1 | import * as util from "util"; 2 | import { List, mkNil, mkCons, append } from "./list"; 3 | 4 | /* ************************************************************************** */ 5 | /* Data-Type */ 6 | /* ************************************************************************** */ 7 | 8 | export type Tree = 9 | { 10 | tag: "LEAF" 11 | } 12 | | { 13 | tag: "NODE", 14 | contents: T, 15 | left: Tree, 16 | right: Tree 17 | }; 18 | 19 | export function mkLeaf(): Tree { 20 | return { 21 | tag: "LEAF" 22 | }; 23 | } 24 | 25 | export function mkNode(x: T, left: Tree, right: Tree): Tree { 26 | return { 27 | tag: "NODE", 28 | contents: x, 29 | left: left, 30 | right: right 31 | }; 32 | } 33 | 34 | export function mkLeafNode(x: T): Tree { 35 | return mkNode(x, mkLeaf(), mkLeaf()); 36 | } 37 | 38 | 39 | /* ************************************************************************** */ 40 | /* Tree Examples */ 41 | /* ************************************************************************** */ 42 | 43 | export const t0 = mkLeaf(); 44 | export const t1 = mkLeafNode(1); 45 | export const t2 = mkNode(2, t1, mkLeaf()); 46 | export const t3 = mkNode(3, t1, mkLeafNode(2)); 47 | export const t4 = mkNode(4, t3, t2); 48 | 49 | 50 | /* ************************************************************************** */ 51 | /* Tree Functions */ 52 | /* ************************************************************************** */ 53 | 54 | export function height(t: Tree): number { 55 | switch (t.tag) { 56 | case "LEAF": { 57 | return 0; 58 | } 59 | case "NODE": { 60 | return 1 + Math.max(height(t.left), height(t.right)); 61 | } 62 | } 63 | } 64 | 65 | 66 | export function treeToString(t: Tree): string { 67 | switch (t.tag) { 68 | case "LEAF": { 69 | return "()"; 70 | } 71 | case "NODE": { 72 | return "(" + t.contents + " " + treeToString(t.left) + " " + treeToString(t.right) + ")"; 73 | } 74 | 75 | } 76 | } 77 | 78 | export function emit(t: Tree): string { 79 | switch (t.tag) { 80 | case "LEAF": { 81 | return "mkLeaf()"; 82 | } 83 | case "NODE": { 84 | return `mkNode(${t.contents}, ${emit(t.left)}, ${emit(t.right)})` 85 | } 86 | } 87 | } 88 | 89 | export function appendRightMost(t1: Tree, t2: Tree): Tree { 90 | /* NOTE: for comparison with list append. */ 91 | switch (t1.tag) { 92 | case "LEAF": { 93 | return t2; 94 | } 95 | case "NODE": { 96 | return mkNode(t1.contents, t1.left, appendRightMost(t1.right, t2)); 97 | } 98 | } 99 | } 100 | 101 | export function map(f: (elem: T) => U, t: Tree): Tree { 102 | switch (t.tag) { 103 | case "LEAF": { 104 | return mkLeaf(); 105 | } 106 | case "NODE": { 107 | return mkNode(f(t.contents), map(f, t.left), map(f, t.right)); 108 | } 109 | } 110 | } 111 | 112 | export function strangeFilter(predicate: (elem: T) => boolean, t: Tree): Tree { 113 | /* NOTE: for comparison with list filter. */ 114 | switch (t.tag) { 115 | case "LEAF": { 116 | return mkLeaf(); 117 | } 118 | case "NODE": { 119 | if (predicate(t.contents)) { 120 | return mkNode(t.contents, strangeFilter(predicate, t.left), strangeFilter(predicate, t.right)); 121 | } else { 122 | /* Non-unique choices. */ 123 | const left = strangeFilter(predicate, t.left); 124 | switch (left.tag) { 125 | case "LEAF": { 126 | return strangeFilter(predicate, t.right); 127 | } 128 | case "NODE": { 129 | return appendRightMost(left, strangeFilter(predicate, t.right)); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | 137 | export function filter(predicate: (elem: T) => boolean, t: Tree): List { 138 | switch (t.tag) { 139 | case "LEAF": { 140 | return mkNil(); 141 | } 142 | case "NODE": { 143 | if (predicate(t.contents)) { 144 | return append(mkCons(t.contents, filter(predicate, t.left)), filter(predicate, t.right)); 145 | } else { 146 | return append(filter(predicate, t.left), filter(predicate, t.right)); 147 | } 148 | } 149 | } 150 | } 151 | 152 | export function reduce(f: (elem: T, acc: U) => U, initial: U, t: Tree): U { 153 | switch (t.tag) { 154 | case "LEAF": { 155 | return initial; 156 | } 157 | case "NODE": { 158 | // Question: Is this the only way to write this function? 159 | // Question: What is the effect of order-of-evaluation? 160 | return reduce(f, f(t.contents, reduce(f, initial, t.left)), t.right); 161 | } 162 | } 163 | } 164 | 165 | export function prefix(t: Tree): List { 166 | switch (t.tag) { 167 | case "LEAF": { 168 | return mkNil(); 169 | } 170 | case "NODE": { 171 | return mkCons(t.contents, append(prefix(t.left), prefix(t.right))); 172 | } 173 | } 174 | } 175 | 176 | export function infix(t: Tree): List { 177 | switch (t.tag) { 178 | case "LEAF": { 179 | return mkNil(); 180 | } 181 | case "NODE": { 182 | return append(infix(t.left), mkCons(t.contents, infix(t.right))); 183 | } 184 | } 185 | } 186 | 187 | export function postfix(t: Tree): List { 188 | switch (t.tag) { 189 | case "LEAF": { 190 | return mkNil(); 191 | } 192 | case "NODE": { 193 | // Question: What is the time-complexity? 194 | // Challenge: Can we write this function faster? 195 | return append(append(prefix(t.left), prefix(t.right)), mkCons(t.contents, mkNil())); 196 | } 197 | } 198 | } 199 | 200 | 201 | /* ************************************************************************** */ 202 | /* Utility */ 203 | /* ************************************************************************** */ 204 | 205 | export function cytoscapify(t: Tree): string { 206 | let count = 0; 207 | function fresh(prefix: string): string { 208 | count += 1; 209 | return prefix + count; 210 | } 211 | 212 | function go(lvl: number, x: number, y: number, t: Tree): any { 213 | switch (t.tag) { 214 | case "LEAF": { 215 | return [ 216 | { 217 | "data": { 218 | "id": fresh("leaf") 219 | }, 220 | "position": { 221 | "x": x, 222 | "y": y 223 | } 224 | } 225 | ]; 226 | } 227 | case "NODE": { 228 | const left = go(lvl + 1, x - 100 / lvl, y + 50, t.left); 229 | const right = go(lvl + 1, x + 100 / lvl, y + 50, t.right); 230 | const nodeId = fresh("node"); 231 | const leftEdgeId = fresh("edge"); 232 | const leftEdge = { 233 | "data": { 234 | "id": leftEdgeId, 235 | "source": nodeId, 236 | "target": left[0].data.id 237 | } 238 | }; 239 | const rightEdgeId = fresh("edge"); 240 | const rightEdge = { 241 | "data": { 242 | "id": rightEdgeId, 243 | "source": nodeId, 244 | "target": right[0].data.id 245 | } 246 | }; 247 | 248 | return [ 249 | { 250 | "data": { 251 | "id": nodeId, 252 | "label": t.contents 253 | }, 254 | "position": { 255 | "x": x, 256 | "y": y 257 | } 258 | }, 259 | leftEdge, 260 | rightEdge 261 | ].concat(left).concat(right); 262 | } 263 | } 264 | }; 265 | 266 | return util.inspect(go(1, 0, 0, t)); 267 | }; 268 | 269 | 270 | /* ************************************************************************** */ 271 | /* Tree Class */ 272 | /* ************************************************************************** */ 273 | 274 | export class TreeCls { 275 | public readonly contents: T | undefined; 276 | public readonly left: TreeCls | undefined; 277 | public readonly right: TreeCls | undefined; 278 | 279 | constructor(contents: T|undefined, left: TreeCls|undefined, right: TreeCls|undefined) { 280 | this.contents = contents; 281 | this.left = left; 282 | this.right = right; 283 | } 284 | 285 | height(): number { 286 | const l = this.left === undefined ? 0 : this.left.height(); 287 | const r = this.right === undefined ? 0 : this.right.height(); 288 | return 1 + Math.max(l, r); 289 | } 290 | 291 | toADT(): Tree { 292 | if (this.contents === undefined && this.left === undefined && this.right === undefined) { 293 | return { 294 | tag: "LEAF" 295 | }; 296 | } else { 297 | return { 298 | tag: "NODE", 299 | contents: this.contents, 300 | left: this.left !== undefined ? this.left.toADT() : { tag: "LEAF" } as Tree, 301 | right: this.right !== undefined ? this.right.toADT() : { tag: "LEAF" } as Tree 302 | }; 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /media/cat-no-meme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/media/cat-no-meme.jpg -------------------------------------------------------------------------------- /media/concur_vs_parallel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/media/concur_vs_parallel.png -------------------------------------------------------------------------------- /media/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/media/context.png -------------------------------------------------------------------------------- /media/expression-problem-function.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 61 | Type 72 | Function 84 | Box 95 | Circle 106 | area() 117 | perimeter() 128 | w×h 139 | πr2 154 | 2w + 2h 165 | 2πr 178 | 185 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /media/expression-problem-type.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 61 | Type 72 | Function 84 | Box 95 | Circle 106 | area() 117 | perimeter() 128 | w×h 139 | πr2 154 | 2w + 2h 165 | 2πr 178 | 185 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /media/expression-problem.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 61 | Type 72 | Function 84 | Box 95 | Circle 106 | area() 117 | perimeter() 128 | w×h 139 | πr2 154 | 2w + 2h 165 | 2πr 178 | 179 | 180 | -------------------------------------------------------------------------------- /media/heap-oop.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 25 | 33 | 38 | 39 | 47 | 52 | 53 | 61 | 66 | 67 | 76 | 81 | 82 | 91 | 96 | 97 | 105 | 110 | 111 | 112 | 131 | 133 | 134 | 136 | image/svg+xml 137 | 139 | 140 | 141 | 142 | 143 | 147 | 150 | firstNamelastNamev-table 171 | 178 | 179 | user 190 | 195 | 198 | fullName() 209 | 216 | 217 | 222 | "John" 233 | 240 | "Smith" 251 | 258 | 263 | 268 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /media/heap-procedural.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 27 | 32 | 33 | 42 | 47 | 48 | 56 | 61 | 62 | 71 | 76 | 77 | 78 | 97 | 99 | 100 | 102 | image/svg+xml 103 | 105 | 106 | 107 | 108 | 109 | 113 | 116 | firstNamelastName 132 | 139 | 140 | user 151 | 156 | "John" 167 | 174 | "Smith" 185 | 192 | 197 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /media/heap-shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 27 | 32 | 33 | 41 | 46 | 47 | 56 | 61 | 62 | 70 | 75 | 76 | 84 | 89 | 90 | 99 | 104 | 105 | 106 | 125 | 127 | 128 | 130 | image/svg+xml 131 | 133 | 134 | 135 | 136 | 137 | 141 | 144 | widthheightv-table 165 | 172 | 173 | shape1 184 | 189 | 191 | 194 | area()print() 210 | 217 | 218 | 219 | 224 | 227 | radiusv-table 243 | 250 | 251 | shape2 262 | 267 | 270 | 273 | area()print() 289 | 296 | 297 | 298 | 303 | 304 | 305 | -------------------------------------------------------------------------------- /media/heap-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 27 | 32 | 33 | 41 | 46 | 47 | 56 | 61 | 62 | 70 | 75 | 76 | 84 | 89 | 90 | 99 | 104 | 105 | 113 | 118 | 119 | 127 | 132 | 133 | 142 | 147 | 148 | 149 | 168 | 170 | 171 | 173 | image/svg+xml 174 | 176 | 177 | 178 | 179 | 180 | 184 | 187 | widthheightv-table 208 | 215 | 216 | shape1 227 | 232 | 234 | 237 | area()print() 253 | 260 | 261 | 262 | 267 | 270 | radiusv-table 286 | 293 | 294 | shape2 305 | 310 | 313 | 316 | area()print() 332 | 339 | 340 | 341 | 346 | 349 | widthheightv-table 370 | 377 | 378 | shape3 389 | 394 | 397 | 400 | area()print() 416 | 423 | 424 | 425 | 430 | 435 | 436 | 437 | -------------------------------------------------------------------------------- /media/making_burger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/media/making_burger.png -------------------------------------------------------------------------------- /media/monad_burrito.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/media/monad_burrito.png -------------------------------------------------------------------------------- /media/thread_vs_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danehuang/PAPL/95893cf97e0195b32b7f5cb2d32c013ec7223008/media/thread_vs_process.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@types/react": "^17.0.24", 4 | "@types/react-dom": "^17.0.9", 5 | "carbon-components": "^10.41.0", 6 | "chart.js": "^3.5.1", 7 | "cytoscape": "^3.19.0", 8 | "immutable": "^4.0.0-rc.12", 9 | "package.json": "^2.0.1", 10 | "plotly": "^1.0.6", 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2", 13 | "react-scripts": "^4.0.3", 14 | "tslab": "^1.0.15", 15 | "uuid": "^8.3.2" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^15.12.4", 19 | "ts-node": "^10.2.1", 20 | "typescript": "^4.4.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tmp/fib_worker.js: -------------------------------------------------------------------------------- 1 | const {parentPort} = require("worker_threads"); 2 | 3 | parentPort.on("message", data => { 4 | const nums = data.nums; 5 | for (let i = data.start; i < data.stop; i++) { 6 | const n = nums[i]; 7 | const res = fibonnaci(n); 8 | Atomics.store(nums, i, res); // thread-safe 9 | parentPort.postMessage({num: n, fib: res}); 10 | } 11 | }) 12 | 13 | function fibonnaci(num) { 14 | if (num === 0) { 15 | return 0; 16 | } else if (num === 1) { 17 | return 1; 18 | } else { 19 | return fibonnaci(num - 1) + fibonnaci(num - 2); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /tmp/haskell.txt: -------------------------------------------------------------------------------- 1 | Haskell contents. -------------------------------------------------------------------------------- /tmp/hello_world.txt: -------------------------------------------------------------------------------- 1 | Hello World! -------------------------------------------------------------------------------- /tmp/notes.txt: -------------------------------------------------------------------------------- 1 | hello 2 | go 3 | -------------------------------------------------------------------------------- /tmp/worker.js: -------------------------------------------------------------------------------- 1 | const {parentPort} = require("worker_threads"); 2 | 3 | parentPort.on("message", data => { 4 | parentPort.postMessage({num: data.num, fib: getFib(data.num)}); 5 | }); 6 | 7 | function getFib(num) { 8 | if (num === 0) { 9 | return 0; 10 | } 11 | else if (num === 1) { 12 | return 1; 13 | } 14 | else { 15 | return getFib(num - 1) + getFib(num - 2); 16 | } 17 | } 18 | --------------------------------------------------------------------------------