예제 #1
0
class MinerProtocol(Protocol):
    """ implementation of the Minecraft protocol """
    def __init__(self, name):
        global debug
        self.log = debug
        self.bot = MinerBot(name, self.send)
        self.buffer = Buffer()
        self.world = World()
        self.packets = {
            #     ID: (function, format
            0x00: (self.onKeepAlive, Standard("Bi")),  # Keep alive
            0x01: (self.onLoginRequest, String("BiSqibbBB")),  # Login request
            0x02: (self.onHandshake, String("BS")),  # Handshake
            0x03: (self.onChat, String("BS")),  # Chat message
            0x04: (None, Standard("Bq")),  # Time update 
            0x05: (None, Standard("Bihhh")),  # Entity Equipment
            0x06: (self.onSpawnPosition, Standard("Biii")),  # Spawn position
            0x07: (None, Standard("Bii?")),  # Use entity  (C --> S)
            0x08: (self.onUpdateHealth, Standard("Bhhf")),  # Update health
            0x09: (None, Standard("Bbbbhl")),  # Respawn  (C --> S)
            0x0A: (None, Standard("B?")),  # Player  (C --> S)
            0x0B: (None, Standard("Bdddd?")),  # Player position  (C --> S)
            0x0C: (None, Standard("Bff?")),  # Player look  (C --> S)
            0x0D: (self.onPLayerPosition,
                   Standard("Bddddff?")),  # Player position & look
            0x0E: (None, Standard("Bbibib")),  #  Player digging  (C --> S)
            0x0F:
            (None, BlockSlot("BibibW")),  # Player block placement  (C --> S)
            0x10: (None, Standard("Bh")),  # Holding change  (C --> S)
            0x11: (None, Standard("Bibibi")),  # Use bed
            0x12: (None, Standard("Bib")),  # Animation
            0x13: (None, Standard("Bib")),  # Entity action  (C --> S)
            0x14: (self.onNamedEntitySpawn,
                   String("BiSiiibbh")),  # Named entity spawn
            0x15: (None, Standard("Bihbhiiibbb")),  # Pickup spawn
            0x16: (None, Standard("Bii")),  # Collect item
            0x17:
            (self.onAddObject, Standard("Bibiiiihhh")),  # Add object/vehicle
            0x18: (None, MetaEntity("BibiiibbE")),  # Mob spawn
            0x19: (None, String("BiSiiii")),  # Entity: painting
            0x1A: (None, Standard("Biiiih")),  # Experience Orb
            0x1B: (None, Standard("Bffff??")),  # Stance update
            0x1C: (None, Standard("Bihhh")),  # Entity velocity
            0x1D: (None, Standard("Bi")),  # Destroy entity
            0x1E: (None, Standard("Bi")),  # Entity
            0x1F: (self.onEntityRelativeMove,
                   Standard("Bibbb")),  # Entity relative move
            0x20: (None, Standard("Bibb")),  # Entity look
            0x21: (None, Standard("Bibbbbb")),  # Entity look and relative move
            0x22: (None, Standard("Biiiibb")),  # Entity teleport
            0x26: (None, Standard("Bib")),  # Entity status
            0x27: (None, Standard("Bii")),  # Attach entity
            0x28: (None, MetaEntity("BiE")),  # Entity metadata
            0x29: (None, Standard("Bibbh")),  # Entity Effect
            0x2A: (None, Standard("Bib")),  # Remove Entity Effect
            0x2B: (None, Standard("Bfhh")),  # Experience
            0x32: (self.onPreChunk, Standard("Bii?")),  # Pre chunk
            0x33: (self.onChunk, MetaChunk()),  # Map chunk
            0x34: (None, BlockArray()),  # Multi-block change
            0x35: (None, Standard("Bibibb")),  # Block change
            0x36: (None, Standard("Bihibb")),  # Block action
            0x3C: (None, BlockExplosion()),  # Explosion
            0x3D: (None, Standard("Biibii")),  # Sound effect
            0x46: (None, Standard("Bbb")),  # New/invalid state
            0x47: (None, Standard("Bi?iii")),  # Thunderbolt
            0x64: (None, String("BbbSb")),  # Open window
            0x65: (None, Standard("Bb")),  # Close window
            0x66: (None, BlockSlot("Bbhbh?W")),  # Window click
            0x67: (None, BlockSlot("BbhW")),  # Set slot
            0x68: (self.onWindowItems, Window()),  # Window items
            0x69: (None, Standard("Bbhh")),  # Update window property
            0x6A: (self.onTransaction, Standard("Bbh?")),  # Transaction
            0x6B: (None, BlockSlot("BhW")),  # Creative inventory action 
            0x6C: (None, Standard("Bbb")),  # Enchant Item
            0x82: (None, String("BihiSSSS")),  # Update sign
            0x83: (None, ByteArray()),  # item data
            0xC8: (None, Standard("Bib")),  # Increment statistic
            0xC9: (self.onPlayerList, String("BS?h")),  # Player List Item
            0xFF: (self.onDisconnected, String("BS")),  # Disconnect
        }

    def connectionMade(self):
        """ called when a connection has been established """
        self.log.info("<%s> connected" % self.bot.getName())
        #        self.writer = PacketWriter(self.transport)
        self.log.debug("ATTEMPTING TO HANDSHAKE")
        self.sendHandshake(self.bot.getName())

    def connectionLost(self, reason):
        self.log.error("connection lost %s" % reason)

    def dataReceived(self, data):
        """ new data are received """
        self.buffer.append(data)

        while True:
            # get the packet Type
            try:
                data = self.buffer.peek()[0]
                packetType = ord(data)
            except IOError:  # if empty buffer stop the loop
                break
            self.log.received(packetType)

            # get the packet information
            if packetType not in self.packets:
                self.log.error("Unknown packet 0x%02X" % packetType)
                self.transport.loseConnection()

            fct, fmt = self.packets[packetType]
            try:
                packet = fmt.unpack(self.buffer)
            except IOError:
                break

            # do action on packet received
            if fct != None:
                fct(self.bot, packet)

    def onDisconnected(self, bot, info):
        self.log.info("Disconnected by server. Reason=%s" % info[1])

    def onKeepAlive(self, bot, info):
        self.send(0x00, 0)

    def onUpdateHealth(self, bot, info):
        bot.setHealth(health=info[1], food=info[2], foodSaturation=info[3])

    def onLoginRequest(self, bot, info):
        bot.setUID(info[1])
        #bot.setMaxSeed(info[3])
        pass

    def onHandshake(self, bot, info):
        #bot.setConnectionHash(info[1])
        self.sendLoginRequest(22, self.bot.getName())
        self.bot.nameToUpper()

    def onSpawnPosition(self, bot, info):
        bot.setSpawnPosition(info)

    def onPLayerPosition(self, bot, info):
        cmd, x, stance, y, z, yaw, pitch, onGround = info
        bot.setPlayerPosition(x, y, z, stance, yaw, pitch, onGround)
        self.send(0x0D, x, y, stance, z, yaw, pitch, onGround)

    def onEntityRelativeMove(self, bot, info):
        bot.setEntityRelativeMove(info)

    def onWindowItems(self, bot, info):
        pass
