def verify_exception(code: str, error: str): """ Verifies that parsing the code results in the given error. """ with pytest.raises(ParserError) as e: parse_file(code, '') # Remove line and column information from the error using a regular expression. assert re.sub(':[0-9]+:[0-9]+: ', 'file:?:?: ', str(e.value)) == error.strip()
def test_parser_error(): # Unexpected EOF - missing 'end'. with pytest.raises(ParserError, match='Unexpected end-of-input.') as e: parse_file(code=""" func f(): const a = 5 """) assert str(e.value).endswith(""" const a = 5 ^""")
def test_func_arg_ret_splitting(): before = """\ func myfunc(a, b, c, foo, bar, variable_name_which_is_way_too_long_but_has_to_be_supported, g) -> (x, y, z, a_return_arg_which_is_also_waaaaaaay_too_long, w): ret end """ after = """\ func myfunc( a, b, c, foo, bar, variable_name_which_is_way_too_long_but_has_to_be_supported, g) -> ( x, y, z, a_return_arg_which_is_also_waaaaaaay_too_long, w): ret end """ assert parse_file(before).format(allowed_line_length=25) == after before = """\ func myfunc(a, b, c, foo, bar, variable_name_which_is_way_too_long_but_has_to_be_supported, g) -> (x, y, z): ret end """ after = """\ func myfunc( a, b, c, foo, bar, variable_name_which_is_way_too_long_but_has_to_be_supported, g) -> (x, y, z): ret end """ assert parse_file(before).format(allowed_line_length=25) == after before = """\ func myfunc(ab, cd, ef) -> (x, y, z, a_return_arg_which_is_also_waaaaaaay_too_long, w): ret end """ after = """\ func myfunc( ab, cd, ef) -> ( x, y, z, a_return_arg_which_is_also_waaaaaaay_too_long, w): ret end """ assert parse_file(before).format(allowed_line_length=25) == after
def main(): parser = argparse.ArgumentParser( description='A tool to automatically format Cairo code.') parser.add_argument('-v', '--version', action='version', version=f'%(prog)s {__version__}') parser.add_argument('files', metavar='file', type=str, nargs='+', help='File names') action = parser.add_mutually_exclusive_group(required=False) action.add_argument('-i', dest='inplace', action='store_true', help='Edit files inplace.') action.add_argument('-c', dest='check', action='store_true', help='Check files\' formats.') args = parser.parse_args() return_code = 0 for path in args.files: old_content = open(path).read() if path != '-' else sys.stdin.read() try: new_content = parse_file( old_content, filename='<input>' if path == '-' else path).format() except Exception as exc: print(exc, file=sys.stderr) return 2 if args.inplace: assert path != '-', 'Using "-i" together with "-" is not supported.' open(path, 'w').write(new_content) elif args.check: assert path != '-', 'Using "-c" together with "-" is not supported.' if old_content != new_content: print(f'File "{path}" is incorrectly formatted.', file=sys.stderr) return_code = 1 else: print(new_content, end='') return return_code
def test_file_format_comment_spaces(): before = """ # First line. #{spaces} # Second line.{spaces} #Fourth line. # Third line. [ap] = [ap] # inline comment. # First line. # Second line. # First line. # Second line. [ap] = [ap] #{spaces} """.format(spaces=' ') after = """\ # First line. # # Second line. # Fourth line. # Third line. [ap] = [ap] # inline comment. # First line. # Second line. # First line. # Second line. [ap] = [ap] # """ assert parse_file(before).format() == after
def test_with(): code = """\ with a , b as c,d : [ap] = [ap] end """ assert parse_file(code).format() == """\
def collect(self, curr_pkg_name: str, location: Optional[Location] = None): # Check for circular dependencies. if curr_pkg_name in self.curr_ancestors: raise UsingCycleError(self.curr_ancestors + [curr_pkg_name]) if curr_pkg_name in self.collected_data: # File already parsed. return try: code, filename = self.read_file(curr_pkg_name) except ModuleNotFoundException as e: raise ImportLoaderError(str(e), location=location) except Exception as e: raise ImportLoaderError( f"Could not load module '{curr_pkg_name}'.\nError: {e}", location=location) parsed_file: CairoFile = parse_file(code, filename=filename) # Get current file dependencies. collector = DirectDependenciesCollector() collector.get_using_pkgs_in_block(parsed_file.code_block) # Add current package to ancestors list before scanning its dependencies. self.curr_ancestors.append(curr_pkg_name) # Collect ASTs recursively. for pkg_name, location in collector.packages: self.collect(pkg_name, location=location) # Pop current package from ancestors list after scanning its dependencies. self.curr_ancestors.pop() self.collected_data[curr_pkg_name] = parsed_file
def test_if(): code = """\ if (a + 1) / b == [fp]: [ap] = [ap] end """ assert parse_file(code).format() == code code = """\ if (a + 1) / b != 5: [ap] = [ap] else: [ap] = [ap] end """ assert parse_file(code).format() == code
def test_parse_func(): before = """\ [ap] = 1; ap++ func fib(): [ap] = 2; ap++ ap += 3 ret end # Comment. call fib """ after = """\ [ap] = 1; ap++ func fib(): [ap] = 2; ap++ ap += 3 ret end # Comment. call fib """ assert parse_file(before).format() == after
def test_file_format_hints_indent(): before = """\ %{ hint1 hint2 %} [fp] = [fp] func f(): %{ if a: b %} [fp] = [fp] end """ after = """\ %{ hint1 hint2 %} [fp] = [fp] func f(): %{ if a: b %} [fp] = [fp] end """ assert parse_file(before).format() == after
def test_directives(): code = """\ [ap] = [ap] # Comment. %builtins ab cd ef # Comment. [fp] = [fp] """ assert parse_file(code).format() == code
def _extract_identifiers(code): """ Extracts the identifiers defined in the code block and returns them as strings. """ ast = parse_file(code) collector = IdentifierCollector() with collector.scoped(ScopedName(), parent=ast): collector.visit(ast.code_block) return [(str(name), identifier_definition.identifier_type) for name, identifier_definition in collector.identifiers.as_dict().items()]
def test_file_format(): before = """ ap+=[ fp ] [ap + -1] = [fp] * 3 const x=y + z member x:T.S let x= ap-y + z let y:a.b.c= x label : [ap] = [fp]; ap ++ tempvar x=y*z+w alloc_locals local z :T*=x assert x*z+x= y+y static_assert ap + (3 + 7 )+ ap ==fp return (1,[fp], [ap +3],) fibonacci (a = 3 , b=[fp +1]) [ap - 1] = [fp]# This is a comment. #This is another comment. label2: jmp rel 17 if [ap+3]!= 0 [fp] = [fp] * [fp]""" after = """\ ap += [fp] [ap + (-1)] = [fp] * 3 const x = y + z member x : T.S let x = ap - y + z let y : a.b.c = x label: [ap] = [fp]; ap++ tempvar x = y * z + w alloc_locals local z : T* = x assert x * z + x = y + y static_assert ap + (3 + 7) + ap == fp return (1, [fp], [ap + 3]) fibonacci(a=3, b=[fp + 1]) [ap - 1] = [fp] # This is a comment. # This is another comment. label2: jmp rel 17 if [ap + 3] != 0 [fp] = [fp] * [fp] """ assert parse_file(before).format() == after
def test_imports(): collector = IdentifierCollector() collector.identifiers.add_identifier(ScopedName.from_string('foo.bar'), ConstDefinition(value=0)) ast = parse_file(""" from foo import bar as bar0 """) with collector.scoped(ScopedName(), parent=ast): collector.visit(ast.code_block) assert collector.identifiers.get_scope(ScopedName()).identifiers == { 'bar0': AliasDefinition(destination=ScopedName.from_string('foo.bar')), }
def test_parse_struct(): before = """\ struct MyStruct: x = 5 y=3 end # Comment. """ after = """\ struct MyStruct: x = 5 y = 3 end # Comment. """ assert parse_file(before).format() == after
def test_parse_namespace(): before = """\ namespace MyNS: x = 5 y=3 end # Comment. """ after = """\ namespace MyNS: x = 5 y = 3 end # Comment. """ assert parse_file(before).format() == after
def test_shallow_tree_graph(): files = { 'root.file': """ from a import aa from b import bb """, 'a': '[ap] = 1', 'b': '[ap] = 2' } expected_res = {name: parse_file(code) for name, code in files.items()} assert collect_imports('root.file', read_file_from_dict(files)) == expected_res assert set(collect_imports('a', read_file_from_dict(files)).keys()) == {'a'}
def test_file_format_comments(): before = """ # First line. [ap] = [ap] # Separator comment. [ap] = [ap] [ap] = [ap] # Comment before label. label : [ap] = [ap] # Inline. #This is another # comment before label. label2:#Inline (label). [ap] = [ap] label3: [ap] = [ap] # End of file comment.""" after = """\ # First line. [ap] = [ap] # Separator comment. [ap] = [ap] [ap] = [ap] # Comment before label. label: [ap] = [ap] # Inline. # This is another # comment before label. label2: # Inline (label). [ap] = [ap] label3: [ap] = [ap] # End of file comment. """ assert parse_file(before).format() == after
def test_return_splitting(): before = """\ return (a, b, c, foo, bar, variable_name_which_is_way_too_long_but_has_to_be_supported, g) """ after = """\ return ( a, b, c, foo, bar, variable_name_which_is_way_too_long_but_has_to_be_supported, g) """ assert parse_file(before).format(allowed_line_length=20) == after
def test_func_arg_splitting(): before = """\ func myfunc(a, b, c, foo, bar, variable_name_which_is_way_too_long_but_has_to_be_supported, g): ret end """ after = """\ func myfunc( a, b, c, foo, bar, variable_name_which_is_way_too_long_but_has_to_be_supported, g): ret end """ assert parse_file(before).format(allowed_line_length=25) == after
def _extract_dependency_graph(codes: Dict[str, str]) -> DependencyGraphVisitor: """ Extracts the dependencies from the given codes (given as a map from a file name to its content). Returns the DependencyGraphVisitor instance. """ modules = [ CairoModule( cairo_file=parse_file(code), module_name=ScopedName.from_string(name), ) for name, code in codes.items()] identifier_collector = IdentifierCollector() for module in modules: identifier_collector.visit(module) dependency_graph_visitor = DependencyGraphVisitor(identifiers=identifier_collector.identifiers) for module in modules: dependency_graph_visitor.visit(module) return dependency_graph_visitor
def _extract_dependency_graph(codes: Dict[str, str]) -> Dict[str, Set[str]]: """ Extracts the dependencies from the given codes (given as a map from a file name to its content). Returns the dependencies as a map from scope name to a set of the identifiers it uses. """ modules = [ CairoModule( cairo_file=parse_file(code), module_name=ScopedName.from_string(name), ) for name, code in codes.items()] identifier_collector = IdentifierCollector() for module in modules: identifier_collector.visit(module) dependency_graph_visitor = DependencyGraphVisitor(identifiers=identifier_collector.identifiers) for module in modules: dependency_graph_visitor.visit(module) return { str(scope): set(map(str, deps)) for scope, deps in dependency_graph_visitor.visited_identifiers.items()}
def test_get_imports(): code = """ from a import b ap += [fp] + 2; ap++ from b.c.d.e import f as g my_label: call that from vim import ide # from vs.code import ide func foo(): from pytest import cairo_stack_test as ci end """ ast = parse_file(code) collector = DirectDependenciesCollector() collector.get_using_pkgs_in_block(ast.code_block) assert set([x for x, _ in collector.packages ]) == {'a', 'b.c.d.e', 'vim', 'pytest'}
def test_dag(): files = { 'root.file': """ from a import aa from b import bb """, 'a': """ from common.first import some1 from common.second import some2 """, 'b': """ from common.first import some1 from common.second import some2 """, 'common.first': '[ap] = 1', 'common.second': '[ap] = 2', } expected_res = {name: parse_file(code) for name, code in files.items()} assert collect_imports('root.file', read_file_from_dict(files)) == expected_res
def test_file_format_hint(): before = """\ label: %{ x = y "[ fp ]"#Python comment is not auto-formatted %}#Cairo Comment %{ %} # Empty hint. """ after = """\ label: %{ x = y "[ fp ]"#Python comment is not auto-formatted %} # Cairo Comment %{ %} # Empty hint. """ assert parse_file(before).format() == after
def _collect_struct_definitions(codes: Dict[str, str]) -> Dict[str, Set[str]]: """ Collects the struct related identifiers from the given codes (given as a map from a file name to its content). Return the collected identifiers as a dict. """ modules = [ CairoModule( cairo_file=parse_file(code), module_name=ScopedName.from_string(name), ) for name, code in codes.items()] identifier_collector = IdentifierCollector() for module in modules: identifier_collector.visit(module) struct_collector = StructCollector(identifiers=identifier_collector.identifiers) for module in modules: struct_collector.visit(module) return { str(name): identifier_definition for name, identifier_definition in struct_collector.identifiers.as_dict().items() if not isinstance(identifier_definition, FutureIdentifierDefinition)}
def test_long_path_grph(): files = {f'a{i}': f'from a{i+1} import b' for i in range(10)} files['a9'] = '[ap] = 0' expected_res = {name: parse_file(code) for name, code in files.items()} assert collect_imports('a0', read_file_from_dict(files)) == expected_res