From: David Kerkeslager Date: Fri, 11 Aug 2017 15:50:11 +0000 (-0400) Subject: Closures (and garbage collection for environments, which is required by closures... X-Git-Url: https://code.kerkeslager.com/?a=commitdiff_plain;h=d6af7d074bf65e782e42055623a197863b5f8000;p=fur Closures (and garbage collection for environments, which is required by closures) (#1) * Add proper reference counting to environments * It has become clear we need a garbage collector for environments before we can implement closures * Allow line comments starting with # * Allocate Environments from a garbage-collected EnvironmentPool * Wrap closure functions in a struct in preparation for storing the defining environment with them * Move the instantiation of closures into where they are created * Fixed a bug in the mark/sweep algorithm: * It would infinitely recurse in the case of cycles, because nothing was checking that Environments had already been marked. * Simply exiting on self->mark == mark would fix this, but introduce a separate, worse bug where recursion wouldn't reach some live objects, since some objects might already be marked with the current mark due to previous cycles. * To fix, I introduced a pass over the pool that marks everything false so we can reliably assume that self->mark == true means that the environment has been marked in the current GC round. It's slower than I wanted, but it's better to do the correct thing slowly than the wrong thing quickly. * Store the defining environment on closures and GC it appropriately * Test a simple case of closures and fix the discovered errors * Add a test that explicitly demonstrates freeing a cycle * Allow user-defined functions to take arguments * Another closure example --- diff --git a/examples/18_comments.fur b/examples/18_comments.fur new file mode 100644 index 0000000..bab72ba --- /dev/null +++ b/examples/18_comments.fur @@ -0,0 +1,2 @@ +# Furface is a great cat +print('Hello, world\n') diff --git a/examples/18_comments.fur.output.txt b/examples/18_comments.fur.output.txt new file mode 100644 index 0000000..a5c1966 --- /dev/null +++ b/examples/18_comments.fur.output.txt @@ -0,0 +1 @@ +Hello, world diff --git a/examples/19_closures.fur b/examples/19_closures.fur new file mode 100644 index 0000000..eb7fd07 --- /dev/null +++ b/examples/19_closures.fur @@ -0,0 +1,15 @@ +def outer() do + hi = 'Hello, world\n' + + def inner() do + hi + end + + inner +end + +get_greeting = outer() + +print(get_greeting()) + + diff --git a/examples/19_closures.fur.output.txt b/examples/19_closures.fur.output.txt new file mode 100644 index 0000000..a5c1966 --- /dev/null +++ b/examples/19_closures.fur.output.txt @@ -0,0 +1 @@ +Hello, world diff --git a/examples/20_cycles.fur b/examples/20_cycles.fur new file mode 100644 index 0000000..ea5b20c --- /dev/null +++ b/examples/20_cycles.fur @@ -0,0 +1,30 @@ +# Environment A is created here + +# outer references environment A +def outer() do + # Environment B is created here referencing A + + # middle references environment B + def middle() do + # Environment C is created here referencing B + + # inner references environment C + def inner() do + # This is never executed + 42 + end + + inner + # At this point, environment C falls out of scope + # However, environment C is still referenced by inner, which is being returned + end + + variable = middle() + # Now environment B -> variable -> inner -> C -> B + + 42 + # Nothing from the cycle is returned and B falls out of scope + # All references are lost, but reference counts are still > 0 because of the cycle +end + +print(outer()) diff --git a/examples/20_cycles.fur.output.txt b/examples/20_cycles.fur.output.txt new file mode 100644 index 0000000..f70d7bb --- /dev/null +++ b/examples/20_cycles.fur.output.txt @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/examples/21_arguments.fur b/examples/21_arguments.fur new file mode 100644 index 0000000..3321724 --- /dev/null +++ b/examples/21_arguments.fur @@ -0,0 +1,10 @@ +def increment(n) do + n + 1 +end + +def add(a, b) do + a + b +end + +print(increment(41), '\n') +print(add(40, 2), '\n') diff --git a/examples/21_arguments.fur.output.txt b/examples/21_arguments.fur.output.txt new file mode 100644 index 0000000..daaac9e --- /dev/null +++ b/examples/21_arguments.fur.output.txt @@ -0,0 +1,2 @@ +42 +42 diff --git a/examples/22_close_arguments.fur b/examples/22_close_arguments.fur new file mode 100644 index 0000000..5ceee14 --- /dev/null +++ b/examples/22_close_arguments.fur @@ -0,0 +1,13 @@ +def make_incrementer(increment_amount) do + def result(i) do + increment_amount + i + end + + result +end + +increment_by_one = make_incrementer(1) +increment_by_two = make_incrementer(2) + +print(increment_by_one(41), '\n') +print(increment_by_two(40), '\n') diff --git a/examples/22_close_arguments.fur.output.txt b/examples/22_close_arguments.fur.output.txt new file mode 100644 index 0000000..daaac9e --- /dev/null +++ b/examples/22_close_arguments.fur.output.txt @@ -0,0 +1,2 @@ +42 +42 diff --git a/generation.py b/generation.py index 3ad3c0d..a1e85f1 100644 --- a/generation.py +++ b/generation.py @@ -22,10 +22,10 @@ CONSTANT_EXPRESSION_MAPPING = { def generate_constant_expression(c_constant_expression): return CONSTANT_EXPRESSION_MAPPING[c_constant_expression.value] -def generate_symbol_expression(c_symbol_expression): +def generate_symbol_expression(symbol_expression): return 'Environment_get(environment, SYMBOL_LIST[{}] /* symbol: {} */)'.format( - c_symbol_expression.symbol_list_index, - c_symbol_expression.symbol, + symbol_expression.symbol_list_index, + symbol_expression.symbol, ) def generate_variable_expression(expression): @@ -65,8 +65,13 @@ def generate_negation_expression(c_negation_expression): ) def generate_function_call(function_call): - return 'Environment_get(environment, "{}").instance.closure(environment, {}, {})'.format( + # TODO This gets called twice, which is really inefficient--normalization would also allow other clauses besides a variable reference + get_closure_clause = 'Environment_get(environment, "{}").instance.closure'.format( function_call.name, + ) + return '{}.call(environmentPool, {}.closed, {}, {})'.format( + get_closure_clause, + get_closure_clause, function_call.argument_count, # TODO This is just a single item containing a reference to the items list--make that clearer generate_expression(function_call.argument_items), @@ -80,11 +85,11 @@ def generate_expression_statement(statement): # TODO Do we need to garbage collect the results of arbitrary statements? return '{};'.format(generate_expression(statement.expression)) -def generate_symbol_assignment_statement(c_assignment_statement): +def generate_symbol_assignment_statement(statement): return 'Environment_set(environment, SYMBOL_LIST[{}] /* symbol: {} */, {});'.format( - c_assignment_statement.target_symbol_list_index, - c_assignment_statement.target, - generate_expression(c_assignment_statement.expression), + statement.target_symbol_list_index, + statement.target, + generate_expression(statement.expression), ) def generate_array_variable_initialization_statement(statement): @@ -142,7 +147,7 @@ def generate_if_else_statement(statement): return generated_if_clause + generated_if_statements + generated_else_statements def generate_function_declaration(statement): - return 'Environment_set(environment, "{}", user${});'.format(statement.name, statement.name) + return 'Environment_set(environment, "{}", (Object){{ CLOSURE, (Instance)(Closure){{ environment, user${}$implementation }} }});'.format(statement.name, statement.name) def generate_statement(statement): return { diff --git a/normalization.py b/normalization.py index 3fa52ac..1cba840 100644 --- a/normalization.py +++ b/normalization.py @@ -67,6 +67,14 @@ NormalExpressionStatement = collections.namedtuple( ], ) +NormalAssignmentStatement = collections.namedtuple( + 'NormalAssignmentStatement', + [ + 'target', + 'expression', + ], +) + NormalIfElseStatement = collections.namedtuple( 'NormalIfElseStatement', [ @@ -80,6 +88,7 @@ NormalFunctionDefinitionStatement = collections.namedtuple( 'NormalFunctionDefinitionStatement', [ 'name', + 'argument_name_list', 'statement_list', ], ) @@ -322,13 +331,25 @@ def normalize_function_definition_statement(counter, statement): (), NormalFunctionDefinitionStatement( name=statement.name, + argument_name_list=statement.argument_name_list, statement_list=normalize_statement_list(statement.statement_list), ), ) +def normalize_assignment_statement(counter, statement): + counter, prestatements, normalized_expression = normalize_expression(counter, statement.expression) + return ( + counter, + prestatements, + NormalAssignmentStatement( + target=statement.target, + expression=normalized_expression, + ), + ) + def normalize_statement(counter, statement): return { - parsing.FurAssignmentStatement: fake_normalization, # TODO unfake this + parsing.FurAssignmentStatement: normalize_assignment_statement, parsing.FurExpressionStatement: normalize_expression_statement, parsing.FurFunctionDefinitionStatement: normalize_function_definition_statement, }[type(statement)](counter, statement) diff --git a/parsing.py b/parsing.py index 041f3aa..6734af7 100644 --- a/parsing.py +++ b/parsing.py @@ -212,30 +212,35 @@ def _or_level_expression_parser(index, tokens): 'or_level', )(index, tokens) -def _comma_separated_list_parser(index, tokens): - start_index = index +def _comma_separated_list_parser(subparser): + def result_parser(index, tokens): + start_index = index - expressions = [] + items = [] - success, index, expression = _expression_parser(index, tokens) + success, index, item = subparser(index, tokens) - if success: - expressions.append(expression) - else: - return (True, start_index, ()) + if success: + items.append(item) + else: + return (True, start_index, ()) - while success and index < len(tokens) and tokens[index].type == 'comma': - success = False + while success and index < len(tokens) and tokens[index].type == 'comma': + success = False - if index + 1 < len(tokens): - success, try_index, expression = _expression_parser(index + 1, tokens) + if index + 1 < len(tokens): + success, try_index, item = subparser(index + 1, tokens) - if success: - expressions.append(expression) - index = try_index + if success: + items.append(item) + index = try_index - return True, index, tuple(expressions) + return True, index, tuple(items) + return result_parser + +def _comma_separated_expression_list_parser(index, tokens): + return _comma_separated_list_parser(_expression_parser)(index, tokens) FurFunctionCallExpression = collections.namedtuple( 'FurFunctionCallExpression', @@ -264,6 +269,7 @@ FurFunctionDefinitionStatement = collections.namedtuple( 'FurFunctionDefinitionStatement', [ 'name', + 'argument_name_list', 'statement_list', ], ) @@ -288,7 +294,7 @@ def _function_call_expression_parser(index, tokens): return failure index += 1 - success, index, arguments = _comma_separated_list_parser(index, tokens) + success, index, arguments = _comma_separated_expression_list_parser(index, tokens) if not success: return failure @@ -363,6 +369,11 @@ def _function_definition_statement_parser(index, tokens): tokens[index].line, )) + success, index, argument_name_list = _comma_separated_list_parser(_symbol_expression_parser)( + index, + tokens, + ) + if tokens[index].type == 'close_parenthese': index += 1 else: @@ -385,7 +396,11 @@ def _function_definition_statement_parser(index, tokens): else: return failure - return True, index, FurFunctionDefinitionStatement(name=name, statement_list=statement_list) + return True, index, FurFunctionDefinitionStatement( + name=name, + argument_name_list=tuple(an.value for an in argument_name_list), + statement_list=statement_list, + ) def _statement_parser(index, tokens): _, index, _ = consume_newlines(index, tokens) diff --git a/templates/program.c b/templates/program.c index 897ca5e..2a08f39 100644 --- a/templates/program.c +++ b/templates/program.c @@ -4,6 +4,20 @@ #include #include +/* Some terminology used in function names: + * - initialize: These functions take a pointer and potentially some other arguments, and use those + * to initialize the value pointed to by self. Initialize functions DO NOT allocate the function, + * so they can be used to initialize stack-allocated variables. + * - construct: This allocates a value for a pointer, initializes it, and returns it. This is for + * heap-allocated values. It may be as simple as allocating the memory, calling an initialize, and + * returning it. + * - deinitialize: These functions dereference or free any objects pointed to by the self pointer's + * value, but they don't actually free the self pointer. This is useful for stack-allocated objects + * which point to heap-allocated objects. + * - destruct: This dereferences or frees memory pointed to by the self argument, and all the + * pointers on the self argument. + */ + {% for standard_library in standard_libraries %} #include <{{standard_library}}> {% endfor %} @@ -18,6 +32,8 @@ struct EnvironmentNode; typedef struct EnvironmentNode EnvironmentNode; struct Environment; typedef struct Environment Environment; +struct EnvironmentPool; +typedef struct EnvironmentPool EnvironmentPool; const char* const STRING_LITERAL_LIST[] = { {% for string_literal in string_literal_list %} @@ -39,10 +55,18 @@ enum Type STRING }; +struct Closure; +typedef struct Closure Closure; +struct Closure +{ + Environment* closed; + Object (*call)(EnvironmentPool*, Environment*, size_t, Object*); +}; + union Instance { bool boolean; - Object (*closure)(Environment*, size_t, Object*); + Closure closure; int32_t integer; const char* string; }; @@ -72,34 +96,63 @@ struct EnvironmentNode struct Environment { - size_t referenceCount; + bool mark; + bool live; + Environment* parent; EnvironmentNode* root; }; -Environment* Environment_construct(Environment* parent) +void Environment_initialize(Environment* self, Environment* parent) { - Environment* result = malloc(sizeof(Environment)); - result->referenceCount = 1; - result->parent = parent; - result->root = NULL; - return result; + self->parent = parent; + self->root = NULL; + + // We are currently only ever initializing environments at the beginning of running functions, so + // for now at least we can assume that we want it to be live immediately. + self->live = true; } -void Environment_destruct(Environment* self) +void Environment_deinitialize(Environment* self) { - self->referenceCount--; + EnvironmentNode* next; + for(EnvironmentNode* node = self->root; node != NULL; node = next) + { + next = node->next; + free(node); + } +} - if(self->referenceCount == 0) +void Environment_setLive(Environment* self, bool live) +{ + self->live = live; +} + +void Environment_mark(Environment* self) +{ + if(self == NULL) return; + if(self->mark) return; // Prevents infinite recursion in the case of cycles + + self->mark = true; + + Environment_mark(self->parent); + + for(EnvironmentNode* node = self->root; node != NULL; node = node->next) { - EnvironmentNode* next; - for(EnvironmentNode* node = self->root; node != NULL; node = next) + switch(node->value.type) { - // No objects are allocated on the heap (yet!) so we don't need to free anything else - next = node->next; - free(node); + case BOOLEAN: + case INTEGER: + case STRING: + break; + + case CLOSURE: + Environment_mark(node->value.instance.closure.closed); + break; + + default: + assert(false); } - free(self); } } @@ -133,6 +186,132 @@ Object Environment_get(Environment* self, const char* const symbol) assert(false); } +# define POOL_SIZE 64 +struct EnvironmentPool +{ + int8_t freeIndex; + bool allocatedFlags[POOL_SIZE]; + Environment environments[POOL_SIZE]; + EnvironmentPool* overflow; +}; + +EnvironmentPool* EnvironmentPool_construct(); +void EnvironmentPool_initialize(EnvironmentPool*); +void EnvironmentPool_deinitialize(EnvironmentPool*); +void EnvironmentPool_destruct(EnvironmentPool*); + +EnvironmentPool* EnvironmentPool_construct() +{ + EnvironmentPool* result = malloc(sizeof(EnvironmentPool)); + EnvironmentPool_initialize(result); + return result; +} + +void EnvironmentPool_initialize(EnvironmentPool* self) +{ + self->overflow = NULL; + self->freeIndex = 0; + + for(size_t i = 0; i < POOL_SIZE; i++) + { + self->allocatedFlags[i] = false; + self->environments[i].live = false; + } +} + +void EnvironmentPool_deinitialize(EnvironmentPool* self) +{ + // We can assume if this is being called, none of the Environments are live + for(int8_t i = 0; i < POOL_SIZE; i++) + { + if(self->allocatedFlags[i]) Environment_deinitialize(&(self->environments[i])); + } + + EnvironmentPool_destruct(self->overflow); +} + +void EnvironmentPool_destruct(EnvironmentPool* self) +{ + if(self == NULL) return; + EnvironmentPool_deinitialize(self); + free(self); +} + +void EnvironmentPool_GC(EnvironmentPool* self) +{ + // Unmark all the environments + for(EnvironmentPool* current = self; current != NULL; current = current->overflow) + { + for(int8_t i = 0; i < POOL_SIZE; i++) + { + current->environments[i].mark = false; + } + } + + // Mark live enviroments and environments referenced by live environments + for(EnvironmentPool* current = self; current != NULL; current = current->overflow) + { + for(int8_t i = 0; i < POOL_SIZE; i++) + { + if(current->environments[i].live) + { + Environment_mark(&(current->environments[i])); + } + } + } + + // TODO We never free pools until the very end--we could free a pool if two pools are empty + for(EnvironmentPool* current = self; current != NULL; current = current->overflow) + { + for(int8_t i = POOL_SIZE - 1; i >= 0; i--) + { + if(!current->environments[i].mark && current->allocatedFlags[i]) + { + Environment_deinitialize(&(current->environments[i])); + current->allocatedFlags[i] = false; + current->freeIndex = i; + } + } + } +} + +Environment* EnvironmentPool_allocate(EnvironmentPool* self) +{ + for(EnvironmentPool* current = self; current != NULL; current = current->overflow) + { + for(; current->freeIndex < POOL_SIZE; current->freeIndex++) + { + if(!current->allocatedFlags[current->freeIndex]) + { + current->allocatedFlags[current->freeIndex] = true; + return &(current->environments[current->freeIndex]); + } + } + } + + EnvironmentPool_GC(self); + + EnvironmentPool* previous; + for(EnvironmentPool* current = self; current != NULL; current = current->overflow) + { + for(; current->freeIndex < POOL_SIZE; current->freeIndex++) + { + if(!current->allocatedFlags[current->freeIndex]) + { + current->allocatedFlags[current->freeIndex] = true; + return &(current->environments[current->freeIndex]); + } + else + { + previous = current; + } + } + } + + previous->overflow = EnvironmentPool_construct(); + return EnvironmentPool_allocate(previous->overflow); +} + Object integerLiteral(int32_t literal) { Object result; @@ -174,7 +353,7 @@ Object operator${{ id.name }}(Object left, Object right) {% endfor %} {% if 'pow' in builtins %} -Object builtin$pow$implementation(Environment* parent, size_t argc, Object* args) +Object builtin$pow$implementation(EnvironmentPool* environmentPool, Environment* parent, size_t argc, Object* args) { assert(argc == 2); @@ -190,11 +369,11 @@ Object builtin$pow$implementation(Environment* parent, size_t argc, Object* args return result; } -Object builtin$pow = { CLOSURE, (Instance)builtin$pow$implementation }; +Object builtin$pow = { CLOSURE, (Instance)(Closure){ NULL, builtin$pow$implementation } }; {% endif %} {% if 'print' in builtins %} -Object builtin$print$implementation(Environment* parent, size_t argc, Object* args) +Object builtin$print$implementation(EnvironmentPool* environmentPool, Environment* parent, size_t argc, Object* args) { for(size_t i = 0; i < argc; i++) { @@ -205,6 +384,11 @@ Object builtin$print$implementation(Environment* parent, size_t argc, Object* ar fputs(output.instance.boolean ? "true" : "false", stdout); break; + case CLOSURE: + // TODO Print something better + printf(""); + break; + case INTEGER: printf("%" PRId32, output.instance.integer); break; @@ -223,29 +407,37 @@ Object builtin$print$implementation(Environment* parent, size_t argc, Object* ar return FALSE; } -Object builtin$print = { CLOSURE, (Instance)builtin$print$implementation }; +Object builtin$print = { CLOSURE, (Instance)(Closure){ NULL, builtin$print$implementation } }; {% endif %} {% for function_definition in function_definition_list %} -Object user${{function_definition.name}}$implementation(Environment* parent, size_t argc, Object* args) +Object user${{function_definition.name}}$implementation(EnvironmentPool* environmentPool, Environment* parent, size_t argc, Object* args) { - Environment* environment = Environment_construct(parent); + assert(argc == {{ function_definition.argument_name_list|length }}); + + Environment* environment = EnvironmentPool_allocate(environmentPool); + Environment_initialize(environment, parent); + + {% for argument_name in function_definition.argument_name_list %} + Environment_set(environment, "{{ argument_name }}", args[{{ loop.index0 }}]); + {% endfor %} {% for statement in function_definition.statement_list[:-1] %} {{ generate_statement(statement) }} {% endfor %} Object result = {{ generate_statement(function_definition.statement_list[-1]) }} - Environment_destruct(environment); + + Environment_setLive(environment, false); return result; } -Object user${{function_definition.name}} = { CLOSURE, (Instance)user${{function_definition.name}}$implementation }; {% endfor %} - int main(int argc, char** argv) { - Environment* environment = Environment_construct(NULL); + EnvironmentPool* environmentPool = EnvironmentPool_construct(); + Environment* environment = EnvironmentPool_allocate(environmentPool); + Environment_initialize(environment, NULL); // TODO Use the symbol from SYMBOL_LIST {% for builtin in builtins %} @@ -256,7 +448,7 @@ int main(int argc, char** argv) {{ generate_statement(statement) }} {% endfor %} - Environment_destruct(environment); - + Environment_setLive(environment, false); + EnvironmentPool_destruct(environmentPool); return 0; } diff --git a/tokenization.py b/tokenization.py index 1a2653e..3c4dc6f 100644 --- a/tokenization.py +++ b/tokenization.py @@ -58,6 +58,12 @@ def tokenize(source): index += 1 continue + if source[index] == '#': + while index < len(source) and source[index] != '\n': + index += 1 + + continue + success = False for matcher in _TOKEN_MATCHERS: diff --git a/transformation.py b/transformation.py index 1a4a8bb..41cd712 100644 --- a/transformation.py +++ b/transformation.py @@ -125,6 +125,7 @@ CFunctionDefinition = collections.namedtuple( 'CFunctionDefinition', [ 'name', + 'argument_name_list', 'statement_list', ], ) @@ -167,12 +168,15 @@ def transform_symbol_expression(accumulators, expression): if expression.value in ['true', 'false']: return CConstantExpression(value=expression.value) - if expression.value not in accumulators.symbol_list: - symbol_list.append(expression.value) + try: + symbol_list_index = accumulators.symbol_list.index(expression.value) + except ValueError: + symbol_list_index = len(accumulators.symbol_list) + accumulators.symbol_list.append(expression.value) return CSymbolExpression( symbol=expression.value, - symbol_list_index=accumulators.symbol_list.index(expression.value), + symbol_list_index=symbol_list_index, ) CInfixDeclaration = collections.namedtuple( @@ -277,12 +281,15 @@ def transform_expression(accumulators, expression): def transform_symbol_assignment_statement(accumulators, assignment_statement): # TODO Check that target is not a builtin - if assignment_statement.target not in accumulators.symbol_list: + try: + symbol_list_index = accumulators.symbol_list.index(assignment_statement.target) + except ValueError: + symbol_list_index = len(accumulators.symbol_list) accumulators.symbol_list.append(assignment_statement.target) return CSymbolAssignmentStatement( target=assignment_statement.target, - target_symbol_list_index=accumulators.symbol_list.index(assignment_statement.target), + target_symbol_list_index=symbol_list_index, expression=transform_expression( accumulators, assignment_statement.expression, @@ -302,18 +309,8 @@ def transform_function_call_expression(accumulators, function_call): ) def transform_expression_statement(accumulators, statement): - # TODO At some point we can verify that all expression types are supported and just call transform_expression - expression = { - parsing.FurFunctionCallExpression: transform_function_call_expression, - parsing.FurInfixExpression: transform_expression, - parsing.FurIntegerLiteralExpression: transform_expression, - parsing.FurSymbolExpression: transform_expression, - normalization.NormalFunctionCallExpression: transform_function_call_expression, - normalization.NormalVariableExpression: transform_expression, - }[type(statement.expression)](accumulators, statement.expression) - return CExpressionStatement( - expression=expression, + expression=transform_expression(accumulators, statement.expression), ) def transform_if_else_statement(accumulators, statement): @@ -346,8 +343,10 @@ def transform_function_definition_statement(accumulators, statement): if any(fd.name == statement.name for fd in accumulators.function_definition_list): raise Exception('A function with name "{}" already exists'.format(statement.name)) + # TODO Add argument names to the symbol table accumulators.function_definition_list.append(CFunctionDefinition( name=statement.name, + argument_name_list=statement.argument_name_list, statement_list=tuple(transform_statement(accumulators, s) for s in statement.statement_list) )) @@ -355,9 +354,9 @@ def transform_function_definition_statement(accumulators, statement): def transform_statement(accumulators, statement): return { - parsing.FurAssignmentStatement: transform_symbol_assignment_statement, parsing.FurExpressionStatement: transform_expression_statement, normalization.NormalArrayVariableInitializationStatement: transform_array_variable_initialization_statement, + normalization.NormalAssignmentStatement: transform_symbol_assignment_statement, normalization.NormalExpressionStatement: transform_expression_statement, normalization.NormalFunctionDefinitionStatement: transform_function_definition_statement, normalization.NormalIfElseStatement: transform_if_else_statement,