#        if info[1] == 0:
#            # inventory window
#            bot.setInventory(info[2], info[3])
#        else:
#            logging.error("Unknown window number=%d" % info[1])

#    def onSetSlot(self, bot, info):
#        if info[1] == 0:
#            bot.setSlotItem(info[1], info[2], info[3], info[4], info[5])
#            logging.debug("SetSlot %d, slot %d" %(info[1], info[2]))

    def onNamedEntitySpawn(self, bot, info):
        bot.setPlayerSpawn(info)

    def onAddObject(self):
        pass

    def onPreChunk(self, bot, info):
        pass

    def onChunk(self, bot, info):
        cmd, x, y, z, sx, sy, sz, compSize, chunkData = info
        # correct the real size
        sx += 1
        sy += 1
        sz += 1
        self.log.chunk(
            "Chunk (x,y,z)=(%d, %d, %d) (sx,sy,sz)=(%d, %d, %d), len=%d" %
            (x, y, z, sx, sy, sz, len(chunkData)))
        self.world.addChunk(x, y, z, sx, sy, sz, chunkData)

    def onTransaction(self, bot, info):
        pass

    def send(self, cmd, *data):
        if cmd not in self.packets:
            self.log.error("Unknown packet to send 0x%02X" % cmd)
            self.transport.loseConnection()
        fct, pack = self.packets[cmd]
        res = pack.pack(cmd, *data)
        deb = ""
        for x in res:
            deb += "%02X " % ord(x)
        self.transport.write(res)
        self.log.send(cmd)

    def sendDisconnect(self, reason):
        self.send(0xFF, "Disconnection by client")
        self.transport.loseConnection()

    def sendHandshake(self, userName):
        self.send(0x02, userName)

    def sendLoginRequest(self, protocol_version, username):
        self.send(0x01, protocol_version, username, 0, 0, 0, 0, 0, 0)

    def onChat(self, bot, info):
        '''
        Chat received. Analyse it to detect a message from the player 
        '''
        info = info[1].upper()
        if ord(info[0]) == 0xA7:  #skip color code
            info = info[2:]
        # if the message is from a player it starts with <PlayerName>
        result = re.match("<(.*)>(.*)", info)
        if result == None:
            name = ""
            msg = info
        else:
            name = result.group(1)
            msg = result.group(2)
            result = msg.split()
            if BOT_NAME_NEEDED:  # Command starts with the targeted Bot name
                if result[0] == bot.getName():
                    bot.parseCommand(name, result[1:])
            else:
                if name <> bot.getName():  # do not compute self message
                    bot.parseCommand(name, result)

    def onPlayerList(self, bot, info):
        bot.updatePlayerList(info[1], info[2])
