diff --git a/langkit/templates/python_api/module_py.mako b/langkit/templates/python_api/module_py.mako index 3043d6cc6..56a711d05 100644 --- a/langkit/templates/python_api/module_py.mako +++ b/langkit/templates/python_api/module_py.mako @@ -1873,16 +1873,26 @@ class App(object): """ return "" - def __init__(self): + def __init__(self, args=None): self.parser = argparse.ArgumentParser(description=self.description) self.parser.add_argument('files', nargs='+', help='Files') self.add_arguments() - self.args = self.parser.parse_args() + + # Parse command line arguments + self.args = self.parser.parse_args(args) + self.ctx = AnalysisContext( 'utf-8', with_trivia=True, unit_provider=self.create_unit_provider() ) + # Parse files + self.units = {} + for file_name in self.args.files: + self.u = self.ctx.get_from_file(file_name) + self.units[file_name] = self.u + + def add_arguments(self): """ Hook for subclasses to add arguments to self.parser. Default @@ -1897,23 +1907,26 @@ class App(object): """ return None - def process_files(self): + def main(self): """ - Load units for all source files on the command-line in `self.units`. - Put the last one in `self.u`. + Default implementation for App.main: just iterates on every units and + call ``process_unit`` on it. """ - self.units = {} - for file_name in self.args.files: - self.u = self.ctx.get_from_file(file_name) - self.units[file_name] = self.u + for u in sorted(self.units.values(), key=lambda u: u.filename): + self.process_unit(u) + + def process_unit(self, unit): + """ + Abstract method that processes one unit. Needs to be subclassed by + implementors. + """ + raise NotImplementedError() @classmethod - def run(cls): + def run(cls, args=None): """ Instantiate and run this application. """ - instance = cls() - instance.process_files() - instance.main() + cls(args).main() ${exts.include_extension(ctx.ext('python_api/app_exts'))} diff --git a/langkit/templates/python_api/module_pyi.mako b/langkit/templates/python_api/module_pyi.mako index 7e3b66dc4..c506d3889 100644 --- a/langkit/templates/python_api/module_pyi.mako +++ b/langkit/templates/python_api/module_pyi.mako @@ -417,12 +417,12 @@ class App(object): @property def description(self) -> str: ... - def __init__(self) -> None: ... + def __init__(self, args: Opt[List[str]]) -> None: ... def add_arguments(self) -> None: ... def create_unit_provider(self) -> Opt[UnitProvider]: ... - def process_files(self) -> None: ... + def process_unit(self, unit: AnalysisUnit) -> None: ... @classmethod - def run(cls) -> None: ... + def run(cls, args: Opt[List[str]]=None) -> None: ... ${exts.include_extension(ctx.ext('python_api', 'mypy_app_exts'))} diff --git a/testsuite/tests/python_api/python_app/expected_concrete_syntax.lkt b/testsuite/tests/python_api/python_app/expected_concrete_syntax.lkt new file mode 100644 index 000000000..6a542a954 --- /dev/null +++ b/testsuite/tests/python_api/python_app/expected_concrete_syntax.lkt @@ -0,0 +1,12 @@ +import lexer_example +@with_lexer(foo_lexer) +grammar foo_grammar { + @main_rule main_rule <- Example("example") + +} + +@abstract class FooNode : Node { +} + +class Example : FooNode implements TokenNode { +} diff --git a/testsuite/tests/python_api/python_app/input1 b/testsuite/tests/python_api/python_app/input1 new file mode 100644 index 000000000..7e0df6c35 --- /dev/null +++ b/testsuite/tests/python_api/python_app/input1 @@ -0,0 +1 @@ +example hello1 diff --git a/testsuite/tests/python_api/python_app/input2 b/testsuite/tests/python_api/python_app/input2 new file mode 100644 index 000000000..c48ed16fb --- /dev/null +++ b/testsuite/tests/python_api/python_app/input2 @@ -0,0 +1 @@ +example hello2 diff --git a/testsuite/tests/python_api/python_app/input3 b/testsuite/tests/python_api/python_app/input3 new file mode 100644 index 000000000..42896c274 --- /dev/null +++ b/testsuite/tests/python_api/python_app/input3 @@ -0,0 +1 @@ +example hello3 diff --git a/testsuite/tests/python_api/python_app/main.py b/testsuite/tests/python_api/python_app/main.py new file mode 100644 index 000000000..2316408ec --- /dev/null +++ b/testsuite/tests/python_api/python_app/main.py @@ -0,0 +1,10 @@ +import libfoolang as lfl + + +class App(lfl.App): + def process_unit(self, unit): + unit.root.dump() + + +if __name__ == '__main__': + App.run(['input3', 'input1', 'input2']) diff --git a/testsuite/tests/python_api/python_app/test.out b/testsuite/tests/python_api/python_app/test.out new file mode 100644 index 000000000..b7be86591 --- /dev/null +++ b/testsuite/tests/python_api/python_app/test.out @@ -0,0 +1,4 @@ +Example input1:1:1-1:8: example +Example input2:1:1-1:8: example +Example input3:1:1-1:8: example +Done diff --git a/testsuite/tests/python_api/python_app/test.py b/testsuite/tests/python_api/python_app/test.py new file mode 100644 index 000000000..00bef6f86 --- /dev/null +++ b/testsuite/tests/python_api/python_app/test.py @@ -0,0 +1,20 @@ +""" +Test python App class. +""" + +from langkit.dsl import ASTNode + +from utils import build_and_run + + +class FooNode(ASTNode): + pass + + +class Example(FooNode): + token_node = True + + +build_and_run(lkt_file='expected_concrete_syntax.lkt', py_script='main.py', + types_from_lkt=True) +print('Done') diff --git a/testsuite/tests/python_api/python_app/test.yaml b/testsuite/tests/python_api/python_app/test.yaml new file mode 100644 index 000000000..30423a038 --- /dev/null +++ b/testsuite/tests/python_api/python_app/test.yaml @@ -0,0 +1 @@ +driver: python