Add files via upload

This commit is contained in:
Peter Norvig 2018-01-12 21:30:50 -08:00 committed by GitHub
parent c11f19c640
commit 2714d19d9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -8,20 +8,18 @@
"\n", "\n",
"# BASIC Interpreter\n", "# BASIC Interpreter\n",
"\n", "\n",
"[Years ago](http://norvig.com/lispy.html), I showed how to write an Interpreter for a dialect of Lisp. Some readers appreciated it, and some asked about an interpreter for a language that isn't just a bunch of parentheses. In 2014 I saw a [celebration](http://time.com/69316/basic/) of the 50th anniversary of the 1964 [Dartmouth BASIC](http://web.archive.org/web/20120716185629/http://www.bitsavers.org/pdf/dartmouth/BASIC_Oct64.pdf) interpreter, and thought that I could show how to implement such an interpreter. I never quite finished in 2014, but in 2017 I rediscovered my unfinished file and completed it. For those of you unfamiliar with BASIC, here is a sample program:" "[Years ago](http://norvig.com/lispy.html), I showed how to write an Interpreter for a dialect of Lisp. Some readers appreciated it, and some asked about an interpreter for a language that isn't just a bunch of parentheses. In 2014 I saw a [celebration](http://time.com/69316/basic/) of the 50th anniversary of the 1964 [Dartmouth BASIC](http://web.archive.org/web/20120716185629/http://www.bitsavers.org/pdf/dartmouth/BASIC_Oct64.pdf) interpreter, and thought that I could show how to implement such an interpreter. I never quite finished in 2014, but now it is 2017, I rediscovered this unfinished file, and completed it. For those of you unfamiliar with BASIC, here is a sample program:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"program = '''\n", "program = '''\n",
"10 REM POWER TABLE\n", "10 REM POWER TABLE\n",
"11 DATA 8, 4\n", "11 DATA 8, 4\n",
"15 READ N0, P0\n", "15 READ N0, P0\n",
"20 PRINT \"N\",\n", "20 PRINT \"N\",\n",
"25 FOR P = 2 to P0\n", "25 FOR P = 2 to P0\n",
@ -47,8 +45,9 @@
"source": [ "source": [
"Of course I don't have to build everything from scratch in assembly language, and I don't have to worry about every byte of storage, like [Kemeny](http://www.dartmouth.edu/basicfifty/basic.html), [Gates](http://www.pagetable.com/?p=774), and [Woz](http://www.retrothing.com/2008/07/restoring-wozs.html) did, so my job is much easier. The interpreter consists of three phases: \n", "Of course I don't have to build everything from scratch in assembly language, and I don't have to worry about every byte of storage, like [Kemeny](http://www.dartmouth.edu/basicfifty/basic.html), [Gates](http://www.pagetable.com/?p=774), and [Woz](http://www.retrothing.com/2008/07/restoring-wozs.html) did, so my job is much easier. The interpreter consists of three phases: \n",
"* **Tokenization**: breaking a text into a list of tokens, for example: `\"10 READ N\"` becomes `['10', 'READ', 'N']`.\n", "* **Tokenization**: breaking a text into a list of tokens, for example: `\"10 READ N\"` becomes `['10', 'READ', 'N']`.\n",
"* **Parsing**: building an executable representation from the tokens, so this statement becomes: `Stmt(num=10, typ='READ', args=['N'])`.\n", "* **Parsing**: building a representation from the tokens: `Stmt(num=10, typ='READ', args=['N'])`.\n",
"* **Execution**: follow the flow of the program and do what each statement says; in this case an assignment: `variables['N'] = data.popleft()`.\n", "* **Execution**: follow the flow of the program and do what each statement says; in this case the `READ` statement\n",
"has the effect of an assignment: `variables['N'] = data.popleft()`.\n",
"\n", "\n",
"\n", "\n",
"\n", "\n",
@ -60,9 +59,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 2,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"import re \n", "import re \n",
@ -73,7 +70,7 @@
" LET|READ|DATA|PRINT|GOTO|IF|FOR|NEXT|END|STOP | # keywords\n", " LET|READ|DATA|PRINT|GOTO|IF|FOR|NEXT|END|STOP | # keywords\n",
" DEF|GOSUB|RETURN|DIM|REM|TO|THEN|STEP | # more keywords\n", " DEF|GOSUB|RETURN|DIM|REM|TO|THEN|STEP | # more keywords\n",
" [A-Z]\\d? | # variable names (letter optionally followed by a digit)\n", " [A-Z]\\d? | # variable names (letter optionally followed by a digit)\n",
" \" .*? \" | # labels (strings in double quotes)\n", " \".*? \" | # labels (strings in double quotes)\n",
" <>|>=|<= | # multi-character relational operators\n", " <>|>=|<= | # multi-character relational operators\n",
" \\S # any non-space single character ''', \n", " \\S # any non-space single character ''', \n",
" re.VERBOSE).findall" " re.VERBOSE).findall"
@ -90,9 +87,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 3,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"data": { "data": {
@ -112,9 +107,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"data": { "data": {
@ -133,9 +126,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": { "metadata": {},
"collapsed": false
},
"source": [ "source": [
"That looks good. Note that my tokens are just strings; it will be the parser's job, not the tokenizer's, to recognize that `'2'` is a number and `'X'` is the name of a variable. (In some interpreters, the tokenizer makes distinctions like these.)\n", "That looks good. Note that my tokens are just strings; it will be the parser's job, not the tokenizer's, to recognize that `'2'` is a number and `'X'` is the name of a variable. (In some interpreters, the tokenizer makes distinctions like these.)\n",
"\n", "\n",
@ -151,9 +142,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 5,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"data": { "data": {
@ -180,15 +169,13 @@
"* `peek()`: returns the next token in `tokens` (without changing `tokens`), or `None` if there are no more tokens.\n", "* `peek()`: returns the next token in `tokens` (without changing `tokens`), or `None` if there are no more tokens.\n",
"* `pop()`: removes and returns the next token. \n", "* `pop()`: removes and returns the next token. \n",
"* `pop(`*string*`)`: removes and returns the next token if it is equal to the string; else return `None` and leave `tokens` unchanged.\n", "* `pop(`*string*`)`: removes and returns the next token if it is equal to the string; else return `None` and leave `tokens` unchanged.\n",
"* `pop(`*predicate*`)`: removes and returns the next token if *predicate*(*token*) is true; else return `None` and leave `tokens` unchanged." "* `pop(`*predicate*`)`: remove and return the next token if *predicate*(*token*) is true; else return `None`, leave `tokens` alone."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 6,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"tokens = [] # Global variable to hold a list of tokens\n", "tokens = [] # Global variable to hold a list of tokens\n",
@ -202,9 +189,10 @@
" return (tokens[0] if tokens else None)\n", " return (tokens[0] if tokens else None)\n",
"\n", "\n",
"def pop(constraint=None):\n", "def pop(constraint=None):\n",
" \"\"\"Remove and return the first token in `tokens`, or return None if first token fails a constraint.\n", " \"\"\"Remove and return the first token in `tokens`, or return None if token fails constraint.\n",
" `constraint` can be None, a literal string (e.g. pop('=')), or a predicate (e.g. pop(is_varname)).\"\"\"\n", " constraint can be None, a literal (e.g. pop('=')), or a predicate (e.g. pop(is_varname)).\"\"\"\n",
" if constraint is None or (peek() == constraint) or (callable(constraint) and constraint(peek())):\n", " top = peek()\n",
" if constraint is None or (top == constraint) or (callable(constraint) and constraint(top)):\n",
" return tokens.pop(0)\n", " return tokens.pop(0)\n",
" \n", " \n",
"def remove_spaces(line): \n", "def remove_spaces(line): \n",
@ -228,9 +216,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 7,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"data": { "data": {
@ -255,11 +241,12 @@
" ['100', 'IF', 'X1', '+', '123.4', '+', 'E1', '-', '12.3E4', '<>', \n", " ['100', 'IF', 'X1', '+', '123.4', '+', 'E1', '-', '12.3E4', '<>', \n",
" '1.2E-34', '*', '-', '12E34', '+', '1', '+', '\"HI\"', 'THEN', '99'])\n", " '1.2E-34', '*', '-', '12E34', '+', '1', '+', '\"HI\"', 'THEN', '99'])\n",
" assert remove_spaces('10 GO TO 99') == '10GOTO99'\n", " assert remove_spaces('10 GO TO 99') == '10GOTO99'\n",
" assert remove_spaces('100 PRINT \"HELLO WORLD\", SIN(X) ^ 2') == '100PRINT\"HELLO WORLD\",SIN(X)^2'\n", " assert (remove_spaces('100 PRINT \"HELLO WORLD\", SIN(X) ^ 2')\n",
" == '100PRINT\"HELLO WORLD\",SIN(X)^2')\n",
" assert lines('one line') == ['one line']\n", " assert lines('one line') == ['one line']\n",
" assert lines(program) == [\n", " assert lines(program) == [\n",
" '10 REM POWER TABLE',\n", " '10 REM POWER TABLE',\n",
" '11 DATA 8, 4',\n", " '11 DATA 8, 4',\n",
" '15 READ N0, P0',\n", " '15 READ N0, P0',\n",
" '20 PRINT \"N\",',\n", " '20 PRINT \"N\",',\n",
" '25 FOR P = 2 to P0',\n", " '25 FOR P = 2 to P0',\n",
@ -313,6 +300,43 @@
"test_tokenizer()" "test_tokenizer()"
] ]
}, },
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['10 REM POWER TABLE',\n",
" '11 DATA 8, 4',\n",
" '15 READ N0, P0',\n",
" '20 PRINT \"N\",',\n",
" '25 FOR P = 2 to P0',\n",
" '30 PRINT \"N ^\" P,',\n",
" '35 NEXT P',\n",
" '40 PRINT \"SUM\"',\n",
" '45 LET S = 0',\n",
" '50 FOR N = 2 TO N0',\n",
" '55 PRINT N,',\n",
" '60 FOR P = 2 TO P0',\n",
" '65 LET S = S + N ^ P',\n",
" '70 PRINT N ^ P,',\n",
" '75 NEXT P',\n",
" '80 PRINT S',\n",
" '85 NEXT N',\n",
" '99 END']"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"lines(program)"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
@ -347,10 +371,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 9,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def Grammar(): \n", "def Grammar(): \n",
@ -408,7 +430,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 10,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@ -419,11 +441,11 @@
" num = linenumber()\n", " num = linenumber()\n",
" typ = pop(is_stmt_type) or fail('unknown statement type')\n", " typ = pop(is_stmt_type) or fail('unknown statement type')\n",
" args = []\n", " args = []\n",
" for c in grammar[typ]: # For each constituent of rule, call if callable or match if literal string\n", " for p in grammar[typ]: # For each part of rule, call if callable or match if literal string\n",
" if callable(c):\n", " if callable(p):\n",
" args.append(c())\n", " args.append(p())\n",
" else:\n", " else:\n",
" pop(c) or fail('expected ' + repr(c))\n", " pop(p) or fail('expected ' + repr(p))\n",
" return Stmt(num, typ, args)" " return Stmt(num, typ, args)"
] ]
}, },
@ -436,19 +458,17 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 11,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def linenumber(): return (int(pop()) if peek().isnumeric() else fail('missing line number'))\n", "def linenumber(): return (int(pop()) if peek().isnumeric() else fail('missing line number'))\n",
"def number(): return (-1 if pop('-') else +1) * float(pop()) # Optional minus sign before number\n", "def number(): return (-1 if pop('-') else +1) * float(pop()) # Optional minus sign\n",
"def step(): return (expression() if pop('STEP') else 1) # 1 is the default step\n", "def step(): return (expression() if pop('STEP') else 1) # 1 is the default step\n",
"def relational(): return pop(is_relational) or fail('expected a relational operator')\n", "def relational(): return pop(is_relational) or fail('expected a relational operator')\n",
"def varname(): return pop(is_varname) or fail('expected a variable name')\n", "def varname(): return pop(is_varname) or fail('expected a variable name')\n",
"def funcname(): return pop(is_funcname) or fail('expected a function name')\n", "def funcname(): return pop(is_funcname) or fail('expected a function name')\n",
"def anycharacters(): tokens.clear() # The tokens in a REM statement don't matter, so just clear them" "def anycharacters(): tokens.clear() # Ignore tokens in a REM statement"
] ]
}, },
{ {
@ -460,18 +480,20 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 12,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"def is_stmt_type(x): return isinstance(x, str) and x in grammar # LET, READ, ...\n", "def is_stmt_type(x): return is_str(x) and x in grammar # LET, READ, ...\n",
"def is_funcname(x): return isinstance(x, str) and len(x) == 3 and x.isalpha() # SIN, COS, FNA, FNB, ...\n", "def is_funcname(x): return is_str(x) and len(x) == 3 and x.isalpha() # SIN, COS, FNA, FNB, ...\n",
"def is_varname(x): return isinstance(x, str) and len(x) in (1, 2) and x[0].isalpha() # A, A1, A2, B, ...\n", "def is_varname(x): return is_str(x) and len(x) in (1, 2) and x[0].isalpha() # A, A1, A2, B, ...\n",
"def is_label(x): return isinstance(x, str) and x.startswith('\"') # \"HELLO WORLD\", ...\n", "def is_label(x): return is_str(x) and x.startswith('\"') # \"HELLO WORLD\", ...\n",
"def is_relational(x): return isinstance(x, str) and x in ('<', '=', '>', '<=', '<>', '>=')\n", "def is_relational(x): return is_str(x) and x in ('<', '=', '>', '<=', '<>', '>=')\n",
"def is_number(x): return isinstance(x, str) and x and x[0] in '.0123456789' # '3', '.14', ..." "def is_number(x): return is_str(x) and x and x[0] in '.0123456789' # '3', '.14', ...\n",
"\n",
"def is_str(x): return isinstance(x, str)"
] ]
}, },
{ {
@ -483,10 +505,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 13,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def variable(): \n", "def variable(): \n",
@ -509,7 +529,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": 14,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@ -536,7 +556,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14, "execution_count": 15,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@ -556,10 +576,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 16,
"metadata": { "metadata": {},
"collapsed": true
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def parse_line(line):\n", "def parse_line(line):\n",
@ -572,8 +590,8 @@
" return stmt\n", " return stmt\n",
" except SyntaxError as err:\n", " except SyntaxError as err:\n",
" print(\"Error in line '{}' at '{}': {}\".format(line, ' '.join(tokens), err))\n", " print(\"Error in line '{}' at '{}': {}\".format(line, ' '.join(tokens), err))\n",
" return Stmt(0, 'REM', []) # Have to return something: a dummy statement\n", " return Stmt(0, 'REM', []) # Return dummy statement\n",
" \n", " \n",
"def fail(message): raise SyntaxError(message)" "def fail(message): raise SyntaxError(message)"
] ]
}, },
@ -588,7 +606,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 17,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@ -596,14 +614,14 @@
"source": [ "source": [
"from collections import namedtuple, defaultdict, deque\n", "from collections import namedtuple, defaultdict, deque\n",
"\n", "\n",
"Stmt = namedtuple('Stmt', 'num, typ, args') # Statement: '20 GOTO 99' => Stmt(20, 'GOTO', 99)\n", "Stmt = namedtuple('Stmt', 'num, typ, args') # '1 GOTO 9' => Stmt(1, 'GOTO', 9)\n",
"Subscript = namedtuple('Subscript', 'var, indexes') # Subscripted reference: 'A(I)' => Subscript('A', ['I'])\n", "Subscript = namedtuple('Subscript', 'var, indexes') # 'A(I)' => Subscript('A', ['I'])\n",
"Funcall = namedtuple('Funcall', 'f, x') # Function call: 'SQR(X)' => Funcall('SQR', 'X')\n", "Funcall = namedtuple('Funcall', 'f, x') # 'SQR(X)' => Funcall('SQR', 'X')\n",
"Opcall = namedtuple('Opcall', 'x, op, y') # Infix operation: 'X + 1' => Opcall('X', '+', 1)\n", "Opcall = namedtuple('Opcall', 'x, op, y') # 'X + 1' => Opcall('X', '+', 1)\n",
"ForState = namedtuple('ForState', 'continu, end, step') # Data used to control a FOR loop variable\n", "ForState = namedtuple('ForState', 'continu, end, step') # Data for FOR loop \n",
"\n", "\n",
"class Function(namedtuple('_', 'parm, body')):\n", "class Function(namedtuple('_', 'parm, body')):\n",
" \"User-defined callable function; 'DEF FNC(X) = X ^ 3' => functions['FNC'] = Function('X', Opcall('X', '^', 3))\"\n", " \"User-defined function; 'DEF FNC(X) = X ^ 3' => Function('X', Opcall('X', '^', 3))\"\n",
" def __call__(self, value): \n", " def __call__(self, value): \n",
" variables[self.parm] = value # Global assignment to the parameter\n", " variables[self.parm] = value # Global assignment to the parameter\n",
" return evalu(self.body)" " return evalu(self.body)"
@ -651,7 +669,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 17, "execution_count": 18,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@ -692,10 +710,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 18, "execution_count": 19,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def expression(prec=1): \n", "def expression(prec=1): \n",
@ -724,9 +740,11 @@
" else:\n", " else:\n",
" return fail('unknown expression')\n", " return fail('unknown expression')\n",
"\n", "\n",
"def precedence(op): return (3 if op == '^' else 2 if op in ('*', '/') else 1 if op in ('+', '-') else 0)\n", "def precedence(op): \n",
" return (3 if op == '^' else 2 if op in ('*', '/') else 1 if op in ('+', '-') else 0)\n",
"\n", "\n",
"def associativity(op): return (0 if op == '^' else 1)" "def associativity(op): \n",
" return (0 if op == '^' else 1)"
] ]
}, },
{ {
@ -740,10 +758,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 19, "execution_count": 20,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"data": { "data": {
@ -768,7 +784,7 @@
" Stmt(num=99, typ='END', args=[])]" " Stmt(num=99, typ='END', args=[])]"
] ]
}, },
"execution_count": 19, "execution_count": 20,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -788,10 +804,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 20, "execution_count": 21,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"data": { "data": {
@ -799,17 +813,17 @@
"'ok'" "'ok'"
] ]
}, },
"execution_count": 20, "execution_count": 21,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"def test_parse(text, result, category=expression):\n", "def test_exp(text, repr):\n",
" \"Test that text can be parsed as a category to yield the semantic result, with no tokens left over.\"\n", " \"Test that text can be parsed as an expression to yield repr, with no tokens left over.\"\n",
" global tokens\n", " global tokens\n",
" tokens = tokenizer(text)\n", " tokens = tokenizer(text)\n",
" return category() == result and not tokens\n", " return (expression() == repr) and not tokens\n",
" \n", " \n",
"def test_parser():\n", "def test_parser():\n",
" assert is_funcname('SIN') and is_funcname('FNZ') # Function names are three letters\n", " assert is_funcname('SIN') and is_funcname('FNZ') # Function names are three letters\n",
@ -818,16 +832,17 @@
" assert not is_varname('FNZ') and not is_varname('A10') and not is_varname('')\n", " assert not is_varname('FNZ') and not is_varname('A10') and not is_varname('')\n",
" assert is_relational('>') and is_relational('>=') and not is_relational('+')\n", " assert is_relational('>') and is_relational('>=') and not is_relational('+')\n",
" \n", " \n",
" assert test_parse('A + B * X + C', Opcall(Opcall('A', '+', Opcall('B', '*', 'X')), '+', 'C'))\n", " assert test_exp('A + B * X + C', Opcall(Opcall('A', '+', Opcall('B', '*', 'X')), '+', 'C'))\n",
" assert test_parse('A + B + X + C', Opcall(Opcall(Opcall('A', '+', 'B'), '+', 'X'), '+', 'C'))\n", " assert test_exp('A + B + X + C', Opcall(Opcall(Opcall('A', '+', 'B'), '+', 'X'), '+', 'C'))\n",
" assert test_parse('SIN(X)^2', Opcall(Funcall('SIN', 'X'), '^', 2))\n", " assert test_exp('SIN(X)^2', Opcall(Funcall('SIN', 'X'), '^', 2))\n",
" assert test_parse('10 ^ 2 ^ 3', Opcall(10, '^', Opcall(2, '^', 3))) # right associative\n", " assert test_exp('10 ^ 2 ^ 3', Opcall(10, '^', Opcall(2, '^', 3))) # right associative\n",
" assert test_parse('10 - 2 - 3', Opcall(Opcall(10, '-', 2), '-', 3)) # left associative\n", " assert test_exp('10 - 2 - 3', Opcall(Opcall(10, '-', 2), '-', 3)) # left associative\n",
" assert test_parse('A(I)+M(I, J)', Opcall(Subscript(var='A', indexes=['I']), '+', \n", " assert test_exp('A(I)+M(I, J)', Opcall(Subscript(var='A', indexes=['I']), '+', \n",
" Subscript(var='M', indexes=['I', 'J'])))\n", " Subscript(var='M', indexes=['I', 'J'])))\n",
" assert test_parse('X * -1', Opcall('X', '*', Funcall('NEG', 1.0)))\n", " assert test_exp('X * -1', Opcall('X', '*', Funcall('NEG', 1.0)))\n",
" assert test_parse('X--Y--Z', Opcall(Opcall('X', '-', Funcall('NEG', 'Y')), '-', Funcall('NEG', 'Z')))\n", " assert test_exp('X--Y--Z', Opcall(Opcall('X', '-', Funcall('NEG', 'Y')), \n",
" assert test_parse('((((X))))', 'X')\n", " '-', Funcall('NEG', 'Z')))\n",
" assert test_exp('((((X))))', 'X')\n",
" return 'ok'\n", " return 'ok'\n",
"\n", "\n",
"test_parser()" "test_parser()"
@ -844,7 +859,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 21, "execution_count": 22,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@ -881,10 +896,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 22, "execution_count": 23,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def execute(stmts): \n", "def execute(stmts): \n",
@ -944,10 +957,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 23, "execution_count": 24,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"import math\n", "import math\n",
@ -1040,10 +1051,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 24, "execution_count": 25,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def basic_print(items): \n", "def basic_print(items): \n",
@ -1082,10 +1091,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 25, "execution_count": 26,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1093,7 +1100,7 @@
"text": [ "text": [
"\n", "\n",
"10 REM POWER TABLE\n", "10 REM POWER TABLE\n",
"11 DATA 8, 4\n", "11 DATA 8, 4\n",
"15 READ N0, P0\n", "15 READ N0, P0\n",
"20 PRINT \"N\",\n", "20 PRINT \"N\",\n",
"25 FOR P = 2 to P0\n", "25 FOR P = 2 to P0\n",
@ -1120,9 +1127,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 26, "execution_count": 27,
"metadata": { "metadata": {
"collapsed": false,
"scrolled": true "scrolled": true
}, },
"outputs": [ "outputs": [
@ -1149,7 +1155,7 @@
" Stmt(num=99, typ='END', args=[])]" " Stmt(num=99, typ='END', args=[])]"
] ]
}, },
"execution_count": 26, "execution_count": 27,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -1160,10 +1166,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 27, "execution_count": 28,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1197,10 +1201,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 28, "execution_count": 29,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1237,10 +1239,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 29, "execution_count": 30,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1277,10 +1277,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 30, "execution_count": 31,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1306,10 +1304,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 31, "execution_count": 32,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1369,10 +1365,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 32, "execution_count": 33,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1401,10 +1395,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 33, "execution_count": 34,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1455,39 +1447,37 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 34, "execution_count": 35,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"defaultdict(float,\n", "defaultdict(float,\n",
" {'J': 5.0,\n", " {('P', (1.0,)): 1.25,\n",
" ('S', (1.0, 1.0)): 40.0,\n",
" ('S', (2.0, 4.0)): 21.0,\n",
" ('P', (1.0,)): 1.25,\n",
" ('S', (3.0, 2.0)): 47.0,\n",
" ('S', (2.0, 2.0)): 16.0,\n",
" ('S', (3.0, 3.0)): 29.0,\n",
" ('S', (3.0, 5.0)): 33.0,\n", " ('S', (3.0, 5.0)): 33.0,\n",
" ('S', (1.0, 5.0)): 42.0,\n",
" ('S', (3.0, 4.0)): 16.0,\n", " ('S', (3.0, 4.0)): 16.0,\n",
" 'I': 3.0,\n", " ('S', (1.0, 5.0)): 42.0,\n",
" ('S', (2.0, 1.0)): 10.0,\n",
" ('P', (3.0,)): 2.5,\n", " ('P', (3.0,)): 2.5,\n",
" ('S', (1.0, 4.0)): 29.0,\n", " ('S', (1.0, 4.0)): 29.0,\n",
" ('S', (2.0, 3.0)): 3.0,\n",
" ('S', (1.0, 3.0)): 37.0,\n",
" ('S', (2.0, 1.0)): 10.0,\n",
" ('S', (1.0, 2.0)): 20.0,\n", " ('S', (1.0, 2.0)): 20.0,\n",
" ('S', (2.0, 5.0)): 8.0,\n",
" ('S', (3.0, 1.0)): 35.0,\n", " ('S', (3.0, 1.0)): 35.0,\n",
" 'I': 3.0,\n",
" ('S', (2.0, 4.0)): 21.0,\n",
" ('P', (2.0,)): 4.3,\n",
" ('S', (2.0, 5.0)): 8.0,\n",
" ('S', (1.0, 1.0)): 40.0,\n",
" ('S', (3.0, 3.0)): 29.0,\n",
" 'J': 5.0,\n",
" ('S', (3.0, 2.0)): 47.0,\n",
" 'S': 169.4,\n", " 'S': 169.4,\n",
" ('P', (2.0,)): 4.3})" " ('S', (2.0, 2.0)): 16.0,\n",
" ('S', (2.0, 3.0)): 3.0,\n",
" ('S', (1.0, 3.0)): 37.0})"
] ]
}, },
"execution_count": 34, "execution_count": 35,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -1498,19 +1488,17 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 35, "execution_count": 36,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"6 3 2 6 9 0 0 3 3 6 9 2 7 9 1 2 1 4 4 1 2 7 3 7 8 \n", "7 2 8 4 9 1 9 8 0 8 3 0 3 4 5 6 4 2 1 6 9 9 9 7 5 \n",
"5 2 5 4 4 9 7 7 0 5 4 9 7 7 5 5 4 9 3 8 6 2 6 4 5 \n", "8 4 1 5 9 9 7 5 5 7 2 0 3 2 1 2 1 8 3 9 9 3 0 0 2 \n",
"8 7 6 0 3 4 1 4 2 2 8 1 1 9 2 4 0 1 0 2 1 3 4 0 2 \n", "2 8 9 7 5 8 9 0 2 7 8 6 7 4 9 9 4 2 5 4 0 9 6 5 3 \n",
"9 0 2 7 4 1 8 4 3 2 8 3 9 3 9 3 2 4 6 8 9 0 9 5 9 \n" "3 3 5 1 7 1 6 1 0 7 3 5 8 1 9 0 4 7 1 7 4 5 1 7 6 \n"
] ]
} }
], ],
@ -1527,10 +1515,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 36, "execution_count": 37,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1564,10 +1550,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 37, "execution_count": 38,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1602,10 +1586,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 38, "execution_count": 39,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1632,10 +1614,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 39, "execution_count": 40,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1668,10 +1648,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 40, "execution_count": 41,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1730,22 +1708,18 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": { "metadata": {},
"collapsed": false
},
"source": [ "source": [
"# Final Program\n", "# Final Program\n",
"\n", "\n",
"Now for a final, longer example, Conway's Game of [Life](https://en.wikipedia.org/wiki/Conway's_Game_of_Life),\n", "Now for a final, longer example, Conway's Game of [Life](https://en.wikipedia.org/wiki/Conway's_Game_of_Life),\n",
"which shows that BASIC is capable of handling a non-trivial problem&mdash;but I wouldn't want to rely on it for anything much bigger. This should give us some added confidence in the validity of the interpreter, but I would say the interpreter needs more work before I would trust it. I hope you found working through the interpreter informative, and maybe you have ideas for how to improve it, or to develop an interpreter for another language." "which shows that BASIC is capable of handling a non-trivial problem&mdash;but I wouldn't want to rely on it for anything much bigger. This should give us some added confidence in the validity of the interpreter, but I would say the interpreter needs more work before I would trust it. I hope you found working through the interpreter infortative, and maybe you have ideas for how to improve it, or to develop an interpreter for another language."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 41, "execution_count": 42,
"metadata": { "metadata": {},
"collapsed": false
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@ -1931,6 +1905,15 @@
"999 END \n", "999 END \n",
"''')" "''')"
] ]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
} }
], ],
"metadata": { "metadata": {
@ -1949,9 +1932,9 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.5.1" "version": "3.5.3"
} }
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 0 "nbformat_minor": 1
} }