예제 #2
0
class MinerProtocol(Protocol):
    """ implementation of the Minecraft protocol """
    def __init__(self, name):
        global debug
        self.log = debug
        self.bot = MinerBot(name, self.send)
        self.buffer = Buffer()
        self.world = World()
        self.packets = {
        #     ID: (function, format
            0x00: (self.onKeepAlive, Standard("Bi")),           # Keep alive
            0x01: (self.onLoginRequest, String("BiSqibbBB")),   # Login request
            0x02: (self.onHandshake, String("BS")),             # Handshake
            0x03: (self.onChat, String("BS")),                  # Chat message
            0x04: (None, Standard("Bq")),                       # Time update 
            0x05: (None, Standard("Bihhh")),                    # Entity Equipment
            0x06: (self.onSpawnPosition, Standard("Biii")),     # Spawn position
            0x07: (None, Standard("Bii?")),                     # Use entity  (C --> S)
            0x08: (self.onUpdateHealth, Standard("Bhhf")),      # Update health
            0x09: (None, Standard("Bbbbhl")),                   # Respawn  (C --> S)
            0x0A: (None, Standard("B?")),                       # Player  (C --> S)
            0x0B: (None, Standard("Bdddd?")),                   # Player position  (C --> S)
            0x0C: (None, Standard("Bff?")),                     # Player look  (C --> S)
            0x0D: (self.onPLayerPosition, Standard("Bddddff?")), # Player position & look
            0x0E: (None, Standard("Bbibib")),                   #  Player digging  (C --> S)
            0x0F: (None, BlockSlot("BibibW")),                  # Player block placement  (C --> S)
            0x10: (None, Standard("Bh")),                       # Holding change  (C --> S)
            0x11: (None, Standard("Bibibi")),                   # Use bed
            0x12: (None, Standard("Bib")),                      # Animation
            0x13: (None, Standard("Bib")),                      # Entity action  (C --> S)
            0x14: (self.onNamedEntitySpawn, String("BiSiiibbh")), # Named entity spawn
            0x15: (None, Standard("Bihbhiiibbb")),              # Pickup spawn
            0x16: (None, Standard("Bii")),                      # Collect item
            0x17: (self.onAddObject, Standard("Bibiiiihhh")),   # Add object/vehicle
            0x18: (None, MetaEntity("BibiiibbE")),              # Mob spawn
            0x19: (None, String("BiSiiii")),                    # Entity: painting
            0x1A: (None, Standard("Biiiih")),                   # Experience Orb
            0x1B: (None, Standard("Bffff??")),                  # Stance update
            0x1C: (None, Standard("Bihhh")),                    # Entity velocity
            0x1D: (None, Standard("Bi")),                       # Destroy entity
            0x1E: (None, Standard("Bi")),                       # Entity
            0x1F: (self.onEntityRelativeMove, Standard("Bibbb")), # Entity relative move
            0x20: (None, Standard("Bibb")),                     # Entity look
            0x21: (None, Standard("Bibbbbb")),                  # Entity look and relative move
            0x22: (None, Standard("Biiiibb")),                  # Entity teleport
            0x26: (None, Standard("Bib")),                      # Entity status
            0x27: (None, Standard("Bii")),                      # Attach entity
            0x28: (None, MetaEntity("BiE")),                    # Entity metadata
            0x29: (None, Standard("Bibbh")),                    # Entity Effect
            0x2A: (None, Standard("Bib")),                      # Remove Entity Effect
            0x2B: (None, Standard("Bfhh")),                     # Experience
            0x32: (self.onPreChunk, Standard("Bii?")),          # Pre chunk
            0x33: (self.onChunk, MetaChunk()),                  # Map chunk
            0x34: (None, BlockArray()),                         # Multi-block change
            0x35: (None, Standard("Bibibb")),                   # Block change
            0x36: (None, Standard("Bihibb")),                   # Block action
            0x3C: (None, BlockExplosion()),                     # Explosion
            0x3D: (None, Standard("Biibii")),                   # Sound effect
            0x46: (None, Standard("Bbb")),                      # New/invalid state
            0x47: (None, Standard("Bi?iii")),                   # Thunderbolt
            0x64: (None, String("BbbSb")),                      # Open window
            0x65: (None, Standard("Bb")),                       # Close window
            0x66: (None, BlockSlot("Bbhbh?W")),                 # Window click
            0x67: (None, BlockSlot("BbhW")),                    # Set slot
            0x68: (self.onWindowItems, Window()),               # Window items
            0x69: (None, Standard("Bbhh")),                     # Update window property
            0x6A: (self.onTransaction, Standard("Bbh?")),       # Transaction
            0x6B: (None, BlockSlot("BhW")),                     # Creative inventory action 
            0x6C: (None, Standard("Bbb")),                      # Enchant Item
            0x82: (None, String("BihiSSSS")),                   # Update sign
            0x83: (None, ByteArray()),                          # item data
            0xC8: (None, Standard("Bib")),                      # Increment statistic
            0xC9: (self.onPlayerList, String("BS?h")),          # Player List Item
            0xFF: (self.onDisconnected, String("BS")),          # Disconnect
        }


    def connectionMade(self):
        """ called when a connection has been established """
        self.log.info("<%s> connected" % self.bot.getName())
