{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The NAND++ Programming language\n", "\n", "_Version: 0.2_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The NAND++ programming language was designed to accompany the upcoming book [\"Introduction to Theoretical Computer Science\"](http://introtcs.org). This is an appendix to this book, which is also available online as a Jupyter notebook in the [boazbk/nandnotebooks](https://github.com/boazbk/nandnotebooks) on Github. You can also try the [live binder version](https://hub.mybinder.org/user/boazbk-nandnotebooks-221ipnov/notebooks/NANDpp_language.ipynb).\n", "\n", "The NAND++ programming language is defined in [Chapter 6: \"Loops and Infinity\"](https://introtcs.netlify.com/public/lec_06_loops.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The NAND programming language we saw before corresponds to _non uniform_, _finite_ computation.\n", "\n", "NAND++ captures uniform computation and is equivalent to Turing Machines.\n", "\n", "One way to think about NAND++ is\n", "\n", "$$NAND++ = NAND \\;+\\; \\text{loops} \\;+\\; \\text{arrays}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Enhanced NAND++\n", "\n", "We start by describing \"enhanced NAND++ programs\", and later we will describe \"vanilla\" or \"standard\" NAND++.\n", "\n", "Enhanced NAND++ programs have the following form: every line is either of the form\n", "\n", "`foo = NAND(bar,blah)`\n", "\n", "or\n", "\n", "`i += foo`\n", "\n", "or \n", "\n", "`i -= foo`\n", "\n", "where `foo` is a variable identifier that is either a _scalar variable_, which is a sequence of letters, numbers and underscopres, or an _array element_, which starts with a capital letter, and ends with `[i]`\n", "\n", "We have a special variable `loop`. If `loop` is set to $1$ at the end of the program then execution goes back to the beginning.\n", "\n", "We have the special input and output arrays `X[.]` and `Y[.]` but because their length is not fixed in advance, we also have `Xvalid[.]` and `Yvalid[.]` arrays. The input is `X[` $0$ `]` , ... , `X[` $n-1$ `]` where $n$ is the smallest integer such that `Xvalid[` $n$ `]` $=0$. The output is `Y[` $0$ `]` , ..., `Y[` $m-1$ `]` where $m$ is the smallest integer such that `Yvalid[` $m$ `]` $=0$.\n", "\n", "The default value of every variable in NAND++ is zero." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ignore in first read: utility code\n", "\n", "We use some utility code, which you can safely ignore in first read, to allow us to write NAND++ code in Python" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# utility code \n", "%run \"NAND programming language.ipynb\"\n", "from IPython.display import clear_output\n", "clear_output()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Ignore this utility function in first and even second and third read\n", "import inspect\n", "import ast\n", "import astor\n", "\n", "def noop(f):\n", " return f;\n", "\n", "def runwithstate(f):\n", " \"\"\"Modify a function f to take and return an argument state and make all names relative to state.\"\"\"\n", " tree = ast.parse(inspect.getsource(f))\n", " tmp = ast.parse(\"def _temp(state):\\n pass\\n\").body[0]\n", " args = tmp.args\n", " name = tmp.name\n", " tree.body[0].args = args\n", " tree.body[0].name = name\n", " tree.body[0].decorator_list = []\n", " \n", "\n", " class AddState(ast.NodeTransformer):\n", " def visit_Name(self, node: ast.Name):\n", " if node.id == \"enandpp\": return ast.Name(id=\"noop\", ctx=Load())\n", " new_node = ast.Attribute(ast.copy_location(ast.Name('state', ast.Load()), node), node.id,\n", " ast.copy_location(ast.Load(), node))\n", " return ast.copy_location(new_node, node)\n", " \n", " tree = AddState().visit(tree)\n", " tree.body[0].body = tree.body[0].body + [ast.parse(\"return state\")]\n", " tree = ast.fix_missing_locations(tree)\n", " src = astor.to_source(tree)\n", " # print(src)\n", " exec(src,globals())\n", " _temp.original_func = f\n", " return _temp\n", "\n", " \n", "def enandpp(f):\n", " g = runwithstate(f)\n", " def _temp1(X):\n", " nonlocal g\n", " return ENANDPPEVAL(g,X)\n", " _temp1.original_func = f\n", " _temp1.transformed_func = g\n", " return _temp1" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# ignore utility class in first and even second or third read\n", "\n", "from collections import defaultdict\n", "class NANDPPstate:\n", " \"\"\"State of a NAND++ program.\"\"\"\n", " \n", " def __init__(self):\n", " self.scalars = defaultdict(int)\n", " self.arrays = defaultdict(lambda: defaultdict(int))\n", " # eventually should make self.i non-negative integer type\n", " \n", " def __getattr__(self,var):\n", " g = globals()\n", " if var in g and callable(g[var]): return g[var]\n", " if var[0].isupper():\n", " return self.arrays[var]\n", " else:\n", " return self.scalars[var]" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def ENANDPPEVAL(f,X):\n", " \"\"\"Evaluate an enhanced NAND++ function on input X\"\"\"\n", " s = NANDPPstate()\n", " for i in range(len(X)):\n", " s.X[i] = X[i]\n", " s.Xvalid[i] = 1\n", " while True:\n", " s = f(s)\n", " if not s.loop: break\n", " res = []\n", " i = 0\n", " while s.Yvalid[i]: \n", " res += [s.Y[i]]\n", " i+= 1\n", " return res" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def rreplace(s, old, new, occurrence=1): # from stackoverflow\n", " li = s.rsplit(old, occurrence)\n", " return new.join(li)\n", "\n", " \n", " \n", "def ENANDPPcode(P):\n", " \"\"\"Return ENAND++ code of given function\"\"\"\n", " \n", " code = ''\n", " counter = 0\n", " \n", " class CodeENANDPPcounter:\n", " def __init__(self,name=\"i\"): \n", " self.name = name\n", " \n", " def __iadd__(self,var):\n", " nonlocal code\n", " code += f'\\ni += {var}'\n", " return self\n", " \n", " def __isub__(self,var):\n", " nonlocal code\n", " code += f'\\ni -= {var}'\n", " return self\n", " \n", " def __str__(self): return self.name\n", " \n", " class CodeNANDPPstate:\n", " \n", " \n", " def __getattribute__(self,var):\n", " # print(f\"getting {var}\")\n", " if var=='i': return CodeENANDPPcounter()\n", " g = globals()\n", " if var in g and callable(g[var]): return g[var]\n", " if var[0].isupper(): \n", " class Temp:\n", " def __getitem__(self,k): return f\"{var}[{str(k)}]\"\n", " def __setitem__(s,k,v): setattr(self,f\"{var}[{str(k)}]\",v) \n", " return Temp()\n", " return var\n", " \n", " def __init__(self):\n", " pass\n", " \n", " def __setattr__(self,var,val):\n", " nonlocal code\n", " if var=='i': return\n", " if code.find(val)==-1:\n", " code += f'\\n{var} = {val}'\n", " else:\n", " code = rreplace(code,val,var)\n", " \n", " s = CodeNANDPPstate()\n", " \n", " def myNAND(a,b):\n", " nonlocal code, counter\n", " var = f'temp_{counter}'\n", " counter += 1\n", " code += f'\\n{var} = NAND({a},{b})'\n", " return var\n", " \n", " \n", " \n", " \n", " \n", " s = runwith(lambda : P.transformed_func(s),\"NAND\",myNAND) \n", " \n", " return code" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Our first NAND++ program\n", "\n", "Here is an enhanced NAND++ program to increment a number:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "@enandpp\n", "def inc():\n", " carry = IF(started,carry,one(started))\n", " started = one(started)\n", " Y[i] = XOR(X[i],carry)\n", " carry = AND(X[i],carry)\n", " Yvalid[i] = one(started)\n", " loop = COPY(Xvalid[i])\n", " i += loop" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 0, 1, 0, 1, 0]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "inc([1,1,0,0,1])" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "temp_0 = NAND(started,started)\n", "temp_1 = NAND(started,temp_0)\n", "temp_2 = NAND(started,started)\n", "temp_3 = NAND(temp_1,temp_2)\n", "temp_4 = NAND(carry,started)\n", "carry = NAND(temp_3,temp_4)\n", "temp_6 = NAND(started,started)\n", "started = NAND(started,temp_6)\n", "temp_8 = NAND(X[i],carry)\n", "temp_9 = NAND(X[i],temp_8)\n", "temp_10 = NAND(carry,temp_8)\n", "Y[i] = NAND(temp_9,temp_10)\n", "temp_12 = NAND(X[i],carry)\n", "carry = NAND(temp_12,temp_12)\n", "temp_14 = NAND(started,started)\n", "Yvalid[i] = NAND(started,temp_14)\n", "temp_16 = NAND(Xvalid[i],Xvalid[i])\n", "loop = NAND(temp_16,temp_16)\n", "i += loop\n" ] } ], "source": [ "print(ENANDPPcode(inc))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And here is an enhanced NAND++ program to compute the `XOR` function on unbounded length inputs (it uses `XOR` on two variables as a subroutine):" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "@enandpp\n", "def UXOR():\n", " Yvalid[0] = one(X[0])\n", " Y[0] = XOR(X[i],Y[0])\n", " loop = Xvalid[i]\n", " i += Xvalid[i]" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "UXOR([1,1,0,0,1,1])" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "temp_0 = NAND(X[0],X[0])\n", "Yvalid[0] = NAND(X[0],temp_0)\n", "temp_2 = NAND(X[i],Y[0])\n", "temp_3 = NAND(X[i],temp_2)\n", "temp_4 = NAND(Y[0],temp_2)\n", "Y[0] = NAND(temp_3,temp_4)\n", "loop = Xvalid[i]\n", "i += Xvalid[i]\n" ] } ], "source": [ "print(ENANDPPcode(UXOR))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## \"Vanilla\" NAND++\n", "\n", "In \"vanilla\" NAND++ we do not have the commands `i += foo` and `i -= foo` but rather `i` travels obliviously according to the sequence $0,1,0,1,2,1,0,1,2,3,2,1,0,1,2,\\ldots$" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 0, 1, 2, 1, 0, 1, 2, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def index():\n", " \"\"\"Generator for the values of i in the NAND++ sequence\"\"\"\n", " i = 0\n", " last = 0\n", " direction = 1\n", " while True:\n", " yield i\n", " i += direction\n", " if i> last: \n", " direction = -1\n", " last = i\n", " if i==0: direction = +1\n", " \n", "a = index()\n", "[next(a) for i in range(20)]" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "def NANDPPEVAL(f,X):\n", " \"\"\"Evaluate a NAND++ function on input X\"\"\"\n", " s = NANDPPstate() # intialize state\n", " \n", " # copy input:\n", " for i in range(len(X)): \n", " s.X[i] = X[i]\n", " s.Xvalid[i] = 1\n", " \n", " # main loop:\n", " for i in index(): \n", " s.i = i\n", " s = f(s)\n", " if not s.loop: break\n", " \n", " # copy output:\n", " res = [] \n", " i = 0\n", " while s.Yvalid[i]: \n", " res += [s.Y[i]]\n", " i+= 1\n", " return res\n", "\n", "\n", "\n", "def nandpp(f):\n", " \"\"\"Modify python code to obtain NAND++ program\"\"\"\n", " g = runwithstate(f)\n", " def _temp1(X):\n", " return NANDPPEVAL(g,X)\n", " _temp1.original_func = f\n", " _temp1.transformed_func = g\n", " return _temp1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is the increment function in vanilla NAND++. Note that we need to keep track of an Array `Visited` to make sure we only add the carry once per location." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "@nandpp\n", "def inc():\n", " carry = IF(started,carry,one(started))\n", " started = one(started)\n", " Y[i] = IF(Visited[i],Y[i],XOR(X[i],carry))\n", " Visited[i] = one(started)\n", " carry = AND(X[i],carry)\n", " Yvalid[i] = one(started)\n", " loop = Xvalid[i]" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 0, 1, 1, 1, 0]" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "inc([1,1,0,1,1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And here is the \"vanilla NAND++\" version of XOR:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "@nandpp\n", "def vuXOR():\n", " Yvalid[0] = one(X[0])\n", " Y[0] = IF(Visited[i],Y[0],XOR(X[i],Y[0]))\n", " Visited[i] = one(X[0])\n", " loop = Xvalid[i]" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "vuXOR([1,0,0,1,0,1,1])" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def NANDPPcode(P):\n", " \"\"\"Return NAND++ code of given function\"\"\"\n", " \n", " code = ''\n", " counter = 0\n", " \n", " \n", " class CodeNANDPPstate:\n", " \n", " \n", " def __getattribute__(self,var):\n", " # print(f\"getting {var}\")\n", " g = globals()\n", " if var in g and callable(g[var]): return g[var]\n", " if var[0].isupper(): \n", " class Temp:\n", " def __getitem__(self,k): return var+\"[i]\"\n", " def __setitem__(s,k,v): \n", " setattr(self,var+\"[i]\",v) \n", " return Temp()\n", " return var\n", " \n", " def __init__(self):\n", " pass\n", " \n", " def __setattr__(self,var,val):\n", " nonlocal code\n", " # print(f\"setting {var} to {val}\")\n", " if code.find(val)==-1:\n", " code += f'\\n{var} = {val}'\n", " else:\n", " code = rreplace(code,val,var)\n", " \n", " s = CodeNANDPPstate()\n", " \n", " def myNAND(a,b):\n", " nonlocal code, counter\n", " var = f'temp_{counter}'\n", " counter += 1\n", " code += f'\\n{var} = NAND({a},{b})'\n", " return var\n", " \n", " \n", " \n", " \n", " \n", " s = runwith(lambda : P.transformed_func(s),\"NAND\",myNAND) \n", " \n", " return code\n", "\n", "\n", "# utility code - replace string from right, taken from stackoverflow\n", "def rreplace(s, old, new, occurrence=1): \n", " li = s.rsplit(old, occurrence)\n", " return new.join(li)\n", "\n" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "temp_0 = NAND(started,started)\n", "temp_1 = NAND(started,temp_0)\n", "temp_2 = NAND(started,started)\n", "temp_3 = NAND(temp_1,temp_2)\n", "temp_4 = NAND(carry,started)\n", "carry = NAND(temp_3,temp_4)\n", "temp_6 = NAND(started,started)\n", "started = NAND(started,temp_6)\n", "temp_8 = NAND(X[i],carry)\n", "temp_9 = NAND(X[i],temp_8)\n", "temp_10 = NAND(carry,temp_8)\n", "temp_11 = NAND(temp_9,temp_10)\n", "temp_12 = NAND(Visited[i],Visited[i])\n", "temp_13 = NAND(temp_11,temp_12)\n", "temp_14 = NAND(Y[i],Visited[i])\n", "Y[i] = NAND(temp_13,temp_14)\n", "temp_16 = NAND(started,started)\n", "Visited[i] = NAND(started,temp_16)\n", "temp_18 = NAND(X[i],carry)\n", "carry = NAND(temp_18,temp_18)\n", "temp_20 = NAND(started,started)\n", "Yvalid[i] = NAND(started,temp_20)\n", "loop = Xvalid[i]\n" ] } ], "source": [ "print(NANDPPcode(inc))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tranforming Enhanced NAND++ to NAND++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Eventually we will have here code to automatically transform an enhanced NAND++ program into a NAND++ program.\n", "At the moment, let us just give the high level ideas. See [Chapter 6 in the book](http://introtcs.org/public/lec_06_loops.html) for more details.\n", "\n", "\n", "To transform an enhanced NAND++ program to a standard NAND++ program we do the following:\n", "\n", "1. We make all our operations \"guarded\" in the sense that there is a special variable `noop` such that if `noop` equals $1$ then we do not make any writes.\n", "\n", "2. We use a `Visited` array to keep track of all locations we visited, and use that to keep track of an `decreasing` variable that is equal to $1$ if and only the value of `i` in the next step will be one smaller.\n", "\n", "3. If we have an operation of the form `i += foo` or `i -= foo` at line $\\ell$ then we replace it with lines of code that do the following: \n", "\n", " a. (Guarded) set `temp_`$\\ell$ ` = foo`\n", " \n", " b. (Unguarded) If `Waitingline_` $\\ell$ and `Restart[i]` : set `noop`=$0$ if `increasing` is equal to `wait_increasing`. (Otherwise `noop` stays the same.)\n", " \n", " c. (Guarded) set `Restart[i]` to $1$.\n", " \n", " d. (Guarded) set `Waitingline_` $\\ell$ to $1$.\n", " \n", " e. (Guarded) set `wait_increasing` to $1$ if the operation is `i += foo` and to $0$ if it's `i -= foo`\n", " \n", " f. (Guarded) set `noop = temp_` $\\ell$\n", " \n", " g. (Unguarded) set `temp_` $\\ell$ `= 0`\n", " \n", " h. (Guarded) set `Restart[i]` to $0$.\n", " \n", " i. (Guarded) set `Waitingline_`$\\ell$ to $0$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }