Skip to content

Instantly share code, notes, and snippets.

@aalhour
Last active March 9, 2018 09:19
Show Gist options
  • Select an option

  • Save aalhour/9a80c6f630df22351ce70a4a45ffea13 to your computer and use it in GitHub Desktop.

Select an option

Save aalhour/9a80c6f630df22351ce70a4a45ffea13 to your computer and use it in GitHub Desktop.

Revisions

  1. aalhour renamed this gist Sep 29, 2017. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. aalhour renamed this gist Sep 29, 2017. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. aalhour renamed this gist Sep 29, 2017. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. aalhour revised this gist Sep 29, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions brainfuck
    Original file line number Diff line number Diff line change
    @@ -151,12 +151,12 @@ class Lexer(object):

    # #########################################################################################
    # #
    # PARSER - RECURSIVE-DESCENT #
    # PARSER #
    # #
    # #########################################################################################
    class Parser(object):
    """
    Recursive-Descent Parser for BrainFuck.
    BrainFuck Grammar Parser.
    """
    def __init__(self, lexer=None):
    """
  5. aalhour revised this gist Sep 21, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion brainfuck
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@
    #
    # @author: Ahmad Alhour (http://aalhour.com)
    # @description: BrainFuck Interpreter and REPL in less than 500 lines of Python 3
    # @license: GNU/GPL v3 (https://www.gnu.org/licenses/gpl-3.0.html)
    # @license: MIT (https://opensource.org/licenses/MIT)
    #
    # @usage:
    # ./brainfuck --help Prints help and exits
  6. aalhour revised this gist Jun 25, 2017. 1 changed file with 5 additions and 12 deletions.
    17 changes: 5 additions & 12 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # PBFI: Pythonic BrainFuck Interpreter [![GNU/GPL v3 Logo](https://www.gnu.org/graphics/gplv3-88x31.png)](https://www.gnu.org/licenses/gpl-3.0-standalone.html)
    # PBFI: Pythonic BrainFuck Interpreter

    Yet another educational interpreter for the BrainFuck Programming Language, written in Python 3. This might not be the shortest BrainFuck interpreter that you had come acorss, however the style of programming is for educational purposes only.

    @@ -26,23 +26,16 @@ Yet another educational interpreter for the BrainFuck Programming Language, writ

    ## LICENSE:

    This gist and all its accompanying files are released under the GNU/GPL v3 License.
    This gist and all its accompanying files are released under the MIT License.

    ```
    PBFI: Pythonic BrainFuck Inpterpreter
    Copyright (C) 2016 Ahmad Alhour
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    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:
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
    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.
    ```
  7. aalhour revised this gist Jun 25, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    # PBFI: Pythonic BrainFuck Interpreter [![GNU/GPL v3 Logo](https://www.gnu.org/graphics/gplv3-88x31.png)](https://www.gnu.org/licenses/gpl-3.0-standalone.html)

    The BrainFuck Interpreter and REPL in Python 3 (python-3.5.1). Self-contained, under 500 lines of code and doesn't depend on any external library.
    Yet another educational interpreter for the BrainFuck Programming Language, written in Python 3. This might not be the shortest BrainFuck interpreter that you had come acorss, however the style of programming is for educational purposes only.


    ## USAGE:
  8. aalhour revised this gist Jul 31, 2016. 2 changed files with 4 additions and 4 deletions.
    4 changes: 2 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    # PBFI: Pythonic BrainFuck Inpterpreter [![GNU/GPL v3 Logo](https://www.gnu.org/graphics/gplv3-88x31.png)](https://www.gnu.org/licenses/gpl-3.0-standalone.html)
    # PBFI: Pythonic BrainFuck Interpreter [![GNU/GPL v3 Logo](https://www.gnu.org/graphics/gplv3-88x31.png)](https://www.gnu.org/licenses/gpl-3.0-standalone.html)

    The BrainFuck Inpterpreter and REPL in Python 3 (python-3.5.1). Self-contained, under 500 lines of code and doesn't depend on any external library.
    The BrainFuck Interpreter and REPL in Python 3 (python-3.5.1). Self-contained, under 500 lines of code and doesn't depend on any external library.


    ## USAGE:
    4 changes: 2 additions & 2 deletions brainfuck
    Original file line number Diff line number Diff line change
    @@ -3,9 +3,9 @@
    # -----------------------------------------------------------------------------------------
    # PBFI: Pythonic BrainFuck Interpreter
    #
    # @author: Ahmad Alhour (aalhour.com)
    # @author: Ahmad Alhour (http://aalhour.com)
    # @description: BrainFuck Interpreter and REPL in less than 500 lines of Python 3
    # @license: GNU/GPL version 3
    # @license: GNU/GPL v3 (https://www.gnu.org/licenses/gpl-3.0.html)
    #
    # @usage:
    # ./brainfuck --help Prints help and exits
  9. aalhour revised this gist Jul 31, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -26,7 +26,7 @@ The BrainFuck Inpterpreter and REPL in Python 3 (python-3.5.1). Self-contained,

    ## LICENSE:

    This gist and all its accompanying files are released under GNU/GPL.
    This gist and all its accompanying files are released under the GNU/GPL v3 License.

    ```
    PBFI: Pythonic BrainFuck Inpterpreter
  10. aalhour revised this gist Jul 31, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # PBFI: Pythonic BrainFuck Inpterpreter
    # PBFI: Pythonic BrainFuck Inpterpreter [![GNU/GPL v3 Logo](https://www.gnu.org/graphics/gplv3-88x31.png)](https://www.gnu.org/licenses/gpl-3.0-standalone.html)

    The BrainFuck Inpterpreter and REPL in Python 3 (python-3.5.1). Self-contained, under 500 lines of code and doesn't depend on any external library.

  11. aalhour revised this gist Jul 31, 2016. 2 changed files with 27 additions and 5 deletions.
    24 changes: 23 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # BrainFuck in Python 3
    # PBFI: Pythonic BrainFuck Inpterpreter

    The BrainFuck Inpterpreter and REPL in Python 3 (python-3.5.1). Self-contained, under 500 lines of code and doesn't depend on any external library.

    @@ -24,3 +24,25 @@ The BrainFuck Inpterpreter and REPL in Python 3 (python-3.5.1). Self-contained,
    ./brainfuck --code "++++++ [ > ++++++++++ < - ] > +++++ ."
    ```

    ## LICENSE:

    This gist and all its accompanying files are released under GNU/GPL.

    ```
    PBFI: Pythonic BrainFuck Inpterpreter
    Copyright (C) 2016 Ahmad Alhour
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
    ```
    8 changes: 4 additions & 4 deletions brainfuck
    Original file line number Diff line number Diff line change
    @@ -1,11 +1,11 @@
    #!/usr/bin/env python3

    # -----------------------------------------------------------------------------------------
    # brainfuck
    # PBFI: Pythonic BrainFuck Interpreter
    #
    # @author: Ahmad Alhour (aalhour.com).
    # @description: BrainFuck Interpreter and REPL in less than 500 lines of Python 3 and
    # no dependencies.
    # @author: Ahmad Alhour (aalhour.com)
    # @description: BrainFuck Interpreter and REPL in less than 500 lines of Python 3
    # @license: GNU/GPL version 3
    #
    # @usage:
    # ./brainfuck --help Prints help and exits
  12. aalhour revised this gist Jul 31, 2016. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions brainfuck
    Original file line number Diff line number Diff line change
    @@ -102,6 +102,9 @@ class Lexer(object):
    self.input_text = input_text if input_text and isinstance(input_text, str) else ""

    def scan(self, input_text):
    """
    Initilizes the lexer with an input text for scanning.
    """
    self.curr_pos = 0
    self.curr_char = None
    self.input_text = input_text
  13. aalhour revised this gist Jul 31, 2016. 1 changed file with 0 additions and 21 deletions.
    21 changes: 0 additions & 21 deletions brainfuck
    Original file line number Diff line number Diff line change
    @@ -110,21 +110,6 @@ class Lexer(object):
    raise ValueError("Input text must be a non-empty string!")
    else:
    self.curr_char = input_text[0]

    def peek(self):
    """
    Returns the next valid token without advancing the lexer.
    """
    if not self.input_text or not isinstance(self.input_text, str):
    raise("Please initialize the input text with the scan method first!")

    peek_pos = int(str(self.curr_pos))

    while peek_pos < len(self.input_text) - 1:
    peek_pos += 1
    next_char = self.input_text[peek_pos]
    if next_char in sym2tok:
    return BrainFuckToken(ttype=sym2tok[next_char], tvalue=next_char)

    return BrainFuckToken(EOF, None)

    @@ -230,12 +215,6 @@ class Parser(object):

    raise Exception(msg)

    def peek(self):
    """
    Peek one token ahead.
    """
    return self._lexer.peek()

    def consume(self, token_type):
    """
    Checked tokens consumption.
  14. aalhour revised this gist Jul 31, 2016. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions brainfuck
    Original file line number Diff line number Diff line change
    @@ -74,7 +74,7 @@ tok2sym = {value: key for key, value in sym2tok.items()}

    # #########################################################################################
    # #
    # LINEAR LEXER #
    # LAZY LEXER #
    # #
    # #########################################################################################
    class BrainFuckToken(object):
    @@ -163,7 +163,7 @@ class Lexer(object):

    # #########################################################################################
    # #
    # PARSER #
    # PARSER - RECURSIVE-DESCENT #
    # #
    # #########################################################################################
    class Parser(object):
    @@ -311,7 +311,7 @@ class Parser(object):

    # #########################################################################################
    # #
    # INTERPRETER and RUNTIME #
    # INTERPRETER and TAPE #
    # #
    # #########################################################################################
    class BrainFuckTape(object):
  15. aalhour revised this gist Jul 31, 2016. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    # BrainFuck in Python 3

    The BrainFuck Inpterpreter and REPL in Python 3 (v3.5). Self-contained and doesn't depend on any external 3rd party library.
    The BrainFuck Inpterpreter and REPL in Python 3 (python-3.5.1). Self-contained, under 500 lines of code and doesn't depend on any external library.


    ## USAGE:
    @@ -24,4 +24,3 @@ The BrainFuck Inpterpreter and REPL in Python 3 (v3.5). Self-contained and doesn
    ./brainfuck --code "++++++ [ > ++++++++++ < - ] > +++++ ."
    ```


  16. aalhour revised this gist Jul 31, 2016. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,7 @@
    # BrainFuck in Python 3

    Two implementations of the BrainFuck Inpterpreter and REPL in Python 3. Both expose the same CLI commands and are under 500 lines of code (each).
    The BrainFuck Inpterpreter and REPL in Python 3 (v3.5). Self-contained and doesn't depend on any external 3rd party library.

    1. [`brainfuck`](https://gist.github.com/aalhour/9a80c6f630df22351ce70a4a45ffea13#file-brainfuck), is self-contained and doesn't depend on any external modules.
    2. [`brainfuck_llyy`](https://gist.github.com/aalhour/9a80c6f630df22351ce70a4a45ffea13#file-brainfuck_llyy), depends on [PLY](https://github.com/dabeaz/ply) v3.8 for lexing and parsing.

    ## USAGE:

  17. aalhour revised this gist Jul 31, 2016. 2 changed files with 83 additions and 363 deletions.
    130 changes: 83 additions & 47 deletions brainfuck
    Original file line number Diff line number Diff line change
    @@ -160,12 +160,16 @@ class Lexer(object):
    return BrainFuckToken(EOF, None)



    # #########################################################################################
    # #
    # PARSER #
    # #
    # #########################################################################################
    class Parser(object):
    """
    Recursive-Descent Parser for BrainFuck.
    """
    def __init__(self, lexer=None):
    """
    Initializer.
    @@ -207,19 +211,20 @@ class Parser(object):
    self.curr_token = self._lexer.next_token()

    # Begin parsing
    node = self._program()
    cmd_node = self._program()
    if self.curr_token.type != EOF:
    self.error(EOF)

    # Return AST
    return node
    return cmd_node

    def error(self, expected_token_type=None):
    """
    Error reporting.
    """
    if expected_token_type:
    msg = 'Invalid syntax at {!r}. Expected: {!r}'.format(self.curr_token, expected_token_type)
    msg = 'Invalid syntax at {!r}. Expected: {!r}'.format(
    self.curr_token, expected_token_type)
    else:
    msg = 'Invalid syntax at {!r}.'.format(self.curr_token)

    @@ -252,24 +257,26 @@ class Parser(object):
    """
    if self.curr_token is None:
    raise ValueError("Please run parse method with an input program text first!")

    commands = []
    ast = []
    token_types = tok2sym.keys()

    while self.curr_token.type in token_types:
    token = self.curr_token

    # Parse while_loop
    if token.type == BEGIN_LOOP_CMD:
    node = self._while_loop()
    cmd_node = self._while_loop()
    elif token.type == END_LOOP_CMD:
    return commands
    return ast
    # Parse terminal_command
    else:
    node = self._terminal_command()
    cmd_node = self._terminal_command()

    # Append to list of commands
    commands.append(node)
    # Append to AST (list of commands)
    ast.append(cmd_node)

    return commands
    return ast

    def _while_loop(self):
    """
    @@ -279,9 +286,9 @@ class Parser(object):
    raise ValueError("Please run parse method with an input program text first!")

    self.consume(BEGIN_LOOP_CMD)
    node = (LOOP_CMD, self._program())
    cmd_node = (LOOP_CMD, self._program())
    self.consume(END_LOOP_CMD)
    return node
    return cmd_node

    def _terminal_command(self):
    """
    @@ -296,9 +303,10 @@ class Parser(object):
    raise ValueError("Please run parse method with an input program text first!")

    token = self.curr_token
    node = (token.type,)
    cmd_node = (token.type,)
    self.consume(token.type)
    return node
    return cmd_node



    # #########################################################################################
    @@ -307,6 +315,15 @@ class Parser(object):
    # #
    # #########################################################################################
    class BrainFuckTape(object):
    """
    The BrainFuck Tape Data Structure.
    Implemented internally as a dictionary for on-demand allocation of values
    at specific indices.
    """
    @property
    def MAX_INDEX(self):
    return 30000

    def __init__(self):
    self.map = {}

    @@ -317,50 +334,66 @@ class BrainFuckTape(object):
    def __setitem__(self, index, value):
    self.map[index] = value

    ###
    # CONSTANTS
    MAX_INDEX = 30000

    ###
    # GLOBAL TAPE STATES
    INDEX = 0
    THE_TAPE = BrainFuckTape()
    global_tape_index = 0
    global_tape = BrainFuckTape()


    def bf_eval(commands_ast):
    """
    BrainFuck AST Nodes Evaluator.
    def bf_eval(expressions):
    global INDEX
    global MAX_INDEX
    global THE_TAPE
    Evaluates every node in a given AST representation of a BrainFuck program.
    The AST is expected to be a linear collection of tuples. Every tuple's first
    item is the type of the command. Only Loop Commands contain two items, where
    the second item is another linear collection of commands.
    """
    global global_tape
    global global_tape_index

    for node in expressions:
    if not node:
    for cmd_node in commands_ast:
    if not cmd_node:
    return None
    elif node[0] == MOV_PTR_PREV_CMD:
    if INDEX > 0:
    INDEX -= 1
    # Move pointer to previous cell
    elif cmd_node[0] == MOV_PTR_PREV_CMD:
    if global_tape_index > 0:
    global_tape_index -= 1
    else:
    raise Exception("Index out of array bounds!")
    elif node[0] == MOV_PTR_NEXT_CMD:
    if INDEX < MAX_INDEX:
    INDEX += 1
    raise Exception("Error: index out of bound! Min allowed index value is 0.")
    # Move pointer to next cell
    elif cmd_node[0] == MOV_PTR_NEXT_CMD:
    if global_tape_index < global_tape.MAX_INDEX:
    global_tape_index += 1
    else:
    raise Exception("Index out of array bounds!")
    elif node[0] == PRINT_CMD:
    print(chr(THE_TAPE[INDEX]), sep='', end='')
    elif node[0] == READ_CMD:
    raise Exception("Error: index out of bound! Max allowed index value is {}".format(
    global_tape.MAX_INDEX))
    # Print the current cell's contents
    elif cmd_node[0] == PRINT_CMD:
    cell_val = global_tape[global_tape_index]
    print(chr(cell_val), sep='', end='')
    # Read input to current cell
    elif cmd_node[0] == READ_CMD:
    str_input = str(input())
    if len(str_input) > 0:
    THE_TAPE[INDEX] = ord(str_input[0])
    elif node[0] == INC_CELL_CMD:
    THE_TAPE[INDEX] += 1
    elif node[0] == DEC_CELL_CMD:
    THE_TAPE[INDEX] -= 1
    elif node[0] == LOOP_CMD:
    while THE_TAPE[INDEX] != 0:
    bf_eval(node[1])
    global_tape[global_tape_index] = ord(str_input[0])
    # Increment the contents of current cell
    elif cmd_node[0] == INC_CELL_CMD:
    global_tape[global_tape_index] += 1
    # Decrement the contents of current cell
    elif cmd_node[0] == DEC_CELL_CMD:
    global_tape[global_tape_index] -= 1
    # Loop until the current cells contents are equal to 0
    elif cmd_node[0] == LOOP_CMD:
    while global_tape[global_tape_index] != 0:
    bf_eval(cmd_node[1])


    def bf_interpret(program_text):
    """
    BrainFuck Program Text Interpreter.
    """
    try:
    brainfuck_parser = Parser()
    bfcode = bfcode = brainfuck_parser.parse(program_text)
    @@ -374,6 +407,9 @@ def bf_interpret(program_text):


    def bf_repl():
    """
    BrainFuck REPL.
    """
    print("BrainFuck on Python!")
    print("Type :clear to clear the screen. Type :quit to exit REPL.")

    @@ -395,8 +431,8 @@ def bf_repl():
    break
    else:
    try:
    bfcode = brainfuck_parser.parse(program_text)
    bf_eval(bfcode)
    bf_code = brainfuck_parser.parse(program_text)
    bf_eval(bf_code)
    print()
    except (KeyboardInterrupt, SystemExit):
    print("\r\nReceived KeyboardInterrupt, stopping...")
    316 changes: 0 additions & 316 deletions brainfuck_llyy
    Original file line number Diff line number Diff line change
    @@ -1,316 +0,0 @@
    #!/usr/bin/env python3

    # -----------------------------------------------------------------------------------------
    # brainfuck_llyy
    #
    # @author: Ahmad Alhour (aalhour.com).
    # @description: BrainFuck Interpreter and REPL in Python 3, using PLY (Python lex & yacc).
    #
    # @requirements: ply==3.8
    # @pre-install: pip3 install ply==3.8
    #
    # @usage:
    # ./brainfuck --help Prints help and exits
    # ./brainfuck --repl Opens REPL
    # ./brainfuck --program ~/some_program.bf Runs code in ~/some_program.bf
    # ./brainfuck --code "++++++[>++++++++++<-]>+++++." Evalues a piece of code
    # -----------------------------------------------------------------------------------------

    # std
    import sys
    import subprocess
    import argparse

    # ply 3.8
    import ply.lex as lex
    import ply.yacc as yacc


    # #########################################################################################
    # #
    # LEX SCANNER #
    # #
    # #########################################################################################
    tokens = [
    'MOV_LEFT',
    'MOV_RIGHT',
    'PRINT',
    'READ',
    'INC',
    'DEC',
    'JMP_FWR',
    'JMP_BKW'
    ]

    # Single line comment
    t_ignore_SINGLE_LINE_COMMENT = r"\#[^\n]*"

    # Tokenization Rules of Brain Fuck
    t_MOV_LEFT = r'\<'
    t_MOV_RIGHT = r'\>'
    t_PRINT = r'\.'
    t_READ = r','
    t_INC = r'\+'
    t_DEC = r'\-'
    t_JMP_FWR = r'\['
    t_JMP_BKW = r'\]'

    # Newlines Rule
    def t_newline(token):
    r"\n+"
    token.lexer.lineno += len(token.value)

    # Ignore Whitespae Rule
    t_ignore = ' \t\r\f'

    def t_error(t):
    t.lexer.skip(1)


    # Build lex-lexer
    lexer = lex.lex(optimize=True, outputdir="lexyacctabs", lextab="lexyacctabs.lextab")


    # #########################################################################################
    # #
    # YACC PARSER #
    # #
    # #########################################################################################
    # YACC Error list
    error_list = []


    def p_start(p):
    """
    start : program
    | empty
    """
    if p is not None:
    p[0] = p[1]


    def p_program_many(p):
    """
    program : program single_inst
    | program loop_inst
    """
    p[0] = p[1] + [p[2]]


    def p_program_single(p):
    """
    program : single_inst
    | loop_inst
    """
    p[0] = [p[1]]


    def p_single_inst(p):
    """
    single_inst : MOV_LEFT
    | MOV_RIGHT
    | PRINT
    | READ
    | INC
    | DEC
    """
    if p[1] == '<':
    p[0] = ('MOV_LEFT',)
    elif p[1] == '>':
    p[0] = ('MOV_RIGHT',)
    elif p[1] == '.':
    p[0] = ('PRINT',)
    elif p[1] == ',':
    p[0] = ('READ',)
    elif p[1] == '+':
    p[0] = ('INC',)
    elif p[1] == '-':
    p[0] = ('DEC',)


    def p_loop_inst(p):
    """
    loop_inst : JMP_FWR program JMP_BKW
    """
    p[0] = ('LOOP', p[2])


    def p_error(p):
    if p is None:
    print("Error! Unexpected end of input!")
    else:
    error = "Syntax error! Line: {}, position: {}, character: {}, type: {}".format(
    p.lineno, p.lexpos, p.value, p.type)
    error_list.append(error)
    parser.errok()


    def p_empty(p):
    """
    empty :
    """
    return None

    # Build yacc-parser
    parser = yacc.yacc(optimize=True, outputdir="lexyacctabs", tabmodule="lexyacctabs.yacctab")


    # #########################################################################################
    # #
    # INTERPRETER #
    # #
    # #########################################################################################
    class BrainFuckTape:
    def __init__(self):
    self.map = {}

    def __getitem__(self, index):
    # return 0 if index was not in self.map
    return self.map.get(index, 0)

    def __setitem__(self, index, value):
    self.map[index] = value

    ###
    # CONSTANTS
    MAX_INDEX = 30000

    ###
    # GLOBAL TAPE STATES
    INDEX = 0
    THE_TAPE = BrainFuckTape()


    def bf_eval(expressions):
    global THE_TAPE
    global INDEX
    global MAX_INDEX

    for node in expressions:
    if not node:
    return None
    elif node[0] == 'MOV_LEFT':
    if INDEX > 0:
    INDEX -= 1
    else:
    raise Exception("Index out of array bounds!")
    elif node[0] == 'MOV_RIGHT':
    if INDEX < MAX_INDEX:
    INDEX += 1
    else:
    raise Exception("Index out of array bounds!")
    elif node[0] == 'PRINT':
    print(chr(THE_TAPE[INDEX]), sep='', end='')
    elif node[0] == 'READ':
    str_input = str(input())
    if len(str_input) > 0:
    THE_TAPE[INDEX] = ord(str_input[0])
    elif node[0] == 'INC':
    THE_TAPE[INDEX] += 1
    elif node[0] == 'DEC':
    THE_TAPE[INDEX] -= 1
    elif node[0] == 'LOOP':
    while THE_TAPE[INDEX] != 0:
    bf_eval(node[1])


    def bf_interpret(program_text):
    try:
    bfcode = parser.parse(program_text)
    bf_eval(bfcode)
    except (KeyboardInterrupt, SystemExit):
    print("\r\nReceived KeyboardInterrupt, stopping...")
    print("Goodbye!")
    sys.exit(0)
    except Exception as err:
    raise


    def bf_repl():
    print("BrainFuck on Python!")
    print("Type :clear to clear the screen. Type :quit to exit REPL.")
    while True:
    try:
    program = input('BrainFuck> ')
    except (EOFError, KeyboardInterrupt, SystemExit):
    break

    if not program:
    continue

    if program == ":clear":
    subprocess.call("clear")
    elif program == ":quit":
    break
    else:
    try:
    bf_interpret(program)
    print()
    except (KeyboardInterrupt, SystemExit):
    print("\r\nReceived KeyboardInterrupt, stopping...")
    break
    except Exception as err:
    print("An error has occurred: {!r}".format(err))

    print("\r\nGoodbye!")


    # #########################################################################################
    # #
    # MAIN PROGRAM ENTRY #
    # #
    # #########################################################################################
    def create_argparser():
    arg_parser = argparse.ArgumentParser(prog="brainfuck")

    # --repl argument
    arg_parser.add_argument(
    "--repl",
    action="store_true", default=False,
    help="BrainFuck REPL: interactive mode.")

    # --program argument
    arg_parser.add_argument(
    "--program",
    type=str, nargs=1, default=None,
    help="Path to a BrainFuck program file, for example: ./brainfuck --program ~/hello_world.bf")

    # --code argument
    arg_parser.add_argument(
    "--code",
    type=str, nargs=1, default=None,
    help="A double-quoted BrainFuck source code program, for example: ./brainfuck --code \"++++ ++++\"")

    return arg_parser


    def main():
    arg_parser = create_argparser()
    args = arg_parser.parse_args()

    # First, --repl has the highest priority
    if args.repl is True:
    bf_repl()
    # Second, --program has the second highest priority.
    elif args.program is not None and len(args.program) > 0:
    program_code = ""
    try:
    with open(args.program[0], mode="r", encoding="utf-8") as file:
    program_code = file.read()
    except (IOError, FileNotFoundError):
    print("Error: file was not found!")
    sys.exit(1)
    bf_interpret(program_code)
    print("")
    # Third, --code has the lowest priority.
    elif args.code is not None and len(args.code) > 0:
    bf_interpret(args.code[0])
    print("")
    # Nothing was specified, print help.
    else:
    arg_parser.print_help()


    if __name__ == "__main__":
    main()

  18. aalhour revised this gist Jul 31, 2016. 1 changed file with 3 additions and 6 deletions.
    9 changes: 3 additions & 6 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,9 @@
    # BrainFuck in Python 3

    Two implementations of the BrainFuck Inpterpreter and REPL in Python 3.
    Two implementations of the BrainFuck Inpterpreter and REPL in Python 3. Both expose the same CLI commands and are under 500 lines of code (each).

    The two implementations implement the same CLI commands.

    The first implementation, [`brainfuck`](https://gist.github.com/aalhour/9a80c6f630df22351ce70a4a45ffea13#file-brainfuck), is self-contained and doesn't depend on any external modules.

    The second implementation, [`brainfuck_llyy`](https://gist.github.com/aalhour/9a80c6f630df22351ce70a4a45ffea13#file-brainfuck_llyy), depends on [PLY](https://github.com/dabeaz/ply) v3.8.
    1. [`brainfuck`](https://gist.github.com/aalhour/9a80c6f630df22351ce70a4a45ffea13#file-brainfuck), is self-contained and doesn't depend on any external modules.
    2. [`brainfuck_llyy`](https://gist.github.com/aalhour/9a80c6f630df22351ce70a4a45ffea13#file-brainfuck_llyy), depends on [PLY](https://github.com/dabeaz/ply) v3.8 for lexing and parsing.

    ## USAGE:

  19. aalhour revised this gist Jul 31, 2016. 1 changed file with 32 additions and 1 deletion.
    33 changes: 32 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1 +1,32 @@
    .
    # BrainFuck in Python 3

    Two implementations of the BrainFuck Inpterpreter and REPL in Python 3.

    The two implementations implement the same CLI commands.

    The first implementation, [`brainfuck`](https://gist.github.com/aalhour/9a80c6f630df22351ce70a4a45ffea13#file-brainfuck), is self-contained and doesn't depend on any external modules.

    The second implementation, [`brainfuck_llyy`](https://gist.github.com/aalhour/9a80c6f630df22351ce70a4a45ffea13#file-brainfuck_llyy), depends on [PLY](https://github.com/dabeaz/ply) v3.8.

    ## USAGE:

    #### Help:

    ```
    ./brainfuck --help
    ```

    #### REPL:

    ```
    ./brainfuck --repl
    ```

    #### Interpreter:

    ```
    ./brainfuck --program ~/hello_world.bf
    ./brainfuck --code "++++++ [ > ++++++++++ < - ] > +++++ ."
    ```


  20. aalhour created this gist Jul 31, 2016.
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    .
    469 changes: 469 additions & 0 deletions brainfuck
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,469 @@
    #!/usr/bin/env python3

    # -----------------------------------------------------------------------------------------
    # brainfuck
    #
    # @author: Ahmad Alhour (aalhour.com).
    # @description: BrainFuck Interpreter and REPL in less than 500 lines of Python 3 and
    # no dependencies.
    #
    # @usage:
    # ./brainfuck --help Prints help and exits
    # ./brainfuck --repl Opens REPL
    # ./brainfuck --program ~/some_program.bf Runs code in ~/some_program.bf
    # ./brainfuck --code "++++++[>++++++++++<-]>+++++." Evalues a piece of code
    # -----------------------------------------------------------------------------------------

    # std
    import sys
    import subprocess
    import argparse


    # #########################################################################################
    # #
    # TOKENS #
    # #
    # #########################################################################################
    # Reads a single input character into the current cell.
    READ_CMD = "READ_CMD"

    # Prints the ASCII value at the current cell (i.e. 65 = 'A').
    PRINT_CMD = "PRINT_CMD"

    # Increments the value at the current cell by one.
    INC_CELL_CMD = "INC_CELL_CMD"

    # Decrements the value at the current cell by one.
    DEC_CELL_CMD = "DEC_CELL_CMD"

    # Moves the data pointer to the next cell (cell on the right).
    MOV_PTR_NEXT_CMD = "MOV_PTR_NEXT_CMD"

    # Moves the data pointer to the previous cell (cell on the left).
    MOV_PTR_PREV_CMD = "MOV_PTR_PREV_CMD"

    # If the value at the current cell is zero, skips to the corresponding ] .
    # Otherwise, move to the next instruction.
    BEGIN_LOOP_CMD = "BEGIN_LOOP_CMD"

    # If the value at the current cell is zero, move to the next instruction.
    # Otherwise, move backwards in the instructions to the corresponding [ .
    END_LOOP_CMD = "END_LOOP_CMD"

    # A matched [ and ]
    LOOP_CMD = "LOOP"

    # END OF FILE
    EOF = "EOF"

    # Fast lookup tables: Symbol to Token Type mapping, and its reversed
    sym2tok = {
    ",": READ_CMD,
    ".": PRINT_CMD,
    "+": INC_CELL_CMD,
    "-": DEC_CELL_CMD,
    "<": MOV_PTR_PREV_CMD,
    ">": MOV_PTR_NEXT_CMD,
    "[": BEGIN_LOOP_CMD,
    "]": END_LOOP_CMD
    }

    tok2sym = {value: key for key, value in sym2tok.items()}


    # #########################################################################################
    # #
    # LINEAR LEXER #
    # #
    # #########################################################################################
    class BrainFuckToken(object):
    """
    The BrainFuck Token class.
    """
    def __init__(self, ttype, tvalue):
    self.type = ttype
    self.value = tvalue

    def __str__(self):
    return "BrainFuckToken({0}, \"{1}\")".format(self.type, self.value)

    def __repr__(self):
    return self.__str__()


    class Lexer(object):
    def __init__(self, input_text=None):
    """
    Initializer.
    """
    self.curr_pos = 0
    self.curr_char = None
    self.input_text = input_text if input_text and isinstance(input_text, str) else ""

    def scan(self, input_text):
    self.curr_pos = 0
    self.curr_char = None
    self.input_text = input_text

    if not input_text or not isinstance(input_text, str):
    raise ValueError("Input text must be a non-empty string!")
    else:
    self.curr_char = input_text[0]

    def peek(self):
    """
    Returns the next valid token without advancing the lexer.
    """
    if not self.input_text or not isinstance(self.input_text, str):
    raise("Please initialize the input text with the scan method first!")

    peek_pos = int(str(self.curr_pos))

    while peek_pos < len(self.input_text) - 1:
    peek_pos += 1
    next_char = self.input_text[peek_pos]
    if next_char in sym2tok:
    return BrainFuckToken(ttype=sym2tok[next_char], tvalue=next_char)

    return BrainFuckToken(EOF, None)

    def advance(self):
    """
    Advances the lexer pointer ahead and assigns the next character to self.curr_char.
    """
    if not self.input_text or not isinstance(self.input_text, str):
    raise("Please initialize the input text with the scan method first!")

    self.curr_pos += 1

    if self.curr_pos >= len(self.input_text):
    self.curr_char = None
    else:
    self.curr_char = self.input_text[self.curr_pos]

    def next_token(self):
    """
    Returns a Token representation of the next valid token in the tape or EOF.
    """
    if not self.input_text or not isinstance(self.input_text, str):
    raise("Please initialize the input text with the scan method first!")

    while self.curr_char is not None:
    value = self.curr_char
    if value in sym2tok:
    self.advance()
    return BrainFuckToken(ttype=sym2tok[value], tvalue=value)
    else:
    self.advance()
    # EOF
    return BrainFuckToken(EOF, None)


    # #########################################################################################
    # #
    # PARSER #
    # #
    # #########################################################################################
    class Parser(object):
    def __init__(self, lexer=None):
    """
    Initializer.
    """
    if lexer is None:
    self._lexer = Lexer()
    elif isinstance(lexer, Lexer):
    self._lexer = lexer
    else:
    raise("Lexer should be of type `Lexer` or None. If it's None, it will be created automatically.")
    self.curr_token = None

    def set_lexer(self, new_lexer):
    """
    Sets internal lexer to a new instance.
    """
    if not isinstance(new_lexer, Lexer):
    raise("Lexer can only be of type `Lexer`.")
    self._lexer = new_lexer

    def parse(self, input_text):
    """
    Parser entry point. Parses the complete BrainFuck Language.
    program : while_loop (program)*
    | terminal_command (program)*
    while_loop : BEGIN_LOOP_CMD program END_LOOP_CMD
    terminal_command : READ_CMD
    | PRINT_CMD
    | INC_CELL_CMD
    | DEC_CELL_CMD
    | MOV_PTR_NEXT_CMD
    | MOV_PTR_PREV_CMD
    """
    # Scan the input text
    self._lexer.scan(input_text)
    self.curr_token = self._lexer.next_token()

    # Begin parsing
    node = self._program()
    if self.curr_token.type != EOF:
    self.error(EOF)

    # Return AST
    return node

    def error(self, expected_token_type=None):
    """
    Error reporting.
    """
    if expected_token_type:
    msg = 'Invalid syntax at {!r}. Expected: {!r}'.format(self.curr_token, expected_token_type)
    else:
    msg = 'Invalid syntax at {!r}.'.format(self.curr_token)

    raise Exception(msg)

    def peek(self):
    """
    Peek one token ahead.
    """
    return self._lexer.peek()

    def consume(self, token_type):
    """
    Checked tokens consumption.
    """
    if self.curr_token is None:
    raise ValueError("Please run parse method with an input program text first!")

    if self.curr_token.type == token_type:
    self.curr_token = self._lexer.next_token()
    else:
    self.error(token_type)

    # ############################## PRIVATE ###########################

    def _program(self):
    """
    program : while_loop (program)*
    | terminal_command (program)*
    """
    if self.curr_token is None:
    raise ValueError("Please run parse method with an input program text first!")

    commands = []
    token_types = tok2sym.keys()

    while self.curr_token.type in token_types:
    token = self.curr_token

    if token.type == BEGIN_LOOP_CMD:
    node = self._while_loop()
    elif token.type == END_LOOP_CMD:
    return commands
    else:
    node = self._terminal_command()

    # Append to list of commands
    commands.append(node)

    return commands

    def _while_loop(self):
    """
    while_loop : BEGIN_LOOP_CMD program END_LOOP_CMD
    """
    if self.curr_token is None:
    raise ValueError("Please run parse method with an input program text first!")

    self.consume(BEGIN_LOOP_CMD)
    node = (LOOP_CMD, self._program())
    self.consume(END_LOOP_CMD)
    return node

    def _terminal_command(self):
    """
    terminal_command : READ_CMD
    | PRINT_CMD
    | INC_CELL_CMD
    | DEC_CELL_CMD
    | MOV_PTR_NEXT_CMD
    | MOV_PTR_PREV_CMD
    """
    if self.curr_token is None:
    raise ValueError("Please run parse method with an input program text first!")

    token = self.curr_token
    node = (token.type,)
    self.consume(token.type)
    return node


    # #########################################################################################
    # #
    # INTERPRETER and RUNTIME #
    # #
    # #########################################################################################
    class BrainFuckTape(object):
    def __init__(self):
    self.map = {}

    def __getitem__(self, index):
    # return 0 if index was not in self.map
    return self.map.get(index, 0)

    def __setitem__(self, index, value):
    self.map[index] = value

    ###
    # CONSTANTS
    MAX_INDEX = 30000

    ###
    # GLOBAL TAPE STATES
    INDEX = 0
    THE_TAPE = BrainFuckTape()


    def bf_eval(expressions):
    global INDEX
    global MAX_INDEX
    global THE_TAPE

    for node in expressions:
    if not node:
    return None
    elif node[0] == MOV_PTR_PREV_CMD:
    if INDEX > 0:
    INDEX -= 1
    else:
    raise Exception("Index out of array bounds!")
    elif node[0] == MOV_PTR_NEXT_CMD:
    if INDEX < MAX_INDEX:
    INDEX += 1
    else:
    raise Exception("Index out of array bounds!")
    elif node[0] == PRINT_CMD:
    print(chr(THE_TAPE[INDEX]), sep='', end='')
    elif node[0] == READ_CMD:
    str_input = str(input())
    if len(str_input) > 0:
    THE_TAPE[INDEX] = ord(str_input[0])
    elif node[0] == INC_CELL_CMD:
    THE_TAPE[INDEX] += 1
    elif node[0] == DEC_CELL_CMD:
    THE_TAPE[INDEX] -= 1
    elif node[0] == LOOP_CMD:
    while THE_TAPE[INDEX] != 0:
    bf_eval(node[1])


    def bf_interpret(program_text):
    try:
    brainfuck_parser = Parser()
    bfcode = bfcode = brainfuck_parser.parse(program_text)
    bf_eval(bfcode)
    except (KeyboardInterrupt, SystemExit):
    print("\r\nReceived KeyboardInterrupt, stopping...")
    print("Goodbye!")
    sys.exit(0)
    except Exception as err:
    raise


    def bf_repl():
    print("BrainFuck on Python!")
    print("Type :clear to clear the screen. Type :quit to exit REPL.")

    # Initialize the parser
    brainfuck_parser = Parser()

    while True:
    try:
    program = input('BrainFuck> ')
    except (EOFError, KeyboardInterrupt, SystemExit):
    break

    if not program:
    continue

    if program == ":clear":
    subprocess.call("clear")
    elif program == ":quit":
    break
    else:
    try:
    bfcode = brainfuck_parser.parse(program_text)
    bf_eval(bfcode)
    print()
    except (KeyboardInterrupt, SystemExit):
    print("\r\nReceived KeyboardInterrupt, stopping...")
    break
    except Exception as err:
    print("An error has occurred: {!r}".format(err))

    print("\r\nGoodbye!")



    # #########################################################################################
    # #
    # MAIN PROGRAM ENTRY #
    # #
    # #########################################################################################
    def create_argparser():
    arg_parser = argparse.ArgumentParser(prog="brainfuck")

    # --repl argument
    arg_parser.add_argument(
    "--repl",
    action="store_true", default=False,
    help="BrainFuck REPL: interactive mode.")

    # --program argument
    arg_parser.add_argument(
    "--program",
    type=str, nargs=1, default=None,
    help="Path to a BrainFuck program file, for example: ./brainfuck --program ~/hello_world.bf")

    # --code argument
    arg_parser.add_argument(
    "--code",
    type=str, nargs=1, default=None,
    help="A double-quoted BrainFuck source code program, for example: ./brainfuck --code \"++++ ++++\"")

    return arg_parser


    def main():
    arg_parser = create_argparser()
    args = arg_parser.parse_args()

    # First, --repl has the highest priority
    if args.repl is True:
    bf_repl()
    # Second, --program has the second highest priority.
    elif args.program is not None and len(args.program) > 0:
    program_code = ""
    try:
    with open(args.program[0], mode="r", encoding="utf-8") as file:
    program_code = file.read()
    except (IOError, FileNotFoundError):
    print("Error: file was not found!")
    sys.exit(1)
    bf_interpret(program_code)
    print("")
    # Third, --code has the lowest priority.
    elif args.code is not None and len(args.code) > 0:
    bf_interpret(args.code[0])
    print("")
    # Nothing was specified, print help.
    else:
    arg_parser.print_help()


    if __name__ == "__main__":
    main()

    316 changes: 316 additions & 0 deletions brainfuck_llyy
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,316 @@
    #!/usr/bin/env python3

    # -----------------------------------------------------------------------------------------
    # brainfuck_llyy
    #
    # @author: Ahmad Alhour (aalhour.com).
    # @description: BrainFuck Interpreter and REPL in Python 3, using PLY (Python lex & yacc).
    #
    # @requirements: ply==3.8
    # @pre-install: pip3 install ply==3.8
    #
    # @usage:
    # ./brainfuck --help Prints help and exits
    # ./brainfuck --repl Opens REPL
    # ./brainfuck --program ~/some_program.bf Runs code in ~/some_program.bf
    # ./brainfuck --code "++++++[>++++++++++<-]>+++++." Evalues a piece of code
    # -----------------------------------------------------------------------------------------

    # std
    import sys
    import subprocess
    import argparse

    # ply 3.8
    import ply.lex as lex
    import ply.yacc as yacc


    # #########################################################################################
    # #
    # LEX SCANNER #
    # #
    # #########################################################################################
    tokens = [
    'MOV_LEFT',
    'MOV_RIGHT',
    'PRINT',
    'READ',
    'INC',
    'DEC',
    'JMP_FWR',
    'JMP_BKW'
    ]

    # Single line comment
    t_ignore_SINGLE_LINE_COMMENT = r"\#[^\n]*"

    # Tokenization Rules of Brain Fuck
    t_MOV_LEFT = r'\<'
    t_MOV_RIGHT = r'\>'
    t_PRINT = r'\.'
    t_READ = r','
    t_INC = r'\+'
    t_DEC = r'\-'
    t_JMP_FWR = r'\['
    t_JMP_BKW = r'\]'

    # Newlines Rule
    def t_newline(token):
    r"\n+"
    token.lexer.lineno += len(token.value)

    # Ignore Whitespae Rule
    t_ignore = ' \t\r\f'

    def t_error(t):
    t.lexer.skip(1)


    # Build lex-lexer
    lexer = lex.lex(optimize=True, outputdir="lexyacctabs", lextab="lexyacctabs.lextab")


    # #########################################################################################
    # #
    # YACC PARSER #
    # #
    # #########################################################################################
    # YACC Error list
    error_list = []


    def p_start(p):
    """
    start : program
    | empty
    """
    if p is not None:
    p[0] = p[1]


    def p_program_many(p):
    """
    program : program single_inst
    | program loop_inst
    """
    p[0] = p[1] + [p[2]]


    def p_program_single(p):
    """
    program : single_inst
    | loop_inst
    """
    p[0] = [p[1]]


    def p_single_inst(p):
    """
    single_inst : MOV_LEFT
    | MOV_RIGHT
    | PRINT
    | READ
    | INC
    | DEC
    """
    if p[1] == '<':
    p[0] = ('MOV_LEFT',)
    elif p[1] == '>':
    p[0] = ('MOV_RIGHT',)
    elif p[1] == '.':
    p[0] = ('PRINT',)
    elif p[1] == ',':
    p[0] = ('READ',)
    elif p[1] == '+':
    p[0] = ('INC',)
    elif p[1] == '-':
    p[0] = ('DEC',)


    def p_loop_inst(p):
    """
    loop_inst : JMP_FWR program JMP_BKW
    """
    p[0] = ('LOOP', p[2])


    def p_error(p):
    if p is None:
    print("Error! Unexpected end of input!")
    else:
    error = "Syntax error! Line: {}, position: {}, character: {}, type: {}".format(
    p.lineno, p.lexpos, p.value, p.type)
    error_list.append(error)
    parser.errok()


    def p_empty(p):
    """
    empty :
    """
    return None

    # Build yacc-parser
    parser = yacc.yacc(optimize=True, outputdir="lexyacctabs", tabmodule="lexyacctabs.yacctab")


    # #########################################################################################
    # #
    # INTERPRETER #
    # #
    # #########################################################################################
    class BrainFuckTape:
    def __init__(self):
    self.map = {}

    def __getitem__(self, index):
    # return 0 if index was not in self.map
    return self.map.get(index, 0)

    def __setitem__(self, index, value):
    self.map[index] = value

    ###
    # CONSTANTS
    MAX_INDEX = 30000

    ###
    # GLOBAL TAPE STATES
    INDEX = 0
    THE_TAPE = BrainFuckTape()


    def bf_eval(expressions):
    global THE_TAPE
    global INDEX
    global MAX_INDEX

    for node in expressions:
    if not node:
    return None
    elif node[0] == 'MOV_LEFT':
    if INDEX > 0:
    INDEX -= 1
    else:
    raise Exception("Index out of array bounds!")
    elif node[0] == 'MOV_RIGHT':
    if INDEX < MAX_INDEX:
    INDEX += 1
    else:
    raise Exception("Index out of array bounds!")
    elif node[0] == 'PRINT':
    print(chr(THE_TAPE[INDEX]), sep='', end='')
    elif node[0] == 'READ':
    str_input = str(input())
    if len(str_input) > 0:
    THE_TAPE[INDEX] = ord(str_input[0])
    elif node[0] == 'INC':
    THE_TAPE[INDEX] += 1
    elif node[0] == 'DEC':
    THE_TAPE[INDEX] -= 1
    elif node[0] == 'LOOP':
    while THE_TAPE[INDEX] != 0:
    bf_eval(node[1])


    def bf_interpret(program_text):
    try:
    bfcode = parser.parse(program_text)
    bf_eval(bfcode)
    except (KeyboardInterrupt, SystemExit):
    print("\r\nReceived KeyboardInterrupt, stopping...")
    print("Goodbye!")
    sys.exit(0)
    except Exception as err:
    raise


    def bf_repl():
    print("BrainFuck on Python!")
    print("Type :clear to clear the screen. Type :quit to exit REPL.")
    while True:
    try:
    program = input('BrainFuck> ')
    except (EOFError, KeyboardInterrupt, SystemExit):
    break

    if not program:
    continue

    if program == ":clear":
    subprocess.call("clear")
    elif program == ":quit":
    break
    else:
    try:
    bf_interpret(program)
    print()
    except (KeyboardInterrupt, SystemExit):
    print("\r\nReceived KeyboardInterrupt, stopping...")
    break
    except Exception as err:
    print("An error has occurred: {!r}".format(err))

    print("\r\nGoodbye!")


    # #########################################################################################
    # #
    # MAIN PROGRAM ENTRY #
    # #
    # #########################################################################################
    def create_argparser():
    arg_parser = argparse.ArgumentParser(prog="brainfuck")

    # --repl argument
    arg_parser.add_argument(
    "--repl",
    action="store_true", default=False,
    help="BrainFuck REPL: interactive mode.")

    # --program argument
    arg_parser.add_argument(
    "--program",
    type=str, nargs=1, default=None,
    help="Path to a BrainFuck program file, for example: ./brainfuck --program ~/hello_world.bf")

    # --code argument
    arg_parser.add_argument(
    "--code",
    type=str, nargs=1, default=None,
    help="A double-quoted BrainFuck source code program, for example: ./brainfuck --code \"++++ ++++\"")

    return arg_parser


    def main():
    arg_parser = create_argparser()
    args = arg_parser.parse_args()

    # First, --repl has the highest priority
    if args.repl is True:
    bf_repl()
    # Second, --program has the second highest priority.
    elif args.program is not None and len(args.program) > 0:
    program_code = ""
    try:
    with open(args.program[0], mode="r", encoding="utf-8") as file:
    program_code = file.read()
    except (IOError, FileNotFoundError):
    print("Error: file was not found!")
    sys.exit(1)
    bf_interpret(program_code)
    print("")
    # Third, --code has the lowest priority.
    elif args.code is not None and len(args.code) > 0:
    bf_interpret(args.code[0])
    print("")
    # Nothing was specified, print help.
    else:
    arg_parser.print_help()


    if __name__ == "__main__":
    main()