#        self.writer = PacketWriter(self.transport)
        self.log.debug("ATTEMPTING TO HANDSHAKE")
        self.sendHandshake(self.bot.getName())

    def connectionLost(self, reason):
        self.log.error("connection lost %s" % reason)

    def dataReceived(self, data):
        """ new data are received """ 
        self.buffer.append(data)

        while True:
            # get the packet Type
            try:
                data = self.buffer.peek()[0]
                packetType = ord(data)
            except IOError:   # if empty buffer stop the loop
                break
            self.log.received(packetType)

            # get the packet information
            if packetType not in self.packets:
                self.log.error("Unknown packet 0x%02X" % packetType)
                self.transport.loseConnection()
                
            fct, fmt = self.packets[packetType]
            try:
                packet = fmt.unpack(self.buffer)
            except IOError:
                break

            # do action on packet received
            if fct!= None:
                fct(self.bot, packet) 
    
  
    def onDisconnected(self, bot, info):
        self.log.info("Disconnected by server. Reason=%s" % info[1])

    def onKeepAlive(self, bot, info):
        self.send(0x00, 0)
    
    def onUpdateHealth(self, bot, info):
        bot.setHealth(health=info[1], food=info[2], foodSaturation=info[3])
        
    def onLoginRequest(self, bot, info):
        bot.setUID(info[1])
        #bot.setMaxSeed(info[3])
        pass
    
    def onHandshake(self, bot, info):
        #bot.setConnectionHash(info[1])
        self.sendLoginRequest(22, self.bot.getName())
        self.bot.nameToUpper()

    
    def onSpawnPosition(self, bot, info):
        bot.setSpawnPosition(info)
    
    def onPLayerPosition(self, bot, info):
        cmd, x, stance, y, z, yaw, pitch, onGround = info
        bot.setPlayerPosition(x, y, z, stance, yaw, pitch, onGround)
        self.send(0x0D, x, y, stance, z, yaw, pitch, onGround)

    def onEntityRelativeMove(self, bot, info):
        bot.setEntityRelativeMove(info)
    
    def onWindowItems(self, bot, info):
        pass
#        if info[1] == 0:
#            # inventory window
#            bot.setInventory(info[2], info[3])
#        else:
#            logging.error("Unknown window number=%d" % info[1])

#    def onSetSlot(self, bot, info):
#        if info[1] == 0:
#            bot.setSlotItem(info[1], info[2], info[3], info[4], info[5])
#            logging.debug("SetSlot %d, slot %d" %(info[1], info[2]))
    
    def onNamedEntitySpawn(self, bot, info):
        bot.setPlayerSpawn(info)
        
    def onAddObject(self):
        pass
    
    def onPreChunk(self, bot, info):
        pass
    
    def onChunk(self, bot, info):
        cmd, x, y, z, sx, sy, sz, compSize, chunkData = info
        # correct the real size
        sx += 1
        sy += 1
        sz += 1
        self.log.chunk("Chunk (x,y,z)=(%d, %d, %d) (sx,sy,sz)=(%d, %d, %d), len=%d" % (x, y, z, sx, sy, sz, len(chunkData)))
        self.world.addChunk(x, y, z, sx, sy, sz, chunkData)
    
    def onTransaction(self, bot, info):
        pass 
    
    def send(self, cmd, *data):
        if cmd not in self.packets:
            self.log.error("Unknown packet to send 0x%02X" % cmd)
            self.transport.loseConnection()
        fct, pack = self.packets[cmd]
        res = pack.pack(cmd, *data)
        deb = ""
        for x in res:
            deb += "%02X " % ord(x)
        self.transport.write(res)
        self.log.send(cmd)
    
    def sendDisconnect(self, reason):
        self.send(0xFF, "Disconnection by client")
        self.transport.loseConnection()

    def sendHandshake(self, userName):
        self.send(0x02, userName)

    def sendLoginRequest(self, protocol_version, username):
        self.send(0x01, protocol_version, username, 0, 0, 0, 0, 0, 0)
   
    def onChat(self, bot, info):
        '''
        Chat received. Analyse it to detect a message from the player 
        '''
        info = info[1].upper()
        if ord(info[0]) == 0xA7: #skip color code
            info = info[2:]
        # if the message is from a player it starts with <PlayerName>
        result = re.match("<(.*)>(.*)", info)
        if result == None:
            name = ""
            msg = info
        else:
            name = result.group(1)
            msg = result.group(2)
            result = msg.split()
            if BOT_NAME_NEEDED: # Command starts with the targeted Bot name
                if result[0] == bot.getName():
                    bot.parseCommand(name, result[1:])
            else:
                if name <> bot.getName():  # do not compute self message
                    bot.parseCommand(name, result)
                
    def onPlayerList(self, bot, info):
        bot.updatePlayerList(info[1], info[2])
