This commit is contained in:
Andreas Koepf 2025-01-30 22:55:04 +01:00
parent 21c47db6c1
commit ebb88e6c6a
24 changed files with 1215 additions and 814 deletions

View file

@ -2,9 +2,9 @@
import argparse
import os
import Interpreter
from Compiler import Compiler
from Compiler import Minify
from Compiler import Compiler, Minify
def process_args():
@ -54,5 +54,5 @@ def compile_file():
Interpreter.brainfuck(brainfuck_code)
if __name__ == '__main__':
if __name__ == "__main__":
compile_file()

View file

@ -1,12 +1,18 @@
#!/usr/bin/env python3
from .Exceptions import BFSyntaxError, BFSemanticError
from .Exceptions import BFSemanticError, BFSyntaxError
from .FunctionCompiler import FunctionCompiler
from .Functions import check_function_exists, get_function_object, insert_function_object
from .General import is_token_literal, get_literal_token_code, unpack_literal_tokens_to_array_dimensions
from .Globals import get_global_variables_size, get_variable_size, get_variable_dimensions, insert_global_variable, create_variable_from_definition
from .General import get_literal_token_code, is_token_literal, unpack_literal_tokens_to_array_dimensions
from .Globals import (
create_variable_from_definition,
get_global_variables_size,
get_variable_dimensions,
get_variable_size,
insert_global_variable,
)
from .Lexical_analyzer import analyze
from .Optimizer import optimize
from .LibraryFunctionCompiler import insert_library_functions
from .Optimizer import optimize
from .Parser import Parser
from .Token import Token
@ -29,20 +35,24 @@ class Compiler:
# returns function named tuple
if self.parser.current_token().type not in [Token.VOID, Token.INT]:
raise BFSemanticError("Function return type can be either void or int, not '%s'" % str(self.parser.current_token()))
raise BFSemanticError(
"Function return type can be either void or int, not '%s'" % str(self.parser.current_token())
)
self.parser.check_next_tokens_are([Token.ID, Token.LPAREN])
# save all tokens of this function
function_name = self.parser.next_token(next_amount=1).data
RPAREN_index = self.parser.find_matching(starting_index=self.parser.current_token_index+2) # first find RPAREN
RPAREN_index = self.parser.find_matching(
starting_index=self.parser.current_token_index + 2
) # first find RPAREN
self.parser.check_next_token_is(Token.LBRACE, starting_index=RPAREN_index)
RBRACE_index = self.parser.find_matching(starting_index=RPAREN_index+1) # then find RBRACE
RBRACE_index = self.parser.find_matching(starting_index=RPAREN_index + 1) # then find RBRACE
# take all tokens between INT and RBRACE and pass them to function object
function_tokens = self.parser.tokens[self.parser.current_token_index:RBRACE_index+1]
function_tokens = self.parser.tokens[self.parser.current_token_index : RBRACE_index + 1]
# skip function definition
self.parser.advance_to_token_at_index(RBRACE_index+1)
self.parser.advance_to_token_at_index(RBRACE_index + 1)
function = FunctionCompiler(function_name, function_tokens)
return function
@ -60,12 +70,12 @@ class Compiler:
# if this is set to True, then the compiler zeros each cell before using it (may generate a lot of unnecessary BF code)
ZERO_CELLS_BEFORE_USE = False
code = '[-]' if ZERO_CELLS_BEFORE_USE else ''
code = "[-]" if ZERO_CELLS_BEFORE_USE else ""
if get_variable_size(variable) > 1: # its an array
if self.parser.current_token().type == Token.SEMICOLON:
# array definition - INT ID (LBRACK NUM RBRACK)+ SEMICOLON
self.parser.advance_token() # skip SEMICOLON
code = (code + '>') * get_variable_size(variable) # advance to after this variable
code = (code + ">") * get_variable_size(variable) # advance to after this variable
return code
elif self.parser.current_token().type == Token.ASSIGN and self.parser.current_token().data == "=":
# array definition and initialization - INT ID (LBRACK NUM RBRACK)+ ASSIGN ((LBRACE ... RBRACE)+|STRING) SEMICOLON
@ -79,25 +89,34 @@ class Compiler:
self.parser.advance_token() # skip SEMICOLON
array_dimensions = get_variable_dimensions(variable)
unpacked_literals_list = unpack_literal_tokens_to_array_dimensions(ID_token, array_dimensions, literal_tokens_list)
unpacked_literals_list = unpack_literal_tokens_to_array_dimensions(
ID_token, array_dimensions, literal_tokens_list
)
for literal in unpacked_literals_list:
code += get_literal_token_code(literal) # evaluate this literal and point to next array element
return code
else:
raise BFSyntaxError("Unexpected %s in array definition. Expected SEMICOLON (;) or ASSIGN (=)" % self.parser.current_token())
raise BFSyntaxError(
"Unexpected %s in array definition. Expected SEMICOLON (;) or ASSIGN (=)"
% self.parser.current_token()
)
elif self.parser.current_token().type == Token.SEMICOLON: # no need to initialize
self.parser.advance_token() # skip SEMICOLON
code += '>' # advance to after this variable
code += ">" # advance to after this variable
else:
self.parser.check_current_token_is(Token.ASSIGN)
if self.parser.current_token().data != "=":
raise BFSyntaxError("Unexpected %s when initializing global variable. Expected ASSIGN (=)" % self.parser.current_token())
raise BFSyntaxError(
"Unexpected %s when initializing global variable. Expected ASSIGN (=)" % self.parser.current_token()
)
self.parser.advance_token() # skip ASSIGN
if not is_token_literal(self.parser.current_token()):
raise BFSemanticError("Unexpected '%s'. expected literal (NUM | CHAR | TRUE | FALSE )" % str(self.parser.current_token()))
raise BFSemanticError(
"Unexpected '%s'. expected literal (NUM | CHAR | TRUE | FALSE )" % str(self.parser.current_token())
)
code += get_literal_token_code(self.parser.current_token())
@ -113,7 +132,7 @@ class Compiler:
When encountering global variable definition - create Variable object
Returns code that initializes global variables and advances the pointer to after them
"""
code = ''
code = ""
token = self.parser.current_token()
while token is not None and token.type in [Token.VOID, Token.INT, Token.SEMICOLON]:
if token.type == Token.SEMICOLON: # can have random semicolons ;)
@ -125,22 +144,31 @@ class Compiler:
if self.parser.next_token(next_amount=2).type == Token.LPAREN:
function = self.create_function_object()
insert_function_object(function)
elif token.type is Token.INT and self.parser.next_token(next_amount=2).type in [Token.SEMICOLON, Token.ASSIGN, Token.LBRACK]:
elif token.type is Token.INT and self.parser.next_token(next_amount=2).type in [
Token.SEMICOLON,
Token.ASSIGN,
Token.LBRACK,
]:
code += self.compile_global_variable_definition()
else:
raise BFSyntaxError("Unexpected '%s' after '%s'. Expected '(' (function definition) or one of: '=', ';', '[' (global variable definition)" % (str(self.parser.next_token(next_amount=2)), str(self.parser.next_token())))
raise BFSyntaxError(
"Unexpected '%s' after '%s'. Expected '(' (function definition) or one of: '=', ';', '[' (global variable definition)"
% (str(self.parser.next_token(next_amount=2)), str(self.parser.next_token()))
)
token = self.parser.current_token()
if self.parser.current_token() is not None: # we have not reached the last token
untouched_tokens = [str(t) for t in self.parser.tokens[self.parser.current_token_index:]]
untouched_tokens = [str(t) for t in self.parser.tokens[self.parser.current_token_index :]]
raise BFSyntaxError("Did not reach the end of the code. Untouched tokens:\n%s" % untouched_tokens)
return code
def compile(self):
insert_library_functions()
code = self.process_global_definitions() # code that initializes global variables and advances pointer to after them
code = (
self.process_global_definitions()
) # code that initializes global variables and advances pointer to after them
check_function_exists(Token(Token.ID, 0, 0, "main"), 0)
code += get_function_object("main").get_code(get_global_variables_size())
@ -159,7 +187,7 @@ def compile(code, optimize_code=False):
return brainfuck_code
if __name__ == '__main__':
if __name__ == "__main__":
print("This file cannot be directly run")
print("Please import it and use the 'compile' function")
print("Which receives a C-like code (string) and returns Brainfuck code (string)")

View file

@ -1,11 +1,28 @@
from collections import namedtuple
from functools import reduce
from .Exceptions import BFSyntaxError, BFSemanticError
from .Exceptions import BFSemanticError, BFSyntaxError
from .Functions import check_function_exists, get_function_object
from .General import get_variable_dimensions_from_token, get_move_to_return_value_cell_code, get_print_string_code, get_variable_from_ID_token
from .General import get_literal_token_value, process_switch_cases, is_token_literal
from .General import (
get_literal_token_value,
get_move_to_return_value_cell_code,
get_print_string_code,
get_variable_dimensions_from_token,
get_variable_from_ID_token,
is_token_literal,
process_switch_cases,
)
from .Globals import create_variable_from_definition, get_global_variables, get_variable_size, is_variable_array
from .Node import NodeToken, NodeTernary, NodeArraySetElement, NodeUnaryPrefix, NodeUnaryPostfix, NodeArrayGetElement, NodeFunctionCall, NodeArrayAssignment
from .Node import (
NodeArrayAssignment,
NodeArrayGetElement,
NodeArraySetElement,
NodeFunctionCall,
NodeTernary,
NodeToken,
NodeUnaryPostfix,
NodeUnaryPrefix,
)
from .Parser import Parser
from .Token import Token
@ -83,7 +100,9 @@ class FunctionCompiler:
# new stack pointer should be at least that size
assert self.current_stack_pointer() <= current_stack_pointer
self.return_value_cell = current_stack_pointer
self.set_stack_pointer(current_stack_pointer+1) # make room for return_value cell. next available cell is the next one after it.
self.set_stack_pointer(
current_stack_pointer + 1
) # make room for return_value cell. next available cell is the next one after it.
function_code = self.compile_function_scope(self.parameters)
self.remove_ids_map() # Global variables
return function_code
@ -123,8 +142,12 @@ class FunctionCompiler:
# multiply by next dimensions sizes
multiply_amount = reduce(lambda x, y: x * y, dimensions[1:]) # size of the following dimensions
node_token_multiply_amount = NodeToken(self.ids_map_list, token=Token(Token.NUM, ID_token.line, ID_token.column, data=str(multiply_amount)))
index_expression = NodeToken(self.ids_map_list, token=multiply_token, left=first_index_expression, right=node_token_multiply_amount)
node_token_multiply_amount = NodeToken(
self.ids_map_list, token=Token(Token.NUM, ID_token.line, ID_token.column, data=str(multiply_amount))
)
index_expression = NodeToken(
self.ids_map_list, token=multiply_token, left=first_index_expression, right=node_token_multiply_amount
)
# handle next dimensions
dimension = 1
@ -132,8 +155,10 @@ class FunctionCompiler:
if self.parser.current_token().type != Token.LBRACK: # too few indexes given...
if dimension == 1:
return first_index_expression # allow use of only one dimension for multi-dimensional array
raise BFSemanticError("%s is a %s-dimensional array, but only %s dimension(s) given as index" %
(str(ID_token), len(dimensions), dimension))
raise BFSemanticError(
"%s is a %s-dimensional array, but only %s dimension(s) given as index"
% (str(ID_token), len(dimensions), dimension)
)
self.parser.check_current_token_is(Token.LBRACK)
self.parser.advance_token() # skip LBRACK
exp = self.expression()
@ -143,19 +168,30 @@ class FunctionCompiler:
# current_dimension_index *= size_of_following_dimensions
if dimension + 1 < len(dimensions): # not last dimension - need to multiply and add
multiply_amount = reduce(lambda x, y: x * y, dimensions[dimension + 1:]) # size of the following dimensions
node_token_multiply_amount = NodeToken(self.ids_map_list, token=Token(Token.NUM, ID_token.line, ID_token.column, data=str(multiply_amount)))
multiply_node = NodeToken(self.ids_map_list, token=multiply_token, left=exp, right=node_token_multiply_amount)
multiply_amount = reduce(
lambda x, y: x * y, dimensions[dimension + 1 :]
) # size of the following dimensions
node_token_multiply_amount = NodeToken(
self.ids_map_list,
token=Token(Token.NUM, ID_token.line, ID_token.column, data=str(multiply_amount)),
)
multiply_node = NodeToken(
self.ids_map_list, token=multiply_token, left=exp, right=node_token_multiply_amount
)
# prev_dimensions_index += current_dimension_index
index_expression = NodeToken(self.ids_map_list, token=add_token, left=index_expression, right=multiply_node)
index_expression = NodeToken(
self.ids_map_list, token=add_token, left=index_expression, right=multiply_node
)
else: # last dimension - no need to multiply, just add
index_expression = NodeToken(self.ids_map_list, token=add_token, left=index_expression, right=exp)
dimension += 1
if self.parser.current_token().type == Token.LBRACK: # too many indexes given...
raise BFSemanticError("%s is a %s-dimensional array. Unexpected %s" %
(str(ID_token), len(dimensions), self.parser.current_token()))
raise BFSemanticError(
"%s is a %s-dimensional array. Unexpected %s"
% (str(ID_token), len(dimensions), self.parser.current_token())
)
return index_expression
def get_token_after_array_access(self, offset=0):
@ -193,12 +229,18 @@ class FunctionCompiler:
if self.parser.next_token().type == Token.SEMICOLON: # INT ID SEMICOLON
self.parser.advance_token(2) # skip ID SEMICOLON
return '' # no code is generated here. code was generated for defining this variable when we entered the scope
return (
"" # no code is generated here. code was generated for defining this variable when we entered the scope
)
elif self.parser.next_token().type == Token.ASSIGN and self.parser.next_token().data == "=": # INT ID = EXPRESSION SEMICOLON
elif (
self.parser.next_token().type == Token.ASSIGN and self.parser.next_token().data == "="
): # INT ID = EXPRESSION SEMICOLON
return self.compile_expression_as_statement() # compile_expression_as_statement skips the SEMICOLON
elif self.parser.next_token().type == Token.LBRACK: # INT ID (LBRACK NUM RBRACK)+ (= ARRAY_INITIALIZATION)? SEMICOLON
elif (
self.parser.next_token().type == Token.LBRACK
): # INT ID (LBRACK NUM RBRACK)+ (= ARRAY_INITIALIZATION)? SEMICOLON
# array definition (int arr[2][3]...[];) or array definition and initialization (arr[2][3]...[] = {...};)
token_id = self.parser.current_token()
self.parser.advance_token() # skip ID
@ -210,7 +252,7 @@ class FunctionCompiler:
initialization_node = self.compile_array_assignment(token_id)
code = initialization_node.get_code(self.current_stack_pointer()) + "<" # discard expression value
else:
code = '' # just array definition
code = "" # just array definition
# no code is generated here. code was generated for defining this variable when we entered the scope
self.parser.check_current_token_is(Token.SEMICOLON)
self.parser.advance_token() # skip SEMICOLON
@ -297,7 +339,9 @@ class FunctionCompiler:
token = self.tokens[i]
if token.type == Token.INT:
if self.tokens[i-2].type != Token.FOR: # if it is not a definition inside a FOR statement (for (int i = 0...))
if (
self.tokens[i - 2].type != Token.FOR
): # if it is not a definition inside a FOR statement (for (int i = 0...))
variable = create_variable_from_definition(self.parser, index=i)
self.insert_to_ids_map(variable)
@ -333,7 +377,7 @@ class FunctionCompiler:
for parameter in parameters:
self.insert_to_ids_map(parameter)
code = '>' # skip return_value_cell
code = ">" # skip return_value_cell
code += self.insert_scope_variables_into_ids_map()
# this inserts scope variables AND moves pointer right, with the amount of BOTH parameters and scope variables
@ -377,7 +421,9 @@ class FunctionCompiler:
if token.type == Token.ID and self.parser.next_token().type == Token.LPAREN:
return self.function_call()
if token.type == Token.ID and self.parser.next_token().type == Token.LBRACK: # array - ID(LBRACK expression RBRACK)+
if (
token.type == Token.ID and self.parser.next_token().type == Token.LBRACK
): # array - ID(LBRACK expression RBRACK)+
index_expression = self.get_array_index_expression()
return NodeArrayGetElement(self.ids_map_list, token, index_expression)
@ -386,7 +432,10 @@ class FunctionCompiler:
return NodeToken(self.ids_map_list, token=token)
if token.type != Token.LPAREN:
raise BFSyntaxError("Unexpected '%s'. expected literal (NUM | ID | ID(LBRACK expression RBRACK)+ | TRUE | FALSE | function_call | ( expression ))" % str(token))
raise BFSyntaxError(
"Unexpected '%s'. expected literal (NUM | ID | ID(LBRACK expression RBRACK)+ | TRUE | FALSE | function_call | ( expression ))"
% str(token)
)
# ( expression )
self.parser.check_current_token_is(Token.LPAREN)
@ -417,7 +466,9 @@ class FunctionCompiler:
if token.type in [Token.NOT, Token.BITWISE_NOT, Token.BINOP]:
if token.type == Token.BINOP and token.data not in ["+", "-"]:
raise BFSyntaxError("Expected either + or - as unary prefix instead of token %s" % self.parser.current_token())
raise BFSyntaxError(
"Expected either + or - as unary prefix instead of token %s" % self.parser.current_token()
)
self.parser.advance_token()
unary_prefix = self.unary_prefix()
@ -618,11 +669,19 @@ class FunctionCompiler:
expression_node = self.expression()
new_node = NodeToken(self.ids_map_list, left=NodeToken(self.ids_map_list, token=id_token), token=assign_token, right=expression_node)
new_node = NodeToken(
self.ids_map_list,
left=NodeToken(self.ids_map_list, token=id_token),
token=assign_token,
right=expression_node,
)
return new_node
elif self.parser.current_token().type == Token.ID and self.parser.next_token().type == Token.LBRACK and \
self.get_token_after_array_access().type == Token.ASSIGN:
elif (
self.parser.current_token().type == Token.ID
and self.parser.next_token().type == Token.LBRACK
and self.get_token_after_array_access().type == Token.ASSIGN
):
# ID (LBRACK expression RBRACK)+ ASSIGN value_expression
id_token = self.parser.current_token()
index_expression = self.get_array_index_expression()
@ -744,7 +803,7 @@ class FunctionCompiler:
if self.parser.current_token().type == Token.SEMICOLON:
# return;
self.parser.advance_token() # skip ;
return '' # nothing to do
return "" # nothing to do
# return exp;
expression_code = self.compile_expression()
@ -763,7 +822,12 @@ class FunctionCompiler:
# this expression can be used as a statement.
# e.g: x+=5; or x++ or ++x;
assert self.parser.current_token().type in [Token.ID, Token.INCREMENT, Token.DECREMENT, Token.UNARY_MULTIPLICATIVE]
assert self.parser.current_token().type in [
Token.ID,
Token.INCREMENT,
Token.DECREMENT,
Token.UNARY_MULTIPLICATIVE,
]
code = self.compile_expression()
self.parser.check_current_token_is(Token.SEMICOLON)
@ -901,7 +965,10 @@ class FunctionCompiler:
self.increase_stack_pointer() # use 1 additional temp cell for indicating we need to execute a case
cases = list() # list of tuples: (value/"default" (int or string), case_code (string), has_break(bool))
while self.parser.current_token().type in [Token.CASE, Token.DEFAULT]: # (default | CASE literal) COLON statement* break;? statements*
while self.parser.current_token().type in [
Token.CASE,
Token.DEFAULT,
]: # (default | CASE literal) COLON statement* break;? statements*
if self.parser.current_token().type == Token.CASE:
self.parser.advance_token() # skip CASE
constant_value_token = self.parser.current_token()
@ -922,7 +989,9 @@ class FunctionCompiler:
inner_case_code = ""
while self.parser.current_token().type not in [Token.CASE, Token.DEFAULT, Token.RBRACE, Token.BREAK]:
inner_case_code += self.compile_statement(allow_declaration=False) # not allowed to declare variables directly inside case
inner_case_code += self.compile_statement(
allow_declaration=False
) # not allowed to declare variables directly inside case
has_break = False
if self.parser.current_token().type == Token.BREAK: # ignore all statements after break
@ -934,7 +1003,9 @@ class FunctionCompiler:
cases.append((value, inner_case_code, has_break))
if self.parser.current_token().type not in [Token.CASE, Token.DEFAULT, Token.RBRACE]:
raise BFSyntaxError("Expected case / default / RBRACE (}) instead of token %s" % self.parser.current_token())
raise BFSyntaxError(
"Expected case / default / RBRACE (}) instead of token %s" % self.parser.current_token()
)
self.parser.check_current_token_is(Token.RBRACE)
self.parser.advance_token()
self.decrease_stack_pointer(amount=2)
@ -943,7 +1014,10 @@ class FunctionCompiler:
def compile_break(self):
# TODO: Make the break statement in scopes inside switch-case (including if/else), and for/do/while
raise NotImplementedError("Break statement found outside of switch case first scope.\nBreak is not currently implemented for while/for/do statements.\nToken is %s" % self.parser.current_token())
raise NotImplementedError(
"Break statement found outside of switch case first scope.\nBreak is not currently implemented for while/for/do statements.\nToken is %s"
% self.parser.current_token()
)
def compile_for(self):
# for (statement expression; expression) inner_scope_code note: statement contains ;, and inner_scope_code can be scope { }
@ -951,17 +1025,17 @@ class FunctionCompiler:
# (the statement cannot contain scope - { and } )
"""
<for> is a special case of scope
the initial code (int i = 0;) is executed INSIDE the scope, but BEFORE the LBRACE
so we manually compile the scope instead of using self.compile_scope():
<for> is a special case of scope
the initial code (int i = 0;) is executed INSIDE the scope, but BEFORE the LBRACE
so we manually compile the scope instead of using self.compile_scope():
we first create an ids map, and in the case that there is a variable definition inside the <for> definition:
we manually insert the ID into the ids map, and move the pointer to the right once, to make room for it
(this needs to be done before the <for> definition's statement)
next, inside the for's scope {}:
after calling insert_scope_variables_into_ids_map, we move the pointer to the left once, since it counts the ID we entered manually as well
after calling exit_scope, we move the pointer to the right, since it counts the ID we entered manually, and we don't want it to be discarded after every iteration
finally, at the end of the <for> loop, we move the pointer once to the left, to discard the variable we defined manually
we first create an ids map, and in the case that there is a variable definition inside the <for> definition:
we manually insert the ID into the ids map, and move the pointer to the right once, to make room for it
(this needs to be done before the <for> definition's statement)
next, inside the for's scope {}:
after calling insert_scope_variables_into_ids_map, we move the pointer to the left once, since it counts the ID we entered manually as well
after calling exit_scope, we move the pointer to the right, since it counts the ID we entered manually, and we don't want it to be discarded after every iteration
finally, at the end of the <for> loop, we move the pointer once to the left, to discard the variable we defined manually
"""
self.parser.check_current_tokens_are([Token.FOR, Token.LPAREN])
@ -969,7 +1043,7 @@ class FunctionCompiler:
manually_inserted_variable_in_for_definition = False
variable = None
code = ''
code = ""
# =============== enter FOR scope ===============
self.add_ids_map()
@ -987,7 +1061,10 @@ class FunctionCompiler:
show_side_effect_warning = self.get_token_after_array_access(offset=1).type != Token.ASSIGN
if show_side_effect_warning:
print("[Warning] For loop variable '%s' isn't assigned to anything and may cause side effects" % self.parser.next_token())
print(
"[Warning] For loop variable '%s' isn't assigned to anything and may cause side effects"
% self.parser.next_token()
)
if self.parser.current_token().type == Token.LBRACE: # statement is a scope
raise BFSyntaxError("Unexpected scope inside for loop statement - %s" % self.parser.current_token())
@ -1042,20 +1119,31 @@ class FunctionCompiler:
token = self.parser.current_token()
if token.type == Token.INT: # INT ID ((= EXPRESSION) | ([NUM])+ (= ARRAY_INITIALIZATION)?)? SEMICOLON
if not allow_declaration:
raise BFSemanticError("Cannot define variable (%s) directly inside case. "
"Can define inside new scope {} or outside the switch statement" % token)
raise BFSemanticError(
"Cannot define variable (%s) directly inside case. "
"Can define inside new scope {} or outside the switch statement" % token
)
return self.compile_variable_declaration()
elif token.type in [Token.INCREMENT, Token.DECREMENT, Token.UNARY_MULTIPLICATIVE]: # ++ID;
return self.compile_expression_as_statement()
elif token.type == Token.ID:
if self.parser.next_token().type in [Token.ASSIGN, Token.LBRACK, Token.INCREMENT, Token.DECREMENT, Token.UNARY_MULTIPLICATIVE]:
if self.parser.next_token().type in [
Token.ASSIGN,
Token.LBRACK,
Token.INCREMENT,
Token.DECREMENT,
Token.UNARY_MULTIPLICATIVE,
]:
# ID ASSIGN expression; or ID([expression])+ ASSIGN expression; or ID++;
return self.compile_expression_as_statement()
elif self.parser.next_token().type == Token.LPAREN: # ID(...); (function call)
return self.compile_function_call_statement()
raise BFSyntaxError("Unexpected '%s' after '%s'. Expected '=|+=|-=|*=|/=|%%=|<<=|>>=|&=|(|=)|^=' (assignment), '++|--' (modification) or '(' (function call)" % (str(self.parser.next_token()), str(token)))
raise BFSyntaxError(
"Unexpected '%s' after '%s'. Expected '=|+=|-=|*=|/=|%%=|<<=|>>=|&=|(|=)|^=' (assignment), '++|--' (modification) or '(' (function call)"
% (str(self.parser.next_token()), str(token))
)
elif token.type == Token.PRINT:
return self.compile_print_string()
@ -1097,7 +1185,7 @@ class FunctionCompiler:
def compile_scope_statements(self):
tokens = self.tokens
code = ''
code = ""
while self.parser.current_token() is not None:
if self.parser.current_token().type == Token.RBRACE:
# we reached the end of our scope
@ -1124,29 +1212,29 @@ class FunctionCompiler:
# will be inserted into the new scope prior to the scope's compilation
"""
example layout:
int global_var1;
int global_var2;
int foo(int a, int b) {
int x;
int y;
return 5;
}
example layout:
int global_var1;
int global_var2;
int foo(int a, int b) {
int x;
int y;
return 5;
}
int main() {
int n;
foo(1, 2);
}
int main() {
int n;
foo(1, 2);
}
global_var1 global_var2 main_return_value n foo_return_value a=1 b=2 x y
global_var1 global_var2 main_return_value n foo_return_value a=1 b=2 x y
calling convention:
caller responsibility: make room for return_value (and zero its cell), place parameters, point to return_value cell
callee responsibility: put return value in return_value cell and point to it (thus "cleaning" parameters)
can assume that there is a zeroed cell at current_stack_pointer (return_value_cell) (therefore ids_map starts at index current_stack_pointer+1)
can assume that the next cells match your parameters
assumes that initially, the pointer points to the first cell (return_value_cell).
therefore begin with '>' * (1 + parameters + scope variables)
calling convention:
caller responsibility: make room for return_value (and zero its cell), place parameters, point to return_value cell
callee responsibility: put return value in return_value cell and point to it (thus "cleaning" parameters)
can assume that there is a zeroed cell at current_stack_pointer (return_value_cell) (therefore ids_map starts at index current_stack_pointer+1)
can assume that the next cells match your parameters
assumes that initially, the pointer points to the first cell (return_value_cell).
therefore begin with '>' * (1 + parameters + scope variables)
"""
assert self.parser.current_token().type == Token.LBRACE

View file

@ -1,4 +1,5 @@
from copy import deepcopy
from .Exceptions import BFSemanticError
functions = dict() # Global dictionary of function_name --> FunctionCompiler objects
@ -30,4 +31,7 @@ def check_function_exists(function_token, parameters_amount):
function = functions[function_name]
if len(function.parameters) != parameters_amount:
raise BFSemanticError("Function '%s' has %s parameters (called it with %s parameters)" % (str(function_token), len(function.parameters), parameters_amount))
raise BFSemanticError(
"Function '%s' has %s parameters (called it with %s parameters)"
% (str(function_token), len(function.parameters), parameters_amount)
)

View file

@ -1,7 +1,8 @@
from .Exceptions import BFSyntaxError, BFSemanticError
from .Token import Token
from functools import reduce
from .Exceptions import BFSemanticError, BFSyntaxError
from .Token import Token
"""
This file holds functions that generate general Brainfuck code
And general functions that are not dependent on other objects
@ -126,23 +127,29 @@ def unpack_multidimensional_literal_tokens_to_array_dimensions(ID_token, array_d
if len(array_dimensions) == 0:
raise BFSemanticError("Tried to initialize array %s with too many nested sub-arrays" % ID_token)
if len(literal_tokens_list) > array_dimensions[0]:
raise BFSemanticError("Tried to initialize array %s dimension %s with too many elements (%s)"
% (ID_token, str(array_dimensions), str(len(literal_tokens_list))))
raise BFSemanticError(
"Tried to initialize array %s dimension %s with too many elements (%s)"
% (ID_token, str(array_dimensions), str(len(literal_tokens_list)))
)
result = []
for element in literal_tokens_list:
if isinstance(element, list):
# recursively unpack the list with the sub-dimension of the sub-array
# E.g if we have arr[3][3][3] and then this call will fill [3][3]=9 elements
result.extend(unpack_multidimensional_literal_tokens_to_array_dimensions(ID_token, array_dimensions[1:], element))
result.extend(
unpack_multidimensional_literal_tokens_to_array_dimensions(ID_token, array_dimensions[1:], element)
)
else:
result.append(element)
if len(array_dimensions) > 1:
dimension_size = dimensions_to_size(array_dimensions[1:]) # current size we need to fill
result.extend([Token(Token.NUM, 0, 0, "0")] * (dimension_size - 1)) # fill missing elements in this dimension with zeros
result.extend(
[Token(Token.NUM, 0, 0, "0")] * (dimension_size - 1)
) # fill missing elements in this dimension with zeros
dimension_size = dimensions_to_size(array_dimensions) # current size we need to fill
result.extend([Token(Token.NUM, 0, 0, "0")] * (dimension_size-len(result))) # fill the result with zeros
result.extend([Token(Token.NUM, 0, 0, "0")] * (dimension_size - len(result))) # fill the result with zeros
return result
@ -157,13 +164,20 @@ def unpack_literal_tokens_to_array_dimensions(ID_token, array_dimensions, litera
if all(not isinstance(element, list) for element in literal_tokens_list):
# special case - if all elements are literals, then we allow assigning them as-is and not care about dimensions
# E.g if we have arr[3][3][3] = {1,2,3,4} then return [1,2,3,4,0,0,0,0,0]
unpacked_literals_list = literal_tokens_list + [Token(Token.NUM, 0, 0, "0")] * (array_size - len(literal_tokens_list)) # fill missing with zeros
unpacked_literals_list = literal_tokens_list + [Token(Token.NUM, 0, 0, "0")] * (
array_size - len(literal_tokens_list)
) # fill missing with zeros
else:
unpacked_literals_list = unpack_multidimensional_literal_tokens_to_array_dimensions(ID_token, array_dimensions, literal_tokens_list)
unpacked_literals_list = unpack_multidimensional_literal_tokens_to_array_dimensions(
ID_token, array_dimensions, literal_tokens_list
)
if len(unpacked_literals_list) > array_size:
raise BFSemanticError("Tried to initialize array %s with incompatible amount of literals."
" (array size is %s and literals size is %s)" % (ID_token, str(array_size), str(len(unpacked_literals_list))))
raise BFSemanticError(
"Tried to initialize array %s with incompatible amount of literals."
" (array size is %s and literals size is %s)"
% (ID_token, str(array_size), str(len(unpacked_literals_list)))
)
assert len(unpacked_literals_list) == array_size
return unpacked_literals_list
@ -208,17 +222,19 @@ def process_switch_cases(expression_code, cases):
code += "<" # point to expression
if all_cases_have_break: # small optimization for evaluating the expression
cases = [case for case in cases if case[0] != "default"] # remove default to be able to sort. it is handled differently
cases = [
case for case in cases if case[0] != "default"
] # remove default to be able to sort. it is handled differently
cases.sort(key=lambda x: x[0], reverse=True) # Can sort since correct flow is not needed
"""
This loop compares the expression value to each case in the switch-case statement, in reverse order
It does so by increasing and decreasing expression, and comparing result to 0
E.G. if we have
E.G. if we have
switch(x) {
case 2:
case 0:
case 5:
case 5:
case 1:
}
x will be put in <expression> cell, then:
@ -244,7 +260,7 @@ def process_switch_cases(expression_code, cases):
<need_to_execute=1>
<compare_with_1> [
<compare_with_5> [
<compare_with_0> [
<compare_with_0> [
<compare_with_2> [
<default_code> <expression_value=0> <need_to_execute=0>
] <if need_to_execute> <code_for_2> <need_to_execute=0>
@ -487,22 +503,22 @@ def get_bitwise_code(code_logic):
code += "<<" # point to a
code += "[" # while a != 0:
code += "-" # a -= 1
code += ">>-" # c -= 1
code += "[>+>>+<<<-]>[<+>-]" # copy c to y (using w)
code += ">>" # point to y
code += ">>+<<" # bit1 += 1
code += "-" # a -= 1
code += ">>-" # c -= 1
code += "[>+>>+<<<-]>[<+>-]" # copy c to y (using w)
code += ">>" # point to y
code += ">>+<<" # bit1 += 1
code += "-[" # if y != 1:
code += "<+" # x += 1
code += "<<++" # c += 2 (c was 0)
code += ">" * 5 # point to bit1
code += "--" # bit1 -= 2 (bit1 was 2)
code += "<<" # point to y
code += "+" # set y to 0
code += "]" # end if
code += "-[" # if y != 1:
code += "<+" # x += 1
code += "<<++" # c += 2 (c was 0)
code += ">" * 5 # point to bit1
code += "--" # bit1 -= 2 (bit1 was 2)
code += "<<" # point to y
code += "+" # set y to 0
code += "]" # end if
code += "<<<<<" # point to a
code += "<<<<<" # point to a
code += "]" # end while
code += ">>>>[<<<<+>>>>-]" # move x to a (x is a/2)
@ -510,21 +526,21 @@ def get_bitwise_code(code_logic):
code += "<" # point to b
code += "[" # while b != 0:
code += "-" # b -= 1
code += ">-" # c -= 1
code += "[>+>>+<<<-]>[<+>-]" # copy c to y (using w)
code += ">>" # point to y
code += ">+<" # z += 1
code += "-" # b -= 1
code += ">-" # c -= 1
code += "[>+>>+<<<-]>[<+>-]" # copy c to y (using w)
code += ">>" # point to y
code += ">+<" # z += 1
code += "-[" # if y != 1:
code += ">--<" # z -= 2 (z was 2)
code += "<+" # x += 1
code += "<<++" # c += 2 (c was 0)
code += ">>>" # point to y
code += "+" # set y to 0
code += "]"
code += "-[" # if y != 1:
code += ">--<" # z -= 2 (z was 2)
code += "<+" # x += 1
code += "<<++" # c += 2 (c was 0)
code += ">>>" # point to y
code += "+" # set y to 0
code += "]"
code += "<<<<" # point to b
code += "<<<<" # point to b
code += "]" # end while
# w is a % 2
@ -658,14 +674,14 @@ def get_unary_prefix_op_code(token, offset_to_variable=None):
assert token.data in ["+", "-"]
if token.data == "+":
# keep value as-is
return '>'
return ">"
elif token.data == "-":
# a temp
code = ">[-]" # zero temp
code += "<" # point to a
code += "[->-<]" # sub a from temp
code += ">" # point to temp
code += "[<+>-]" # copy temp to a
code = ">[-]" # zero temp
code += "<" # point to a
code += "[->-<]" # sub a from temp
code += ">" # point to temp
code += "[<+>-]" # copy temp to a
return code
raise NotImplementedError
@ -1127,7 +1143,6 @@ def get_op_boolean_operator_code(node, current_pointer):
raise NotImplementedError
def get_print_string_code(string):
code = "[-]" # zero the current cell
code += ">[-]" # zero the next cell (will be used for loop counts)
@ -1200,6 +1215,7 @@ def get_move_left_index_cell_code():
# General
# =================
def get_literal_token_value(token):
# known at compilation time
assert is_token_literal(token)

View file

@ -1,6 +1,7 @@
from collections import namedtuple
from .Token import Token
from .General import dimensions_to_size, get_NUM_token_value
from .Token import Token
"""
This file holds the program's functions and global variables
@ -55,7 +56,7 @@ def create_variable_from_definition(parser, index=None, advance_tokens=False):
if index is None, then assumes we start at the current_token_index
if advance_tokens is True, then modifies current_token_index accordingly using parser.advance_token()
"""
if index is None:
index = parser.current_token_index

View file

@ -1,6 +1,7 @@
import re
from .Token import Token
from .Optimizer import optimize
from .Token import Token
class LexicalErrorException(Exception):
@ -14,64 +15,59 @@ def analyze(text):
"""
rules = [
('\s+', Token.WHITESPACE),
('void', Token.VOID),
('int', Token.INT),
('bool', Token.INT), # treat bool as int
('char', Token.INT), # treat char as int
('true', Token.TRUE),
('false', Token.FALSE),
('&&', Token.AND),
('\|\|', Token.OR),
('\!', Token.NOT),
('return', Token.RETURN),
('if', Token.IF),
('else', Token.ELSE),
('while', Token.WHILE),
('for', Token.FOR),
('do', Token.DO),
('print', Token.PRINT),
('switch', Token.SWITCH),
('case', Token.CASE),
('default', Token.DEFAULT),
('break', Token.BREAK),
('continue', Token.CONTINUE), # todo
(':', Token.COLON),
(';', Token.SEMICOLON),
(',', Token.COMMA),
('\(', Token.LPAREN),
('\)', Token.RPAREN),
('\{', Token.LBRACE),
('\}', Token.RBRACE),
('\[', Token.LBRACK),
('\]', Token.RBRACK),
('=|\+=|-=|\*=|/=|%=|<<=|>>=|&=|\|=|\^=', Token.ASSIGN),
('\?', Token.TERNARY),
('<=|>=|==|!=|<|>', Token.RELOP),
('\+\+', Token.INCREMENT),
('--', Token.DECREMENT),
('\+|-|\*|/|%', Token.BINOP),
('\*\*|//|%%', Token.UNARY_MULTIPLICATIVE),
('<<|>>', Token.BITWISE_SHIFT),
('~', Token.BITWISE_NOT),
('&', Token.BITWISE_AND),
('\|', Token.BITWISE_OR),
('\^', Token.BITWISE_XOR),
('([a-zA-Z_][a-zA-Z0-9_]*)', Token.ID),
('(\d+)', Token.NUM),
('(0x[A-Fa-f\d]+)', Token.NUM), # hexadecimal number
('(0o[0-7]+)', Token.NUM), # octal number
('(0b[01]+)', Token.NUM), # binary number
(r'\"(\\\"|[^"])*"', Token.STRING),
(r'\'(\\\'|(\\)?[^\'])\'', Token.CHAR),
('//.*(\\n|$)', Token.COMMENT),
(r'/\*[\s\S]*?\*/', Token.COMMENT), # multiline comments
('.', Token.UNIDENTIFIED)
(r"\s+", Token.WHITESPACE),
("void", Token.VOID),
("int", Token.INT),
("bool", Token.INT), # treat bool as int
("char", Token.INT), # treat char as int
("true", Token.TRUE),
("false", Token.FALSE),
("&&", Token.AND),
(r"\|\|", Token.OR),
(r"\!", Token.NOT),
("return", Token.RETURN),
("if", Token.IF),
("else", Token.ELSE),
("while", Token.WHILE),
("for", Token.FOR),
("do", Token.DO),
("print", Token.PRINT),
("switch", Token.SWITCH),
("case", Token.CASE),
("default", Token.DEFAULT),
("break", Token.BREAK),
("continue", Token.CONTINUE), # todo
(":", Token.COLON),
(";", Token.SEMICOLON),
(",", Token.COMMA),
(r"\(", Token.LPAREN),
(r"\)", Token.RPAREN),
(r"\{", Token.LBRACE),
(r"\}", Token.RBRACE),
(r"\[", Token.LBRACK),
(r"\]", Token.RBRACK),
(r"=|\+=|-=|\*=|/=|%=|<<=|>>=|&=|\|=|\^=", Token.ASSIGN),
(r"\?", Token.TERNARY),
(r"<=|>=|==|!=|<|>", Token.RELOP),
(r"\+\+", Token.INCREMENT),
("--", Token.DECREMENT),
(r"\+|-|\*|/|%", Token.BINOP),
(r"\*\*|//|%%", Token.UNARY_MULTIPLICATIVE),
("<<|>>", Token.BITWISE_SHIFT),
("~", Token.BITWISE_NOT),
("&", Token.BITWISE_AND),
(r"\|", Token.BITWISE_OR),
(r"\^", Token.BITWISE_XOR),
("([a-zA-Z_][a-zA-Z0-9_]*)", Token.ID),
(r"(\d+)", Token.NUM),
(r"(0x[A-Fa-f\d]+)", Token.NUM), # hexadecimal number
("(0o[0-7]+)", Token.NUM), # octal number
("(0b[01]+)", Token.NUM), # binary number
(r'\"(\\\"|[^"])*"', Token.STRING),
(r"\'(\\\'|(\\)?[^\'])\'", Token.CHAR),
("//.*(\\n|$)", Token.COMMENT),
(r"/\*[\s\S]*?\*/", Token.COMMENT), # multiline comments
(".", Token.UNIDENTIFIED),
]
rules = [(re.compile(r), t) for r, t in rules]
@ -79,7 +75,7 @@ def analyze(text):
tokens = []
# create a mapping of [line number] to [offset of that line from the beginning of the text]
newline = re.compile('\n')
newline = re.compile("\n")
lines = [0] + [m.end() for m in re.finditer(newline, text)]
i = 0
@ -99,12 +95,12 @@ def analyze(text):
# calculate line and column
line, column = None, None
for line_idx in range(len(lines)-1):
if lines[line_idx] <= longest_match.start() < lines[line_idx+1]:
line, column = line_idx+1, (longest_match.start() - lines[line_idx])+1 # humans count from 1 :)
for line_idx in range(len(lines) - 1):
if lines[line_idx] <= longest_match.start() < lines[line_idx + 1]:
line, column = line_idx + 1, (longest_match.start() - lines[line_idx]) + 1 # humans count from 1 :)
break
if not line:
line, column = len(lines), (longest_match.start() - lines[-1])+1
line, column = len(lines), (longest_match.start() - lines[-1]) + 1
if matched_token in [Token.COMMENT, Token.WHITESPACE]:
pass # do nothing
@ -112,8 +108,18 @@ def analyze(text):
raise LexicalErrorException("Unidentified Character '%s' (line %s column %s)" % (text[i], line, column))
elif matched_token in [Token.STRING, Token.CHAR]:
# remove quotes at beginning and end, un-escape characters
tokens.append(Token(matched_token, line, column, longest_match.group()[1:-1].encode("utf8").decode("unicode_escape")))
elif matched_token in [Token.NUM, Token.ID, Token.BINOP, Token.RELOP, Token.ASSIGN, Token.UNARY_MULTIPLICATIVE, Token.BITWISE_SHIFT]:
tokens.append(
Token(matched_token, line, column, longest_match.group()[1:-1].encode("utf8").decode("unicode_escape"))
)
elif matched_token in [
Token.NUM,
Token.ID,
Token.BINOP,
Token.RELOP,
Token.ASSIGN,
Token.UNARY_MULTIPLICATIVE,
Token.BITWISE_SHIFT,
]:
tokens.append(Token(matched_token, line, column, longest_match.group()))
else:
tokens.append(Token(matched_token, line, column))
@ -128,16 +134,40 @@ def tests():
text = "my international int ; int; pints; international;"
res = analyze(text)
expected = [Token.ID, Token.ID, Token.INT, Token.SEMICOLON, Token.INT, Token.SEMICOLON, Token.ID,
Token.SEMICOLON, Token.ID, Token.SEMICOLON]
expected = [
Token.ID,
Token.ID,
Token.INT,
Token.SEMICOLON,
Token.INT,
Token.SEMICOLON,
Token.ID,
Token.SEMICOLON,
Token.ID,
Token.SEMICOLON,
]
assert len(res) == len(expected) and all(res[i].type == expected[i] for i in range(len(res)))
def test2():
text = "true !||!false falsek k||y+-a&&x"
res = analyze(text)
expected = [Token.TRUE, Token.NOT, Token.OR, Token.NOT, Token.FALSE, Token.ID, Token.ID, Token.OR, Token.ID,
Token.BINOP, Token.BINOP, Token.ID, Token.AND, Token.ID]
expected = [
Token.TRUE,
Token.NOT,
Token.OR,
Token.NOT,
Token.FALSE,
Token.ID,
Token.ID,
Token.OR,
Token.ID,
Token.BINOP,
Token.BINOP,
Token.ID,
Token.AND,
Token.ID,
]
assert len(res) == len(expected) and all(res[i].type == expected[i] for i in range(len(res)))
def test3():
@ -166,9 +196,29 @@ def tests():
# test all arithmetic operations
text = "(1+2*3/6)+(1%3)*(6-1)"
tokens = analyze(text)
expected = [Token.LPAREN, Token.NUM, Token.BINOP, Token.NUM, Token.BINOP, Token.NUM, Token.BINOP, Token.NUM,
Token.RPAREN, Token.BINOP, Token.LPAREN, Token.NUM, Token.BINOP, Token.NUM, Token.RPAREN,
Token.BINOP, Token.LPAREN, Token.NUM, Token.BINOP, Token.NUM, Token.RPAREN]
expected = [
Token.LPAREN,
Token.NUM,
Token.BINOP,
Token.NUM,
Token.BINOP,
Token.NUM,
Token.BINOP,
Token.NUM,
Token.RPAREN,
Token.BINOP,
Token.LPAREN,
Token.NUM,
Token.BINOP,
Token.NUM,
Token.RPAREN,
Token.BINOP,
Token.LPAREN,
Token.NUM,
Token.BINOP,
Token.NUM,
Token.RPAREN,
]
assert len(tokens) == len(expected) and all(tokens[i].type == expected[i] for i in range(len(tokens)))
optimize(tokens)
assert tokens[1].data == "2" and tokens[5].data == "1" and tokens[9].data == "5"
@ -179,5 +229,5 @@ def tests():
test3()
if __name__ == '__main__':
if __name__ == "__main__":
tests()

View file

@ -48,7 +48,9 @@ def get_readint_code():
code += ">" # point to tmp
code += "[<++++++++++>-]" # res = tmp * 10, tmp = 0
code += ">" # point to input
code += "-" * (0x30 - 10) # convert character to a digit by subtracting 0x30 from it (we already subtracted 10 before)
code += "-" * (
0x30 - 10
) # convert character to a digit by subtracting 0x30 from it (we already subtracted 10 before)
code += "[<<+>>-]" # res += input
code += "]" # end if

View file

@ -1,10 +1,20 @@
from .Exceptions import BFSemanticError
from .General import get_copy_from_variable_code, get_copy_to_variable_code
from .General import get_move_left_index_cell_code, get_move_right_index_cells_code
from .General import get_offset_to_variable, get_variable_dimensions_from_token
from .General import get_op_between_literals_code, get_literal_token_code, get_token_ID_code
from .General import get_unary_prefix_op_code, get_unary_postfix_op_code, is_token_literal
from .General import unpack_literal_tokens_to_array_dimensions, get_op_boolean_operator_code
from .General import (
get_copy_from_variable_code,
get_copy_to_variable_code,
get_literal_token_code,
get_move_left_index_cell_code,
get_move_right_index_cells_code,
get_offset_to_variable,
get_op_between_literals_code,
get_op_boolean_operator_code,
get_token_ID_code,
get_unary_postfix_op_code,
get_unary_prefix_op_code,
get_variable_dimensions_from_token,
is_token_literal,
unpack_literal_tokens_to_array_dimensions,
)
from .Token import Token
"""
@ -60,7 +70,14 @@ class NodeToken(Node):
else:
return get_literal_token_code(self.token)
elif self.token.type in [Token.BINOP, Token.RELOP, Token.BITWISE_SHIFT, Token.BITWISE_AND, Token.BITWISE_OR, Token.BITWISE_XOR]:
elif self.token.type in [
Token.BINOP,
Token.RELOP,
Token.BITWISE_SHIFT,
Token.BITWISE_AND,
Token.BITWISE_OR,
Token.BITWISE_XOR,
]:
code = self.left.get_code(current_pointer)
code += self.right.get_code(current_pointer + 1)
code += "<<" # point to the first operand
@ -78,7 +95,7 @@ class NodeToken(Node):
elif self.token.type == Token.ASSIGN:
assert self.left.token.type == Token.ID
if self.token.data == '=':
if self.token.data == "=":
# id = expression
code = self.right.get_code(current_pointer)
@ -119,7 +136,7 @@ class NodeTernary(Node):
code = ">" # point to bool_evaluate_node_false
code += "[-]+" # bool_evaluate_node_false=1
code += ">" # point to condition
code += self.condition.get_code(current_pointer+2) # evaluate condition
code += self.condition.get_code(current_pointer + 2) # evaluate condition
code += "<" # point to condition
code += "[" # if condition is non-zero
@ -150,7 +167,14 @@ class NodeUnaryPrefix(Node):
def get_code(self, current_pointer, *args, **kwargs):
# unary prefix (!x or ++x or ~x or -x)
assert self.token_operation.type in [Token.NOT, Token.INCREMENT, Token.DECREMENT, Token.UNARY_MULTIPLICATIVE, Token.BITWISE_NOT, Token.BINOP]
assert self.token_operation.type in [
Token.NOT,
Token.INCREMENT,
Token.DECREMENT,
Token.UNARY_MULTIPLICATIVE,
Token.BITWISE_NOT,
Token.BINOP,
]
if self.token_operation.type in [Token.NOT, Token.BITWISE_NOT, Token.BINOP]:
code = self.node_literal.get_code(current_pointer)
@ -178,10 +202,15 @@ class NodeUnaryPrefix(Node):
# the token to apply on must be an ID
if isinstance(self.node_literal, NodeToken) is False:
raise BFSemanticError("Prefix operator %s can only be applied to a variable" % str(self.token_operation))
raise BFSemanticError(
"Prefix operator %s can only be applied to a variable" % str(self.token_operation)
)
if self.node_literal.token.type != Token.ID:
raise BFSemanticError("Prefix operator %s cannot be applied to %s, but only to a variable" % (str(self.token_operation), str(self.node_literal.token)))
raise BFSemanticError(
"Prefix operator %s cannot be applied to %s, but only to a variable"
% (str(self.token_operation), str(self.node_literal.token))
)
offset_to_ID = get_offset_to_variable(self.ids_map_list, self.node_literal.token, current_pointer)
return get_unary_prefix_op_code(self.token_operation, offset_to_ID)
@ -218,7 +247,10 @@ class NodeUnaryPostfix(Node):
raise BFSemanticError("Postfix operator %s can only be applied to a variable" % str(self.token_operation))
if self.node_literal.token.type != Token.ID:
raise BFSemanticError("Postfix operator %s cannot be applied to %s, but only to a variable" % (str(self.token_operation), str(self.node_literal.token)))
raise BFSemanticError(
"Postfix operator %s cannot be applied to %s, but only to a variable"
% (str(self.token_operation), str(self.node_literal.token))
)
offset_to_ID = get_offset_to_variable(self.ids_map_list, self.node_literal.token, current_pointer)
return get_unary_postfix_op_code(self.token_operation, offset_to_ID)
@ -227,27 +259,31 @@ class NodeUnaryPostfix(Node):
class NodeFunctionCall(Node):
def __init__(self, ids_map_list, function_to_call, parameters):
"""
receives a FunctionCompiler object
that implements get_code() which gets a stack pointer and returns code
receives a list of parameters - Node objects
each one gets a stack pointer and returns code that evaluates the parameter
receives a FunctionCompiler object
that implements get_code() which gets a stack pointer and returns code
receives a list of parameters - Node objects
each one gets a stack pointer and returns code that evaluates the parameter
"""
Node.__init__(self, ids_map_list)
self.function_to_call = function_to_call
self.parameters = parameters
def get_code(self, current_pointer, *args, **kwargs):
code = '[-]>' # return_value_cell=0
code = "[-]>" # return_value_cell=0
# evaluate parameters from left to right, and put them on the "stack" in that order
# after each parameter code, the pointer points to the next available cell (one after the parameter)
for i, parameter in enumerate(self.parameters):
code += parameter.get_code(current_pointer+1+i) # evaluate each parameter at its cell offset (starting at one after return_value_cell)
code += parameter.get_code(
current_pointer + 1 + i
) # evaluate each parameter at its cell offset (starting at one after return_value_cell)
# at this point we point to one after the last parameter
code += "<" * len(self.parameters) # point back to first parameter
code += "<" # point to return_value_cell
code += self.function_to_call.get_code(current_stack_pointer=current_pointer) # after this we point to return value cell
code += self.function_to_call.get_code(
current_stack_pointer=current_pointer
) # after this we point to return value cell
code += ">" # point to next available cell (one after return value)
return code
@ -377,9 +413,10 @@ class NodeArraySetElement(NodeArrayElement):
class NodeArrayAssignment(Node):
"""
Used for array assignment
E.g arr = = { 1, 2, 3... }
Used for array assignment
E.g arr = = { 1, 2, 3... }
"""
def __init__(self, ids_map_list, token_id, literal_tokens_list):
Node.__init__(self, ids_map_list)
self.token_id = token_id
@ -387,7 +424,9 @@ class NodeArrayAssignment(Node):
def get_code(self, current_pointer, *args, **kwargs):
array_dimensions = get_variable_dimensions_from_token(self.ids_map_list, self.token_id)
unpacked_literals_list = unpack_literal_tokens_to_array_dimensions(self.token_id, array_dimensions, self.literal_tokens_list)
unpacked_literals_list = unpack_literal_tokens_to_array_dimensions(
self.token_id, array_dimensions, self.literal_tokens_list
)
offset = get_offset_to_variable(self.ids_map_list, self.token_id, current_pointer)
code = "<" * offset # point to first array element

View file

@ -15,9 +15,13 @@ def optimize_once(tokens):
# optimize arithmetic operations. E.g replace 1+2 with 3
# need to be careful not to optimize (1+2*3) to (3*3)
if tokens[start_index+1].data in ["*", "/", "%"] or (start_index+3 >= len(tokens)) or (tokens[start_index+3].data not in ["*", "/", "%"]):
num1, num2 = get_NUM_token_value(tokens[start_index]), get_NUM_token_value(tokens[start_index+2])
op = tokens[start_index+1].data
if (
tokens[start_index + 1].data in ["*", "/", "%"]
or (start_index + 3 >= len(tokens))
or (tokens[start_index + 3].data not in ["*", "/", "%"])
):
num1, num2 = get_NUM_token_value(tokens[start_index]), get_NUM_token_value(tokens[start_index + 2])
op = tokens[start_index + 1].data
if op == "+":
val = num1 + num2
elif op == "-":
@ -38,8 +42,13 @@ def optimize_once(tokens):
raise NotImplementedError(op)
# remove the 3 old tokens and replace them with new one
new_token = Token(Token.NUM, tokens[start_index].line, tokens[start_index].column, data=str(val),
original_tokens=tokens[start_index:start_index+3])
new_token = Token(
Token.NUM,
tokens[start_index].line,
tokens[start_index].column,
data=str(val),
original_tokens=tokens[start_index : start_index + 3],
)
for _ in range(3):
tokens.pop(start_index)
@ -52,16 +61,24 @@ def optimize_once(tokens):
# replace printint(50) with print("50")
# since printing strings compiles into less Brainfuck code than printing ints
if tokens[start_index].data == "printint":
tokens[start_index] = Token(Token.PRINT, tokens[start_index].line, tokens[start_index].column, original_tokens=[tokens[start_index]])
tokens[start_index+2] = Token(Token.STRING, tokens[start_index].line, tokens[start_index].column,
data=str(tokens[start_index+2].data), original_tokens=[tokens[start_index+2]])
tokens[start_index] = Token(
Token.PRINT, tokens[start_index].line, tokens[start_index].column, original_tokens=[tokens[start_index]]
)
tokens[start_index + 2] = Token(
Token.STRING,
tokens[start_index].line,
tokens[start_index].column,
data=str(tokens[start_index + 2].data),
original_tokens=[tokens[start_index + 2]],
)
return True
return False
rules = [([Token.NUM, Token.BINOP, Token.NUM], optimize_binop), # arithmetic operations
([Token.ID, Token.LPAREN, Token.NUM, Token.RPAREN], optimize_printint), # printint(50) to print("50")
]
rules = [
([Token.NUM, Token.BINOP, Token.NUM], optimize_binop), # arithmetic operations
([Token.ID, Token.LPAREN, Token.NUM, Token.RPAREN], optimize_printint), # printint(50) to print("50")
]
# try to match one of the rules to the tokens in a "sliding window" style
i = 0
@ -69,7 +86,7 @@ def optimize_once(tokens):
optimized = False
for tokens_sequence, optimization_function in rules:
if i + len(tokens_sequence) <= len(tokens):
if all(tokens_sequence[n] == tokens[i+n].type for n in range(len(tokens_sequence))):
if all(tokens_sequence[n] == tokens[i + n].type for n in range(len(tokens_sequence))):
if optimization_function(tokens, i):
optimized = True
if optimized:
@ -82,7 +99,7 @@ def optimize(tokens):
prev_tokens = [token.type for token in tokens]
while True:
optimize_once(tokens)
print(".", end='')
print(".", end="")
current_tokens = [token.type for token in tokens]
if current_tokens == prev_tokens:
break

View file

@ -1,12 +1,13 @@
from .Exceptions import BFSyntaxError, BFSemanticError
from .Token import Token
from .Exceptions import BFSemanticError, BFSyntaxError
from .General import is_token_literal
from .Token import Token
class Parser:
"""
Used to easily iterate tokens
"""
def __init__(self, tokens):
self.tokens = tokens
self.current_token_index = 0
@ -80,7 +81,10 @@ class Parser:
raise BFSyntaxError("Expected %s after %s" % (str(tokens_list), str(self.tokens[starting_index])))
for i in range(0, len(tokens_list)):
if self.tokens[starting_index + 1 + i].type != tokens_list[i]:
raise BFSyntaxError("Expected %s after %s" % (str(tokens_list[i]), [str(t) for t in self.tokens[starting_index: starting_index+1+i]]))
raise BFSyntaxError(
"Expected %s after %s"
% (str(tokens_list[i]), [str(t) for t in self.tokens[starting_index : starting_index + 1 + i]])
)
def check_next_token_is(self, token, starting_index=None):
self.check_next_tokens_are([token], starting_index=starting_index)

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
import sys
import argparse
import sys
def create_jumps_dictionary(program):
@ -9,9 +9,9 @@ def create_jumps_dictionary(program):
res = dict()
for index, command in enumerate(program):
if command == '[':
if command == "[":
lbraces.append(index)
elif command == ']':
elif command == "]":
if len(lbraces) == 0:
raise SyntaxError("Brainfuck: mismatched parentheses (at index: %s)" % index)
@ -35,26 +35,26 @@ def brainfuck(program, bits=8):
while instruction_pointer < len(program):
command = program[instruction_pointer]
if command == '>':
if command == ">":
data_pointer += 1
elif command == '<':
elif command == "<":
data_pointer -= 1
elif command == '+':
data[data_pointer] = (data.get(data_pointer, 0) + 1)
if data[data_pointer] == 2 ** bits:
elif command == "+":
data[data_pointer] = data.get(data_pointer, 0) + 1
if data[data_pointer] == 2**bits:
data[data_pointer] = 0
elif command == '-':
data[data_pointer] = (data.get(data_pointer, 0) - 1)
elif command == "-":
data[data_pointer] = data.get(data_pointer, 0) - 1
if data[data_pointer] == -1:
data[data_pointer] = 2 ** bits - 1
elif command == ',':
data[data_pointer] = 2**bits - 1
elif command == ",":
data[data_pointer] = ord(sys.stdin.read(1)) % 256
elif command == '.':
print(chr(data.get(data_pointer, 0)), end='', flush=True)
elif command == '[':
elif command == ".":
print(chr(data.get(data_pointer, 0)), end="", flush=True)
elif command == "[":
if data.get(data_pointer, 0) == 0:
instruction_pointer = jumps[instruction_pointer]
elif command == ']':
elif command == "]":
if data.get(data_pointer, 0) != 0:
instruction_pointer = jumps[instruction_pointer]
else: # everything else is comment
@ -63,16 +63,19 @@ def brainfuck(program, bits=8):
instruction_pointer += 1
if data_pointer != 0:
print("WARNING (interpreter) - at the end of the execution the data pointer is %s instead of 0 (possibly a compiler issue)" % str(data_pointer))
print(
"WARNING (interpreter) - at the end of the execution the data pointer is %s instead of 0 (possibly a compiler issue)"
% str(data_pointer)
)
if __name__ == '__main__':
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("filepath")
parser.add_argument("--bits", "-b", "--interpreter-bits", type=int, default=8, help="Amount of bits each cell uses")
args = parser.parse_args()
with open(args.filepath, 'r') as f:
with open(args.filepath, "r") as f:
code = f.read()
brainfuck(code, args.bits)

View file

@ -57,7 +57,7 @@ int main()
$ ./BF-it.py helloworld.code
Compiling file 'helloworld.code'...
Compiled successfully to 'helloworld.bf'
$ cat helloworld.bf
$ cat helloworld.bf
>[-]>[-]<>++++++++[-<+++++++++>]<.>++++[-<+++++++>]
<+.+++++++..+++.>++++++[-<------------->]<-.>+++++[
-<+++++++++++>]<.>++++[-<++++++>]<.+++.------.-----
@ -98,4 +98,3 @@ If you found a bug, or have an idea for a feature, open an issue
* https://introcs.cs.princeton.edu/java/11precedence/ for operator precedence
* https://logomakr.com/ for creating a logo
* https://www.youtube.com/ for setting the mood