X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=07-interpreter%2Fmachine-code.ipynb;h=69cd43ff07b09f91c79e0082aa7cd6c0aa40b2a1;hb=41af8166480ab766f9f2b9e8ce9c7666ae9b02b9;hp=cb8e18600d40cbfd97a6eb768e2ebb982478a3d4;hpb=009976af40a98c5cb0151fca6525c75f12222373;p=ou-summer-of-code-2017.git diff --git a/07-interpreter/machine-code.ipynb b/07-interpreter/machine-code.ipynb index cb8e186..69cd43f 100644 --- a/07-interpreter/machine-code.ipynb +++ b/07-interpreter/machine-code.ipynb @@ -1,9 +1,43 @@ { "cells": [ { - "cell_type": "code", - "execution_count": 111, + "cell_type": "markdown", "metadata": {}, + "source": [ + "# Machine interpreter\n", + "\n", + "## Instructions\n", + "\n", + "Three registers, `a`, `b`, and `c`. \n", + "Program counter, `pc`.\n", + "Unlimited memory in numbered addresses, completely separate from program storage.\n", + "\n", + "Each register and memory location can hold 8-byte integers (Java `long`s).\n", + "\n", + "Machine carries out the instruction at location `pc`. After it's executed, `pc` increments by 1.\n", + "\n", + "`jmp` and `jpz` override this normal change in `pc`.\n", + "\n", + "| Instruction | Description |\n", + "|:------------|:------------|\n", + "| `inc r` | increment contents of register `r` |\n", + "| `dec r` | decrement contents of register `r` |\n", + "| `set r i` | set contents of register `r` to literal value `i` |\n", + "| `cpy r s` | copy contents of register `r` into register `s` | \n", + "| `sto r m` | store contents of register `r` into memory location `m` |\n", + "| `ld r m` | load contents of memory location `m` into register `r` |\n", + "| `jmp i` | jump to instruction `i` places forward |\n", + "| `jpz r i` | jump to instruction `i` places forward if
register `r` contains zero, otherwise continue to next instruction |\n", + "\n", + "For `jmp` and `jpz`, `i` is relative to the current instruction. `i` can be negative to jump to earlier places in the program. `i`=1 is a no-op, `i`=0 causes an infinite loop." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def new_machine():\n", @@ -16,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 193, + "execution_count": 2, "metadata": { "collapsed": true }, @@ -30,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 112, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -43,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 113, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -56,7 +90,7 @@ }, { "cell_type": "code", - "execution_count": 114, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -68,7 +102,7 @@ }, { "cell_type": "code", - "execution_count": 115, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -83,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 116, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -96,7 +130,7 @@ }, { "cell_type": "code", - "execution_count": 117, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -109,7 +143,7 @@ }, { "cell_type": "code", - "execution_count": 129, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -122,7 +156,7 @@ }, { "cell_type": "code", - "execution_count": 130, + "execution_count": 10, "metadata": { "collapsed": true }, @@ -138,8 +172,10 @@ }, { "cell_type": "code", - "execution_count": 131, - "metadata": {}, + "execution_count": 11, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "instruction_table = {'inc': inc, 'dec': dec, 'jmp': jmp,\n", @@ -150,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -168,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 122, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -177,7 +213,7 @@ "{'a': 2, 'b': 1, 'c': 0, 'instructions': [], 'pc': 3}" ] }, - "execution_count": 122, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -193,7 +229,7 @@ }, { "cell_type": "code", - "execution_count": 123, + "execution_count": 14, "metadata": { "collapsed": true }, @@ -205,19 +241,110 @@ }, { "cell_type": "code", - "execution_count": 124, + "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def program_from_listing(listing, machine):\n", - " program_from_instructions([i for i in listing.split('\\n') if i], machine)" + " labelled_instructions = [i.strip() for i in listing.split('\\n') \n", + " if i.strip() \n", + " if not i.strip().startswith('#')]\n", + " instructions = replace_labels(labelled_instructions)\n", + " program_from_instructions(instructions, machine)" ] }, { "cell_type": "code", - "execution_count": 156, + "execution_count": 16, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def replace_labels(listing):\n", + " locations = {}\n", + " for n, i in enumerate(listing):\n", + " if ':' in i:\n", + " locations[i.split(':')[0]] = n\n", + "\n", + " unlabelled_listing = []\n", + " for n, i in enumerate(listing):\n", + " instr = i.split()\n", + " if ':' in i:\n", + " instr = i.split(':')[1].split()\n", + " else:\n", + " instr = i.split()\n", + " terms = []\n", + " for term in instr:\n", + " if term in locations:\n", + " terms += [str(locations[term] - n)]\n", + " else:\n", + " terms += [term]\n", + " transformed_instr = ' '.join(terms)\n", + " unlabelled_listing += [transformed_instr]\n", + " \n", + " return unlabelled_listing " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['inc', 'a']" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "'fred: inc a'.split(':')[1].split()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "set a 10\n", + "dec a\n", + "inc b\n", + "sto b 1\n", + "jpz a 2\n", + "jmp -4\n" + ] + } + ], + "source": [ + "program = \"\"\"\n", + " set a 10\n", + " # comment line\n", + " \n", + "loop: dec a\n", + " inc b\n", + " sto b 1\n", + " jpz a 2\n", + " jmp loop\n", + "\"\"\"\n", + "labelled_instructions = [i.strip() for i in program.split('\\n') if i.strip() if not i.strip().startswith('#')]\n", + "instructions = replace_labels(labelled_instructions)\n", + "print('\\n'.join(instructions))" + ] + }, + { + "cell_type": "code", + "execution_count": 19, "metadata": { "collapsed": true }, @@ -235,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": 146, + "execution_count": 20, "metadata": { "collapsed": true }, @@ -250,7 +377,7 @@ }, { "cell_type": "code", - "execution_count": 127, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -266,7 +393,7 @@ " 'pc': 4}" ] }, - "execution_count": 127, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -283,26 +410,26 @@ }, { "cell_type": "code", - "execution_count": 143, + "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'c': 20,\n", + "{'a': 0,\n", + " 1: 10,\n", + " 'pc': 6,\n", + " 'c': 20,\n", " 'instructions': [(, ['a', 10]),\n", " (, ['a']),\n", " (, ['b']),\n", " (, ['b', 1]),\n", " (, ['a', 2]),\n", " (, [-4])],\n", - " 1: 10,\n", - " 'b': 10,\n", - " 'pc': 6,\n", - " 'a': 0}" + " 'b': 10}" ] }, - "execution_count": 143, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -324,7 +451,45 @@ }, { "cell_type": "code", - "execution_count": 159, + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a': 0,\n", + " 1: 10,\n", + " 'pc': 6,\n", + " 'c': 20,\n", + " 'instructions': [(, ['a', 10]),\n", + " (, ['a']),\n", + " (, ['b']),\n", + " (, ['b', 1]),\n", + " (, ['a', 2]),\n", + " (, [-4])],\n", + " 'b': 10}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "program = \"\"\"\n", + " set a 10\n", + "loop: dec a\n", + " inc b\n", + " sto b 1\n", + " jpz a 2\n", + " jmp loop\n", + "\"\"\"\n", + "execute(program, initial_state={'c': 20})" + ] + }, + { + "cell_type": "code", + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -345,7 +510,7 @@ " 'pc': 10}" ] }, - "execution_count": 159, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -370,38 +535,599 @@ }, { "cell_type": "code", - "execution_count": 196, + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'10: 10, a: 0, b: 0, c: 5, pc: 11'" + "{'a': 0,\n", + " 'b': 1,\n", + " 'c': 5,\n", + " 'instructions': [(, ['c', 'a']),\n", + " (, ['b', 0]),\n", + " (, ['a']),\n", + " (, ['b', 3]),\n", + " (, ['b']),\n", + " (, [2]),\n", + " (, ['b']),\n", + " (, ['a', 2]),\n", + " (, [-6])],\n", + " 'pc': 9}" ] }, - "execution_count": 196, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "# b holds parity of number in c: (c % 2)\n", "program = \"\"\"\n", - "sto a 10\n", - "set c 0\n", - "set b 0\n", - "dec a\n", - "jpz b 4\n", - "dec b\n", - "inc c\n", - "jmp 2\n", - "inc b\n", - "jpz a 2\n", - "jmp -7\n", + " cpy c a\n", + " set b 0\n", + "loop: dec a\n", + " jpz b odd\n", + " dec b\n", + " jmp end\n", + "odd: inc b\n", + "end: jpz a 2\n", + " jmp loop\n", "\"\"\"\n", "# m = new_machine()\n", "# program_from_listing(program, m)\n", "# run(m)\n", - "show_machine(execute(program, initial_state={'a': 10}))" + "execute(program, initial_state={'c': 5})" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'1: 8, a: 0, b: 1, c: 8, pc: 11'" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# c holds floor(a/2)\n", + "program = \"\"\"\n", + " set c 0\n", + " set b 0\n", + "loop: dec a\n", + " jpz b odd\n", + " dec b\n", + " inc c\n", + " jmp end\n", + "odd: inc b\n", + "end: jpz a 2\n", + " jmp loop\n", + " sto c 1\n", + "\"\"\"\n", + "# m = new_machine()\n", + "# program_from_listing(program, m)\n", + "# run(m)\n", + "show_machine(execute(program, initial_state={'a': 17}))" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'a: 4, b: 0, c: 12, pc: 9'" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# c holds a * 3\n", + "program = \"\"\"\n", + " set c 0\n", + " cpy a b\n", + " # start of main loop\n", + "loop: jpz b end\n", + " dec b\n", + " inc c\n", + " inc c\n", + " inc c\n", + " jmp loop\n", + " \n", + " # end of program \n", + " \n", + "end: jmp 1\n", + "\"\"\"\n", + "# m = new_machine()\n", + "# program_from_listing(program, m)\n", + "# run(m)\n", + "show_machine(execute(program, initial_state={'a': 4}))" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'1: 0, a: 0, b: 0, c: 27, pc: 11'" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# c holds a * b\n", + "program = \"\"\"\n", + " set c 0\n", + " sto a 1\n", + "loop: jpz b end \n", + " dec b\n", + " ld a 1\n", + "smul: jpz a emul\n", + " inc c\n", + " dec a\n", + " jmp smul\n", + "emul: jmp loop \n", + " \n", + "end: sto a 1\n", + "\"\"\"\n", + "m = new_machine()\n", + "program_from_listing(program, m)\n", + "run(m)\n", + "show_machine(execute(program, initial_state={'a': 9, 'b': 3}))" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "set c 0\n", + "sto a 1\n", + "jpz b 8\n", + "dec b\n", + "ld a 1\n", + "jpz a 4\n", + "inc c\n", + "dec a\n", + "jmp -3\n", + "jmp -7\n", + "sto a 1\n" + ] + } + ], + "source": [ + "labelled_instructions = [i.strip() for i in program.split('\\n') if i.strip() if not i.strip().startswith('#')]\n", + "instructions = replace_labels(labelled_instructions)\n", + "print('\\n'.join(instructions))" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'1: 0, a: 0, b: 0, c: 10, pc: 13'" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# c holds a * b\n", + "program = \"\"\"\n", + " set a 2\n", + " set b 5\n", + " set c 0\n", + " sto a 1\n", + "loop: jpz b end \n", + " dec b\n", + " ld a 1\n", + "smul: jpz a emul\n", + " inc c\n", + " dec a\n", + " jmp smul\n", + "emul: jmp loop \n", + " \n", + "end: sto a 1\n", + "\"\"\"\n", + "m = new_machine()\n", + "program_from_listing(program, m)\n", + "run(m)\n", + "show_machine(execute(program, initial_state={'a': 9, 'b': 3}))" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "set a 2\n", + "set b 5\n", + "set c 0\n", + "sto a 1\n", + "jpz b 8\n", + "dec b\n", + "ld a 1\n", + "jpz a 4\n", + "inc c\n", + "dec a\n", + "jmp -3\n", + "jmp -7\n", + "sto a 1\n" + ] + } + ], + "source": [ + "labelled_instructions = [i.strip() for i in program.split('\\n') \n", + " if i.strip() \n", + " if not i.strip().startswith('#')]\n", + "instructions = replace_labels(labelled_instructions)\n", + "print('\\n'.join(instructions))" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'1: 0, a: 52, b: 0, c: 0, pc: 48'" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Collatz. a initially holds value, but location 1 is used to store the current value as we're going along.\n", + "# Location 2 holds number of steps taken\n", + "# Location 3 holds max value reached\n", + "program = \"\"\"\n", + " sto a 1\n", + " \n", + " set b 0\n", + " \n", + " # if a is one, finish, \n", + "main: dec a\n", + " jpz a end\n", + " inc a\n", + " \n", + " # find parity of a\n", + " cpy a c\n", + " set b 0\n", + "prty: dec c\n", + " jpz b odd\n", + " dec b\n", + " jmp prte\n", + "odd: inc b\n", + "prte: jpz c 2\n", + " jmp prty\n", + " \n", + " # b == 0 means a even; b == 1 means a odd\n", + " jpz b isev\n", + "\n", + " # c holds a * 3 + 1\n", + " cpy a b\n", + "mul: jpz b emul\n", + " dec b\n", + " inc c\n", + " inc c\n", + " inc c\n", + " jmp mul\n", + "emul: inc c\n", + " cpy c a\n", + " jmp fin\n", + " \n", + " \n", + "isev: set c 0\n", + " set b 0\n", + "hlvl: dec a\n", + " jpz b oddh\n", + " dec b\n", + " inc c\n", + " jmp endh\n", + "oddh: inc b\n", + "endh: jpz a 2\n", + " jmp hlvl\n", + " cpy c a\n", + "\n", + "fin: cpy a b\n", + " ld c 1\n", + "maxc: jpz c this\n", + " jpz b othr\n", + " dec b\n", + " dec c\n", + " jmp maxc\n", + "this: sto a 1\n", + "othr: jmp main\n", + " \n", + " # end of program \n", + " \n", + "end: ld a 1\n", + " set c 0\n", + " sto c 1\n", + "\"\"\"\n", + "# m = new_machine()\n", + "# program_from_listing(program, m)\n", + "# run(m)\n", + "show_machine(execute(program, initial_state={'a': 7}))" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "40" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "13*3+1" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def max_collatz(start):\n", + " mc = start\n", + " i = start\n", + " while i != 1:\n", + " if i % 2 == 0:\n", + " i = i // 2\n", + " else:\n", + " i = 3 * i + 1\n", + " if i > mc:\n", + " mc = i\n", + " return mc" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "52" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "max_collatz(7)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(250504, 937)" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "max([(max_collatz(i), i) for i in range(1, 1000)])" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, 0, 1),\n", + " (2, 0, 2),\n", + " (3, 0, 16),\n", + " (4, 0, 4),\n", + " (5, 0, 16),\n", + " (6, 0, 16),\n", + " (7, 0, 52),\n", + " (8, 0, 8),\n", + " (9, 0, 52),\n", + " (10, 0, 16),\n", + " (11, 0, 52),\n", + " (12, 0, 16),\n", + " (13, 0, 40),\n", + " (14, 0, 52),\n", + " (15, 0, 160),\n", + " (16, 0, 16),\n", + " (17, 0, 52),\n", + " (18, 0, 52),\n", + " (19, 0, 88),\n", + " (20, 0, 20),\n", + " (21, 0, 64),\n", + " (22, 0, 52),\n", + " (23, 0, 160),\n", + " (24, 0, 24),\n", + " (25, 0, 88),\n", + " (26, 0, 40),\n", + " (27, 0, 9232),\n", + " (28, 0, 52),\n", + " (29, 0, 88),\n", + " (30, 0, 160),\n", + " (31, 0, 9232),\n", + " (32, 0, 32),\n", + " (33, 0, 100),\n", + " (34, 0, 52),\n", + " (35, 0, 160),\n", + " (36, 0, 52),\n", + " (37, 0, 112),\n", + " (38, 0, 88),\n", + " (39, 0, 304),\n", + " (40, 0, 40),\n", + " (41, 0, 9232),\n", + " (42, 0, 64),\n", + " (43, 0, 196),\n", + " (44, 0, 52),\n", + " (45, 0, 136),\n", + " (46, 0, 160),\n", + " (47, 0, 9232),\n", + " (48, 0, 48),\n", + " (49, 0, 148),\n", + " (50, 0, 88),\n", + " (51, 0, 232),\n", + " (52, 0, 52),\n", + " (53, 0, 160),\n", + " (54, 0, 9232),\n", + " (55, 0, 9232),\n", + " (56, 0, 56),\n", + " (57, 0, 196),\n", + " (58, 0, 88),\n", + " (59, 0, 304),\n", + " (60, 0, 160),\n", + " (61, 0, 184),\n", + " (62, 0, 9232),\n", + " (63, 0, 9232),\n", + " (64, 0, 64),\n", + " (65, 0, 196),\n", + " (66, 0, 100),\n", + " (67, 0, 304),\n", + " (68, 0, 68),\n", + " (69, 0, 208),\n", + " (70, 0, 160),\n", + " (71, 0, 9232),\n", + " (72, 0, 72),\n", + " (73, 0, 9232),\n", + " (74, 0, 112),\n", + " (75, 0, 340),\n", + " (76, 0, 88),\n", + " (77, 0, 232),\n", + " (78, 0, 304),\n", + " (79, 0, 808),\n", + " (80, 0, 80),\n", + " (81, 0, 244),\n", + " (82, 0, 9232),\n", + " (83, 0, 9232),\n", + " (84, 0, 84),\n", + " (85, 0, 256),\n", + " (86, 0, 196),\n", + " (87, 0, 592),\n", + " (88, 0, 88),\n", + " (89, 0, 304),\n", + " (90, 0, 136),\n", + " (91, 0, 9232),\n", + " (92, 0, 160),\n", + " (93, 0, 280),\n", + " (94, 0, 9232),\n", + " (95, 0, 9232),\n", + " (96, 0, 96),\n", + " (97, 0, 9232),\n", + " (98, 0, 148),\n", + " (99, 0, 448)]" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ans = []\n", + "for i in range(1, 100):\n", + " m = execute(program, initial_state={'a': i})\n", + " c = max_collatz(i)\n", + " if m[1] != c:\n", + " ans += [(i, m[1], c)]\n", + "ans" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'1: 0, a: 250504, b: 0, c: 0, pc: 48'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_machine(execute(program, initial_state={'a': 937}))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# labelled_instructions = [i.strip() for i in program.split('\\n') \n", + "# if i.strip() \n", + "# if not i.strip().startswith('#')]\n", + "# instructions = replace_labels(labelled_instructions)\n", + "# print('\\n'.join(instructions))\n", + "# open('07-program.txt', 'w').write('\\n'.join(instructions))" ] }, {