예제 #3
0
파일: Parser.py 프로젝트: ArtanisCV/Mercury
class Parser(object):
    def __init__(self, lexer, error_handler=None):
        self.input = Buffer(lexer.tokenize())
        self.error_handler = error_handler

        self.is_eof = lambda t: isinstance(t, EOFToken)
        self.is_def = lambda t: isinstance(t, DefToken)
        self.is_extern = lambda t: isinstance(t, ExternToken)
        self.is_if = lambda t: isinstance(t, IfToken)
        self.is_then = lambda t: isinstance(t, ThenToken)
        self.is_else = lambda t: isinstance(t, ElseToken)
        self.is_for = lambda t: isinstance(t, ForToken)
        self.is_in = lambda t: isinstance(t, InToken)
        self.is_var = lambda t: isinstance(t, VarToken)
        self.is_identifer = lambda t: isinstance(t, IdentifierToken)
        self.is_number = lambda t: isinstance(t, NumberToken)
        self.is_character = lambda t: isinstance(t, CharacterToken)
        self.is_binary = lambda t: isinstance(t, BinaryToken)
        self.is_unary = lambda t: isinstance(t, UnaryToken)
        self.is_binop = lambda t: OperatorManager.is_binop(t)
        self.is_unop = lambda t: OperatorManager.is_unop(t)

    def collect(self):
        return self.input.accept()

    def look(self, condition=None):
        return self.input.peek(condition)

    def expect(self, condition=None):
        return self.input.move(condition)

    def try_number_expr(self):
        """
        number_expr ::= number
        """

        number = self.expect(self.is_number)
        if number is None:
            return None

        return NumberExprNode(number)

    def try_paren_expr(self):
        """
        paren_expr ::= '(' expr ')'
        """

        left_paren = self.expect(lambda t: t.name == '(')
        if left_paren is None:
            return None

        expr = self.try_expr()

        right_paren = self.expect(lambda t: t.name == ')')
        if right_paren is None:
            raise ExpectedRightParen(left_paren.line if expr is None else expr.line)

        return expr

    def try_identifier_expr(self):
        """
        identifier_expr
            ::= identifier
            ::= identifier '(' ')'
            ::= identifier '(' expr (',' expr)* ')'
        """

        identifier = self.expect(self.is_identifer)
        if identifier is None:
            return None

        left_paren = self.expect(lambda t: t.name == '(')
        if left_paren is None:
            return VariableExprNode(identifier)
        else:
            args = []

            while True:
                arg = self.try_expr()
                if arg is not None:
                    args.append(arg)

                    self.expect(lambda t: t.name == ',')
                else:
                    right_paren = self.expect(lambda t: t.name == ')')
                    if right_paren is None:
                        raise ExpectedRightParen(left_paren.line if len(args) == 0 else args[-1].line)

                    return CallExprNode(identifier, args)

    def try_if_expr(self):
        """
        if_expr ::= if expr then expr else expr
        """

        token = self.expect(self.is_if)
        if token is None:
            return None

        condition = self.try_expr()
        if condition is None:
            raise ExpectedExpr(token.line)

        token = self.expect(self.is_then)
        if token is None:
            raise ExpectedThen(condition.line)

        true = self.try_expr()
        if true is None:
            raise ExpectedExpr(token.line)

        token = self.expect(self.is_else)
        if token is None:
            raise ExpectedElse(true.line)

        false = self.try_expr()
        if false is None:
            raise ExpectedExpr(token.line)

        return IfExprNode(condition, true, false)

    def try_for_expr(self):
        """
        for_expr ::= for identifier '=' expr ',' expr (',' expr)? in expr
        """

        token = self.expect(self.is_for)
        if token is None:
            return None

        variable = self.expect(self.is_identifer)
        if variable is None:
            raise ExpectedIdentifier(token.line)

        token = self.expect(lambda t: t.name == '=')
        if token is None:
            raise ExpectedEqualSign(variable.line)

        begin = self.try_expr()
        if begin is None:
            raise ExpectedExpr(token.line)

        token = self.expect(lambda t: t.name == ',')
        if token is None:
            raise ExpectedComma(begin.line)

        end = self.try_expr()
        if end is None:
            raise ExpectedExpr(token.line)

        token = self.expect(lambda t: t.name == ',')
        if token is not None:
            step = self.try_expr()
            if step is None:
                raise ExpectedExpr(token.line)
        else:
            step = None

        token = self.expect(self.is_in)
        if token is None:
            raise ExpectedIn(end.line if step is None else step.line)

        body = self.try_expr()
        if body is None:
            raise ExpectedExpr(token.line)

        return ForExprNode(variable, begin, end, step, body)

    def try_var_expr(self):
        """
        var_expr ::= var identifier ('=' expr)? (',' identifier ('=' expr)?)* in expr
        """

        var_token = self.expect(self.is_var)
        if var_token is None:
            return None

        variables = {}
        while True:
            identifier = self.expect(self.is_identifer)
            if identifier is None:
                break

            assignment = self.expect(lambda t: t.name == '=')
            if assignment is not None:
                expr = self.try_expr()
                if expr is None:
                    raise ExpectedExpr(assignment.line)

                variables[identifier] = expr
            else:
                variables[identifier] = None

            self.expect(lambda t: t.name == ',')

        if len(variables) == 0:
            raise ExpectedVariableList(var_token.line)

        in_token = self.expect(self.is_in)
        if in_token is None:
            raise ExpectedIn(max([var.line for var in variables.keys()]))

        body = self.try_expr()
        if body is None:
            raise ExpectedExpr(in_token.line)

        return VarExprNode(variables, body)

    def try_primary_expr(self):
        """
        primary_expr
            ::= number_expr
            ::= paren_expr
            ::= identifier_expr
            ::= if_expr
            ::= for_expr
            ::= var_expr
        """

        expr = self.try_number_expr()
        if expr is not None:
            return expr

        expr = self.try_paren_expr()
        if expr is not None:
            return expr

        expr = self.try_identifier_expr()
        if expr is not None:
            return expr

        expr = self.try_if_expr()
        if expr is not None:
            return expr

        expr = self.try_for_expr()
        if expr is not None:
            return expr

        return self.try_var_expr()

    def try_unary_expr(self):
        """
        unary_expr
            ::= primary_expr
            ::= unop unary_expr
        """

        unop = self.expect(self.is_unop)
        if unop is None:
            return self.try_primary_expr()

        operand = self.try_unary_expr()
        if operand is None:
            raise ExpectedOperand(unop.line)

        return UnaryExprNode(unop, operand)

    def try_binop_rhs(self, lhs, lhs_prec):
        """
        binop_rhs ::= (binop unary_expr)*
        """

        while True:
            binop = self.look(self.is_binop)
            if binop is None:
                return lhs

            prec = OperatorManager.get_binop_precedence(binop)
            if prec < lhs_prec:
                return lhs

            self.expect(self.is_binop)  # eat binop

            rhs = self.try_unary_expr()
            if rhs is None:
                raise ExpectedUnaryExpr(binop.line)

            next_binop = self.look(self.is_binop)
            if next_binop is not None and \
               OperatorManager.get_binop_precedence(next_binop) > prec:
                rhs = self.try_binop_rhs(rhs, prec + 1)

            lhs = BinaryExprNode(binop, lhs, rhs)

    def try_expr(self):
        """
        expr ::= unary_expr binop_rhs
        """

        lhs = self.try_unary_expr()
        if lhs is None:
            return None

        return self.try_binop_rhs(lhs, 0)

    def try_normal_prototype(self):
        """
        normal_prototype ::= identifier '(' identifier* ')'
        """

        identifier = self.expect(self.is_identifer)
        if identifier is None:
            return None

        left_paren = self.expect(lambda t: t.name == '(')
        if left_paren is None:
            raise ExpectedLeftParen(identifier.line)

        args = []
        while True:
            arg = self.expect(self.is_identifer)
            if arg is not None:
                args.append(arg)
            else:
                right_paren = self.expect(lambda t: t.name == ')')
                if right_paren is None:
                    raise ExpectedRightParen(left_paren.line if len(args) == 0 else args[-1].line)

                return PrototypeNode(identifier, args)

    def try_binary_prototype(self):
        """
        binary_prototype ::= binary character number? '(' identifier identifier ')'
        """

        binary = self.expect(self.is_binary)
        if binary is None:
            return None

        operator = self.expect(self.is_character)
        if operator is None:
            raise ExpectedOperator(binary.line)

        precedence = self.expect(self.is_number)

        left_paren = self.expect(lambda t: t.name == '(')
        if left_paren is None:
            raise ExpectedLeftParen(operator.line if precedence is None else precedence.line)

        arg1 = self.expect(self.is_identifer)
        if arg1 is None:
            raise ExpectedIdentifier(left_paren.line)

        arg2 = self.expect(self.is_identifer)
        if arg2 is None:
            raise ExpectedIdentifier(arg1.line)

        right_paren = self.expect(lambda t: t.name == ')')
        if right_paren is None:
            raise ExpectedRightParen(arg2.line)

        return BinOpPrototypeNode(IdentifierToken(binary.name + operator.name, binary.line),
                                  precedence, [arg1, arg2])

    def try_unary_prototype(self):
        """
        unary_prototype ::= unary character '(' identifier ')'
        """

        unary = self.expect(self.is_unary)
        if unary is None:
            return None

        operator = self.expect(self.is_character)
        if operator is None:
            raise ExpectedOperator(unary.line)

        left_paren = self.expect(lambda t: t.name == '(')
        if left_paren is None:
            raise ExpectedLeftParen(operator.line)

        arg = self.expect(self.is_identifer)
        if arg is None:
            raise ExpectedIdentifier(left_paren.line)

        right_paren = self.expect(lambda t: t.name == ')')
        if right_paren is None:
            raise ExpectedRightParen(arg.line)

        return UnOpPrototypeNode(IdentifierToken(unary.name + operator.name, unary.line), [arg])

    def try_prototype(self):
        """
        prototype
            ::= normal_prototype
            ::= binary_prototype
            ::= unary_prototype
        """

        prototype = self.try_normal_prototype()
        if prototype is not None:
            return prototype

        prototype = self.try_binary_prototype()
        if prototype is not None:
            return prototype

        prototype = self.try_unary_prototype()
        if prototype is not None:
            return prototype

    def try_function(self):
        """
        function ::= def prototype expr
        """

        keyword = self.expect(self.is_def)
        if keyword is None:
            return None

        prototype = self.try_prototype()
        if prototype is None:
            raise ExpectedPrototype(keyword.line)

        expr = self.try_expr()
        if expr is None:
            raise ExpectedExpr(prototype.line)

        return FunctionNode(prototype, expr)

    def try_declaration(self):
        """
        declaration ::= extern prototype
        """

        keyword = self.expect(self.is_extern)
        if keyword is None:
            return None

        prototype = self.try_prototype()
        if prototype is None:
            raise ExpectedPrototype(keyword.line)

        return prototype

    def try_toplevel_expr(self):
        """
        toplevel_expr ::= expr

        we make an anonymous prototype to represent a top-level expr
        """

        expr = self.try_expr()
        if expr is None:
            return None

        return TopLevelExpr(expr)

    def try_negligible_character(self):
        # ignore top-level semicolons
        return self.expect(lambda t: t.name == ';')

    def try_eof(self):
        return self.expect(self.is_eof)

    def try_unknown(self):
        # try to recovery from syntax errors by eating an unknown token
        token = self.expect()
        if token is not None:
            raise UnknownToken(token)

    def parse(self):
        while True:
            try:
                node = self.try_function()
                if node is not None:
                    yield node
                    continue

                node = self.try_declaration()
                if node is not None:
                    yield node
                    continue

                node = self.try_toplevel_expr()
                if node is not None:
                    yield node
                    continue

                if self.try_negligible_character() is not None:
                    continue

                if self.try_eof() is not None:
                    break

                self.try_unknown()
            except BoidaeSyntaxError as e:
                if self.error_handler is not None:
                    self.error_handler(e)