From 284b70084e0665061553fbe4c40edff700478916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enmanuel=20Verdesia=20Su=C3=A1rez?= Date: Wed, 28 Apr 2021 14:32:56 -0400 Subject: [PATCH 001/161] Added lexer and related errors --- src/coolcmp/__init__.py | 0 src/coolcmp/errors.py | 9 ++ src/coolcmp/lexer.py | 254 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 src/coolcmp/__init__.py create mode 100644 src/coolcmp/errors.py create mode 100644 src/coolcmp/lexer.py diff --git a/src/coolcmp/__init__.py b/src/coolcmp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/coolcmp/errors.py b/src/coolcmp/errors.py new file mode 100644 index 000000000..b8d3dbca1 --- /dev/null +++ b/src/coolcmp/errors.py @@ -0,0 +1,9 @@ +""" +Compilation errors. +""" + +LEX_ERROR = '(%s,%s) - LexicographicError: Unexpected symbol "%s".' +UNT_STR = '(%s,%s) - LexicographicError: Unterminated string.' +EOF_STR = '(%s,%s) - LexicographicError: EOF in string.' +NULL_STR = '(%s,%s) - LexicographicError: String contains null character' +EOF_COMM = '(%s,%s) - LexicographicError: EOF in comment.' diff --git a/src/coolcmp/lexer.py b/src/coolcmp/lexer.py new file mode 100644 index 000000000..d189095b9 --- /dev/null +++ b/src/coolcmp/lexer.py @@ -0,0 +1,254 @@ +from ply import lex +from ply.lex import TOKEN + +import errors as err + + +class Lexer: + """ + Cool Lexer. + """ + def __init__(self, build_lexer=False, debug=False): + self.lexer = None + self.errors = [] + + if build_lexer: + self.lexer = lex.lex(module=self, debug=True) + + def build(self, **kwargs): + self.lexer = lex.lex(module=self, **kwargs) + + states = ( + ('STR', 'exclusive'), + ('COMM', 'exclusive') + ) + + reserved = { + 'class': 'CLASS', + 'inherits': 'INHERITS', + 'if': 'IF', + 'then': 'THEN', + 'else': 'ELSE', + 'fi': 'FI', + 'while': 'WHILE', + 'loop': 'LOOP', + 'pool': 'POOL', + 'case': 'CASE', + 'of': 'OF', + 'esac': 'ESAC', + 'let': 'LET', + 'new': 'NEW', + 'isvoid': 'ISVOID', + 'not': 'NOT', + 'in': 'IN', + } + + tokens = [ + # Identifiers + 'TYPE', 'ID', + + # Built-in types + 'INT', 'STRING', 'BOOL', + + # Special Notation + 'PLUS', 'MINUS', 'STAR', 'DIV', 'LESS', 'LEQ', 'EQ', 'COMP', + 'COLON', 'SEMI', 'COMMA', 'DOT', 'ASSIGN', 'ARROW', 'AT', + 'OPAR', 'CPAR', 'OCCUR', 'CCUR' + + ] + list(reserved.values()) + + # Tokens defined as strings go after defined in functions. + # This order is not relevant, they are ordered by length (longer first). + t_PLUS = r'\+' + t_MINUS = r'\-' + t_STAR = r'\*' + t_DIV = r'\/' + t_LESS = r'\<' + t_LEQ = r'\<\=' + t_EQ = r'\=' + t_COMP = r'\~' + t_COLON = r'\:' + t_SEMI = r'\;' + t_COMMA = r'\,' + t_DOT = r'\.' + t_ASSIGN = r'\<\-' + t_ARROW = r'\=\>' + t_AT = r'\@' + t_OPAR = r'\(' + t_CPAR = r'\)' + t_OCCUR = r'\{' + t_CCUR = r'\}' + + t_ignore = ' \t\f\r' + t_ignore_comment = r'\-\-[^\n]*' + + # ######################### + # ##### INITIAL state ##### + # ######################### + + # match types + @TOKEN(r'[A-Z]\w*') + def t_TYPE(self, t: lex.LexToken) -> lex.LexToken: + if t.value[0] in ('t', 'f') and t.value.lower() in ('true', 'false'): + t.type = 'BOOL' + else: + t.type = self.reserved.get(t.value.lower(), 'TYPE') + + return t + + # match ids + @TOKEN(r'[a-z]\w*') + def t_ID(self, t: lex.LexToken) -> lex.LexToken: + if t.value[0] in ('t', 'f') and t.value.lower() in ('true', 'false'): + t.type = 'BOOL' + else: + t.type = self.reserved.get(t.value.lower(), 'ID') + + return t + + # match integers + @TOKEN(r'\d+') + def t_INT(self, t: lex.LexToken) -> lex.LexToken: + t.value = int(t.value) + + return t + + # also defined for COMM state, newline tracker + @TOKEN(r'\n+') + def t_INITIAL_COMM_newline(self, t: lex.LexToken): + t.lexer.lineno += len(t.value) + + # in case of error + def t_error(self, t: lex.LexToken): + t.lexer.skip(1) + + line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) + self.errors.append(err.LEX_ERROR % (line, col, t.value[0])) + + # ##################### + # ##### STR state ##### + # ##################### + + t_STR_ignore = '' + + @TOKEN(r'"') + def t_begin_STR(self, t: lex.LexToken): + t.lexer.string_start = t.lexer.lexpos - 1 + t.lexer.push_state('STR') + + @TOKEN(r'"') + def t_STR_end(self, t: lex.LexToken) -> lex.LexToken: + t.value = t.lexer.lexdata[t.lexer.string_start: t.lexer.lexpos] + t.type = 'STRING' + t.lexer.pop_state() + + return t + + @TOKEN(r'\n+') + def t_STR_newline(self, t: lex.LexToken): + line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) + self.errors.append(err.UNT_STR % (line, col)) + + t.lexer.lineno += len(t.value) + t.lexer.pop_state() + + @TOKEN(r'\\(.|\n)') + def t_STR_escaped(self, t: lex.LexToken): + lookahead = t.value[1] + + if lookahead == '\n': + t.lexer.lineno += 1 + elif lookahead == '0': # TODO: Not detecting null caracter in sting3.cl + line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) + self.errors.append(err.NULL_STR % (line, col + 1)) + + @TOKEN(r'[^"\n\\\0]+') + def t_STR_char(self, t: lex.LexToken): + pass + + def t_STR_error(self, t: lex.LexToken): + t.lexer.skip(1) + + def t_STR_eof(self, t: lex.LexToken): + line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) + self.errors.append(err.EOF_STR % (line, col)) + + # ###################### + # ##### COMM state ##### + # ###################### + + t_COMM_ignore = '' + + @TOKEN(r'\(\*') + def t_begin_COMM(self, t: lex.LexToken): + t.lexer.comment_start = t.lexer.lexpos + t.lexer.level = 1 + t.lexer.push_state('COMM') + + @TOKEN(r'\(\*') + def t_COMM_new(self, t: lex.LexToken): + t.lexer.level += 1 + + @TOKEN(r'\*\)') + def t_COMM_end(self, t: lex.LexToken): + t.lexer.level -= 1 + + if t.lexer.level == 0: + t.lexer.pop_state() + + def t_COMM_error(self, token): + token.lexer.skip(1) + + def t_COMM_eof(self, t: lex.LexToken): + line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) + self.errors.append(err.EOF_COMM % (line, col)) + + # ##################### + # not lexical functions + # ##################### + + def find_column(self, input_text: str, token: lex.LexToken) -> int: + """ + Used for compute column in case of error. + """ + line_start = input_text.rfind('\n', 0, token.lexpos) + 1 + + return (token.lexpos - line_start) + 1 + + def input(self, cool_source_code): + if self.lexer is None: + raise Exception('You must call first build method.') + + self.lexer.input(cool_source_code) + + def __iter__(self): + for token in self.lexer: + yield token + +if __name__ == '__main__': + import sys + + if len(sys.argv) != 2: + print('Usage: python3 lexer.py program.cl') + exit() + elif not sys.argv[1].endswith('.cl'): + print('COOl source code files must end with .cl extension.') + print('Usage: python3 lexer.py program.cl') + exit() + + cool_program = open(sys.argv[1], encoding='utf8').read() + # lexer = Lexer(build_lexer=True, debug=True) + lexer = Lexer() + lexer.build(debug=False) + lexer.input(cool_program) + + tokens = [(t.value, t.type) for t in lexer] + # print(tokens) + + for i in range(10): + try: + print(lexer.errors[i]) + except IndexError: + break + + print(len(lexer.errors)) From e988421ae8fac1266133997afa4ad651e670cc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enmanuel=20Verdesia=20Su=C3=A1rez?= Date: Wed, 28 Apr 2021 18:35:24 -0400 Subject: [PATCH 002/161] Defined compiler main entry point --- src/coolc.py | 33 +++++++++++++++++++++++++++++++++ src/coolc.sh | 6 ++++-- src/coolcmp/lexer.py | 9 ++++++--- 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 src/coolc.py diff --git a/src/coolc.py b/src/coolc.py new file mode 100644 index 000000000..c0e5d809d --- /dev/null +++ b/src/coolc.py @@ -0,0 +1,33 @@ +""" +Main entry point of COOL compiler +""" +from coolcmp.lexer import Lexer + + +def main(cool_program): + lexer = Lexer(build_lexer=True) + lexer.input(cool_program) + + [(t.value, t.type) for t in lexer] # make lexer process the input + errors = lexer.errors + + if errors: + for e in errors: + print(e) + + exit(1) + +if __name__ == '__main__': + import sys + + if len(sys.argv) != 2: + print('Usage: python3 coolc.py program.cl') + exit(1) + elif not sys.argv[1].endswith('.cl'): + print('COOl source code files must end with .cl extension.') + print('Usage: python3 coolc.py program.cl') + exit(1) + + cool_program = open(sys.argv[1], encoding='utf8').read() + + main(cool_program) \ No newline at end of file diff --git a/src/coolc.sh b/src/coolc.sh index 3088de4f9..76c776800 100755 --- a/src/coolc.sh +++ b/src/coolc.sh @@ -4,8 +4,10 @@ INPUT_FILE=$1 OUTPUT_FILE=${INPUT_FILE:0: -2}mips # Si su compilador no lo hace ya, aquí puede imprimir la información de contacto -echo "LINEA_CON_NOMBRE_Y_VERSION_DEL_COMPILADOR" # TODO: Recuerde cambiar estas -echo "Copyright (c) 2019: Nombre1, Nombre2, Nombre3" # TODO: líneas a los valores correctos +echo "COOL-Compiler v0.0.1" # TODO: Recuerde cambiar estas +echo "Copyright (c) 2021: Samuel David Suárez Roddríguez, Enmanuel Verdesia Suárez" # TODO: líneas a los valores correctos # Llamar al compilador echo "Compiling $INPUT_FILE into $OUTPUT_FILE" + +python3 coolc.py $INPUT_FILE diff --git a/src/coolcmp/lexer.py b/src/coolcmp/lexer.py index d189095b9..9f6a361ca 100644 --- a/src/coolcmp/lexer.py +++ b/src/coolcmp/lexer.py @@ -1,7 +1,10 @@ from ply import lex from ply.lex import TOKEN -import errors as err +try: + import errors as err +except ImportError: + import coolcmp.errors as err class Lexer: @@ -13,7 +16,7 @@ def __init__(self, build_lexer=False, debug=False): self.errors = [] if build_lexer: - self.lexer = lex.lex(module=self, debug=True) + self.lexer = lex.lex(module=self, debug=debug) def build(self, **kwargs): self.lexer = lex.lex(module=self, **kwargs) @@ -251,4 +254,4 @@ def __iter__(self): except IndexError: break - print(len(lexer.errors)) + # print(len(lexer.errors)) From 5722e343464b8caf2db871f5cb9478b1fd1c85d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enmanuel=20Verdesia=20Su=C3=A1rez?= Date: Wed, 28 Apr 2021 20:23:57 -0400 Subject: [PATCH 003/161] Lexer passed all tests --- src/coolc.py | 2 +- src/coolc.sh | 2 +- src/coolcmp/lexer.py | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/coolc.py b/src/coolc.py index c0e5d809d..d9f003652 100644 --- a/src/coolc.py +++ b/src/coolc.py @@ -30,4 +30,4 @@ def main(cool_program): cool_program = open(sys.argv[1], encoding='utf8').read() - main(cool_program) \ No newline at end of file + main(cool_program) diff --git a/src/coolc.sh b/src/coolc.sh index 76c776800..11c91c8e6 100755 --- a/src/coolc.sh +++ b/src/coolc.sh @@ -5,7 +5,7 @@ OUTPUT_FILE=${INPUT_FILE:0: -2}mips # Si su compilador no lo hace ya, aquí puede imprimir la información de contacto echo "COOL-Compiler v0.0.1" # TODO: Recuerde cambiar estas -echo "Copyright (c) 2021: Samuel David Suárez Roddríguez, Enmanuel Verdesia Suárez" # TODO: líneas a los valores correctos +# echo "Copyright (c) 2021: Samuel David Suárez Roddríguez, Enmanuel Verdesia Suárez" # TODO: líneas a los valores correctos # Llamar al compilador echo "Compiling $INPUT_FILE into $OUTPUT_FILE" diff --git a/src/coolcmp/lexer.py b/src/coolcmp/lexer.py index 9f6a361ca..ce0253d59 100644 --- a/src/coolcmp/lexer.py +++ b/src/coolcmp/lexer.py @@ -161,16 +161,21 @@ def t_STR_escaped(self, t: lex.LexToken): if lookahead == '\n': t.lexer.lineno += 1 - elif lookahead == '0': # TODO: Not detecting null caracter in sting3.cl - line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) - self.errors.append(err.NULL_STR % (line, col + 1)) - @TOKEN(r'[^"\n\\\0]+') + @TOKEN(r'\x00') + def t_STR_null(self, t: lex.LexToken): + t.lexer.skip(1) + line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) + self.errors.append(err.NULL_STR % (line, col)) + + @TOKEN(r'[^"\n\\\x00]+') def t_STR_char(self, t: lex.LexToken): pass def t_STR_error(self, t: lex.LexToken): t.lexer.skip(1) + line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) + self.errors.append(err.EOF_STR % (line, col)) def t_STR_eof(self, t: lex.LexToken): line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) From 9a2e6db6fce0280b971000666f003dda94bd4c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enmanuel=20Verdesia=20Su=C3=A1rez?= Date: Fri, 30 Apr 2021 17:27:38 -0400 Subject: [PATCH 004/161] Reestructured project and parsing tests passed --- src/coolc.py | 20 ++-- src/coolcmp/errors.py | 3 + src/coolcmp/lexer.py | 262 ------------------------------------------ 3 files changed, 15 insertions(+), 270 deletions(-) delete mode 100644 src/coolcmp/lexer.py diff --git a/src/coolc.py b/src/coolc.py index d9f003652..9ec55fc10 100644 --- a/src/coolc.py +++ b/src/coolc.py @@ -1,22 +1,26 @@ """ Main entry point of COOL compiler """ -from coolcmp.lexer import Lexer +from coolcmp.lexing_parsing.lexer import errors as lexer_errors +from coolcmp.lexing_parsing.parser import parser, errors as parser_errors def main(cool_program): - lexer = Lexer(build_lexer=True) - lexer.input(cool_program) + parser.parse(cool_program) - [(t.value, t.type) for t in lexer] # make lexer process the input - errors = lexer.errors + if lexer_errors: + for er in lexer_errors: + print(er) - if errors: - for e in errors: - print(e) + exit(1) + + if parser_errors: + for er in parser_errors: + print(er) exit(1) + if __name__ == '__main__': import sys diff --git a/src/coolcmp/errors.py b/src/coolcmp/errors.py index b8d3dbca1..bc6f51205 100644 --- a/src/coolcmp/errors.py +++ b/src/coolcmp/errors.py @@ -7,3 +7,6 @@ EOF_STR = '(%s,%s) - LexicographicError: EOF in string.' NULL_STR = '(%s,%s) - LexicographicError: String contains null character' EOF_COMM = '(%s,%s) - LexicographicError: EOF in comment.' + +SYN_ERROR = '(%s,%s) - SyntacticError: Syntax error at or near "%s"' +SYN_EOF = '(0, 0) - SyntacticError: ERROR at or near EOF' # empty program diff --git a/src/coolcmp/lexer.py b/src/coolcmp/lexer.py deleted file mode 100644 index ce0253d59..000000000 --- a/src/coolcmp/lexer.py +++ /dev/null @@ -1,262 +0,0 @@ -from ply import lex -from ply.lex import TOKEN - -try: - import errors as err -except ImportError: - import coolcmp.errors as err - - -class Lexer: - """ - Cool Lexer. - """ - def __init__(self, build_lexer=False, debug=False): - self.lexer = None - self.errors = [] - - if build_lexer: - self.lexer = lex.lex(module=self, debug=debug) - - def build(self, **kwargs): - self.lexer = lex.lex(module=self, **kwargs) - - states = ( - ('STR', 'exclusive'), - ('COMM', 'exclusive') - ) - - reserved = { - 'class': 'CLASS', - 'inherits': 'INHERITS', - 'if': 'IF', - 'then': 'THEN', - 'else': 'ELSE', - 'fi': 'FI', - 'while': 'WHILE', - 'loop': 'LOOP', - 'pool': 'POOL', - 'case': 'CASE', - 'of': 'OF', - 'esac': 'ESAC', - 'let': 'LET', - 'new': 'NEW', - 'isvoid': 'ISVOID', - 'not': 'NOT', - 'in': 'IN', - } - - tokens = [ - # Identifiers - 'TYPE', 'ID', - - # Built-in types - 'INT', 'STRING', 'BOOL', - - # Special Notation - 'PLUS', 'MINUS', 'STAR', 'DIV', 'LESS', 'LEQ', 'EQ', 'COMP', - 'COLON', 'SEMI', 'COMMA', 'DOT', 'ASSIGN', 'ARROW', 'AT', - 'OPAR', 'CPAR', 'OCCUR', 'CCUR' - - ] + list(reserved.values()) - - # Tokens defined as strings go after defined in functions. - # This order is not relevant, they are ordered by length (longer first). - t_PLUS = r'\+' - t_MINUS = r'\-' - t_STAR = r'\*' - t_DIV = r'\/' - t_LESS = r'\<' - t_LEQ = r'\<\=' - t_EQ = r'\=' - t_COMP = r'\~' - t_COLON = r'\:' - t_SEMI = r'\;' - t_COMMA = r'\,' - t_DOT = r'\.' - t_ASSIGN = r'\<\-' - t_ARROW = r'\=\>' - t_AT = r'\@' - t_OPAR = r'\(' - t_CPAR = r'\)' - t_OCCUR = r'\{' - t_CCUR = r'\}' - - t_ignore = ' \t\f\r' - t_ignore_comment = r'\-\-[^\n]*' - - # ######################### - # ##### INITIAL state ##### - # ######################### - - # match types - @TOKEN(r'[A-Z]\w*') - def t_TYPE(self, t: lex.LexToken) -> lex.LexToken: - if t.value[0] in ('t', 'f') and t.value.lower() in ('true', 'false'): - t.type = 'BOOL' - else: - t.type = self.reserved.get(t.value.lower(), 'TYPE') - - return t - - # match ids - @TOKEN(r'[a-z]\w*') - def t_ID(self, t: lex.LexToken) -> lex.LexToken: - if t.value[0] in ('t', 'f') and t.value.lower() in ('true', 'false'): - t.type = 'BOOL' - else: - t.type = self.reserved.get(t.value.lower(), 'ID') - - return t - - # match integers - @TOKEN(r'\d+') - def t_INT(self, t: lex.LexToken) -> lex.LexToken: - t.value = int(t.value) - - return t - - # also defined for COMM state, newline tracker - @TOKEN(r'\n+') - def t_INITIAL_COMM_newline(self, t: lex.LexToken): - t.lexer.lineno += len(t.value) - - # in case of error - def t_error(self, t: lex.LexToken): - t.lexer.skip(1) - - line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) - self.errors.append(err.LEX_ERROR % (line, col, t.value[0])) - - # ##################### - # ##### STR state ##### - # ##################### - - t_STR_ignore = '' - - @TOKEN(r'"') - def t_begin_STR(self, t: lex.LexToken): - t.lexer.string_start = t.lexer.lexpos - 1 - t.lexer.push_state('STR') - - @TOKEN(r'"') - def t_STR_end(self, t: lex.LexToken) -> lex.LexToken: - t.value = t.lexer.lexdata[t.lexer.string_start: t.lexer.lexpos] - t.type = 'STRING' - t.lexer.pop_state() - - return t - - @TOKEN(r'\n+') - def t_STR_newline(self, t: lex.LexToken): - line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) - self.errors.append(err.UNT_STR % (line, col)) - - t.lexer.lineno += len(t.value) - t.lexer.pop_state() - - @TOKEN(r'\\(.|\n)') - def t_STR_escaped(self, t: lex.LexToken): - lookahead = t.value[1] - - if lookahead == '\n': - t.lexer.lineno += 1 - - @TOKEN(r'\x00') - def t_STR_null(self, t: lex.LexToken): - t.lexer.skip(1) - line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) - self.errors.append(err.NULL_STR % (line, col)) - - @TOKEN(r'[^"\n\\\x00]+') - def t_STR_char(self, t: lex.LexToken): - pass - - def t_STR_error(self, t: lex.LexToken): - t.lexer.skip(1) - line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) - self.errors.append(err.EOF_STR % (line, col)) - - def t_STR_eof(self, t: lex.LexToken): - line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) - self.errors.append(err.EOF_STR % (line, col)) - - # ###################### - # ##### COMM state ##### - # ###################### - - t_COMM_ignore = '' - - @TOKEN(r'\(\*') - def t_begin_COMM(self, t: lex.LexToken): - t.lexer.comment_start = t.lexer.lexpos - t.lexer.level = 1 - t.lexer.push_state('COMM') - - @TOKEN(r'\(\*') - def t_COMM_new(self, t: lex.LexToken): - t.lexer.level += 1 - - @TOKEN(r'\*\)') - def t_COMM_end(self, t: lex.LexToken): - t.lexer.level -= 1 - - if t.lexer.level == 0: - t.lexer.pop_state() - - def t_COMM_error(self, token): - token.lexer.skip(1) - - def t_COMM_eof(self, t: lex.LexToken): - line, col = t.lexer.lineno, self.find_column(t.lexer.lexdata, t) - self.errors.append(err.EOF_COMM % (line, col)) - - # ##################### - # not lexical functions - # ##################### - - def find_column(self, input_text: str, token: lex.LexToken) -> int: - """ - Used for compute column in case of error. - """ - line_start = input_text.rfind('\n', 0, token.lexpos) + 1 - - return (token.lexpos - line_start) + 1 - - def input(self, cool_source_code): - if self.lexer is None: - raise Exception('You must call first build method.') - - self.lexer.input(cool_source_code) - - def __iter__(self): - for token in self.lexer: - yield token - -if __name__ == '__main__': - import sys - - if len(sys.argv) != 2: - print('Usage: python3 lexer.py program.cl') - exit() - elif not sys.argv[1].endswith('.cl'): - print('COOl source code files must end with .cl extension.') - print('Usage: python3 lexer.py program.cl') - exit() - - cool_program = open(sys.argv[1], encoding='utf8').read() - # lexer = Lexer(build_lexer=True, debug=True) - lexer = Lexer() - lexer.build(debug=False) - lexer.input(cool_program) - - tokens = [(t.value, t.type) for t in lexer] - # print(tokens) - - for i in range(10): - try: - print(lexer.errors[i]) - except IndexError: - break - - # print(len(lexer.errors)) From 52819a0ee890786e4270e74bac4a127517c469a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enmanuel=20Verdesia=20Su=C3=A1rez?= Date: Fri, 30 Apr 2021 17:31:18 -0400 Subject: [PATCH 005/161] Added missing files in previous commit --- src/coolcmp/ast.py | 221 +++++++++++++++ src/coolcmp/lexing_parsing/lexer.py | 220 +++++++++++++++ src/coolcmp/lexing_parsing/parser.py | 369 +++++++++++++++++++++++++ src/coolcmp/lexing_parsing/parsetab.py | 83 ++++++ src/coolcmp/utils.py | 12 + 5 files changed, 905 insertions(+) create mode 100644 src/coolcmp/ast.py create mode 100644 src/coolcmp/lexing_parsing/lexer.py create mode 100644 src/coolcmp/lexing_parsing/parser.py create mode 100644 src/coolcmp/lexing_parsing/parsetab.py create mode 100644 src/coolcmp/utils.py diff --git a/src/coolcmp/ast.py b/src/coolcmp/ast.py new file mode 100644 index 000000000..011670ff4 --- /dev/null +++ b/src/coolcmp/ast.py @@ -0,0 +1,221 @@ +""" +Cool AST. +""" + + +class Node: + def __init__(self): + self.line = 0 + self.col = 0 + + def set_pos(self, line, col): + self.line = line + self.col = col + + +class ProgramNode(Node): + def __init__(self, declarations): + super().__init__() + + self.declarations = declarations + + +class DeclarationNode(Node): + pass + + +class ExpressionNode(Node): + pass + + +class ClassDeclarationNode(DeclarationNode): + def __init__(self, idx, features, parent=None): + super().__init__() + + self.id = idx + self.parent = parent + self.features = features + + +class FuncDeclarationNode(DeclarationNode): + def __init__(self, idx, params, return_type, body): + super().__init__() + + self.id = idx + self.params = params + self.return_type = return_type + self.body = body + + +class AttrDeclarationNode(DeclarationNode): + def __init__(self, idx, typex, expr=None): + super().__init__() + + self.id = idx + self.type = typex + self.expr = expr + + +class LetDeclarationNode(DeclarationNode): + def __init__(self, idx, typex, expr=None): + super().__init__() + + self.id = idx + self.type = typex + self.expr = expr + + +class ParamNode(DeclarationNode): + def __init__(self, idx, typex): + super().__init__() + + self.id = idx + self.type = typex + + +class ParenthesisExpr(ExpressionNode): + def __init__(self, expr): + super().__init__() + + self.expr = expr + + +class BlockNode(ExpressionNode): + def __init__(self, expressions): + super().__init__() + + self.expressions = expressions + + +class LetNode(ExpressionNode): + def __init__(self, declarations, expr): + super().__init__() + + self.declarations = declarations + self.expr = expr + + +class CaseNode(ExpressionNode): + def __init__(self, expr, cases): + super().__init__() + + self.expr = expr + self.cases = cases + + +class AssignNode(ExpressionNode): + def __init__(self, idx, expr): + super().__init__() + + self.id = idx + self.expr = expr + + +class ConditionalNode(ExpressionNode): + def __init__(self, ifx, then, elsex): + super().__init__() + + self.if_expr = ifx + self.then_expr = then + self.else_expr = elsex + + +class WhileNode(ExpressionNode): + def __init__(self, condition, body): + super().__init__() + + self.condition = condition + self.body = body + + +class CallNode(ExpressionNode): + def __init__(self, idx, args, obj=None, typex=None): + super().__init__() + + self.obj = obj + self.id = idx + self.args = args + self.type = typex + + +class AtomicNode(ExpressionNode): + def __init__(self, lex): + super().__init__() + + self.lex = lex + + +class BinaryNode(ExpressionNode): + def __init__(self, left, operation, right): + super().__init__() + + self.left = left + self.operation = operation + self.right = right + + +class UnaryNode(ExpressionNode): + def __init__(self, expr): + super().__init__() + + self.expr = expr + + +class VariableNode(AtomicNode): + pass + + +class InstantiateNode(AtomicNode): + pass + + +class IntegerNode(AtomicNode): + pass + + +class StringNode(AtomicNode): + pass + + +class BooleanNode(AtomicNode): + pass + + +class PlusNode(BinaryNode): + pass + + +class MinusNode(BinaryNode): + pass + + +class StarNode(BinaryNode): + pass + + +class DivNode(BinaryNode): + pass + + +class LessThanNode(BinaryNode): + pass + + +class LessEqualNode(BinaryNode): + pass + + +class EqualNode(BinaryNode): + pass + + +class IsVoidNode(UnaryNode): + pass + + +class NegationNode(UnaryNode): + pass + + +class ComplementNode(UnaryNode): + pass diff --git a/src/coolcmp/lexing_parsing/lexer.py b/src/coolcmp/lexing_parsing/lexer.py new file mode 100644 index 000000000..b4a02dd9e --- /dev/null +++ b/src/coolcmp/lexing_parsing/lexer.py @@ -0,0 +1,220 @@ +from ply import lex +from ply.lex import TOKEN + + +import coolcmp.errors as err +from coolcmp.utils import find_column + +states = ( + ('STR', 'exclusive'), + ('COMM', 'exclusive') +) + +reserved = { + 'class': 'CLASS', + 'inherits': 'INHERITS', + 'if': 'IF', + 'then': 'THEN', + 'else': 'ELSE', + 'fi': 'FI', + 'while': 'WHILE', + 'loop': 'LOOP', + 'pool': 'POOL', + 'case': 'CASE', + 'of': 'OF', + 'esac': 'ESAC', + 'let': 'LET', + 'new': 'NEW', + 'isvoid': 'ISVOID', + 'not': 'NOT', + 'in': 'IN', +} + +tokens = [ + # Identifiers + 'TYPE', 'ID', + + # Built-in types + 'INT', 'STRING', 'BOOL', + + # Special Notation + 'PLUS', 'MINUS', 'STAR', 'DIV', 'LESS', 'LEQ', 'EQ', 'COMP', + 'COLON', 'SEMI', 'COMMA', 'DOT', 'ASSIGN', 'ARROW', 'AT', + 'OPAR', 'CPAR', 'OCUR', 'CCUR' + +] + list(reserved.values()) + +# Tokens defined as strings go after defined in functions. +# This order is not relevant, they are ordered by length (longer first). +t_PLUS = r'\+' +t_MINUS = r'\-' +t_STAR = r'\*' +t_DIV = r'\/' +t_LESS = r'\<' +t_LEQ = r'\<\=' +t_EQ = r'\=' +t_COMP = r'\~' +t_COLON = r'\:' +t_SEMI = r'\;' +t_COMMA = r'\,' +t_DOT = r'\.' +t_ASSIGN = r'\<\-' +t_ARROW = r'\=\>' +t_AT = r'\@' +t_OPAR = r'\(' +t_CPAR = r'\)' +t_OCUR = r'\{' +t_CCUR = r'\}' + +t_ignore = ' \t\f\r' +t_ignore_comment = r'\-\-[^\n]*' + + +# ######################### +# ##### INITIAL state ##### +# ######################### + +# match types +@TOKEN(r'[A-Z]\w*') +def t_TYPE(t: lex.LexToken) -> lex.LexToken: + if t.value[0] in ('t', 'f') and t.value.lower() in ('true', 'false'): + t.type = 'BOOL' + else: + t.type = reserved.get(t.value.lower(), 'TYPE') + + return t + + +# match ids +@TOKEN(r'[a-z]\w*') +def t_ID(t: lex.LexToken) -> lex.LexToken: + if t.value[0] in ('t', 'f') and t.value.lower() in ('true', 'false'): + t.type = 'BOOL' + else: + t.type = reserved.get(t.value.lower(), 'ID') + + return t + + +# match integers +@TOKEN(r'\d+') +def t_INT(t: lex.LexToken) -> lex.LexToken: + t.value = int(t.value) + + return t + + +# also defined for COMM state, newline tracker +@TOKEN(r'\n+') +def t_INITIAL_COMM_newline(t: lex.LexToken): + t.lexer.lineno += len(t.value) + + +# in case of error +def t_error(t: lex.LexToken): + t.lexer.skip(1) + + line, col = t.lexer.lineno, find_column(t.lexer.lexdata, t.lexpos) + errors.append(err.LEX_ERROR % (line, col, t.value[0])) + + +# ##################### +# ##### STR state ##### +# ##################### + +t_STR_ignore = '' + + +@TOKEN(r'"') +def t_begin_STR(t: lex.LexToken): + t.lexer.string_start = t.lexer.lexpos - 1 + t.lexer.push_state('STR') + + +@TOKEN(r'"') +def t_STR_end(t: lex.LexToken) -> lex.LexToken: + t.value = t.lexer.lexdata[t.lexer.string_start: t.lexer.lexpos] + t.type = 'STRING' + t.lexer.pop_state() + + return t + + +@TOKEN(r'\n+') +def t_STR_newline(t: lex.LexToken): + line, col = t.lexer.lineno, find_column(t.lexer.lexdata, t.lexpos) + errors.append(err.UNT_STR % (line, col)) + + t.lexer.lineno += len(t.value) + t.lexer.pop_state() + + +@TOKEN(r'\\(.|\n)') +def t_STR_escaped(t: lex.LexToken): + lookahead = t.value[1] + + if lookahead == '\n': + t.lexer.lineno += 1 + + +@TOKEN(r'\x00') +def t_STR_null(t: lex.LexToken): + t.lexer.skip(1) + line, col = t.lexer.lineno, find_column(t.lexer.lexdata, t.lexpos) + errors.append(err.NULL_STR % (line, col)) + + +@TOKEN(r'[^"\n\\\x00]+') +def t_STR_char(t: lex.LexToken): + pass + + +def t_STR_error(t: lex.LexToken): + t.lexer.skip(1) + line, col = t.lexer.lineno, find_column(t.lexer.lexdata, t.lexpos) + errors.append(err.EOF_STR % (line, col)) + + +def t_STR_eof(t: lex.LexToken): + line, col = t.lexer.lineno, find_column(t.lexer.lexdata, t.lexpos) + errors.append(err.EOF_STR % (line, col)) + + +# ###################### +# ##### COMM state ##### +# ###################### + +t_COMM_ignore = '' + + +@TOKEN(r'\(\*') +def t_begin_COMM(t: lex.LexToken): + t.lexer.comment_start = t.lexer.lexpos + t.lexer.level = 1 + t.lexer.push_state('COMM') + + +@TOKEN(r'\(\*') +def t_COMM_new(t: lex.LexToken): + t.lexer.level += 1 + + +@TOKEN(r'\*\)') +def t_COMM_end(t: lex.LexToken): + t.lexer.level -= 1 + + if t.lexer.level == 0: + t.lexer.pop_state() + + +def t_COMM_error(token): + token.lexer.skip(1) + + +def t_COMM_eof(t: lex.LexToken): + line, col = t.lexer.lineno, find_column(t.lexer.lexdata, t.lexpos) + errors.append(err.EOF_COMM % (line, col)) + + +errors = [] +lexer = lex.lex() diff --git a/src/coolcmp/lexing_parsing/parser.py b/src/coolcmp/lexing_parsing/parser.py new file mode 100644 index 000000000..a485ebda9 --- /dev/null +++ b/src/coolcmp/lexing_parsing/parser.py @@ -0,0 +1,369 @@ +import ply.yacc as yacc + +from coolcmp import ast +from coolcmp import errors as err +from coolcmp.lexing_parsing import lexer +from coolcmp.utils import find_column + + +tokens = lexer.tokens + +precedence = ( + ('right', 'ASSIGN'), + ('right', 'NOT'), + ('nonassoc', 'LEQ', 'LESS', 'EQ'), + ('left', 'PLUS', 'MINUS'), + ('left', 'STAR', 'DIV'), + ('right', 'ISVOID'), + ('right', 'COMP'), + ('left', 'AT'), + ('left', 'DOT') +) + + +def p_program(p): + """ + program : class_list + """ + p[0] = ast.ProgramNode(p[1]) + + +def p_class_list(p): + """ + class_list : class_def SEMI class_list + | class_def SEMI + """ + if len(p) == 4: + p[0] = [p[1]] + p[3] + else: + p[0] = [p[1]] + + +def p_class_def(p): + """ + class_def : CLASS TYPE INHERITS TYPE OCUR feature_list CCUR + | CLASS TYPE OCUR feature_list CCUR + """ + if len(p) == 8: + p[0] = ast.ClassDeclarationNode(p[2], p[6], p[4]) + else: + p[0] = ast.ClassDeclarationNode(p[2], p[4]) + + p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) + + +def p_feature_list(p): + """ + feature_list : attr_def SEMI feature_list + | func_def SEMI feature_list + | empty + """ + if len(p) == 3: + p[0] = [p[1]] + p[3] + else: + p[0] = [] + + +def p_attr_def(p): + """ + attr_def : ID COLON TYPE ASSIGN expr + | ID COLON TYPE + """ + if len(p) == 6: + p[0] = ast.AttrDeclarationNode(p[1], p[3], p[5]) + else: + p[0] = ast.AttrDeclarationNode(p[1], p[3]) + + p[0].set_pos(p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) + + +def p_func_def(p): + """ + func_def : ID OPAR param_list CPAR COLON TYPE OCUR expr CCUR + | ID OPAR CPAR COLON TYPE OCUR expr CCUR + """ + if len(p) == 10: + p[0] = ast.FuncDeclarationNode(p[1], p[3], p[6], p[8]) + p[0].set_pos(p.lineno(6), find_column(p.lexer.lexdata, p.lexpos(6))) + else: + p[0] = ast.FuncDeclarationNode(p[1], [], p[5], p[7]) + p[0].set_pos(p.lineno(5), find_column(p.lexer.lexdata, p.lexpos(5))) + + +def p_param_list(p): + """ + param_list : ID COLON TYPE COMMA param_list + | ID COLON TYPE + """ + if len(p) == 6: + p[0] = [ast.ParamNode(p[1], p[3])] + p[5] + else: + p[0] = [ast.ParamNode(p[1], p[3])] + + +# expr productions + +def p_expr_assign(p): + """ + expr : ID ASSIGN expr + """ + p[0] = ast.AssignNode(p[1], p[3]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_expr_list(p): + """ + expr_list : expr COMMA expr_list_not_empty + | expr + """ + if len(p) == 4: + p[0] = [p[1]] + p[3] + else: + p[0] = [p[1]] + + +def p_expr_list_empty(p): + """ + expr_list : empty + """ + p[0] = [] + + +def p_expr_list_not_empty(p): + """ + expr_list_not_empty : expr COMMA expr_list_not_empty + | expr + """ + if len(p) == 4: + p[0] = [p[1]] + p[3] + else: + p[0] = [p[1]] + + +def p_expr_func_call(p): + """ + expr : ID OPAR expr_list CPAR + | expr DOT ID OPAR expr_list CPAR + | expr AT TYPE DOT ID OPAR expr_list CPAR + """ + if len(p) == 5: + p[0] = ast.CallNode(p[1], p[3]) + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + elif len(p) == 7: + p[0] = ast.CallNode(p[3], p[5], p[1]) + p[0].set_pos(p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) + else: + p[0] = ast.CallNode(p[5], p[7], p[1], p[3]) + p[0].set_pos(p.lineno(5), find_column(p.lexer.lexdata, p.lexpos(5))) + + +def p_expr_if(p): + """ + expr : IF expr THEN expr ELSE expr FI + """ + p[0] = ast.ConditionalNode(p[2], p[4], p[6]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_expr_while(p): + """ + expr : WHILE expr LOOP expr POOL + """ + p[0] = ast.WhileNode(p[2], p[4]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_block(p): + """ + block : expr SEMI block + | expr SEMI + """ + if len(p) == 4: + p[0] = [p[1]] + p[3] + else: + p[0] = [p[1]] + + +def p_expr_block(p): + """ + expr : OCUR block CCUR + """ + p[0] = ast.BlockNode(p[2]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_decl_list(p): + """ + decl_list : ID COLON TYPE ASSIGN expr COMMA decl_list + | ID COLON TYPE COMMA decl_list + | ID COLON TYPE ASSIGN expr + | ID COLON TYPE + """ + if len(p) == 8: + p[0] = [ast.LetDeclarationNode(p[1], p[3], p[5])] + p[7] + elif len(p) == 6: + if p[4] == ',': + p[0] = [ast.LetDeclarationNode(p[1], p[3])] + p[5] + else: + p[0] = [ast.LetDeclarationNode(p[1], p[3], p[5])] + else: + p[0] = [ast.LetDeclarationNode(p[1], p[3])] + + +def p_expr_let(p): + """ + expr : LET decl_list IN expr + """ + p[0] = ast.LetNode(p[2], p[4]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_case_list(p): + """ + case_list : ID COLON TYPE ARROW expr SEMI case_list + | ID COLON TYPE ARROW expr SEMI + """ + if len(p) == 8: + p[0] = [(p[1], p[3], p[5])] + p[7] # maybe here is better to use a Node + else: + p[0] = [(p[1], p[3], p[5])] + + +def p_expr_case(p): + """ + expr : CASE expr OF case_list ESAC + """ + p[0] = ast.CaseNode(p[2], p[4]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_expr_new(p): + """ + expr : NEW TYPE + """ + p[0] = ast.InstantiateNode(p[2]) + + p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) + + +def p_expr_isvoid(p): + """ + expr : ISVOID expr + """ + p[0] = ast.IsVoidNode(p[2]) + + p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) + + +def p_expr_binary_op(p): + """ + expr : expr PLUS expr + | expr MINUS expr + | expr STAR expr + | expr DIV expr + | expr LESS expr + | expr LEQ expr + | expr EQ expr + """ + if p[2] == '+': + p[0] = ast.PlusNode(p[1], p[2], p[3]) + elif p[2] == '-': + p[0] = ast.MinusNode(p[1], p[2], p[3]) + elif p[2] == '/': + p[0] = ast.DivNode(p[1], p[2], p[3]) + elif p[2] == '<': + p[0] = ast.LessThanNode(p[1], p[2], p[3]) + elif p[2] == '<=': + p[0] = ast.LessEqualNode(p[1], p[2], p[3]) + else: + p[0] = ast.EqualNode(p[1], p[2], p[3]) + + p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) + + +def p_expr_comp(p): + """ + expr : COMP expr + """ + p[0] = p[1] + + +def p_expr_not(p): + """ + expr : NOT expr + """ + p[0] = ast.NegationNode(p[2]) + + p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) + + +def p_expr_pars(p): + """ + expr : OPAR expr CPAR + """ + p[0] = p[2] + + +def p_expr_id(p): + """ + expr : ID + """ + p[0] = ast.VariableNode(p[1]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_expr_int(p): + """ + expr : INT + """ + p[0] = ast.IntegerNode(p[1]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_expr_string(p): + """ + expr : STRING + """ + p[0] = ast.StringNode(p[1]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +def p_expr_bool(p): + """ + expr : BOOL + """ + p[0] = ast.BooleanNode(p[1]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + + +# empty productions + +def p_empty(p): + """ + empty : + """ + pass + + +# error handling + +def p_error(p): + if p: + line, col = p.lineno, find_column(p.lexer.lexdata, p.lexpos) + errors.append(err.SYN_ERROR % (line, col, p.value)) + else: + errors.append(err.SYN_EOF) + + +errors = [] +parser = yacc.yacc() diff --git a/src/coolcmp/lexing_parsing/parsetab.py b/src/coolcmp/lexing_parsing/parsetab.py new file mode 100644 index 000000000..d8cc43351 --- /dev/null +++ b/src/coolcmp/lexing_parsing/parsetab.py @@ -0,0 +1,83 @@ + +# parsetab.py +# This file is automatically generated. Do not edit. +# pylint: disable=W,C,R +_tabversion = '3.10' + +_lr_method = 'LALR' + +_lr_signature = 'rightASSIGNrightNOTnonassocLEQLESSEQleftPLUSMINUSleftSTARDIVrightISVOIDrightCOMPleftATleftDOTARROW ASSIGN AT BOOL CASE CCUR CLASS COLON COMMA COMP CPAR DIV DOT ELSE EQ ESAC FI ID IF IN INHERITS INT ISVOID LEQ LESS LET LOOP MINUS NEW NOT OCUR OF OPAR PLUS POOL SEMI STAR STRING THEN TYPE WHILE\n program : class_list\n \n class_list : class_def SEMI class_list\n | class_def SEMI\n \n class_def : CLASS TYPE INHERITS TYPE OCUR feature_list CCUR\n | CLASS TYPE OCUR feature_list CCUR\n \n feature_list : attr_def SEMI feature_list\n | func_def SEMI feature_list\n | empty\n \n attr_def : ID COLON TYPE ASSIGN expr\n | ID COLON TYPE\n \n func_def : ID OPAR param_list CPAR COLON TYPE OCUR expr CCUR\n | ID OPAR CPAR COLON TYPE OCUR expr CCUR\n \n param_list : ID COLON TYPE COMMA param_list\n | ID COLON TYPE\n \n expr : ID ASSIGN expr\n \n expr_list : expr COMMA expr_list_not_empty\n | expr\n \n expr_list : empty\n \n expr_list_not_empty : expr COMMA expr_list_not_empty\n | expr\n \n expr : ID OPAR expr_list CPAR\n | expr DOT ID OPAR expr_list CPAR\n | expr AT TYPE DOT ID OPAR expr_list CPAR\n \n expr : IF expr THEN expr ELSE expr FI\n \n expr : WHILE expr LOOP expr POOL\n \n block : expr SEMI block\n | expr SEMI\n \n expr : OCUR block CCUR\n \n decl_list : ID COLON TYPE ASSIGN expr COMMA decl_list\n | ID COLON TYPE COMMA decl_list\n | ID COLON TYPE ASSIGN expr\n | ID COLON TYPE\n \n expr : LET decl_list IN expr\n \n case_list : ID COLON TYPE ARROW expr SEMI case_list\n | ID COLON TYPE ARROW expr SEMI\n \n expr : CASE expr OF case_list ESAC\n \n expr : NEW TYPE\n \n expr : ISVOID expr\n \n expr : expr PLUS expr\n | expr MINUS expr\n | expr STAR expr\n | expr DIV expr\n | expr LESS expr\n | expr LEQ expr\n | expr EQ expr\n \n expr : COMP expr\n \n expr : NOT expr\n \n expr : OPAR expr CPAR\n \n expr : ID\n \n expr : INT\n \n expr : STRING\n \n expr : BOOL\n \n empty :\n ' + +_lr_action_items = {'CLASS':([0,5,],[4,4,]),'$end':([1,2,5,7,],[0,-1,-3,-2,]),'SEMI':([3,12,13,17,25,29,34,35,46,47,48,67,71,72,73,74,78,84,85,86,87,88,89,90,91,94,102,109,114,120,123,125,127,135,138,140,],[5,18,19,-5,-10,-4,-49,-9,-50,-51,-52,95,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,-33,-12,-25,-36,-11,-22,-24,-23,141,]),'TYPE':([4,8,20,31,33,42,50,55,97,124,],[6,10,25,49,51,71,76,83,110,132,]),'INHERITS':([6,],[8,]),'OCUR':([6,10,30,36,37,38,39,41,43,44,45,51,52,53,56,57,58,59,60,61,62,76,77,92,93,95,96,100,103,104,119,121,126,128,137,],[9,16,39,39,39,39,39,39,39,39,39,77,39,39,39,39,39,39,39,39,39,100,39,39,39,39,39,39,39,39,39,39,39,39,39,]),'ID':([9,16,18,19,21,30,36,37,38,39,40,41,43,44,45,52,53,54,56,57,58,59,60,61,62,75,77,92,93,95,96,98,100,103,104,105,119,121,122,126,128,136,137,141,],[15,15,15,15,26,34,34,34,34,34,69,34,34,34,34,34,34,82,34,34,34,34,34,34,34,26,34,34,34,34,34,112,34,34,34,118,34,34,69,34,34,69,34,112,]),'CCUR':([9,11,14,16,18,19,22,23,24,34,46,47,48,66,71,72,73,74,78,84,85,86,87,88,89,90,91,94,95,101,102,108,109,113,120,123,127,135,138,],[-53,17,-8,-53,-53,-53,29,-6,-7,-49,-50,-51,-52,94,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-27,114,-21,-26,-33,125,-25,-36,-22,-24,-23,]),'COLON':([15,26,28,32,69,112,],[20,31,33,50,97,124,]),'OPAR':([15,30,34,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,82,92,93,95,96,100,103,104,118,119,121,126,128,137,],[21,36,53,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,104,36,36,36,36,36,36,36,128,36,36,36,36,36,]),'CPAR':([21,27,34,46,47,48,49,53,63,71,72,73,74,78,79,80,81,84,85,86,87,88,89,90,91,94,99,102,104,109,115,116,117,120,123,127,128,133,134,135,138,],[28,32,-49,-50,-51,-52,-14,-53,91,-37,-38,-46,-47,-15,102,-17,-18,-39,-40,-41,-42,-43,-44,-45,-48,-28,-13,-21,-53,-33,-20,-16,127,-25,-36,-22,-53,-19,138,-24,-23,]),'ASSIGN':([25,34,110,],[30,52,121,]),'IF':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,]),'WHILE':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,]),'LET':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,]),'CASE':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,]),'NEW':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,]),'ISVOID':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,]),'COMP':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,]),'NOT':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,]),'INT':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,]),'STRING':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,]),'BOOL':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,]),'DOT':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,83,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,54,-50,-51,-52,54,54,54,54,54,-37,54,54,54,54,54,105,54,54,54,54,54,54,54,-48,-28,54,-21,54,54,54,54,54,-25,-36,-22,54,54,-24,-23,54,]),'AT':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,55,-50,-51,-52,55,55,55,55,55,-37,55,55,55,55,55,55,55,55,55,55,55,55,-48,-28,55,-21,55,55,55,55,55,-25,-36,-22,55,55,-24,-23,55,]),'PLUS':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,56,-50,-51,-52,56,56,56,56,56,-37,-38,-46,56,56,56,-39,-40,-41,-42,56,56,56,-48,-28,56,-21,56,56,56,56,56,-25,-36,-22,56,56,-24,-23,56,]),'MINUS':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,57,-50,-51,-52,57,57,57,57,57,-37,-38,-46,57,57,57,-39,-40,-41,-42,57,57,57,-48,-28,57,-21,57,57,57,57,57,-25,-36,-22,57,57,-24,-23,57,]),'STAR':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,58,-50,-51,-52,58,58,58,58,58,-37,-38,-46,58,58,58,58,58,-41,-42,58,58,58,-48,-28,58,-21,58,58,58,58,58,-25,-36,-22,58,58,-24,-23,58,]),'DIV':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,59,-50,-51,-52,59,59,59,59,59,-37,-38,-46,59,59,59,59,59,-41,-42,59,59,59,-48,-28,59,-21,59,59,59,59,59,-25,-36,-22,59,59,-24,-23,59,]),'LESS':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,60,-50,-51,-52,60,60,60,60,60,-37,-38,-46,60,60,60,-39,-40,-41,-42,None,None,None,-48,-28,60,-21,60,60,60,60,60,-25,-36,-22,60,60,-24,-23,60,]),'LEQ':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,61,-50,-51,-52,61,61,61,61,61,-37,-38,-46,61,61,61,-39,-40,-41,-42,None,None,None,-48,-28,61,-21,61,61,61,61,61,-25,-36,-22,61,61,-24,-23,61,]),'EQ':([34,35,46,47,48,63,64,65,67,70,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,101,102,106,107,109,113,115,120,123,127,129,130,135,138,140,],[-49,62,-50,-51,-52,62,62,62,62,62,-37,-38,-46,62,62,62,-39,-40,-41,-42,None,None,None,-48,-28,62,-21,62,62,62,62,62,-25,-36,-22,62,62,-24,-23,62,]),'THEN':([34,46,47,48,64,71,72,73,74,78,84,85,86,87,88,89,90,91,94,102,109,120,123,127,135,138,],[-49,-50,-51,-52,92,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,-33,-25,-36,-22,-24,-23,]),'LOOP':([34,46,47,48,65,71,72,73,74,78,84,85,86,87,88,89,90,91,94,102,109,120,123,127,135,138,],[-49,-50,-51,-52,93,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,-33,-25,-36,-22,-24,-23,]),'OF':([34,46,47,48,70,71,72,73,74,78,84,85,86,87,88,89,90,91,94,102,109,120,123,127,135,138,],[-49,-50,-51,-52,98,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,-33,-25,-36,-22,-24,-23,]),'COMMA':([34,46,47,48,49,71,72,73,74,78,80,84,85,86,87,88,89,90,91,94,102,109,110,115,120,123,127,130,135,138,],[-49,-50,-51,-52,75,-37,-38,-46,-47,-15,103,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,-33,122,126,-25,-36,-22,136,-24,-23,]),'ELSE':([34,46,47,48,71,72,73,74,78,84,85,86,87,88,89,90,91,94,102,106,109,120,123,127,135,138,],[-49,-50,-51,-52,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,119,-33,-25,-36,-22,-24,-23,]),'POOL':([34,46,47,48,71,72,73,74,78,84,85,86,87,88,89,90,91,94,102,107,109,120,123,127,135,138,],[-49,-50,-51,-52,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,120,-33,-25,-36,-22,-24,-23,]),'FI':([34,46,47,48,71,72,73,74,78,84,85,86,87,88,89,90,91,94,102,109,120,123,127,129,135,138,],[-49,-50,-51,-52,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,-33,-25,-36,-22,135,-24,-23,]),'IN':([34,46,47,48,68,71,72,73,74,78,84,85,86,87,88,89,90,91,94,102,109,110,120,123,127,130,131,135,138,139,],[-49,-50,-51,-52,96,-37,-38,-46,-47,-15,-39,-40,-41,-42,-43,-44,-45,-48,-28,-21,-33,-32,-25,-36,-22,-31,-30,-24,-23,-29,]),'ESAC':([111,141,142,],[123,-35,-34,]),'ARROW':([132,],[137,]),} + +_lr_action = {} +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = {} + _lr_action[_x][_k] = _y +del _lr_action_items + +_lr_goto_items = {'program':([0,],[1,]),'class_list':([0,5,],[2,7,]),'class_def':([0,5,],[3,3,]),'feature_list':([9,16,18,19,],[11,22,23,24,]),'attr_def':([9,16,18,19,],[12,12,12,12,]),'func_def':([9,16,18,19,],[13,13,13,13,]),'empty':([9,16,18,19,53,104,128,],[14,14,14,14,81,81,81,]),'param_list':([21,75,],[27,99,]),'expr':([30,36,37,38,39,41,43,44,45,52,53,56,57,58,59,60,61,62,77,92,93,95,96,100,103,104,119,121,126,128,137,],[35,63,64,65,67,70,72,73,74,78,80,84,85,86,87,88,89,90,101,106,107,67,109,113,115,80,129,130,115,80,140,]),'block':([39,95,],[66,108,]),'decl_list':([40,122,136,],[68,131,139,]),'expr_list':([53,104,128,],[79,117,134,]),'case_list':([98,141,],[111,142,]),'expr_list_not_empty':([103,126,],[116,133,]),} + +_lr_goto = {} +for _k, _v in _lr_goto_items.items(): + for _x, _y in zip(_v[0], _v[1]): + if not _x in _lr_goto: _lr_goto[_x] = {} + _lr_goto[_x][_k] = _y +del _lr_goto_items +_lr_productions = [ + ("S' -> program","S'",1,None,None,None), + ('program -> class_list','program',1,'p_program','parser.py',33), + ('class_list -> class_def SEMI class_list','class_list',3,'p_class_list','parser.py',40), + ('class_list -> class_def SEMI','class_list',2,'p_class_list','parser.py',41), + ('class_def -> CLASS TYPE INHERITS TYPE OCUR feature_list CCUR','class_def',7,'p_class_def','parser.py',51), + ('class_def -> CLASS TYPE OCUR feature_list CCUR','class_def',5,'p_class_def','parser.py',52), + ('feature_list -> attr_def SEMI feature_list','feature_list',3,'p_feature_list','parser.py',64), + ('feature_list -> func_def SEMI feature_list','feature_list',3,'p_feature_list','parser.py',65), + ('feature_list -> empty','feature_list',1,'p_feature_list','parser.py',66), + ('attr_def -> ID COLON TYPE ASSIGN expr','attr_def',5,'p_attr_def','parser.py',76), + ('attr_def -> ID COLON TYPE','attr_def',3,'p_attr_def','parser.py',77), + ('func_def -> ID OPAR param_list CPAR COLON TYPE OCUR expr CCUR','func_def',9,'p_func_def','parser.py',89), + ('func_def -> ID OPAR CPAR COLON TYPE OCUR expr CCUR','func_def',8,'p_func_def','parser.py',90), + ('param_list -> ID COLON TYPE COMMA param_list','param_list',5,'p_param_list','parser.py',102), + ('param_list -> ID COLON TYPE','param_list',3,'p_param_list','parser.py',103), + ('expr -> ID ASSIGN expr','expr',3,'p_expr_assign','parser.py',115), + ('expr_list -> expr COMMA expr_list_not_empty','expr_list',3,'p_expr_list','parser.py',124), + ('expr_list -> expr','expr_list',1,'p_expr_list','parser.py',125), + ('expr_list -> empty','expr_list',1,'p_expr_list_empty','parser.py',135), + ('expr_list_not_empty -> expr COMMA expr_list_not_empty','expr_list_not_empty',3,'p_expr_list_not_empty','parser.py',142), + ('expr_list_not_empty -> expr','expr_list_not_empty',1,'p_expr_list_not_empty','parser.py',143), + ('expr -> ID OPAR expr_list CPAR','expr',4,'p_expr_func_call','parser.py',153), + ('expr -> expr DOT ID OPAR expr_list CPAR','expr',6,'p_expr_func_call','parser.py',154), + ('expr -> expr AT TYPE DOT ID OPAR expr_list CPAR','expr',8,'p_expr_func_call','parser.py',155), + ('expr -> IF expr THEN expr ELSE expr FI','expr',7,'p_expr_if','parser.py',170), + ('expr -> WHILE expr LOOP expr POOL','expr',5,'p_expr_while','parser.py',179), + ('block -> expr SEMI block','block',3,'p_block','parser.py',188), + ('block -> expr SEMI','block',2,'p_block','parser.py',189), + ('expr -> OCUR block CCUR','expr',3,'p_expr_block','parser.py',199), + ('decl_list -> ID COLON TYPE ASSIGN expr COMMA decl_list','decl_list',7,'p_decl_list','parser.py',208), + ('decl_list -> ID COLON TYPE COMMA decl_list','decl_list',5,'p_decl_list','parser.py',209), + ('decl_list -> ID COLON TYPE ASSIGN expr','decl_list',5,'p_decl_list','parser.py',210), + ('decl_list -> ID COLON TYPE','decl_list',3,'p_decl_list','parser.py',211), + ('expr -> LET decl_list IN expr','expr',4,'p_expr_let','parser.py',226), + ('case_list -> ID COLON TYPE ARROW expr SEMI case_list','case_list',7,'p_case_list','parser.py',235), + ('case_list -> ID COLON TYPE ARROW expr SEMI','case_list',6,'p_case_list','parser.py',236), + ('expr -> CASE expr OF case_list ESAC','expr',5,'p_expr_case','parser.py',246), + ('expr -> NEW TYPE','expr',2,'p_expr_new','parser.py',256), + ('expr -> ISVOID expr','expr',2,'p_expr_isvoid','parser.py',265), + ('expr -> expr PLUS expr','expr',3,'p_expr_binary_op','parser.py',274), + ('expr -> expr MINUS expr','expr',3,'p_expr_binary_op','parser.py',275), + ('expr -> expr STAR expr','expr',3,'p_expr_binary_op','parser.py',276), + ('expr -> expr DIV expr','expr',3,'p_expr_binary_op','parser.py',277), + ('expr -> expr LESS expr','expr',3,'p_expr_binary_op','parser.py',278), + ('expr -> expr LEQ expr','expr',3,'p_expr_binary_op','parser.py',279), + ('expr -> expr EQ expr','expr',3,'p_expr_binary_op','parser.py',280), + ('expr -> COMP expr','expr',2,'p_expr_comp','parser.py',300), + ('expr -> NOT expr','expr',2,'p_expr_not','parser.py',307), + ('expr -> OPAR expr CPAR','expr',3,'p_expr_pars','parser.py',316), + ('expr -> ID','expr',1,'p_expr_id','parser.py',323), + ('expr -> INT','expr',1,'p_expr_int','parser.py',332), + ('expr -> STRING','expr',1,'p_expr_string','parser.py',341), + ('expr -> BOOL','expr',1,'p_expr_bool','parser.py',350), + ('empty -> ','empty',0,'p_empty','parser.py',361), +] diff --git a/src/coolcmp/utils.py b/src/coolcmp/utils.py new file mode 100644 index 000000000..14920da35 --- /dev/null +++ b/src/coolcmp/utils.py @@ -0,0 +1,12 @@ +""" + +""" + + +def find_column(input_text: str, token_lexpos: int) -> int: + """ + Used for compute column in case of error. + """ + line_start = input_text.rfind('\n', 0, token_lexpos) + 1 + + return (token_lexpos - line_start) + 1 From f214396b2841cee259dc1d4dd913ac732fd7105b Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Thu, 17 Feb 2022 09:12:21 -0500 Subject: [PATCH 006/161] Fixed test error file for class1.cl --- tests/semantic/class1_error.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/semantic/class1_error.txt b/tests/semantic/class1_error.txt index 19c507672..86fb261ec 100644 --- a/tests/semantic/class1_error.txt +++ b/tests/semantic/class1_error.txt @@ -1,2 +1,2 @@ -(7, 5) - SemanticError: Classes may not be redefined +(7, 7) - SemanticError: Classes may not be redefined From 42bf096950ccee49a047ceab1c67e4b175084eeb Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Thu, 17 Feb 2022 09:22:58 -0500 Subject: [PATCH 007/161] Passed semantic tests --- .gitignore | 2 + src/coolc.py | 35 ++- src/coolcmp/ast.py | 83 ++++-- src/coolcmp/errors.py | 37 ++- src/coolcmp/formatter.py | 137 +++++++++ src/coolcmp/lexing_parsing/parser.py | 78 +++-- src/coolcmp/lexing_parsing/parsetab.py | 88 +++--- src/coolcmp/semantic.py | 266 +++++++++++++++++ src/coolcmp/semantics/__init__.py | 20 ++ src/coolcmp/semantics/builder.py | 89 ++++++ src/coolcmp/semantics/checker.py | 380 +++++++++++++++++++++++++ src/coolcmp/semantics/collector.py | 58 ++++ src/coolcmp/semantics/consistence.py | 34 +++ src/coolcmp/utils.py | 5 +- src/coolcmp/visitor.py | 80 ++++++ src/makefile | 2 + 16 files changed, 1282 insertions(+), 112 deletions(-) create mode 100644 src/coolcmp/formatter.py create mode 100644 src/coolcmp/semantic.py create mode 100644 src/coolcmp/semantics/__init__.py create mode 100644 src/coolcmp/semantics/builder.py create mode 100644 src/coolcmp/semantics/checker.py create mode 100644 src/coolcmp/semantics/collector.py create mode 100644 src/coolcmp/semantics/consistence.py create mode 100644 src/coolcmp/visitor.py diff --git a/.gitignore b/.gitignore index 4acafde18..ab0682b4b 100644 --- a/.gitignore +++ b/.gitignore @@ -408,3 +408,5 @@ dmypy.json # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) +env/ +.idea/ \ No newline at end of file diff --git a/src/coolc.py b/src/coolc.py index 9ec55fc10..7901d6059 100644 --- a/src/coolc.py +++ b/src/coolc.py @@ -3,29 +3,39 @@ """ from coolcmp.lexing_parsing.lexer import errors as lexer_errors from coolcmp.lexing_parsing.parser import parser, errors as parser_errors +from coolcmp.semantics import check_semantics +from coolcmp.formatter import FormatVisitor -def main(cool_program): - parser.parse(cool_program) +def main(cool_code: str, verbose: bool = False): + ast = parser.parse(cool_code) - if lexer_errors: - for er in lexer_errors: - print(er) + if verbose: + formatter = FormatVisitor() + print(formatter.visit(ast)) + if lexer_errors: + for error in lexer_errors: + print(error) exit(1) if parser_errors: - for er in parser_errors: - print(er) + for error in parser_errors: + print(error) + exit(1) + sem_errors = check_semantics(ast) + if sem_errors: + for error in sem_errors: + print(error) exit(1) if __name__ == '__main__': import sys - if len(sys.argv) != 2: - print('Usage: python3 coolc.py program.cl') + if len(sys.argv) < 2: + print('Usage: python3 coolc.py program.cl [-v]') exit(1) elif not sys.argv[1].endswith('.cl'): print('COOl source code files must end with .cl extension.') @@ -33,5 +43,8 @@ def main(cool_program): exit(1) cool_program = open(sys.argv[1], encoding='utf8').read() - - main(cool_program) + try: + verbose = sys.argv[2] == '-v' + except IndexError: + verbose = False + main(cool_program, verbose) diff --git a/src/coolcmp/ast.py b/src/coolcmp/ast.py index 011670ff4..edc0e69dd 100644 --- a/src/coolcmp/ast.py +++ b/src/coolcmp/ast.py @@ -1,6 +1,7 @@ """ Cool AST. """ +from __future__ import annotations class Node: @@ -8,20 +9,24 @@ def __init__(self): self.line = 0 self.col = 0 - def set_pos(self, line, col): + def set_pos(self, line, col) -> None: self.line = line self.col = col + @property + def pos(self) -> tuple[int, int]: + return self.line, self.col + class ProgramNode(Node): - def __init__(self, declarations): + def __init__(self, declarations: list[DeclarationNode]): super().__init__() self.declarations = declarations class DeclarationNode(Node): - pass + id: str class ExpressionNode(Node): @@ -29,16 +34,31 @@ class ExpressionNode(Node): class ClassDeclarationNode(DeclarationNode): - def __init__(self, idx, features, parent=None): + def __init__(self, + idx: str, + features: list[FuncDeclarationNode | AttrDeclarationNode], + parent: str = None): super().__init__() self.id = idx self.parent = parent self.features = features + self.parent_pos = (-1, -1) + + +class ParamNode(DeclarationNode): + def __init__(self, idx: str, typex: str): + super().__init__() + + self.id = idx + self.type = typex + + self.type_pos = (-1, -1) + class FuncDeclarationNode(DeclarationNode): - def __init__(self, idx, params, return_type, body): + def __init__(self, idx: str, params: list[ParamNode], return_type: str, body): super().__init__() self.id = idx @@ -46,57 +66,68 @@ def __init__(self, idx, params, return_type, body): self.return_type = return_type self.body = body + self.type_pos = (-1, -1) + class AttrDeclarationNode(DeclarationNode): - def __init__(self, idx, typex, expr=None): + def __init__(self, idx: str, typex: str, expr: ExpressionNode = None): super().__init__() self.id = idx self.type = typex self.expr = expr + self.type_pos = (-1, -1) + self.expr_pos = (-1, -1) + class LetDeclarationNode(DeclarationNode): - def __init__(self, idx, typex, expr=None): + def __init__(self, idx: str, typex: str, expr: ExpressionNode = None): super().__init__() self.id = idx self.type = typex self.expr = expr - -class ParamNode(DeclarationNode): - def __init__(self, idx, typex): - super().__init__() - - self.id = idx - self.type = typex + self.type_pos = (-1, -1) + self.expr_pos = (-1, -1) class ParenthesisExpr(ExpressionNode): - def __init__(self, expr): + def __init__(self, expr: ExpressionNode): super().__init__() self.expr = expr class BlockNode(ExpressionNode): - def __init__(self, expressions): + def __init__(self, expressions: list[ExpressionNode]): super().__init__() self.expressions = expressions class LetNode(ExpressionNode): - def __init__(self, declarations, expr): + def __init__(self, declarations: list[LetDeclarationNode], expr: ExpressionNode): super().__init__() self.declarations = declarations self.expr = expr +class CaseBranchNode(DeclarationNode): + def __init__(self, id_: str, type_: str, expr: ExpressionNode): + super().__init__() + + self.id = id_ + self.type = type_ + self.expr = expr + + self.type_pos = (-1, -1) + + class CaseNode(ExpressionNode): - def __init__(self, expr, cases): + def __init__(self, expr: ExpressionNode, cases: list[CaseBranchNode]): super().__init__() self.expr = expr @@ -104,7 +135,7 @@ def __init__(self, expr, cases): class AssignNode(ExpressionNode): - def __init__(self, idx, expr): + def __init__(self, idx: str, expr: ExpressionNode): super().__init__() self.id = idx @@ -112,7 +143,7 @@ def __init__(self, idx, expr): class ConditionalNode(ExpressionNode): - def __init__(self, ifx, then, elsex): + def __init__(self, ifx: ExpressionNode, then: ExpressionNode, elsex: ExpressionNode): super().__init__() self.if_expr = ifx @@ -121,7 +152,7 @@ def __init__(self, ifx, then, elsex): class WhileNode(ExpressionNode): - def __init__(self, condition, body): + def __init__(self, condition: ExpressionNode, body: ExpressionNode): super().__init__() self.condition = condition @@ -129,7 +160,7 @@ def __init__(self, condition, body): class CallNode(ExpressionNode): - def __init__(self, idx, args, obj=None, typex=None): + def __init__(self, idx: str, args, obj: ExpressionNode = None, typex: str = None): super().__init__() self.obj = obj @@ -137,16 +168,18 @@ def __init__(self, idx, args, obj=None, typex=None): self.args = args self.type = typex + self.parent_pos = (-1, -1) + class AtomicNode(ExpressionNode): - def __init__(self, lex): + def __init__(self, lex: str): super().__init__() self.lex = lex class BinaryNode(ExpressionNode): - def __init__(self, left, operation, right): + def __init__(self, left: ExpressionNode, operation: str, right: ExpressionNode): super().__init__() self.left = left @@ -155,7 +188,7 @@ def __init__(self, left, operation, right): class UnaryNode(ExpressionNode): - def __init__(self, expr): + def __init__(self, expr: ExpressionNode): super().__init__() self.expr = expr diff --git a/src/coolcmp/errors.py b/src/coolcmp/errors.py index bc6f51205..6368bd7e3 100644 --- a/src/coolcmp/errors.py +++ b/src/coolcmp/errors.py @@ -2,11 +2,34 @@ Compilation errors. """ -LEX_ERROR = '(%s,%s) - LexicographicError: Unexpected symbol "%s".' -UNT_STR = '(%s,%s) - LexicographicError: Unterminated string.' -EOF_STR = '(%s,%s) - LexicographicError: EOF in string.' -NULL_STR = '(%s,%s) - LexicographicError: String contains null character' -EOF_COMM = '(%s,%s) - LexicographicError: EOF in comment.' +# lexical analysis +LEX_ERROR = '(%s, %s) - LexicographicError: Unexpected symbol "%s".' +UNT_STR = '(%s, %s) - LexicographicError: Unterminated string.' +EOF_STR = '(%s, %s) - LexicographicError: EOF in string.' +NULL_STR = '(%s, %s) - LexicographicError: String contains null character.' +EOF_COMM = '(%s, %s) - LexicographicError: EOF in comment.' -SYN_ERROR = '(%s,%s) - SyntacticError: Syntax error at or near "%s"' -SYN_EOF = '(0, 0) - SyntacticError: ERROR at or near EOF' # empty program +# parsing +SYN_ERROR = '(%s, %s) - SyntacticError: Syntax error at or near "%s".' +SYN_EOF = '(0, 0) - SyntacticError: ERROR at or near EOF.' # empty program + +# semantic analysis +CANNOT_INHERIT = '%s - SemanticError: Type "%s" cannot be inherited.' +CYCLIC_INHERITANCE = '%s - SemanticError: Cyclic inheritance involving "%s".' +TYPE_ALREADY_DEFINED = '%s - SemanticError: Type "%s" already defined.' +ATTRIBUTE_DEFINED_IN_PARENT = '%s - SemanticError: Attribute "%s" is already defined in parent.' +ATTRIBUTE_ALREADY_DEFINED = '%s - SemanticError: Attribute "%s" already defined in "%s".' +METHOD_ALREADY_DEFINED = '%s - SemanticError: Method "%s" already defined in "%s".' +WRONG_SIGNATURE = '%s - SemanticError: Method "%s" already defined in "%s" with a different signature.' +LOCAL_ALREADY_DEFINED = '%s - SemanticError: Variable "%s" is already defined in method "%s".' +SELF_TYPE_INVALID_PARAM_TYPE = '%s - SemanticError: SELF_TYPE cannot be a static type for a parameter.' +SELF_INVALID_ID = '%s - SemanticError: Cannot define "self" as attribute of a class or an identifier.' +SELF_IS_READONLY = '%s - SemanticError: Variable "self" is read-only.' +CASE_DUPLICATED_BRANCH = '%s - SemanticError: Duplicate branch "%s" in case statement.' +UNDEFINED_METHOD = '%s - AttributeError: Method "%s" is not defined in "%s" or inherited.' +UNDEFINED_TYPE = '%s - TypeError: Type "%s" is not defined.' +INCOMPATIBLE_TYPES = '%s - TypeError: Cannot convert "%s" into "%s".' +INVALID_ANCESTOR = '%s - TypeError: Class "%s" has not class "%s" as ancestor.' +INVALID_BINARY_OPERATOR = '%s - TypeError: Operation "%s" is not defined between "%s" and "%s".' +INVALID_UNARY_OPERATOR = '%s - TypeError: Operation "%s" is not defined for "%s".' +VARIABLE_NOT_DEFINED = '%s - NameError: Variable "%s" is not defined in "%s".' diff --git a/src/coolcmp/formatter.py b/src/coolcmp/formatter.py new file mode 100644 index 000000000..d8f6315b9 --- /dev/null +++ b/src/coolcmp/formatter.py @@ -0,0 +1,137 @@ +from coolcmp import visitor +from coolcmp.ast import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode, LetNode, \ + AssignNode, BlockNode, ConditionalNode, WhileNode, CaseNode, CallNode, BinaryNode, AtomicNode, InstantiateNode, \ + UnaryNode + + +class FormatVisitor(object): + @visitor.on('node') + def visit(self, node, tabs): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode, tabs: int = 0): + ans = ' ' * tabs + f'\\__ProgramNode [ ... ]' + statements = '\n'.join(self.visit(child, tabs + 1) for child in node.declarations) + return f'{ans}\n{statements}' + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode, tabs: int = 0): + parent = '' if node.parent is None else f": {node.parent}" + ans = ' ' * tabs + f'\\__ClassDeclarationNode: class {node.id} {parent} {{ ... }}' + features = '\n'.join(self.visit(child, tabs + 1) for child in node.features) + return f'{ans}\n{features}' + + @visitor.when(AttrDeclarationNode) + def visit(self, node: AttrDeclarationNode, tabs: int = 0): + ans = ' ' * tabs + f'\\__AttrDeclarationNode: {node.id} : {node.type}' + return f'{ans}' + + @visitor.when(FuncDeclarationNode) + def visit(self, node: FuncDeclarationNode, tabs: int = 0): + params = ', '.join(f'{param.id}: {param.type}' for param in node.params) + ans = ' ' * tabs + f'\\__FuncDeclarationNode: {node.id}({params}) : {node.return_type} -> ' + body = self.visit(node.body, tabs + 1) + return f'{ans}\n{body}' + + @visitor.when(LetNode) + def visit(self, node: LetNode, tabs: int = 0): + declarations = [] + for declaration_node in node.declarations: + _id = declaration_node.id + _type = declaration_node.type + _expr = declaration_node.expr + if _expr is not None: + declarations.append( + ' ' * tabs + + f'\\__VarDeclarationNode: {_id}: {_type} <-\n{self.visit(_expr, tabs + 1)}' + ) + else: + declarations.append(' ' * tabs + + f'\\__VarDeclarationNode: {_id} : {_type}') + declarations = '\n'.join(declarations) + ans = ' ' * tabs + f'\\__LetNode: let' + expr = self.visit(node.expr, tabs + 2) + return f'{ans}\n {declarations}\n' + ' ' * (tabs + 1) + 'in\n' + f'{expr}' + + @visitor.when(AssignNode) + def visit(self, node: AssignNode, tabs: int = 0): + ans = ' ' * tabs + f'\\__AssignNode: {node.id} <- ' + expr = self.visit(node.expr, tabs + 1) + return f'{ans}\n{expr}' + + @visitor.when(BlockNode) + def visit(self, node: BlockNode, tabs: int = 0): + ans = ' ' * tabs + f'\\__BlockNode:' + body = '\n'.join( + self.visit(child, tabs + 1) for child in node.expressions) + return f'{ans}\n{body}' + + @visitor.when(ConditionalNode) + def visit(self, node: ConditionalNode, tabs: int = 0): + ifx = self.visit(node.if_expr, tabs + 2) + then = self.visit(node.then_expr, tabs + 2) + elsex = self.visit(node.else_expr, tabs + 2) + + return '\n'.join([ + ' ' * tabs + + f'\\__ConditionalNode: if then else fi', + ' ' * (tabs + 1) + f'\\__if \n{ifx}', + ' ' * (tabs + 1) + f'\\__then \n{then}', + ' ' * (tabs + 1) + f'\\__else \n{elsex}', + ]) + + @visitor.when(WhileNode) + def visit(self, node: WhileNode, tabs: int = 0): + condition = self.visit(node.condition, tabs + 2) + body = self.visit(node.body, tabs + 2) + + return '\n'.join([ + ' ' * tabs + f'\\__WhileNode: while loop pool', + ' ' * (tabs + 1) + f'\\__while \n{condition}', + ' ' * (tabs + 1) + f'\\__loop \n{body}', + ]) + + @visitor.when(CaseNode) + def visit(self, node: CaseNode, tabs: int = 0): + cases = [] + for _id, _type, _expr in node.cases: + expr = self.visit(_expr, tabs + 3) + cases.append(' ' * tabs + + f'\\__CaseNode: {_id} : {_type} =>\n{expr}') + expr = self.visit(node.expr, tabs + 2) + cases = '\n'.join(cases) + + return '\n'.join([ + ' ' * tabs + + f'\\__CaseNode: case of [ ... ] esac', + ' ' * (tabs + 1) + f'\\__case \n{expr} of', + ]) + '\n' + cases + + @visitor.when(CallNode) + def visit(self, node: CallNode, tabs: int = 0): + obj = self.visit(node.obj, tabs + 1) + ans = ' ' * tabs + f'\\__CallNode: .{node.id}(, ..., )' + args = '\n'.join(self.visit(arg, tabs + 1) for arg in node.args) + return f'{ans}\n{obj}\n{args}' + + @visitor.when(BinaryNode) + def visit(self, node: BinaryNode, tabs: int = 0): + ans = ' ' * tabs + f'\\__ {node.__class__.__name__} ' + left = self.visit(node.left, tabs + 1) + right = self.visit(node.right, tabs + 1) + return f'{ans}\n{left}\n{right}' + + @visitor.when(AtomicNode) + def visit(self, node: AtomicNode, tabs: int = 0): + return ' ' * tabs + f'\\__{node.__class__.__name__}: {node.lex}' + + @visitor.when(InstantiateNode) + def visit(self, node: InstantiateNode, tabs: int = 0): + return ' ' * tabs + f'\\__InstantiateNode: new {node.lex}()' + + @visitor.when(UnaryNode) + def visit(self, node: UnaryNode, tabs: int = 0): + ans = ' ' * tabs + f'\\__{node.__class__.__name__}: ' + expr = self.visit(node.expr, tabs + 1) + return f'{ans}\n{expr}' diff --git a/src/coolcmp/lexing_parsing/parser.py b/src/coolcmp/lexing_parsing/parser.py index a485ebda9..37fb3b2e6 100644 --- a/src/coolcmp/lexing_parsing/parser.py +++ b/src/coolcmp/lexing_parsing/parser.py @@ -46,9 +46,9 @@ def p_class_def(p): """ if len(p) == 8: p[0] = ast.ClassDeclarationNode(p[2], p[6], p[4]) + p[0].parent_pos = (p.lineno(4), find_column(p.lexer.lexdata, p.lexpos(4))) else: p[0] = ast.ClassDeclarationNode(p[2], p[4]) - p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) @@ -58,7 +58,7 @@ def p_feature_list(p): | func_def SEMI feature_list | empty """ - if len(p) == 3: + if len(p) == 4: p[0] = [p[1]] + p[3] else: p[0] = [] @@ -71,10 +71,12 @@ def p_attr_def(p): """ if len(p) == 6: p[0] = ast.AttrDeclarationNode(p[1], p[3], p[5]) + p[0].expr_pos = p[5].pos else: p[0] = ast.AttrDeclarationNode(p[1], p[3]) - p[0].set_pos(p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + p[0].type_pos = (p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) def p_func_def(p): @@ -84,10 +86,12 @@ def p_func_def(p): """ if len(p) == 10: p[0] = ast.FuncDeclarationNode(p[1], p[3], p[6], p[8]) - p[0].set_pos(p.lineno(6), find_column(p.lexer.lexdata, p.lexpos(6))) + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + p[0].type_pos = (p.lineno(6), find_column(p.lexer.lexdata, p.lexpos(6))) else: p[0] = ast.FuncDeclarationNode(p[1], [], p[5], p[7]) - p[0].set_pos(p.lineno(5), find_column(p.lexer.lexdata, p.lexpos(5))) + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + p[0].type_pos = (p.lineno(5), find_column(p.lexer.lexdata, p.lexpos(5))) def p_param_list(p): @@ -95,10 +99,13 @@ def p_param_list(p): param_list : ID COLON TYPE COMMA param_list | ID COLON TYPE """ + param = ast.ParamNode(p[1], p[3]) + param.set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + param.type_pos = (p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) if len(p) == 6: - p[0] = [ast.ParamNode(p[1], p[3])] + p[5] - else: - p[0] = [ast.ParamNode(p[1], p[3])] + p[0] = [param] + p[5] + elif len(p) == 4: + p[0] = [param] # expr productions @@ -109,7 +116,7 @@ def p_expr_assign(p): """ p[0] = ast.AssignNode(p[1], p[3]) - p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) def p_expr_list(p): @@ -156,6 +163,7 @@ def p_expr_func_call(p): else: p[0] = ast.CallNode(p[5], p[7], p[1], p[3]) p[0].set_pos(p.lineno(5), find_column(p.lexer.lexdata, p.lexpos(5))) + p[0].parent_pos = (p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) def p_expr_if(p): @@ -203,15 +211,33 @@ def p_decl_list(p): | ID COLON TYPE ASSIGN expr | ID COLON TYPE """ - if len(p) == 8: - p[0] = [ast.LetDeclarationNode(p[1], p[3], p[5])] + p[7] - elif len(p) == 6: - if p[4] == ',': - p[0] = [ast.LetDeclarationNode(p[1], p[3])] + p[5] + if len(p) > 4 and p[4] == '<-': + declaration = ast.LetDeclarationNode(p[1], p[3], p[5]) + declaration.type_pos = (p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) + declaration.expr_pos = p[5].pos + declaration.set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + if len(p) == 8: + p[0] = [declaration] + p[7] else: - p[0] = [ast.LetDeclarationNode(p[1], p[3], p[5])] + p[0] = [declaration] else: - p[0] = [ast.LetDeclarationNode(p[1], p[3])] + declaration = ast.LetDeclarationNode(p[1], p[3]) + declaration.type_pos = (p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) + declaration.set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + if len(p) == 6: + p[0] = [declaration] + p[5] + else: + p[0] = [declaration] + + # if len(p) == 8: + # p[0] = [ast.LetDeclarationNode(p[1], p[3], p[5])] + p[7] + # elif len(p) == 6: + # if p[4] == ',': + # p[0] = [ast.LetDeclarationNode(p[1], p[3])] + p[5] + # else: + # p[0] = [ast.LetDeclarationNode(p[1], p[3], p[5])] + # else: + # p[0] = [ast.LetDeclarationNode(p[1], p[3])] def p_expr_let(p): @@ -228,10 +254,14 @@ def p_case_list(p): case_list : ID COLON TYPE ARROW expr SEMI case_list | ID COLON TYPE ARROW expr SEMI """ + branch = ast.CaseBranchNode(p[1], p[3], p[5]) + branch.set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) + branch.type_pos = (p.lineno(3), find_column(p.lexer.lexdata, p.lexpos(3))) + if len(p) == 8: - p[0] = [(p[1], p[3], p[5])] + p[7] # maybe here is better to use a Node + p[0] = [branch] + p[7] else: - p[0] = [(p[1], p[3], p[5])] + p[0] = [branch] def p_expr_case(p): @@ -249,7 +279,7 @@ def p_expr_new(p): """ p[0] = ast.InstantiateNode(p[2]) - p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) def p_expr_isvoid(p): @@ -258,7 +288,7 @@ def p_expr_isvoid(p): """ p[0] = ast.IsVoidNode(p[2]) - p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) def p_expr_binary_op(p): @@ -277,6 +307,8 @@ def p_expr_binary_op(p): p[0] = ast.MinusNode(p[1], p[2], p[3]) elif p[2] == '/': p[0] = ast.DivNode(p[1], p[2], p[3]) + elif p[2] == '*': + p[0] = ast.StarNode(p[1], p[2], p[3]) elif p[2] == '<': p[0] = ast.LessThanNode(p[1], p[2], p[3]) elif p[2] == '<=': @@ -291,7 +323,9 @@ def p_expr_comp(p): """ expr : COMP expr """ - p[0] = p[1] + p[0] = ast.ComplementNode(p[2]) + + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) def p_expr_not(p): @@ -300,7 +334,7 @@ def p_expr_not(p): """ p[0] = ast.NegationNode(p[2]) - p[0].set_pos(p.lineno(2), find_column(p.lexer.lexdata, p.lexpos(2))) + p[0].set_pos(p.lineno(1), find_column(p.lexer.lexdata, p.lexpos(1))) def p_expr_pars(p): diff --git a/src/coolcmp/lexing_parsing/parsetab.py b/src/coolcmp/lexing_parsing/parsetab.py index d8cc43351..d30f40548 100644 --- a/src/coolcmp/lexing_parsing/parsetab.py +++ b/src/coolcmp/lexing_parsing/parsetab.py @@ -27,20 +27,20 @@ del _lr_goto_items _lr_productions = [ ("S' -> program","S'",1,None,None,None), - ('program -> class_list','program',1,'p_program','parser.py',33), - ('class_list -> class_def SEMI class_list','class_list',3,'p_class_list','parser.py',40), - ('class_list -> class_def SEMI','class_list',2,'p_class_list','parser.py',41), - ('class_def -> CLASS TYPE INHERITS TYPE OCUR feature_list CCUR','class_def',7,'p_class_def','parser.py',51), - ('class_def -> CLASS TYPE OCUR feature_list CCUR','class_def',5,'p_class_def','parser.py',52), - ('feature_list -> attr_def SEMI feature_list','feature_list',3,'p_feature_list','parser.py',64), - ('feature_list -> func_def SEMI feature_list','feature_list',3,'p_feature_list','parser.py',65), - ('feature_list -> empty','feature_list',1,'p_feature_list','parser.py',66), - ('attr_def -> ID COLON TYPE ASSIGN expr','attr_def',5,'p_attr_def','parser.py',76), - ('attr_def -> ID COLON TYPE','attr_def',3,'p_attr_def','parser.py',77), - ('func_def -> ID OPAR param_list CPAR COLON TYPE OCUR expr CCUR','func_def',9,'p_func_def','parser.py',89), - ('func_def -> ID OPAR CPAR COLON TYPE OCUR expr CCUR','func_def',8,'p_func_def','parser.py',90), - ('param_list -> ID COLON TYPE COMMA param_list','param_list',5,'p_param_list','parser.py',102), - ('param_list -> ID COLON TYPE','param_list',3,'p_param_list','parser.py',103), + ('program -> class_list','program',1,'p_program','parser.py',26), + ('class_list -> class_def SEMI class_list','class_list',3,'p_class_list','parser.py',33), + ('class_list -> class_def SEMI','class_list',2,'p_class_list','parser.py',34), + ('class_def -> CLASS TYPE INHERITS TYPE OCUR feature_list CCUR','class_def',7,'p_class_def','parser.py',44), + ('class_def -> CLASS TYPE OCUR feature_list CCUR','class_def',5,'p_class_def','parser.py',45), + ('feature_list -> attr_def SEMI feature_list','feature_list',3,'p_feature_list','parser.py',57), + ('feature_list -> func_def SEMI feature_list','feature_list',3,'p_feature_list','parser.py',58), + ('feature_list -> empty','feature_list',1,'p_feature_list','parser.py',59), + ('attr_def -> ID COLON TYPE ASSIGN expr','attr_def',5,'p_attr_def','parser.py',69), + ('attr_def -> ID COLON TYPE','attr_def',3,'p_attr_def','parser.py',70), + ('func_def -> ID OPAR param_list CPAR COLON TYPE OCUR expr CCUR','func_def',9,'p_func_def','parser.py',84), + ('func_def -> ID OPAR CPAR COLON TYPE OCUR expr CCUR','func_def',8,'p_func_def','parser.py',85), + ('param_list -> ID COLON TYPE COMMA param_list','param_list',5,'p_param_list','parser.py',99), + ('param_list -> ID COLON TYPE','param_list',3,'p_param_list','parser.py',100), ('expr -> ID ASSIGN expr','expr',3,'p_expr_assign','parser.py',115), ('expr_list -> expr COMMA expr_list_not_empty','expr_list',3,'p_expr_list','parser.py',124), ('expr_list -> expr','expr_list',1,'p_expr_list','parser.py',125), @@ -50,34 +50,34 @@ ('expr -> ID OPAR expr_list CPAR','expr',4,'p_expr_func_call','parser.py',153), ('expr -> expr DOT ID OPAR expr_list CPAR','expr',6,'p_expr_func_call','parser.py',154), ('expr -> expr AT TYPE DOT ID OPAR expr_list CPAR','expr',8,'p_expr_func_call','parser.py',155), - ('expr -> IF expr THEN expr ELSE expr FI','expr',7,'p_expr_if','parser.py',170), - ('expr -> WHILE expr LOOP expr POOL','expr',5,'p_expr_while','parser.py',179), - ('block -> expr SEMI block','block',3,'p_block','parser.py',188), - ('block -> expr SEMI','block',2,'p_block','parser.py',189), - ('expr -> OCUR block CCUR','expr',3,'p_expr_block','parser.py',199), - ('decl_list -> ID COLON TYPE ASSIGN expr COMMA decl_list','decl_list',7,'p_decl_list','parser.py',208), - ('decl_list -> ID COLON TYPE COMMA decl_list','decl_list',5,'p_decl_list','parser.py',209), - ('decl_list -> ID COLON TYPE ASSIGN expr','decl_list',5,'p_decl_list','parser.py',210), - ('decl_list -> ID COLON TYPE','decl_list',3,'p_decl_list','parser.py',211), - ('expr -> LET decl_list IN expr','expr',4,'p_expr_let','parser.py',226), - ('case_list -> ID COLON TYPE ARROW expr SEMI case_list','case_list',7,'p_case_list','parser.py',235), - ('case_list -> ID COLON TYPE ARROW expr SEMI','case_list',6,'p_case_list','parser.py',236), - ('expr -> CASE expr OF case_list ESAC','expr',5,'p_expr_case','parser.py',246), - ('expr -> NEW TYPE','expr',2,'p_expr_new','parser.py',256), - ('expr -> ISVOID expr','expr',2,'p_expr_isvoid','parser.py',265), - ('expr -> expr PLUS expr','expr',3,'p_expr_binary_op','parser.py',274), - ('expr -> expr MINUS expr','expr',3,'p_expr_binary_op','parser.py',275), - ('expr -> expr STAR expr','expr',3,'p_expr_binary_op','parser.py',276), - ('expr -> expr DIV expr','expr',3,'p_expr_binary_op','parser.py',277), - ('expr -> expr LESS expr','expr',3,'p_expr_binary_op','parser.py',278), - ('expr -> expr LEQ expr','expr',3,'p_expr_binary_op','parser.py',279), - ('expr -> expr EQ expr','expr',3,'p_expr_binary_op','parser.py',280), - ('expr -> COMP expr','expr',2,'p_expr_comp','parser.py',300), - ('expr -> NOT expr','expr',2,'p_expr_not','parser.py',307), - ('expr -> OPAR expr CPAR','expr',3,'p_expr_pars','parser.py',316), - ('expr -> ID','expr',1,'p_expr_id','parser.py',323), - ('expr -> INT','expr',1,'p_expr_int','parser.py',332), - ('expr -> STRING','expr',1,'p_expr_string','parser.py',341), - ('expr -> BOOL','expr',1,'p_expr_bool','parser.py',350), - ('empty -> ','empty',0,'p_empty','parser.py',361), + ('expr -> IF expr THEN expr ELSE expr FI','expr',7,'p_expr_if','parser.py',171), + ('expr -> WHILE expr LOOP expr POOL','expr',5,'p_expr_while','parser.py',180), + ('block -> expr SEMI block','block',3,'p_block','parser.py',189), + ('block -> expr SEMI','block',2,'p_block','parser.py',190), + ('expr -> OCUR block CCUR','expr',3,'p_expr_block','parser.py',200), + ('decl_list -> ID COLON TYPE ASSIGN expr COMMA decl_list','decl_list',7,'p_decl_list','parser.py',209), + ('decl_list -> ID COLON TYPE COMMA decl_list','decl_list',5,'p_decl_list','parser.py',210), + ('decl_list -> ID COLON TYPE ASSIGN expr','decl_list',5,'p_decl_list','parser.py',211), + ('decl_list -> ID COLON TYPE','decl_list',3,'p_decl_list','parser.py',212), + ('expr -> LET decl_list IN expr','expr',4,'p_expr_let','parser.py',245), + ('case_list -> ID COLON TYPE ARROW expr SEMI case_list','case_list',7,'p_case_list','parser.py',254), + ('case_list -> ID COLON TYPE ARROW expr SEMI','case_list',6,'p_case_list','parser.py',255), + ('expr -> CASE expr OF case_list ESAC','expr',5,'p_expr_case','parser.py',269), + ('expr -> NEW TYPE','expr',2,'p_expr_new','parser.py',278), + ('expr -> ISVOID expr','expr',2,'p_expr_isvoid','parser.py',287), + ('expr -> expr PLUS expr','expr',3,'p_expr_binary_op','parser.py',296), + ('expr -> expr MINUS expr','expr',3,'p_expr_binary_op','parser.py',297), + ('expr -> expr STAR expr','expr',3,'p_expr_binary_op','parser.py',298), + ('expr -> expr DIV expr','expr',3,'p_expr_binary_op','parser.py',299), + ('expr -> expr LESS expr','expr',3,'p_expr_binary_op','parser.py',300), + ('expr -> expr LEQ expr','expr',3,'p_expr_binary_op','parser.py',301), + ('expr -> expr EQ expr','expr',3,'p_expr_binary_op','parser.py',302), + ('expr -> COMP expr','expr',2,'p_expr_comp','parser.py',324), + ('expr -> NOT expr','expr',2,'p_expr_not','parser.py',333), + ('expr -> OPAR expr CPAR','expr',3,'p_expr_pars','parser.py',342), + ('expr -> ID','expr',1,'p_expr_id','parser.py',349), + ('expr -> INT','expr',1,'p_expr_int','parser.py',358), + ('expr -> STRING','expr',1,'p_expr_string','parser.py',367), + ('expr -> BOOL','expr',1,'p_expr_bool','parser.py',376), + ('empty -> ','empty',0,'p_empty','parser.py',387), ] diff --git a/src/coolcmp/semantic.py b/src/coolcmp/semantic.py new file mode 100644 index 000000000..d36e1c0c3 --- /dev/null +++ b/src/coolcmp/semantic.py @@ -0,0 +1,266 @@ +from __future__ import annotations +import itertools as itt +from collections import OrderedDict + +from coolcmp.ast import Node, ParamNode + + +class SemanticError(Exception): + @property + def text(self) -> str: + return self.args[0] + + +class Attribute: + def __init__(self, name: str, typex: Type): + self.name = name + self.type = typex + + def __str__(self) -> str: + return f'[attrib] {self.name} : {self.type.name};' + + def __repr__(self) -> str: + return str(self) + + +class Method: + def __init__(self, name: str, param_names: list[str], params_types: list[Type], return_type: Type): + self.name = name + self.param_names = param_names + self.param_types = params_types + self.return_type = return_type + + def __str__(self) -> str: + params = ', '.join(f'{n}: {t.name}' for n, t in zip(self.param_names, self.param_types)) + return f'[method] {self.name}({params}): {self.return_type.name};' + + def __eq__(self, other) -> bool: + return other.name == self.name and \ + other.return_type == self.return_type and \ + other.param_types == self.param_types + + +class Type: + def __init__(self, name: str): + self.name = name + self.attributes: list[Attribute] = [] + self.methods: list[Method] = [] + self.parent: Type | None = None + + def set_parent(self, parent: Type) -> None: + if self.parent is not None: + raise SemanticError(f'Parent type is already set for {self.name}.') + self.parent = parent + + def get_attribute(self, name: str, from_class: str = None) -> Attribute: + try: + return next(attr for attr in self.attributes if attr.name == name) + except StopIteration: + if self.parent is None: + raise SemanticError(f'Attribute "{name}" is not defined in {self.name}.') + try: + if from_class is not None and (self.parent.name == from_class or self.name == from_class): + raise SemanticError(f'Cyclic inheritance in class "{from_class}"') + return self.parent.get_attribute(name, from_class) + except SemanticError: + raise SemanticError(f'Attribute "{name}" is not defined in {self.name}.') + + def define_attribute(self, name: str, typex: Type, from_class: str = None) -> Attribute: + try: + self.get_attribute(name, from_class) + except SemanticError: + attribute = Attribute(name, typex) + self.attributes.append(attribute) + return attribute + else: + raise SemanticError(f'Attribute "{name}" is already defined in {self.name}.') + + def get_method(self, name: str, get_owner: bool = False) -> Method | tuple[Method, Type]: + try: + meth = next(method for method in self.methods if method.name == name) + return meth if not get_owner else (meth, self) + except StopIteration: + if self.parent is None: + raise SemanticError(f'Method "{name}" is not defined in {self.name}.') + try: + return self.parent.get_method(name, get_owner) + except SemanticError: + raise SemanticError(f'Method "{name}" is not defined in {self.name}.') + + def define_method(self, name: str, param_names: list[str], param_types: list[Type], return_type: Type) -> Method: + if name in (method.name for method in self.methods): + raise SemanticError(f'Method "{name}" already defined in {self.name}') + + method = Method(name, param_names, param_types, return_type) + self.methods.append(method) + return method + + def all_attributes(self, clean: bool = True) -> dict[str, tuple[Attribute, Type]] | tuple[Attribute, Type]: + plain = OrderedDict() if self.parent is None else self.parent.all_attributes(False) + for attr in self.attributes: + plain[attr.name] = (attr, self) + return plain.values() if clean else plain + + def all_methods(self, clean: bool = True) -> dict[str, tuple[Method, Type]] | tuple[Method, Type]: + plain = OrderedDict() if self.parent is None else self.parent.all_methods(False) + for method in self.methods: + plain[method.name] = (method, self) + return plain.values() if clean else plain + + def get_ancestors(self, from_: str = None, ignore_from: bool = True) -> list[Type]: + if not ignore_from and self.name == from_: + raise SemanticError(f'Cyclic inheritance in class {from_}') + if self.parent is None: + return [self] + else: + return [self] + self.parent.get_ancestors(from_, False) + + def conforms_to(self, other: Type) -> bool: + return other.bypass() or self == other or self.parent is not None and self.parent.conforms_to(other) + + def join(self, type_: Type) -> Type: + ancestors = self.get_ancestors() + current = type_ + while current is not None: + if current in ancestors: + break + current = current.parent + return current + + def bypass(self) -> bool: + return False + + def __str__(self) -> str: + output = f'type {self.name}' + parent = '' if self.parent is None else f' : {self.parent.name}' + output += parent + output += ' {' + output += '\n\t' if self.attributes or self.methods else '' + output += '\n\t'.join(str(x) for x in self.attributes) + output += '\n\t' if self.attributes else '' + output += '\n\t'.join(str(x) for x in self.methods) + output += '\n' if self.methods else '' + output += '}\n' + return output + + def __repr__(self) -> str: + return str(self) + + def __eq__(self, other: Type) -> bool: + return self.name == other.name + + +class ErrorType(Type): + def __init__(self): + Type.__init__(self, '') + + def conforms_to(self, other) -> bool: + return True + + def bypass(self) -> bool: + return True + + def __eq__(self, other: Type) -> bool: + return isinstance(other, Type) + + +class VoidType(Type): + def __init__(self): + Type.__init__(self, '') + + def conforms_to(self, other) -> bool: + raise Exception('Invalid type: void type.') + + def bypass(self) -> bool: + return True + + def __eq__(self, other: Type) -> bool: + return isinstance(other, VoidType) + + +class IntType(Type): + def __init__(self): + Type.__init__(self, 'int') + + def __eq__(self, other) -> bool: + return other.name == self.name or isinstance(other, IntType) + + +class Context: + def __init__(self): + self.types: dict[str, Type] = {} + + def create_type(self, name: str) -> Type: + if name in self.types: + raise SemanticError(f'Type with the same name ({name}) already in context.') + typex = self.types[name] = Type(name) + return typex + + def get_type(self, name: str) -> Type: + try: + return self.types[name] + except KeyError: + raise SemanticError(f'Type "{name}" is not defined.') + + def set_type(self, name: str, type_: Type) -> None: + self.types[name] = type_ + + def __str__(self) -> str: + return '{\n\t' + '\n\t'.join(y for x in self.types.values() for y in str(x).split('\n')) + '\n}' + + def __repr__(self) -> str: + return str(self) + + +class VariableInfo: + def __init__(self, name: str, vtype: Type, is_attr: bool = False, is_param: bool = False): + self.name = name + self.type = vtype + self.is_attr = is_attr + self.is_param = is_param + + def __str__(self) -> str: + return f'{self.name}: {self.type}' + + +class Scope: + def __init__(self, parent: Scope = None): + self.locals: list[VariableInfo] = [] + self.parent = parent + self.children: list[Scope] = [] + self.index = 0 if parent is None else len(parent) + + def __len__(self) -> int: + return len(self.locals) + + def create_child(self) -> Scope: + child = Scope(self) + self.children.append(child) + return child + + def define_variable(self, vname: str, vtype: Type, is_attr: bool = False, is_param: bool = False) -> VariableInfo: + info = VariableInfo(vname, vtype, is_attr, is_param) + self.locals.append(info) + return info + + def find_variable(self, vname: str, index: int = None) -> VariableInfo | None: + locals_ = self.locals if index is None else itt.islice(self.locals, index) + try: + return next(x for x in locals_ if x.name == vname) + except StopIteration: + return self.parent.find_variable(vname, self.index) if self.parent is not None else None + + def is_defined(self, vname: str) -> bool: + return self.find_variable(vname) is not None + + def is_local(self, vname: str) -> bool: + return any(True for x in self.locals if x.name == vname) + + def __str__(self) -> str: + s = 'Scope\n' + for v in self.locals: + s += f'{v.name}: {v.type.name}\n' + if self.children: + for child in self.children: + s += str(child) + return s diff --git a/src/coolcmp/semantics/__init__.py b/src/coolcmp/semantics/__init__.py new file mode 100644 index 000000000..5c8a3a88b --- /dev/null +++ b/src/coolcmp/semantics/__init__.py @@ -0,0 +1,20 @@ +from .collector import TypeCollector +from .builder import TypeBuilder +from .consistence import TypeConsistence +from .checker import TypeChecker + + +def check_semantics(ast) -> list[str]: + collector = TypeCollector() + collector.visit(ast) + + builder = TypeBuilder(collector.context, collector.errors) + builder.visit(ast) + + cons = TypeConsistence(builder.context, builder.errors) + cons.visit(ast) + + checker = TypeChecker(cons.context, cons.errors) + checker.visit(ast) + + return checker.errors diff --git a/src/coolcmp/semantics/builder.py b/src/coolcmp/semantics/builder.py new file mode 100644 index 000000000..71a79e968 --- /dev/null +++ b/src/coolcmp/semantics/builder.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from coolcmp.ast import ProgramNode, ClassDeclarationNode, FuncDeclarationNode, AttrDeclarationNode +from coolcmp.semantic import SemanticError, Context, ErrorType, Type +from coolcmp import visitor, errors as err + + +class TypeBuilder: + """ + Collects attributes, methods and parent in classes. + In case of a type error set type to ErrorType. + """ + def __init__(self, context: Context, errors: list[str]): + self.context = context + self.current_type: Type | None = None + self.errors = errors + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode): + for declaration in node.declarations: + self.visit(declaration) + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode): + self.current_type = self.context.get_type(node.id) + + # check parent + if node.parent is not None: + if node.parent in ('SELF_TYPE', 'String', 'Int', 'Bool', node.id): + self.current_type.set_parent(ErrorType()) + self.errors.append(err.CANNOT_INHERIT % (node.parent_pos, node.parent)) + else: + try: + parent_type = self.context.get_type(node.parent) + except SemanticError: # the parent type is not defined + parent_type = ErrorType() + self.errors.append(err.UNDEFINED_TYPE % (node.parent_pos, node.parent)) + try: + self.current_type.set_parent(parent_type) + except SemanticError: # this node already has a parent + self.errors.append(err.CANNOT_INHERIT % (node.pos, node.id, node.parent)) + else: + try: + self.current_type.set_parent(self.context.get_type('Object')) + except SemanticError: + pass + + # visit features + for feature in node.features: + self.visit(feature) + + @visitor.when(FuncDeclarationNode) + def visit(self, node: FuncDeclarationNode): + param_names = [] + param_types = [] + for param_node in node.params: + param_names.append(param_node.id) + try: + param_types.append(self.context.get_type(param_node.type)) + except SemanticError: + param_types.append(ErrorType()) + self.errors.append(err.UNDEFINED_TYPE % (param_node.type_pos, param_node.type)) + try: + ret_type = self.context.get_type(node.return_type) + except SemanticError: + self.errors.append(err.UNDEFINED_TYPE % (node.pos, node.return_type)) + ret_type = ErrorType() + + try: + self.current_type.define_method(node.id, param_names, param_types, ret_type) + except SemanticError: + self.errors.append(err.METHOD_ALREADY_DEFINED % (node.pos, node.id, self.current_type.name)) + + @visitor.when(AttrDeclarationNode) + def visit(self, node: AttrDeclarationNode): + try: + attr_type = self.context.get_type(node.type) + except SemanticError: + attr_type = ErrorType() + self.errors.append(err.UNDEFINED_TYPE % (node.pos, node.type)) + + try: + self.current_type.define_attribute(node.id, attr_type, self.current_type.name) + except SemanticError: + self.errors.append(err.ATTRIBUTE_ALREADY_DEFINED % (node.pos, node.id, self.current_type.name)) diff --git a/src/coolcmp/semantics/checker.py b/src/coolcmp/semantics/checker.py new file mode 100644 index 000000000..1f6c3498b --- /dev/null +++ b/src/coolcmp/semantics/checker.py @@ -0,0 +1,380 @@ +from __future__ import annotations + +from coolcmp import visitor, errors as err +from coolcmp.semantic import Context, Method, Type, SemanticError, ErrorType, Scope +from coolcmp.ast import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode, BlockNode, \ + LetNode, CaseNode, AssignNode, ConditionalNode, WhileNode, CallNode, VariableNode, InstantiateNode, IntegerNode, \ + StringNode, BooleanNode, PlusNode, MinusNode, StarNode, DivNode, LessThanNode, LessEqualNode, EqualNode, \ + IsVoidNode, NegationNode, ComplementNode, BinaryNode, UnaryNode, CaseBranchNode, LetDeclarationNode, ParamNode + + +class TypeChecker: + def __init__(self, context, errors): + self.context: Context = context + self.current_type: Type | None = None + self.current_method: Method | None = None + self.errors = errors + + @visitor.on('node') + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode, scope: Scope = None): + if scope is None: + scope = Scope() + + # for declaration in node.declarations: + # self.visit(declaration, scope.create_child()) + + pending = [(class_node.id, class_node) for class_node in node.declarations] + scopes = {'IO': scope.create_child()} + + while pending: + + actual = pending.pop(0) + type_ = self.context.get_type(actual[0]) + + if type_.parent.name not in ('Object', ''): + try: + scopes[type_.name] = scopes[type_.parent.name].create_child() + self.visit(actual[1], scopes[type_.name]) + except KeyError: # Parent not visited yet + pending.append(actual) + else: + scopes[type_.name] = scope.create_child() + self.visit(actual[1], scopes[type_.name]) + + return scope + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode, scope: Scope): + self.current_type = self.context.get_type(node.id) + + # visit features + for feature in node.features: + if isinstance(feature, AttrDeclarationNode): + self.visit(feature, scope) + elif isinstance(feature, FuncDeclarationNode): + self.visit(feature, scope.create_child()) + else: + raise Exception(f'Invalid feature at class {node.id}') + + @visitor.when(AttrDeclarationNode) + def visit(self, node: AttrDeclarationNode, scope: Scope): + # Check attribute override + try: + attr = self.current_type.parent.get_attribute(node.id, self.current_type.name) + self.errors.append(err.ATTRIBUTE_DEFINED_IN_PARENT % (node.pos, attr.name)) + except SemanticError: + pass + + if node.id == 'self': + self.errors.append(err.SELF_INVALID_ID % (node.pos, )) + + try: + attr_type = self.context.get_type(node.type) if node.type != 'SELF_TYPE' else self.current_type + except SemanticError: + attr_type = ErrorType() + self.errors.append(err.UNDEFINED_TYPE % (node.type_pos, node.type)) + + if node.expr is not None: + expr_type = self.visit(node.expr, scope.create_child()) + if not expr_type.conforms_to(attr_type): + self.errors.append(err.INCOMPATIBLE_TYPES % (node.expr_pos, expr_type.name, attr_type.name)) + + scope.define_variable(node.id, attr_type) + + @visitor.when(FuncDeclarationNode) + def visit(self, node: FuncDeclarationNode, scope: Scope): + self.current_method = self.current_type.get_method(node.id) + + # Check method override + try: + method, method_owner = self.current_type.parent.get_method(node.id, get_owner=True) + if method != self.current_method: + self.errors.append(err.WRONG_SIGNATURE % (node.pos, node.id, method_owner.name)) + except SemanticError: + pass + + scope.define_variable('self', self.current_type) + + for param_node in node.params: + self.visit(param_node, scope) + + try: + ret_type = self.context.get_type(node.return_type) if node.return_type != 'SELF_TYPE' else self.current_type + except SemanticError: + # this error is logged by type builder + # self.errors.append(err.UNDEFINED_TYPE % (node.pos, node.return_type)) + ret_type = ErrorType() + + expr_type = self.visit(node.body, scope) + if not expr_type.conforms_to(ret_type): + self.errors.append(err.INCOMPATIBLE_TYPES % (node.pos, expr_type.name, ret_type.name)) + + @visitor.when(ParamNode) + def visit(self, node: ParamNode, scope: Scope): + if not scope.is_local(node.id): + if node.type == 'SELF_TYPE': + type_ = ErrorType() + self.errors.append(err.SELF_TYPE_INVALID_PARAM_TYPE % (node.type_pos, )) + else: + try: + type_ = self.context.get_type(node.type) + except SemanticError: + # this error is logged by the type builder + # self.errors.append(err.UNDEFINED_TYPE % (node.type_pos, node.type)) + type_ = ErrorType() + scope.define_variable(node.id, type_) + else: + self.errors.append(err.LOCAL_ALREADY_DEFINED % (node.pos, node.id, self.current_method.name)) + + @visitor.when(BlockNode) + def visit(self, node: BlockNode, scope: Scope): + child_scope = scope.create_child() + ret_type = ErrorType() + for expr in node.expressions: + ret_type = self.visit(expr, child_scope) + return ret_type + + @visitor.when(LetNode) + def visit(self, node: LetNode, scope: Scope): + new_scope = scope + for declaration in node.declarations: + new_scope = new_scope.create_child() + self.visit(declaration, new_scope) + + return self.visit(node.expr, new_scope.create_child()) + + @visitor.when(LetDeclarationNode) + def visit(self, node: LetDeclarationNode, scope: Scope): + try: + type_ = self.context.get_type(node.type) if node.type != 'SELF_TYPE' else self.current_type + except SemanticError: + self.errors.append(err.UNDEFINED_TYPE % (node.type_pos, node.type)) + type_ = ErrorType() + + if node.id == 'self': + self.errors.append(err.SELF_INVALID_ID % (node.pos, )) + else: + scope.define_variable(node.id, type_) + + expr_type: Type = self.visit(node.expr, scope.create_child()) if node.expr is not None else None + if expr_type is not None and not expr_type.conforms_to(type_): + self.errors.append(err.INCOMPATIBLE_TYPES % (node.expr_pos, expr_type.name, type_.name)) + + return type_ + + @visitor.when(CaseNode) + def visit(self, node: CaseNode, scope: Scope): + self.visit(node.expr, scope.create_child()) + + case_types = [] + reported_types = [] + for case_node in node.cases: + type_ = case_node.type + if type_ in case_types: + if type_ not in reported_types: + self.errors.append(err.CASE_DUPLICATED_BRANCH % (case_node.type_pos, type_)) + reported_types.append(case_node.type) + else: + case_types.append(type_) + + types = [ + self.visit(case, scope.create_child()) + for case in node.cases + ] + + ret_type = types[0] + for type_ in types[1:]: + ret_type = ret_type.join(type_) + + return ret_type + + @visitor.when(CaseBranchNode) + def visit(self, node: CaseBranchNode, scope: Scope): + try: + id_type = self.context.get_type(node.type) + except SemanticError: + self.errors.append(err.UNDEFINED_TYPE % (node.type_pos, node.type)) + id_type = ErrorType() + + if node.id == 'self': + self.errors.append(err.SELF_INVALID_ID % (node.pos, )) + else: + scope.define_variable(node.id, id_type) + + return self.visit(node.expr, scope) + + @visitor.when(AssignNode) + def visit(self, node: AssignNode, scope: Scope): + if node.id == 'self': + self.errors.append(err.SELF_IS_READONLY % (node.pos, )) + + var = scope.find_variable(node.id) + expr_type = self.visit(node.expr, scope.create_child()) + if var is None: + self.errors.append(err.VARIABLE_NOT_DEFINED % (node.pos, node.id, self.current_method.name)) + else: + if not expr_type.conforms_to(var.type): + self.errors.append(err.INCOMPATIBLE_TYPES % (expr_type.name, var.type.name)) + return expr_type + + @visitor.when(ConditionalNode) + def visit(self, node: ConditionalNode, scope: Scope): + if_type = self.visit(node.if_expr, scope) + then_type = self.visit(node.then_expr, scope) + esle_type = self.visit(node.else_expr, scope) + + if if_type != self.context.get_type('Bool'): + self.errors.append(err.INCOMPATIBLE_TYPES % (node.pos, if_type.name, 'Bool')) + + return then_type.join(esle_type) + + @visitor.when(WhileNode) + def visit(self, node: WhileNode, scope: Scope): + cond_type = self.visit(node.condition, scope) + if cond_type != self.context.get_type('Bool'): + self.errors.append(err.INCOMPATIBLE_TYPES % (node.pos, cond_type.name, 'Bool')) + + self.visit(node.body, scope.create_child()) + + return self.context.get_type('Object') + + @visitor.when(CallNode) + def visit(self, node: CallNode, scope: Scope): + if node.obj is None: + obj_type = self.current_type + else: + obj_type = self.visit(node.obj, scope) + + if node.type is not None: + try: + anc_type = self.context.get_type(node.type) + except SemanticError: + anc_type = ErrorType() + self.errors.append(err.UNDEFINED_TYPE % (node.parent_pos, node.type)) + if not obj_type.conforms_to(anc_type): + self.errors.append(err.INVALID_ANCESTOR % (node.pos, obj_type.name, anc_type.name)) + else: + anc_type = obj_type + + try: + method = anc_type.get_method(node.id) + except SemanticError: + self.errors.append(err.UNDEFINED_METHOD % (node.pos, node.id, anc_type.name)) + for arg in node.args: + self.visit(arg, scope) + return ErrorType() + + if len(node.args) != len(method.param_names): + self.errors.append(err.WRONG_SIGNATURE % (node.pos, method.name, obj_type.name)) + else: + for i, arg in enumerate(node.args): + arg_type = self.visit(arg, scope) + if not arg_type.conforms_to(method.param_types[i]): + self.errors.append(err.INCOMPATIBLE_TYPES % (arg.pos, arg_type.name, method.param_types[i].name)) + + return method.return_type if method.return_type.name != 'SELF_TYPE' else anc_type + + @visitor.when(VariableNode) + def visit(self, node: VariableNode, scope: Scope): + var = scope.find_variable(node.lex) + if var is None: + self.errors.append(err.VARIABLE_NOT_DEFINED % (node.pos, node.lex, self.current_method.name)) + return ErrorType() + return var.type + + @visitor.when(InstantiateNode) + def visit(self, node: InstantiateNode, _: Scope): + try: + return self.context.get_type(node.lex) if node.lex != 'SELF_TYPE' else self.current_type + except SemanticError: + self.errors.append(err.UNDEFINED_TYPE % (node.pos, node.lex)) + return ErrorType() + + @visitor.when(IntegerNode) + def visit(self, _: IntegerNode, __: Scope): + return self.context.get_type('Int') + + @visitor.when(StringNode) + def visit(self, _: StringNode, __: Scope): + return self.context.get_type('String') + + @visitor.when(BooleanNode) + def visit(self, _: BooleanNode, __: Scope): + return self.context.get_type('Bool') + + @visitor.when(PlusNode) + def visit(self, node: PlusNode, scope: Scope): + ret_type = self.context.get_type('Int') + return self._check_binary_node(node, scope, '+', ret_type) + + @visitor.when(MinusNode) + def visit(self, node: MinusNode, scope: Scope): + ret_type = self.context.get_type('Int') + return self._check_binary_node(node, scope, '-', ret_type) + + @visitor.when(StarNode) + def visit(self, node: StarNode, scope: Scope): + ret_type = self.context.get_type('Int') + return self._check_binary_node(node, scope, '*', ret_type) + + @visitor.when(DivNode) + def visit(self, node: DivNode, scope: Scope): + ret_type = self.context.get_type('Int') + return self._check_binary_node(node, scope, '/', ret_type) + + @visitor.when(LessThanNode) + def visit(self, node: LessThanNode, scope: Scope): + ret_type = self.context.get_type('Bool') + return self._check_binary_node(node, scope, '<', ret_type) + + @visitor.when(LessEqualNode) + def visit(self, node: LessEqualNode, scope: Scope): + ret_type = self.context.get_type('Bool') + return self._check_binary_node(node, scope, '<=', ret_type) + + @visitor.when(EqualNode) + def visit(self, node: EqualNode, scope: Scope): + left_type = self.visit(node.left, scope) + right_type = self.visit(node.right, scope) + basic_types = ('Int', 'String', 'Bool') + + if left_type.name in basic_types or right_type.name in basic_types: + if left_type.name != right_type.name: + self.errors.append(err.INCOMPATIBLE_TYPES % (node.pos, left_type.name, right_type.name)) + + return self.context.get_type('Bool') + + @visitor.when(IsVoidNode) + def visit(self, node: IsVoidNode, scope: Scope): + self.visit(node.expr, scope) + return self.context.get_type('Bool') + + @visitor.when(NegationNode) + def visit(self, node: NegationNode, scope: Scope): + return self._check_unary_node(node, scope, 'not', self.context.get_type('Bool')) + + @visitor.when(ComplementNode) + def visit(self, node: ComplementNode, scope: Scope): + return self._check_unary_node(node, scope, '~', self.context.get_type('Int')) + + def _check_binary_node(self, node: BinaryNode, scope: Scope, oper: str, ret_type: Type): + int_type = self.context.get_type('Int') + left_type = self.visit(node.left, scope) + right_type = self.visit(node.right, scope) + if left_type == right_type == int_type: + return ret_type + else: + self.errors.append(err.INVALID_BINARY_OPERATOR % (node.pos, oper, left_type.name, right_type.name)) + return ErrorType() + + def _check_unary_node(self, node: UnaryNode, scope: Scope, oper: str, expected_type: Type): + type_ = self.visit(node.expr, scope) + if type_ == expected_type: + return type_ + else: + self.errors.append(err.INVALID_UNARY_OPERATOR % (node.pos, oper, type_.name)) + return ErrorType() diff --git a/src/coolcmp/semantics/collector.py b/src/coolcmp/semantics/collector.py new file mode 100644 index 000000000..9eb3e5bbd --- /dev/null +++ b/src/coolcmp/semantics/collector.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from coolcmp import visitor, errors as err +from coolcmp.ast import ProgramNode, ClassDeclarationNode +from coolcmp.semantic import Context, SemanticError + + +class TypeCollector(object): + def __init__(self): + self.context: Context | None = None + self.errors: list[str] = [] + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode): + self.context = Context() + + # Default types definition + self.context.create_type('') + self_ = self.context.create_type('SELF_TYPE') + object_ = self.context.create_type('Object') + io = self.context.create_type('IO') + string = self.context.create_type('String') + int_ = self.context.create_type('Int') + bool_ = self.context.create_type('Bool') + + # Default types inheritance + io.set_parent(object_) + string.set_parent(object_) + int_.set_parent(object_) + bool_.set_parent(object_) + + # Default types methods + object_.define_method('abort', [], [], object_) + object_.define_method('type_name', [], [], string) + object_.define_method('copy', [], [], self_) + + io.define_method('out_string', ['x'], [string], self_) + io.define_method('out_int', ['x'], [int_], self_) + io.define_method('in_string', [], [], string) + io.define_method('in_int', [], [], int_) + + string.define_method('length', [], [], int_) + string.define_method('concat', ['s'], [string], string) + string.define_method('substr', ['i', 'l'], [int_, int_], string) + + for declaration in node.declarations: + self.visit(declaration) + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode): + try: + self.context.create_type(node.id) + except SemanticError: + self.errors.append(err.TYPE_ALREADY_DEFINED % (node.pos, node.id)) diff --git a/src/coolcmp/semantics/consistence.py b/src/coolcmp/semantics/consistence.py new file mode 100644 index 000000000..5c531ff94 --- /dev/null +++ b/src/coolcmp/semantics/consistence.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from coolcmp import visitor, errors as err +from coolcmp.ast import ProgramNode, ClassDeclarationNode +from coolcmp.semantic import Context, ErrorType, SemanticError + + +class TypeConsistence: + """ + Checks for cyclic inheritance. + """ + + def __init__(self, context: Context, errors: list[str]): + self.context = context + self.errors = errors + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode): + for declaration in reversed(node.declarations): # TODO: reversed to match tests + self.visit(declaration) + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode): + type_ = self.context.get_type(node.id) + try: + type_.get_ancestors(node.id) + except SemanticError: + self.errors.append(err.CYCLIC_INHERITANCE % (node.parent_pos, node.parent)) + type_.parent = None + type_.set_parent(ErrorType()) diff --git a/src/coolcmp/utils.py b/src/coolcmp/utils.py index 14920da35..8ceb00169 100644 --- a/src/coolcmp/utils.py +++ b/src/coolcmp/utils.py @@ -5,8 +5,7 @@ def find_column(input_text: str, token_lexpos: int) -> int: """ - Used for compute column in case of error. + Used for compute column of tokens. Assumed that tabs have length 4. """ line_start = input_text.rfind('\n', 0, token_lexpos) + 1 - - return (token_lexpos - line_start) + 1 + return (token_lexpos - line_start) + input_text.count('\t', line_start, token_lexpos) * 3 + 1 diff --git a/src/coolcmp/visitor.py b/src/coolcmp/visitor.py new file mode 100644 index 000000000..964842836 --- /dev/null +++ b/src/coolcmp/visitor.py @@ -0,0 +1,80 @@ +# The MIT License (MIT) +# +# Copyright (c) 2013 Curtis Schlak +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import inspect + +__all__ = ['on', 'when'] + +def on(param_name): + def f(fn): + dispatcher = Dispatcher(param_name, fn) + return dispatcher + return f + + +def when(param_type): + def f(fn): + frame = inspect.currentframe().f_back + func_name = fn.func_name if 'func_name' in dir(fn) else fn.__name__ + dispatcher = frame.f_locals[func_name] + if not isinstance(dispatcher, Dispatcher): + dispatcher = dispatcher.dispatcher + dispatcher.add_target(param_type, fn) + def ff(*args, **kw): + return dispatcher(*args, **kw) + ff.dispatcher = dispatcher + return ff + return f + + +class Dispatcher(object): + def __init__(self, param_name, fn): + frame = inspect.currentframe().f_back.f_back + top_level = frame.f_locals == frame.f_globals + self.param_index = self.__argspec(fn).args.index(param_name) + self.param_name = param_name + self.targets = {} + + def __call__(self, *args, **kw): + typ = args[self.param_index].__class__ + d = self.targets.get(typ) + if d is not None: + return d(*args, **kw) + else: + issub = issubclass + t = self.targets + ks = t.keys() + ans = [t[k](*args, **kw) for k in ks if issub(typ, k)] + if len(ans) == 1: + return ans.pop() + return ans + + def add_target(self, typ, target): + self.targets[typ] = target + + @staticmethod + def __argspec(fn): + # Support for Python 3 type hints requires inspect.getfullargspec + if hasattr(inspect, 'getfullargspec'): + return inspect.getfullargspec(fn) + else: + return inspect.getargspec(fn) diff --git a/src/makefile b/src/makefile index 30df993f5..8d37b296b 100644 --- a/src/makefile +++ b/src/makefile @@ -10,3 +10,5 @@ clean: test: pytest ../tests -v --tb=short -m=${TAG} +compile: + python3 coolc.py ../tests/$(s).cl $(p) From 0f68946d2692bd029819cb05f95be1a3d7291f3b Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Thu, 17 Feb 2022 10:13:01 -0500 Subject: [PATCH 008/161] Added SPIM manual --- doc/SPIM_Manual.pdf | Bin 0 -> 518753 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/SPIM_Manual.pdf diff --git a/doc/SPIM_Manual.pdf b/doc/SPIM_Manual.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b785613c8afa1a36aceadee59326205abd729b94 GIT binary patch literal 518753 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

|vsGhftq|S3x<-Cq#3)oR}!ZJIb#knC} zukx5vCHO#F&q9g#juHov7ULvMvG#E1z<<1$^0F`1E+H<0#lS=5dz%+8BGm?@O&qqc zjbDi)(&!v1NEeAP$X ziaAZQES)l5Y&zC$Yl}FoHwkQsXhx0+%sJS_e|-S6JccZrB*}NvXZ#KV76M{a>l`-R zN2YrJ)ak=%d*pheG~SU-fee543U;dVTX<%5PA3<)d1G%{s_8T*S{msoe}-A>GR%m) zpx_FIv+~Pa%~r)ior^0T`3zHp6~d8vu^p95I-U~0l+{lu$&_i)?=KDo+})lr$gO=! z<2EA36NuvEOhfX2pDb%8w=?Ra|5ju(hOit-v!X~y*h`Bk>SGVncPMU^(`nzwW)C5= z_y+9!cZRdTwENKzazCL58L2vz@){!aktAIl;4&&aximkh>5-lmTs;t`UX8LJYDhK!E9~iVe%{6A!z^(nZ&jUEzsx=5AUT2R_>gvz|0m^5S_ke|yN;1F<9NR~=7a zWe*Y{(u{Nncx~i+yooDYZAi3Q(2^d6FXPnnQtsoO}w{p(C3Vj!ffn>qPZ;>fZ&Gh~=2|^_cw)luC zw|u^TsNu`Lx5AI*MXOS1##hvhe*7nwO!GPBOVa&<$J8gSlj~_Ft_EGX5@sdd(Ugz! zOK+SGu(_PFOc+MVhU9U=6->^DC5>7?LKUx!7KBCn@z9ORV-P)`7VUl}oXR)ao5#b{ zxku@ZoNRC&CHEp|`WXb#L;Vdjlb=7(^s&)Q@bu&U>j##;3Yy7}9~<;-DPjqyaP|-N z2g&1S^XdsFSi)=~R8d?hh;M>+$IgH*#+vo_3g#1ylnr1) zBh$Qs?u$&i?b2WJ2Jh$U!YsO+SId@gaKqwakfc$daY$Zr!kE9-C~aZzBbUi<6Gm)* zf4;2p3a!(8U61tHM9*7yT}?IY098Ca4Gcpn^D#si5&q@?FS1oMb4>rDMBECeL(*L# ze}C*ZQ?%w?-9^lf5=HZ`d+GpyFf|k%;YrBm2=297pn5B`Zl+IBTm9Ekz+&wT;bv&ENAqcyX z8oDt}nvLPv-?i!~K+@iB_$D9!S)M`z!CfbY4;~X*O^O}O@$>f(x;khm$ z?8;CQp1grao{ViHF*4W&F7Fr|GHo(&Xm=1D7^6l}7h%d#K3t^IQ2i{)1-1agiY{|J9S(3tCsocgpL+Bm#0KO*)hn6rXBN%J zx}N!qN5Y5RH}zMcU{7<9WHm`zuQC?D+>TbGv%>f_iX+-I;M2;%w_hz!D8G@*4(`@# z5oJY<7}DIF&NxGRBav41Sy#;}SNKt}jfxTM6Ji$Ek`I}!%r&0Af;hUc|)=S*sP zq*EEqNVuN^&6_<$^*bELHLub|wew$vFfwC2a*l^w8X}{}8){gzT@ z$<9C{t*j-MCgMn=)ni#KOmYWn>jk8)vbe-^Z&fPKsbiwfw^ip?hN7?Bd89-M8)s5ujx+BKrw``;z zmvvz9T|u?Kg*bx-gDz?Vm^QFX03I21(NX{ySS0{~IRHH_K?)S$5M*L)9#vEI4c_h1<7of`(MLV>4!hn)yO?mzD~%U#n3 zjzEuXpWS$j4{bvA0Y3(frn)^~lnNk|UT zZu@o_>ks7IyPtBE)SKRxj7Nag+Sp{k3(XBo<-IlmgIE$fDMWZ?%RHYao7U7X3y1{h zmW{IcFf;H1P5+3Ij4y7S`U$3dfx!p54Oa2pFQ)N#h2#q@-Fv+FTCx}%hp1e z54Vp`Sud--!q-H!^E#gd>P?dXh>@A50(b62~%(LcL( zJGb@s@JJkjy2Kj1eU6*LgErWMad|>O-s{#SDZbQ8ZzR6TM4~qs|7|JQP2lyrArFn^ zU>NWlJ9#>N^6YZ3-6KKquxkeKL&Flz33*O+zVQ3A>DX3%rOwT-6`<3U5Y}7IzgqNB`K?@Uf)A4n-V;so8}b_9p4I@~GAE zb)mGRql19^QDUqkWE$4R%wu%0fYH=Tb1+XaHLgb1Pz;h>WHcDa{Q35Fp9C*+Aq!CItH&l6NX#TAR**Dk^fW>q9_#7{UgCl`1Jrce~c zTZ8=4BH8`=7?%xDxMwn3ZnI=4cXtHVfVU>QyT;q+=<=cq(4F*PjsI|^R7kG|; zFAM2SddDAB)PehI3lZg@ufUg#+r9`dq9S!D+AM< z6tV=t&BlY;F8UZYv|lL0nCCDXqvjwbmS<@3Aq2eH-e#K@8Tj(2BbM!w2kKI8bTvne zb;j}b4@JZJXJoo@=Ho>$kx%HdgUPtJt?1#Gxl1V4Gw}8FJq}44SeOTDM0u8;tyWqE zH^mi>4eZ#llDyGqLXY8c7UFi=A;|lDG@?`K>)qC%oysvhPi7r}5zy+V-lgM>f`bX` zBi0X#^;$(i&^V;ku&UcayWfXlRo8K0C7UFyR+9$uwR8xIlBmmi@V@7;OL(#vt^G2 zUito8An`;JahDccF)&oDF+-Ku<>PGBH_^zj8(;-z*WKR#x;RAMj}(Q!PXL zs_6EGN`N%IxWLpc-#J*l3i46GQ+0MjaEN3AT#1Ym8W*;8e8)}=qAHW%Lpy!X)}yN= zs`>M`?hN>pN=~Xi(E|w1Wn(*$d;@dnBVNs5}wI@;_l0a7{J%D?<;G(Ut4ZPLpR_Lt@L8O;l15nXIJ_g zvqaVPwh(LQ7q2`yOXa);BE4k?!ZcFqshn7ow#0dFhna#bYoQftdMc z`W;#IYc=eRP`O)Pu^$IZT)0&BN`e#ce6dR?7L3TN630Tl2_C#w(yCrNM|1#ZC4a`TF(AG&yCVzSaP=AAu=)q_fvtQ~L z-i-LD@6nbaKegBxf^7khzL+CCUZ27HfbLWt%3B^Q2yuhQ*;=ZmqdaY1m&M3J8 zo3&FjRHnMHEo*nhLOX_`GU2N2(e5AP+19yatS0sa28X`2@vLq;uk8V6Zlz~w@^T*) z^zNCi#bsLr2d6xB>E=+lY*{}M@&uZOAX0^suV!3wlx%of8Z4r46t@c}-W<}xVya

=FH+i0=Y1?WOAY#8S}0y+Mx%+qU%^EGEvFB5#4xTj|6+Y@h!&-u8*`@HHUrysXTe+kavN!Rbc&bbOg z{6vPGQce8%LD0v1Emn;Pnb}43PaH$>WkQ(h1sRYpLJ==lU;apy{Z7Lc4_bdS1B#da z(?$4yK!5*?mi-}X{@+M$W}Sd5zaqDXYVNW3;0c7bpQely4WuO4J;2p zph1`W1>Dhr6(|Ta=oX>?G_XSy7=1_ST>u(bgMvWcVJ~0?eE#P>XTM8G0t!3d0}Z-= z4q)cL@)Pv)2J}@2t*U@q*??dR8$bbpRI$KWW}wi3^?^p;hij~$i}(W8eIKr|g3j~? zD6;!-?SBxXzdmq)B>YQ5|F=ICV72?jp9)aUKSunIBK(=I2BZi9iGcqk!hg!|aB_10 zFvOWzI2hZJFp68~I~a=^8(JF~KS21Qa_t@LjP)%aAUu7$r}_*~eh0OQ;K(yU1mzQ% ziKM)&Ub#?^z;GwF8c9SA1}w{CG)=D%O449;RK}Shwpi;5?F|snjQ8#F?D`RB1g4 zXqPI~OaOM;#rU53_~O*=SZ|&PnQW0taOuFLlO|^}HHK_9dxge&xs>92uAt~8_9qGO z3s27*s0&Gz9C>v@>!zabS_})t${$hs{2zF(MCy94cn`JExcLMyVpjD*`$EMbD#B1^ z9D-A=jHaK8)oIN!;#jkR^_!u#z_P)7jlaCI4L}>LEfK{T+-Z}fSf#jx|G>9%rQ3`m zjU?^}cVk!|!sJ#>kR`s_TBmwe)`DL5JS1xEbv~lo?E-5CAG&LQx882`tF;b!Kjt0w z?*SA6(ng)fVyp40W_5I(%+F5pF(YC+8u(R<2lC#}H|PaEsHgOsYoij>{XmQ!exj4U z9TiZ?1E)MZ$-@W!dao|y^3WGrR$c?XfG$p%5Ro>Tx*sJvMs5!Yfir|Tm~FHF|6%Se zpt9Q5zHeHPltxh+6p*}00qGKu?hZku8w8Y=kQSt*l$H+Z25As52oXiPr9-}(z0Y&b z+1_gnm*4li@A$?z!=roepL4Fc{#pk8+m0r(K9=Z;MXVX|@iJ1xnln*7f4p3b zOnzu>$yf@V3o4<*r0uW<$uEbFD7@;RlDXHCFzuhS=N3GXDU-Jm zVLDQ}w9Vi<3F8p>u&7YpaHEjyvKwE2#XDNngV%Uc{Te6JCMt_{5 zaP64LXu+HBdLpgc5%V>+0Yg~?sg?I-P1)nix?8B77lu50L>X^)dg`Nw@cXix-Vz(* zLuw##yj-(^hTuHzn@#krLfemRr_cKImw>6#wQqk-ob0H4qdq|B8EBixWT1VP zW2(x>A(nkFQ(PytWeF*GAnJ=UrAEm!T{jEM@zDEU?+cGSH)Au+&GMO_3MV=d>D0)+ zANb`q!=*9HHKNehAv)h(DV2m$O_+9=s`kf!IwWvL%7kv9H@fG?toDt$I!HOLF_y_45NLBikOiYn36joj_M(K?qTvVYjS&kdVJH_QJKYA%yX~pHklDly~3!xSHQBAn(+b;OKLq>Z96-=w8x<(ynUVHYvKFURh@G!goE zD1kU_+I|W7MQiV#_g6vf{H8d?jG)Y#AJvJwGa|EN4(`UP$}!;!LwwD>^8`jI7vbN{o~dk(N6uH4OAD&B5X>M$EB2HmSh2?nX53PYr@<(bjsAJ)Yh)+ng; z`*x@i11VF_re*BH-n1m_I_BSYFS;!xBcs9OcZ}vESFh`Rc_$e4^nO4D@pv_P7PT+s z&Ny9ny|})O6n6NDh5kWhoqiB@`Y`E)x$grjmDMPfg#48Unl#SJjOl2}qaPJ^i{%wR zs9h4ifhQ!jv`opx=%$epr#fBXg?=1qQPkbbbkv!l`AR3>0(tO~FkQtqK_|++2)(Ct zS4~Z(Urju&VSeOv|4p85ZNrx#?T2DTCArJ+%3$X2we_z)9kTsEUYxwpP45d6|Tb=*@ zivDkH*#AG2vw?3n5I1Rmr;1UtYwPI#wLPK+-IM>?e+|B)M}&dfXTnEDJPshjpt~f* z9U2163om^GxV=M6*+!f;5B#1S5Jt?|27tk~hVZ1@|5m{HmKb5$U?ISqh}}*AVED5E zPy3(R100zfq<#q}yza$sr_BxA`V;O!cY_j2LGkSe>xoz9OaiVpVjef*YJ!Xn~!^%@~ z7r?=_U`JhEB2Cmlx};+7I{ zXFcTZy9eQz?}y`^4`sCSjBPHd-H@C}Tgt~6BrVJedW2VC9bWyGhnQrs-7D>ZLBqWA zyv1~bKnk{6t8$w^;XNKCNJr*+B(%G%PN9)VdcN zgS5tpcg5Nbd0qz}bp|KpYx)iHSr}GrQckfb(4&Wzvh7$KpqQ#pvx)`cMt3ZUPCTQ2 zJ`*Gn&qzPJUHxgk*ualAk=N&RUel&kK(13@+OMG8ouF5?m;MQ?aXq$ag)rRI-+1-T zv4YUhlf>nN04}CZp@1hJv4qg63SHAo?zeJvEXdak*tA+XXxB&?Ea5dJ&NtH19KEJ+ zl|5Cr!E3^f4pM#lXw|(S4`#DMcipn6w2RRG;zitAN5&zXJd&a?c^-S^tGybZ`@deN z9!&b)te>36x2y8HrnJ~Sq`b{YO_2_qK#B-56^pF>-!LH^8n%0-Bq zgzM+QkO2WNw(ZaC-5dNdINxXm-KAy8zYkVb^VVXFla#7j6hB1g5`MXVSXaspqpU&K zoE$@qzOb@;RJzZaM;U&EvppumQ9NoPFWgo#A7Amr;r>SnecKVQM-R2XJP${wdQ$hZ zqtb1bW?tC*>awk|f`~JzlQq$(%tYHloOB?k`ZV0fQ}_55f7bF~*aH?^i`SEv92^Nx zaz*A#ireQiR=950_`TZ4J~lute<6i?AuXV{yiF+Iz*0{_4?ibl#V^g;wuMfstCL~^ zyO!@c3A=RPo%Q&n&~{IK7Blm*lJI5PUbT(1viR2Jf$z6qOQd)W1&(D@yZMt3)0Q~v zU7y%7*1}_d4QFOFXSoAva#jdk+-nGNtZ%7YepVl;1iWfr{BmQ<*ijgWR@&y zAi{xitK|}_b@NbR(E;+}N!<-i^0orInrfM@Ppk?-{dqMyt7>FsaJN^TuyIGiQVrYb zr!tm+`wy}`N`6RaMvZS6%o~OGYxJbTyfA{zFWmmVr*oT*68-7}tC_t+%w~z}bMjt!moG=bU^X*rS>DrUwp%z- zmg1qTkcZIhjv%Ra=7h_J_;!n1>z1Td|4LYB%j>~K=SO9MaU78tx{j7_ddvqe1Pdj2 zaG-At(uNhpY#)pJeC|kiBOIr#U^+E+RGxeD*7_Ho7n`!#ms#$!6kqDhRYI+&I*?g) z$=o_V+a{W%-BET58L({Zi%30OVZ-$D3r8ijJT7nH+?joWz-_G zt0&H3iewqA>U?ENE?eJ;{^~fH&W-CXc1l2F>%p#c^Rq%Pt)f=v{z(R=9rwmad3XLn z+&KDHRKpk5j5@A5$-%-4`*xqRq6x$JP%FL~_35c3@S<|YG?g$&5`QS-5Tj@?OtNix z?UaW3tY$;~qXVViNR3;aT5aY%WU*ILXqSh`iji;@?)b+xvfR`BV%4laG2kbbDLzYd zu(pipk?)?BcJsLav!&4qnV%f*CO z-WhJgg>T3zYibMv#aTn%F@e9QqrM;a>y4T>lmlOj zcCMLc++KCN-9wx$SA5Idt~Tg9+PJ{@EXky@^z{!$D{3DY{jzU5n-$rh*6~)5aiM5$ zDH?u%I#J6qaG$Q+PsI25gk(ZmW5Q-S1LFgIR8sk#=eAe>_sWJhj}BhZF_PI9 zd&EVh(op+PIFB$LC-1S!5%)Zpb?X#2<#1>I9$YrzsvfbLF1_u)=`h}-&vQ4ZzNSV_ zT2j(a)%Srtiygb2tWj#O-{uf~1Ovajv{L}{bijpfblI1yiBFw`s3PZlFJDeI^EBUH zk@gfnkX_F$Yh_?Vztnth$5XoN-VoiL%k8p5(*^c8IgRTN$^~HjSCb>B?T7NUqqUXC z+>!+qJWR2-RFl6}RVHy>vhpTi?FvCB5qt20_D3LolIIh%hcCKlg8SPd<+aHwC#@aM zLSL~zE5-03c%PAala?9P?`+Cw+dpPd;K@Kkhro#92jrmmkgFe;?rjejEm3;y_)b)8 zskYJYPC5^#ltz-RZAIP5-ogH|tKM=chlr6*&uz-g4Eg&KdWGJt)lI2oB@X?AsnE(> zt!5AXM_oMHnitcaNDH2Xf5sAcPtz^f<-1u%b?HjySEC)%)-U}gWAA+UjQY*aSS~(k zbw5VAE2PA9MunuK5b+V@0_m3|;Qy@)B+%-}@Q7=E_#W}E?BV}cE?K^ zWaoK4CHx#9!2kQ>!e5*H{~QzH$A!O7iU0N`!e8${eTjdcF#hMT@K+8qf-muxjy?Z6 zEO7I2b3pYaDmudt3yt-?XUH-S``W&yNLD*NAG@cTl&`*KS6LOZMY$vPsaH8mTvapH zs2P`lAcdHyH-GmYp4-l=AW!sOrzu9rR2#^dNbUaYiXvGe8Id%_sJYjh2Kc9Ap~n8_X~~Bx5 zJGtmmK1CXyiJW$J%1f{o-)FuVnDSHDIRCL!&OU|b^Y6z+w4Gt*6`jJh&)%6I#`gC< zcwuPqvW^{3PnFEvK#P7zlV`UgRZnN!TxITf;Qd35mv*!7^|;bCC}xW?xv^LoQ@3Fw zFR+zW7?qQ36Mem&-Mr{wvw0a!LQ`tVh_s%rak8+Mj=naVv45>9e9?RPxXQ8#XN+>% zH7J)YY=CWOne|Mez@S?D;X{efa-TNSes=yK;@V_!(-A2A(3xPKPWq{MFEt{-meAKx zc%+ncHQ3?c!52QinVsM_9dCBCG}r==)UKi%qC9h|_E2uaanLr{J})VF7ZDehWp(7WF9I}D zOFjt(>1f9=&!7n_ZkEFCm#@gjg;b_1n{aYT;`Z}B5<@k|+U_>{TM#m%E%Z%QUO9U^ zDKjDz>}}Cs({t$=4!^@giR2>(2Hg^>4BZle1+Dl7;Q|k$WLizecu&3IJF(w2+#XwR zb7YFp=ucg1Du|m`*G&_VXJSQjFngi9=-qPXs^MM!ggw2&HLO(W#TAV*_LXDbGZw9T zt9wWGbE*A-IjJk%8c6q+)%TNZ>w9ZDP(LYMxc0u~Vd|A!M$Wxk$#`Xj%AN-pmvL{Y zVDX6etuLp(>NnffHgJAemTljRDt?(Ib?YS#8VgmeJZhcZx%-y2HJR z`lev#q&Pmj4YJK6q#t+=)-mpLH8T`Cb%_s96ltn%<-5w`Uat=g9y@aM_1yYQgQVb` z^#J=B=}Eui$2UGWN?4LA@|V7tS|!V?-b*AqqtPQHRIR|0s}0Zm5}iUHy0t;`>XD&g zgEbowFMZO%q@*}&kwrmh8R?+z(gu;10}@}Dtm~Lgw@-i1itGKsmzlwiev|&Y1-rFkzM=sRs54jd^{yS@$i7Hi7^)VE;EE|&*gcFZ z#;;>gc=<9Re{7oQr62pWG#$}tS*Mfpye)^_egRm{xr z`ERUl_h>wlAXL6BUcV;m+*c;uxlUT?Nz=c!+p{c#X*&{Z!`tCBvZra~>>u!m&Tj~i_Tp#(OW*NWnfkvP+vZs|3W2ouyC#J8F62qGx zM$UmeORXbMPX7<(*@b-H&;goLSP%uxb9%Yh)<;g5gJvZ%Dt1 z@8>8Np?LJgcU#ZSqf?0xa`+um_?N{5iC(#LHC!wFOoCEQ4I}j1>0_5i4JjGouj=aU z#||4Pt*uL`-fsnEVQH27Y*kUzTh*wCTCuOp?W3hi?WOO1K9^L`b%OvM!nv3oN0MdHUcjY`y!ED|q{=ON^zZXrYR^Ywuy`<3h)q z{8grvsjnC5>^BC#+QC-I|HHuYSukG^s&u`N$eJUWfmYQm59&^}Vp_bn1XnbS+VAzj(9a@L+uQq51W$!?tyyrbq53HJ_#hI(_S2yRZ;8@zA3x zct7%%RMb4&dJ+7psJC@RPh&8PV&+lH@hrpwYvS=vxR0!13$@= zD|DtyLKtn9jq#0YJ^iFz$y+HEN6q*odOieb8_(2?W(M90I3*yhJSHH1eu=Qs&hoM0 zKy{y+GWC|oY-=?+sS|$`bIwRfWvJ{8HLTT?8{Tcw@w;K(H0*M|&bkk;ejH7fNvCD+ z;qrFE@@G1YYAKwT2pJ)6dh;abeaA*I!DG*m52J?fnhg!FZ%2y8g~r1zia*B{by?UA zZV$^((HeQkg@JLb+Cd{Qa*9o~kT^1-DhFSj{>OI}lSOy0zwnn5m0_ZW+v3lBGo>Ld zA7t-L=gwjTW8mG7qg3?#mPujtmU!<;PJ^HGWXKQocRr5fs}zb~4htM(6?SH`ZlJ|8 zW|Cr>MH%;|yi{Cnu)$$O^Deb^g_*EF*2H~3!ty*iF*+xTMA0+5S#9)g;1JbjY;=N} zNOE*y%JY`>kcKQQGl2qe_fr2G`xZz+47V=5c9rr+#bBZ=b!^+BB@82ro^VhOH+)2_ zwA3My;i25^i?aQWS`)<`bCsl&pzukMl@#k7Nm;4ZoW(oYpKn>7qa?h$vG>j{D?6+K zC4q1vL`-`mu+doY$gnl=BVEbcYY)*`2QK?!vLq9bZii`qWHD`Z+S9xapHB>Jg7Z!o zBV}2qptPiRE4GOgd*Jtpad-Lbhc+Lq!j2_2*s^AGXc#!(8~6(&7YEIJ%?f?+@mp;W z=LtT|WZAYFnVmdr>7KigIJv!j_DSNJ^@pl&!G4X%XYDuEXs70ieS=Ah8?!=-#QDahAmyxj(wE-+L8z9V9g4Fq z6sIFm2?Z4w*fX`Bu#LAnTr7LRQ#)a5f6?;EJFUN&7%US>_nQT!l-|MqPYU0L+H{3A zcC!#RNBQv$mM`H=1iq>xG^9SQddE$riAtuHNSn&$Z&=VtleZR@cgOR(q=&#>J*Cy_ z6lyneBmbRPq$K-TB=rXRqZN9^(L3c&dOi@0xk-(^5+WAY7R0SDinTBQXp(y8lf|>6 zXpITB!X3r;utwJK=c%}2Ye&%*Ld3MUbZl13x7nC)$fgKq{eeLI!>C1xpy2IoxNANO!UV(41_y_MJD>6ncimh zGifsxPMeM*G`~5(^JatXN!;;z0WEKCe0Jnl@nQ6>w>kvY^~}qvv==kstF1PR>HWwB zMW&-aipBnA_V;Rs0mD}VJfA2!%uA#anv_PD^e_2PWI%8t6>|G4t-+vG+o+vo`= zx^9F9+^Uy080tvtpqz=2rc=188(*iCkbkx6-dqZ5CstF}Sglx1Q6n`5OVyf&gPTjv%zi+`}z97=G zT$fO2espA!?Yzra)<*Oiseyj%ihp3?28Cs+`2e|)tL}tQtVM*!)M6fe5o~_6;y~l> zDQ0a?yiKo&Yg)N}t0@xm1CKj#S>@%DSUx5y7ZI}%>3sdOb4`FVPLeA|g~n0B9NYwgYqP(UkNfWgcz8Cv!1=#Bcdwowzop?}D)2x4%XN2tH}Oa5 z@tp8nY=o*&{%zdk_Z4a=&B(`a3IorARW>;5j(R1s0EDX0;q}r zeQYj(vIvMYFGvg=7h-=lfSH5SP_zb4BZhJO$;_c>4SZ}Ub`6|{V%NZFC|U!j{~4hH zY#n$(LN&Pn>LUQGIVcUqu7Qt@7%}uGV?)v!=*%H$4U~qYHIQ~0Tz_T8{pa!>kGN+9B9;UQim6)<9`US_7kja_ykc94OcB4`@hQ1D!b}t-&oX&{vQPDAx{g=Abkr zt%1^z>>4Ny$*zIYkhBI$L(v)!$QPIkDAx{Q&B19Xb`6|{qBU?Diq^nsD0U5;hN3kd z_}T*M93W=^(Vm0TP_zb4L(v*I4Ml4(keXp!0C@w5&kcj0eE?StAaUSNXee3(9~+9+ zz-cI21E-;A4F>ns1Fsqsy9Q1}(Hb}nMQh+RB&~s*OSk|s2msUqN<*=0Fi;wj)<9`U zS_7paX$_Qyq%}|)lGZ?JC|cu&*R=-J`ClpCzuPN<(@?YqN(1B)AZiVqhN3la8j9Ay zX((Fb2K$cv+6fNuu7?{QX#@Di{;IGaW!EnEZVsZTv|w?ao;GU!F5_OWSjUtdbScTPR$s$te8K7ap{bzfo+_adsZkEm9rB;rZD}t> zE34F*Wk@HN0%LfXo9al+eRc{v4Gz4YD~YJ{#aDbvQpAYAilm&_{8Y4raGEjt%ZpdO2%#-`=(^0_et2K4j6Ymd+X=5r`G$p7@$%`R zq>|Bt@>MDEJ)_}CFK40g3xsW6d$EP)W0{%C)F(Gda(jDT%qr7g3wPnaw*Kxg-7HpD zCDY)N2EPn%{R)b*{yWaI!XuB%IHEViUe%K$DTc6(UVC{z{E3V9Yu={Mb7s4bJE)I0 zc;1QjEoaByJKJ<5^=x^ouV#xI-5HMEy{8@O7uSxi@vbVSrb_5VjVE%{Hxl!R%dfl= zjz}nrsZ^8b_7*bX(=*i$S;N4@zsz1l>ho=w;UxHRU3CS7?tFN zcHMCzZ*|>rcgCh>*Seykk54J1(>}Xf8dDuc(sTGdhckD4lEl2N*zteD!nG!8tc(=t7Sgx}%zZ<32s5f57>iU5Q<4qlq97q;^Xfg60y?&Mk~h!wbkZEq^$$rF#(PUzf>EbR!%hi`lH4TYRIWCBb*&^l zE=gZ$Y?d8aygM`>wsLXP=;RKcG=^$n<$Zg-x))D=p3olD@ zdX-iAJ0g+Ov(Nn$?a6B&uS7qX>2A@#Ui%H}a9vJorlT&JI5C^ zjWR_0PN&ss+4GEPwQVVo6!Sku-AYHsctT;Wa3K&4N%AM^_M;qys;5-#`$$G++1+-? zrp#}?P^n0NCZN3cSSWg_13#8vUis|QdGew|&Ul``eXhAmy9%4XG+{>YBgTechamX> zFyeVLDoy0CVS8}K3Y z9<-p48dr{EYWq`vh%T;S|8{lcbH?!Sd)P(Ms#+Hn3BFxf6lL4aH!Bb5#d0z)UXhEw zzxWRRH^e)><%)csigSn1T-!xmn#Ggtglh(OfOQ7nza@2(r#-*e2+i-AL9-QX!uJx7 z@#r2ER8JOmUJ4gYyq<{I9|G6ba zoH;MNEEh11xIIFo&x^MM(}>$602-tlHrHQQcE6iB4@h}7E&x|I;@B{Fc02IQ5x3$1 zG)SK=E&x|IBFzoo_5zQMxD^MWLEjkSRveM$0~;H_)%_S|T;0%T&JA}R{2Gh`K*QZ_ zK&!?Lzp#Nq17F*61M~m@8XJs;q&1KdIouqOv<6B;(i$j@c*XE1&kady9AIw@lGeaz z09QA_%t2{LS_7paX$_QyV%K;{NQ0y8w4Ml68o<^4Cp09jfsPHyu7T1}w8jl`7UTvflZW`+;4~Difzwd5 z#truUhSw|ny>$Smp=b@9hGN&w?FyiA`P;McpTqyTYvk_`o_}Yl!w){cWU2p8V)}Ux zB<{bQ<-fW{xPPgm_dl)?7(c{{*KpU!Ulp&hhaY`WG3;QSp^dsxck?nV9BJ!C>@|J8 z&~aD#68i47=8b5t=c=sZhO8(Zi~^}i_VXzeN2jznpMB<@WOb^MlS_UX%Fs3mjeq|f zJAjnkM0A+@txuihVl7U9Kx-gJrFM{*i*TZK^}#iaiRtkt1vQDU=}&G4wo14(PBMHF zJoPKKbe6{qE$-v;Jgim>+rH={SMg{n(V|*7o9=cs&!t+3&_kHJ)6YdUt} zni@HW3o2tX9SR2i6GE5pYfXfec&K5j#2C0Z{)$Be{IsEaywxTSR2Um%_WEQvEF`x+ zP-WpSJ*ZO}Hgl0J#O*bTGv(D1@|qJG#E+_&p!1)<$a3{g<-z8>YEwzqdgF0k%3HOs z-jAauux_Bur`}@Hb(PvqP0&!KCfjhQ%C{QDAk+W)CC)JvYg8DW&8TuvONF;)M)#h$ ziI~QQby_{9&#`BYXLi|hE~BT(3mwKj6GGBPvELtjCEwZ*DS1aSfw`n^Flo=!bUJrHKFX{>Xa82qWtE8$5igybPnRi$Kip@$k$@vNsC3jI z*IkiE&MUcJ>}GPpQes1#NYKb6`gq$wRh#;LX!)F{&a09hjevq5ZzPY4{H#g|GwLK{ zIe4-#wAW)bj^Y^~+#xBP-TN^utV>#`sLN1jZA8yzy3(QfD0I6Y>-F|3iBnP=*Q4@V zgM(kAstrV^M8y)c58D|HY?p(PL`aTt_&BZVwVES>a?Ua%7@8`BZTYgQh3o9vpNjc& zW2)eA;-ET&tGQAVtE_rgh{h|GPpI%t_C{cbo+B`|5kg;$>Oi zs^l1W8L#oXum;}fBe-evoV*Gr_|qE`snT*zVH5s=f|GtyQC!9?v;$YwNIpgyY8UbJ zZIfhw{i{rJF7-2-OLoVj(~I~KiyK7>>D+l~WJXr0WD23z@R(Pu@7ubl+{hI`Zo$FX z&a8937X*8r^m#y#9v_z``H;6w>Um2?#N^c>bZ(s0E+(A);11r~{Xg2OOHjRGjp%D( z-i=08aoJ1v<4q{8l-SBNFAxQNz$GEha!9^QL>F}V+wySp^6luz0E;*gdvAh;NW5ZW zmln#a&UU75n;!@99NTJVIMO(mcqSu*n{8h-M&5eE`_pRKn5aK9(7cdVH@D<%)3sYD zD(_{*(NWn}oKBz0`I~gncB(%myrx)A!;#d=YDv$0PeGc2HSk{P^9R>7Dg<2IZKd95 zGSl8(EZ+^Q+EO2kR76_L9TX^~<``)x%b3HtrW+GTR+`}`Y005ct0B)r7ULE0gWM*? z7)vKhu{32BY4bGb?Q2rc8*Z7;+wCICJ~k_wCWa}}BqmNo}=rf`lR>Eu=-YW-vmXh z0f&hk9|j_q02{+$;7TYuEd-;TV zeY@VeBq~!eD~I!XGn2#);|1@P7r9^RNHA`$)e4v0Ig8g>IB~8iv(3C?5LLlON+|Dl zR8|VF0THA=Au!(AmYByENyy`JH|x_T{qojVxv?uR0y%x|rr-{ZV!VE~L|;_r)t;!F zo(_2XQlezM+8ToZyDXx&OLJ0HEO?PQXI| z58?&|fCh=J=7wiI{eEm-kfONU0P#_XWAnm~DZn#F+@Ju^VAnDL@ll904@hUq^G2J$ ze{LRlCtSdp|J4lgKYOF|gBRl1u>VvY{YMQWZh&&D05s3PYnJ{(L(v)xe(eN2b0}Kl z261I^{}mhayXWSH0lRd#;XQ7xfi&9siX@0#z1L+_$b6T22MlK z8ZW$kG2j~mDD{OnHaLxV@cI)Piq?3+EYmMhP{3;rPD8P4;4~Di@xZfEf!7?0*1%~f zb`6vUa5f{pF>o4+*1+k1et3c37&r|@Yv42#t%1`}>>3X|3;^(b1LRc$tT`wRNo$}q zB&~tckhBIy12~)i;JM+J`u7%@2h5cPkXH>bHYg2AYap4?+yHsih-1UxeIWqV0?=sw zPiQE14SZ}US_7w{Xbqf(qBSrY2HRX((FbhL=DE`Wb_F9D`t6KxqJHGvXTqr=i$2a2kr%z-cIU4V;Fe zwezzN;F|x^_Z0Z+4^BhT8aNF_Yv42#t)1U!13xzut%1@2&Spfdoo569jt!7kjYyx9 zfcW2c?Eg8C!7u6mPG=uebED zU)r1g`<9-E6aIHV=u26t)nhj)OVEfte0wJbB}-|eZ=_YhpOZ0(dL%WKk~mz2T-H=n zrP{JdWE?x6QPEnA;7Y>yz_RY=!UDD?T?Hm~BWKBxuPZL51b#*VdEd2ZEpX=BvO3)a zX{>Pl?M1jMKi7|c{rpHoiOKyFZjL!`ZeaB4n$gDXEPR}%tdvuc8Q)JQAwLb*--mn` zO!qOIQet|aQYx!D&dtg6oM4{ElFvEcGJIpSCmq{zVZfzr_-5*HBxb6YWPC==;ySlj z;Zk50N-Gl<8<9XGNpzVtK?ALXf>-jumr;N<$IMCFjAzbM=%P3x`M-Zl@3N(wse zHT#31eC(?1VF@MFx$&mD+^rL5%QqR%w_ou6q|{y4%w1!S45j45ar^YffUXRgBLs)6 zzOI4Qg$C_jd}Mc1@4l-#QdbrcD}QVB@Z(g9ZDhU>3~X8EX~qu*du&GRE0n^ld?6bNW~}IGkLB;sOhK*c$k33D;7$ztS|*hSTTdWEZ7NeKU7@`g+yNNjPbW z`H|OHP+F>MNMB*-MJ!2XD*kT*Q z+)%0S(_45xDH1lT_O5}*MC_0i+>5qvmwCQl3?SVJS2<=P52nDews`9|io=~J=xR<* zGHKhy{H0u|ou?`%?#a6YxjmdUOKtbGZ%OmvvLS{v3JX@;pM4Sj*h<5E4)QctQh2(p+c#-MvjaCv-9o6Kn-Bb(gJYaz;>$dWwQA+w6-lxBtv z8|mdT(YMc+UUmkTb4XZlyiL3?cifLXcVU|BI>jxGZ#k{~fBy&Su^%x zO%_bA_8IIa(l|ZcT?M}On-5gwJqwHPw`8=Udm78TXXaF! zlKkK^qZSjByTz#_-}Dk5i|$Z2!-+y_mpH0Jhx79 z7oB{S_T;{nCi+Xg7SxI9!-jyAI3!vHK94tKRnhKu*+j+Y1N%Hh#g9K$xyZ`CaW6OR za;e6p%FevPA*m5ad7a}a7K5e8!KYO00&YQ~%~g7o2_9}Gufut4c0*~6rZv|h>pYA5 ztUI-H&RQs9HBUrsvt(dt9N#ZW%VId!H6woweyl|~zkzfw08JeYo3J{@Aq|h7zsex= zO^oc57m@H^^P2F+KaA`L?4LTYyQFDbvz7SgzVR2EdUL2=>-F5QjJLS!qYDRw*vstL;y7Wn+;4OZd?&*9{5!# zFpaoz1)$-ZL|__mG~&h;fCh>7hXM53M5NEJ-GRqO+_(bJAPy85K(|dq z8tiHq#tlhpAh`lCfM}vW7#kp(2$ALmaYMiW3TONY4Ml6;56ca{7;@6 ziq^o#Mm!|_$=Fb|#tQ?n4)A~ed#eaa0~F2xm^s+kzr+lkA58y*hNLynu_0*RX-Haw-)#WBZytawWPs-er6Fkz zl!j#2Kxs%?1EnEp4U7hG3?r@@SYbOJcuDEs+ZJ#diq^nsC|U!jp=b@9hGN&iX((C) zr=e&KoQ9$`Zus5^@Mnw@iq=4B0LL(Z*1*OFa18$m4M}UDV?)v!C=EqxTp(v)p1*GW zf45fzr=e&KoQ9&cbEN>vJ<52Y^ZkUpttU-S zJv?P@lO`!k>+?%J^3smk?pdKG3RQc}in(3AU`*ZVfmlkDgYU9E3e@?oT^^B|Av*pe zqz)Si#I}<<&(arDHl*|ai=0Zqdy!Gq5sfhDwsz|WQX<528(R?mt$j=0qrWgXN@4DQ{GJQnTSJP8Nk~Iov1C&XAD0_bI66#)LZlIEK*4 z-d*Fc{S7%i{#O*)A8a$@svZ2epY`S1<}a`FYdel(l3B)t=PG`Tx8=K!(j%a3T=`MQ zSlwaW=~?FONCT`TWk5rAQ#oTL zkXe@BAzgDH_p+?v>=aX|(%r_+zhp`5reQqC>UW*PlnITSq?LX@z?r8*oiq$hWn%AH z>cl&=M=5G)hqC)V=K9M1F_{_fz8BZ63^&0Zb=y|4?YZ5Q#!iyMCFUvfLehrux>hYwP4rz$I$hQPm_}=3!mGe#Lp23eCG3ep>6mrN?rIBmzniT ztB&paVQM*C3_Bl#;a$Frn_s@qXLlY_T|~9j8e1U@b3nCoi)g+3O@= zYptiihlYM9&|_lE%#6j#>crActv`L9cjPc>@*c;4t@T5K+s?O6%PNhN9eE8!>>iJL z(y)%kOWjJxe>?X6=c$8IWv*~#`q2e^tU>P1ol1k0B_i>+5u$gSD_I&1r_l&+tM4zE zSw1&CAejvHdz+JFKK6khHb7MFb3{^}oA_|&LddG(7{$15Tcq2#kKPO3?gG2|`AXht zj?b3J*iXy}J&TS9+Pq&6Yd5~O+o#U>_-+^HzH}AR9V|l0o!#&%CWhcb z(Lwcze%Fb%TSLwsd^Jgx0W>?exL*oRx7@zO>UxVUZw`(0m@CVN@Z~hAIKE-q`=rU} z+Pn$e@7}GU3PA)vg6^>fUC2ZU5Mt}0;R~C{3cM3A^z<$x+Er1qoJ7-AZM z4*p0qrdMg-XPRDRe%C7ZvK<=*Ey{b#zk;Gvt^`=JyHZXC-{0h%^X77k3sRh@Xf zDH8%r+oxQ<(bs4isg#gv(AR`5xWxIz9!HnqQLGG?;ay$&Qg&6SI^f4!GIsuB(^PKf zDZ){@2tg*yOAf;L3no_grUj%sT5din-J+qYm3S+XYl!s3PtQAI(;umo?y4aHwR(|+ zlpuerSs)o2COjC&L+nd<6us5bf-S2t`QU_}3co!%YE8zch}upx3gt(lEnBd@P*LBUNFI|=+oKWK$@U#4+9P`IPV1T-y7-w+W`iD+_sGe9+2?g6F$E+ zJa6_6Euw%2URMwT4H8jso`eR0hM%GV_ptDj(C=wpkP3i2@ERHrXpkl%JOI9N#8u+~ zi3#8V@Qnk|APE9I0KRcV8myYld5ezUKQ|2QWDnpQ2N)Z~e|3KU`}?uEK~g$+;K_Fo zXt47!K&1`DnRCHg!vSgyz8=3Hn+qiTm34au&7(vY+UN<-2b zC=Eqx=l55@YYs(g;4~DifzklJam4oxPD9ZeI1REt zNp|PaTfevG;4~Dw22MlK+IjRH@R~!>8aNF_YhW}lfNvb&je*RZ7r-};NQ1@2^8!@p z{u3ICT>~E*iq^nsC|U!jp=b@9hN87|zYvhtc;OiizyB!#r=e&Klm_sP1H5mrnFIL7 z5os=vTZ{9C$-jSYa2kr%&bxR3t~o%3Zp4|NlYsc&KD&Q-{r=^r{oCtzo`QSs_4`K( z?!U8c&%J)|u3+$o|JCcq`%9nd|MB|qaYIbO{hvaaSI)hD7@DjzQD*g%q&F{D%6e;4 zFSV9g$OM^PsMfdK|F97qZyr`g`B0Bov2E7DaHe~9aoU65DK63T5NQiYrh4#mXCG z_|Kwd2QH|62qAn#?mM0v+Lijl;WCk(x0Eh+XV#QvC&svDUfzH<|8gNxb5hpxi{qT% zS;Qn@DQoxSAGRBJnM%cpp0rNl+zj^&TIWiCJB>Mg7Elm^GC-ZvW38Lv#KV$Hu+YDT z=HO*Y+-pjE&_k|cAJJybdgIo$Z{|D_ck7woxErge z+BWsh%Tbh1N>-ObzyS7gK%arP)2*f`1J9Dy0!>rXMZv9rgQfa3a5mnoz=JUQ_LCKf zZu`vma*IVIw+45;9`Rj#Mt@yPo8sokK?z;=`}%?n zbAP|+ulyMPlc*SWjx8ByO_=%hR^~I*u`Rua4Ihak6IVRLyZ7j+Q{`+TmbBO2l~dss za|TooJ^! zuc7b%eEV)UyXmx~ft&|xnaj*eG$(T$z`7+@bieyuge4!ya8 z;mrF{-i%JCc~Z?7^=^ks3E5*@NsS%JiQJapV#TpCB&C3XAb(AbfHhHODyncDth=2L ztcWgF5)L@at}JYlOd0v5nIiXe})q3wsK@CvGxn)aqv3r4HO1vZ_weWXKB?s+TlA+1xQVdLKtIAuKHRZeiO`_Z`j6 z2muifdu9$({P}B)eTTk{Ndl_;OUOZS{f~Q;B-rny!N$>9PRAa{bU2I(Oscj^Y<$J+ z=^Bv=G)BY5WBiFvlgFFL8y8HlHNr+S{O0DOkB)7MX?2ZF`HkV~$0=`uDURk&b7QZr zH+L`Fw3y`CPo9z}kduAs!Eq7vh%>!n`ArV(edWs8wqYMhHyTDm6Q&`><`n(QjxutG z+3Jn3{srf$GS{+P=z4Bz@&0M2)|FBPA?TZne(ar`0_fw_eHKg*=h>T zBwG_2oB%0156xD2)mhE>d)zg#T~ry#=5tA9^W1?vyBePhQKB%A&oXrfsqlBVe~w)z zQ@7w??AuKA+Pdj{z2=ynSh{)#59#o;%k_@+Sb6e!mf48j8C~qpM%e8!!_C@QGD2dq zcN!!ZF3Sr&WHgk=+SfiCpHLgsp=l-?6UW&&nMD+wn4*3%3A6%$Tv zij$e-LqUZ|G11%$nUoV`qIb0L^67g-o@VGr&C1-yrG9joEu*Cah8ItHxGE!1NuM?n z^SDv#PD_S_ORe&4);P4fbEHnsh_3UIo$KgGD!gmWN|j%^t!AY60cl*Q z`njG!b*1}QLwnw+pe$Fi#3=)cd*)oyS(L^p{>77tpM=kkVqSkGDwwGDwMrS+E~+3H z+V7atYx^Mcm}1bkyXG8n57g5cm05Tm>r?Ph-5To1j*_6h{es&xUh_b>+vTZ|+l$>t z^Cn)ADn7I}JsiZztj-DJ3_6m>$V^BSAEp_w?k$RY@w^*H zbp4)rAXnRc8yeFqrIU%B`3E~DjA?OHG~Bag)5f$lwic5UGcWIUTCtFb-Yps_yrN30 z%z`Q6re_TlaP0|HViOj#$3)2bkGaLb#~HJ@C~6U& zsACG(Zqqx2*Q;I#hSf5mSrb6>t03FF)S8=xQQUn=p3i9B{A@;XRDgM8Y z9AXh)%kL)}EOHk3J#q-P*sy>cG60P<@dTc106Am;8fk9?rt!~Y_%s&rW`k+`GZ}z} zG!Mf5R)M}5Z^fr2k#^(YvGLFU02*@J+vZJd|NMzbK(gPkfZB8Xv0;%zETHxrKtm#j zSj7AN&ohTwE{Go&1&ur(aLs>3!~cB@27h8wNL&?6qtP0iMx!-2jc;q>uQ{AXqcu2< zMr&{y-`3p9%<*l_t-ejNg;b|t0rB;%&}g&<9~+Im`B(G$jYj z9MXvd3#dKc3XNu8gU=kzz6PgJX$|_^sI&&@f`SFa*8!^rrP1tbo9-gu_Z!fZd@Ez4 z(i(JZH2WHS=BV~HNS6dGpeZ@vxuG;FtwCv2T7%MPv<7Qoj|Ifn@t+%(U55q4*SA8W z+1KD>qtP0iMx!-2jYeydkX+2oJ?Ed#$Z#5s*5EW6t-)zDT9ZIBU4VNFAij>T7C4P& zUxU+Vv?h*3Wx+E?qcu22=xwo6=xPR6foJONHIE_YYo9hi;bmTShpHoLF zq5h1h{6{5}_-~a^|0;g^6%#=^^8;cczlGlaiit>wiK457dTKK2j8sBhWV-wFa?Fz6 zqI%Ww%%#_WSeni*C$cTg5gT@O)t zW&&A`j?XVVlZ|gI%zqx&OMR+d-RZ&O5dN02Py)B1-d-D%e7dzSm5y$;W?YRd^eOxH z-e1sSYt}a6Y$3K|bnCUD??}>JWgIc!_HmZXj0=Y&|vqun}bR&%o0x^~_N!jiCvL$W5G8mubs ztLB-m6gDWw#oCu2=u_<+sANi_uPNYyzSwqq7 z#HD?9M45bzG2L7>^Dcq?sjLF~AI_7l zE2?>CRwNP3=cEVtYepC?Q{+C(I-j8z%6OHA?7MMKc-$WO|6H6%9Kf1b~}Ed3qz6Lsv5ifd-2OJeoxJ0o@!6Q-W&X#2!P zu}JQ5K3{VELQX=-^$lk8vF!b4#-Hy~VPZ;1%q;%4{pNKMp%-ehWsfqLjUH{NUQwtX zV!KECkw!b=^2C0D`v<1`Ub&@YOHh1eJj9r)XUFRw%6jni*^)$yJD-ByzIZt?@ZR&r z;0y0B<@?TkEDd!~J3jFGRAW&5H@bQIjnLJ1Gi!5S&nxuk*BGg!-CoqRRSgNHU>G5a zr9N|5%FJ4CRp-&dBU+hU>$?vxDLS3;3Z^q>`5ylH+`9s877J%fy? z6df49CefHI624DX$uDV=_#R?8V>Dk*ySlHyIBz>$L##)=5MyO&%Aqb;%pf8yEc)&6WTTdq0K}RBX z@}&~BkGj+%AK6LE8fhBjim!@0pXzLlc{gn3$Ygn91D_nmcJ3g08=IiUaG+M=F&U9v z9D`~uGgshUC3UfmPBNZgmN{Dzx?lf}Aa?OSopRL!+G8=&yrLX};dEUqGkkJk!Nvpe z?@ONr)-cTa4P0`WZGL>4tSITv>3sk`DQ8ymHAzV8VkuB~ z7;nv?G$=g01saXk#36SmN&UJw;&&jn?2a8m(<=94K_`FXQsxQ%B6xKb;x> zPT51g-G5Kn`&X9Urg&M8m^2AoTDywensM=@AbqMbcc2R)%{HdywFPUr z^5eJd7%0Q6PfPAyQ2VIpMdp|i(q1Cat%H3qoOwM>H@2Ww_@XOCdtaJ=FXPQ4Gd6Xt z{^zb~UQrFDA!ZL|b8n-PP^a;f@$67_iZCYakuE9Cs(qnub*GKf4?}0g(QyqY6V@~J zsOik`Ai*q~X}#+!9Szfs2f5Zhk2yLX8SwkKq$y0c$PV$RGx;ovzTk<-y25Z^>U``_ zt}IE6C|#!!#{v%xU2SQXZD80}!uv+~@rU(B9nVaDZOh2nd-XFB9WimJUwG%sHOfHu zG-n0T@CJJOi)?w`GvSx0A8|iDFL>(T^+pz4_GRTa;ffo(tDV(xNA9=jc8z zg{@QgSmsklCeC5W=h0B1afx@QE-g#a`Q{>`5t+Bg2yc~~&->;&SQHpX_`oeMKfXAx zB~41#tT?CqvYp6qT<*5HOJZpY>{NydG;A-{uu}XT0lKCY)j@%T!t_2*g7{@qx$ec+ zzK=DoTA*Kl#_kiIjw@-N^A$OBImyZ+?Z&tLwGx5sat&HE{Y`D#1s>8Ri4*Kjne8lh z=-NAcPuFNTJ6o&Lzi*dkZ%O}JaC1!1m7*=v6=(iQOs}Sq&~Pv(_tS1C>G zzTBv@f7~MU>IU-{4vUhJBmIX{?IY;buN}=ZIOH1Lb3bHx`n-MY!~8Oa{L67ku6il6 zDbXqHU*;~Hz!}pt1*P*#Ji07z)V!PQm{A{th;i!vcXd40dXDoaDve#QPZG3Q%^7!8 zy?ofAC*7yQ+dZ;?i(ffFN_wdNY+OTec&5SK{G*1Mmts2IDA==XJ!NqNxAxv-<4Ly| z4LtVMOK+%{c==v(FQeX}oCg7uM_juz4)X2SG+ME1=rBEM#Jq2iJ>y|pH1ng+hc!)D zp7;+61(+SHHSx|+-u@t$Fp1RHZd$IO@$po(NRIQaUCOr8@`aZ<>@nn)s*#U#uf5Gy zyEfUwT9kCE1;edcm$FUk{CLx4?cQ#Yk%h2lpZWa#9pHzm%Ppzg<4SO~v4 zDg8{zy!2&k*Oi@YYSa%C+gsf4N;k3Xo+Z^+)3-6z1P%M#S$2TL29+2LM?%_%s%BeXkUtiv@s2 zx;6lhhsd1%dFGPH5dci%zq0Yi-t4;orty!5__PFK9E0hO{4vD)+0lHWKXyh0Od~QIN4L%Jug8*GDwnF2ZB3q%+XbnDdG+KkxXtV~W z(P&Ky$rAv7V)&*AVAY^BzA3U58sF^Q0u9Kr0nmt{27GQnmJNu8>`X|f@PFQ!q#&JQ zq>zfH|At1THR#x=v<9V7X$_JsDuwKKD4rWiqtY6bMx`|v4al;=SIcJZG*D}RE*4v% z(P#}mHX5zLX*61c(`d8?r_pE)i#Wf)YmP>1a2k!);4~VoL1{pi4Zd0=k^3@%&ke}3 z0ny;>e=UTv?hsU+Jk3~Mr&{yjn?2a8m&o+!PXp&*5EW6t-)zDT7%MnEF1jK zd-K*W;3tMuB>eM!4yV!VYj7Hk*5EW6t-)zDT7%PQv?c+`$leU{p;>dtu87!a{|${s zYtWhhmP!M*iNqzq6*f}8#cW|TM5({r#Q*lcZk0x(HF3y|TvCAWEB<#3PNUHpoJONH zal~Q-e!l_XSHR4nG#ahJ$3~+y7>xsjU-4(Y`OM%4|E(qXrn~-+K*N7zlZpP8P4#o;!N3zK(9aMi>#~3!A{6w|X5!4-{CEog! z{@u(JS4%>zVIo^xKKsvaABecfyAltSx(bL#mw1~mR27#KjJE9ZkgWTRmfriSN#)yEb^^lc4)Yo0`_T8{}6m zIya_&o?8A^HejB3-bE(a?S-k^F{6gwhOz-8fkm~_wc(8~@Wet{pn(N=~bKxKdxp&y{0K!n#*F38EY!5~eA~iJ8M~%ubue zsGXhcyg3-uaa}#?vGDTesaB^dGr!P=FTPUA5&H*)BXWj5)Px20JPs|FIBIb3&Y*~T zOXXz7sl@wx+dn_@y{(}A+S~1@xBN?zE79oO_({0tfQpsouwT?{wduCH);1(j-PCSvx%=9O8oF zi84oO^O<8_9z0KBuD@G7y;jMDh-T@=y_P$&0+IZlrunn`1o!nUB5v6$>7OU}u-nu> zzM9mhq|ebxx{rvxkbYc%ONW3jQRr1{!F2G=)0MaSr)?xFUnkyVal)R_p35W~+Ss2+ z{bc+58#TH$-HOS}!wS^y@{==KR>* zqvRhsziJj_e7JYM>tlqGSueBoOV)i^b_d@J@c)Rh%Urep=qV*w%9&7ZFX9l`B0=x= z$X!Z;zTJ0a`G5%o?JxQg_x@Uzrt%E`be+Z($-;YfeAiCur1svKO|u`b zB0Kd^Xw;u{k7VPAqXp&)l$w0IsyL(LO`9vIKB`}tG+EzyO0B?)?hKAt)?tN_rXq)7iA}W$g%kJ@cvTdR# z&Qw{ps^6rs-zGiYMM7=daH!+#j5Uev3>nuQUv)xGy}QdTEUGEr4`%2{9gmaek!PyV zWTz3+`cX4-y~ScGZ}qTR_G}5yo-qvVYG=rd`Eoyou`#QuJ}B{1#VRvL z8tJvB+u7t%tn8-rf!P8M_W~5PW81e&T3=z#j${51S^MZlE6MHL$%w${W7|Gn3Jf}4 zqrsQG=W2a-OPTXsD_ZYU3?KHXGsh@61*;`(aLd>1t&~ zqAJoVlh<*oVy|+}g?DX_oOX8ab}zvmjq3W0J9;6c-2z^?i1t<2X}2ZhCB<``O7l+(Ajd<(ZiveU+SnPr0C z1mOa?BZ!H4MSbo1>_RvujaVfd`yF+UCJ$C+?i7hXq)p!0%`oAweJWVWgG|Iam@Jem zj^+(ns9^M6W^3ORU!|*$GK-x?RZIkRsLisn#q}Oi-RR?$(RK{7UGJf!ZKLJRU|wAe zWuBlv6(komSyN-q@vuK@nK{olKHbyVR&tm5vgCb{vfG)#b9Q@~-#M`GR-`F;lk+P7 zYb{6`$hzA@BC9f24V>9IHyT^2i3u)`GRA{r9Jt?7eEz3m92hQ)=;m8{^Bt&)^tky~ z!~^*+EEnciwa~Bshr}+##C{p7zy1?B>LM@9%@aBDpIh#fjRPd^;1E9!@~@$Yj^F_O zj{r2}jyv41=l%P+5$aEK-RyBz_b zAwCKmp#Kp*Edi;u$NiS-zj>Ai(2%Ne93XKApN8G;gZr%+1bA$5$UTy{UtJjfNiE`# z#2p+UaR-0q7)T#K93XKApN7R4aDcuJTcPnS{jJbwv<9C!zNNpFvGJ|1t!U6g^;IBEH#`h|1g+`+_=-7aWGhl2;?KKV%aR$+l zeGL$C-U^LIYm$&64jiDb!&b&dqc!;0XtX8)xgP`vh&bcV98ROr8k|O>H7E^;I0I&m z*dM^}7$D-j6&lUHCIKBAjn-f#AUMFi3HURI(`d8?r_pE)gG3_0YmOh8!Jj#tMx!-2 zjYexwfA=5W>VF$1h`;+!Z}}ho?#+&}cCz?caa(2Vsn%z&%%iMcZP}%-c3Y*()I7lo!BY-3~Yg?kqr zG3Tq>>z*rlu#3yEh##?Ao+y_8*ksAZ(d4P0q5Gs&YH3~K`1{OUk7ZXkDK^vMV7`dA zZ97k~9BsCkn)7|wV7p|8TlEZlSHVe*D-H6wLl$Dz9<0A~S=KM(v>2OcA^)Q0q0=nG z%JgJe!S>A9y5}1%F5=QJ^_NWF?U@R9kXM#klej4#@2HBk(~n(pO^I7>aVp~$v6EwQ zr+4%hHYw((ZF~^9buPJsUwA>-TPac zyfpgE^k>SYWp-t(rndFBc$RTJac*-iH)pid=+jiA<~zlxG-t`>aXY_Hgq=%BT4vEw*)K!#ZJf65GSyW2JQz*>h=lEH{tdz9raL*6N z%!DYj*t<8@?VfSn^!)Nw{~R~`UE{&LHzU?ne-XnX>e@9?LuhWNqHU1nkp7zEgQwwim1Akn4Q`7eR zk;7UWSF)R?`-5bXLQZOecfCTRpQpsCXuit%|K*+vNmtwRth}_nr;~Bb|=z2cI?f zUm~+Vx!01=fP<81xj}u5J9ciRvR2z)7Mol36}Mpf;{HNEC3Bu+-Hl^UCWoV2GSW}{ zj2aQer0cCyujo8&x(#6U+`;-?%IDnI;~8`bX2+B)HMeyt?j#Z zg1eLFNLI=5cNAA<+4Bhph8I~}Ua%FmM6B%9WXc+nn9OW_o_jqY$MvLRsWXs4IugNsQ$0aUgw{ z*&g$il_D?sF?E7*@<)@ruNHy*RwwR!>O6O?Dz?IV_I@t^k|^CsTI-wBKkF~E{HcI?VxJch#5m~4b{CEP;cBO=tIp) zc4BY$+7H)rJ?633PAApJabJHw-~L+p;;Hb28SUjpU7a+Vm39NIRoVCScZ5UrM}v)| ztnKK%cOR#}9*gb&_WdcnQtdqOK@s<}Y$pe0!U)}d*Cnk`Vm+dR z8MKczM$R4cvK47x zVrvdl{6R;jwZA5=^HJ(n8t5x|6ku4^5}GgRx$VBacBPtu!!aMj2-o3Hi=m-m^Dp#n zG0Q#P7H##wl}pqkRIpVR88gT5=5}WVnggR!mU-Dy4^lLb`;Z(8RP2oq8~#FuPz&{B zp+ibb)X`S9;~jEL%YzrJaL%^Ag2OB(y#n=L2?LZi_dd~7sYgOw8F0L>lnXAY;)Xbn!I+1Fr&$T(>A`d5th-)18C8!8T^(d=t*8jaTA7Vd9J z)rf`rr)~a+g^PR-kXQWXwPjdIDGTIx#{ZGSwQ1l=|B5pIZs1D)7Crvcz(tZY(b>24 ziNj7q3bdgg53I4zbQxMd5fJ%urVFc|Zk!YRQBRoiT=>yO9mn*iKHu8%qiAT!j8yjS zxk@Ac^OgLa^QBkKXTKjI2%2m2JbZVR-1=TP&0ezbSZ?K*d?o9Ub_-qUhe!H%A3ZD- zW;-GCy1}$be|cQyaun62>`|-FdjjOk+g_D>`*sat)<~Js#nKaena*T$&SncYi?61f zmS2d$9Y5?@VEt{QMJkCu=AA_T(n$~2(UP{=m#53U*r}xi*9aI$?_Abj-S*`84$Xe+ z*h}vu9QAhUFj#TRQTZ|%^e30Koq2k+Y)U!%$^riw3r5#xC1J8bMq(`P6K_kpNc$Mi zdP*|9kW*E;^V*xG&n)>Nrt)E3TS4Fp57j(b4tsw~a-QHv-&nIcr9YmEerBX`yTR@o z>r9cs_2W0=A3ls&sT~O^DZJ6;oqtsQ!`+VD3<;wFv0Ij6rODWcL7z$e9V!V)3lUFz zU#XbwH@#+6?2_$Q`+!Bc|L9jPp-u{iAS$W4Q=$9^*(|pSwpzd9H_duyW&C`B>Q$gd zaD1zVR33Y^UmtzLtJ_8Edc~A)DQm55tB;>c{OFh{uG$mq_|_}@LI(M0g^^N)AN}j` z-budRGiL7hF6K}6WQNPJ`DPH?Ti!3?`WETg{7ggMo9Q<5-X``>ygo}}+c{PH8Qb^k z<l)T%}ltCGtMe}sr;U_WBC+Mh)EbNR3s!W z8YT3~I-w%Vpx}m2dx2x+C;qz|@2oxgSkvB+9KITFRoHoCWqmneenluG^iYxeUY!v` zXFY+Bd%H!9r#Y2hxvi>QA|zN6o%I3R8q&WdS^Q7g8vl`?vSo8Z8jyS;{VSd0?@1@% z1EMq_`2s*g%0s0A$rk_`QXVP|NWQ?QvB)75bi9-XBwqk%NKvgcAo&77BU!TGvGETK z__QQ+)$k7t02iEMfltFOQIrNGUjS$%Fa~^XK=K8Eh9vq(1G=W*)8dH90v;Rx zzyP3;Fe;eFKb+#zu*(Ldf6Kz&JZ@}-#y>D@g+`+__{`C04R%$5G@#rcFmp(Oh%_Mi z0-werE`8u921vdD(crL?G$8q6D>NFdVX?5W(P#~pOec+WB>Vkf4465@WS9Qc+Trg9 zV|-c?wxewnF0@-CLpY4WX^jXtV}hH3>9YgC$Q&BUZ*g zYYk4L+1KDSntcsUqtY5AX;d01koqs48<2bf_`DJ08n~|kk}p6sG@5rP zoJONHIE_YYo6ih+a8xf*m{b7~Iee+Y>iz%l=KcG?w&@Q1Bh>JRI}n)gg8M7S<(EJ3 zS4qpXYaE<%LYo4=kKJejEy#3*hnoBNbP(wOZm z+rdW(8sEOUi)b`u7|ixEKGI%VT1s`N;q<9aYJVL-8ywfr;AcY~@-tCXz+UBPQBiF4 z{`^_-BgV9=pAxPah2=Gr{;as!-WG9QGW|@0;QfXhx)<-n7qA7ypSS2g@pIt&o;x?+ z&p$IOn*Q3*D0`=xH?C$Q``Z{#pt9;tGL{3-ufF z+NgSn+vn82zTz}Yx=8QOTu_7!cy?!ou6n~NJ>_cXb=#{ZLp{dfv_IRw_!V2^i}^eu zyq@ne7@+p8CGnx_$|F%b-!7?jqv3wB^#X_cdveVpLL`Ry1|Jsxlp{PMV-ZQVV_%J* zz9nHyD)whlK!us`SI7Ri(GX{+;q})+cQZ~(GOWftmr+TvtK?hvD*EuvOP9xnpVxM~ zLZ##Q&4cPZ(s9N%HFVO)&Q{Ado9YQBy|f9tp8w|@31i}US$S~ADo9ST>(Tg%c@gy!uXPg5U?n9=-j);O>?MRDM1t=e~m z6GwdrSy)u}+^^<$tyqlDNs+m!9ku6W@tY5B{s9_^?^WE(-yUQ*@yL04GLcp_di}t$ zH!3=`BbT>Nx3mS0J*dvlJ2jZoo-c3OSaNPYPFwtt8R;(lJMHB*YZc=0Q6(J2}GQcgD5}c)=el5<`=(kNTc$qkLQk!eMo-C^FfJ+6&yeoKaFB$1R?}HZ< zX3O8*9ImpUj(?Zb)bnaDRcXghmHpkyrY8>0_RxOTc6m0sQq^H~xoY&wBzAD2jWvP% zRNrxzx+i<{9z4AMQhn+|#`|xRMT{p8|8!}UcU8u?40tp7UJv-bFf3%C?${Ak6e3bk zV^kp*sxD_wuJwJ#1d~+eOa$HZvWu@9z4N&f#(f$`6*3EV=9oS|MKbb6e%+dP>2m%T zGs)9WmCgKkt>kc{3A@D7X80yM_AiTGIPGE;)y~vFlcg}iVA48sKY{Ctufmav+YHyL z6*UfTtPmf$Q26n6?5AM=_-hHT&JvVs?&9w7Z%Q-r;4`lxJrFWO>ZNb8-=CN>E|u|v zzL!DAgG4eMab;n=7SoO&A(pzG3@-O|_l51b!%voQ_0#9Zdsh_C`09Sj!0ZSoY|MWs zapPr?!3z!MXEAQ{7*YmP{Rg{U3RlZ)WPa%BJ_$$PCi>vq=#Q*UpW**++5x_w;YIN!B1A!G^HxHeL z`zrm0jOP{a*Elp-;ymaWtP7vHkf{kzNJkb`&b`=J)tpZDdYoFA8K#|n8VC!iHfd?M49_Li`c=r1!G$n7ByjM?lD{ZPLh((`Y`lMR?sT%G~ zoN{2DnoDAolDo3d{nny{+NonlN;EMC#CYt5l3I9As_ol`TT9)*(L?c63(i^fj zd(2O&)$-XBni(8;lpMX|n!|1x)uLcyAMKEMA8j$7+-$v*5A0|Ku7tB|1m9FG(NN|} z91mwFkA6vd3L~7x8>N?A{a6}vrf;gsN_Do%s&A&MCj1qXhVp_s&(t2Z1)MU;N%H7{ zxbbjVF3o1*n{OX0ns)}Uj9BEVZ18Hk3xCH7JcrYfu`M z)}S;htwCv2S_3zfMAjUY)?hR;Cp_Ob{wD^d(d=uGhLRXjPy`rnY)C^%ghsQkK^!dz zjYey58jaTAG#ahJX*61c(`d8?Y0HSL8XB!ZX;1_h|MN!n5a7E66an4>jb>ki++2kb z1vGre-;v=o8m+-;G+KkxXtV~W(P#}$qtTiK*qTGu9F5kXG$;a$rxrMkW?zHTXtX8{ z&UC zH8_n*Yv9Z3F`|Hm@A&(gIFv@EH7JcrYfu`M)}S;htwCv2S_8LRK-L_U)?hR!0*tRU zD2+;MP#TTaHs9T#7r2=8Z`EcB{V2$Tl)P@5@P*VHlZsnE^=%YH`#)PrF(}~ zJ0f+5-(DSY{*pUwBgbnZpHr&)<^2U#A>tX!7Z)cSjackM$gUi1>{4=@Tyo`j`IM(K zGWrDNb=4iq;%n@k-Gk?(#y%cA=z1*iene7a;6rS0;=)WzZ&k`i^}93gFT`di8iv@v zxya2tZT>Xyvr4dDy+!ZM>#+)w3I_`o8TqayblU5(sZ6eY-4XdN`G^n)l{$-(*&t`f zw;U<1E6wp=Y!#ee_WHl9*C0u?sU%65cG7*`#7t>`WxK0J7@)!WU76`{%eTk+FBCoV znz0uAMoc#ySDqvgPLjU%>eN}Qk~&Kr?`9)29L7_B!+@f==wU`CZIK_Hp|`k&2*;!A zFKQW{++|UHkz(&)m|%Q-S?$E!jp~H=fnF;#zJ5hHFRQezFv+zn&!Q@Xy4c=vByW%oh+XY35g%joMyv>~EU3BB#tFX*j~FA9J-`_qq-rS@ zzv8EV$kjx$X4Ky2=!Ta0CHfO5C-;L961PO0G^r*e*-~HT*WRH#U~{=Yz361u!5w2I zPkXEyB)_<_bQzsf{&GuG_I{FngHyfHG$&KaO27-t3>qHg>*w93U(i_U3ZNt+02E*XHyCk9=3zlax1`Ld)UC z+cms$Nj=Ket-Zae@q_9}Yg?vfHz|foMNelBdj3k~HU3S*YLkc)Q$~ zQJI=?_F>yeGu7uYS@%t%(yl;icKN~URfjpMNrJV*%a^R#E4c*@E=_yr)F|j~KTMvp ziV3VCYaFi{zc{^{FyAtmu4BQlsYEd>E+%;5qu!E&UT>GS=&|u0tM69yRH;YVNJnhz zv=#1Vcq=3Y)ADmwc(^NzN{~H2D0eSS(W>RyWO(R)>fJSSe0k-&z9tnc;qLanla01D zFS*MQ5RqgjI!<#u{NP-0hgghNP29^&EuZallvIZm8r39DhZoW#KV9o3r-`qU%?dNS zkwlxU$;kN%2bae?8Xt8QHPa^M$+bJ&BU!|*ESmH%lby5EsxZat&E}AluP%3L9y`D9 zgP5hHr}ku-U9aJQj&{h~o*wRFk}BTinbJJWT3MO9d03F2W^A2S)b{hDc`4hJqLH6v zYV9EhOGzGNX1=aDeYE>hhqg97kI_{{RywNPf#>~{m4ad){+gSWl^jin-RLY~SyA&I zqdZUNMY8ib69uJZna^v|X1ew$=a`DR^z|5e(mwCtBKf!)9=eU&Od#6!<(rzCk(9cg zKOQTrPe!gUIqwh7X_m8MdUB> zj%*+rd~r4Mmv~1u5DmU^8zYLJNClv=Naz#j_V}$;4U&chqLI{HFpYO)1JU3Le`Mx( zrYL}xL}myc`|k%c@XRH__xmAZ|6Q&S8g^9u?W_r=C1Amx-?F73G*bQoer_~cgVd8E z&y7ZF;@~heMif8%4*0|{NbnLkbNui-fQAG$k+IQe4Njxc8k|O>H7Ri2G%|BET7%Pg zrYLCDpkt%i*RY6@1OCL&Xbn!I(HfM-55EJRTN0dZfi&dCGex&VqtP0CY&2SvK#~)H zYmR?!3Sj1N8jaTAG#ahJX*61c(`d8?r_pE)(pwuNh97^tts0a@r8OvxN^4LWmDZp%8m&RP}y z)}S;htwCutTHAbof?uM)e7S!sboevy@UKD#BwtGsse1o+-;+Na$Vh3!rZZUVR{;OF zItS#pe^=pO&R~o*7Qs~Q&Uu`1;}TIlXYFxD`OGQj(`Wv;*Tdbz?Tob})eiM56(*W7 zNMp}GoWZKRp>^o zYTAkRtZO%%NeD63H+A+~ubw5xDgJT=vnOA?sl!~V_Pkgsb;RU{-48xLj?8Pr3%f|M zEtka-{r$c+QmtK^)LqKSX?AW|;az()F!GV<>-56s?;9R(0x8rO<}OF3>c8-Qlv>$b zl-ydaBT(I1d8fFWT;QYr$9>ihJQ1(2n%jDE+({Y2LY~#8lP|Bzq?4x{J?zQY*WJ|W zvE=(>k6Kf9AOG~6|CkNuv&cY(;SDznj%Vd&!dk}l&{D_{a zhlcN1`;FX`@0FJ4=8ZPg870ZhD*e(~4_=xOx|}&veQ)NTpw}5AzO)y+v)ZIbU&i)k zzK*x>sm_^qGm&Ieda?PS_K_@~+($Fo+Rjg^v5_nD0Y@26zi(N3WI9Hb)2#j> z`^|H9CV_SBU0;4gQAH4j?mzXCfi3u&+Z5@XoQT|+Cf!eVGa7HsR5|C`Pq(*MMcLM{ ze-I=lWpYvDOCDCg%^V<%o7dEKyDOVwt1fxWEINR%@bIyIejn+q5=_tIDzD;OrxRX@ zt#6|#%n5Il6}i|movA>Z7&BQxtawY}Z2E8kr|K$og6nynXA7ie9~=q?`zG6{(rcH* zX4gNzj+42&AoOw4??%d<+4Tl?Ew}E8b942F+IGlIE%f>s8HzjPkH5RyAEkIFo?t~h zb*hTuYr=X0TMdn@{nC_?>5ZD|>_a2wr}=z}v+aMf7H-_|EgCg^Ot8^KPIarx^XZKk zPnM(rqQE;XTs`pULxvaZtq47 zoE$IQ{C?v4`pNR8*1cb{({d~HUs6eaN}QS2YyJ>sub%#E$aSzp;usfIQS!Kv0)-NhdL~N+VUeXV`(eY5moZ?Dd zv3iZvDE+1V8F7H0Q; z%=aG2iA0|Sm#AP-#lx50X=a2(2eS>RQ>c+QZFkKG(P3jwVp84LS~d15X4Hb*aOF!~ zZJEYZ2iB<}t&Md$5Al_qWjBRPWJr2#hT_yI*vX|I#W6~@91 zg8Yo{`s#qq4&!Bq9id+xb{KzgnCJ7Ae6Rkm;jSc^3PeBcxWYSq*|^MSwef@S-es3? zicfuC+X+mWWa`0w#Lb4w|9PnVU+^RTS0xF^I|2XwF81pV6EvPfG5nM#P46B4*b} zDNlfzL+*gWh~eKj0-}+7rodz4pC$2Uj)C}s#PCy|0As^^e`5G4Pg|n@eqscx1wJ+! zt-)!0y9IyM;4~Vo!D%#FgWTeT5yMY;0}!y_kPsS;*5EW6t-)zDT7%PQv<9cqXbp0c5=IO^HOPHL$js4b4NjxcnmBTb2Y%jwSS|kNje!(His6?^ z1KuSVNI4_Yiv`6e2B*(PjqLFp?7vk}|K(uEVle0&?DZx{wzVp475(_d0p5_%oBs0Dhj8ljdu%(1jCH-J z3snZSR$Nqabe7&7T)(G&Byk$6V67l=Hth52%<$l#RJZJfjcDG8%t+ZM;Wc+uj#*zx z5KE$n%*>VLougiUZHe*dpEA=B<)+kj{`{~+@OrXV^;GhwF&5?CiAEjz0M5?~IVIlS z5BffT+gFy9kU(o-E}>miW@bD*v+G;YiJxzOCg;sQs4VE1{>Ukp*5X@qyltq}Ysu%E zSbIRSjKjyzSJPs~mne9~7wPl^<- z!FyvuiY?Y8o-eZOrn>(EPZWUZE? z45uf1`M_HJM{X{WBcohV(yk+R*|sXynnjT(zhQfytjaAZKcXGkb4%DnRL+5h(%{wo zO#RZN*4g7OvY3~Tim=j^dqq=-PDGL$HMog8yjx-S9q4(7O%hJ7PZ7FuB*IMo)o7WM z{^%aVqXcY330TracJ@8#=Wb|y62FJrn?WUhIy!-|yZe=yaU}Cz#rw6*1h=^#r_T?6 z`%pFdb$kDv+_gJ4++f>o1% z%=!j8RTUY1CaQHKyl-qX{>HraNNAB^`rLPuXanJrFzxOhsTOD3;@bz_m3vBZ_9q)W zB+(bKI_6r|6WBR<|F*MNM@!ibx&GZHak1AIy?kBv>Axnh{yEgz=QtXmv{F`ADorI3 zq}{yDW;;%5csMJLk#RMD8|QiTg4wJMdxfN1URMJSFKf&O+G|P5f2cV0Kp<54qPgUn z)#Q)P#}5YkR|1}yr&=1lh%xdk_0jS=(e++BOz!kH)%JqyDbttM%0Jz{&T)oG1=Y>& zu4sReBIHZ?j_J+=A_}aim)wi!vPaI}IDc}oM-=lCPvxVKD& zvrqforObv8&+??+@n4eNyHiUuTRmibS%V-@uJ@cJS=L4Vr5Y!jh4If%(#|S4-wUmx zUw%LH)ROEN>yM{nKE1O3drQeFQ#sxJQob2J;8;1N6^YZ#@9@7h@H&KYykw2;IEMgf zcwCogk3>xISm(14JD(axHG-c9u97Kxf1Hr#7h+sA>^L|nI8SZJB(c_9Kyi$p!d@4b zO)s?W?kDxtv(0MJ{ic=tYD3zRFOhqd(X#;^K_M1(BU37o`;saLg-ECGt+pOGz<>W_ z!@cYw5+co#eRqO~t!ZVoA2JRozPTkx&PXY;XEl&J?Jj*9pHL5B>iWr&HI3SH2@!OK z?v#4$6)j9@`y}o*RO%-$o)J2{biH*Xns=w$Sqy=75;4&mF9lOxLNe21!4HJAoUaav z-6Kq+ZQ)kvnCGJmA>>dv9TRiu%I@)rF0$=0O79qCKV-k$`{rhV@BV{@Q_4 z;fC#8hg+HHa4iGejk0_0b9HodbWgb|WnkHTqSJ|)y2LwNsYtxK!ew0@Bt(mqx>l2%j@)u~gB9jL85&=sSzr{XdlbKvC=N}kKxB`|U7bHW!%{FQ%5 zcq?}#g&fpk2+P)4Z#lsry6sd+^#?KDiqJ*V0vSTvl5`RY++(FE!8yCdxXquYhpzJO z@O4$N(%Jnm!?XC!iS^w57RS0}BKwWEnW)*?>#^O&tPnZ*>***7Q0)?@B69N6)2Yje zQ>9&y#IX*B2i^|VeaNK7u>8j5hB>>(wo(#B%b?-sG8Ny9ZWLbXC`<7r90}iP`)igcBz`tPVb7j(CP@RZ3#_7m1C znD!sL2@3S{G{IpCqzA*_&Mf~6VT%9BbZ4a0`ft27!?cFq^8=t%aftDc#2wI#jVKbR z7NkP+KVy-QZwa^)60+uKv<7paBi8?aH8vWp!N*3UH7E^8I>P^s!D%#FgVSiV26NtH z5Cx<7ykXyx-)tg?TA(y4twCv2T7%MPw1!0jVZd(*AOMd4d1E0ZBp5&ddF$^F84wyNVMx!2L+q8WPO-=kJ&# zBw-nY-}3`-Zh_K(061XHVPgXV;9H?lX$?9yDy@O1s zMx`|jL`>l6NVD4o074XV=ZB)4NkR)ABZ4)f%W8^ zO}*fwQJJJG(0;;Qxzq2^EnDSrU0!=!qPksFsi3Q9VRW>Q`9A2te)?X0!zCI{q9Oaj$Fntj;NI?Dm$ z+?>4m(UZh9A96Ae(6myyxyfG8Rw1zFdVH;m$cpG_GY0{8Ps>`-j@L}PZ3hqf?#57f z?-xjCqrbniA1CnAOGtpYlc#=@n6;jg+0rc0+ZpcBFP4eTg&cC*4UVhK8<` z$tgq~J3Ev+?U(nZv@Vv|fOyR>2$qxt>4p}c0|A*pFsBLUi> zf=EV3ri+P-dG>x{OzZxoI0Bv``A)g6I`c7Y0#Lyl}N^s*LD_bkd~8NXt&^W>myWgyaxT=QEtDa)6yRkFoDSH;l(SakgJ7bF3vv%54S0s5TpK06iYM1;C3H$O-W$ty~ z%H(l|i7Rq`Vd8hQ)6Pv?oyZ_odW;e6zTzCkX(AIyp}-cm@M2C=oxx3F?puyj*=?<#N$#TbbalFJkF|`_9?Mm3ZBtC^u7xBU2Lm zZblo(94<<9uafMrXds0bb+G)gPT!+-&kM_2zPi*D{ou;|PVjO!v8Bt0eYc_vl}}m^ zWb1bZofNF|JodifYvJ|zi1N!(p4+#_h4*BizjN@bdpF&(Xh-`ThT*xj`EJ*3 zM>2ZqemK71K4M^1E7I(4dcAd;ky>TaW1y!0V?`$9U(A+~JoCiY_>S@-_#5u&apOlIt}VGPx$m(Od*&3RJ<@;kMyPU{Iiyk4T* z*1F)NC3{AXf>v8UAh~&3@qksES-H63qUdyX&4-3vI$`-WgN&-%=eui?-yhns|BB0r zAFTVvzwika7QcLCluUW^)+;xC+P88OD}q6CX%fd3i`BE=?@cFddTE*9d8kfCrun{} z*J&P=_nHM%!lUF-PM1Vds46FUSF|^DfI(6dOd-`{i@azyvi4_k}vX> z(&_~SHpnFS2bzTN1f5?O2s1tKz>GzPuq{_mZaSrpbM?Y^DhA7?eqMX=nP{5}A1|^f z51(4m*CRS~k>!4g`;fGX-7t-Lm>IFut>I94)OVR%Y|hU`2xwlBpZC-J-v47Rf$-S- z?3zI4-111F-14F!!Hx!29y#h7S|3_Aa*1JlB8rHJU}ewV)GwMDX3YA1Vgs}MBYln6_i}Tx77=Y#LXnNre&{p>bm_Ri5B&Y7ik#4{G_wDH!$th3(as$@KkP3DVb8^Kgdh^rgmzkq z{2o05>|H@5ra{o?bP`Ap5~7F*CM|+4q5w@p_<$HA2ku=#B&K27Mnd-#781hj;UNS; zgZyrY#57DA0kjRV-oUtS1R&{kyGsSeItzma9UKwD?BM}%-Jo%PVW@ci9&E$X8lZBzFjPDr(>8P;NFiaUc>W$} zSXu+xhNU$i4NGf48kW{TG*mnv{#}EL=VQ`<1CYW{@%%l|u(SrW4QpNl(y-<=APq}v zKpKwL5P$=a!sz|-Kfi&&G#sq~X{dNU#`6Z#aOO2I4d)#Srr~G}OvBL{n1-V@(7w3v zu2b;P?-DQ#OKX5*!opDTe9Y$!q+w|dL_@{%;oF8d(2nWt2?O%8w)e~b{2dbpl*$uE z`_BIx8jjY$w&7?EOv9Piz%(4KfoVAN8kmNoHDPoY0;sitFjPDrLu+6f&b$VuVQCFG za#&ge(y+7!q~U07`+h^-^k0F9e-~!@J1YLKgCl~!m+2S!75?}ulp&1Rmh0|NhA`rH z8U7v05EFpefx7Ttm6_7;%Jd3+LaA<_?V}=~V}4!!Vs4e$U@1@EpjY2D>H8+>k?3RR zyFRnpQa{ffc4qqI!w@l;H&Ku}=pxr{Cy{Eq9n0Wwxn&t`%7@UOpA^2%bZVi*Ac$#Z z!+!hhNUq58`LEVl4dyFrK5^YI2-25a3)%x(JA6E7UU+%GZOU1{KFAXnOHTPBYu4x0 z;CS#i+%4nSpWR1n6VfIG&y=JNUXv@S`%$WNZ|2$h#T7HDO?RuDEs<2guULjZ!tqg( zEQ)Ps+}3EMdwfo7_)iF@;#TRO#rCgdC%~@QnDdajLR8OH@Vq9T%$S^h-RP^M?n4y~ zDV~$s@;&$0tOgjUl}=}jW**DV_rTiv%J*g8xe=bIE=r5Lmp+^BYm3D*!^!gX`8iE} zA`_|Deq!>igm->~$cGLy>6pj8W9~+bZN6GeYqR}tmx$Xpaqh>6JobJ|W*hVI@o^2K zSS1=VX{DQKQm<$qA-+f*@ep&>Sys`I{c3urV7%Psbz+solZaUg#$%Q5Fi|cTtQ8qJ=I6EnPoJuM1SSwu)Frl52nEaZ(cDI3|m7))VSzlG402_iAI~MExubBi<(=VjAzG|KRe7A&3 zSnn&=@YRk~q88RCy&T0)rDR3qL;XBHJpU4E@>=_jn7Fo1T+cx>Ea5Zh{xLmNn+&iXXSeH(lEbcnPr>~7tfqGb|S5#L&fI# zD+ZmOeY$$iJCpHj4(`VJbroT`)@0ED4YlEroMhmw(c;R%IA8?9K-Hbd%-p7 z`oYk13J6s_CnqIb!VkX34s#i9=_>~~pKz$Bikp}+3fO1pnZ0bGV|1Zb{F63khpT{) z?uLi7RiV-m^<>c?_Rl>f@^9iE*2Z417_$DH>A(8!)5(Xy=|P#3dG50ZKdXOy`ab93 zr`FUT&b}WItG0YXQ>{+8?=BA+Qt`E-Z*|UrrccURRWS2}MJr-M#-IC+%wj zYxe1RsdzJc=ar2_wuh#p33E~di!*^uB5%mn6NqWUo(Dyk_Ri?xp0w|&y;C!6H|?-8 z@b#+yR^IA|M?b%@me!v;UNQcia_F6HdES$O=Zyn4?;`~3>6!;9o8;<5CNHdty*FHl z6d~oa;lC*QS?;9l6M;A$_f6}8*c-*ZpHj@%qLv9eNSft?i^*d|!-DwseOwlZj*!l4 zymwj1&X{suf+m=awRE_0KmYRK?y*k3*Vsh3rBpwLzf+1}?eCD@V!{=T6}zwx7g0kq zS!=!?5Z}?}{8P*D5mh0wZuDkd0gpW57Udof@O9)-h9t9U*01cshE zDEr~(T^%EO7{-wy9?@LFdYpBUclb)hkc(E$4fP5CIt^U1#3(LDqIbRDAGmX3(N4`% z9rd0KsqoBYE7K#q``Ubc@@gYd*Vm5m#k0QUOw2KETW=qfAw{A@HdJkw^{yGbe@m{E zd~q(C^mGhALZVBnxer&nv zMXc+vQSHv%T{Pr8{#CpX_0@+i-3r~NfI9v;NwzF^r}OoKpaYz3XkM1s-c$O%;H?{E zqs~LvWuDTJVQ*@tzAidnSC@S|C@z_(P0^Py+04rsIqA*y$i+Is4%wSX`Tipsy+N{# zQjj^+%F@@M)&)qVakqIQ_6xC9iWQDJ%@(D{Pn49q_qZytuc~Z+#{Eg}$2u8S5eM5Y zsSqq&MAKEcB9+W-A;BUeY2juJKD5EiHw-J88*rivJOu(JNW$7nn5Y)XcUY3WG*x*Ft*A5LtPcwn1?ZVTi0e2pV0S3fea2 z>I9Ppm4y~Y@0b4hx)JDtV9>TPS0|XZg#l%!gfTmD2|>{4VsDVPA+qu?Y0x&~UUS0%-Ch3{jQ_(>9ofqct!MM{8gj&b$Vu;b;v^!_gX;hNCqQjev;$ z!+&B3h!!FcG&*hz^)^7DV~&6J_C$a*oOumQ!_peiHY}|HX;@kV(y+7!q~T}{l;whe zIL(jwypez+!U%}y|6XV~{uju(r<~6Ww zSXu+xhNU$i4NGf48kW{TG(_|t;+_M}DI*|G^Y4X*r8S^!SXu+pu;w)&4NGf48kW|8 zG%T$FX;@kV(y+7!IG>Gx6*oWtX;||bkcOo-APq}vKpK|TfHW+v0cki|69%*&L%@m~ zfN3~d1JiJ{2BgvZjeqvIz%(4KfoV8e1JiJ{2BzU?Z97LF@(sMJdpWef2Bu+Y4QLzA zyau)nOKU*eu(SrEVZ{x$?>FR4|CP!0&o(srRQq4qn*TY~zTH~&w=R9?f3ZC)YzK4E zUHT9^-OJHGY#(9!&tbc5C+*)~fxmOXL{Y-q@!>so=`-jg{PZB=Y%dA=RC~L-S{Or; zQJ>3DQOq5Y+t)rFmSVq|ZzW+HBX44CEPn_jtLNhmJ)u|aLgr0e{=8u&UdEdef6KUq_F+;SnE}@_E_?3fmT@*j&dT~7tyXOtpZ0ekglL-;= z%4A5Z8j40N#9I~(@Y6jL*)07umq?D+GuJTc`XcMzIGYRNM1{4GXV&Zd3|c99oTzhx zi*gB%ytIQ$f>{*p-e#M>&}Eahd~R&-;XcaRzhEdS*mJ|B;bvS;^qWzei25`x?mOdW zM!E-Lyn8%92}>&}VToICT)uLE>A}||rlBRrR~4TS_qIr>!jQI1smxJ_CccI*yIqj`NqZi(CEWq*XHG!$CK(oN!C|= zEoe&XZmV5Z&`?P{VH;=GbdtaL^#`W1-p+^@LhC3=1$Ie`7^|&(qb}3UE%VE^8RPsZ zA6?VWMLzjXz#D(1{QmfR-D{=e5%n(R>qU(`=3_WMv>uVS+o{KKgVh#{)Iyxv1WTM>A2zG+XIfCJVz#a) z`?l4ZsQKFTL{HME_P9wTd;B*}q~+)7>p{vUTf;$;bZ@4EBpKcuCb2D#sLa8U?Moi8 zdjI%6ol>Bmu@g+?!HJz2G z4|q@AMKRea;&^3P{0yfjWS~zkTRj{X`{bGQS8fY|19}e$zZWiAn7Si1ncp~ikGlY`D3QR(l+ zt8m_Ddfi#lXcv^qd4;2t+b84_S}PPq{MhfBP2+VIPFl7HO99S;IR}}?zKdrmVig>j zXUIKRm8(?xQ5^4B)CooQ(Tv#j7qQ9=ta4?931yTnh0NS`NeAtvt5kGYQTDm3H%J?7 z-#w+yH9RQ&<5A|@X*)BaX{OAy3C4$Yx@(p6)9j^#f|t_mObZX3SeHbeU#v&v+@1J- zv;BS4bq`h5I3-&_0lLl5Z~g5=cdlgH z5tGCO9BpoJn>d#f^E4@(R6nybEA9HHLec9Vi4~0x>dWYvG`L-t$PS@)cCH%UY$F`1 zzmK)Qgvi(nTZ?op1f5$|i5p<5eS`v=KxT$`7>A%bS^HQu5hXUm0p9=`H~hu|g&WM$ zL#?@pK4yjdz^@dS7jK_7zX~eP*ad3$}`*4#o_HeI|tU*m_tpFdvBWl%g?CsxCiXF`5 zGTYxE*qNRi@;KHcQ(u=jYmk84?0d2dVV90Gl?&Zv?KaXwPdIPh*Rcq!YJJIYNS=TY zS1;mbI<{4C@!adU2L}vtxugkrUQmtV5Xdz8+06D5h~q|59WSdX#79ix9J}6;!W8#d zyZgP&nxo4(|(;egrFX^sQNJlcuS+n6&2E22a>OajC+%Ot>Yv;ECGZ*Nw z&lX2YU9VE?%)pJ$>LU57wSZ0PU(+;D$d;;Vs#jql!aCBwvLE%luZ;WK_8ns_*nSL? zB5sasw`Ip%6!cfNKR@8}U!OEX-oe_IMDqZ@#@k!3c#>AQ=Y`+&obhXA5B^hf3&xSm z*O{donopCvh0t3Hh-O?8_TpkCtNVl2lpNoILN&in~Wg&Jvf`8nIZQl*C({SeZ)9BE7Hi%s{Z~!!*mN5b%4i8}) z(13Bfsr$cgFSKn?14abIDSS-ZB7g>r2#7d5CXED~!bd>F;UQ?iiBSaVcLxpnxFF>NEzwR<5weTY-|m^A2&5CY;9J_HTgMSzIIW741;pY1k5e^v{ahNCqwjk!+P z!^k1x@Oz;#*Q9%);b;wb-4Jp3z1YTFlS0sd3?T%%i3*J84Wu#Gq#0Rp?`nbLeGj|+i&&*MK93IEB9#+px3-ylz-p17xxzA*$*_j2uYA z(i)J4r8OW8OKU(H&b$U{xVc>^7{c6rz%(4KApxgkkPvOh{HpS91x#GB4KF_NW;<^h(@nZ|NIW! z&KifjZiw@Hd$?|N&eQ+KHY}|HM-FFR69#m5Lqf#iF|Qj;!_peiHk^44Y#WZ&z%(4K z0cnUhJj7i?7fylH+D;JtPu~4@upXL*qct!MM{8gjmeznHhcmB%ZNt(U&^9cs0cluT z19TEZ!ipP!8l)p(#SOqToV5X%hNU&ob;F7qfNjIk+V(dD@@w)dQ{|s889LYHuYA#8 zxh~rcv(Zr^d{=wWxR3vyRT;qP1*^uZH&xh^@nDwq01$X{Fv`iXTW z`=H=Du9sV{xTczL%;DLIu_3wqrp`FSsN&`2igo5PKe_Wala*OrzdY++Tcuz=$+I-K zRmOaa$8jm=DAkXy+NpCCM%oV}k>~p-4|A0GZF)8~I^4T(37xajIC3TB(1lAi7s8Hs z>6DcnGV{KiFJJw$)yqRW>4aoXSJMQi*u9p+4+~Kj`#x-yRD3A*J@dn{>t}h`>`JuT z&zy$w$yZWQO%p%9d@k>^zhFW`x^-OvO(YRQl|@#wHvu4M;ox zc;=7O`0dLN`kb;J(k9W1`9=dG4tO6`H`gYWS9t@pjSdd$>SX8mH>w_LUi+L$QgFE` zUO@iwUB87EhhZt*ZW_(7!xW^ly~0IPz|!}8xrd%j!HbO zHGd?e33z2RI%V`InA}+AeU3~z1EZGJp&zvrYCcbp3~v%%M^MTbmoxS ziMJxr%kd5edtCyYuG38?*?z+3yKgGysvYwZ%Y>oqOHyHiId!bIl3(>{W{8|T(>`uK%&oUU|VA7ZQb)T(D9svk77 zdoQ7Udhkn0#%0o}`)lQopJj^4`Sn}~nYFnpcmJNFpKp-;xnyy6&oy0!rr~|Lg?IBN z{VcM0%FM6K9A=Q<5!3JD%Pylcer+mvw2*UgD~mgRT)6D~OST$o^NM-byXQ2fm)5yS zTZH;gi7?J7lo6hx*lO&yCw+9|IM;VNyMF$;jk*@)$LWFewsR!9b(D^e(*;5ct{#{; zVKT4NSu6BGS$f5TcX;;l#pxbHAtSE!(Wh>yme*fKj~=AQE@MW3D(H zWE_~KDQmjXFTaJ`9B97j{ll=BQ;6o~tMjcUL%coQR~+Z!kL%jD&PUii_%t?D$e2j> zvz*3C{e7cp!AqNckLggY+U->&KD{cBH7@Cg*d9aReK9!}aOF^j1ogAIzL;*^loP(~ z*iN;K?)* z)@j@n@4p{MS;TFp-o%Q~Q3`!~2>CdWLP~(O67~Cs)P&tA4cIqA`Ssad1+pWr@ioqj zI!ip}xX5^=>1u-fpzA`T9&cDk<^RYQ`tj%yT?sqGedyx!fed}++QI19H-q^h4F;4V zc?|}%!w1+{Lxfpn_OVz-vtg5?NB+Pa+vPMLi(lziZ-J<(D!Dn{yHw#d^Y`P_-RF2& zo^%_?baWd~Q+N@vp)cIig`5?eT;`}4`UBpJ%jpNx;JFZB)pFQioqtBXN`3q7@virH zJk>_dC%l{RI=jg_*t{p#{phYJ%HP7r3K%*YL4FGV&V;&cL?LNZ7mIB*2R3;;b8a0m z{=Ij20y1B0485F=eICR+?Nqk!$(yTMq*bRUwN9ij9776LqyL^=%x52)R-)`fu^ro% z1T;rp8^)eI#iDfbbD=5K?Op=H+nf8C`IhH?V(sf>bm@f72|?}(L;s)52|+?coRK?~ zw}EEdn?hibVH4`18m`09B=t5D{lg z+emZ=FzAtEdc+VkdT$w;#`K6W>Frz}Xd2TahMO0%n14NSwC*T6KKc@0d%n%6*g4LxK1`8x)-4M%HW8jjXP(2)qp=Z&uD z0%PPr8kW|8G%T%wXo!e2gw}vGEUf`)I9fxBLBB({Bhi1p=SXy~5XdJ6(Z?P0y1_IY zt$}GcS_9K?v<9Z(Xbnum(HfA3h&V&sbF}LT`NSaVzVC&Gr8S^!SXu+paOSmbmmB)J zVQCFW!_pcchgSq5;tX-mfix_w0cluT1JMu>=RJ%ZJ!8R`TY$DNZ%xmD0 z!_pdX$9CJIfxzHlX{7$j-U4f5LcsrQytL;E}`98gS&Wv<9SMX$_rC4>_+vM4Tb+IiMDY z2t>qrFEku~4OCS^1mdLHUTnje*TA-6&1=BB21{!|8jjYs`(;8tZ}f`e&%Ppc6rz%-nBZTo&h-t=Gj0sl-*=nR9ua)5qi7@#8tg6RFw->Yf-r+~(G zH4TxS!*07X47OV-|NCM7one4LBH(2h6zcZ5qB9J9Cy9Sz-PYo~?q3(~qr9DB@P4|} zTWRUOTz>W~KBkzPeXkZqWGDmJ^;5D}^{A+u>clGWh}H*qS{)s6hAmekFL0Qx8&Db( zWQXu69k-OX?vu2z42)P<@XxW6Nc6pw+Um8M<*6igPg%?&XMMfz$l93N>!)K5M*Jlv zqCX?amIr18On5m>tS_Sz5IByfm#W6y6?#is+ECY*MG@1pCb{Wu{n0D+!l}=TFBSrZ z4O#<)m!f>WRX4D|HBbV)dBp0;!~wr1wLdLo#0vEqNxsiW@V#v|nn&(9b;a_P{>PRjTdF`r z;&qd>4xknVQ6i6nO2p%4O){*y#`s6~%?cnQYI1_r>Ea?VW1k z>&B1us>FC%#xMDsOUvEd@-0iuusP?CyS(wa)}=1SYPfQy$s8}_c2tMc@V@8!4?T}?InmDEMmx~;o5jmoRGSqfTv+u`&xhd*Hk z5nU?T!L*xnc<;Nh9-I5$Ak<6ilaV#xo{y`EyRfM4=2b!zXb@napq3duD>HoiXXfor zA)X|y>r2BQI-~PU&h`drSQPdY*(iPabe72I=$cD~{H?F&-Ygezi~m6KPu%}1Mqcin zwOGm(ot&Ls<)2;ue&1w=-?fD>j|} z|E5ik2s$kA?>E-AlP-cj!~w5Oj|fByaR?d^{1-vHL@;avg8!lrp=3qelP z!$RAJqcvf`7Q86Bc;TOIgF@({5QmpBuNz21gp&6{W4gK!G#~&k3UPROFEr+|YA^Kf z{w(zD015_+LL6Skv<;@=Xbnum(HfYBqct!MXI=x+5TWEf+%<^9%X^{W%xj?Vy(mN| z8NxOoj4cWgO5O_%XI=x39G2FABZs9mAPq-rpzysY#NlPky9Np+i$a8w_d>&&*TA;n zXbnumnb*KHoOumQ!_gX;hNCr57+Vw~lnn92K<`j=)51T$L&3J;%xgdzB9y#`kwb)% z_d>(b+IG<>NWTT*@G_=tFbzj*U>c6rz%(4KfoVAN8kmNowe6yC(DxjU)_^obC>i33 zfowyBlJ`Qx(i))TnkYo`Q3%^W8kW|8G%T$FX;@kV(y+7!D9|Sgad;Wxt^sLSS_9D# zp=3{o4f7&LfO{w z;`a8nl?D0(5a=R|Lg*qC+pR2wkb*|&zmRcqwX||&HrUR<7C`^vw>r?j_OpN65&tW8 zv;C3G20Lf&1a_Ud`_D1E?HzH^orBc7cf>_Set%E@y(2Cx0&_<^M_0+I4@LfxsO{R2 z8JUo}ppT|_W`#<-IT6*xPti;aO7F5SHU)8%OuzbcI69g`%%i;fs2@A&Yf8t2E z_G2-$`N_OXf&Jy+p7^>qDDRR}4hy3Sk+)5kxXnx#C5CVLu&$ZV9vcaXxml~>S7qZ! zf^`Pbgd3p3&@wtTKho^Z@mR2{>}qOryZv#?bJ2Lm<{f)g_`Swo}wPns(t;kn2rK4h1F7*_~9OO^Plzx0M)WyL@ zp#Q^ZSJ4RbOFs5wP1+{ZkMXjr)nm?^(9&?os2I9_?9-9CQSeqmDWTa=mgChz@utP> zvcA@=yBF82_F0eFrUb`tTB4(dq8~F-4BnQ}iV5BN_RaP6f&F}H_%fOITc7rfC1jQf zu(e0EG_BNC84)P9=6=(NEoC|IoK1VP1Bc<9}Q0Q`7M486o+=34VDqeK>PIj50 z`z_Td@3qR~YYs(El9!LzEz;nID=ap~jK0Lt?+E%pc$ogAUA;SxyVPBmYj@@;DF%^f0$)~P;_Ya@=*e8JN zFs2oB{^pmU$Wqj62KQ%!kw`}NTW*+zl=9iL-AA4qoePFeXGyMtus6E}Y_?pF9JNb*l*e+eo zJlg&2bU(hwPsb*4c8nm;*!EmLOUo4UazkzEj_mF1ugLFn@x!9dHe4G&+ncIB@Z3L{ z)1-^VO{RxSbJQ8@=trr6eUWVTHT&4FP|qnU;`r0c(Y!v$T&k*|WrZ|!@RCvzCLTUy zns+8}{LwJpL34riys@aVWo)ZWNxL4b15LNa*Nfl2-t=63^OsR zR&M*xuUf$=Gk3qSJi5vyQbmb>fM*|Rg0xb}ZHZEb&qDS&!!OxTq~Ed9i7)%~rR;mT z#HY^~Je@v{fBdp)nN8#W{5flpk0wvKrwEL-*%y*2_1{UT$bR;+SWL++8+uT!u2nO$z)2Cm+1Q(l7VzuNrsOozS|y%2?&1CyKrFl1~z= z&FwjY@r*7_%Dz_n5R2C=t*E8YJEv&%unJ0pCE3(Ym^GN?w@dMC-F|-!Z=ZzvO+fKt zQIX#rhyU~Y)}olHNa&#L@9#`ekUzikcKzpFhu+n~WC2BtMMW_D6igQ6bro#j2L5HFOdE7e6?*8U>Jt50M%$wn89MqK?B%d zOY(ml4s@E^Upo`O7WL>45=763zo)tVC$nvPQIFaQHt$~4qtII+|8^+;TGR^(2#BFS z7)H~mTe?lIef5+B1vh`TDJ@bHSS+6Q$Tt_$8Hs&6lFLKIfBJQ#9I-jAq#U=&wTels z{r95{E99=!zoqA_Ey^=^E$15f4h-_sBnTx**N!9%hfjK^ zJe?C0y~nQa@KcXZ%D(a2oAK)4E1jDMBs;|qEDs4w`mWs9*KaeTc~$2kBnO2ob)Z|UH4~O7iCb*f0W~2l8chkeX9P&`_uA^eH?5*=9ISD z_6K(;sF~hmcO&7)ZeYw^7-G)uIIY`xgXEI$9h62BlOEN|8#=mHVsw%UOhqdu=@zd#bQh)ReZanp{r7kPj_b_ehqnEPUUyR1q+bCNW zFSq1gGVWsB|1|B+8LaPa*>4TM;0T^?#vLP|sp}4nx2iv@n=Vu@l}%7MZeo6dzyF@Q z#24KL?$CkS3f0bRJm{B9Cy4R<`Nv5LBh+qUUq04J z<{3xX>myQou#HEGrbYCQpI^xB$Uua}-Xs|>*0n#)Fqt_ga8T>JbT-!^rE6r@Q43E< z>rS5vA7%T1`?JAzi(2qamc5aKpQlu0va?(qermXrm}0DP)%_J&qrwBIE$y#mGXBSM zaY)&SE{u5h=ZoKLVJCi&aU_w;evZ$+{phgMvvW7D9arXT6%UaS{$bD1~%hKqFcn8CgkSq z?_!Ts4Pm#qFp8~^e|RNt)TwUUb;`g$mdhHYEpb`a=H}gyJn9Sdiw_>1Xe~D2>(vnv z78$9nmpaWZG3ZfT+vE)s?;wFjdwq7j1mKW1=Cvg$A@Eni)#npr(kw0suE)VDBgr36ve*SgYoSib?V&f@N z9y2}$T8bArmtHjAd8%7LAfeyh0k(*Gv}KQE*r(dU$dA`z@n_pxb$aw4Ytb4q6Sw zmX1oL$^LvoRB(`kXn=eT zJ8j4<9=P!>R@55j!#RW3&)vsD@|Zrnt*(wqUI>z=ObD+(GO^Smoo9B4-Sh1E8X6iV zoD`fO%bz9Lk*L@ibQ7u8#pj(Zl?b|g_luXKkLcXPcak6@5#dphQ*PB63oid&@aRJR z%RBjYPv55wFMjc{Ab%sx@C5aoR1`Iepkbh;@RyY{sfb+b}kt8-_&gV6tyb1JYZ0@<;ed$@bX zH*@}N_BjqG7bPxF3j=RX;t!EF`AqYJBG%Ns_l=HQF_ixBOUeswB{6#ECZTSSNUt+C zJsWa$>3PNX5b3!$HH>URx{^*sXBWjYMK0QRv46ECu=tiIMMNLsG<%4&=XxGp zt0fOi6QY>HdVrN9!V;E@|8tu2#%N7kbUe%Xfk@iEISQ}W{Q_ZM>VvUZiWZ+zG~sxA zy-IFY4QF_zN6xlI8<6$r65b}(7RjI*m5*!P`!L&+p_9c4?+7|Bt5Ooj*wbdDJXg$h zJnYh;fVJ(<_dwLB+WOjC}RnIA-=nLH=7*KU&+Xef!gO& zxW#UjOMnZ?_hPwu`Y6$FM7mUI{|8zdNr`&sSIobgwX0Oc-`Wfn$cDDf$R9%q{aIHcfLvmf(4TcB0@!6mVP>9T-YdY4778=(43h}l?#|(PDm>FgeEa3T~qeXwb&wvIpD9r3KOuL}xi-4gk(03oR zNC}4B?fDgYlHIAc{Lju3)JqfzdhsLvJYj*^KMP9~(1#O+__MHp?CylJoh%)6ylq#+ z5$zBC^-hA7r6Ytcc(#4sK?ps!pxZk9F-r%vJ2UN9ve0&T@89kz@}J-(%wY&I3!`5n zLg?3lurQoq?2`L`YZz$rXfGID?HfIuKj$o=cIV3d_l6;a{!(r~6d@!M#zWclCHyaG z1D&0?qYZS4RCwCh^`7`I-3@ebap$3+mjLh{%C1+=ue-7Rwf(n5|Mf+KDN*#@K+jk^ z-xQ=MjEAyolGzysY9~7M?{OginOR|tLl_+o7eq6{=-VNP6oxU5T@%)?r?PwEK%y66 zdzm;8=!0q7!$6>4@UVul>(H1w^RFPnf0FyqFY@2(>HjC4Zzl|ZF1mz%e!Fr8QM)o9 z|IY0b6%>XS28h-DHhJ* z?1wmiZhcm0jGjXbkh6QR5BgsIv^A!7Z)0xdsS>_v!M)b5q0v@49fB~Hvzo=Xx}IfC z2p?JX{5;@*f8|6fug}CLi+%n^bA@qE^plqO4W6R>W7v%k1(IuDz1f(1SK&yfFLJ@Z*m8lT z@=OE&5Ze&N{v%Sa2QD00+TiiqKT+_GBsEu;;6BUq)Ci{7<(_$;)ff)ud3=1IS)8jV z(Gi4M)A;+cs_ULT5-x|{Fm*Z<9U>ef{akmDHtjeG-VbI|ZaETphfeKCZy_h)sL@wb z>S_L$e+aK{Fy~)cdS5nP!)n?v!+$_`K`{%dtM)90(DjRLnet3ERel%t%)V4hqW=8^ zHnYlHH57`UX;DS8ZUp->@(EdqMvMwxpLTRUX?e9$HFDoYY@#^n3;6wYsl&l)W>iK6 z>m>a*Z{_%LOZwZH7ph#y?oPjl=M|u+MHznj0)5^sB<-i64Mn@r9DO%+&Ugn^G4B^- zqDP#hPuj8_ZyCyn4)5HrDZ{Eu81X%{vNbH2m4C+fwl!)0&}Fkibe1Ex*^54(+jMKu z@rmw(5gVqqt@7)vH1D2Ow4H2^tn@!wr*Y`CUZwIxbx@Y(4fe~(z^~oe)oRKd)FWqN z^Vyy2%(h4kdFMT?-{sI!x*q0iCW{Gu_JoJ*Rsi+k-cYtB&CcG(UAR-#R*HV^3`Y;P z#`Al94rhJkeL$3s@W2R`3*wX5Z83jto7jGCG8>w7?pP8HwW`FoU{yeoh}at ztfUP-HpxF^ZJ=V67hE{%C+M}`NK56|DM{<}=d5_f{4dYBP6>#e3Qe}yv|2n9?#`yg zVxZDqn_^h?TmbK5sro_2jQrxFAg@T8#L;6aFI2fci0Jur-PIhYcoJ2^#XA_IW{p267&a0n_qaF=O{^U0KF@Sh`Bb3%t3!TUZ364}vK{wFxzRF^9qvSOyiT0= zJDnLl?(r_1JA}Ph3z_YvnudfyYM3j7_2*UWbwN}gL|?>VSUlaQgcMjM)*8G5~W ziqtHUQwYyapq4v0(uK$Og!YU=Nvo@dIfMH#5_f0fxDj^`Q|D2HOns53m0H(Gdy>!0 zskqroqJ(%>rM*N?qJPZZ>E-d)JgvMmWI5h<`=lCW*1bjJPcK3S4ci`Yh(rXlN+^f4 z;q}CcJ6me*==mdSRp!V2`MT{Ep;S+b^9WQ+4i#+l z4Ton;uC-sfWy?lC7PHEJ;7Vd|l6qoS((w=10=>!o5lWkedjH>xBK6>jIZ_hZQb9nfO`jwd<6k(CM>^GU~hlZb0 zF8Ab_9N8?zbtI_2{o14efzM3PzeL-%Uxi-Rb3eg|v~9@dRDuqJp^mPluErGs{PWQ{ zL4p07Bp=_AIQU;UJSN8$B9nB|`~%JEAmE+TlfX?dD5PEex|RkjwsSa zUyhkHi7VY_1GBx(Dz(3iHTbTxl*<)-5S7pEbF)dDujPU3Gcps#f%)_^17b?p#z+P? z@?jYngF6}3D(z1?YovQc2PPG2e3>f{51sTlp04r6O*K&YjDObjZdMsh876r$&nI0= zy^&24{)TY_K|NL7aXcLBI+2U7SMg}V)T26`Y^AI~ocVRrFP$2OT}%S3C_Ka26DQk7 z$ypq8K8UdDj6|EO5#5YUlQ{E%isPiMRI4IyvR6+?D~kccNt)^Fw^gFB#@|m3YZl$% z=uWovw6jr28Fy=VF5J>AY|x|z9IF-uCKU#Mp^w^RE6c@O`VzmM9LXOCWx388*vF@p=<-P{ZKnz z&~M9F2pWmbMT8zX#)1-xMgp#TXSMw&+lYTwl)pc3%)Cqp+uNgt9{TRo%wKNl_Cn@2 z?-*Ue?XN3`de1(WPW+++u1r8MXCOiptIwq?B&4%=?}569O(hoyRNw9E{glOR_3Sg7Q`uP)t;cEmi=s@|GAl<8QV%Qdu< z{?^Gh#)Fm+QS4J0KN-<_$vc@0kAE;|NcO5I!-JvWS5)75ECtzZ-&S+osAM9Q)Jq$# zxVKLy@#Xlqv$I6G=~Yz9x0K0nz9Xf_11G${3tl_PU|Fw;`#d$tE77}odH<-Xn_zNF z{l=r=AtBC33qd#b4UU$n4Gm}Ut}_|d%%35PtMMn>zj&?Hdg8f#+`jU#n)NAZBtt(j z-qTA$h{Hoibd|>n1c;3UMZ^oLOe$53wy>5*c&aQm=;OJVy3?l}zEfOH!P9W7$~PGz z<4ru&?^9Zy@>(Y}PPUyVZM-~izldU8hoqDB>zUfjj6wtPms=i`84Wu?Lf*85n_wq zhQB<%+evk!EmKW8tms?g&j22&c>ThWlxy^Q{KZxH*RW~C4^9ws2s{%g&39fcmXT%a z^NkGMXn*LsFK%P7xxVdUk9KT9w!Ee(jzN1~OIqT^aDf_0;_FW)UOuj+3ZH2e3u|$ooJc>5O@O(!KKuJ<9gRC$GLN+*Z~$>cIo1 z1Yy@vKU80@GO~@G82wE39-l6dkq3UaQ&iEX2m)eJqjjd};&rK*VwEJ)Ee{>Jke7G-F&!}SVztd{fO`Bwspjd`$YS@B9)%C9 zfek?|Zz5wN6UpC*&xjQ&_SmFdNq(?pU!zO+s5AAHfZ$ZX)EGBUsFFimU^|(>nugv3 zm92zo+MXZTLJxRIYYvdSW^2ugpTCw#GXGM&LbvU+NI>~Tqw4oO4XWN3oZ9hqZP)Ex4tz8e7p zS3*%oQi*vh_4h|;RHSrsG=6vEi^fvgV)tR187&$Z_j7wbwB=JCet4|{m!>Q|aaAVN zO}M6I9Gg>Dwx~wZ<;R?=yb-rEp{>yb_v>R-ELuT!FOy%YIZ2#E9s42WYb#L@>A*c3 z8s?mN>hSVt=g+4U&dW02>Q0?VU?HA#X8){M>}s)~*q*ZXw6Cp}XZ~4fPJ2=UcWJZ& zPPJhN-9~xvS4;cq=K5y>8646K-Qnqc9#aR`ke3oYG!KTX(|%I4D~K7TN#pc2ym%_Z z$L9+hO@@sJC%?+-U=wb}>g1OTd^Ri#BrP6}I){ak`Rm(6$ylgz(46AA z;iSwZ$?qV@NSW6@Pq=mwA#fwhzCIu++<4B3WXtsMdkOrOb^{VS(uuX%=UrbJZPsNk z8qd#NOk!!}@UNKoP#@~oo;4cYZA85AoM*LSNh+cvx5)>!etBPtnsze4`$q4P{yXln zNADY0ZtGv3IR9xuw3EK$UH`xG%Q4Sa2%&b)Yy8{K5(PAbMq!??fTGb}0;GS6ZiMvj zyR+lfW7tLl>JFhG%DG~W90@3jio!f+0WtFb!9V?dGaH?`{MTycmoJ1qjx2<(W%2u# zCF<{8${k+_wd2h0-m=_j4Em3U^_MS%6oBap-HI8$m?1!0{|ayEq~sbeuie4E^G(lh zMD&Hz>(X{QU1IpG+-HxZwkWA``MFtVlV)WkA@}LAT8e0EtLGhcp>@H4Q~CET_dDJu zsMm1z=Zd&7Y;*m={QwJFchO~XCE3u{1Mg(nuW^}aUSn8`=2SEn6%7g*RCA17Mfd7F zanDiY+~f>NM+!e`(%$YC57WndA*QRxw&rjX-f28#pSXPT!rdq474K&9XHKw}#2Rvj z_mOb0I65?OI`=Iex^5A)i5HPXiG`zBPAhr$7F&0q;iBh4RbsRlhc2lf?L5AozlG@& zT8Rw>Yz>6RS$k)e0HSJKU#ju2fS_afEBc9aWkRjEVc9&UGdbBB@p@dj&4dOSUn!Sz z^MtfQiBDLxtxwclMB4p1?Ymp zUp)ywsTme)>r;k(^5M)kTNBR;)ptF!i4R{qb##1RGNNK7@l<w<-2vjX)Aoj*fWM z+EebL+QqB&wOIWPQTK}4m{w<$r&OjJ=t~#eaW_oRzhiyS>}L ze)ZdB{WAB@bNin!OAtNG)2>!7?pBT#Ud$*lKJ*zD^sl5HFW6f#BepME)7i=Gw7a8) zo2`@M|HIy!KwExP<>G;Wgh&J-3=bap9}y&h^vyl8&ehG8y%j>*fRC=nc5|W6UaUj%z(ju_UO_bksP`E0 z(^%QaS*r&}ODl~_w_yjc!G9Ru*g0C+KQ@@69ZMVQJH~6fMh6dJtOItv^cZ~4{e5i~ zCeG}ON=wTdxRT?vbCwb1J8QgWbuV~?lwaRi8;=g2aQt~sJ?q2g@3`&p#~icc?6c19 z{p@XS_A6(ceY2b0?Kz{Lz56ZR_sUZ){o5};@?l^7+z0l2Z)Nkn54h^&mz;9&UN64Y zIrq5Po?m|FZEktu%}>19**|-mU%u=%*Z;>$e(A?IT=a%xF4?~8XTJTuKm5k;-}E+5 z`}pvm?s>QE$K3ptmt6Frk3RgqCtY^jsqbDq{hV_@ap3v)xZmQl*4MxKq-Q+snA@Lu z+z+qb^Yyd$Uh?Rh|Hi#K|9tQ_?!EWKKYq=_FaP69Up4GL|GfYHu+cRiTKbhM@BYl6 z`;+T`{xjDNxBvVz@9`^_Jbn4pmt6VnpL+BAZ~w_#U3IHZ{?zy>ANk$C`|p4I_CNp1 zdq4Q`@9h1;BR_GUkG%e?FZ=X)AX9`uU0 z{LU}DYwwe^Gz1DvU&P5ezEma z7e4aKU;W@!$3EpZ_pQC+r@!)%1Mghe-G1xZLk?X1zW%+JcYo{KFZ;$7fBmS>z3KGT z7k})z=brn`2mR=oufOzTJH9-)9iB7(+RowK{`Ir(e)R+Xxa z@0L$}Z}Y7`dfW?N{;#+F!)Nv1cH%dNm)_~b+rIAOUpV29et6rDyyrG&ov`{>zxR)C z{N|zKo_p&5eDRAO+9@%AVF{9SH%@Uf)d7hn7GJ6`w6m)-FlH@)edb{zk% zYu-%qi z!?#!d?VQKn;b-6S#h2aVcTOK(^n?9h`|hv)$x}Z1s&gOx^=JQ&`#j>Mmpb`^y`2B@sGaa*Sf_MGvcTRukou7K;@z;Lop}+F7JAUNy<1T*L-@N1hTyy-Fk9*{uUiLp; z|I#l%a`f^?-t4#D@Ue^D{E_Bu7N2{!zdvx#y_>K8)?fW_@AdDy=j;CTxJQm|`Qq#D z^X>D__>DW<A3Si8ueo<6rtSC%*L6fBSRo^EOU=(HT$Mc+Q>gfBvhF`~2^ma?0g*?>=hv zWiR^LWv~CySD*g%PkiNj$KUKe@BYDSuYJo~p8c_pe&_xFbje@--r*~MaQv~K+q<^! zPag5%gD-!>>Y?2?{Lv+^eaAa~_^fTOz4*C*@vis$diz#){=4RzcD7pI`nPMpJUab_ zul?NWv;OBpU-!g+AKmNH_w0Y-_E$Z4w0P?4-};X0?|IJ`zUqd%KKtF*{PoAb^4Yh4 z_L|e~aogW|#MO7X_b)u}aUXp1Tkm?@S-1P*-`(ZG&wJd*-u%?}+fRSk>tC?)?1wEp^BJw5I{bkz{O(hJ`hCBB$tT;t_UvsZe);Raz3uW3{`+Un zc*j@%>Wg2z_TT^X(HGqPw%>i$1^0Z-bzj*3)Bo+*Kfm_d*X?=JN3MLwvrl{NEB^j* z@A=#ruekUzD{uclC*A48_qoH}{^p^teD2$Z5BF{V!|3^MUpQuK3UkK6J-FA8h~IcfRq!;kUixFP`)8UwYdgzx8&{ z_?zc`@d*!m;dPh)!_Td*-r^6=|Ky%qfA3Cre(q;~-2VKxKKd{J_RSys!*5@3uYdff zAKq}Q{kPitusc5aub%MKw;uY;=Rg0_8_s#%9d7wIpI!gVp_9J%XV1QN+vUIYYuB${ zeZL@mk(J^1-szwXUX`R;vAddXLhJ@#8SX?*SppL)`7U;peA zzj@M^e*aF-x%DwuJ??+s@SbBoeeBnt@TJj(g%Wc7E;0Prt=?AN`H%j{VN>zG3~xclZ`iee$m! z^P69K!!gGm^Uh|L8@$R z96@tU?#+5bJwjWaA7K7vq_uL+6c` z_8*9!S(4vwYxZR0EsyrEVIwv4u>bZ7rNCg)!P`4BIj$`Ta$sgSg!7 zoVC%)qn4Le&RyL&u(W5}dE?O)SD}IPH%7}#douB~F+TX2jrD9$L!zDrg?rxc5e6#;rGB$PH4%u~h5{HA!a~F5fF`jIYR3p>?$XNYHX8=M z%g3{=DN9`=8t`(sIH1k|2vppWl9$DfO0zU;K!a?SbSa(BQWHmsx3v(;f;-Y|3|l&P zSQyYwxCSeg@oY7k9yHA4Xe!BDGp46imzNMdsElkI1>0%6 zBp_za(&UlspKxqb7!OJ*W4!is#ye|#aH%pVXT0X*d=)saWZ7;h^k#70BF$-&;t&(f zK6!<01@+Vf=NbFqB*(DVnp{sg`)#A7*m6*c!$bkmrRz{xO_QecPc~1Px~=k{tJ@~x zqI0R+*-SV??^bdreQcmsy0n#0vUO1Vc5r%aedEjnqct-cE*OPR$VEWGhb-;gIIxb0 z5<-S;bkW664}bHgkMyTLwv6;0f8!^9M;MRg@Hb&693Q`#^;@llT6x(Mj^~9sJ+zsA zLVsQ0!Iud6LmWH&%p*bg%|qjSx`jW$A$(G4mdIKhq9#kJb^MN_iN9I)gyTE-w~K!C zgZBBGSmy9J0)EH8i9;-R!ttufZ`cZ2U`@8bze!Df&z1pW8MTJrEEC6&C;(U-okkq% zSAV0uNK2oIh-dsAaf~Im`fV*lyz^3;3DTHj!4DjI8z85IE8EaL;rKWlM~mOv{~9y~ z0Kx@kvkp16>@5C^mO0t@Q8ye#qzh>>L=&VuD2`7mLgwX99I&+(k#6D}LddrKp(^C( z&^!KOJ=Sk7qV32h={F0o@$4tg4F{Zfi%sK4je#u0j&ByTVO)TOv=jSb*ZiC4op3xQ z;@K;R*et8N$;KU_D&q5X z_i#{-ee>@wvg-)XapCub58*Qr&L40CN~>1*HhyMpm1OHYf`=cDi2cXkh66u!&}Ikw z{2fdKp>2%0h4$m9_&0x8j=$L>ap7m918cLEen?e(%i6>vw!+e}@Az9(;7B8$VLWk! z!i?Bee}HN1?}X#yUlGsvJK|WaE$oP4_z7dkI3eO3e`}^NTei9bdthB756LFNrk4Ui zRnV{r5dO=-@&{$GaZhBdLni|3q`LT9-H>m@1*8us4o3{^ffQb=ZBiuQ97oLgH554G zO>zZFLzuBa$L@@3}5O z=;!aO2#~G(6^N*rpT zY>ED~?2$g&$6;RnO~&T+^8+OPw~@?FT>3cd!taLIWgZ|gng?&FP9RS37axo3;u;5@ z+0}f42gG4fdQ6ufLLWJ)M2nqtaQ26tYK7r6O$&S1%x&do{>?KoUBJ6QhDZQqsv&H> zcW`dGO;oYXi^w-nMXhF7tAUquDJBUL84)C4?{Rn>$X0}vOaPX|6sA=83w!e1mrp+t zN8*|P5WD=xCR2tNaEyHt&mPwtr-QN{Kf^#7QilSh18m#CU{Zjv?UDfDYY%J<7^5cH z&sTg|fJD+@j)&OK=am_=*Zn zs})5E4-euyLpc(|gjQLE1lo`uaq5lrrkNl_9_znqf)K_Z8!kDSKb)2cUP(Md5rVnQ ze~1TvC^O?6MKKb|QvJ;VS7AqIf=on*YjQ;K?5Y`3x!r6&F{1G3T!u*WAnCZ#QR9ys zJc>*5d-#dp@pFrgA%4@*GoLbkJMDM1C6+apkl_$&aQUfdlTIZ+ccOEOzv;)yQOEZZ zb)g)~f*tU$+*d+Fi5^`}!0CIlAO6kX5p(_)4){$kAe2H0_NyAPbilNQ&m24Z=ijMi z{0$uNui9q`{Lp>o=NQD{@95{V?>wj!$DnbrUK|g9hjwH8`R^Q4$3S?ZR{WjUnD)2L zGkuHTm2m+=vLp) zf*+AQ$#Z>e|c zIKcS#jpj(z9Elv1|0Wf4K|+&+KP*!=O!C@L+bDOVal+jj$c!!bYIsz{H-hV;g#oIX zXd&=OMGHf&ar1~WD{KX2m_2lKXx~{tclT&xeKN$$_}mfqsG~c1k0ExP^|Gik5Er`FLSYmLpeump0(#U z&*DO5Krr&sjeacz8N9I@{o2S8q6Xy9|AjC+rU9(UAIh>DgX|%JD5N~pjj}?Jy^RAJ zM=nEjo_ww#do+xyG6cyHW(Z_jj2y-C+IYZke%@LaM*fSxqqK^j`J22MPA=oGsPOn( z#z+QZZ5k=sxWaF4*2MC_fo5G`v)Haaqa@=m+D74Tl!-by{<(7oK7CX*~QK>QDQfZPlSh#Ff9N z)yluKxfGR2tL%rRh)@0GG6oY!jIX1gLs*;!{%!xqpqK|0!(JUHPeibPZK6{+3}0rH z;kIDq+_H!o2^x?#QnS54;Yg1tkZi1xC)tGd>BL^FLW1LWw1Hzm{*Duq_UV99go)fJ zLk8fI(y?Xy);=BEGpTsj#)}0X>=rWsGQq#Z}{yLk1Vf zYPJWzxAjZC%un%O5FL;dh5O3HTA2 zAQczpD0cH9OO9eYzjNa@k$ z{Fh{LAK|u8IM-9%bBi0TlNnbeA?&`ZZfU4RNA%QftzQ6 z&5jZ9-+=rEUt(Jf7u>F|Zmw}adWf8$GjT){WdMnXj?P?LJ*11VkV}k|%`Tp&>4*1Y z8kZvR5luDhOjKn^#eKG!n4}wp6DE29M!0Q>$@Z0%Rp5N7o{`;&d`w}bH%ReGryGE{ z0hLLI93OqcfQY|dNy0BHf)r)~b^ZarXt3^NHs2YIOZ6xYKjd>)x#`{<Ycm&fv6^saLb-o0O(r z6bF;&h3`noCNiYz2LQ$tpv`+u%cjDdb0Uc3StlGW`zavStURec=OpujB{V&O zneiM!Gx@n`*XAR?MC=-vuxXEnRk*NOd3=3d43R-t#ZU}Aopx9YP2KQUT?|d+4<8MW z8S_pMD&8kP523#O9FR-*Ko;PM0zH#G3^YvApOilV+(1Oh8qQ6%bvHZVp=?MTE$fDU}jz2 z(1WNFvn{DRby*M(WQP_gR(Yts%=-mAY+dDCM~5h&Hk)L~cF#y;i?vFk%KLqDqozQ~ z&hzq%gUx^aJ&R;bS|bI>bzv~dSv0Bt$Dz=K)*b$WE!#ZjPP`Y&g)wzSFgnIsyx zo1u;dZ#p9h2|Nl4nc+2o8Ls!78?#`=wN2Ru zo^G2_S6PlqIzI{w^*GJMYHY_>Y^JbuqMa?~h?&+^jmvS{#L*CsK53v-Ty1!hK7yul zd2zK#rcI|%$5aF6%SC#})J8ss44lt9haK}b0);x}k5uNaBRAv7QR|winWNS+cSB6VmfL(KPDkSR45po)i--BFOJ zZcFy&vn6@kSpg5kIgW}RQZ18HWub>zEYp+PtkHvPKb;)C{BR!Q2T&3!6pM%KSdtge_q<@K`{Uh68i%r}9$7EW8yRFdL*GL9Rmv z68uNL5(mjzOCi;P8*5wY_Jo>QW{p9z$+!}E+7+{1SpkFM8xpfwZ(zPfz7hwW=+JF# zp(PYe*67X{LrN%0kk?R@5IPDf)`i7jK4CE^3X8!U!eWp@g)ZOytxcazmmgcsCa26~ zN_EQSh))#pu(Hd70K#F!4T#7E6!ffs4uJNnC6{bSf@UoPr8*_e?z0#I?QkRftkYO)QTF#D$1V_1WPrN6<`K zaJ3<5MsJnLo0ZpaOCr8Z!4PntpkN5bPo*Hayk#bI6&Z`&PZyqLWZE8HO4Mq*A+x32 zA~Nk1Zkpipwt`qkgA8v@uBg9e$3g)!DH8$^Rmybc*ncWhrdtonH}McpL~tr)cujeM zsV!J|G?eM3J7tnhhaKP9BAnCIGf`HhOs{yHWYQ8ZQ^viU3>o*krcx$E8fwVQ5kn{= z(^t~Lgmu^CMd5*urXtg~096&4{v2_pX7Iqkmzki(TgkEGw3-I#_2=mdY+$cOSCKIv zaR$$nk*V9?&B1&IILei5BY-)+qd_LvrxQYNI7gh43_L7GC7Wg$9~Cn7I7V|gM<7GU z6vGv$PRVc-$Y6uN79|BN3BqX%jB+JQ1K>H7XNhO9Y7)%UGh&G|O2P_86_ae`rX=(f1DTVZ=VXJEh*wWd-gX!j~*c=`R&QKs!@m(ny@ z=C=glRAk!eEK{zv@wCCwGH}O^yiHK11y9}Hf6fDgfj_lkFfOls{iW%@2t5@qBv}IqlyfvSDH%P8hP3SmiFQVvlb!V9N@ymV;A~mI~;hPQ=jZ4Dv#5K&*uH& zRK32o!-I@^<1F_kPJ8RYk96`|btaE5;~`qqoQ_t@EC(DN4-9VN1{`6Z?T8l6TH8Xf zBTD#at01s`+h+F&P&2o0I%>9x8g@7YU54nSorJ(=3x(nC2E(*#QX7eZMq|EzLt6LJ zZo#~CGe>DUawbppyw%%(LMZh-w=>J8|2tfd?t1IxlJ#^qP)Z6k3DW zt3`zo0XMF*=rRtM+x|M>#&3b1Ar1&WbTKw3!v$AwI(i|XhV}yuL(m8S3(nw=c&HJv z3ZoR_2_|Km;p7faPTTx8!yFs|_XSgN2A<|m0-K=Y@JE==;KSf6^B-{PG6ZHD!EWYh zTXPKYhq*0$hDu?GBS19Bujz=k%X33}XyG#i{weD?ba7$#z1iY=lc2%j3q$=Zr^djn#dP_3_%$>gIZ5 zY31_GwW}I?M$AxYjFwkd#*NE2*Eg2-UDa6MJalMnys)rv3aE*D(Kn*_KkbrVd1zy0 zb9ou5Q62rkkp_B)5zyehXnf@q{D^f8zwPVmsR>OqQ8Y$0O8y(&~ z+ViM&&i6D_f`WEbx`^3rjY_0nBv+i}K&}dr2(6)ZF(&hjrTvW^OM5mp*D|t1J?^-4 za7tk|E`cnTgwM&1W{}P0(1Vql%gSTYp?nR^2kyRnyl10v&JtG6Ijd_2M;ktY(b8~C zdq~WwV;Y$#Qb`i4Hi3lJy5GKc@6yK7>dI)jan5MZ(lS1b*E0-)Vss3;FwwWhAkJGP zjbSu2`3|;E>pkOq%sKBvy-_4NOgS9Uy7-o0z%_4UU3AyB%pZxz@9`$KE1``1PX7aA8In9x>r1Q8_M8bRX&r*A|!2)YVU zi_*UhgjWw?nL@FyZ>(+Z;ra#gP+)sU8>7a)wbg@@1fPy@8wc}eBOEB2mQ6`X46Daz zjSD&n;nCXu&4Ws6Bcv5u?<3Y3vg^Q}AyU;j)1(I;I zy!055nbUu8wC4bLv#~bbzqAfUU1yQT{_)CqZM1x9V+O)FB{Cb~s9z#W1Q}XEn9v#z z_ZAxW-#mB-sIG#E*EdFM8{0MyHP%))H;DAcfzd`|kMT_4KQsKCve~h=x@Wu> zrbOe+(eiSpB%|ByLaGgKzlyi-1x38lxuPJRd$IQAWcQR>)7q6PMh=eQl2q zP-cyT5Fz!fctXITr9m9HD1JAAprFvSN_=+bL zFDa>DTvS+K)g7ycL{u>%EW@G@fRU>&)|Vz(wT>|&i(At)VjNi{&^RVQPGT%*ZE?X; z+^I?CI=UPdjTVyGh|w&EOo&EU4DI(Rzk` za(HRufTDrZuxb|qYPB^SDU`4(`cB&Ks$r#YbPE85&WYSDrA`#+}kE!+* zqLk%#+@5Icx@u)(bfwQw*k;Vk-Cn_HtI3~8h=fehrO*ld8e!t)YmE!f-?0nJ$^%Xr zT48t6=ZnWqXVa+SH^jVv3XLeHYG`GM)S~8bv((Y)Vl#3!Iu)lNCF(|ZJ>38n+;vO& zK@2;%>(!XGOA{L;RE2^?5q{z3 z^2XAk<*Nc3h!i#E!=zmbW+hC55g2Jz!K7&VZ(oC^JviQ2+M^ie1*`j)U|es-YjNY- zXLl~kAR!opw;kf~9f@H|{tB~e``13ZeEy*_nxdPqefRq6@+PD!eU!`)Ogr`&;@D%Z z&uK-f$2_%4-$kiD7i-lC*u(Llt?`I6S`qW{#(_z^swr-!{VI5E$!i7FFmJO_s|bEO z#ZsNF>Xil+)Rqn&iz_T2i39a!KRswRmEVBN6IVYDvI$5td4+OJe@rov5JhhC}xdZMHwIU zcE;m%@^njPVQsxvBznx`pCpfDb>dD-I?gZMs+IOVApnDyYrN;5 z74dCnY-`+I6yGxl&_PDp?EWiK+Hw|}q zMwX&gz3afzzKzE6nE#l=nK5h~pI#|)S;MCyj$`Ukg+4`bT*n8JZ*7UErKTf>c;n28 zi-bvpDvDtp^{Y*sA-1-({{W8eCQp4TPTkVhTa8#1@f&kQE5s^_-!e|~5v*H^Y1WXd z7}^#DtZ`OR_~yvX!&=4t-x@*{13TV#QlVB+7#9$lk5nD(+0W*!ij=l<^fgiyrS#6# z4XSvq!l?|^o=2ZYk|ZV%(yBtDqPPx-h|4ofVgkjH%B709ww!`0Nvg?vaihSFXH2zt z3V7k<-R!BV$nTgbKS?1$>tY}r8<}Nc*HP=i2%L#pBvG8am|R;yttiURzH-m<=K2!O z7fopWIwoDP$ZSk1LOjL+E0`38_$6DY{W>Zg9AcY|N(hiwx~6QZ%@6LWz#jj~#JcN=j%@4G=6;_p<)oF+{zdsw^T^jtTBcfWy4kxa#g5}<$~4G z-UjkQu!mg=>C~y&MtWq8nkm8?BfbHd3N?!|To}yRH;zhg-FFDwM8Z`1E{bbx_8x+w zUB@2z7Sk8gxqSMH=2CTYyh@OCpGlg&D7V7&`K^+6D8E_T;ix9L1{pS{WrY)oI1ICr ziPHk!rQm5|t2l8*L0-L$qR@3?Gejw-Ju1QJx*XGO~5JMz%)6bg4Wo1S5A&1 zN7o6{g8?ubsfs+0_eNKcDn?8uk&1+x6T2?bjAvpONfbqTe9NJNT~VYHyYZuOGGyw^ zbxN_e#64l=B2f|=!S1Y}QIzLIW503(m;*3cnFlAFC!I~2AlMU5MSHw@{g!ih9jEzN zy$$1RHgXliJf@yk2v#=CD+J4@FQos@5L&hAt61Xkd|?Hfq7<)9Uu5&?XqEQ&YiL#M zaV6Lz9kGH|QNT|wy&9X>=+uKpJ$u%os*M6F2&@W1gw7C9La4}3b<9e3d5u(2lt#>o z;=FnTi8DKMMrRX=cbUy5QjsXqgEgX@u{K)Sb6`sqT%$!X#$8jv6STmE0Z|Y{6`Zbu zlnO?HXpKe-s+Oyu8e3t%+49OdQ1bk zN_j9NCXLUK2nmfK&=ZLwT1A1beC7Gj+BaHWpMh6t53Gh)oXdz;QK**_dIPh;8YSslg*sHu2%N~ULRPAg(QrueH{Z%R3J*HWk_lDkC{14w^58kU6U3Y&I73m8Mdv6kh~OM`CVi7op~h=OJ!7r{ zwsq4mbJZetML}QAauu-(W=y?nUi?&tuk_Zb}`Dn)^=q}Jl#lYyKP>86r{Po~Yr zX~LohjzxhkXc?!N(>E2BZv7Z(fjnh$6vedy{ab|D>$6k5dnAt34rylSgxN{i7m1Cd zSPE>Mazz3gH`_HnW{n!zg!{-6SEf>}aQGHz#?=*>s3@%~7lnmuchvK4q!3N*me=ss z`6hN7c{vgzpb--vCnaGyTU2O7p^S{tNqlNIP8AH3j0_Qrbjr-N zyknAUqD&A!6=aI?_CdHL%?>>;bA!6Rs7grH!_ox`#p#QzQxwHY@2pMN7DyG$`T};n zx~*Ekt|FnMx|$@JptG=>IfuNja`s9ql1FN`hQKb}*HBjQ1%h;uBvk3YdL=_fpfFuA z-6&jc*Itato^}kPy_m9pf`YGHi4iJnf%~LdE8$!}%!?K)0h9Q=Qf1O8v$p7WF81zV zF<0o7^&M1dB1fQCOvS6UapTf=p3T+d?bpjRH13oGqWtHqpC_Y*Q4~ zO55CmG)2W>k9F+KoYQVS;9j6jMMjs>GEs}7sfLZqXuCe9u(S$T)l>EgSXE?mlr)nw zCg@D8DvPX+QKx?QXaS?5a4zRwp2gb=FGSWcuLRs8m{8!JHeSW3a8F%Gr-c@{C#CR2 zFDxY+1}`k;V8v|i%2m9O;zdK7=)3V)!WL-@6>QN~MLd^MEm5i{p4p?_@pxcqYR|2b zlaYE3LNSpcDzjo(qnvZ;m@H>|g6THz8KSU;Nku%DQZ7N0qIec2xchK|BJoyn$D>z2 z0GiE3q)0_1my#?|t0Xy;i5T zsUNcrE4USCip?Ig7$-TtoXlCrY7Sa;ApB?3D%kBYFjmU#Y%i8`JCS>WJB&6O zhKufUSI91t^L1La>*qe|w2HJzSQX{I(i>}mtLVqq(5guIQlcj~YYPc) zoaMv{vzf70om$=c?Rf>(s)+egiYL(8Ld;hgelE1|3e(JW7^#ycG03orbSTRDimu*k z_t4bjR-Is-dQNPCU=>MUO7sN5ijuyJ*&G~J$XF;~R+01Zt@aAdigI3CFZ-hDZANR2 zV)3!4BNY=BNgtCOC-ExhftK*9(W`)0{d`k_UKLqi$^&gLmh(W(vpiyY6|TIf;}z)> zdB2nd3cR+A!!#=MI0wCY*kql#CL?_kUPW=QGdDGHU*Y_CT~6IvtbT7(vnnz^#!V)1D<_6-h1(o-n@?^P>7>w4jkAzM zKi0+4IvjRB-!fR+JhWvPs!pwL-IJ|{p^|MFW-?~;rLEJLB$~od(e>Txh@UFdMiVZP z*Gov_II_%UETCW7Zb?zHh;C8v>)o*$3cKuTV?Glwwc}3xI(WRI5cdaymz~HG5hdc#Pi80fd^ck5oW--MrB)&Rakpo4$efvTaugJ6; zmv$f4_{GccM(Os2?tL4({CydG#`9?Fd)AhA1hy};;cuz1!pAS84jF9N*l*juhGYF2*#B9ui_K0>$ z%)6{$UOWj2hBeHa6PWXzr}ZU1kb%blqrO32{a(BZg-2BmPrjH^BY3-hNm-rXk-!P< zV#uhRwYq(wSLKu8eeudFuTtSd45v1X&v}#^?*-yQtQEcXGo9Lns~ihxM~NR+W$C?L zyu3~^T;P-b5u$AjK7n?6@py*Sm{fLHmoY0i(Yq+2%W1FM7Y6?|h{Mj9$={K}3GrZR zRQS87(zY)Q4e^%qH#p@Xl7h}1c4d9sS2dO=`MW^zUMX@_qj;cjLcHk9NEsDa zrMW1~p>r1T)N&N#rgeI!HZZa+IDQtl6fVLpuz7*Vco}z6i0r`Qx=FY0Y+TxbXTlpJ zdvL^i1HNW`+Jjjn(E%@THaKB^H~Dr!)q7P$YbAp2Ant{reWax8rnHzI{q#X!oGm=guj3DLy=| zt_HgTh;UY-hZf_*Au2Hhc1^q?pE_xy(N;((Vu>C)9op4~YNFRig_a#KG{8Ur@1&1m zpoGY;5`ByV08N?!&?W#u#j}?_dIdz<(~1!FmJV1MG5Y9t7`BgDV@P7yP*h=v76w_T zLmRteF~rzG9q(d@7TfWpq?!aRI^n@}e8$wB(&#$cpe(yc^VM7=Y4Xn`-5|Umq6RP_ z@eWXZ22Nc&{6m~UTz+;kfDQlzb5#X^QQJ%(RcHbOdR~EJnoVGU9@ChbL!xZa4^<$Q zx)y@4&p@$j*1UZNhTSM}*dxgn-*bK8wekaxE?u9`6Z+Xpt~Irvp|EKPN)r%|?87 zu5LBaWzXr?A=C9-#sff> zakDh4yt0FG3GX?^5IU$x88a|U% zZKFQ`*oiG;18~ZerYSYj>YC&T76Gy7YEjzpL#x7BHR~aKCWY->paD3|W`o#gPO8}r z;WMe{Dzb&wHwkb~?Q+_&KM2mX1w!%Dx5pl>3C@sZgoV$VA%$fOzzMPREc;AaTFRI{ zlPmi65}kb}9V}7EK68*tPuf`*G?Q*eX3VsD7&?g7rogEP8h+Kcgd6LDRI%(I`;0MT zx%%oNTLR!?l4;S-n7@ZXqD0c+tsX`RfUhvKHR9P(PA83xVJ7+elpA&@iO}eCkvHX_ z7)SJh*DzBBTnj^2nRr8hpR+E3)G!mDeY7_;X=rmf0O^OBI2;fo*SRe&ouTPmVKks{ zgyKzdY(QDqbUOkTGYOGNhQmaGqSSxthq|~qvL_n`t z0yZ%KZD$eyKs&w)7bGlm>;Q1q#}PFhB z2Y|d{?yn{Z%78R#y9EG<*vG^gX#gYK$|OPpLP9bzH7+!O#6o6tSv6_}6iOCLyGgm< zp55H_7@ z0o;&mO=q3Sgyuz(32AaRYGuP9uL6<{gUs3N#Y6&;k&+xBpY`TI4Y^8bHwQv7pN@f= z&Zk3c-E+a(ZpsADBq<=IVlSHwGbn@VsY@n$8ATHa5(!g8{5#BbK3zwc}jaYFaxP7t$GIHJ_{&*B&4~S*@0+ z?nm^X~;vfdBp==y9UQo?> zPR0lz*)X&$GE2PO>WZCQ5CNirWlN{k6+6X^cg|vqQUfa?sf%{YR+Ag?fT(Gg-GEp_ z8Pc|NTzNttb?DciD<4;fMiU?!SHOpE13+2RR3Ct31EW9&BpVpzF(A&cAWH+{)6=1S4M;W$vNRysD7w~8Hj1vblZ`^! zcF|U5U5>HCvK*5KK!$^4u81*x1v%xCxuZ2PM21up`nczg*k-y*{s3fIHfpXCK(bMD z1ptzbnkxX1Y}9?x!TJhQPEC$NK5QE3O_(bQpB{xMJVbA&M-~JmE2A(*E8~G4#VQ1# zGeM8)9uOZpZc!E_)1}xZ7s#i)N1FiEeDJ-NHROZuQLLhj557mS3P^@D#VQ~f(r6fu zJZ8;Ms)aloBaaMR=p`ox=#C;8dgvK|3_D6azJCzs(+phqF>zg>syMQ=;?PD`#TJlI zHAMPi;btZnWE!;Gjl{cH9AG}4zIeKs@kC!d4c+h9(OpF?CyN9p!ZTiko(PI$lB+Ks zMXV|-#A#|;7*;x}Pum|5Cm!u^L}i=-`lJntI63>WXfxx!-S1n&K23cwHcSzA9>}9v za7VP=r&ZKK1jrGjQ3Oavb{a*1WWDUBetwc*(m5Z03V<40ewqAe7-3lK~A6VfjqYm4h`r~<6)kBz6Txk zmFeUGoePx7rWZxfa)ypvzz}xRR19c(p;9(ZIfXg@4YY!T2nzVn^n!(&H4PG?uWX<_ zO;;w#Reg9cSTnE znGLLvLoQ*4(meglC|0}x4Lf%uOv0|RJrvloBx*k=vz*Sp1+{H*BxE3D9Uu9eeUCA> z5insp9%TlEtkKWxn4x^mex`%9n}r3)hU?Oc3|*F9LyBBTuMV7`0|hP7a#o85Ja{69uoA>~ zlCP!`F|p$<%QKoDVM!vP?QTYt42RQHInFlXZyi`Sj*W+we|+4#sWfGe1m*4wD<^!xI7KH0zQK$_qP&L#@h%x^W#%FP;si^cv2`mMoCOt8hT>x2u?e}g zu)VTDDKrtbPmMYdoGvovfGQ08mN?cD*%Xj-G2c@D7*gxh`RFd5+Lfz zJl4ASA}bcf7b=1^NzEQCyh-cJzJp!w3qmS3^Q7S28@A3jO`6T9Hs7{gJ#bqBWvpcv1@Qez47Vjv##(k(V(bGbI|X6$!Tbp)do?$(p9FEi zEvuz%D>0xW^L5d+UI*F$Ef3u@>L|ho!_Ud12p=Fm#S+{IStStzgA<)M-6+5%kw6*# zLWT}|YUjdBkvg{k@J=R6WOJtt8Q)OFy$vdm6njU?f!8$!Qw;zjl0GVanYrE_i~bm! z8BHYPGtaxDrH=SuYD6Nxl}R2geZ&jB1X&fE;|^fp*(s>7MdnF%x{L+~H^|WFDv;qW zLWkxy+RPeJU?US9QZubgkV)ROGO;CT)AERFN1;(fF%zPaKKaHD!rNfbY?}c9OdQTf zWpywO*feqg)j-Sb6$O%jdsc#LFxwo0BoV+#NYT=0nXyurO>C(wn|fS5Sot_FRuxV9 z3J0$fp167zv*H00U;(_ar{pC>bhnWa00RVjOj!xwi8P}O00gk5owi3T;Mfuox9hq(vC?*C?b=SG zYl*J>&9;kjob6&8F3E8YQUIZ96p=h^yEE5C!aq3Amr+*&vhD1w;|GY$I7KAy+Aa^f z5@sD|=dNTIb_)`TB)i(C?F|tLFT~2ch!lwXhg}ITM5eri^n*KoUD?!#(Rc|FAe(Oq zqPBZ|lp`RKIej%jBU72dx(L6jh zO0?XW0|5Ncae;tyeVEHm#&tMTMS!n=6k#2ALjb@sgVOYVxy(8l*V&N>SAN|dmsuxc z!yY3j9S@tK1bcDK4*+|~+;~9Pjc-ABWg_OI>2s}S;S`imH%>&Hlwt`dB92B8;Df=* zCjyc%!_|CjG~0~^gsr-92mo7kivj?)>Xsk?Y}E}xoIOyhzT9z8h9xpoxpsNsU7TJV zWFbJP8n@%=BSJDV0PMw$b)4l?FIu_* z5sCyD3f>L9Ay@$!K;2}cp@jscpOsKI*xN^cY}E}Q0NASA zJ6-tH# zYy*JZWTIKy0-a0}Bl5)(8SePZ0Fn2i^=u|}T@aCSWkG~6msvyuEW1f>4Z?}^$b>uM zT?AxvDT$CtT|}K&BAZK!P`GD~s3bxrhH>i&s$^ogFNqM-4kaZe5;8g5mqf_q@IV5< zgKyn|1VAQ52ND386vc}+?APZKaS;L>cLgWlxe<4pTGcyb8PVa9CFx2RLr%B_t6%_JtCpyIEoZ5s3sh#ZW}8X2??_gtJdFd9{lGqsF`8 zHe%Opvo69n1h^1EtcHLLcS?k=nZN-NR6#e+i14OaBpVGS0$dpHmqtU^jWxXJs&2BG zV3Z8Dlvj~5cT*In#@UNm1OXAs2v}EH9d?t6L_`n>$VNjE(aj_Rxfz4W5TS_ZW{Vl& zA_B6(P(*Z%zXKwa5nbORz=?Ksla0o-a}HoP9)Ro)Xs>rOyK+Dg(ar2ioYN*C8w^E6 z*v|-v0Dur}HsO>J*#-xW)~lOrG_hjDW3ERm(T;m*Eh;@{stOi?wIn)+G^{ zt1O9ht`Mso$E~V@NMH3bEEIx4v^RCCtr~39I{+iY;GI50E~@z0F@=hPHVDXi6|RS# z?~iu1-x6{#jHL1r5D@h`lJ*%dE7IO}wbN6^0fIEKioD2XW{Kzs0sx8Y;6-BX_FQ8C zfN5a{QWQjhpyTX75&#giFqBd^*dik!F&Q?J&?*e2tOO>i@5T#CfMfb@yx^)(646Z} z06-$_#b=06X7t?vKoPd;20)Lv1DI$A0NAkab_Ph!Zd|!6Nv zs65hc#-WP72g#ucQe+*mo`jkLay?(ONF+d`2;F!IlCtM3l+pxcU5kVP?`px}gMLcM56)vW!(c3V;MD0ete!`^ZGA0#w0(H$=?F&t#3x>0}l>@}1PG zb0Kvi`gK&z>dWK?l_Z3ZFK9)mGJ9Q)GD-+lLqrj(%wU)6jPYjuibQT)D`ba8oFa-) zb=fAQPpXLj@!~b(`hDZ3=z{liwz)@9nEjOK!v7;i6t9JBw4@4ZEG*)IQ%c+OfN*sXv8nlsF%JTAYR>MbU$3k3E zQI4>RP_4@$4yh=I6diz~p~o}np6MI3=wmen=W7ZnHtN%dYKq1UYox@mM2I$jm0TRGG{s?kgG@ zY+^%%iWcW}oMEId5c*70D}B-Rut?vI%ectwYySojN3SBx++5N5#b(#TB7Nmrbj=1b zM5GZAaZHU!0d(D>f%!vJJqn@&jsQ3TwENQaD2O5)*tu{nlty6cdt_Y6e*_Q-FhryP zx-K*OQUKVbNp)m2AcE)sVdg=PzBEFX2;7%OKvd4FP^KW}<^XX~X0Z090J6jzotH!% zpBh;mS-P;oBq-siN+Tc!le&=t$daNJH(^lKOK9>e9$^&G76rlsUW78F>*g6lvIIC; zr4isArz+A2a9bUqh2|m?$AHv^qbk=DejTeABHDY-vYQn;LBCE`IbT`6vmzxp6yOw* zybme5G!k%N2K{E~vVBQ+*mwz5Mov&dgQ?3QHeHlMI>3n@;CEIHB7KD>6FC%n0U+V2 zvz`DXawzr!KtgL^$MJkOcOcOow)i+Sg42nL_mp^(bb zk9CL`fNYeM9wC`H&<5B6;!`E1o5dLh+Iq~+#{j@lyL!aIU&B6)84{b@)h-RRu@JB6 zjzXpp^k-d2v0_I>=V7g(NoNmh0YEau7f?tBoGlY1t>vLkBxRwmp{(mHjx|)z*qKUn zfzV?kZfu|k?Jr1upCuE{ZW`j67ZMy&Jhda?1Bg#HQqEK+E!`oKQ3dhdk&FxoUy}(I zB+i_(Oxq1rK&*^7!X`?oeJJE3#||&6xotYMvr}!~kQ-tzPGuZv_?UoXJIq5w^i;-? zhSR_@zWIyfa@utH1~EXamhCDJ;U^H{Thl|FOi-DmE{5Ucq>lo&ri%EQLUJ@9UEiI? z23LOw6`wtGm~oCH zp%R=mS1-6qfV|G!y7@LP#Aq z&jF$Q@av5Lu%q+I#sI{QH(MM(prg2+-+6>W3heo{NQ8)2lb1?}FiegtA?iN)sSqr( zD+Vc~28bj+EP+HH%WcPUA*3$8IAbO8g{G*kIk`iq&k6ArzJqdi=E4b3&}NvC561)v zNlpmPI*SFuW{R3NMTZU&y11f+wX#9tjut_j0inJG5wjbO+af!%cDx~p3}o$O5H+3T z0rJooJw*Pp*TP08A<1+@8ER*g@piB^pfZk7$a*$5Fobju5*o^ywnXT3b^=W^jE-v_ zeAOUMO-MREg)$r%ATC!BsTRa(hETO2zW5QV7DT-pLY8ZgqM%?MauY1xL?7))z~apS z!Y?`Ps3ZI^|6a!MO9}{OgdZrDl_7rkhLC`b5X6!7tPm;hDYtA07y{x0Rs0L)b*iNB z7c`0kxQPG*(CcVN01y`{I)Da9MjYJJrD~3IpJEU93OY3uN(96SrdT2%-gKWr1Q73D z!9zft4=nk)oq~s85-Mngs6`J$r_^N28h#8AtpeaSCR0=G8xY+$OMQF zOjg=3g|eB4Pr`@oSeq{$N5C+=Whr(zHi`5S0O8_HEpFW`gk~H+?ehcTW3WY(?tn=z-c0%FbC5zQYu1MhS*^ z(TLAJY*0ab_T^y0F!#|=r3}rMG|DRD4?;S_g?$5(7)@6eLAEAZ+dZEsk{Z@^ktmY7 zwhKN{B!fswIPsgZI6(_yCuEvRjDzPB@dbc`^o?Jn*lJ<;-Y_6EE8q)xC5h)`x1xlN zIxzwOY}U7eS;Pjdi4Vv~qatF_8Q6vEa06QAEq636-VBpFS z5X!e;0U0v7#}El2&aLvT;eC1E(lp5{M&BZp3lsR=BzR`lK`9bFH;e%gdMPZ6RxI?G zS$KlOYPNQPC|O6~gr#bntMt zi6smXodWT5KPV!?xit;|AUMmNTE*c!0-ONaCWH#`5~LBfJ~IGt_rM@}5zS2W2Sh|h zc!gd`bcBDGK{#aicMj32bl(b_la#NEuF?VFh~V92mDu~DyLBi-rbK!<%LIIMa0ULI zR}ML9EmIwiFE^AoBa#s(#gFpvfe1?vE12}J9OOZ%; zZyCh;a-n7C$&t#Qm9f6EVJWf#H_Xc@x`CK*22sl+P)&@XBB!dCnRIeJf;5pJrs!s@C=riGzQBae z0=TmTBSaEHR)kc*88(LifJ#{rtwAUt7BO68jN!(MR7tj2E^TCjATH!3&@-C#esshh zc6inZ*c?Ti8MQZyIFUDE1Ih#_r2*>rnCV-=>-I6rt;@xXwuc-s?eln_ik5nh3Y9w2 zk|W^~StAAAA@t|7FP9{jPud7ZWIK(5ClgR5>r=zZWRW(35xR_3FsrJPhKV5)U}B># znn=^4XhOFVP24m^HGlO0!b)q{#SQ?R50^UVj$?HZgOCfUpYD`F8@diD?FcZCLBw57 zS+f1WgRi&+2?gwxOJgp@<=``>8H5+iBJTF#Mh5{B=O`Z@jigCH=DJFxS=LRKbVL(H zsdxcMlK{_608PE77EYHSJ`2>lsN=Jsly7MC)GGBbJhZfUkW?;!qZ&e6-mjKE1?FLVG$2Zzo9T8|c# zFhsN|4sk&i<&chOz(itPcTx`7;SJol>wRiX zxo@Y#9KhS>NyjX3hRUmOaYHdXKqPke9O>XJ;cgAyD-b|qH;T%o$89R95tSiGue zO^JX~?f#|%stB`e>l#@Os^iE@xBGQQI2a@nxnqc&23GMH4RRU;a8#w+{U#!`A*4nk;=QBEM?{rmHY>>85K@P{k3^POE1L;zlqblJeaE5Q*&cw|E43 zx$4)E*Lh371O#;qv$TYealk6BymWF1fHXC_Yz2U%RdkXFfV8$1v;)AW#*RR?-P+OZ zD+qjNb%d(#$JilBPtV3-GNI~nx624O+VIh7quH~Qv3$jNCU%V?(&wJ?iz1|==kEeQ zDthKD0L07_WdYz4@Vr<6h?ze{1^_YhELZ?Iy*L#{j;PBaPI6HW;kh~aWemtn${~FN zLF;TNode06m`*y!Gs9|#@%V{W^ zQ>)TBo?-^KOf+}H45=a{kJ|?diBi^h8zQmq-RdeDP*md&h!~BX0Ma>rDGt0piXFcd z1pqE?zZ3)jVdiQ|5mXvM2@!d2etQL~h-O(x28keQ)%jAPAl#gn8j;Qj_a;!x+K#{H zKm#DmJPL|D5H{+rjZOpu;44TvBV5;D6fvcd@rZPWU*LdlL^F4)k@mrE++sr7hXBqa zQX+m`1Fm}0Rsp zq%-`+9+VJfre_Ti1zr3+C5mYFJfwn+TG83LNJ_*n=s^|H%p)R-)xs=HBvPmrfHXCB z8Cy7_DMS>zMUf=l?Q%6gH3_?3n0drix2>T{B9YzI@5$$|{55&5R+010!FXIxfn5NE zYF!R-xfSJ*^4!6v$XKb!A$@}ueXP<#exDDplKk{gqP-2^_xYfNXzm`iy%pf8y|@a{ zMwVtp>Fp4IQn5x+?Mi!UFY0K;xX!W11w6GEC4`xK*orfwRi7297|&}&6;8hAHR3`+ zVivOM4H2oBa0Li?TXAR<@m8gCJTDO?$f$nd2LPOWSP5}C=!QE0oB&cWo|lL!!ps6U zhKSrwPfUcHM|SZ0JpdqyJTDOdA>u|j-UCvJB#7}GkN`=z;WZ!u998KIm<_}|AR^Q} za)XC_SjVLs-kB4?mylG1Cn~}t6=uG_)011`cXgnIFmtOK@Aarrp=yZ8FY(KF;MN)< zPFAT1*bRyBQV~edaDZr*t#$?XJcSaCie?@jQ9d|q05O0eA{F6zd8i{2Wvg8(0)|BD zS0Ms_O%(pNXqK&ZsR)0e7A1s;AGyG5n52j;RzpN8!p}OR4waW@&;cOKJRYWd(NMxU zKq|s7Z-8SDs+MAhJ~gy#wM)go)JO!Bit(&e zRME(?)ov=rEn8j11~h%BQZb&Cijaob(yT6{s8oz+rJ{&1bF<5i%6V2QN{Hreb}4-v zmPJx+N*@Oxi4dia)Aop?)8TgjoY|F64oKo4<&y)DrbX%FP&A1|N*|{&;;o8AzR#r_ z-%!G+SaB`8%x=7Y7WneaUnq$8<>$jlAVigj?%F_EcIeZGDi6Y+0z?&&*k!-HuHz2@qJ&1~vfm!p39kSeMWrwN zVOczk$%P4(pGd4QRCsAtjWtN*HEoUE1S7egzgvNXoeERt7n(gowK`$N_MHsQdf?Nisw^Xj{ zB+Xo8DuWz=!~ym;kUyKvlY*?`Athyy10bquYLr3_h~p}G@2^&)5pleq6;@6;syM20 z06c{r#~sC19y8D-fvDoB$^n2KKoS`u+B3qPtchrfI^kj&2M7`0%hj!c*vjy!kpl1s zn;|7QHJ&>T04d^cDNBwJpsA_LAvRr6}YsCPFpsovalOalEGb`ZGsE5}B5|K(dg0Lvj>f~|x(*SF)|pV^K0CsQbEB-1 zEjr0mf9wa_03uPg=wyS3S9}Z+$xuk+guH~CKjMRoBq8qy3Y5(a;hO}862hU5lGsHS z1Tpi-oieupNNlCdZ2+_ig@{tO0ZDwN)NKgPL;#)5p}dZ$Ds`Lk+5r@hkk<|n0X!_F z6Fex9=1OOH01)nmi0n|@2;z-sYCJ5Zglmj9iV*EdMZC2imA1tY@FYf|DcgziYG5^& zf7(Ox^CH?oO4Fj;B;VH7Zc=K8DjZ3?u@fqAKgOk9Bs_DGz6CoT~7jG^=qvP2}vB-BOn%L zQqN&4JGDjG;hbwb3W-B(KOu@1UG|RMlUP@Vf3nEKPC~-cf<&TxT?lZ3Nk}u0fNetfyjs91&6~Lo32k#898cNBX%y06K8+?5{96z^Z2gN~G~H8_PFn%K?XQb# zeVbd`0LX!j^r2Go9c}FZwA1Mw#s9-2KU^?0)kDT2`?Xkq90D%f<+pLXiu-#qqoV%p z~*SNn%%zDsW`mvYbpvKb~QmPo#hT&a{35iYsAL{%6A z3^z5XV+Pti{Hfbg>Gp<6R7~A3ZP1+)9>r8V-D8;Wwb52uAPTN~98jV4>~1;*(nBDP zh*7Y4&`dFO4_(2(#Bth%Q_K z76;Av2Vx%SBQ~Dg!Ie#8bQ+7q7){X$pnQ z7RX){dm&X!A3-g@FHHBQ;$jij)qoiX@s|)4^!97Qa9bR{O;Qn#EX?CVe_&2wZ+~45*O2ksH06wV`(bXyyZvQ9MZ3c*ejG6+EMA`t zcqsIZyWC^Pifemrq=MR>HK&laXRqtN7kECYEz%ibY;+I$GK^D&lRUF-~t0;x{MIz(l=blIqR6p(P#2R0Z8s}< zri23BNIvlTSPJ;;_31{2yB9QW3Vaku>@6wgZ)d?k044lbncbo484>ub@6!~~Rz`bK z{osLT>hmPY?1t5lrXU2g{`?Z!kU5pKogS(|_JlVGUPSUl8X-+$e2$(Fl$k!KsgvLA zKD(Z_M|mME1gI|nNGqCg9(+#B&oB=@Cj_W5@LWRTQ3ieRIekxpA!EWnvppOLj!ev$ z5Izgxx{M&+MHxYjaPql~I!?+6!qHrsVMfc5Kb|Ip+5n#&9To7kuTVL>&`3bLTD6^Q zEPN)yo^%KRyU0?B0AVMKDKc)ivHFD`_)MbUecnI>07#X^w1o)($KjXL0ARhuc>usV zi6gNaUhzUX+vH3Fpk7i)0RZ)q_A~)dFUb)GK)oal2Y`A>I!?O)$WV7Q@) zGj`LSYkloyU^WuT=!)UWl{7p>oQ7%VZ@^t{+#HdHt6dAfAiy>)aQR#$k2WB}<0G2J zkZPD3u;7u?Pn!)8#IXTIkKAw@XsaPaYe`?ZRk+qJl?PKGQz=cLr5KuA7Xk_c;k06` zpzUAmm#5Ec0O9ZS854|cEdL%9!W~a(Su2?1_i*CwHGS{(p}V0ck{ydXP6%+4a>?Pi zcIrTT82)UAt~JEryTruW!SF{0V929B4t+<2wI}RX$itM!_d$q=0=6aWve0&fodP9xGuAl!T&N5Hk8 zcKPjUCs-)yThb2}N%*X*b@4gUFYLu6B?b3G<&$97&P!7Cc42PXwzMr4_sVRC#hqB& zEpZ=LnpET+(uSu>!Rfk=*6+#E z?Nxq@m@c03Cs?`gVV{cRb*VvoN>W4j(4KsLbAgw&SN1jZUC8DTHkcznA+Gyrkt7%A zgi79%2Ow{dou7gmxDAURfvFI`rVHcw!0>hWF`>l`U$?4tO_&?iaCPu)vZ>)0@nb?uVf>8H zx?3e_VBC2pHOwsVttG_-BMWCOs{VtI>^Yv4NB>6#SJ zYpFoOSAyEFW5(lJ`aTg-$9DWdv7v(QeyQ?Mg4yp>9_mDAxKh~=kYGug0q<7Qq_^q`;LN8jU;;?8WLpS;F%P08C4j_xI(SQve7z{{ z2_RuorZxdEGBDLVQea$UFfu$0;|!!rgK(TM1L;^+ID=Ui94uK79BdzA7UVhk!r=n) zv4_2dbIx2iXKDZD+IZp2(V>l{)s=;F&Oi4-JI`L&-s~;x+HwAc>kF40URv3^dU#=J zWn;XyZ?tE;u(7dl<@%wegA+!Jw~qCgjfm%m;8F(uKq=kHfTa^yRuD{S-1FjRzP^Q8 zi*b?-MpjNabp|5yq~tDvl$sT8s|C9ljN&M*34s(BM+Z(+W>yQ;Xr!o|u7SkPK%zYo zp=qn&{^1D_I)Id#fXP+{duQeBc!9Zx5ji_y5cl=fshu&1#$^COkf)QK0YpB{xUj{0 zN62H|BL|*tO=c~HfU@bqc~X=}W)F(63T*b&28DOl2_dNEnIwHyl};6d2_tJu)1$y zJlb<$Vc*htdGEpqYis?$>fx1zjn##vjrE1)@xBd=Ac-9I2ZyPv6=RS)ZauI9_5zzz@Ea^_Us-t2%F>k)m+{)dp|#b$n|naI(aHh@7n%j}Kmg&k zj-q>))-e)**T;L%U5+2lPjnV`U6e?7(dNP3Xp$ac3~drC%>Oj3h6@*rt^&C@c?ZV_ zSJ$qhV9BVC{ei)hQ8k-c1EYoYjnSS*E$mz4ERZwouS2Vnp$q#CE$Gj6vc$##5HP&@ zgarUxaRF<)Rp5qZ3-d5u+q1MjUMGu3o@W(YXE9Bfw~vr1&Rab^z5-MPatmT{ij^Q& z)E%~7rch&`?v|HU9)&j6$D_492RONCYI!+uIOv?sG-rM<*M zq8&>du^ZNztassP&)O>aF$UpT47(O3yqb$9H+SOdr$wKF3xX_?T+ir=0DZeKI;?{S zjIUa_c)WJd)Xib@SRK*;`rMbSacT=KFY(83E=IV!qvq4=O4RW@MZII#Q zsI3(U)Scr!n``Tkf(tv=R`-n2AeM7VN>OE;h5$+%<104~FYSe}%`L2}zy}AhMAoiY zg8UN!f@zAX{~0D4>{Rhf-AW|Y3RWWfg@A3A2p^|xyarTH-T~!5VFJ!w6((R>Jvh9w zIGi$NUCEMyD&wS`nU*ItQq*Z6V~jHpC0xZ(8clcMLJ9hX2dzU7(nxVW!kHN|a80Xh zr=gl7kL;>x$Tv+JW9gqc+FXZ5_LZM0Cmfs^P7|k1kpO}SEJFD++L+Lo~d+D|7 zy3#Y;7oh*3PIYOTq(l-h+=wfMTnZ?mF0Dcuv_-{6EUBPN;>xejGv+yG^X&1ia?}y` zcjj7S&D)scKL273{`}$h4$O0EI$s};`QhL{e)!&GJy~sLJVaK!yqMiLFaG|A58oI^ zL-8cZPIEB`FJJr@8|Rnb|MBC8Z(jVTfB*U8i+}j>o8e#YA8fP_OvQO=p#O?S@$UKV z|J~RBa=6Xkef=-9?4QEghvJyrFFtS>)*n6|E#=|7xE%sIc#E7rFOZ;e@q({D?NRe# zyZ`Qo@BjD5?;qLfQ`+cxfh&IhT2CGpB^LhOpT5T`oeF>WVVJbz?VA^ljczHZ=<$aizH>Bud^~%}CND-XeR9qnaw9eMe0lxi@6Xw8+;4SYrn%Jnt8phB z8Rc^?Z+P4Bp=OaH8kbiz*$lU;xM_wRrH@Rxb~$qeNae){G2^QWIb{7A;N z`yQ_dd>EF8Kh6^Ur-wJs!u4ji=G?yXQyVk--5mNvuaC5oc}&~eMLJ*8Y>fMpHsR+O zZ=T{S{G^+y+Vb#XKVSR#q-gm`@nS%}{dE+@htO1?>d&M2J?Z&=j&=AcdfQK0-K}Jf zpQ9B%jm_6D(Eaq}1Qg#~qWLC( z5B1YG-YW0==^JluJU@NoEp_ng7wFt#C;ak}x1={eedI0U%TFJ9OYZU0v$xz0KYb&^ z!qY$=>%`M0_gCL|v!naz8y`~^PJhQ-Dh2EI@SI3btjme)*X_l}l=S2<@HgLhQyTpA zjgm&5UNnmRt@!&n+O3=aKy;XgE<^`U8E#AAKF;<5wk2<*^ze3*g1_qFtz5jHI(WOt z#ZMii?mrqNzJ7tiV?e=BI%Zg#{pr;YcGw4#?-{NKOYBoXfCqHb=Kb_KBj&idjoE`S z_9>uCd*4dAx((BVIrd37^mkvY4&tW|n9qI8Yds$@&p(;MzkV@(GKmGZp%~)^VdX}C z%(uL|k4!`R`P^sIa*Q9h=qrDp;b3EZ>i;q4zI?FKKae-LQ|$lN!1po~-M;r&OP=j& z&2KdtzkYFyz3!EJdio(a`^m}U?>=`*sNDYUbLj|AY+VDxOL@EX=U97h)*OE~JT%!) z1m<(!J$a4%eLnXV6X&<#mDqE8-;DmJ)-(41!J0jv@M+Ed_dk8}(-;4WT|L;9{Nq6) z^^b?4@{fmJ`NzZ8`NxB3?jH}s=N}If=^qaZgl*>G)BNM<&5zL{kf%3426K-g=M0>G zoHu{|nSVXK`q8rnd3yDucUslCr&m8Z^B_;Je&i~Ur&m8(2_R2zezfL6p5FXu?tlH{ z>i3VwDfF+WS3kP+Ay2PkS z<{X{>o@28!7k1Xl#zq=k*y-8EX5%jG+%~kZ6SuyxvA8en+^)Vb35;pSzglcugZIyp zj#ht#*B=-2eLIz4%No!{Kz8zg=zmEZZ z*x6k>@%Xc8pJQStPUY&2jn`sf$56JgqwsEQR{g?Gj>yKwA+fMijA>&XQ^H$mD7=*S z9XIcK-#L{wHZe*I6Z=wZf+knH6YzPDDHw{$Zfwt`9e23tYG-leS=B0oo%*5c*;7RE zIi`r>saNzIL-!Q@dXA~Bcyb(`W5W@K|3AmHJGbOK$FyV7+U+=X);iVhTzUBXS@tx= zeD9x~%NpkZx^HK{WeoUR*0W3wC)nlrv)*?M`Oh)!q@&!^(C*YAyK5)(W?W*=G3}1% z{TvhQaIRQ*j%jz2J)UFQog&50G3`$Bz`~C6=?jHozU9rSTD_hXi*T5utkzPNk8|r6Zu&sT{lLo_j(Ub~0l(c0q$Qbw4f= z5l&H;^{i4(Y4JWs^S&7=MKt&L3A4PwB1_393Qv?zANds4z9|q?8m}*CoLbz^znPS+ntHzZlsR1O zPUWVBNkUl9s%_o51n4|V^bY2H+q~0q9uwvL``l!(*PlGd=#9!A?zuFk(GA>U z*S!ymE>3EXjP%B-mpP&n7kd}}LNB|ZaWYWv`)lv#eI~P5?Qlv>mxhYmS*Q5H1-9ix zt51QYJs*nRXFYQW?6b!O0$cxJ=rc?A?_*~;Cg=MxP#Sg3ISKRE;Z3m3o3tZ!-P3?M z8H!!dIAz4ww-A2vthzSAPPx$a>?!xRFd2ROSv(@|lbe>kpnkyKM_+Q}Zuvls)vR3s zA!GG_{w>D7HWXH*oIS97-GFWNg2W@wfpw{~;d#e`} zxd}Fov-RvLbG@;g)41+<30AvP1n(Zh+Sgt+&Zq48Y8TraC*jGbTJ25=*^Nb>w6`Kp z!m@^FTamM=my-#yXuK~CLz;GM3c*z9Sp2}LRl5pJXkzp3=?cHyJV!KFbTC-wNRgYY z7X@guXVjoseCQ=RP2>&O#8j>CJ7sbDLR=CNB0HRdE=aIyHl%WecvfxvanM*V`HJAtkXU#NHphO$h&7rInlRF=F6m?bC9(?J|#JMktf+`!=E*azBT@ey+7=% z$jY#7d&Njj9@hQ%xl(2C%kgz=o})@r$3Q)@eSWCF)G<&3Z1Wt_clDX6uv(r`3} zt_lFfPVmqD`(|VAZ?^D@oov7L>?zN?#*nX|SubUH7>#Q|Sd(8XQPz$542I zm4=Yd)y`Pezxqy2MdV30e`L;u7;c?O?oH%zUhL?Nu-M2%&BaCri+xB@)#&FctCoh% zu=Q0rqR}g0u>r8YL^oimk1kGCtId)MvBw3s-+JRqSad6?644Q1S@)uHqrXYkD-CRI z>u>KB@4CQBL->2!o!ipy*0YkYrQs6My@pg`${d;Tt^4tH5Mb{2hgMuF_n45oQw8|` zTd;_=#tOXm5ZSp+_x@W{!(Pwoud)UE`>>&m z0RvXCQrdSeq!_F&2`=!d40BV6z|z-($3~Db}HlDe=|Ex+c9wL`BnP2*eT1u-glDVo?~{t zB5rx#xd3vtI|)$FG5hWPw$q5lJXbK?wNq+*+xpN=4g&1d8GHWhV0#Q;=kC$fj`8rm zAKIPty4CL7IkmA18mDmheLI&?mxk(7o38xyg#pf8hxhLj)v@@!4Sh;9t#+rH(8jj> zazdEM}3cj6^M(X+LZE?m~wWky)enRT^lZ?%=fY7t~1ZGi35PW-mwOQ?RXVf^w7b!{SS6- zeOUcCRi8GtgNOKsIlJxmI|8 zb7Kv(i);ZCgpp_8Y+s$FpxwD%aL4%gK z#(pYC6iZAX$4O!W2P+LLCht?qu~>ZN-uJrmA-A*7I(&9Ngyj}Hcf{O}m1N1bJ9Rl% zyYd5h&KES26QUpIGKlrQ_w9$Yw!M!;RBnCMh@J%1=kLQL1=8*@s1Jxjm;9*N(_0_p zn2rBvs!;DZ-zl`V|EX8rprEs&1Gcn=<3y)Rm zJo3SW#4MS{_$e~J=n7%!8j?D<*V^PStoK>F=PuN{IlstXa59!hz@i(FsKbX~;X~sV z-+YKwYTMixxuSQ&qIVA#ol_Zu%$tavjH#^c@SMp(So_yl#a_ola4uZ9A3wIG%n=o$ zSn>zG?_4;u-p9zj=Un>R1$IG$l9Ol871N7G;v;-Z^3Vp`G!!H|g|^rG5+|cwWz18l zZG9h!b=$`&Bsj-v$K(-RP3A-Pk*SQ#1&chSlzQYLSoSJ6MD{sTtBan@ zinQ&MV_6Ena+KvfXT91nvz|RQq3+h7R@;Z6-MRStz8yVk^XGBTEIw(Izllvj2FJFo zYggM6Y}?kgqbRTWfL+i?JrB>?aCufG_l;RX7g%YKo7yh9uNox>8WtUUu<(1Q{^s{0 zAWz|(`}r6_JN-!B4$fY^%+1DGyAxBs=GU5xt(g_`Tt9RFEpl5jE<}|JEawM%?DjMH z=5sOBJ&oqIX-CZ5W5D!ZEHULXE;-+fXwE^ZRk5#litp(ftK{-fqmtW9jjD{!Oc-4a z7WsuXvtxg~PjKG-x4@DoeAKtLORk%C$#om-f`-!aq3d4AVi)~L3^N+%`pEk^7e1c% z2?)8sN`n~IcIWo$dm7R&vtF2Ev$wF9-Hzvd?^V0<6u`Tqp%TrOOYEZdgEU?m3`C(@ zpC&cAwp^-n_!Tqo1&!JrMYx71!om~XQ+z?Ayj^z6ML){hg+E`=C~qIrb;qT=opmE} zHY|Kse0Jm}9*pgy7ko-NSZkc6FEZws!uR?U^&c4%7F(_hiT8Rzw{k4(f=1<7To}4jodvGy_*`8mwkQF;uAm5L@)YrB<*R9^SV@pdr0Vc(gGivZy;`aAl z=o%!S3mVvVo+U^WyP%O8Wxl2M7uW@ji!(*pnbOTUgN8_%eBb-fa2}-}u+Sj=WzKmu zLSsU>M5eLJLj18))$0#=3Jf9MfpDN#(n-WkLTQf%lE#|b7>v1 zt-~(9C9&dT3E#2FQ;#{ZE-eN0o-E7v{#M$q&ZDRV zYYc&%5Iza(n2ISXjdL~Po!lwoF?`5s*|~?~PVO4QvNIn9iS)w(y!ip|QFt+|^TuAB zey|s3y|5Q&?Oxc6xxjNCiDd8Eb?mnvjcegT3%?(?TGlwbPx^s5r5`qW`oZqA`8cmt z&O9e~+TyCvG=75#`iZXvcqJYXI-XQEb*N#r_G+>0gtZ@7TzRAJ^UUPem^nl z?Z?}_p7kR&S750zI9T{Su9kC^CK-MY%bsSLiyd7gc6cW&dWwvO$QH1~SXlY zt$@XUB!VS6jtHBa-LS~E#Lk@qLtwCA_Wp3FWDjD-S-W$?U7nRuvN3tMp<(NX#xTk} zo4R0_(5voqwxH5D_o&^;2VCmi=ftq|gC5X+P>}L{=LVHUqjn3TeD}GekkUxbnf@m4 z#G(>Ge*Z17#ETBL_c=_oRh~_K2JLc2!IFnI*xu)`)W3qI9?xKVpTiQv3`^YCV0)j# zQb%~OZ7*?#CT0hg*pI>XK4;xXjSB6~?PJf^0k1~lrN}BdXU6-nWwXJy4+C~Cv05~e z7p>i!ts}zX8yjrrAiz@VZ?K(@2xF1hYpDKY?aqxk>zgAxDm$GV-odtv4vQUWu&u+w z&TV1$bh*oxEUKNtYP%o&$xBw&F1fjbZF>$ZdF6v`*%s!|3*F>{X@?o79m7SjEt@G% zojS9y#03qub!=E_9l(-fGT7D;VVr3D`=sckA73UP%(Dzrd6xGiSaO>^8(Yg@TmOJb zY|Hyn_echL^66j#O!l)JqL~l5k)grtoqovC$ygC{N+a<|z9o52u=sTc>plmS1`AgD zmU|1lFR|i-ZCwbTv_tm$C~Ki1jv`|<2HrdyTNN6y+rku1&a;VY)-JWoV6jI~?2{v= zUGh13tizu@89Rt}kv9h0@&+vO#$a2ofJNSbMcx=}%NwxB8-s1Vg7c{IhERyDS7=vx zLw-->4J!NAD?D3yV-oq6Y^Gi14afP`E3~V;F$rc{ZkhzH(x|*4zbEpB(EY7fpj&x^ zg?8%|g3>B)fFf^*B;I<3XWc5k);MhI71}A*nRdzd(=Kt5u=qX)+j<2obtMPedIc=C zFk!i`W3a7PNOMrAeD`B!xY*V!JR3jVVB3BGi`^S0P&G7?r>>odxU@@tlyc)g z-Gt_1Fm1;* zdp5ZlgGHv9RJ?tL!nW<2F)Z~XU|zY$nHZb#WBV@+4vRdS+RSLA?&@GWKLwUr?Xbl0 z3>KM2@JwVHSY#T~Msye6{q1|Qtdc(nJJ(s>=@oAC>ROw^jeA_!$TC)JWLy4I#dZ6l z(Ty*9u;?yMkG;mVi!XYx=q{6znK{RX+K>36RZ)sBda&p&B7dX1z@odza@~Gd?~5;b zu;?x%knID~F23l&qPtA)VdjI4Ec;^|FMEDDF)~(cWSMiNF+zilEHqlHZXzbS&&5lH z1{+yv$RgU`+8AXncu^63Jm3toxiRKmG7|87qF+j1^Tp{g{hxcR$|dJ|N$cI-i4epA#KRBlWL5 z%SaZw0yBzrpHt~VV*=Rrvx31wL;iVaa9?Juh`Xhc_@-AfP`l49gVK<-m+wn0AMeZE z5-^d{c{X)YwM!1$VA})F&J)<02|I<04`w;{uCZ z>}u`sKiYZ51s466WE=hm>$p%l_Zs3A3jc#e|CL)Gx!BFi;eW93KQ6xTKUnzRYl6;R zL(DJXf3V2Kw4?ApSoohvgYZ9=>+nBV_#d}q5n}+|vBInro(SKp#f6QFre?~rX4lMjn3BT|^SoojYCc^*b`n;@N{?*Ml z<{rw;o0#Fk|6t*NPLIeruAHo4a#_`XR`$&!*-Xl_YycaC`l4n0x#w z!|?Z6;o*HQQ0Q>LJyvfiIl)wN2uAoW7JrImAAbrg@f&Wpk3U7U&yLgb&8h1Q%e_m~ zG9umnzTV}h9gjfTu^I(S4ROyVUv04XQ(iLzmqzN0dDewWp_^J^+I zA7abW55t#! z2-izL*wRWP_el8W?NPOF}iGldAZ z`Ln*|O$AzDc_*`Ch;iq%`d3oK1pU$1s(NkFY%j+)ubj-lz zbr*iR37=eRh+tMf9jv_W!cV8Fb9vo`pH3B)^12H@ohp#!b+%yUO|*dtFU!w7t(KZETO#+{?YkYVPGOjr!^Aj@To(5)$`?6(=5P?r~rG z0^aEWx#zJgK|O0>Icr(mvi^h!=d6Wg{ZVzZ{$M$4MFnO3iNwkJgJt~*_sLlc>-uxj zWZuXsnKxB4GjGhSnYRnS-`pWn8a->(TI>3|@cW5e?fSd$`(51C&Gl5jAFS(-8lN?O z;rDY>K+YM}N+VOla?bGJ#vTUCIpezC&Km}pF(n_Y^=ysTCv3)Jd*Et|?Ex0sgAnz| zG)i>G_5e#PCr-TgAVUiQb?-rWhTDcBv@EsKCQWYm>sH>9WUzjxO#dq=8<{*%e zvKL^;+ijnn~HQ zx5Kg*WO`&TI6kr$V6mUFn8)7E(vrOZ%U%%4l)V7UnI}v-d%-TxnFq^WaO7n#OuGH% zmQDrZ%*6qA#5^&EvQgO~aSk64vq;uv?iGcLO(%5`M2usd9X2%%a3V);@(iTgjXPzD1eNNyAzpBitzdiFrEp?w`(3x|0 z*@uQgv*`zhntotT8LQ?<2%+kkN9^oASMs7Xdgjd?x$Q^KJdtnR=h8zr4erbG&eq1T z*;@NVx#rlUr_{#Q{dgs#`>|%n_5h3SCxtRLMp$$|X3^Lltg7gKu-G1m=&?P_5VRix z;`Sb#A|J(Kdyw`S-EZnymWCw7^}eZRxoJ#2%OzWgE|Im8XLDD8cDYvpmV2*ZynK0I zYF%m9oF7>HDQZ{7#>lG{e+n!%#wo+H$4W?GY>cq@Q=D$GF~YKTWzWRMsOn5?jIh{v zgbT#RLw$>l2NoL-&unZwu-JIyTWxugG%ofL2T10PGa>WFSYH|!`^Ye-9}KOb@#V!n;(8Aa zVYKOoAt;TDeZ)wXeyHPB8W;PBA*nPj_7THHXSKkAcXTFQ9|>v@S8 zXRK_G(7+@zR&VQ*vkIcyt31|yPPH$M=FTyP#U}^rJ{P;ye$*%D`m*kGbqBMq#BOEI zTX&G=T%R1Q`&`|@(x^{Pc0l(zHnhhI8}IzKR=Z0=l20-rciF2d6GZ=2`6P0&A&6W& z!?Mp5CdTN$u;{-NYQJ@ecj_N(*&7!9mxLbumlrYmFD&}6X^YKwrZWAIFuu;gxr#3O z@1zH$A8cCb$0SuRnc>|eC)nN&i)AzV?<~L4sGUptLhM|V(vW^!*ty(P8~qoy zWrm4RivH{BmFT~)=)bdN(vJ%}7fV6(-$}!0KWgVv!7uu+Y>4Q;u;{;TGKl^Qi~cLh zE&4B$dF))U*tygoj{eKC6#W+#JD1Zp`Y){e{KC$K4RxPi*twYbyU#D|T$ApS`M9ui zsY~2_eqrY_Yu)D;b}p>5`~1Sr#Wm4=eqraD6rJ{?b}kizyU#D|T<HFMnBhouj)n5?3(M0__`N zpfqrk%}<^CrSZP!8-H2);ou4l{<6@(ctZmdTVpS^O7h75J{zetBsHX+h>&7E-}uW) zgEc76Dp(cFiBzoXp1&+K_{&0rzbrHyQK9i(V)UlVUsf8a*E|_N@#9RWRcT1#4UOE7 z?R~A+0_*waSS<}wO=#pk5j1ia>|n9)PI%a+A<8`G8!UbtH=XBvdoPx0&j&2~+$orS z4$D5L5oe#9^6c{oX=^_y3~Q`ZR?j|%WuLz%C$=A|&+hk;6GKDIfxWIKEoQG>%QX8O zw)eSAvf3Ec*^b_$RQA?6@#5;2OthN@LoN&c?i(*@`XNCj{otC&SYg>2D`n$DL#l6R zFw=yF1cA_SM_OnI0V)kb&F&jpP-&!Aw&?cM${sAdql>as^ChIOCI<7+=6Kfh9|z55)A^n-$)u_B&^2E{S`poC>UC^V(f z+Lc^rl^K*dDUH^yoRXfU(b|=666!v8#Y1Vdb|s`Tg9A8YCAD1!T8l<5_!JJH@!tPBGSEd%tvC_57IZ-+NU}h~1S=ynS`#F7|;sj~;W%rvP_0kaO zk!Kal4#u)vY@e&Xg@nJ`O|WUPeXhzTNezR+wk{4!4TH%<={_errXQA2`avbhd@yIX zAE{x0ZfY37Qo{h28U}-Pp9>7iSdksm56eFNphl%1Oz`Q4poG#$4Fkzm6x%&-%JY{- zY8ZGnH4I=BfxIs@475uP0~ld1&!&cfcBx@7SkKjQtnK%Wld4$v`8ZF~jw7O2_c^0^ zX)reD+1y!7I2G)db^_;uz3o014_6xRy3eu1(r6qC4@>vC7`f0OJ7%oNj-eq&F8yGD z$ykYzD-D^hdknDg()gaixQ041|$o3}BIIXj>Tr=DLi*lnu=oC{($}S@vLf9W1<# z|xW)BSk&>1U1 z!l9w~Q)mcLFAWSZbOp!;d((YR94`$ECeO;238o{(y3YlTl?Lx;o|PE5G5+q*;J(aQ zQ6WNuNjfwH<7^r{kUO55!jyai#e%B;@(yObDE$;48f?cupYoV1hHt7i?mUOVcHAZ` z_qD-tU)x|iZWAV>ZjCc+$8BmS7$WUji)KoCwjUhcd6uPcV@!gfF_-_Ooj|f+g0g}U zJ&WzSQ4`&?<2DD|ahtH**9N0_heqye(=PY5!E#^QU^~wNmiyWU+i{zcylU&4(y1A% zx&2~)vviX5gXuN%F~#5WZ0>9GExE4^*1Xv%pj{fdug$Z$uWhg$pAE}>ZLr+eHrS5O z79gB_CRp;B2HWx3u;eoh)_tzbUFJi9(aeW*gUkmLcgBjimiZ7KQ5wl-@_jA<-g7A= zx-^o{%^;z5Sxye~EPw7WRpn7#Y#+lzh(rVEV={w#&E?RX~lv)q!! zF8GU(w(O4ySYGcVDM!vxVmf*SwwCwNoOdqDl#||h1cQZ_FtF`>9PLue5te&yMRF!r zVX)G0?r$E(P?K|?`Jnw6L&F{`>C?q}Zc(T&Xb42f`;v#IALY9wxQl+sY|Hy{uY&g_ ze`~PzqqWti{k~wQgg~TSC)cZ z&=8~<8i~Ub$zHqdS*ZKjne<==Fj(&SL8JbFsgJ(T->HS(_oa5P*VRuvqD9^}#Z&k9 z38UEhkx;+zHJ_JrdctJ393f19$9wBq>hum4IgI$yH6#x$-z-G4bW^7njnwIdrB1J5 zu6`-CVCO<w?$R*qR+^&pVM~#ZjQtsEzEfo<{f8)5- z_Bkj!D)v)3Sdkqk?K9sjk7~=OETyqyPA*jF@;PLzhdyTVZ$3A|Me>7Tc z^kiPi@RxT&!8Sh-o*0`iFGTo(2)WqrZPoY!xu>IJPYO)NfSEPlq6kjLk3N|BAU0+E z@W1Rgu@HG5T`lWR@r2Etb;wrBniSFzpOUn(?0rXf_OEEO$n7j? zIV~C(3Zc=gXH-#})?PrDNWek+!Up&II;Ty1v`H-8o_Nq}y+%PP4U0}IG0hWAXSbUXp zMb(xG$MmrEEm(P?@cTWc>=BLUhb1O~1tqaAqDpJGxR7atRYljpA!2`#bsYI+!m8FB z(X1LTz-!-U-I=r2frG3u{xbU_msqBIe#eBDGgS46_`79V=Ill`XRXnTZG^=#X8^a$ zu3cn)9X@-Fzs>lW^7ytHpb{rIk#Ae>oEwaG42x-ttx7;v>L{6w*eF;}Q`1CXZO%*A zetos&)8Q`p7JmD^hHUck#JQV%^OA9&uQT3qPJJoMKJi?ZWNH|okoXb_-?3SeFcS}A z0U3lfmvcvCFyWKI`2B)W-F6ivDYBM Date: Fri, 18 Feb 2022 14:25:03 -0500 Subject: [PATCH 009/161] build: Add ply to --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 9eb0cad1a..da29e1ebf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pytest pytest-ordering +ply==3.11 From b95844721eb597513928aa2f889a36d1160ad6bc Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Sun, 20 Feb 2022 17:25:23 -0500 Subject: [PATCH 010/161] Project refactoring & CIL working --- src/coolc.py | 34 ++- src/coolcmp/codegen/__init__.py | 0 src/coolcmp/codegen/cool2cil/__init__.py | 11 + src/coolcmp/codegen/cool2cil/cil_visitor.py | 9 + src/coolcmp/codegen/cool2cil/code_visitor.py | 158 +++++++++++++ .../codegen/cool2cil/types_data_visitor.py | 122 ++++++++++ src/coolcmp/lexing_parsing/__init__.py | 0 src/coolcmp/lexing_parsing/parser.py | 2 +- src/coolcmp/semantics/__init__.py | 7 +- src/coolcmp/semantics/builder.py | 7 +- src/coolcmp/semantics/checker.py | 31 ++- src/coolcmp/semantics/collector.py | 7 +- src/coolcmp/semantics/consistence.py | 7 +- src/coolcmp/utils/__init__.py | 1 + src/coolcmp/{ => utils}/ast.py | 15 +- .../{formatter.py => utils/ast_formatter.py} | 21 +- src/coolcmp/utils/cil.py | 221 ++++++++++++++++++ src/coolcmp/utils/cil_formatter.py | 119 ++++++++++ src/coolcmp/{ => utils}/semantic.py | 46 +++- src/coolcmp/{ => utils}/utils.py | 0 src/coolcmp/{ => utils}/visitor.py | 0 tests/codes/hello.cl | 45 ++++ tests/codes/silly.cl | 16 ++ 23 files changed, 821 insertions(+), 58 deletions(-) create mode 100644 src/coolcmp/codegen/__init__.py create mode 100644 src/coolcmp/codegen/cool2cil/__init__.py create mode 100644 src/coolcmp/codegen/cool2cil/cil_visitor.py create mode 100644 src/coolcmp/codegen/cool2cil/code_visitor.py create mode 100644 src/coolcmp/codegen/cool2cil/types_data_visitor.py create mode 100644 src/coolcmp/lexing_parsing/__init__.py create mode 100644 src/coolcmp/utils/__init__.py rename src/coolcmp/{ => utils}/ast.py (87%) rename src/coolcmp/{formatter.py => utils/ast_formatter.py} (91%) create mode 100644 src/coolcmp/utils/cil.py create mode 100644 src/coolcmp/utils/cil_formatter.py rename src/coolcmp/{ => utils}/semantic.py (87%) rename src/coolcmp/{ => utils}/utils.py (100%) rename src/coolcmp/{ => utils}/visitor.py (100%) create mode 100644 tests/codes/hello.cl create mode 100644 tests/codes/silly.cl diff --git a/src/coolc.py b/src/coolc.py index 7901d6059..d4e0d5818 100644 --- a/src/coolc.py +++ b/src/coolc.py @@ -4,15 +4,17 @@ from coolcmp.lexing_parsing.lexer import errors as lexer_errors from coolcmp.lexing_parsing.parser import parser, errors as parser_errors from coolcmp.semantics import check_semantics -from coolcmp.formatter import FormatVisitor +from coolcmp.codegen.cool2cil import build_cil +from coolcmp.utils.ast_formatter import ASTFormatter +from coolcmp.utils.cil_formatter import CILFormatter -def main(cool_code: str, verbose: bool = False): +def main(cool_code: str, verbose: int): ast = parser.parse(cool_code) - if verbose: - formatter = FormatVisitor() - print(formatter.visit(ast)) + if verbose > 0: + ast_str = ASTFormatter().visit(ast) + print(ast_str) if lexer_errors: for error in lexer_errors: @@ -24,18 +26,30 @@ def main(cool_code: str, verbose: bool = False): print(error) exit(1) - sem_errors = check_semantics(ast) + sem_errors, ctx, scope = check_semantics(ast) if sem_errors: for error in sem_errors: print(error) exit(1) + # print('-' * 40) + # print(ctx) + # print('-' * 40) + # print(scope) + # print('-' * 40) + + cil = build_cil(ast, ctx, scope) + + if verbose > 1: + cil_str = CILFormatter().visit(cil) + print(cil_str) + if __name__ == '__main__': import sys if len(sys.argv) < 2: - print('Usage: python3 coolc.py program.cl [-v]') + print('Usage: python3 coolc.py program.cl [-v+]') exit(1) elif not sys.argv[1].endswith('.cl'): print('COOl source code files must end with .cl extension.') @@ -44,7 +58,7 @@ def main(cool_code: str, verbose: bool = False): cool_program = open(sys.argv[1], encoding='utf8').read() try: - verbose = sys.argv[2] == '-v' + verb_count = sys.argv[2].count('v') except IndexError: - verbose = False - main(cool_program, verbose) + verb_count = 0 + main(cool_program, verb_count) diff --git a/src/coolcmp/codegen/__init__.py b/src/coolcmp/codegen/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/coolcmp/codegen/cool2cil/__init__.py b/src/coolcmp/codegen/cool2cil/__init__.py new file mode 100644 index 000000000..ce03c3ccd --- /dev/null +++ b/src/coolcmp/codegen/cool2cil/__init__.py @@ -0,0 +1,11 @@ +from .cil_visitor import CILVisitor +from .types_data_visitor import DotTypesDataVisitor +from .code_visitor import DotCodeVisitor + + +def build_cil(ast, context, scope): + cil = DotTypesDataVisitor(context).visit(ast) + + DotCodeVisitor(cil).visit(ast, scope) + + return cil diff --git a/src/coolcmp/codegen/cool2cil/cil_visitor.py b/src/coolcmp/codegen/cool2cil/cil_visitor.py new file mode 100644 index 000000000..69ebf7533 --- /dev/null +++ b/src/coolcmp/codegen/cool2cil/cil_visitor.py @@ -0,0 +1,9 @@ +class CILVisitor: + + def __init__(self): + self.function_id: int = 0 + + @property + def next_function_id(self) -> int: + self.function_id += 1 + return self.function_id diff --git a/src/coolcmp/codegen/cool2cil/code_visitor.py b/src/coolcmp/codegen/cool2cil/code_visitor.py new file mode 100644 index 000000000..770eab673 --- /dev/null +++ b/src/coolcmp/codegen/cool2cil/code_visitor.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +from coolcmp.codegen.cool2cil import CILVisitor +from coolcmp.utils import ast, cil, visitor +from coolcmp.utils.semantic import Scope + + +class DotCodeVisitor(CILVisitor): + """ + Builds the .CODE section. + """ + def __init__(self, cil_root: cil.ProgramNode): + super().__init__() + self.root = cil_root + self.code = cil_root.dot_code + self.current_function: cil.FunctionNode | None = None + self.current_type: str | None = None + + def add_function(self, name: str = None): + if name is None: + name = f'f{self.next_function_id}' + self.current_function = cil.FunctionNode(name, [], [], []) + self.code.append(self.current_function) + + def add_param(self, name: str) -> str: + param = cil.ParamNode(name) + self.current_function.params.append(param) + return name + + def add_local(self, name: str, internal: bool = True) -> str: + if internal: + name = f'_{name}_{len(self.current_function.local_vars)}' + local = cil.LocalNode(name) + self.current_function.local_vars.append(local) + return name + + def add_inst(self, inst: cil.InstructionNode) -> cil.InstructionNode: + self.current_function.instructions.append(inst) + return inst + + @visitor.on('node') + def visit(self, node: ast.Node, scope: Scope): + pass + + @visitor.when(ast.ProgramNode) + def visit(self, node: ast.ProgramNode, scope: Scope): + # build the entry function: + for class_ in node.declarations: + if class_.id == 'Main': + for feature in class_.features: + if isinstance(feature, ast.FuncDeclarationNode) and feature.id == 'main': + self.add_function('entry') + main_scope = scope.get_tagged_scope('Main') + instance = self.add_local('instance') + self.add_inst(cil.AllocateNode('Main', instance)) + for attr in (f for f in class_.features if isinstance(f, ast.AttrDeclarationNode)): + if attr.expr is not None: + expr_dest = self.visit(attr.expr, main_scope) + self.add_inst(cil.SetAttrNode(instance, f'Main_{attr.id}', expr_dest)) + result = self.add_local('result') + self.add_inst(cil.ArgNode(instance)) + self.add_inst(cil.DynamicCallNode('Main', 'Main_main', result)) + self.add_inst(cil.ReturnNode(0)) + break + + # build the code functions + for class_ in node.declarations: + self.visit(class_, scope.get_tagged_scope(class_.id)) + + # add the default functions of COOL + # TODO: add missing instructions + self.code += [ + # cil.FunctionNode('abort', [], [], []), + # cil.FunctionNode('type_name', [], [], []), + # cil.FunctionNode('copy', [], [], []), + # cil.FunctionNode('out_string', [], [], []), + cil.FunctionNode( + name='out_string', + params=[ + cil.ParamNode('str_addr') + ], + local_vars=[], + instructions=[ + cil.PrintNode('str_addr'), + cil.ReturnNode(0), + ] + ), + # cil.FunctionNode('out_int', [], [], []), + # cil.FunctionNode('in_int', [], [], []), + ] + + @visitor.when(ast.ClassDeclarationNode) + def visit(self, node: ast.ClassDeclarationNode, scope: Scope): + self.current_type = node.id + methods = (f for f in node.features if isinstance(f, ast.FuncDeclarationNode)) + for method in methods: + self.visit(method, scope.get_tagged_scope(method.id)) + + @visitor.when(ast.FuncDeclarationNode) + def visit(self, node: ast.FuncDeclarationNode, scope: Scope): + self.add_function() + + for local in scope.all_locals(): + if local.is_param: + self.add_param(local.name) + else: + local_name = self.add_local(local.name, internal=False) + if local.is_attr: + attr_name = f'{self.current_type}_{local.name}' + self.add_inst(cil.GetAttrNode(local_name, 'self', attr_name)) + + result = self.visit(node.body, scope) + self.add_inst(cil.ReturnNode(result)) + + @visitor.when(ast.CallNode) + def visit(self, node: ast.CallNode, scope: Scope): + # allocate and push the object type + if node.obj is None: + obj = ast.VariableNode('self') + else: + obj = node.obj + obj_dest = self.visit(obj, scope) + internal = self.add_local('internal') + self.add_inst(cil.TypeOfNode(obj_dest, internal)) + self.add_inst(cil.ArgNode(internal)) + + # allocate and push the args + for arg in node.args: + arg_dest = self.visit(arg, scope) + self.add_inst(cil.ArgNode(arg_dest)) + + # call the function + result = self.add_local('result') + self.add_inst(cil.DynamicCallNode(internal, node.id, result)) + + return result + + @visitor.when(ast.VariableNode) + def visit(self, node: ast.VariableNode, scope: Scope): + return node.lex + + @visitor.when(ast.StringNode) + def visit(self, node: ast.StringNode, scope: Scope): + dest = self.add_local('internal') + self.add_inst(cil.LoadNode(dest, self.root.get_data_name(node.lex))) + return dest + + @visitor.when(ast.InstantiateNode) + def visit(self, node: ast.StringNode, scope: Scope): + instance = self.add_local('instance') + self.add_inst(cil.AllocateNode(node.lex, instance)) + type_node = self.root.get_type(node.lex) + for attr in type_node.attributes: + attr_expr = type_node.get_attr_node(attr) + if attr_expr is not None: + attr_dest = self.visit(attr_expr, scope) + self.add_inst(cil.SetAttrNode(instance, attr, attr_dest)) + return instance diff --git a/src/coolcmp/codegen/cool2cil/types_data_visitor.py b/src/coolcmp/codegen/cool2cil/types_data_visitor.py new file mode 100644 index 000000000..945be7d5d --- /dev/null +++ b/src/coolcmp/codegen/cool2cil/types_data_visitor.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +from coolcmp.codegen.cool2cil import CILVisitor +from coolcmp.utils import visitor, ast, cil +from coolcmp.utils.semantic import Context, Type + + +class DotTypesDataVisitor(CILVisitor): + """ + Builds the .TYPES and .DATA sections. + """ + def __init__(self, context: Context): + super().__init__() + self.context = context + self.current_type: Type | None = None + self.root = cil.ProgramNode([], [], []) + self.types = self.root.dot_types + + @visitor.on('node') + def visit(self, node: ast.Node): + pass + + @visitor.when(ast.ProgramNode) + def visit(self, node: ast.ProgramNode): + for class_ in node.declarations: + self.visit(class_) + + return self.root + + @visitor.when(ast.ClassDeclarationNode) + def visit(self, node: ast.ClassDeclarationNode): + type_ = self.context.get_type(node.id) + type_attributes: list[str] = [] + type_methods: dict[str, str] = {} + + for attr, _ in type_.all_attributes(): + type_attributes.append(f'{type_.name}_{attr.name}') + + for meth, owner in type_.all_methods(): + if owner.name in ('Object', 'IO', 'String', ): + func_target = meth.name + else: + func_target = f'f{self.next_function_id}' + type_methods[f'{node.id}_{meth.name}'] = func_target + + type_node = cil.TypeNode(type_.name, type_attributes, type_methods) + self.types.append(type_node) + + for feature in node.features: + if isinstance(feature, ast.AttrDeclarationNode): + type_node.add_attr_node(f'{node.id}_{feature.id}', feature.expr) + self.visit(feature) + + @visitor.when(ast.AttrDeclarationNode) + def visit(self, node: ast.AttrDeclarationNode): + self.visit(node.expr) + + @visitor.when(ast.FuncDeclarationNode) + def visit(self, node: ast.FuncDeclarationNode): + self.visit(node.body) + + @visitor.when(ast.LetDeclarationNode) + def visit(self, node: ast.LetDeclarationNode): + self.visit(node.expr) + + @visitor.when(ast.ParenthesisExpr) + def visit(self, node: ast.ParenthesisExpr): + self.visit(node.expr) + + @visitor.when(ast.BlockNode) + def visit(self, node: ast.BlockNode): + for expr in node.expressions: + self.visit(expr) + + @visitor.when(ast.LetNode) + def visit(self, node: ast.LetNode): + for declaration in node.declarations: + self.visit(declaration) + + @visitor.when(ast.CaseBranchNode) + def visit(self, node: ast.CaseBranchNode): + self.visit(node.expr) + + @visitor.when(ast.CaseNode) + def visit(self, node: ast.CaseNode): + self.visit(node.expr) + + @visitor.when(ast.AssignNode) + def visit(self, node: ast.AssignNode): + self.visit(node.expr) + + @visitor.when(ast.ConditionalNode) + def visit(self, node: ast.ConditionalNode): + self.visit(node.if_expr) + self.visit(node.then_expr) + self.visit(node.else_expr) + + @visitor.when(ast.WhileNode) + def visit(self, node: ast.WhileNode): + self.visit(node.condition) + self.visit(node.body) + + @visitor.when(ast.CallNode) + def visit(self, node: ast.CallNode): + if node.obj is not None: + self.visit(node.obj) + + for arg in node.args: + self.visit(arg) + + @visitor.when(ast.BinaryNode) + def visit(self, node: ast.BinaryNode): + self.visit(node.left) + self.visit(node.right) + + @visitor.when(ast.UnaryNode) + def visit(self, node: ast.UnaryNode): + self.visit(node.expr) + + @visitor.when(ast.StringNode) + def visit(self, node: ast.StringNode): + self.root.set_data(node.lex) diff --git a/src/coolcmp/lexing_parsing/__init__.py b/src/coolcmp/lexing_parsing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/coolcmp/lexing_parsing/parser.py b/src/coolcmp/lexing_parsing/parser.py index 37fb3b2e6..d0a5daaa3 100644 --- a/src/coolcmp/lexing_parsing/parser.py +++ b/src/coolcmp/lexing_parsing/parser.py @@ -1,6 +1,6 @@ import ply.yacc as yacc -from coolcmp import ast +from coolcmp.utils import ast from coolcmp import errors as err from coolcmp.lexing_parsing import lexer from coolcmp.utils import find_column diff --git a/src/coolcmp/semantics/__init__.py b/src/coolcmp/semantics/__init__.py index 5c8a3a88b..bcacf0790 100644 --- a/src/coolcmp/semantics/__init__.py +++ b/src/coolcmp/semantics/__init__.py @@ -2,9 +2,10 @@ from .builder import TypeBuilder from .consistence import TypeConsistence from .checker import TypeChecker +from coolcmp.utils.semantic import Context, Scope -def check_semantics(ast) -> list[str]: +def check_semantics(ast) -> tuple[list[str], Context, Scope]: collector = TypeCollector() collector.visit(ast) @@ -15,6 +16,6 @@ def check_semantics(ast) -> list[str]: cons.visit(ast) checker = TypeChecker(cons.context, cons.errors) - checker.visit(ast) + scope = checker.visit(ast) - return checker.errors + return checker.errors, checker.context, scope diff --git a/src/coolcmp/semantics/builder.py b/src/coolcmp/semantics/builder.py index 71a79e968..f9caa29d2 100644 --- a/src/coolcmp/semantics/builder.py +++ b/src/coolcmp/semantics/builder.py @@ -1,8 +1,9 @@ from __future__ import annotations -from coolcmp.ast import ProgramNode, ClassDeclarationNode, FuncDeclarationNode, AttrDeclarationNode -from coolcmp.semantic import SemanticError, Context, ErrorType, Type -from coolcmp import visitor, errors as err +from coolcmp import errors as err +from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode, FuncDeclarationNode, AttrDeclarationNode +from coolcmp.utils.semantic import SemanticError, Context, ErrorType, Type +from coolcmp.utils import visitor class TypeBuilder: diff --git a/src/coolcmp/semantics/checker.py b/src/coolcmp/semantics/checker.py index 1f6c3498b..2999c9607 100644 --- a/src/coolcmp/semantics/checker.py +++ b/src/coolcmp/semantics/checker.py @@ -1,8 +1,9 @@ from __future__ import annotations -from coolcmp import visitor, errors as err -from coolcmp.semantic import Context, Method, Type, SemanticError, ErrorType, Scope -from coolcmp.ast import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode, BlockNode, \ +from coolcmp import errors as err +from coolcmp.utils import visitor +from coolcmp.utils.semantic import Context, Method, Type, SemanticError, ErrorType, Scope +from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode, BlockNode, \ LetNode, CaseNode, AssignNode, ConditionalNode, WhileNode, CallNode, VariableNode, InstantiateNode, IntegerNode, \ StringNode, BooleanNode, PlusNode, MinusNode, StarNode, DivNode, LessThanNode, LessEqualNode, EqualNode, \ IsVoidNode, NegationNode, ComplementNode, BinaryNode, UnaryNode, CaseBranchNode, LetDeclarationNode, ParamNode @@ -21,28 +22,24 @@ def visit(self, node, scope): @visitor.when(ProgramNode) def visit(self, node: ProgramNode, scope: Scope = None): - if scope is None: - scope = Scope() - - # for declaration in node.declarations: - # self.visit(declaration, scope.create_child()) + scope = Scope('Object') pending = [(class_node.id, class_node) for class_node in node.declarations] - scopes = {'IO': scope.create_child()} + scopes = {'Object': scope, 'IO': scope.create_child('IO')} while pending: actual = pending.pop(0) type_ = self.context.get_type(actual[0]) - if type_.parent.name not in ('Object', ''): + if type_.parent.name != '': try: - scopes[type_.name] = scopes[type_.parent.name].create_child() + scopes[type_.name] = scopes[type_.parent.name].create_child(type_.name) self.visit(actual[1], scopes[type_.name]) except KeyError: # Parent not visited yet pending.append(actual) else: - scopes[type_.name] = scope.create_child() + scopes[type_.name] = scope.create_child(type_.name) self.visit(actual[1], scopes[type_.name]) return scope @@ -56,7 +53,7 @@ def visit(self, node: ClassDeclarationNode, scope: Scope): if isinstance(feature, AttrDeclarationNode): self.visit(feature, scope) elif isinstance(feature, FuncDeclarationNode): - self.visit(feature, scope.create_child()) + self.visit(feature, scope.create_child(feature.id)) else: raise Exception(f'Invalid feature at class {node.id}') @@ -79,11 +76,11 @@ def visit(self, node: AttrDeclarationNode, scope: Scope): self.errors.append(err.UNDEFINED_TYPE % (node.type_pos, node.type)) if node.expr is not None: - expr_type = self.visit(node.expr, scope.create_child()) + expr_type = self.visit(node.expr, scope) if not expr_type.conforms_to(attr_type): self.errors.append(err.INCOMPATIBLE_TYPES % (node.expr_pos, expr_type.name, attr_type.name)) - scope.define_variable(node.id, attr_type) + scope.define_variable(node.id, attr_type, is_attr=True) @visitor.when(FuncDeclarationNode) def visit(self, node: FuncDeclarationNode, scope: Scope): @@ -97,7 +94,7 @@ def visit(self, node: FuncDeclarationNode, scope: Scope): except SemanticError: pass - scope.define_variable('self', self.current_type) + scope.define_variable('self', self.current_type, is_param=True) for param_node in node.params: self.visit(param_node, scope) @@ -126,7 +123,7 @@ def visit(self, node: ParamNode, scope: Scope): # this error is logged by the type builder # self.errors.append(err.UNDEFINED_TYPE % (node.type_pos, node.type)) type_ = ErrorType() - scope.define_variable(node.id, type_) + scope.define_variable(node.id, type_, is_param=True) else: self.errors.append(err.LOCAL_ALREADY_DEFINED % (node.pos, node.id, self.current_method.name)) diff --git a/src/coolcmp/semantics/collector.py b/src/coolcmp/semantics/collector.py index 9eb3e5bbd..e16f4e82d 100644 --- a/src/coolcmp/semantics/collector.py +++ b/src/coolcmp/semantics/collector.py @@ -1,8 +1,9 @@ from __future__ import annotations -from coolcmp import visitor, errors as err -from coolcmp.ast import ProgramNode, ClassDeclarationNode -from coolcmp.semantic import Context, SemanticError +from coolcmp import errors as err +from coolcmp.utils import visitor +from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode +from coolcmp.utils.semantic import Context, SemanticError class TypeCollector(object): diff --git a/src/coolcmp/semantics/consistence.py b/src/coolcmp/semantics/consistence.py index 5c531ff94..d0e635af3 100644 --- a/src/coolcmp/semantics/consistence.py +++ b/src/coolcmp/semantics/consistence.py @@ -1,8 +1,9 @@ from __future__ import annotations -from coolcmp import visitor, errors as err -from coolcmp.ast import ProgramNode, ClassDeclarationNode -from coolcmp.semantic import Context, ErrorType, SemanticError +from coolcmp import errors as err +from coolcmp.utils import visitor +from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode +from coolcmp.utils.semantic import Context, ErrorType, SemanticError class TypeConsistence: diff --git a/src/coolcmp/utils/__init__.py b/src/coolcmp/utils/__init__.py new file mode 100644 index 000000000..16281fe0b --- /dev/null +++ b/src/coolcmp/utils/__init__.py @@ -0,0 +1 @@ +from .utils import * diff --git a/src/coolcmp/ast.py b/src/coolcmp/utils/ast.py similarity index 87% rename from src/coolcmp/ast.py rename to src/coolcmp/utils/ast.py index edc0e69dd..5eaca5a5e 100644 --- a/src/coolcmp/ast.py +++ b/src/coolcmp/utils/ast.py @@ -2,6 +2,7 @@ Cool AST. """ from __future__ import annotations +from typing import Type class Node: @@ -19,7 +20,7 @@ def pos(self) -> tuple[int, int]: class ProgramNode(Node): - def __init__(self, declarations: list[DeclarationNode]): + def __init__(self, declarations: list[ClassDeclarationNode]): super().__init__() self.declarations = declarations @@ -58,7 +59,11 @@ def __init__(self, idx: str, typex: str): class FuncDeclarationNode(DeclarationNode): - def __init__(self, idx: str, params: list[ParamNode], return_type: str, body): + def __init__(self, + idx: str, + params: list[ParamNode], + return_type: str, + body: ExpressionNode): super().__init__() self.id = idx @@ -160,7 +165,11 @@ def __init__(self, condition: ExpressionNode, body: ExpressionNode): class CallNode(ExpressionNode): - def __init__(self, idx: str, args, obj: ExpressionNode = None, typex: str = None): + def __init__(self, + idx: str, + args: list[ExpressionNode], + obj: ExpressionNode = None, + typex: str = None): super().__init__() self.obj = obj diff --git a/src/coolcmp/formatter.py b/src/coolcmp/utils/ast_formatter.py similarity index 91% rename from src/coolcmp/formatter.py rename to src/coolcmp/utils/ast_formatter.py index d8f6315b9..7ebe7a78b 100644 --- a/src/coolcmp/formatter.py +++ b/src/coolcmp/utils/ast_formatter.py @@ -1,10 +1,16 @@ -from coolcmp import visitor -from coolcmp.ast import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode, LetNode, \ +from __future__ import annotations + +from . import visitor +from .ast import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode, LetNode, \ AssignNode, BlockNode, ConditionalNode, WhileNode, CaseNode, CallNode, BinaryNode, AtomicNode, InstantiateNode, \ - UnaryNode + UnaryNode, VariableNode + +class ASTFormatter(object): + + def __init__(self): + self.current_type: str | None = None -class FormatVisitor(object): @visitor.on('node') def visit(self, node, tabs): pass @@ -17,6 +23,8 @@ def visit(self, node: ProgramNode, tabs: int = 0): @visitor.when(ClassDeclarationNode) def visit(self, node: ClassDeclarationNode, tabs: int = 0): + self.current_type = node.id + parent = '' if node.parent is None else f": {node.parent}" ans = ' ' * tabs + f'\\__ClassDeclarationNode: class {node.id} {parent} {{ ... }}' features = '\n'.join(self.visit(child, tabs + 1) for child in node.features) @@ -25,7 +33,8 @@ def visit(self, node: ClassDeclarationNode, tabs: int = 0): @visitor.when(AttrDeclarationNode) def visit(self, node: AttrDeclarationNode, tabs: int = 0): ans = ' ' * tabs + f'\\__AttrDeclarationNode: {node.id} : {node.type}' - return f'{ans}' + expr = f'\n{self.visit(node.expr, tabs + 1)}' if node.expr is not None else '' + return f'{ans}{expr}' @visitor.when(FuncDeclarationNode) def visit(self, node: FuncDeclarationNode, tabs: int = 0): @@ -110,7 +119,7 @@ def visit(self, node: CaseNode, tabs: int = 0): @visitor.when(CallNode) def visit(self, node: CallNode, tabs: int = 0): - obj = self.visit(node.obj, tabs + 1) + obj = self.visit(node.obj or VariableNode('self'), tabs + 1) + ' ' ans = ' ' * tabs + f'\\__CallNode: .{node.id}(, ..., )' args = '\n'.join(self.visit(arg, tabs + 1) for arg in node.args) return f'{ans}\n{obj}\n{args}' diff --git a/src/coolcmp/utils/cil.py b/src/coolcmp/utils/cil.py new file mode 100644 index 000000000..3c448af04 --- /dev/null +++ b/src/coolcmp/utils/cil.py @@ -0,0 +1,221 @@ +from __future__ import annotations + +from coolcmp.utils import ast + + +class Node: + pass + + +class ProgramNode(Node): + def __init__(self, + dot_types: list[TypeNode], + dot_data: list[DataNode], + dot_code: list[FunctionNode]): + self.dot_types = dot_types + self.dot_data = dot_data + self.dot_code = dot_code + + def get_type(self, name: str): + for type_ in self.dot_types: + if type_.name == name: + return type_ + + def get_data_name(self, value: str): + for data in self.dot_data: + if data.value == value: + return data.name + raise ValueError(f'No data defined for value {value}') + + def set_data(self, value: str): + if value not in [data.value for data in self.dot_data]: + self.dot_data.append(DataNode(f's{len(self.dot_data) + 1}', value)) + + +class TypeNode(Node): + def __init__(self, name: str, attrs: list[str], methods: dict[str, str]): + self.name = name + self.attributes = attrs + self.methods = methods + self.attr_expr_nodes: dict[str, ast.ExpressionNode] = {} + + # Add the expression node of the attributes, so when is created an instance + # get quick access to the instructions of the attribute initialization. + def add_attr_node(self, attr: str, node: ast.ExpressionNode): + self.attr_expr_nodes[attr] = node + + def get_attr_node(self, attr: str): + return self.attr_expr_nodes.get(attr) + + +class DataNode(Node): + def __init__(self, vname: str, value: str): + self.name = vname + self.value = value + + +class FunctionNode(Node): + def __init__(self, + name: str, + params: list[ParamNode], + local_vars: list[LocalNode], + instructions: list[InstructionNode]): + self.name = name + self.params = params + self.local_vars = local_vars + self.instructions = instructions + + +class ParamNode(Node): + def __init__(self, name: str): + self.name = name + + +class LocalNode(Node): + def __init__(self, name: str): + self.name = name + + +class InstructionNode(Node): + pass + + +class AssignNode(InstructionNode): + def __init__(self, dest: str, source: str): + self.dest = dest + self.source = source + + +class ArithmeticNode(InstructionNode): + def __init__(self, dest, left, right): + self.dest = dest + self.left = left + self.right = right + + +class PlusNode(ArithmeticNode): + pass + + +class MinusNode(ArithmeticNode): + pass + + +class StarNode(ArithmeticNode): + pass + + +class DivNode(ArithmeticNode): + pass + + +class GetAttrNode(InstructionNode): + def __init__(self, dest: str, src: str, attr: str): + self.dest = dest + self.src = src + self.attr = attr + + +class SetAttrNode(InstructionNode): + def __init__(self, instance: str, attr: str, value: str): + self.instance = instance + self.attr = attr + self.value = value + + +class GetIndexNode(InstructionNode): + pass + + +class SetIndexNode(InstructionNode): + pass + + +class AllocateNode(InstructionNode): + def __init__(self, type_: str, dest: str): + self.type = type_ + self.dest = dest + + +class ArrayNode(InstructionNode): + pass + + +class TypeOfNode(InstructionNode): + def __init__(self, obj, dest): + self.type = None + self.obj = obj + self.dest = dest + + +class LabelNode(InstructionNode): + pass + + +class GotoNode(InstructionNode): + pass + + +class GotoIfNode(InstructionNode): + pass + + +class StaticCallNode(InstructionNode): + def __init__(self, function: str, dest: str): + self.function = function + self.dest = dest + + +class DynamicCallNode(InstructionNode): + def __init__(self, xtype, method, dest): + self.type = xtype + self.method = method + self.dest = dest + + +class ArgNode(InstructionNode): + def __init__(self, name: str): + self.name = name + + +class ReturnNode(InstructionNode): + def __init__(self, value=None): + self.value = value + + +class LoadNode(InstructionNode): + def __init__(self, dest, msg): + self.dest = dest + self.msg = msg + + +class LengthNode(InstructionNode): + pass + + +class ConcatNode(InstructionNode): + pass + + +class PrefixNode(InstructionNode): + pass + + +class SubstringNode(InstructionNode): + pass + + +class ToStrNode(InstructionNode): + def __init__(self, dest, value): + self.dest = dest + self.value = value + + +class ReadNode(InstructionNode): + def __init__(self, dest): + self.dest = dest + + +class PrintNode(InstructionNode): + def __init__(self, str_addr): + self.str_addr = str_addr diff --git a/src/coolcmp/utils/cil_formatter.py b/src/coolcmp/utils/cil_formatter.py new file mode 100644 index 000000000..3d602bc46 --- /dev/null +++ b/src/coolcmp/utils/cil_formatter.py @@ -0,0 +1,119 @@ +from . import visitor +from .cil import ProgramNode, TypeNode, FunctionNode, ParamNode, AssignNode, PlusNode, MinusNode, StarNode, DivNode, \ + TypeOfNode, StaticCallNode, DynamicCallNode, ArgNode, ReturnNode, LocalNode, AllocateNode, DataNode, GetAttrNode, \ + PrintNode, LoadNode, SetAttrNode + + +class CILFormatter(object): + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode): + types = '\n'.join(self.visit(t) for t in node.dot_types) + data = '\n'.join(self.visit(t) for t in node.dot_data) + code = '\n'.join(self.visit(t) for t in node.dot_code) + + return ( + '.TYPES\n' + (f'{types}\n\n' if types else '\n') + + '.DATA\n' + (f'{data}\n\n' if data else '\n') + + f'.CODE\n{code}' + ) + + @visitor.when(TypeNode) + def visit(self, node: TypeNode): + attributes = '\n '.join(f'attribute {x}' for x in node.attributes) + methods = '\n '.join(f'method {x}: {y}' for x, y in node.methods.items()) + + return ( + f'type {node.name} {{' + + (f'\n {attributes}\n' if attributes else '') + + (f'\n {methods}\n' if methods else '') + '}' + ) + + @visitor.when(DataNode) + def visit(self, node: DataNode): + return f'{node.name} = {node.value}' + + @visitor.when(FunctionNode) + def visit(self, node: FunctionNode): + params = '\n '.join(self.visit(x) for x in node.params) + local_vars = '\n '.join(self.visit(x) for x in node.local_vars) + instructions = '\n '.join(self.visit(x) for x in node.instructions) + + return ( + f'function {node.name} {{' + + (f'\n {params}\n' if params else '') + + (f'\n {local_vars}\n' if local_vars else '') + + (f'\n {instructions}\n' if instructions else '') + '}' + ) + + @visitor.when(ParamNode) + def visit(self, node: ParamNode): + return f'PARAM {node.name}' + + @visitor.when(LocalNode) + def visit(self, node: LocalNode): + return f'LOCAL {node.name}' + + @visitor.when(AssignNode) + def visit(self, node: AssignNode): + return f'{node.dest} = {node.source}' + + @visitor.when(PlusNode) + def visit(self, node: PlusNode): + return f'{node.dest} = {node.left} + {node.right}' + + @visitor.when(MinusNode) + def visit(self, node: MinusNode): + return f'{node.dest} = {node.left} - {node.right}' + + @visitor.when(StarNode) + def visit(self, node: StarNode): + return f'{node.dest} = {node.left} * {node.right}' + + @visitor.when(DivNode) + def visit(self, node: DivNode): + return f'{node.dest} = {node.left} / {node.right}' + + @visitor.when(GetAttrNode) + def visit(self, node: GetAttrNode): + return f'{node.dest} = GETATTR {node.src} {node.attr}' + + @visitor.when(SetAttrNode) + def visit(self, node: SetAttrNode): + return f'SETATTR {node.instance} {node.attr} {node.value}' + + @visitor.when(AllocateNode) + def visit(self, node: AllocateNode): + return f'{node.dest} = ALLOCATE {node.type}' + + @visitor.when(TypeOfNode) + def visit(self, node: TypeOfNode): + return f'{node.dest} = TYPEOF {node.obj}' + + @visitor.when(StaticCallNode) + def visit(self, node: StaticCallNode): + return f'{node.dest} = CALL {node.function}' + + @visitor.when(DynamicCallNode) + def visit(self, node: DynamicCallNode): + return f'{node.dest} = VCALL {node.type} {node.method}' + + @visitor.when(ArgNode) + def visit(self, node: ArgNode): + return f'ARG {node.name}' + + @visitor.when(ReturnNode) + def visit(self, node: ReturnNode): + return f'RETURN {node.value if node.value is not None else ""}' + + @visitor.when(LoadNode) + def visit(self, node: LoadNode): + return f'{node.dest} = LOAD {node.msg}' + + @visitor.when(PrintNode) + def visit(self, node: PrintNode): + return f'PRINT {node.str_addr}' + diff --git a/src/coolcmp/semantic.py b/src/coolcmp/utils/semantic.py similarity index 87% rename from src/coolcmp/semantic.py rename to src/coolcmp/utils/semantic.py index d36e1c0c3..e4bd48609 100644 --- a/src/coolcmp/semantic.py +++ b/src/coolcmp/utils/semantic.py @@ -2,8 +2,6 @@ import itertools as itt from collections import OrderedDict -from coolcmp.ast import Node, ParamNode - class SemanticError(Exception): @property @@ -224,8 +222,9 @@ def __str__(self) -> str: class Scope: - def __init__(self, parent: Scope = None): + def __init__(self, tag: str = None, parent: Scope = None): self.locals: list[VariableInfo] = [] + self.tag = tag self.parent = parent self.children: list[Scope] = [] self.index = 0 if parent is None else len(parent) @@ -233,8 +232,8 @@ def __init__(self, parent: Scope = None): def __len__(self) -> int: return len(self.locals) - def create_child(self) -> Scope: - child = Scope(self) + def create_child(self, tag_type: str = None) -> Scope: + child = Scope(tag_type, self) self.children.append(child) return child @@ -256,11 +255,40 @@ def is_defined(self, vname: str) -> bool: def is_local(self, vname: str) -> bool: return any(True for x in self.locals if x.name == vname) - def __str__(self) -> str: - s = 'Scope\n' + def get_tagged_scope(self, tag: str) -> Scope | None: + scope = None + if self.tag is not None and self.tag == tag: + scope = self + else: + for child in self.children: + if (child_scope := child.get_tagged_scope(tag)) is not None: + scope = child_scope + break + return scope + + def all_locals(self) -> list[VariableInfo]: + if self.parent is None: + return self.locals + else: + return self.locals + self.parent.all_locals() + + def to_str(self, tabs: int = 0) -> str: + s = f'Scope ({self.tag}):\n' for v in self.locals: - s += f'{v.name}: {v.type.name}\n' + if v.is_attr: + tag = 'attr' + elif v.is_param: + tag = 'param' + else: + tag = 'local' + s += ' ' * tabs + f'\\__ [{tag}] {v.name}: {v.type.name}\n' if self.children: for child in self.children: - s += str(child) + s += ' ' * tabs + f'\\__ {child.to_str(tabs + 1)}' return s + + def __str__(self): + return self.to_str() + + def __repr__(self): + return str(self) diff --git a/src/coolcmp/utils.py b/src/coolcmp/utils/utils.py similarity index 100% rename from src/coolcmp/utils.py rename to src/coolcmp/utils/utils.py diff --git a/src/coolcmp/visitor.py b/src/coolcmp/utils/visitor.py similarity index 100% rename from src/coolcmp/visitor.py rename to src/coolcmp/utils/visitor.py diff --git a/tests/codes/hello.cl b/tests/codes/hello.cl new file mode 100644 index 000000000..64906b47c --- /dev/null +++ b/tests/codes/hello.cl @@ -0,0 +1,45 @@ +class Main inherits IO { + msg : String <- "Hello World!\n"; + + main() : IO { + self.out_string(msg) + }; +}; + +(* + .TYPES + type Main { + attribute Main_msg ; + method Main_main: f1 ; + } + + .DATA + s1 = "Hello World!\n"; + + .CODE + function entry { + LOCAL lmsg ; + LOCAL instance ; + LOCAL result ; + + lmsg = LOAD s1 ; + instance = ALLOCATE Main ; + SETATTR instance Main_msg lmsg ; + + ARG instance ; + result = VCALL Main Main_main ; + + RETURN 0 ; + } + + function f1 { + PARAM self ; + + LOCAL lmsg ; + + lmsg = GETATTR self Main_msg ; + PRINT lmsg ; + + RETURN self ; + } +*) \ No newline at end of file diff --git a/tests/codes/silly.cl b/tests/codes/silly.cl new file mode 100644 index 000000000..e9cc9c8ba --- /dev/null +++ b/tests/codes/silly.cl @@ -0,0 +1,16 @@ +class Main { + x : Sally <- (new Sally).copy(); + main() : Sally { + x + }; +}; + +class Silly { + copy() : SELF_TYPE { + self + }; +}; + +class Sally inherits Silly { + y : String <- "Hi from Silly"; +}; From 4b1e9534681c2462f15b2b690e4b37b73aced446 Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Tue, 22 Feb 2022 13:08:18 -0500 Subject: [PATCH 011/161] Added more nodes to CIL --- src/coolcmp/codegen/cool2cil/cil_visitor.py | 2 +- src/coolcmp/codegen/cool2cil/code_visitor.py | 259 +++++++++++++++++-- src/coolcmp/semantics/builder.py | 14 +- src/coolcmp/semantics/checker.py | 27 +- src/coolcmp/semantics/collector.py | 11 +- src/coolcmp/utils/cil.py | 43 ++- src/coolcmp/utils/cil_formatter.py | 103 ++++---- src/coolcmp/utils/semantic.py | 5 +- tests/codes/conditional.cl | 10 + tests/codes/let.cl | 13 + tests/codes/loop.cl | 8 + 11 files changed, 397 insertions(+), 98 deletions(-) create mode 100644 tests/codes/conditional.cl create mode 100644 tests/codes/let.cl create mode 100644 tests/codes/loop.cl diff --git a/src/coolcmp/codegen/cool2cil/cil_visitor.py b/src/coolcmp/codegen/cool2cil/cil_visitor.py index 69ebf7533..eac282f3a 100644 --- a/src/coolcmp/codegen/cool2cil/cil_visitor.py +++ b/src/coolcmp/codegen/cool2cil/cil_visitor.py @@ -1,7 +1,7 @@ class CILVisitor: def __init__(self): - self.function_id: int = 0 + self.function_id: int = -1 @property def next_function_id(self) -> int: diff --git a/src/coolcmp/codegen/cool2cil/code_visitor.py b/src/coolcmp/codegen/cool2cil/code_visitor.py index 770eab673..890e369e4 100644 --- a/src/coolcmp/codegen/cool2cil/code_visitor.py +++ b/src/coolcmp/codegen/cool2cil/code_visitor.py @@ -1,4 +1,5 @@ from __future__ import annotations +from copy import deepcopy from coolcmp.codegen.cool2cil import CILVisitor from coolcmp.utils import ast, cil, visitor @@ -15,6 +16,12 @@ def __init__(self, cil_root: cil.ProgramNode): self.code = cil_root.dot_code self.current_function: cil.FunctionNode | None = None self.current_type: str | None = None + self.label_count = -1 + + def new_label(self, name: str) -> cil.LabelNode: + self.label_count += 1 + name = f'_{name}_{self.label_count}' + return cil.LabelNode(name) def add_function(self, name: str = None): if name is None: @@ -50,13 +57,12 @@ def visit(self, node: ast.ProgramNode, scope: Scope): for feature in class_.features: if isinstance(feature, ast.FuncDeclarationNode) and feature.id == 'main': self.add_function('entry') - main_scope = scope.get_tagged_scope('Main') + main_scope = deepcopy(scope.get_tagged_scope('Main')) instance = self.add_local('instance') self.add_inst(cil.AllocateNode('Main', instance)) for attr in (f for f in class_.features if isinstance(f, ast.AttrDeclarationNode)): - if attr.expr is not None: - expr_dest = self.visit(attr.expr, main_scope) - self.add_inst(cil.SetAttrNode(instance, f'Main_{attr.id}', expr_dest)) + expr_dest = self.visit(attr.expr, main_scope) + self.add_inst(cil.SetAttrNode(instance, f'Main_{attr.id}', expr_dest)) result = self.add_local('result') self.add_inst(cil.ArgNode(instance)) self.add_inst(cil.DynamicCallNode('Main', 'Main_main', result)) @@ -73,24 +79,48 @@ def visit(self, node: ast.ProgramNode, scope: Scope): # cil.FunctionNode('abort', [], [], []), # cil.FunctionNode('type_name', [], [], []), # cil.FunctionNode('copy', [], [], []), - # cil.FunctionNode('out_string', [], [], []), + cil.FunctionNode( + name='get_void', + params=[], + local_vars=[], + instructions=[ + cil.LocalNode('void_inst'), + cil.AllocateNode('', 'void_inst'), + cil.ReturnNode('void_inst'), + ] + ), cil.FunctionNode( name='out_string', params=[ - cil.ParamNode('str_addr') + cil.ParamNode('self'), + cil.ParamNode('str_addr'), ], local_vars=[], instructions=[ - cil.PrintNode('str_addr'), - cil.ReturnNode(0), + cil.PrintStringNode('str_addr'), + cil.ReturnNode(), + ] + ), + cil.FunctionNode( + name='out_int', + params=[ + cil.ParamNode('self'), + cil.ParamNode('int_addr'), + ], + local_vars=[], + instructions=[ + cil.PrintIntNode('int_addr'), + cil.ReturnNode(), ] ), - # cil.FunctionNode('out_int', [], [], []), # cil.FunctionNode('in_int', [], [], []), ] @visitor.when(ast.ClassDeclarationNode) def visit(self, node: ast.ClassDeclarationNode, scope: Scope): + print('>>> class', node.id) + print(scope) + self.current_type = node.id methods = (f for f in node.features if isinstance(f, ast.FuncDeclarationNode)) for method in methods: @@ -98,6 +128,8 @@ def visit(self, node: ast.ClassDeclarationNode, scope: Scope): @visitor.when(ast.FuncDeclarationNode) def visit(self, node: ast.FuncDeclarationNode, scope: Scope): + print('>>> method', node.id) + print(scope) self.add_function() for local in scope.all_locals(): @@ -112,17 +144,123 @@ def visit(self, node: ast.FuncDeclarationNode, scope: Scope): result = self.visit(node.body, scope) self.add_inst(cil.ReturnNode(result)) + @visitor.when(ast.LetDeclarationNode) + def visit(self, node: ast.LetDeclarationNode, scope: Scope): + local = self.add_local(node.id, internal=False) + if node.expr is not None: + expr_dest = self.visit(node.expr, scope) + self.add_inst(cil.AssignNode(local, expr_dest)) + + @visitor.when(ast.LetNode) + def visit(self, node: ast.LetNode, scope: Scope): + scope = scope.children.pop(0) + for let_declaration in node.declarations: + self.visit(let_declaration, scope) + + return self.visit(node.expr, scope) + + @visitor.when(ast.ParenthesisExpr) + def visit(self, node: ast.ParenthesisExpr, scope: Scope): + return self.visit(node, scope) + + @visitor.when(ast.BlockNode) + def visit(self, node: ast.BlockNode, scope: Scope): + scope = scope.children.pop(0) + last_expr = None + for expr in node.expressions: + last_expr = self.visit(expr, scope) + + return last_expr + + @visitor.when(ast.CaseBranchNode) + def visit(self, node: ast.CaseBranchNode, scope: Scope): + pass + + @visitor.when(ast.CaseNode) + def visit(self, node: ast.CaseNode, scope: Scope): + pass + + @visitor.when(ast.AssignNode) + def visit(self, node: ast.AssignNode, scope: Scope): + expr_dest = self.visit(node.expr, scope) + self.add_inst(cil.AssignNode(node.id, expr_dest)) + return expr_dest + + @visitor.when(ast.ConditionalNode) + def visit(self, node: ast.ConditionalNode, scope: Scope): + """ + Note that the 'else' branch comes before the 'then' branch. + + + if_dest = + IF if_dest GOTO then + else_dest = + cond_res = else_dest + GOTO endif + LABEL then + then_dest = + cond_res = then_dest + LABEL endif + """ + then_label = self.new_label('then') + endif_label = self.new_label('endif') + + cond_res = self.add_local('cond_res') + if_dest = self.visit(node.if_expr, scope) + self.add_inst(cil.GotoIfNode(if_dest, then_label.name)) + else_dest = self.visit(node.else_expr, scope.children[1]) + self.add_inst(cil.AssignNode(cond_res, else_dest)) + self.add_inst(cil.GotoNode(endif_label.name)) + self.add_inst(then_label) + then_dest = self.visit(node.then_expr, scope.children[0]) + self.add_inst(cil.AssignNode(cond_res, then_dest)) + self.add_inst(endif_label) + return cond_res + + @visitor.when(ast.WhileNode) + def visit(self, node: ast.WhileNode, scope: Scope): + """ + + LABEL while_cond + cond_dest = + IF cond_dest GOTO while_body + GOTO end_while + LABEL while_body + <----- the body return is not used, just side effects + GOTO while_cond + LABEL end_while + + void_res = VCALL Object get_void + """ + cond_label = self.new_label('while_cond') + body_label = self.new_label('while_body') + end_while_label = self.new_label('end_while') + + self.add_inst(cond_label) + cond_dest = self.visit(node.condition, scope) + self.add_inst(cil.GotoIfNode(cond_dest, body_label.name)) + self.add_inst(cil.GotoNode(end_while_label.name)) + self.add_inst(body_label) + self.visit(node.body, scope) + self.add_inst(cil.GotoNode(cond_label.name)) + self.add_inst(end_while_label) + + # return void + void_res = self.add_local('void_res') + self.add_inst(cil.StaticCallNode('get_void', void_res)) + return void_res + @visitor.when(ast.CallNode) def visit(self, node: ast.CallNode, scope: Scope): - # allocate and push the object type + # allocate and push the object if node.obj is None: obj = ast.VariableNode('self') else: obj = node.obj obj_dest = self.visit(obj, scope) - internal = self.add_local('internal') - self.add_inst(cil.TypeOfNode(obj_dest, internal)) - self.add_inst(cil.ArgNode(internal)) + inst_type = self.add_local('inst_type') + self.add_inst(cil.TypeOfNode(obj_dest, inst_type)) + self.add_inst(cil.ArgNode(obj_dest)) # allocate and push the args for arg in node.args: @@ -130,20 +268,10 @@ def visit(self, node: ast.CallNode, scope: Scope): self.add_inst(cil.ArgNode(arg_dest)) # call the function - result = self.add_local('result') - self.add_inst(cil.DynamicCallNode(internal, node.id, result)) - - return result + call_res = self.add_local('call_res') + self.add_inst(cil.DynamicCallNode(inst_type, node.id, call_res)) - @visitor.when(ast.VariableNode) - def visit(self, node: ast.VariableNode, scope: Scope): - return node.lex - - @visitor.when(ast.StringNode) - def visit(self, node: ast.StringNode, scope: Scope): - dest = self.add_local('internal') - self.add_inst(cil.LoadNode(dest, self.root.get_data_name(node.lex))) - return dest + return call_res @visitor.when(ast.InstantiateNode) def visit(self, node: ast.StringNode, scope: Scope): @@ -156,3 +284,82 @@ def visit(self, node: ast.StringNode, scope: Scope): attr_dest = self.visit(attr_expr, scope) self.add_inst(cil.SetAttrNode(instance, attr, attr_dest)) return instance + + @visitor.when(ast.StringNode) + def visit(self, node: ast.StringNode, _): + dest = self.add_local('string') + self.add_inst(cil.LoadNode(dest, self.root.get_data_name(node.lex))) + return dest + + @visitor.when(ast.AtomicNode) + def visit(self, node: ast.AtomicNode, _): + return node.lex + + @visitor.when(ast.PlusNode) + def visit(self, node: ast.PlusNode, scope: Scope): + return self.build_arithmetic_node(cil.PlusNode, node, scope) + + @visitor.when(ast.MinusNode) + def visit(self, node: ast.MinusNode, scope: Scope): + return self.build_arithmetic_node(cil.MinusNode, node, scope) + + @visitor.when(ast.StarNode) + def visit(self, node: ast.PlusNode, scope: Scope): + return self.build_arithmetic_node(cil.StarNode, node, scope) + + @visitor.when(ast.DivNode) + def visit(self, node: ast.DivNode, scope: Scope): + return self.build_arithmetic_node(cil.DivNode, node, scope) + + @visitor.when(ast.LessThanNode) + def visit(self, node: ast.LessThanNode, scope: Scope): + return self.build_arithmetic_node(cil.MinusNode, node, scope) + + @visitor.when(ast.LessEqualNode) + def visit(self, node: ast.LessEqualNode, scope: Scope): + return self.build_arithmetic_node(cil.MinusNode, node, scope) + + @visitor.when(ast.EqualNode) + def visit(self, node: ast.EqualNode, scope: Scope): + if isinstance(node.left, ast.IntegerNode) and isinstance(node.right, ast.IntegerNode): + return self.build_arithmetic_node(cil.MinusNode, node, scope) + else: + left_dest = self.visit(node.left, scope) + right_dest = self.visit(node.right, scope) + left_type = self.add_local('left_type') + right_type = self.add_local('right_type') + self.add_inst(cil.TypeOfNode(left_dest, left_type)) + self.add_inst(cil.TypeOfNode(right_dest, right_type)) + comp_res = self.add_local('comp_res') + self.add_inst(cil.CompareNode(comp_res, left_type, right_type)) + return comp_res + + @visitor.when(ast.IsVoidNode) + def visit(self, node: ast.IsVoidNode, scope: Scope): + expr_dest = self.visit(node.expr, scope) + type_expr = self.add_local('expr_type') + self.add_inst(cil.TypeOfNode(expr_dest, type_expr)) + comp_res = self.add_local('comp_res') + self.add_inst(cil.CompareNode(comp_res, type_expr, '')) + return comp_res + + @visitor.when(ast.NegationNode) + def visit(self, node: ast.NegationNode, scope: Scope): + neg_res = self.add_local('neg_res') + expr_res = self.visit(node.expr, scope) + self.add_inst(cil.NegationNode(neg_res, expr_res)) + return neg_res + + @visitor.when(ast.ComplementNode) + def visit(self, node: ast.ComplementNode, scope: Scope): + com_res = self.add_local('com_res') + expr_res = self.visit(node.expr, scope) + self.add_inst(cil.ComplementNode(com_res, expr_res)) + return com_res + + def build_arithmetic_node(self, new_node_cls, node: ast.BinaryNode, scope: Scope): + left_dest = self.visit(node.left, scope) + right_dest = self.visit(node.right, scope) + oper_dest = self.add_local('oper_dest') + self.add_inst(new_node_cls(oper_dest, left_dest, right_dest)) + return oper_dest diff --git a/src/coolcmp/semantics/builder.py b/src/coolcmp/semantics/builder.py index f9caa29d2..638e6a417 100644 --- a/src/coolcmp/semantics/builder.py +++ b/src/coolcmp/semantics/builder.py @@ -1,7 +1,8 @@ from __future__ import annotations from coolcmp import errors as err -from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode, FuncDeclarationNode, AttrDeclarationNode +from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode, FuncDeclarationNode, AttrDeclarationNode, \ + IntegerNode, StringNode, BooleanNode, VariableNode from coolcmp.utils.semantic import SemanticError, Context, ErrorType, Type from coolcmp.utils import visitor @@ -84,6 +85,17 @@ def visit(self, node: AttrDeclarationNode): attr_type = ErrorType() self.errors.append(err.UNDEFINED_TYPE % (node.pos, node.type)) + # add a default initialization expr to the node if it doesn't have one + if node.expr is None: + if attr_type == self.context.get_type('Int'): + node.expr = IntegerNode('0') + elif attr_type == self.context.get_type('String'): + node.expr = StringNode('') + elif attr_type == self.context.get_type('Bool'): + node.expr = BooleanNode('false') + else: + node.expr = VariableNode('void') + try: self.current_type.define_attribute(node.id, attr_type, self.current_type.name) except SemanticError: diff --git a/src/coolcmp/semantics/checker.py b/src/coolcmp/semantics/checker.py index 2999c9607..870580a7a 100644 --- a/src/coolcmp/semantics/checker.py +++ b/src/coolcmp/semantics/checker.py @@ -24,6 +24,9 @@ def visit(self, node, scope): def visit(self, node: ProgramNode, scope: Scope = None): scope = Scope('Object') + for attr in self.context.get_type('Object').attributes: + scope.define_variable(attr.name, attr.type, is_attr=True) + pending = [(class_node.id, class_node) for class_node in node.declarations] scopes = {'Object': scope, 'IO': scope.create_child('IO')} @@ -137,12 +140,11 @@ def visit(self, node: BlockNode, scope: Scope): @visitor.when(LetNode) def visit(self, node: LetNode, scope: Scope): - new_scope = scope + child_scope = scope.create_child() for declaration in node.declarations: - new_scope = new_scope.create_child() - self.visit(declaration, new_scope) + self.visit(declaration, child_scope) - return self.visit(node.expr, new_scope.create_child()) + return self.visit(node.expr, child_scope) @visitor.when(LetDeclarationNode) def visit(self, node: LetDeclarationNode, scope: Scope): @@ -157,7 +159,7 @@ def visit(self, node: LetDeclarationNode, scope: Scope): else: scope.define_variable(node.id, type_) - expr_type: Type = self.visit(node.expr, scope.create_child()) if node.expr is not None else None + expr_type: Type = self.visit(node.expr, scope) if node.expr is not None else None if expr_type is not None and not expr_type.conforms_to(type_): self.errors.append(err.INCOMPATIBLE_TYPES % (node.expr_pos, expr_type.name, type_.name)) @@ -165,7 +167,8 @@ def visit(self, node: LetDeclarationNode, scope: Scope): @visitor.when(CaseNode) def visit(self, node: CaseNode, scope: Scope): - self.visit(node.expr, scope.create_child()) + child_scope = scope.create_child() + self.visit(node.expr, child_scope) case_types = [] reported_types = [] @@ -179,7 +182,7 @@ def visit(self, node: CaseNode, scope: Scope): case_types.append(type_) types = [ - self.visit(case, scope.create_child()) + self.visit(case, child_scope) for case in node.cases ] @@ -210,7 +213,7 @@ def visit(self, node: AssignNode, scope: Scope): self.errors.append(err.SELF_IS_READONLY % (node.pos, )) var = scope.find_variable(node.id) - expr_type = self.visit(node.expr, scope.create_child()) + expr_type = self.visit(node.expr, scope) if var is None: self.errors.append(err.VARIABLE_NOT_DEFINED % (node.pos, node.id, self.current_method.name)) else: @@ -221,13 +224,13 @@ def visit(self, node: AssignNode, scope: Scope): @visitor.when(ConditionalNode) def visit(self, node: ConditionalNode, scope: Scope): if_type = self.visit(node.if_expr, scope) - then_type = self.visit(node.then_expr, scope) - esle_type = self.visit(node.else_expr, scope) + then_type = self.visit(node.then_expr, scope.create_child()) + else_type = self.visit(node.else_expr, scope.create_child()) if if_type != self.context.get_type('Bool'): self.errors.append(err.INCOMPATIBLE_TYPES % (node.pos, if_type.name, 'Bool')) - return then_type.join(esle_type) + return then_type.join(else_type) @visitor.when(WhileNode) def visit(self, node: WhileNode, scope: Scope): @@ -235,7 +238,7 @@ def visit(self, node: WhileNode, scope: Scope): if cond_type != self.context.get_type('Bool'): self.errors.append(err.INCOMPATIBLE_TYPES % (node.pos, cond_type.name, 'Bool')) - self.visit(node.body, scope.create_child()) + self.visit(node.body, scope) return self.context.get_type('Object') diff --git a/src/coolcmp/semantics/collector.py b/src/coolcmp/semantics/collector.py index e16f4e82d..40201f04a 100644 --- a/src/coolcmp/semantics/collector.py +++ b/src/coolcmp/semantics/collector.py @@ -3,7 +3,7 @@ from coolcmp import errors as err from coolcmp.utils import visitor from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode -from coolcmp.utils.semantic import Context, SemanticError +from coolcmp.utils.semantic import Context, SemanticError, IntType, VoidType, ErrorType class TypeCollector(object): @@ -20,20 +20,25 @@ def visit(self, node: ProgramNode): self.context = Context() # Default types definition - self.context.create_type('') + self.context.types[''] = ErrorType() + void = self.context.types[''] = VoidType() self_ = self.context.create_type('SELF_TYPE') object_ = self.context.create_type('Object') io = self.context.create_type('IO') string = self.context.create_type('String') - int_ = self.context.create_type('Int') + int_ = self.context.types['Int'] = IntType() bool_ = self.context.create_type('Bool') # Default types inheritance + void.set_parent(object_) io.set_parent(object_) string.set_parent(object_) int_.set_parent(object_) bool_.set_parent(object_) + # Default types attributes + object_.define_attribute('void', void) + # Default types methods object_.define_method('abort', [], [], object_) object_.define_method('type_name', [], [], string) diff --git a/src/coolcmp/utils/cil.py b/src/coolcmp/utils/cil.py index 3c448af04..78b5332f7 100644 --- a/src/coolcmp/utils/cil.py +++ b/src/coolcmp/utils/cil.py @@ -87,7 +87,7 @@ def __init__(self, dest: str, source: str): class ArithmeticNode(InstructionNode): - def __init__(self, dest, left, right): + def __init__(self, dest: str, left: str, right: str): self.dest = dest self.left = left self.right = right @@ -149,15 +149,19 @@ def __init__(self, obj, dest): class LabelNode(InstructionNode): - pass + def __init__(self, name: str): + self.name = name class GotoNode(InstructionNode): - pass + def __init__(self, label: str): + self.label = label class GotoIfNode(InstructionNode): - pass + def __init__(self, condition: str, label: str): + self.condition = condition + self.label = label class StaticCallNode(InstructionNode): @@ -217,5 +221,32 @@ def __init__(self, dest): class PrintNode(InstructionNode): - def __init__(self, str_addr): - self.str_addr = str_addr + def __init__(self, addr: str): + self.addr = addr + + +class PrintIntNode(PrintNode): + pass + + +class PrintStringNode(PrintNode): + pass + + +class NegationNode(InstructionNode): + def __init__(self, dest: str, src: str): + self.dest = dest + self.src = src + + +class ComplementNode(InstructionNode): + def __init__(self, dest: str, src: str): + self.dest = dest + self.src = src + + +class CompareNode(InstructionNode): + def __init__(self, dest: str, left: str, right: str): + self.dest = dest + self.left = left + self.right = right diff --git a/src/coolcmp/utils/cil_formatter.py b/src/coolcmp/utils/cil_formatter.py index 3d602bc46..b87fa81b9 100644 --- a/src/coolcmp/utils/cil_formatter.py +++ b/src/coolcmp/utils/cil_formatter.py @@ -1,7 +1,4 @@ -from . import visitor -from .cil import ProgramNode, TypeNode, FunctionNode, ParamNode, AssignNode, PlusNode, MinusNode, StarNode, DivNode, \ - TypeOfNode, StaticCallNode, DynamicCallNode, ArgNode, ReturnNode, LocalNode, AllocateNode, DataNode, GetAttrNode, \ - PrintNode, LoadNode, SetAttrNode +from . import visitor, cil class CILFormatter(object): @@ -9,8 +6,8 @@ class CILFormatter(object): def visit(self, node): pass - @visitor.when(ProgramNode) - def visit(self, node: ProgramNode): + @visitor.when(cil.ProgramNode) + def visit(self, node: cil.ProgramNode): types = '\n'.join(self.visit(t) for t in node.dot_types) data = '\n'.join(self.visit(t) for t in node.dot_data) code = '\n'.join(self.visit(t) for t in node.dot_code) @@ -21,8 +18,8 @@ def visit(self, node: ProgramNode): f'.CODE\n{code}' ) - @visitor.when(TypeNode) - def visit(self, node: TypeNode): + @visitor.when(cil.TypeNode) + def visit(self, node: cil.TypeNode): attributes = '\n '.join(f'attribute {x}' for x in node.attributes) methods = '\n '.join(f'method {x}: {y}' for x, y in node.methods.items()) @@ -32,12 +29,12 @@ def visit(self, node: TypeNode): (f'\n {methods}\n' if methods else '') + '}' ) - @visitor.when(DataNode) - def visit(self, node: DataNode): + @visitor.when(cil.DataNode) + def visit(self, node: cil.DataNode): return f'{node.name} = {node.value}' - @visitor.when(FunctionNode) - def visit(self, node: FunctionNode): + @visitor.when(cil.FunctionNode) + def visit(self, node: cil.FunctionNode): params = '\n '.join(self.visit(x) for x in node.params) local_vars = '\n '.join(self.visit(x) for x in node.local_vars) instructions = '\n '.join(self.visit(x) for x in node.instructions) @@ -49,71 +46,83 @@ def visit(self, node: FunctionNode): (f'\n {instructions}\n' if instructions else '') + '}' ) - @visitor.when(ParamNode) - def visit(self, node: ParamNode): + @visitor.when(cil.ParamNode) + def visit(self, node: cil.ParamNode): return f'PARAM {node.name}' - @visitor.when(LocalNode) - def visit(self, node: LocalNode): + @visitor.when(cil.LocalNode) + def visit(self, node: cil.LocalNode): return f'LOCAL {node.name}' - @visitor.when(AssignNode) - def visit(self, node: AssignNode): + @visitor.when(cil.AssignNode) + def visit(self, node: cil.AssignNode): return f'{node.dest} = {node.source}' - @visitor.when(PlusNode) - def visit(self, node: PlusNode): + @visitor.when(cil.PlusNode) + def visit(self, node: cil.PlusNode): return f'{node.dest} = {node.left} + {node.right}' - @visitor.when(MinusNode) - def visit(self, node: MinusNode): + @visitor.when(cil.MinusNode) + def visit(self, node: cil.MinusNode): return f'{node.dest} = {node.left} - {node.right}' - @visitor.when(StarNode) - def visit(self, node: StarNode): + @visitor.when(cil.StarNode) + def visit(self, node: cil.StarNode): return f'{node.dest} = {node.left} * {node.right}' - @visitor.when(DivNode) - def visit(self, node: DivNode): + @visitor.when(cil.DivNode) + def visit(self, node: cil.DivNode): return f'{node.dest} = {node.left} / {node.right}' - @visitor.when(GetAttrNode) - def visit(self, node: GetAttrNode): + @visitor.when(cil.GetAttrNode) + def visit(self, node: cil.GetAttrNode): return f'{node.dest} = GETATTR {node.src} {node.attr}' - @visitor.when(SetAttrNode) - def visit(self, node: SetAttrNode): + @visitor.when(cil.SetAttrNode) + def visit(self, node: cil.SetAttrNode): return f'SETATTR {node.instance} {node.attr} {node.value}' - @visitor.when(AllocateNode) - def visit(self, node: AllocateNode): + @visitor.when(cil.AllocateNode) + def visit(self, node: cil.AllocateNode): return f'{node.dest} = ALLOCATE {node.type}' - @visitor.when(TypeOfNode) - def visit(self, node: TypeOfNode): + @visitor.when(cil.TypeOfNode) + def visit(self, node: cil.TypeOfNode): return f'{node.dest} = TYPEOF {node.obj}' - @visitor.when(StaticCallNode) - def visit(self, node: StaticCallNode): + @visitor.when(cil.LabelNode) + def visit(self, node: cil.LabelNode): + return f'LABEL {node.name}' + + @visitor.when(cil.GotoNode) + def visit(self, node: cil.GotoNode): + return f'GOTO {node.label}' + + @visitor.when(cil.GotoIfNode) + def visit(self, node: cil.GotoIfNode): + return f'IF {node.condition} GOTO {node.label}' + + @visitor.when(cil.StaticCallNode) + def visit(self, node: cil.StaticCallNode): return f'{node.dest} = CALL {node.function}' - @visitor.when(DynamicCallNode) - def visit(self, node: DynamicCallNode): + @visitor.when(cil.DynamicCallNode) + def visit(self, node: cil.DynamicCallNode): return f'{node.dest} = VCALL {node.type} {node.method}' - @visitor.when(ArgNode) - def visit(self, node: ArgNode): + @visitor.when(cil.ArgNode) + def visit(self, node: cil.ArgNode): return f'ARG {node.name}' - @visitor.when(ReturnNode) - def visit(self, node: ReturnNode): + @visitor.when(cil.ReturnNode) + def visit(self, node: cil.ReturnNode): return f'RETURN {node.value if node.value is not None else ""}' - @visitor.when(LoadNode) - def visit(self, node: LoadNode): + @visitor.when(cil.LoadNode) + def visit(self, node: cil.LoadNode): return f'{node.dest} = LOAD {node.msg}' - @visitor.when(PrintNode) - def visit(self, node: PrintNode): - return f'PRINT {node.str_addr}' + @visitor.when(cil.PrintNode) + def visit(self, node: cil.PrintNode): + return f'PRINT {node.addr}' diff --git a/src/coolcmp/utils/semantic.py b/src/coolcmp/utils/semantic.py index e4bd48609..60655fa01 100644 --- a/src/coolcmp/utils/semantic.py +++ b/src/coolcmp/utils/semantic.py @@ -167,7 +167,8 @@ def __init__(self): Type.__init__(self, '') def conforms_to(self, other) -> bool: - raise Exception('Invalid type: void type.') + # raise Exception('Invalid type: void type.') + return True def bypass(self) -> bool: return True @@ -178,7 +179,7 @@ def __eq__(self, other: Type) -> bool: class IntType(Type): def __init__(self): - Type.__init__(self, 'int') + Type.__init__(self, 'Int') def __eq__(self, other) -> bool: return other.name == self.name or isinstance(other, IntType) diff --git a/tests/codes/conditional.cl b/tests/codes/conditional.cl new file mode 100644 index 000000000..e806cc2be --- /dev/null +++ b/tests/codes/conditional.cl @@ -0,0 +1,10 @@ +--The predicate must have static type Bool. + +class A { }; +class B inherits A { }; +class C inherits B { }; + +class Main inherits IO { + main(): IO { out_string("Hello World!")}; + a: A <- if let x: Bool <- true in x then new B else new C fi; +}; \ No newline at end of file diff --git a/tests/codes/let.cl b/tests/codes/let.cl new file mode 100644 index 000000000..4924f8089 --- /dev/null +++ b/tests/codes/let.cl @@ -0,0 +1,13 @@ +--The type of an initialization expression must conform to the declared type of the identifier. + +class A { }; +class B inherits A { }; +class C inherits B { }; + +class Main inherits IO { + test: B <- let a: Bool, a: Int <- 5, a: String, b: C <- new C in b; + + main(): IO { out_string("Hello World!")}; + + get_test(): B { test }; +}; \ No newline at end of file diff --git a/tests/codes/loop.cl b/tests/codes/loop.cl new file mode 100644 index 000000000..3e3c53a5c --- /dev/null +++ b/tests/codes/loop.cl @@ -0,0 +1,8 @@ +--The predicate must have static type Bool. + +class Main inherits IO { + main(): IO { out_string("Hello World!")}; + + i: Int <- 1; + test: Object <- while true loop i <- i + 1 pool; +}; \ No newline at end of file From 67312d74c13de2b41a95efe428cb64760a84f87c Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Tue, 22 Feb 2022 15:00:35 -0500 Subject: [PATCH 012/161] Added more nodes to CIL formatter --- src/coolcmp/utils/cil_formatter.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/coolcmp/utils/cil_formatter.py b/src/coolcmp/utils/cil_formatter.py index b87fa81b9..6760ff228 100644 --- a/src/coolcmp/utils/cil_formatter.py +++ b/src/coolcmp/utils/cil_formatter.py @@ -126,3 +126,14 @@ def visit(self, node: cil.LoadNode): def visit(self, node: cil.PrintNode): return f'PRINT {node.addr}' + @visitor.when(cil.NegationNode) + def visit(self, node: cil.NegationNode): + return f'{node.dest} = NOT {node.src}' + + @visitor.when(cil.ComplementNode) + def visit(self, node: cil.ComplementNode): + return f'{node.dest} = COMPLEMENT {node.src}' + + @visitor.when(cil.CompareNode) + def visit(self, node: cil.CompareNode): + return f'{node.dest} = {node.left} == {node.right}' From 626f9aa31eba333e72c5c2706ec618b48c72ae9e Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Tue, 22 Feb 2022 15:02:25 -0500 Subject: [PATCH 013/161] Fixed bug with empty string in CIL --- src/coolcmp/codegen/cool2cil/types_data_visitor.py | 2 ++ src/coolcmp/semantics/builder.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coolcmp/codegen/cool2cil/types_data_visitor.py b/src/coolcmp/codegen/cool2cil/types_data_visitor.py index 945be7d5d..d0c4fc404 100644 --- a/src/coolcmp/codegen/cool2cil/types_data_visitor.py +++ b/src/coolcmp/codegen/cool2cil/types_data_visitor.py @@ -22,6 +22,8 @@ def visit(self, node: ast.Node): @visitor.when(ast.ProgramNode) def visit(self, node: ast.ProgramNode): + self.root.set_data('""') + for class_ in node.declarations: self.visit(class_) diff --git a/src/coolcmp/semantics/builder.py b/src/coolcmp/semantics/builder.py index 638e6a417..5d401ca17 100644 --- a/src/coolcmp/semantics/builder.py +++ b/src/coolcmp/semantics/builder.py @@ -90,7 +90,7 @@ def visit(self, node: AttrDeclarationNode): if attr_type == self.context.get_type('Int'): node.expr = IntegerNode('0') elif attr_type == self.context.get_type('String'): - node.expr = StringNode('') + node.expr = StringNode('""') elif attr_type == self.context.get_type('Bool'): node.expr = BooleanNode('false') else: From 2da7c853a2b4a0cf09904aec193ab896a35371dd Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Tue, 22 Feb 2022 18:15:16 -0500 Subject: [PATCH 014/161] fix: Initilize void --- src/coolcmp/codegen/cool2cil/code_visitor.py | 15 +++++---------- .../codegen/cool2cil/types_data_visitor.py | 10 +++++++--- src/coolcmp/semantics/builder.py | 10 ++++++++-- src/coolcmp/semantics/checker.py | 3 ++- src/coolcmp/semantics/collector.py | 13 ++++++------- src/coolcmp/utils/semantic.py | 9 ++++++--- 6 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/coolcmp/codegen/cool2cil/code_visitor.py b/src/coolcmp/codegen/cool2cil/code_visitor.py index 890e369e4..213101230 100644 --- a/src/coolcmp/codegen/cool2cil/code_visitor.py +++ b/src/coolcmp/codegen/cool2cil/code_visitor.py @@ -57,12 +57,10 @@ def visit(self, node: ast.ProgramNode, scope: Scope): for feature in class_.features: if isinstance(feature, ast.FuncDeclarationNode) and feature.id == 'main': self.add_function('entry') + void = self.add_local('void', internal=False) + self.add_inst(cil.AllocateNode('', void)) main_scope = deepcopy(scope.get_tagged_scope('Main')) - instance = self.add_local('instance') - self.add_inst(cil.AllocateNode('Main', instance)) - for attr in (f for f in class_.features if isinstance(f, ast.AttrDeclarationNode)): - expr_dest = self.visit(attr.expr, main_scope) - self.add_inst(cil.SetAttrNode(instance, f'Main_{attr.id}', expr_dest)) + instance = self.visit(ast.InstantiateNode('Main'), main_scope) result = self.add_local('result') self.add_inst(cil.ArgNode(instance)) self.add_inst(cil.DynamicCallNode('Main', 'Main_main', result)) @@ -119,7 +117,6 @@ def visit(self, node: ast.ProgramNode, scope: Scope): @visitor.when(ast.ClassDeclarationNode) def visit(self, node: ast.ClassDeclarationNode, scope: Scope): print('>>> class', node.id) - print(scope) self.current_type = node.id methods = (f for f in node.features if isinstance(f, ast.FuncDeclarationNode)) @@ -129,7 +126,6 @@ def visit(self, node: ast.ClassDeclarationNode, scope: Scope): @visitor.when(ast.FuncDeclarationNode) def visit(self, node: ast.FuncDeclarationNode, scope: Scope): print('>>> method', node.id) - print(scope) self.add_function() for local in scope.all_locals(): @@ -280,9 +276,8 @@ def visit(self, node: ast.StringNode, scope: Scope): type_node = self.root.get_type(node.lex) for attr in type_node.attributes: attr_expr = type_node.get_attr_node(attr) - if attr_expr is not None: - attr_dest = self.visit(attr_expr, scope) - self.add_inst(cil.SetAttrNode(instance, attr, attr_dest)) + attr_dest = self.visit(attr_expr, scope) + self.add_inst(cil.SetAttrNode(instance, attr, attr_dest)) return instance @visitor.when(ast.StringNode) diff --git a/src/coolcmp/codegen/cool2cil/types_data_visitor.py b/src/coolcmp/codegen/cool2cil/types_data_visitor.py index d0c4fc404..f6b93629f 100644 --- a/src/coolcmp/codegen/cool2cil/types_data_visitor.py +++ b/src/coolcmp/codegen/cool2cil/types_data_visitor.py @@ -27,6 +27,11 @@ def visit(self, node: ast.ProgramNode): for class_ in node.declarations: self.visit(class_) + # add default types + self.root.dot_types = [ + cil.TypeNode('', [], {}), + ] + self.types + return self.root @visitor.when(ast.ClassDeclarationNode) @@ -34,9 +39,11 @@ def visit(self, node: ast.ClassDeclarationNode): type_ = self.context.get_type(node.id) type_attributes: list[str] = [] type_methods: dict[str, str] = {} + type_node = cil.TypeNode(type_.name, type_attributes, type_methods) for attr, _ in type_.all_attributes(): type_attributes.append(f'{type_.name}_{attr.name}') + type_node.add_attr_node(f'{node.id}_{attr.name}', attr.node) for meth, owner in type_.all_methods(): if owner.name in ('Object', 'IO', 'String', ): @@ -45,12 +52,9 @@ def visit(self, node: ast.ClassDeclarationNode): func_target = f'f{self.next_function_id}' type_methods[f'{node.id}_{meth.name}'] = func_target - type_node = cil.TypeNode(type_.name, type_attributes, type_methods) self.types.append(type_node) for feature in node.features: - if isinstance(feature, ast.AttrDeclarationNode): - type_node.add_attr_node(f'{node.id}_{feature.id}', feature.expr) self.visit(feature) @visitor.when(ast.AttrDeclarationNode) diff --git a/src/coolcmp/semantics/builder.py b/src/coolcmp/semantics/builder.py index 5d401ca17..df51558d5 100644 --- a/src/coolcmp/semantics/builder.py +++ b/src/coolcmp/semantics/builder.py @@ -3,7 +3,7 @@ from coolcmp import errors as err from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode, FuncDeclarationNode, AttrDeclarationNode, \ IntegerNode, StringNode, BooleanNode, VariableNode -from coolcmp.utils.semantic import SemanticError, Context, ErrorType, Type +from coolcmp.utils.semantic import SemanticError, Context, ErrorType, Type, VoidType from coolcmp.utils import visitor @@ -26,6 +26,11 @@ def visit(self, node: ProgramNode): for declaration in node.declarations: self.visit(declaration) + # self.context.get_type('Object').define_attribute('void', VoidType) + # void_attr = AttrDeclarationNode('void', '', VariableNode('void')) + # object_class = ClassDeclarationNode('Object', [void_attr]) + # node.declarations.append(object_class) + @visitor.when(ClassDeclarationNode) def visit(self, node: ClassDeclarationNode): self.current_type = self.context.get_type(node.id) @@ -97,6 +102,7 @@ def visit(self, node: AttrDeclarationNode): node.expr = VariableNode('void') try: - self.current_type.define_attribute(node.id, attr_type, self.current_type.name) + self.current_type.define_attribute( + node.id, attr_type, node.expr or VariableNode('void'), self.current_type.name) except SemanticError: self.errors.append(err.ATTRIBUTE_ALREADY_DEFINED % (node.pos, node.id, self.current_type.name)) diff --git a/src/coolcmp/semantics/checker.py b/src/coolcmp/semantics/checker.py index 870580a7a..36fe9796b 100644 --- a/src/coolcmp/semantics/checker.py +++ b/src/coolcmp/semantics/checker.py @@ -2,7 +2,7 @@ from coolcmp import errors as err from coolcmp.utils import visitor -from coolcmp.utils.semantic import Context, Method, Type, SemanticError, ErrorType, Scope +from coolcmp.utils.semantic import Context, Method, Type, SemanticError, ErrorType, VoidType, Scope from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode, BlockNode, \ LetNode, CaseNode, AssignNode, ConditionalNode, WhileNode, CallNode, VariableNode, InstantiateNode, IntegerNode, \ StringNode, BooleanNode, PlusNode, MinusNode, StarNode, DivNode, LessThanNode, LessEqualNode, EqualNode, \ @@ -23,6 +23,7 @@ def visit(self, node, scope): @visitor.when(ProgramNode) def visit(self, node: ProgramNode, scope: Scope = None): scope = Scope('Object') + scope.define_variable('void', VoidType()) for attr in self.context.get_type('Object').attributes: scope.define_variable(attr.name, attr.type, is_attr=True) diff --git a/src/coolcmp/semantics/collector.py b/src/coolcmp/semantics/collector.py index 40201f04a..afcfa1432 100644 --- a/src/coolcmp/semantics/collector.py +++ b/src/coolcmp/semantics/collector.py @@ -1,8 +1,7 @@ from __future__ import annotations from coolcmp import errors as err -from coolcmp.utils import visitor -from coolcmp.utils.ast import ProgramNode, ClassDeclarationNode +from coolcmp.utils import visitor, ast from coolcmp.utils.semantic import Context, SemanticError, IntType, VoidType, ErrorType @@ -15,8 +14,8 @@ def __init__(self): def visit(self, node): pass - @visitor.when(ProgramNode) - def visit(self, node: ProgramNode): + @visitor.when(ast.ProgramNode) + def visit(self, node: ast.ProgramNode): self.context = Context() # Default types definition @@ -37,7 +36,7 @@ def visit(self, node: ProgramNode): bool_.set_parent(object_) # Default types attributes - object_.define_attribute('void', void) + object_.define_attribute('void', void, ast.VariableNode('void')) # Default types methods object_.define_method('abort', [], [], object_) @@ -56,8 +55,8 @@ def visit(self, node: ProgramNode): for declaration in node.declarations: self.visit(declaration) - @visitor.when(ClassDeclarationNode) - def visit(self, node: ClassDeclarationNode): + @visitor.when(ast.ClassDeclarationNode) + def visit(self, node: ast.ClassDeclarationNode): try: self.context.create_type(node.id) except SemanticError: diff --git a/src/coolcmp/utils/semantic.py b/src/coolcmp/utils/semantic.py index 60655fa01..f6703fa16 100644 --- a/src/coolcmp/utils/semantic.py +++ b/src/coolcmp/utils/semantic.py @@ -2,6 +2,8 @@ import itertools as itt from collections import OrderedDict +from . import ast + class SemanticError(Exception): @property @@ -10,9 +12,10 @@ def text(self) -> str: class Attribute: - def __init__(self, name: str, typex: Type): + def __init__(self, name: str, typex: Type, node: ast.ExpressionNode): self.name = name self.type = typex + self.node = node def __str__(self) -> str: return f'[attrib] {self.name} : {self.type.name};' @@ -63,11 +66,11 @@ def get_attribute(self, name: str, from_class: str = None) -> Attribute: except SemanticError: raise SemanticError(f'Attribute "{name}" is not defined in {self.name}.') - def define_attribute(self, name: str, typex: Type, from_class: str = None) -> Attribute: + def define_attribute(self, name: str, typex: Type, attr_node: ast.ExpressionNode, from_class: str = None) -> Attribute: try: self.get_attribute(name, from_class) except SemanticError: - attribute = Attribute(name, typex) + attribute = Attribute(name, typex, attr_node) self.attributes.append(attribute) return attribute else: From 69360c646aa995b4a8db1ed38106d8c1202430d9 Mon Sep 17 00:00:00 2001 From: samueldsr8 Date: Tue, 22 Feb 2022 18:49:04 -0500 Subject: [PATCH 015/161] feat: Add mips ast --- src/coolcmp/utils/mips.py | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/coolcmp/utils/mips.py diff --git a/src/coolcmp/utils/mips.py b/src/coolcmp/utils/mips.py new file mode 100644 index 000000000..b1613d728 --- /dev/null +++ b/src/coolcmp/utils/mips.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from coolcmp.utils import ast + + +class Node: + pass + + +class ProgramNode(Node): + def __init__(self, data, types, functions): + self.data = data + self.types = types + self.functions = functions + + +class FunctionNode(Node): + def __init__(self, name, params, local_vars): + self.name = name + self.params = params + self.local_vars = local_vars + self.instructions = [] + + def parameter_address(self, name): + """ + Get the + """ + + +class InstructionNode(Node): + pass + + +class DataNode(Node): + def __init__(self, label): + self.label = label + + +class StringNode(DataNode): + def __init__(self, label: str, value: str): + self.label = label + self.value = value + + +class SWNode(Node): + def __init__(self, reg, address): + self.reg = reg + self.address = address + + +class LINode(Node): + def __init__(self, reg, value): + self.reg = reg + self.value = value + + +class Type: + def __init__(self, label, address, attrs, methods, index, default=[]): + self.label = label + self.address = address + self.attrs = attrs + self.default_attrs = dict(default) + self.methods = methods + self.index = index + + def length(self): + return len(self.attrs) + + def __str__(self): + return f"{self.label}-{self.address}-{self.attrs}-{self.default_attrs}-{self.methods}-{self.index}" From d1e337168f6cfe1d7ccbdcc40a3195c6b4062d4a Mon Sep 17 00:00:00 2001 From: samueldsr8 Date: Tue, 22 Feb 2022 18:49:57 -0500 Subject: [PATCH 016/161] feat: Add registers utils --- src/coolcmp/utils/registers.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/coolcmp/utils/registers.py diff --git a/src/coolcmp/utils/registers.py b/src/coolcmp/utils/registers.py new file mode 100644 index 000000000..575c9c989 --- /dev/null +++ b/src/coolcmp/utils/registers.py @@ -0,0 +1,13 @@ +DW = 4 + + +class Register: + def __init__(self, name: str): + self.name = name + + def __str__(self): + return f"reg_{self.name}" + + +T = [Register(f"t{i}") for i in range(9)] +V0 = Register("v0") From ca53d0f1218a49d6481c666b53850510cbb8fb28 Mon Sep 17 00:00:00 2001 From: samueldsr8 Date: Tue, 22 Feb 2022 18:50:27 -0500 Subject: [PATCH 017/161] feat: Add mips visitor & formatter --- .../codegen/cil2mips/cil2mips_visitor.py | 177 ++++++++++++++++++ .../codegen/cil2mips/mips_formatter.py | 46 +++++ 2 files changed, 223 insertions(+) create mode 100644 src/coolcmp/codegen/cil2mips/cil2mips_visitor.py create mode 100644 src/coolcmp/codegen/cil2mips/mips_formatter.py diff --git a/src/coolcmp/codegen/cil2mips/cil2mips_visitor.py b/src/coolcmp/codegen/cil2mips/cil2mips_visitor.py new file mode 100644 index 000000000..f60d7cd1c --- /dev/null +++ b/src/coolcmp/codegen/cil2mips/cil2mips_visitor.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +from coolcmp.utils import cil, visitor +from coolcmp.utils import mips, registers + + +class LabelUUID: + """ + Class for generating unique labels + """ + + def __init__(self, name: str): + self.name = name + self.counter = 0 + + def next(self): + label = f"{self.name}_{self.counter}" + self.counter += 1 + return label + + +class CILToMipsVisitor: + def __init__(self): + self.data = {} + self.types = {} + self.functions = {} + self.cur_function = None + + self.function_names = {} + + self.data_label_gen = LabelUUID("data") + self.types_label_gen = LabelUUID("type") + self.code_label_gen = LabelUUID("code") + + self.labels = {} + + @visitor.on("node") + def visit(self, node): + pass + + @visitor.on("node") + def collect_function_names(self, node): + pass + + @visitor.when(cil.ProgramNode) + def collect_function_names(self, node: cil.ProgramNode): + print("function_name: ProgramNode") + for f in node.dot_code: + self.collect_function_names(f) + + @visitor.when(cil.FunctionNode) + def collect_function_names(self, node: cil.FunctionNode): + print("function_name: FunctionNode") + self.function_names[node.name] = "main" if node.name == "entry" else node.name + + @visitor.when(cil.ProgramNode) + def visit(self, node: cil.ProgramNode): + print("ProgramNode") + self.collect_function_names(node) + + for i in node.dot_types: + self.visit(i) + for i in node.dot_data: + self.visit(i) + for i in node.dot_code: + self.visit(i) + + return mips.ProgramNode( + list(self.data.values()), + list(self.types.values()), + list(self.functions.values()), + ) + + @visitor.when(cil.TypeNode) + def visit(self, node: cil.TypeNode): + print(f"TypeNode {node.name} {node.methods}") + new_label = self.data_label_gen.next() + self.data[node.name] = mips.StringNode(new_label, f'"{node.name}"') + + type_label = self.types_label_gen.next() + methods = [ + self.function_names[i] + for i in node.methods.values() + if self.function_names.get(i) + ] + + defaults = ( + [] + if node.name == "String" + else [("value", "default_str"), ("len", "type_4_proto")] + ) + print(methods, defaults) + + type_ = mips.Type( + type_label, + new_label, + list(node.attributes), + methods, + len(self.types), + defaults, + ) + + self.types[node.name] = type_ + + @visitor.when(cil.DataNode) + def visit(self, node: cil.DataNode): + print(f"DataNode {node.name} {node.value}") + data_label = self.data_label_gen.next() + self.data[node.name] = mips.StringNode(data_label, node.value) + + @visitor.when(cil.FunctionNode) + def visit(self, node: cil.FunctionNode): + print( + f"FunctionNode {node.name} {node.params} {node.local_vars} {node.instructions}" + ) + function_label = self.function_names[node.name] + params = [x.name for x in node.params] + local_vars = [x.name for x in node.local_vars] + + local_vars_size = len(local_vars) * registers.DW + + function_node = mips.FunctionNode(function_label, params, local_vars) + self.functions[node.name] = function_node + self.cur_function = function_node + self.labels = {} + + instructions = [f"# code for function {self.function_names[node.name]}..."] + + for instruction in node.instructions: + instructions.extend(self.visit(instruction)) + + function_node.instructions = instructions + self.cur_function = None + return instructions + + @visitor.when(cil.AllocateNode) + def visit(self, node: cil.AllocateNode): + print(f"AllocateNode {node.type} {node.dest}") + + # TODO: Finish this + print(self.types) + # type_ = node.type if isinstance(node.type, int) else self.types[node.type].index + + reg_t0 = registers.T[0] + reg_t1 = registers.T[1] + v0 = registers.V0 + + # li = mips.LINode(reg_t0, type_) + # sw = mips.SWNode( + # v0, + # ) + + instructions = ["# allocate instructions..."] + + return instructions + + @visitor.when(cil.ReturnNode) + def visit(self, node: cil.ReturnNode): + value = 0 if node.value is None else node.value + + return [mips.LINode(registers.V0, value)] + + @visitor.when(cil.PrintIntNode) + def visit(self, node: cil.PrintIntNode): + print(f"PrintIntNode {node.addr}") + + return ["# print int instructions"] + + @visitor.when(cil.PrintStringNode) + def visit(self, node: cil.PrintStringNode): + print(f"PrintStringNode {node.addr}") + return ["# print string instructions"] + + @visitor.when(cil.DynamicCallNode) + def visit(self, node: cil.DynamicCallNode): + print(f"DynamicCallNode {node.method} {node.type} {node.dest}") + return ["# dynamic call instructions"] diff --git a/src/coolcmp/codegen/cil2mips/mips_formatter.py b/src/coolcmp/codegen/cil2mips/mips_formatter.py new file mode 100644 index 000000000..5a0f36c44 --- /dev/null +++ b/src/coolcmp/codegen/cil2mips/mips_formatter.py @@ -0,0 +1,46 @@ +from coolcmp.utils import visitor +from coolcmp.utils import mips +from coolcmp.utils import registers + + +class MIPSFormatter: + def __init__(self): + pass + + @visitor.on("node") + def visit(self, node): + pass + + @visitor.when(mips.ProgramNode) + def visit(self, node: mips.ProgramNode): + data = "\n# data\n.data\n" + "\n".join(self.visit(d) for d in node.data) + functions = "\n# functions\n" + "\n".join(self.visit(f) for f in node.functions) + return data + "\n" + functions + + @visitor.when(mips.FunctionNode) + def visit(self, node: mips.FunctionNode): + return f"{node.name}:\n\t" + f"\n\t".join( + self.visit(ins) for ins in node.instructions + ) + + @visitor.when(str) + def visit(self, node: str): + return node + + @visitor.when(int) + def visit(self, node: int): + return node + + @visitor.when(registers.Register) + def visit(self, node: registers.Register): + return node.name + + @visitor.when(mips.StringNode) + def visit(self, node: mips.StringNode): + return f"{node.label}: .asciiz {node.value}" + + @visitor.when(mips.LINode) + def visit(self, node: mips.LINode): + register = self.visit(node.reg) + value = self.visit(node.value) + return f"li {register} {value}" From a002d5d7452e309b2b8f9887db3bc06b584bd072 Mon Sep 17 00:00:00 2001 From: samueldsr8 Date: Tue, 22 Feb 2022 18:50:40 -0500 Subject: [PATCH 018/161] expose `build_mips` method --- src/coolcmp/codegen/cil2mips/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/coolcmp/codegen/cil2mips/__init__.py diff --git a/src/coolcmp/codegen/cil2mips/__init__.py b/src/coolcmp/codegen/cil2mips/__init__.py new file mode 100644 index 000000000..cba961fa9 --- /dev/null +++ b/src/coolcmp/codegen/cil2mips/__init__.py @@ -0,0 +1,5 @@ +from .cil2mips_visitor import CILToMipsVisitor + + +def build_mips(ast, context, scope): + return CILToMipsVisitor().visit(ast) From febce30b9a58e98da80562debba8195a92836a9e Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Tue, 22 Feb 2022 23:36:19 -0500 Subject: [PATCH 019/161] fix: repeated in locals --- src/coolcmp/codegen/cool2cil/code_visitor.py | 24 ++++++-------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/coolcmp/codegen/cool2cil/code_visitor.py b/src/coolcmp/codegen/cool2cil/code_visitor.py index 213101230..efdfc7568 100644 --- a/src/coolcmp/codegen/cool2cil/code_visitor.py +++ b/src/coolcmp/codegen/cool2cil/code_visitor.py @@ -51,6 +51,12 @@ def visit(self, node: ast.Node, scope: Scope): @visitor.when(ast.ProgramNode) def visit(self, node: ast.ProgramNode, scope: Scope): + # the root scope stores void to avoid semantic errors initializing + # the void attribute of classes to void. After that every function + # has access to void through that attribute in every class. + # So, pop it to avoid repeated locals. + scope.locals.pop(0) + # build the entry function: for class_ in node.declarations: if class_.id == 'Main': @@ -74,19 +80,6 @@ def visit(self, node: ast.ProgramNode, scope: Scope): # add the default functions of COOL # TODO: add missing instructions self.code += [ - # cil.FunctionNode('abort', [], [], []), - # cil.FunctionNode('type_name', [], [], []), - # cil.FunctionNode('copy', [], [], []), - cil.FunctionNode( - name='get_void', - params=[], - local_vars=[], - instructions=[ - cil.LocalNode('void_inst'), - cil.AllocateNode('', 'void_inst'), - cil.ReturnNode('void_inst'), - ] - ), cil.FunctionNode( name='out_string', params=[ @@ -241,10 +234,7 @@ def visit(self, node: ast.WhileNode, scope: Scope): self.add_inst(cil.GotoNode(cond_label.name)) self.add_inst(end_while_label) - # return void - void_res = self.add_local('void_res') - self.add_inst(cil.StaticCallNode('get_void', void_res)) - return void_res + return 'void' @visitor.when(ast.CallNode) def visit(self, node: ast.CallNode, scope: Scope): From c63fa4b5c779f110e45b03b1b9f4603a02338c4a Mon Sep 17 00:00:00 2001 From: samueldsr8 Date: Wed, 23 Feb 2022 11:25:31 -0500 Subject: [PATCH 020/161] feat: Add some shitty code --- .../codegen/cil2mips/cil2mips_visitor.py | 55 ++++++++++++------- .../codegen/cil2mips/mips_formatter.py | 41 +++++++++++--- src/coolcmp/utils/cil.py | 26 +++++---- src/coolcmp/utils/mips.py | 52 +++++++++++++++--- src/coolcmp/utils/registers.py | 37 +++++++++++++ 5 files changed, 164 insertions(+), 47 deletions(-) diff --git a/src/coolcmp/codegen/cil2mips/cil2mips_visitor.py b/src/coolcmp/codegen/cil2mips/cil2mips_visitor.py index f60d7cd1c..d1bf9c735 100644 --- a/src/coolcmp/codegen/cil2mips/cil2mips_visitor.py +++ b/src/coolcmp/codegen/cil2mips/cil2mips_visitor.py @@ -44,13 +44,11 @@ def collect_function_names(self, node): @visitor.when(cil.ProgramNode) def collect_function_names(self, node: cil.ProgramNode): - print("function_name: ProgramNode") for f in node.dot_code: self.collect_function_names(f) @visitor.when(cil.FunctionNode) def collect_function_names(self, node: cil.FunctionNode): - print("function_name: FunctionNode") self.function_names[node.name] = "main" if node.name == "entry" else node.name @visitor.when(cil.ProgramNode) @@ -84,23 +82,17 @@ def visit(self, node: cil.TypeNode): if self.function_names.get(i) ] - defaults = ( - [] - if node.name == "String" - else [("value", "default_str"), ("len", "type_4_proto")] - ) - print(methods, defaults) - type_ = mips.Type( type_label, new_label, list(node.attributes), methods, len(self.types), - defaults, + [], ) self.types[node.name] = type_ + print("TYPES", [(i, str(j)) for i, j in self.types.items()]) @visitor.when(cil.DataNode) def visit(self, node: cil.DataNode): @@ -110,9 +102,7 @@ def visit(self, node: cil.DataNode): @visitor.when(cil.FunctionNode) def visit(self, node: cil.FunctionNode): - print( - f"FunctionNode {node.name} {node.params} {node.local_vars} {node.instructions}" - ) + print(f"FunctionNode {node.name}") function_label = self.function_names[node.name] params = [x.name for x in node.params] local_vars = [x.name for x in node.local_vars] @@ -124,13 +114,20 @@ def visit(self, node: cil.FunctionNode): self.cur_function = function_node self.labels = {} - instructions = [f"# code for function {self.function_names[node.name]}..."] + instructions = [f"# start of function {self.function_names[node.name]}"] + # Push local vars + instructions.extend(registers.push_register_instructions(registers.RA)) + instructions.extend(registers.push_register_instructions(registers.FP)) + instructions.append(mips.ADDINode(registers.FP, registers.SP, 4)) + instructions.append(mips.ADDINode(registers.SP, registers.SP, -local_vars_size)) for instruction in node.instructions: instructions.extend(self.visit(instruction)) function_node.instructions = instructions self.cur_function = None + + instructions.extend([f"# end of function {self.function_names[node.name]}"]) return instructions @visitor.when(cil.AllocateNode) @@ -138,14 +135,13 @@ def visit(self, node: cil.AllocateNode): print(f"AllocateNode {node.type} {node.dest}") # TODO: Finish this - print(self.types) - # type_ = node.type if isinstance(node.type, int) else self.types[node.type].index + type_ = node.type if isinstance(node.type, int) else self.types[node.type].index reg_t0 = registers.T[0] reg_t1 = registers.T[1] v0 = registers.V0 - # li = mips.LINode(reg_t0, type_) + li = mips.LINode(reg_t0, type_) # sw = mips.SWNode( # v0, # ) @@ -158,7 +154,17 @@ def visit(self, node: cil.AllocateNode): def visit(self, node: cil.ReturnNode): value = 0 if node.value is None else node.value - return [mips.LINode(registers.V0, value)] + if node.value is None: + value = 0 + elif isinstance(node.value, int): + value = node.value + else: + pass + # TODO: Handle returns + + jr = mips.JRNode() + + return [jr] @visitor.when(cil.PrintIntNode) def visit(self, node: cil.PrintIntNode): @@ -168,8 +174,17 @@ def visit(self, node: cil.PrintIntNode): @visitor.when(cil.PrintStringNode) def visit(self, node: cil.PrintStringNode): - print(f"PrintStringNode {node.addr}") - return ["# print string instructions"] + """ + li $v0, 4 + la $a0, str + syscall + """ + print("FUNCTIONS: ", self.data) + li = mips.LINode(registers.V0, 4) + la = mips.LANode(registers.A0, node.addr) + syscall = mips.SysCallNode() + + return [li, la, syscall] @visitor.when(cil.DynamicCallNode) def visit(self, node: cil.DynamicCallNode): diff --git a/src/coolcmp/codegen/cil2mips/mips_formatter.py b/src/coolcmp/codegen/cil2mips/mips_formatter.py index 5a0f36c44..5738c79d4 100644 --- a/src/coolcmp/codegen/cil2mips/mips_formatter.py +++ b/src/coolcmp/codegen/cil2mips/mips_formatter.py @@ -14,11 +14,14 @@ def visit(self, node): @visitor.when(mips.ProgramNode) def visit(self, node: mips.ProgramNode): data = "\n# data\n.data\n" + "\n".join(self.visit(d) for d in node.data) - functions = "\n# functions\n" + "\n".join(self.visit(f) for f in node.functions) + functions = "\n# functions\n.text\n" + "\n".join( + self.visit(f) for f in node.functions + ) return data + "\n" + functions @visitor.when(mips.FunctionNode) def visit(self, node: mips.FunctionNode): + print(node.name, node.instructions) return f"{node.name}:\n\t" + f"\n\t".join( self.visit(ins) for ins in node.instructions ) @@ -31,16 +34,38 @@ def visit(self, node: str): def visit(self, node: int): return node - @visitor.when(registers.Register) - def visit(self, node: registers.Register): - return node.name - @visitor.when(mips.StringNode) def visit(self, node: mips.StringNode): return f"{node.label}: .asciiz {node.value}" + @visitor.when(registers.Register) + def visit(self, node: registers.Register): + return f"${node.name}" + @visitor.when(mips.LINode) def visit(self, node: mips.LINode): - register = self.visit(node.reg) - value = self.visit(node.value) - return f"li {register} {value}" + return f"li {self.visit(node.reg)}, {node.value}" + + @visitor.when(mips.LANode) + def visit(self, node: mips.LANode): + return f"la {self.visit(node.reg)}, {node.value}" + + @visitor.when(mips.SysCallNode) + def visit(self, node: mips.SysCallNode): + return "syscall" + + @visitor.when(mips.JRNode) + def visit(self, node: mips.JRNode): + return "jr $ra" + + @visitor.when(mips.LWNode) + def visit(self, node: mips.LWNode): + return f"lw {self.visit(node.dest)}, {node.offset}({self.visit(node.src)})" + + @visitor.when(mips.SWNode) + def visit(self, node: mips.SWNode): + return f"sw {self.visit(node.dest)}, {node.offset}({self.visit(node.src)})" + + @visitor.when(mips.ADDINode) + def visit(self, node: mips.ADDINode): + return f"addi {self.visit(node.dest)}, {self.visit(node.src)}, {self.visit(node.isrc)}" diff --git a/src/coolcmp/utils/cil.py b/src/coolcmp/utils/cil.py index 78b5332f7..b2ab94d4a 100644 --- a/src/coolcmp/utils/cil.py +++ b/src/coolcmp/utils/cil.py @@ -8,10 +8,12 @@ class Node: class ProgramNode(Node): - def __init__(self, - dot_types: list[TypeNode], - dot_data: list[DataNode], - dot_code: list[FunctionNode]): + def __init__( + self, + dot_types: list[TypeNode], + dot_data: list[DataNode], + dot_code: list[FunctionNode], + ): self.dot_types = dot_types self.dot_data = dot_data self.dot_code = dot_code @@ -25,11 +27,11 @@ def get_data_name(self, value: str): for data in self.dot_data: if data.value == value: return data.name - raise ValueError(f'No data defined for value {value}') + raise ValueError(f"No data defined for value {value}") def set_data(self, value: str): if value not in [data.value for data in self.dot_data]: - self.dot_data.append(DataNode(f's{len(self.dot_data) + 1}', value)) + self.dot_data.append(DataNode(f"s{len(self.dot_data) + 1}", value)) class TypeNode(Node): @@ -55,11 +57,13 @@ def __init__(self, vname: str, value: str): class FunctionNode(Node): - def __init__(self, - name: str, - params: list[ParamNode], - local_vars: list[LocalNode], - instructions: list[InstructionNode]): + def __init__( + self, + name: str, + params: list[ParamNode], + local_vars: list[LocalNode], + instructions: list[InstructionNode], + ): self.name = name self.params = params self.local_vars = local_vars diff --git a/src/coolcmp/utils/mips.py b/src/coolcmp/utils/mips.py index b1613d728..b0f4ea742 100644 --- a/src/coolcmp/utils/mips.py +++ b/src/coolcmp/utils/mips.py @@ -21,11 +21,6 @@ def __init__(self, name, params, local_vars): self.local_vars = local_vars self.instructions = [] - def parameter_address(self, name): - """ - Get the - """ - class InstructionNode(Node): pass @@ -43,9 +38,17 @@ def __init__(self, label: str, value: str): class SWNode(Node): - def __init__(self, reg, address): - self.reg = reg - self.address = address + def __init__(self, dest: str, offset: int, src: str): + self.dest = dest + self.offset = offset + self.src = src + + +class LWNode(Node): + def __init__(self, dest: str, offset: int, src: str): + self.dest = dest + self.offset = offset + self.src = src class LINode(Node): @@ -54,6 +57,39 @@ def __init__(self, reg, value): self.value = value +class JALNode(Node): + def __init__(self, dest): + self.dest = dest + + +class LANode(Node): + def __init__(self, reg, value): + self.reg = reg + self.value = value + + +class ADDNode(Node): + def __init__(self, dest, src1, src2): + self.dest = dest + self.src1 = src1 + self.src2 = src2 + + +class ADDINode(Node): + def __init__(self, dest, src, isrc): + self.dest = dest + self.src = src + self.isrc = isrc + + +class JRNode(Node): + pass + + +class SysCallNode(Node): + pass + + class Type: def __init__(self, label, address, attrs, methods, index, default=[]): self.label = label diff --git a/src/coolcmp/utils/registers.py b/src/coolcmp/utils/registers.py index 575c9c989..c2584aa64 100644 --- a/src/coolcmp/utils/registers.py +++ b/src/coolcmp/utils/registers.py @@ -1,4 +1,5 @@ DW = 4 +from coolcmp.utils import mips class Register: @@ -10,4 +11,40 @@ def __str__(self): T = [Register(f"t{i}") for i in range(9)] +ARG = [Register(f"a{i}") for i in range(4)] +ZERO = Register("zero") +LOW = Register("low") V0 = Register("v0") +V1 = Register("v1") +A0 = Register("a0") +FP = Register("fp") +SP = Register("sp") +RA = Register("ra") + + +class RegisterLocation: + def __init__(self, name, offset): + self.name = name + self.offset = offset + + +def push_register_instructions(reg_name): + """ + addi $sp, $sp, -4 + sw , 0($sp) + """ + addi = mips.ADDINode(SP, SP, -DW) + sw = mips.SWNode(reg_name, 0, SP) + + return [addi, sw] + + +def pop_register_instructions(reg_name): + """ + lw , $sp, 0 + addi $sp, $sp, 4 + """ + lw = mips.LWNode(reg_name, 0, SP) + addi = mips.ADDINode(SP, SP, DW) + + return [lw, addi] From a53a475eb8296b533d7cd3971eac1739d0bacc8a Mon Sep 17 00:00:00 2001 From: samueldsr8 Date: Wed, 23 Feb 2022 11:59:11 -0500 Subject: [PATCH 021/161] feat: Append `mips` to `coolc.py` --- src/coolc.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/coolc.py b/src/coolc.py index d4e0d5818..bd479e54d 100644 --- a/src/coolc.py +++ b/src/coolc.py @@ -1,13 +1,18 @@ """ Main entry point of COOL compiler """ +from coolcmp.codegen.cil2mips.mips_formatter import MIPSFormatter from coolcmp.lexing_parsing.lexer import errors as lexer_errors from coolcmp.lexing_parsing.parser import parser, errors as parser_errors from coolcmp.semantics import check_semantics from coolcmp.codegen.cool2cil import build_cil + +from coolcmp.codegen.cil2mips import build_mips from coolcmp.utils.ast_formatter import ASTFormatter from coolcmp.utils.cil_formatter import CILFormatter +from coolcmp.utils.cil import ProgramNode + def main(cool_code: str, verbose: int): ast = parser.parse(cool_code) @@ -38,27 +43,35 @@ def main(cool_code: str, verbose: int): # print(scope) # print('-' * 40) + cil: ProgramNode cil = build_cil(ast, ctx, scope) if verbose > 1: cil_str = CILFormatter().visit(cil) print(cil_str) + mips = build_mips(cil, None, None) + + if verbose > 1: + mips_str = MIPSFormatter().visit(mips) + print("Mips code:") + print(mips_str) + -if __name__ == '__main__': +if __name__ == "__main__": import sys if len(sys.argv) < 2: - print('Usage: python3 coolc.py program.cl [-v+]') + print("Usage: python3 coolc.py program.cl [-v+]") exit(1) - elif not sys.argv[1].endswith('.cl'): - print('COOl source code files must end with .cl extension.') - print('Usage: python3 coolc.py program.cl') + elif not sys.argv[1].endswith(".cl"): + print("COOl source code files must end with .cl extension.") + print("Usage: python3 coolc.py program.cl") exit(1) - cool_program = open(sys.argv[1], encoding='utf8').read() + cool_program = open(sys.argv[1], encoding="utf8").read() try: - verb_count = sys.argv[2].count('v') + verb_count = sys.argv[2].count("v") except IndexError: verb_count = 0 main(cool_program, verb_count) From 24f98ad3c1d91b3639c9c9e9889eabe03e873a01 Mon Sep 17 00:00:00 2001 From: Enmanuel Verdesia Suarez Date: Wed, 23 Feb 2022 12:02:45 -0500 Subject: [PATCH 022/161] Added mips instructions pdf --- doc/MIPS_Instruction_Set.pdf | Bin 0 -> 149425 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/MIPS_Instruction_Set.pdf diff --git a/doc/MIPS_Instruction_Set.pdf b/doc/MIPS_Instruction_Set.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d34ae7c62ddb89c62c800966c5a4a9703b9434da GIT binary patch literal 149425 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