From b347e687b888667421eebd8529e2048f373bda63 Mon Sep 17 00:00:00 2001 From: Dragon of Shuu <68718280+DragonOfShuu@users.noreply.github.com> Date: Fri, 21 Feb 2025 23:58:50 -0700 Subject: [PATCH 1/4] Major Issues With Flipper Special Keys + Modifier Keys Fixed --- ducklingscript/compiler/commands/__init__.py | 28 +++++++++---------- .../compiler/commands/bases/base_command.py | 19 +++++++++++-- .../compiler/commands/bases/block_command.py | 11 ++++---- .../compiler/commands/bases/simple_command.py | 7 ++--- .../commands/flipper_modifier_keys.py | 22 +++++++++++++-- ...er_extended.py => flipper_special_keys.py} | 6 ++-- .../commands/quackinter_general_keys.py | 2 +- ducklingscript/compiler/commands/start.py | 14 ++++------ .../compiler/commands/while_loop.py | 12 -------- ducklingscript/compiler/compile_options.py | 2 +- ducklingscript/compiler/stack.py | 5 ++-- tests/custom_test.py | 9 ++++-- 12 files changed, 79 insertions(+), 58 deletions(-) rename ducklingscript/compiler/commands/{flipper_extended.py => flipper_special_keys.py} (66%) diff --git a/ducklingscript/compiler/commands/__init__.py b/ducklingscript/compiler/commands/__init__.py index 7eeb9a8..9ea2ed2 100644 --- a/ducklingscript/compiler/commands/__init__.py +++ b/ducklingscript/compiler/commands/__init__.py @@ -35,7 +35,7 @@ from .flipper_altcode import FlipperAltCode from .flipper_altstring import FlipperAltString from .flipper_default_string_delay import FlipperDefaultStringDelay -from .flipper_extended import FlipperExtended +from .flipper_special_keys import FlipperSpecialKeys from .flipper_hold_release import FlipperHoldRelease from .flipper_media import FlipperMedia from .flipper_modifier_keys import FlipperModifierKeys @@ -47,6 +47,19 @@ from .quackinter_println import QuackinterPrintln command_palette: list[type[BaseCommand]] = [ + QuackinterPrintln, + QuackinterGeneralKey, + FlipperAltChar, + FlipperAltCode, + FlipperAltString, + FlipperDefaultStringDelay, + FlipperSpecialKeys, + FlipperHoldRelease, + FlipperMedia, + FlipperModifierKeys, + FlipperStringDelay, + FlipperSysrq, + FlipperWaitForButtonPress, Alt, ArrowKeys, BreakLoop, @@ -75,17 +88,4 @@ Var, While, Whitespace, - FlipperAltChar, - FlipperAltCode, - FlipperAltString, - FlipperDefaultStringDelay, - FlipperExtended, - FlipperHoldRelease, - FlipperMedia, - FlipperModifierKeys, - FlipperStringDelay, - FlipperSysrq, - FlipperWaitForButtonPress, - QuackinterPrintln, - QuackinterGeneralKey, ] diff --git a/ducklingscript/compiler/commands/bases/base_command.py b/ducklingscript/compiler/commands/bases/base_command.py index d77bdcb..432cdd4 100644 --- a/ducklingscript/compiler/commands/bases/base_command.py +++ b/ducklingscript/compiler/commands/bases/base_command.py @@ -32,9 +32,22 @@ def __init__(self, env: Environment, stack: Any): self.env = env self.stack: Stack = stack - @classmethod def is_this_command( - cls, + self, + command_name: PreLine, + argument: str | None, + code_block: list[PreLine] | None, + stack: Any | None = None, + ) -> bool: + try: + self.check_validity() + except InvalidCommandError: + return False + + return self.run_is_this_command(command_name, argument, code_block, stack) + + def run_is_this_command( + self, command_name: PreLine, argument: str | None, code_block: list[PreLine] | None, @@ -46,7 +59,7 @@ def is_this_command( to override this command; instead, set the `names` variable for this class. """ - return False if not cls.names else (command_name.content_as_upper() in cls.names) + return False if not self.names else (command_name.content_as_upper() in self.names) def compile( self, diff --git a/ducklingscript/compiler/commands/bases/block_command.py b/ducklingscript/compiler/commands/bases/block_command.py index c6bd0f4..528e868 100644 --- a/ducklingscript/compiler/commands/bases/block_command.py +++ b/ducklingscript/compiler/commands/bases/block_command.py @@ -65,9 +65,8 @@ def token_arg(self) -> token_return_types: raise TypeError("Argument was tokenized before it was created.") return Tokenizer.tokenize(self.arg, self.stack, self.env) - @classmethod - def is_this_command( - cls, + def run_is_this_command( + self, command_name: PreLine, argument: str | None, code_block: list[PreLine] | None, @@ -84,10 +83,10 @@ def is_this_command( """ if ( command_name.content.startswith("$") - and command_name.content_as_upper()[1:] in cls.names - ) or (cls.code_block_required and not code_block): + and command_name.content_as_upper()[1:] in self.names + ) or (self.code_block_required and not code_block): return False - return super().is_this_command(command_name, argument, code_block) + return super().run_is_this_command(command_name, argument, code_block) def compile( self, diff --git a/ducklingscript/compiler/commands/bases/simple_command.py b/ducklingscript/compiler/commands/bases/simple_command.py index 6225c93..ceb311a 100644 --- a/ducklingscript/compiler/commands/bases/simple_command.py +++ b/ducklingscript/compiler/commands/bases/simple_command.py @@ -127,9 +127,8 @@ class SimpleCommand(BaseCommand): """ arg_type: type[token_return_types] | str = str - @classmethod - def is_this_command( - cls, + def run_is_this_command( + self, command_name: PreLine, argument: str | None, code_block: list[PreLine] | None, @@ -147,7 +146,7 @@ def is_this_command( command = command_name.content_as_upper() if command.startswith("$"): command = command[1:] - return super().is_this_command( + return super().run_is_this_command( PreLine(command, command_name.number, command_name.file_index), argument, code_block, diff --git a/ducklingscript/compiler/commands/flipper_modifier_keys.py b/ducklingscript/compiler/commands/flipper_modifier_keys.py index 5f81841..9049fe6 100644 --- a/ducklingscript/compiler/commands/flipper_modifier_keys.py +++ b/ducklingscript/compiler/commands/flipper_modifier_keys.py @@ -1,5 +1,11 @@ +from .flipper_special_keys import FlipperSpecialKeys from .bases import ArgLine, SimpleCommand +from .gui import Gui +from .alt import Alt +from .shift import Shift +from .ctrl import Ctrl + desc = """ As if the user was to press any of the given combinations. Accepts a single character as an argument. @@ -7,13 +13,25 @@ class FlipperModifierKeys(SimpleCommand): - names = ["CTRL-ALT", "CTRL-SHIFT", "ALT-SHIFT", "ALT-GUI", "GUI-SHIFT"] + names = ["CTRL-ALT", "CTRL-SHIFT", "ALT-SHIFT", "ALT-GUI", "GUI-SHIFT"] + Ctrl.names + Shift.names + Alt.names + Gui.names + parameters = FlipperSpecialKeys.names flipper_only = True description = desc + def format_arg(self, arg: ArgLine) -> ArgLine: + arg.update( + arg.content.upper() + if arg.content.upper() in self.parameters + else arg.content + ) + return arg + def verify_arg(self, arg: ArgLine) -> str | None: + if (arg.content.upper() in self.parameters): + return None + return ( None if len(arg.content) == 1 - else "This command only allows 1 character. No more no less." + else "This command only allows 1 character or special keys." ) diff --git a/ducklingscript/compiler/commands/flipper_extended.py b/ducklingscript/compiler/commands/flipper_special_keys.py similarity index 66% rename from ducklingscript/compiler/commands/flipper_extended.py rename to ducklingscript/compiler/commands/flipper_special_keys.py index 6284339..e7e93ee 100644 --- a/ducklingscript/compiler/commands/flipper_extended.py +++ b/ducklingscript/compiler/commands/flipper_special_keys.py @@ -1,3 +1,5 @@ +from .extended import Extended +from .arrow_keys import ArrowKeys from .bases.doc_command import ArgReqType from .bases.simple_command import SimpleCommand @@ -6,8 +8,8 @@ """ -class FlipperExtended(SimpleCommand): +class FlipperSpecialKeys(SimpleCommand): arg_req = ArgReqType.NOTALLOWED - names = ["BACKSPACE", "GLOBE", *[f"F{n+1}" for n in range(12)]] + names = ["BACKSPACE", "GLOBE", *[f"F{n+1}" for n in range(12)]] + Extended.names + ArrowKeys.names flipper_only = True description = desc diff --git a/ducklingscript/compiler/commands/quackinter_general_keys.py b/ducklingscript/compiler/commands/quackinter_general_keys.py index bc63fd3..d7e3a37 100644 --- a/ducklingscript/compiler/commands/quackinter_general_keys.py +++ b/ducklingscript/compiler/commands/quackinter_general_keys.py @@ -20,7 +20,7 @@ class QuackinterGeneralKey(SimpleCommand): names = [] @classmethod - def is_this_command( + def run_is_this_command( cls, command_name: PreLine, argument: str | None, diff --git a/ducklingscript/compiler/commands/start.py b/ducklingscript/compiler/commands/start.py index 6d0910d..a867648 100644 --- a/ducklingscript/compiler/commands/start.py +++ b/ducklingscript/compiler/commands/start.py @@ -2,7 +2,6 @@ from .bases.doc_command import ArgReqType from .bases.simple_command import ArgLine, SimpleCommand -from ducklingscript.compiler.environments.environment import Environment from ducklingscript.compiler.pre_line import PreLine from ducklingscript.compiler.compiled_ducky import CompiledDucky from ..errors import ( @@ -40,14 +39,6 @@ class Start(SimpleCommand): arg_req = ArgReqType.REQUIRED description = desc - def __init__(self, env: Environment, stack: Any): - if stack.file is None: - raise NotAValidCommandError( - stack, "The START command cannot be used outside of a file." - ) - - super().__init__(env, stack) - def convert_to_path(self, relative_path: str) -> Path: # Folder the stack is inside if self.stack.file is None: @@ -101,6 +92,11 @@ def verify_arg(self, arg: ArgLine) -> str | None: def run_compile(self, command_name: PreLine, arg: ArgLine) -> CompiledDucky | None: from ..compiler import DucklingCompiler + if self.stack.file is None: + raise NotAValidCommandError( + self.stack, "The START command cannot be used outside of a file." + ) + file_path = self.convert_to_path(arg.content) with file_path.open() as f: diff --git a/ducklingscript/compiler/commands/while_loop.py b/ducklingscript/compiler/commands/while_loop.py index 4a8f108..87b8266 100644 --- a/ducklingscript/compiler/commands/while_loop.py +++ b/ducklingscript/compiler/commands/while_loop.py @@ -1,5 +1,3 @@ -from typing import Any - from ducklingscript.compiler.pre_line import PreLine from ducklingscript.compiler.compiled_ducky import StackReturnType, CompiledDucky from .bases import BlockCommand, Example @@ -53,16 +51,6 @@ class While(BlockCommand): examples = example_list arg_type = " or ," - @classmethod - def is_this_command( - cls, - command_name: PreLine, - argument: str | None, - code_block: list[PreLine] | None, - stack: Any | None = None, - ) -> bool: - return super().is_this_command(command_name, argument, code_block, stack) - def parse_argument(self, argument: str): """ Convert the argument diff --git a/ducklingscript/compiler/compile_options.py b/ducklingscript/compiler/compile_options.py index cab24b7..3ef8998 100644 --- a/ducklingscript/compiler/compile_options.py +++ b/ducklingscript/compiler/compile_options.py @@ -20,7 +20,7 @@ class CompileOptions: only commands inside the compiler. """ - quackinter_commands: bool = True + quackinter_commands: bool = False """ Allow the use of quackinter commands inside the compiler. diff --git a/ducklingscript/compiler/stack.py b/ducklingscript/compiler/stack.py index f17b0ea..6a95a1d 100644 --- a/ducklingscript/compiler/stack.py +++ b/ducklingscript/compiler/stack.py @@ -143,8 +143,9 @@ def run(self) -> CompiledDucky: the_command: BaseCommand | None = None for i in command_palette: - if i.is_this_command(**new_command.asdict()): - the_command = i(self.env, self) + initted_command = i(self.env, self) + if initted_command.is_this_command(**new_command.asdict()): + the_command = initted_command break new_compiled: None | CompiledDucky = None diff --git a/tests/custom_test.py b/tests/custom_test.py index 0e2b18d..04358dd 100644 --- a/tests/custom_test.py +++ b/tests/custom_test.py @@ -8,6 +8,7 @@ from ducklingscript import DucklingInterpreter, Compiled, WarningsObject from ducklingscript.cli.components import CompileComponent +from ducklingscript.compiler.compile_options import CompileOptions CUSTOM_SCRIPT_FOLDER_NAME = "custom_test_scripts" @@ -19,6 +20,10 @@ def run_test(index: int = 1): delay=1000, output=lambda output, line: print(f"{line.line_num} -> {output}") ) + compile_options = CompileOptions( + quackinter_commands=False, + ) + ducky_code = CUSTOM_SCRIPT_LOCATION / f"custom{index}.dkls" if not ducky_code.exists: print("File index given does not exist.") @@ -46,11 +51,11 @@ def while_interpret( return progress.update(interpret_task, completed=line_count) - interpreter = DucklingInterpreter(quack_config=config) + interpreter = DucklingInterpreter(quack_config=config, compile_options=compile_options) interpreter.on_compilation_failure(lambda error: CompileComponent.get().compile_with_error(error)) interpreter.on_compilation_successful(compile_success) interpreter.on_fail_safe(lambda: print("[bold red]FAILSAFE TRIGGERED. Exiting...[/bold red]")) - interpreter.on_internal_error(lambda error: print(f"[bold red]INTERNAL ERROR: {error.__class__.__name__}:[/bold] {error}\n{''.join(traceback.TracebackException.from_exception(error).format())}")) + interpreter.on_internal_error(lambda error: print(f"[bold red]INTERNAL ERROR: {error.__class__.__name__}:[/bold red] {error}\n{''.join(traceback.TracebackException.from_exception(error).format())}")) interpreter.on_interpretation_failure(CompileComponent.get().interpret_error) interpreter.while_interpretation(while_interpret) From 9a7e206ac072a2d78a261b052e19d457cd562c57 Mon Sep 17 00:00:00 2001 From: Dragon of Shuu <68718280+DragonOfShuu@users.noreply.github.com> Date: Sat, 22 Feb 2025 02:05:40 -0700 Subject: [PATCH 2/4] Added Support For The Mouse --- ducklingscript/compiler/commands/__init__.py | 8 +++++ .../compiler/commands/flipper_device_id.py | 31 +++++++++++++++++++ .../commands/flipper_modifier_keys.py | 2 +- .../compiler/commands/flipper_mouse_click.py | 12 +++++++ .../compiler/commands/flipper_mouse_move.py | 22 +++++++++++++ .../compiler/commands/flipper_mouse_scroll.py | 13 ++++++++ .../compiler/commands/flipper_special_keys.py | 2 +- .../compile_tests/test_commands/test_mouse.py | 22 +++++++++++++ 8 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 ducklingscript/compiler/commands/flipper_device_id.py create mode 100644 ducklingscript/compiler/commands/flipper_mouse_click.py create mode 100644 ducklingscript/compiler/commands/flipper_mouse_move.py create mode 100644 ducklingscript/compiler/commands/flipper_mouse_scroll.py create mode 100644 tests/compile_tests/test_commands/test_mouse.py diff --git a/ducklingscript/compiler/commands/__init__.py b/ducklingscript/compiler/commands/__init__.py index 9ea2ed2..01d1fba 100644 --- a/ducklingscript/compiler/commands/__init__.py +++ b/ducklingscript/compiler/commands/__init__.py @@ -35,10 +35,14 @@ from .flipper_altcode import FlipperAltCode from .flipper_altstring import FlipperAltString from .flipper_default_string_delay import FlipperDefaultStringDelay +from .flipper_device_id import FlipperDeviceId from .flipper_special_keys import FlipperSpecialKeys from .flipper_hold_release import FlipperHoldRelease from .flipper_media import FlipperMedia from .flipper_modifier_keys import FlipperModifierKeys +from .flipper_mouse_click import FlipperMouseClick +from .flipper_mouse_move import FlipperMouseMove +from .flipper_mouse_scroll import FlipperMouseScroll from .flipper_string_delay import FlipperStringDelay from .flipper_sysrq import FlipperSysrq from .flipper_wait_for_button_press import FlipperWaitForButtonPress @@ -53,10 +57,14 @@ FlipperAltCode, FlipperAltString, FlipperDefaultStringDelay, + FlipperDeviceId, FlipperSpecialKeys, FlipperHoldRelease, FlipperMedia, FlipperModifierKeys, + FlipperMouseClick, + FlipperMouseMove, + FlipperMouseScroll, FlipperStringDelay, FlipperSysrq, FlipperWaitForButtonPress, diff --git a/ducklingscript/compiler/commands/flipper_device_id.py b/ducklingscript/compiler/commands/flipper_device_id.py new file mode 100644 index 0000000..47eb7be --- /dev/null +++ b/ducklingscript/compiler/commands/flipper_device_id.py @@ -0,0 +1,31 @@ + +from .bases.doc_command import ArgReqType +from ducklingscript.compiler.commands.bases.simple_command import ArgLine +from .bases.simple_command import SimpleCommand + +import re + +desc = """ +Set the device ID of the flipper +""" + +class FlipperDeviceId(SimpleCommand): + names = ["ID"] + flipper_only = True + description = desc + arg_req = ArgReqType.REQUIRED + + def verify_arg(self, arg: ArgLine) -> str | None: + content: str = arg.content + arg_parts: list[str] = content.split(' ', 1) + + first_matchable = re.match(r"^[\w\d]+:[\w\d]+$", arg_parts[0]) + if first_matchable is None: + return "For the first argument, you must do: VID:PID" + + if len(arg_parts) == 1: + return None + + second_matchable = re.match(r"^[\w\d\s]+:[\w\d\s]+$", arg_parts[1]) + if second_matchable is None: + return "The second argument is OPTIONAL, but must be: Manufacturer:Product" \ No newline at end of file diff --git a/ducklingscript/compiler/commands/flipper_modifier_keys.py b/ducklingscript/compiler/commands/flipper_modifier_keys.py index 9049fe6..7ba6739 100644 --- a/ducklingscript/compiler/commands/flipper_modifier_keys.py +++ b/ducklingscript/compiler/commands/flipper_modifier_keys.py @@ -13,7 +13,7 @@ class FlipperModifierKeys(SimpleCommand): - names = ["CTRL-ALT", "CTRL-SHIFT", "ALT-SHIFT", "ALT-GUI", "GUI-SHIFT"] + Ctrl.names + Shift.names + Alt.names + Gui.names + names = ["CTRL-ALT", "CTRL-SHIFT", "ALT-SHIFT", "ALT-GUI", "GUI-SHIFT", "GLOBE"] + Ctrl.names + Shift.names + Alt.names + Gui.names parameters = FlipperSpecialKeys.names flipper_only = True description = desc diff --git a/ducklingscript/compiler/commands/flipper_mouse_click.py b/ducklingscript/compiler/commands/flipper_mouse_click.py new file mode 100644 index 0000000..b15885d --- /dev/null +++ b/ducklingscript/compiler/commands/flipper_mouse_click.py @@ -0,0 +1,12 @@ +from .bases.simple_command import SimpleCommand +from .bases.doc_command import ArgReqType + +desc = """ +Click down on the mouse. +""" + +class FlipperMouseClick(SimpleCommand): + names = ["LEFTCLICK", "LEFT_CLICK", "RIGHTCLICK", "RIGHT_CLICK"] + description = desc + arg_req = ArgReqType.NOTALLOWED + flipper_only = True diff --git a/ducklingscript/compiler/commands/flipper_mouse_move.py b/ducklingscript/compiler/commands/flipper_mouse_move.py new file mode 100644 index 0000000..4b9f8fb --- /dev/null +++ b/ducklingscript/compiler/commands/flipper_mouse_move.py @@ -0,0 +1,22 @@ +from ducklingscript.compiler.commands.bases.simple_command import ArgLine +from .bases.doc_command import ArgReqType +from .bases.simple_command import SimpleCommand + +desc = """ +Move mouse in certain direction and amount +""" + +class FlipperMouseMove(SimpleCommand): + names = ["MOUSEMOVE", "MOUSE_MOVE"] + description = desc + flipper_only = True + arg_req = ArgReqType.REQUIRED + + def verify_arg(self, arg: ArgLine) -> str | None: + arg_parts: list[str] = arg.content.split(' ') + if len(arg_parts) != 2: + return "Exactly two arguments are required" + + if not all([i.isdigit() for i in arg_parts]): + return 'Both arguments must be an integer.' + \ No newline at end of file diff --git a/ducklingscript/compiler/commands/flipper_mouse_scroll.py b/ducklingscript/compiler/commands/flipper_mouse_scroll.py new file mode 100644 index 0000000..f56689e --- /dev/null +++ b/ducklingscript/compiler/commands/flipper_mouse_scroll.py @@ -0,0 +1,13 @@ +from .bases.doc_command import ArgReqType +from .bases.simple_command import SimpleCommand + +desc = """ +Scroll the mouse by the distance +""" + +class FlipperMouseScroll(SimpleCommand): + names = ["MOUSESCROLL", "MOUSE_SCROLL"] + description = desc + flipper_only = True + arg_req = ArgReqType.REQUIRED + arg_type = int \ No newline at end of file diff --git a/ducklingscript/compiler/commands/flipper_special_keys.py b/ducklingscript/compiler/commands/flipper_special_keys.py index e7e93ee..28a3bec 100644 --- a/ducklingscript/compiler/commands/flipper_special_keys.py +++ b/ducklingscript/compiler/commands/flipper_special_keys.py @@ -10,6 +10,6 @@ class FlipperSpecialKeys(SimpleCommand): arg_req = ArgReqType.NOTALLOWED - names = ["BACKSPACE", "GLOBE", *[f"F{n+1}" for n in range(12)]] + Extended.names + ArrowKeys.names + names = ["BACKSPACE", *[f"F{n+1}" for n in range(12)]] + Extended.names + ArrowKeys.names flipper_only = True description = desc diff --git a/tests/compile_tests/test_commands/test_mouse.py b/tests/compile_tests/test_commands/test_mouse.py new file mode 100644 index 0000000..bd5a022 --- /dev/null +++ b/tests/compile_tests/test_commands/test_mouse.py @@ -0,0 +1,22 @@ +from ducklingscript import DucklingCompiler +from pytest import raises + +from ducklingscript import InvalidArgumentsError + +def test_mouse_click(): + x = ["rightclick"] + answer = DucklingCompiler().compile(x, skip_indentation=True) + assert answer.output == ["RIGHTCLICK"] + +def test_mouse_move(): + x = ["MOUSEMOVE"] + with raises(InvalidArgumentsError): + DucklingCompiler().compile(x, skip_indentation=True) + + x = ["MOUSEMOVE f f"] + with raises(InvalidArgumentsError): + DucklingCompiler().compile(x, skip_indentation=True) + + x = ["MOUSEMOVE 4 4"] + compiled = DucklingCompiler().compile(x, skip_indentation=True) + assert compiled.output == ["MOUSEMOVE 4 4"] \ No newline at end of file From d1d197f82d4f3394c078569d87cd03896725536b Mon Sep 17 00:00:00 2001 From: Dragon of Shuu <68718280+DragonOfShuu@users.noreply.github.com> Date: Sat, 22 Feb 2025 02:07:14 -0700 Subject: [PATCH 3/4] Format + Checks --- .../cli/components/compile_component.py | 13 +++++----- .../compiler/commands/bases/base_command.py | 9 ++++--- .../compiler/commands/bases/block_command.py | 3 ++- .../compiler/commands/bases/doc_command.py | 1 + .../compiler/commands/bases/simple_command.py | 9 ++++--- .../compiler/commands/flipper_device_id.py | 10 ++++---- .../commands/flipper_modifier_keys.py | 12 ++++++--- .../compiler/commands/flipper_mouse_click.py | 1 + .../compiler/commands/flipper_mouse_move.py | 8 +++--- .../compiler/commands/flipper_mouse_scroll.py | 3 ++- .../compiler/commands/flipper_special_keys.py | 6 ++++- .../compiler/commands/flipper_string_delay.py | 1 + ducklingscript/compiler/compiled_ducky.py | 3 +-- ducklingscript/compiler/compiler.py | 5 ++-- .../compiler/environments/base_environment.py | 1 + .../compiler/environments/environment.py | 1 + .../environments/project_environment.py | 1 + .../environments/variable_environment.py | 1 + ducklingscript/compiler/pre_line.py | 5 +++- ducklingscript/compiler/tab_parse.py | 2 +- .../compiler/tokenization/tokenizer.py | 5 ++-- .../compile_tests/test_commands/test_mouse.py | 6 +++-- tests/custom_test.py | 25 +++++++++++++------ 23 files changed, 84 insertions(+), 47 deletions(-) diff --git a/ducklingscript/cli/components/compile_component.py b/ducklingscript/cli/components/compile_component.py index 73c1737..bb174dd 100644 --- a/ducklingscript/cli/components/compile_component.py +++ b/ducklingscript/cli/components/compile_component.py @@ -44,20 +44,17 @@ def compile_successful(self, compiled: Compiled): ) self.print_std_out(compiled) print("---") - + def interpret_error( self, error: QuackinterError, duckling_stacktrace: list[StackTraceNode] ): - stacktrace_error_str = "\n".join( - self.listify_stack_nodes(duckling_stacktrace) - ) + stacktrace_error_str = "\n".join(self.listify_stack_nodes(duckling_stacktrace)) print("---\n[bright_red bold] -> Stacktrace[/bright_red bold]") print(f"[red]{stacktrace_error_str}[/red]") print(f"[bold red]{type(error).__name__}:[/bold red] {error.args[0]}") print("---\n[bold bright_red]Run failed with an error.[/bold bright_red] ⛔") print("---") - def print_std_out(self, obj: Compiled | DucklingScriptError): if not isinstance(obj, (Compiled, CompilationError)): return @@ -98,8 +95,10 @@ def display_warnings(self, warnings: WarningsObject): print(f"[{text_col}]{stack_trace}[/{text_col}]") print(f"[{title_col}] -> Warning[/{title_col}]") print(f"[{text_col}]{warning.error}[/{text_col}]") - - def compile_success_with_warnings(self, warnings: WarningsObject, compiled: Compiled): + + def compile_success_with_warnings( + self, warnings: WarningsObject, compiled: Compiled + ): self.display_warnings(warnings) self.compile_successful(compiled) diff --git a/ducklingscript/compiler/commands/bases/base_command.py b/ducklingscript/compiler/commands/bases/base_command.py index 432cdd4..074afdd 100644 --- a/ducklingscript/compiler/commands/bases/base_command.py +++ b/ducklingscript/compiler/commands/bases/base_command.py @@ -16,6 +16,7 @@ class BaseCommand(DocCommand): The base for all command types in DucklingScript. """ + names: list[str] = [] """ Command names to match up with this command. @@ -38,12 +39,12 @@ def is_this_command( argument: str | None, code_block: list[PreLine] | None, stack: Any | None = None, - ) -> bool: + ) -> bool: try: self.check_validity() except InvalidCommandError: return False - + return self.run_is_this_command(command_name, argument, code_block, stack) def run_is_this_command( @@ -59,7 +60,9 @@ def run_is_this_command( to override this command; instead, set the `names` variable for this class. """ - return False if not self.names else (command_name.content_as_upper() in self.names) + return ( + False if not self.names else (command_name.content_as_upper() in self.names) + ) def compile( self, diff --git a/ducklingscript/compiler/commands/bases/block_command.py b/ducklingscript/compiler/commands/bases/block_command.py index 528e868..37cd58d 100644 --- a/ducklingscript/compiler/commands/bases/block_command.py +++ b/ducklingscript/compiler/commands/bases/block_command.py @@ -13,7 +13,7 @@ class BlockCommand(BaseCommand): """ Block Commands are commands that come with a block scope after the command. - + Block command example: ``` REPEAT 5 @@ -21,6 +21,7 @@ class BlockCommand(BaseCommand): STRINGLN goodbye world ``` """ + accept_new_lines = True """ diff --git a/ducklingscript/compiler/commands/bases/doc_command.py b/ducklingscript/compiler/commands/bases/doc_command.py index ca97785..e831285 100644 --- a/ducklingscript/compiler/commands/bases/doc_command.py +++ b/ducklingscript/compiler/commands/bases/doc_command.py @@ -67,6 +67,7 @@ class DocCommand(ABC): A class for making Documentation inside of commands. """ + description: str = "" """ Description of this command diff --git a/ducklingscript/compiler/commands/bases/simple_command.py b/ducklingscript/compiler/commands/bases/simple_command.py index ceb311a..7512d59 100644 --- a/ducklingscript/compiler/commands/bases/simple_command.py +++ b/ducklingscript/compiler/commands/bases/simple_command.py @@ -97,19 +97,20 @@ def __iter__(self) -> Iterator[ArgLine]: class SimpleCommand(BaseCommand): """ For commands that are simple. Simple commands - are all commands that don't have/require a - block scope. All Ducky Script 1.0 commands + are all commands that don't have/require a + block scope. All Ducky Script 1.0 commands are Simple Commands. - Simple Commands support $'s and tabbed + Simple Commands support $'s and tabbed new lines for repeating the command. Simple Command Example: ``` - STRINGLN + STRINGLN hello world ``` """ + arg_req: ArgReqType = ArgReqType.ALLOWED """ If the argument should be diff --git a/ducklingscript/compiler/commands/flipper_device_id.py b/ducklingscript/compiler/commands/flipper_device_id.py index 47eb7be..a71dab5 100644 --- a/ducklingscript/compiler/commands/flipper_device_id.py +++ b/ducklingscript/compiler/commands/flipper_device_id.py @@ -1,14 +1,14 @@ - from .bases.doc_command import ArgReqType from ducklingscript.compiler.commands.bases.simple_command import ArgLine from .bases.simple_command import SimpleCommand -import re +import re desc = """ Set the device ID of the flipper """ + class FlipperDeviceId(SimpleCommand): names = ["ID"] flipper_only = True @@ -17,8 +17,8 @@ class FlipperDeviceId(SimpleCommand): def verify_arg(self, arg: ArgLine) -> str | None: content: str = arg.content - arg_parts: list[str] = content.split(' ', 1) - + arg_parts: list[str] = content.split(" ", 1) + first_matchable = re.match(r"^[\w\d]+:[\w\d]+$", arg_parts[0]) if first_matchable is None: return "For the first argument, you must do: VID:PID" @@ -28,4 +28,4 @@ def verify_arg(self, arg: ArgLine) -> str | None: second_matchable = re.match(r"^[\w\d\s]+:[\w\d\s]+$", arg_parts[1]) if second_matchable is None: - return "The second argument is OPTIONAL, but must be: Manufacturer:Product" \ No newline at end of file + return "The second argument is OPTIONAL, but must be: Manufacturer:Product" diff --git a/ducklingscript/compiler/commands/flipper_modifier_keys.py b/ducklingscript/compiler/commands/flipper_modifier_keys.py index 7ba6739..f31f299 100644 --- a/ducklingscript/compiler/commands/flipper_modifier_keys.py +++ b/ducklingscript/compiler/commands/flipper_modifier_keys.py @@ -13,7 +13,13 @@ class FlipperModifierKeys(SimpleCommand): - names = ["CTRL-ALT", "CTRL-SHIFT", "ALT-SHIFT", "ALT-GUI", "GUI-SHIFT", "GLOBE"] + Ctrl.names + Shift.names + Alt.names + Gui.names + names = ( + ["CTRL-ALT", "CTRL-SHIFT", "ALT-SHIFT", "ALT-GUI", "GUI-SHIFT", "GLOBE"] + + Ctrl.names + + Shift.names + + Alt.names + + Gui.names + ) parameters = FlipperSpecialKeys.names flipper_only = True description = desc @@ -27,9 +33,9 @@ def format_arg(self, arg: ArgLine) -> ArgLine: return arg def verify_arg(self, arg: ArgLine) -> str | None: - if (arg.content.upper() in self.parameters): + if arg.content.upper() in self.parameters: return None - + return ( None if len(arg.content) == 1 diff --git a/ducklingscript/compiler/commands/flipper_mouse_click.py b/ducklingscript/compiler/commands/flipper_mouse_click.py index b15885d..0886438 100644 --- a/ducklingscript/compiler/commands/flipper_mouse_click.py +++ b/ducklingscript/compiler/commands/flipper_mouse_click.py @@ -5,6 +5,7 @@ Click down on the mouse. """ + class FlipperMouseClick(SimpleCommand): names = ["LEFTCLICK", "LEFT_CLICK", "RIGHTCLICK", "RIGHT_CLICK"] description = desc diff --git a/ducklingscript/compiler/commands/flipper_mouse_move.py b/ducklingscript/compiler/commands/flipper_mouse_move.py index 4b9f8fb..05da0f3 100644 --- a/ducklingscript/compiler/commands/flipper_mouse_move.py +++ b/ducklingscript/compiler/commands/flipper_mouse_move.py @@ -6,6 +6,7 @@ Move mouse in certain direction and amount """ + class FlipperMouseMove(SimpleCommand): names = ["MOUSEMOVE", "MOUSE_MOVE"] description = desc @@ -13,10 +14,9 @@ class FlipperMouseMove(SimpleCommand): arg_req = ArgReqType.REQUIRED def verify_arg(self, arg: ArgLine) -> str | None: - arg_parts: list[str] = arg.content.split(' ') + arg_parts: list[str] = arg.content.split(" ") if len(arg_parts) != 2: return "Exactly two arguments are required" - + if not all([i.isdigit() for i in arg_parts]): - return 'Both arguments must be an integer.' - \ No newline at end of file + return "Both arguments must be an integer." diff --git a/ducklingscript/compiler/commands/flipper_mouse_scroll.py b/ducklingscript/compiler/commands/flipper_mouse_scroll.py index f56689e..1f661a1 100644 --- a/ducklingscript/compiler/commands/flipper_mouse_scroll.py +++ b/ducklingscript/compiler/commands/flipper_mouse_scroll.py @@ -5,9 +5,10 @@ Scroll the mouse by the distance """ + class FlipperMouseScroll(SimpleCommand): names = ["MOUSESCROLL", "MOUSE_SCROLL"] description = desc flipper_only = True arg_req = ArgReqType.REQUIRED - arg_type = int \ No newline at end of file + arg_type = int diff --git a/ducklingscript/compiler/commands/flipper_special_keys.py b/ducklingscript/compiler/commands/flipper_special_keys.py index 28a3bec..afdfd19 100644 --- a/ducklingscript/compiler/commands/flipper_special_keys.py +++ b/ducklingscript/compiler/commands/flipper_special_keys.py @@ -10,6 +10,10 @@ class FlipperSpecialKeys(SimpleCommand): arg_req = ArgReqType.NOTALLOWED - names = ["BACKSPACE", *[f"F{n+1}" for n in range(12)]] + Extended.names + ArrowKeys.names + names = ( + ["BACKSPACE", *[f"F{n+1}" for n in range(12)]] + + Extended.names + + ArrowKeys.names + ) flipper_only = True description = desc diff --git a/ducklingscript/compiler/commands/flipper_string_delay.py b/ducklingscript/compiler/commands/flipper_string_delay.py index e683091..719f3f0 100644 --- a/ducklingscript/compiler/commands/flipper_string_delay.py +++ b/ducklingscript/compiler/commands/flipper_string_delay.py @@ -8,6 +8,7 @@ class FlipperStringDelay(SimpleCommand): for the line of code directly beneath this one. """ + names = ["STRINGDELAY", "STRING_DELAY"] flipper_only = True tokenize_args = True diff --git a/ducklingscript/compiler/compiled_ducky.py b/ducklingscript/compiler/compiled_ducky.py index 5efc075..47e4bf5 100644 --- a/ducklingscript/compiler/compiled_ducky.py +++ b/ducklingscript/compiler/compiled_ducky.py @@ -127,7 +127,7 @@ def add_lines(self, *lines: CompiledDuckyLine): def add_to_std(self, x: StdOutData): """ - Add the StdOutData to the + Add the StdOutData to the currently stored list. """ self.std_out.append(x) @@ -192,4 +192,3 @@ def __getitem__(self, index: int): def __len__(self): return self.data.__len__() - \ No newline at end of file diff --git a/ducklingscript/compiler/compiler.py b/ducklingscript/compiler/compiler.py index c6ca37b..1320793 100644 --- a/ducklingscript/compiler/compiler.py +++ b/ducklingscript/compiler/compiler.py @@ -33,6 +33,7 @@ class DucklingCompiler: >>> DucklingCompiler.compile("STRINGLN \\n\\thello\\n\\tworld") Compiled(output=['STRINGLN hello', 'STRINGLN world']...) """ + def __init__(self, options: CompileOptions | None = None): self.compile_options = options @@ -80,7 +81,7 @@ def compile( ): """ Compile the given text. - + >>> DucklingCompiler.compile("STRINGLN \\n\\thello\\n\\tworld") Compiled(output=['STRINGLN hello', 'STRINGLN world']...) """ @@ -123,7 +124,7 @@ def compile( @staticmethod def get_docs(command_name: str): """ - Get the documentation for a + Get the documentation for a command based on the name. """ command = DucklingCompiler.get_command(command_name) diff --git a/ducklingscript/compiler/environments/base_environment.py b/ducklingscript/compiler/environments/base_environment.py index 8d011ff..4d63952 100644 --- a/ducklingscript/compiler/environments/base_environment.py +++ b/ducklingscript/compiler/environments/base_environment.py @@ -6,6 +6,7 @@ class BaseEnvironment(ABC): """ A base class for resource managers. """ + @abstractmethod def append_env(self, x: BaseEnvironment): """ diff --git a/ducklingscript/compiler/environments/environment.py b/ducklingscript/compiler/environments/environment.py index decd798..b715db2 100644 --- a/ducklingscript/compiler/environments/environment.py +++ b/ducklingscript/compiler/environments/environment.py @@ -16,6 +16,7 @@ class Environment(BaseEnvironment): project env and the variable env. """ + def __init__( self, variable_env: VariableEnvironment | None = None, diff --git a/ducklingscript/compiler/environments/project_environment.py b/ducklingscript/compiler/environments/project_environment.py index f65a731..98423df 100644 --- a/ducklingscript/compiler/environments/project_environment.py +++ b/ducklingscript/compiler/environments/project_environment.py @@ -13,6 +13,7 @@ class ProjectEnvironment(BaseEnvironment): The environment for a project. Includes configuration data and file sources. """ + config_name = "config.yaml" def __init__( diff --git a/ducklingscript/compiler/environments/variable_environment.py b/ducklingscript/compiler/environments/variable_environment.py index e353a09..4f9e8d3 100644 --- a/ducklingscript/compiler/environments/variable_environment.py +++ b/ducklingscript/compiler/environments/variable_environment.py @@ -26,6 +26,7 @@ class VariableEnvironment(BaseEnvironment): An environment that stores variables. """ + acceptable_vars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_" def __init__( diff --git a/ducklingscript/compiler/pre_line.py b/ducklingscript/compiler/pre_line.py index d38ee60..cfebedd 100644 --- a/ducklingscript/compiler/pre_line.py +++ b/ducklingscript/compiler/pre_line.py @@ -10,6 +10,7 @@ """ DimensionalPreLine = list[Union["PreLine", "DimensionalPreLine"]] + class PreLine: """ Stores a line of code. @@ -37,7 +38,9 @@ def convert_to(lines: list[str], file_index: int) -> list[PreLine]: ] @staticmethod - def convert_to_recur(lines: DimensionalString, file_index: int, line_num_offset: int = 0) -> DimensionalPreLine: + def convert_to_recur( + lines: DimensionalString, file_index: int, line_num_offset: int = 0 + ) -> DimensionalPreLine: """ Recursively convert from a list of strings to a diff --git a/ducklingscript/compiler/tab_parse.py b/ducklingscript/compiler/tab_parse.py index be521e6..baec760 100644 --- a/ducklingscript/compiler/tab_parse.py +++ b/ducklingscript/compiler/tab_parse.py @@ -39,7 +39,7 @@ def parse_document( ) -> DimensionalPreLine: """ Converts a 1-dimensional list of PreLines - into a multidimensional list of PreLines, + into a multidimensional list of PreLines, determined by the amount of tabs at the beginning of each line. diff --git a/ducklingscript/compiler/tokenization/tokenizer.py b/ducklingscript/compiler/tokenization/tokenizer.py index 636986b..b9b2971 100644 --- a/ducklingscript/compiler/tokenization/tokenizer.py +++ b/ducklingscript/compiler/tokenization/tokenizer.py @@ -12,9 +12,10 @@ @dataclass class SolveData: """ - A stateful object containing the + A stateful object containing the tokenizers current state on parsing. """ + start_index: int = 0 index: int = 0 token: Token | None = None @@ -388,7 +389,7 @@ def tokenize_all( strings: list[str], stack: Any | None = None, env: Environment | None = None ) -> list[token_return_types]: """ - Just like `tokenize`, but + Just like `tokenize`, but instead tokenizes a list of strings. diff --git a/tests/compile_tests/test_commands/test_mouse.py b/tests/compile_tests/test_commands/test_mouse.py index bd5a022..029ff67 100644 --- a/tests/compile_tests/test_commands/test_mouse.py +++ b/tests/compile_tests/test_commands/test_mouse.py @@ -3,11 +3,13 @@ from ducklingscript import InvalidArgumentsError + def test_mouse_click(): x = ["rightclick"] answer = DucklingCompiler().compile(x, skip_indentation=True) assert answer.output == ["RIGHTCLICK"] + def test_mouse_move(): x = ["MOUSEMOVE"] with raises(InvalidArgumentsError): @@ -16,7 +18,7 @@ def test_mouse_move(): x = ["MOUSEMOVE f f"] with raises(InvalidArgumentsError): DucklingCompiler().compile(x, skip_indentation=True) - + x = ["MOUSEMOVE 4 4"] compiled = DucklingCompiler().compile(x, skip_indentation=True) - assert compiled.output == ["MOUSEMOVE 4 4"] \ No newline at end of file + assert compiled.output == ["MOUSEMOVE 4 4"] diff --git a/tests/custom_test.py b/tests/custom_test.py index 04358dd..4904007 100644 --- a/tests/custom_test.py +++ b/tests/custom_test.py @@ -28,7 +28,7 @@ def run_test(index: int = 1): if not ducky_code.exists: print("File index given does not exist.") return - + with Progress() as progress: compile_task = progress.add_task("Compile", start=False) interpret_task = progress.add_task("Interpret", False, visible=False) @@ -40,22 +40,32 @@ def compile_success(warnings: WarningsObject, compiled: Compiled): progress.update(interpret_task, visible=True, total=len(compiled.compiled)) progress.start_task(interpret_task) progress.print("Await 1 second...") - + def while_interpret( line_count: int, total_lines: int, stack: Any, line: Any, ): - if not interpret_task: + if not interpret_task: return progress.update(interpret_task, completed=line_count) - interpreter = DucklingInterpreter(quack_config=config, compile_options=compile_options) - interpreter.on_compilation_failure(lambda error: CompileComponent.get().compile_with_error(error)) + interpreter = DucklingInterpreter( + quack_config=config, compile_options=compile_options + ) + interpreter.on_compilation_failure( + lambda error: CompileComponent.get().compile_with_error(error) + ) interpreter.on_compilation_successful(compile_success) - interpreter.on_fail_safe(lambda: print("[bold red]FAILSAFE TRIGGERED. Exiting...[/bold red]")) - interpreter.on_internal_error(lambda error: print(f"[bold red]INTERNAL ERROR: {error.__class__.__name__}:[/bold red] {error}\n{''.join(traceback.TracebackException.from_exception(error).format())}")) + interpreter.on_fail_safe( + lambda: print("[bold red]FAILSAFE TRIGGERED. Exiting...[/bold red]") + ) + interpreter.on_internal_error( + lambda error: print( + f"[bold red]INTERNAL ERROR: {error.__class__.__name__}:[/bold red] {error}\n{''.join(traceback.TracebackException.from_exception(error).format())}" + ) + ) interpreter.on_interpretation_failure(CompileComponent.get().interpret_error) interpreter.while_interpretation(while_interpret) @@ -63,7 +73,6 @@ def while_interpret( def generate_tests(): - CUSTOM_SCRIPT_LOCATION.mkdir(exist_ok=True) custom1 = CUSTOM_SCRIPT_LOCATION / "custom1.dkls" custom1.write_text( From 7bcc9bd73dea10821ef0801314ba221e02624d1f Mon Sep 17 00:00:00 2001 From: Dragon of Shuu <68718280+DragonOfShuu@users.noreply.github.com> Date: Sat, 22 Feb 2025 13:09:23 -0700 Subject: [PATCH 4/4] 3.13 Support + Version Bump --- .github/workflows/main.yml | 2 +- ducklingscript/cli/compile.py | 10 +-- ducklingscript/cli/interpret.py | 7 +- ducklingscript/cli/utils/config.py | 8 ++- pyproject.toml | 6 +- tests/custom.py | 109 ++++++++++++++++++++++++++--- tests/custom_test.py | 105 --------------------------- 7 files changed, 117 insertions(+), 130 deletions(-) delete mode 100644 tests/custom_test.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 846cd69..b3c46cb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.11", "3.12"] + python-version: ["3.11", "3.12", "3.13"] poetry-version: ["1.1.15", "1.6.1"] os: [macos-latest, windows-latest] runs-on: ${{ matrix.os }} diff --git a/ducklingscript/cli/compile.py b/ducklingscript/cli/compile.py index 02ee160..1968a6f 100644 --- a/ducklingscript/cli/compile.py +++ b/ducklingscript/cli/compile.py @@ -45,18 +45,18 @@ def compile( typer.Option( help="The max amount of stacks allowed in your program", min=5, max=200 ), - ] = Configuration.config.stack_limit, + ] = Configuration.config().stack_limit, comments: Annotated[ bool, typer.Option(help="If comments should appear in the compiled file") - ] = Configuration.config.include_comments, + ] = Configuration.config().include_comments, sourcemap: Annotated[ bool, typer.Option(help="If we should make a sourcemap") - ] = Configuration.config.create_sourcemap, + ] = Configuration.config().create_sourcemap, ): """ Compile a file, and output it to the given location with the given name. """ - options = Configuration.config.to_dict() + options = Configuration.config().to_dict() options.update({"stack_limit": stack_limit}) options.update({"include_comments": comments}) options.update({"create_sourcemap": sourcemap}) @@ -73,7 +73,7 @@ def compile( ) as progress: progress.add_task(description="Compiling...", total=None) compiled = compile_component.prepare_and_compile( - filename, output, compile_options + filename, output, False, compile_options ) except DucklingScriptError as e: error = e diff --git a/ducklingscript/cli/interpret.py b/ducklingscript/cli/interpret.py index 135205c..f4a956e 100644 --- a/ducklingscript/cli/interpret.py +++ b/ducklingscript/cli/interpret.py @@ -42,7 +42,7 @@ def interpret( typer.Option( help="The max amount of stacks allowed in your program", min=5, max=200 ), - ] = Configuration.config.stack_limit, + ] = Configuration.config().stack_limit, delay: Annotated[ int, typer.Option(help="How long in milliseconds to wait before we run the script."), @@ -53,7 +53,7 @@ def interpret( """ with Progress() as progress: main_task = progress.add_task("Loading config...", False, delay) - config = Configuration.config.to_dict() + config = Configuration.config().to_dict() config["stack_limit"] = stack_limit config["create_sourcemap"] = False @@ -98,8 +98,9 @@ def on_fail_safe(): delay=delay, output=lambda output, line: print(f"-> {output}") ) + new_config = {**config, "quackinter_commands": True} interpreter = DucklingInterpreter( - compile_options=CompileOptions(**config), quack_config=quack_config + compile_options=CompileOptions(**new_config), quack_config=quack_config ) interpreter.on_compilation_successful(on_compilation_successful) interpreter.on_compilation_failure(on_compilation_failure) diff --git a/ducklingscript/cli/utils/config.py b/ducklingscript/cli/utils/config.py index 89db0a1..4b93e9e 100644 --- a/ducklingscript/cli/utils/config.py +++ b/ducklingscript/cli/utils/config.py @@ -21,11 +21,15 @@ def load(cls): with cls.config_file.open() as f: new_config = yaml.safe_load(f) + + if new_config is None: + cls.save() + return + cls._config = Config(**new_config) cls.save() @classmethod - @property def config(cls) -> Config: if cls._config is None: cls.load() @@ -42,7 +46,7 @@ def save(cls): cls.create_dir() with cls.config_file.open("w") as f: - yaml.dump(cls.config.to_dict(), f) + yaml.dump(cls.config().to_dict(), f) @classmethod def create_dir(cls): diff --git a/pyproject.toml b/pyproject.toml index 644ced3..471bac5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ducklingscript" -version = "0.2.0.1" +version = "0.2.1" description = "A transcompiler for converting DucklingScript code into Rubber Ducky Language 1.0" authors = ["Dragon of Shuu "] readme = "README.md" @@ -48,11 +48,11 @@ script = "ducklingscript.tasks:base64vlq_decode" args = [{name = "text", positional = true}] [tool.poe.tasks.test] -script = "tests.custom_test:run_test" +script = "tests.custom:run_test" args = [{name = "index", positional = true}] [tool.poe.tasks.gen_tests] -script = "tests.custom_test:generate_tests" +script = "tests.custom:generate_tests" [tool.ruff.lint] select = ["E4", "E7", "E9", "F", "N", "ANN0", "Q002"] diff --git a/tests/custom.py b/tests/custom.py index 608d2a1..5aa1990 100644 --- a/tests/custom.py +++ b/tests/custom.py @@ -1,18 +1,105 @@ -# from ducklingscript import Compiler -# from ducklingscript.cli.compiler.pre_line import PreLine -# import json as j +from pathlib import Path +from typing import Any +from rich import print +from rich.progress import Progress +import traceback -# parser = Compiler() +from quackinter.config import Config -# with open("tests/compile_tests/indent_test.txt") as f: -# x = parser._convert_to_list(PreLine.convert_to(f.read().split("\n"))) +from ducklingscript import DucklingInterpreter, Compiled, WarningsObject +from ducklingscript.cli.components import CompileComponent +from ducklingscript.compiler.compile_options import CompileOptions -# print(j.dumps(x, indent=2)) -from ducklingscript.compiler.tokenization import Tokenizer +CUSTOM_SCRIPT_FOLDER_NAME = "custom_test_scripts" +CUSTOM_SCRIPT_LOCATION = Path(__file__).parent / CUSTOM_SCRIPT_FOLDER_NAME -# stack = Stack([]) -# tokenizer = ExprTokenizer(stack, "2/2") +def run_test(index: int = 1): + config = Config( + delay=1000, output=lambda output, line: print(f"{line.line_num} -> {output}") + ) -assert Tokenizer.tokenize("5 < 2==1") == 80 + compile_options = CompileOptions( + quackinter_commands=True, + ) + + ducky_code = CUSTOM_SCRIPT_LOCATION / f"custom{index}.dkls" + if not ducky_code.exists: + print("File index given does not exist.") + return + + with Progress() as progress: + compile_task = progress.add_task("Compile", start=False) + interpret_task = progress.add_task("Interpret", False, visible=False) + + def compile_success(warnings: WarningsObject, compiled: Compiled): + CompileComponent.get().display_warnings(warnings) + progress.start_task(compile_task) + progress.update(compile_task, total=1, completed=1) + progress.update(interpret_task, visible=True, total=len(compiled.compiled)) + progress.start_task(interpret_task) + progress.print("Await 1 second...") + + def while_interpret( + line_count: int, + total_lines: int, + stack: Any, + line: Any, + ): + if not interpret_task: + return + progress.update(interpret_task, completed=line_count) + + interpreter = DucklingInterpreter( + quack_config=config, compile_options=compile_options + ) + interpreter.on_compilation_failure( + lambda error: CompileComponent.get().compile_with_error(error) + ) + interpreter.on_compilation_successful(compile_success) + interpreter.on_fail_safe( + lambda: print("[bold red]FAILSAFE TRIGGERED. Exiting...[/bold red]") + ) + interpreter.on_internal_error( + lambda error: print( + f"[bold red]INTERNAL ERROR: {error.__class__.__name__}:[/bold red] {error}\n{''.join(traceback.TracebackException.from_exception(error).format())}" + ) + ) + interpreter.on_interpretation_failure(CompileComponent.get().interpret_error) + interpreter.while_interpretation(while_interpret) + + interpreter.interpret_file(ducky_code) + + +def generate_tests(): + CUSTOM_SCRIPT_LOCATION.mkdir(exist_ok=True) + custom1 = CUSTOM_SCRIPT_LOCATION / "custom1.dkls" + custom1.write_text( + """ +FUNC powershell + WIN r + DELAY 500 + STRINGLN powershell + DELAY 1000 + +RUN powershell +STRINGLN notepad +DELAY 1000 +CTRL t +DELAY 500 +DEFAULTSTRINGDELAY 20 +STRINGLN + \"\"\" + ---> + DUCKLINGSCRIPT + ---> + + A programming language built for injecting + key strokes. + \"\"\" +""" + ) + print( + 'Generation complete! Run "poetry run poe test" to run! (only runs DuckyScript right now)' + ) diff --git a/tests/custom_test.py b/tests/custom_test.py deleted file mode 100644 index 4904007..0000000 --- a/tests/custom_test.py +++ /dev/null @@ -1,105 +0,0 @@ -from pathlib import Path -from typing import Any -from rich import print -from rich.progress import Progress -import traceback - -from quackinter.config import Config - -from ducklingscript import DucklingInterpreter, Compiled, WarningsObject -from ducklingscript.cli.components import CompileComponent -from ducklingscript.compiler.compile_options import CompileOptions - - -CUSTOM_SCRIPT_FOLDER_NAME = "custom_test_scripts" -CUSTOM_SCRIPT_LOCATION = Path(__file__).parent / CUSTOM_SCRIPT_FOLDER_NAME - - -def run_test(index: int = 1): - config = Config( - delay=1000, output=lambda output, line: print(f"{line.line_num} -> {output}") - ) - - compile_options = CompileOptions( - quackinter_commands=False, - ) - - ducky_code = CUSTOM_SCRIPT_LOCATION / f"custom{index}.dkls" - if not ducky_code.exists: - print("File index given does not exist.") - return - - with Progress() as progress: - compile_task = progress.add_task("Compile", start=False) - interpret_task = progress.add_task("Interpret", False, visible=False) - - def compile_success(warnings: WarningsObject, compiled: Compiled): - CompileComponent.get().display_warnings(warnings) - progress.start_task(compile_task) - progress.update(compile_task, total=1, completed=1) - progress.update(interpret_task, visible=True, total=len(compiled.compiled)) - progress.start_task(interpret_task) - progress.print("Await 1 second...") - - def while_interpret( - line_count: int, - total_lines: int, - stack: Any, - line: Any, - ): - if not interpret_task: - return - progress.update(interpret_task, completed=line_count) - - interpreter = DucklingInterpreter( - quack_config=config, compile_options=compile_options - ) - interpreter.on_compilation_failure( - lambda error: CompileComponent.get().compile_with_error(error) - ) - interpreter.on_compilation_successful(compile_success) - interpreter.on_fail_safe( - lambda: print("[bold red]FAILSAFE TRIGGERED. Exiting...[/bold red]") - ) - interpreter.on_internal_error( - lambda error: print( - f"[bold red]INTERNAL ERROR: {error.__class__.__name__}:[/bold red] {error}\n{''.join(traceback.TracebackException.from_exception(error).format())}" - ) - ) - interpreter.on_interpretation_failure(CompileComponent.get().interpret_error) - interpreter.while_interpretation(while_interpret) - - interpreter.interpret_file(ducky_code) - - -def generate_tests(): - CUSTOM_SCRIPT_LOCATION.mkdir(exist_ok=True) - custom1 = CUSTOM_SCRIPT_LOCATION / "custom1.dkls" - custom1.write_text( - """ -FUNC powershell - WIN r - DELAY 500 - STRINGLN powershell - DELAY 1000 - -RUN powershell -STRINGLN notepad -DELAY 1000 -CTRL t -DELAY 500 -DEFAULTSTRINGDELAY 20 -STRINGLN - \"\"\" - ---> - DUCKLINGSCRIPT - ---> - - A programming language built for injecting - key strokes. - \"\"\" -""" - ) - print( - 'Generation complete! Run "poetry run poe test" to run! (only runs DuckyScript right now)' - )