diff --git a/CHANGELOG.md b/CHANGELOG.md index c6804dd..f334377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,70 @@ +### [1.4.2](https://github.com/sheikhartin/farr/releases/tag/1.4.2) + +Moving the algorithms library to the [examples](examples) folder. + +### [1.4.1](https://github.com/sheikhartin/farr/releases/tag/1.4.1) + +A hotfix for interpreting arguments before creating a new environment! + +### [1.4.0](https://github.com/sheikhartin/farr/releases/tag/1.4.0) + +Enhancements and new features in invocation and operations: + +- Added left (`<<`) and right (`>>`) shift operators for binary manipulation. +- Support for binary, octal, and hexadecimal literals. +- Enforced keyword-only arguments for optional parameters to prevent reassignment through positional arguments. +- Enhanced function, struct, and method invocation error handling with improved messages. + +### [1.3.1](https://github.com/sheikhartin/farr/releases/tag/1.3.1) + +Adding the `functools` module to our native libraries with these useful functions: + +- `all` +- `any` +- `map` +- `partial` + +### [1.3.0](https://github.com/sheikhartin/farr/releases/tag/1.3.0) + +Smarter prefix and postfix operations; and a huge improvement in tests... + +Because the use of terms has been reduced to two, now parentheses must be used to separate expressions! For a better understanding, look at the parse trees taken from the execution of code `^ 5 2 == 25;` in the previous version and then the current version: + +```diff +- ModuleNode(body=[RelationalOperationNode(row=2, +- column=7, +- operator='EqualEqual', +- left=ArithmeticOperationNode(row=2, +- column=1, +- operator='Power', +- left=IntegerNode(row=2, +- column=3, +- value='5'), +- right=IntegerNode(row=2, ++ ModuleNode(body=[ArithmeticOperationNode(row=2, ++ column=1, ++ operator='Power', ++ left=IntegerNode(row=2, ++ column=3, ++ value='5'), ++ right=RelationalOperationNode(row=2, ++ column=7, ++ operator='EqualEqual', ++ left=IntegerNode(row=2, +- value='2')), +- right=IntegerNode(row=2, +- column=10, +- value='25'))]) ++ value='2'), ++ right=IntegerNode(row=2, ++ column=10, ++ value='25')))]) +``` + +### [1.2.0](https://github.com/sheikhartin/farr/releases/tag/1.2.0) + +Providing the ability to use all types of assignments even in chained form without any intermediate method... + ### [1.1.0](https://github.com/sheikhartin/farr/releases/tag/1.1.0) For better debugging, we will use a [linter](https://github.com/astral-sh/ruff). @@ -25,22 +92,22 @@ Updating our `libs` folder structure... │ └── funda.farr - ├── datetime - │ └── funda.farr -+ datetime.farr ++ ├── datetime.farr - ├── fs - │ └── funda.farr -+ fs.farr ++ ├── fs.farr - ├── logging - │ └── funda.farr -+ logging.farr ++ ├── logging.farr ├── math │ ├── funda.farr │ └── random.farr - ├── os - │ └── funda.farr -+ os.farr ++ ├── os.farr - └── platform - └── funda.farr -+ platform.farr ++ └── platform.farr ``` ### [0.2.0](https://github.com/sheikhartin/farr/releases/tag/0.2.0) diff --git a/README.md b/README.md index 7fe166a..a88b63c 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,11 @@ if rng_start >= rng_end = { } for let i in [rng_start..rng_end] = { - if % i 15 == 0 = { + if ((% i 15) == 0) = { println("...Fizzbuzz"); - } else if % i 3 == 0 = { + } else if ((% i 3) == 0) = { println("...Fizz"); - } else if % i 5 == 0 = { + } else if ((% i 5) == 0) = { println("...Buzz"); } else = { println("${i}"); // Or just pass `i` as an argument! diff --git a/examples/binary_search/sol01.farr b/examples/binary_search/sol01.farr new file mode 100644 index 0000000..ace2148 --- /dev/null +++ b/examples/binary_search/sol01.farr @@ -0,0 +1,37 @@ +/** + * Binary search efficiently locates an item in a sorted sequence by + * repeatedly dividing the search interval in half. If the target is less than + * the item in the middle, the search continues in the lower half, otherwise + * in the upper half, until the item is found or the search space is empty. + */ + +use math/random; + +fn binary_search(let list, let target) = { + let left = 1; + let right = list.length; + + while left <= right = { + let mid = (+ / - right left 2 left).toint(); + let result = list.[mid]; + + if result < target = { + left = + mid 1; + } else if result > target = { + right = - mid 1; + } else = { + return! mid; + } + } + + return! -1; +} + +let nums = random.randint(0, 50, size=10); +let goal = ( + readln!("What is your goal number? ") + .toint() +); + +println("Generated numbers: ${nums}"); +println("Does it exist? ${binary_search(nums, goal)}"); diff --git a/examples/bubble_sort/sol01.farr b/examples/bubble_sort/sol01.farr new file mode 100644 index 0000000..7056f8b --- /dev/null +++ b/examples/bubble_sort/sol01.farr @@ -0,0 +1,26 @@ +/** + * Bubble sort is a simple sorting algorithm that repeatedly steps through + * the list, compares adjacent elements and swaps them if they are in + * the wrong order. This process is repeated until the list is sorted. + */ + +use math/random; + +fn bubble_sort(let list) = { + let length = list.length; + for let i in [1..length] = { + for let j in [1..- length i] = { + if list.[j] > list.[+ j 1] = { + let tmp = list.[j]; + list.[j] = list.[+ j 1]; + list.[+ j 1] = tmp; + } + } + } + return! list; +} + +let nums = random.randint(0, 50, size=10); + +println("Before sorting: ${nums}"); +println("After sorting: ${bubble_sort(nums)}"); diff --git a/examples/fizzbuzz/sol01.farr b/examples/fizzbuzz/sol01.farr index 819b324..f92a56c 100644 --- a/examples/fizzbuzz/sol01.farr +++ b/examples/fizzbuzz/sol01.farr @@ -16,11 +16,11 @@ if rng_start >= rng_end = { } for let i in [rng_start..rng_end] = { - if % i 15 == 0 = { + if ((% i 15) == 0) = { println("...Fizzbuzz"); - } else if % i 3 == 0 = { + } else if ((% i 3) == 0) = { println("...Fizz"); - } else if % i 5 == 0 = { + } else if ((% i 5) == 0) = { println("...Buzz"); } else = { println("${i}"); // Or just pass `i` as an argument! diff --git a/examples/rock_paper_scissors/sol01.farr b/examples/rock_paper_scissors/sol01.farr index 5ea301f..dfa17b5 100644 --- a/examples/rock_paper_scissors/sol01.farr +++ b/examples/rock_paper_scissors/sol01.farr @@ -23,9 +23,9 @@ for let i in [1..rounds] = { if user_move == computer_move = { println("It's a tie!"); } else if ( - (user_move == "r" && computer_move == "s") || - (user_move == "p" && computer_move == "r") || - (user_move == "s" && computer_move == "p") + ((user_move == "r") && (computer_move == "s")) || + ((user_move == "p") && (computer_move == "r")) || + ((user_move == "s") && (computer_move == "p")) ) = { println("Unfortunately, you won!"); } else = { diff --git a/farr/__init__.py b/farr/__init__.py index 87690e3..c66f395 100644 --- a/farr/__init__.py +++ b/farr/__init__.py @@ -5,4 +5,4 @@ __author__ = 'Artin Mohammadi ' __license__ = 'MIT' -__version__ = '1.1.0' +__version__ = '1.4.2' diff --git a/farr/interpreter/__init__.py b/farr/interpreter/__init__.py index dc65c9f..f905f19 100644 --- a/farr/interpreter/__init__.py +++ b/farr/interpreter/__init__.py @@ -29,6 +29,9 @@ BlockNode, PassNode, NullNode, + BinaryNode, + OctalNode, + HexadecimalNode, IntegerNode, FloatNode, StringNode, @@ -39,6 +42,7 @@ ListNode, HashMapNode, PairNode, + ExpandableArgumentNode, CallNode, GroupedExpressionNode, NegationOperationNode, @@ -52,7 +56,10 @@ TernaryOperationNode, UseNode, VariableDeclarationNode, + VariadicParameterDeclarationNode, AssignmentNode, + LeftShiftAssignmentNode, + RightShiftAssignmentNode, AddAssignmentNode, SubtractAssignmentNode, MultiplyAssignmentNode, @@ -171,6 +178,21 @@ def _interpret_null_node(self, node: NullNode) -> NullObject: """Returns a `NullObject`.""" return NullObject() + def _interpret_binary_node(self, node: BinaryNode) -> IntegerObject: + """Converts a `BinaryNode` to a `IntegerObject`.""" + return IntegerObject(value=int(node.value, 2)) + + def _interpret_octal_node(self, node: OctalNode) -> IntegerObject: + """Converts an `OctalNode` to an `IntegerObject`.""" + return IntegerObject(value=int(node.value, 8)) + + def _interpret_hexadecimal_node( + self, + node: HexadecimalNode, + ) -> IntegerObject: + """Converts a `HexadecimalNode` to a `IntegerObject`.""" + return IntegerObject(value=int(node.value, 16)) + def _interpret_integer_node(self, node: IntegerNode) -> IntegerObject: """Converts an `IntegerNode` to an `IntegerObject`.""" return IntegerObject(value=int(node.value)) @@ -316,22 +338,97 @@ def _populate_params( args: ItemizedExpressionNode, ) -> None: """Tries to assign arguments to parameters.""" - required, _ = partition_a_sequence( + required, optional = partition_a_sequence( params.items, lambda x: x.expression is None ) + required, variadic = partition_a_sequence( + required, + lambda x: not isinstance(x, VariadicParameterDeclarationNode), + ) args_, kwargs = partition_a_sequence( args.items, lambda x: not isinstance(x, AssignmentNode) ) - if len(args.items) > len(params.items) or len(args_) < len(required): + args_, exp_args = partition_a_sequence( + args_, lambda x: not isinstance(x, ExpandableArgumentNode) + ) + if len(args.items) > len(params.items) and not variadic: raise TypeError( - 'It seems that there is a problem with ' - 'matching parameters and arguments!' + 'Too many arguments provided. ' + 'Please check the function definition.' ) - for param, arg in zip(required, args_): + elif len(args.items) < len(required) and not variadic and not exp_args: + raise TypeError( + 'Not enough arguments provided for the required parameters.' + ) + elif len(args_) > len(required) and not variadic: + raise TypeError( + 'Provided more positional arguments than the function accepts.' + ) + elif not args_ and not exp_args and required: + raise TypeError('Required parameters are missing arguments.') + elif (args_ or exp_args) and not required and optional: + raise TypeError( + 'Positional arguments provided but the function expects ' + 'only keyword arguments.' + ) + elif kwargs and not optional: + raise TypeError( + 'Keyword arguments provided but the function does not accept them.' + ) + elif kwargs and set( + map(lambda x: x.references.items[0].value, kwargs) + ).isdisjoint(map(lambda x: x.identifier.value, optional)): + raise TypeError( + 'Provided keyword arguments do not correspond to any optional ' + 'parameters. Please check the function definition for valid ' + 'parameter names.' + ) + elif len(variadic) > 1: + raise TypeError( + 'Multiple variadic parameters defined. ' + 'A function can only have one variadic parameter.' + ) + + for param, arg in zip(required.copy(), args_.copy()): + required.pop(0) + args_.pop(0) self.environment.assign(param.identifier.value, arg) + if ( + exp_args + and (exp_args := exp_args.pop(0).expression) + and variadic + and not required + ): + self.environment.assign(variadic.pop(0).identifier.value, exp_args) + elif ( + exp_args + and len(exp_args.elements) == len(required) # type: ignore[attr-defined] + and not variadic + ): + for param, arg in zip(required, exp_args): + self.environment.assign(param.identifier.value, arg) + elif exp_args and required: + for param, arg in zip(required.copy(), exp_args.elements.copy()): # type: ignore[attr-defined] + required.pop(0) + exp_args.elements.pop(0) # type: ignore[attr-defined] + self.environment.assign(param.identifier.value, arg) + if exp_args and not variadic: + raise TypeError( + 'Extra arguments remain after unpacking, but no variadic ' + 'parameter is available to receive them.' + ) + variadic_ = self.environment.locate( + variadic.pop(0).identifier.value + ) + variadic_.elements.extend(exp_args) + elif args_ and variadic: + variadic_ = self.environment.locate( + variadic.pop(0).identifier.value + ) + variadic_.elements.extend(args_) for kwarg in kwargs: if not self.environment.exists( - name := kwarg.variables.items.copy().pop().value, 0 + name := kwarg.references.items.copy().pop().value, 0 ): raise NameError(f'There is no parameter name `{name}`!') self.environment.assign(name, kwarg.expression) @@ -343,18 +440,25 @@ def _call_non_python_native_object( args: ItemizedExpressionNode, ) -> FarrObject: """Calls native objects of our language.""" - # To avoid passing something that should be called, we interpret - # the arguments here. We could also create a new object to provide - # a more meaningful structure and use that to assign keyword arguments, - # but at least for now we use `AssignmentNode`... args_, kwargs = partition_a_sequence( args.items, lambda x: not isinstance(x, AssignmentNode) ) + args_, exp_args = partition_a_sequence( + args_, lambda x: not isinstance(x, ExpandableArgumentNode) + ) args_ = list(map(self._interpret, args_)) + exp_args = list( + map( + lambda x: ExpandableArgumentNode( + expression=self._interpret(x.expression) + ), + exp_args, + ) + ) kwargs = list( map( lambda x: AssignmentNode( - variables=x.variables, + references=x.references, expression=self._interpret(x.expression), ), kwargs, @@ -383,7 +487,7 @@ def _call_non_python_native_object( ) else invoke.attributes # type: ignore[attr-defined] ), - ItemizedExpressionNode(items=sum([args_, kwargs], [])), + ItemizedExpressionNode(items=sum([args_, exp_args, kwargs], [])), ) try: self._interpret(invoke.body) @@ -412,7 +516,7 @@ def _call_python_native_object( **dict( map( lambda x: ( - x.variables.items.copy().pop(0).value, + x.references.items.copy().pop(0).value, self._interpret(x.expression), ), kwargs, @@ -459,7 +563,19 @@ def _interpret_pre_increment_node( return result pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[target] = ( + result := ( + pointer[target := self._interpret(target)] + + IntegerObject(value=1) + ) + ) + return result pointer.environment.replace( target.value, result := pointer.environment.locate(target.value) @@ -482,7 +598,19 @@ def _interpret_pre_decrement_node( return result pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[target] = ( + result := ( + pointer[target := self._interpret(target)] + - IntegerObject(value=1) + ) + ) + return result pointer.environment.replace( target.value, result := pointer.environment.locate(target.value) @@ -505,7 +633,16 @@ def _interpret_post_increment_node( return result pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[target] = ( + result := pointer[target := self._interpret(target)] + ) + IntegerObject(value=1) + return result pointer.environment.replace( target.value, (result := pointer.environment.locate(target.value)) @@ -528,7 +665,16 @@ def _interpret_post_decrement_node( return result pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[target] = ( + result := pointer[target := self._interpret(target)] + ) - IntegerObject(value=1) + return result pointer.environment.replace( target.value, (result := pointer.environment.locate(target.value)) @@ -545,6 +691,10 @@ def _interpret_arithmetic_operation_node( right = self._interpret(node.right) match node.operator: + case 'LeftShift': + return left << right + case 'RightShift': + return left >> right case 'Add': return left + right case 'Subtract': @@ -707,9 +857,23 @@ def _interpret_variable_declaration_node( ), ) + def _interpret_variadic_parameter_declaration_node( + self, + node: VariadicParameterDeclarationNode, + ) -> None: + """Interprets a variadic parameter declaration node.""" + self.environment.assign( + node.identifier.value, + ( + self._interpret(node.expression) + if node.expression is not None + else ListObject(elements=[]) + ), + ) + def _interpret_assignment_node(self, node: AssignmentNode) -> None: """Updates the content of a variable.""" - *pointers, target = node.variables.items + *pointers, target = node.references.items if not pointers: self.environment.replace( target.value, self._interpret(node.expression) # type: ignore[union-attr] @@ -717,14 +881,83 @@ def _interpret_assignment_node(self, node: AssignmentNode) -> None: return None pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] = self._interpret(node.expression) + return None pointer.environment.replace( target.value, self._interpret(node.expression) # type: ignore[union-attr] ) + def _interpret_left_shift_assignment_node( + self, + node: LeftShiftAssignmentNode, + ) -> None: + """Performs a left shift assignment on the target variable.""" + *pointers, target = node.references.items + if not pointers: + self.environment.replace( + target.value, # type: ignore[union-attr] + self.environment.locate(target.value) # type: ignore[union-attr] + << self._interpret(node.expression), + ) + return None + pointer = self._interpret(pointers.pop(0)) + while pointers: + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] <<= self._interpret( + node.expression + ) + return None + pointer.environment.replace( + target.value, # type: ignore[union-attr] + pointer.environment.locate(target.value) # type: ignore[union-attr] + << self._interpret(node.expression), + ) + + def _interpret_right_shift_assignment_node( + self, + node: RightShiftAssignmentNode, + ) -> None: + """Performs a right shift assignment on the target variable.""" + *pointers, target = node.references.items + if not pointers: + self.environment.replace( + target.value, # type: ignore[union-attr] + self.environment.locate(target.value) # type: ignore[union-attr] + >> self._interpret(node.expression), + ) + return None + pointer = self._interpret(pointers.pop(0)) + while pointers: + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] >>= self._interpret( + node.expression + ) + return None + pointer.environment.replace( + target.value, # type: ignore[union-attr] + pointer.environment.locate(target.value) # type: ignore[union-attr] + >> self._interpret(node.expression), + ) + def _interpret_add_assignment_node(self, node: AddAssignmentNode) -> None: """Adds something to the previous value.""" - *pointers, target = node.variables.items + *pointers, target = node.references.items if not pointers: self.environment.replace( target.value, # type: ignore[union-attr] @@ -734,9 +967,18 @@ def _interpret_add_assignment_node(self, node: AddAssignmentNode) -> None: return None pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] += self._interpret(node.expression) + return None pointer.environment.replace( - target.value, pointer.environment.locate(target.value) + self._interpret(node.expression) # type: ignore[union-attr] + target.value, # type: ignore[union-attr] + pointer.environment.locate(target.value) # type: ignore[union-attr] + + self._interpret(node.expression), ) def _interpret_subtract_assignment_node( @@ -744,7 +986,7 @@ def _interpret_subtract_assignment_node( node: SubtractAssignmentNode, ) -> None: """Subtracts something from the previous value.""" - *pointers, target = node.variables.items + *pointers, target = node.references.items if not pointers: self.environment.replace( target.value, # type: ignore[union-attr] @@ -754,9 +996,18 @@ def _interpret_subtract_assignment_node( return None pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] -= self._interpret(node.expression) + return None pointer.environment.replace( - target.value, pointer.environment.locate(target.value) - self._interpret(node.expression) # type: ignore[union-attr] + target.value, # type: ignore[union-attr] + pointer.environment.locate(target.value) # type: ignore[union-attr] + - self._interpret(node.expression), ) def _interpret_multiply_assignment_node( @@ -764,7 +1015,7 @@ def _interpret_multiply_assignment_node( node: MultiplyAssignmentNode, ) -> None: """Multiplies something by the previous value.""" - *pointers, target = node.variables.items + *pointers, target = node.references.items if not pointers: self.environment.replace( target.value, # type: ignore[union-attr] @@ -774,9 +1025,18 @@ def _interpret_multiply_assignment_node( return None pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] *= self._interpret(node.expression) + return None pointer.environment.replace( - target.value, pointer.environment.locate(target.value) * self._interpret(node.expression) # type: ignore[union-attr] + target.value, # type: ignore[union-attr] + pointer.environment.locate(target.value) # type: ignore[union-attr] + * self._interpret(node.expression), ) def _interpret_divide_assignment_node( @@ -784,7 +1044,7 @@ def _interpret_divide_assignment_node( node: DivideAssignmentNode, ) -> None: """Divides something by the previous value.""" - *pointers, target = node.variables.items + *pointers, target = node.references.items if not pointers: self.environment.replace( target.value, # type: ignore[union-attr] @@ -794,9 +1054,18 @@ def _interpret_divide_assignment_node( return None pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] /= self._interpret(node.expression) + return None pointer.environment.replace( - target.value, pointer.environment.locate(target.value) / self._interpret(node.expression) # type: ignore[union-attr] + target.value, # type: ignore[union-attr] + pointer.environment.locate(target.value) # type: ignore[union-attr] + / self._interpret(node.expression), ) def _interpret_modulus_assignment_node( @@ -804,7 +1073,7 @@ def _interpret_modulus_assignment_node( node: ModulusAssignmentNode, ) -> None: """Calculates the remainder of dividing the previous value by something.""" - *pointers, target = node.variables.items + *pointers, target = node.references.items if not pointers: self.environment.replace( target.value, # type: ignore[union-attr] @@ -814,9 +1083,18 @@ def _interpret_modulus_assignment_node( return None pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] %= self._interpret(node.expression) + return None pointer.environment.replace( - target.value, pointer.environment.locate(target.value) % self._interpret(node.expression) # type: ignore[union-attr] + target.value, # type: ignore[union-attr] + pointer.environment.locate(target.value) # type: ignore[union-attr] + % self._interpret(node.expression), ) def _interpret_power_assignment_node( @@ -824,7 +1102,7 @@ def _interpret_power_assignment_node( node: PowerAssignmentNode, ) -> None: """Raises the previous value to the power of something.""" - *pointers, target = node.variables.items + *pointers, target = node.references.items if not pointers: self.environment.replace( target.value, # type: ignore[union-attr] @@ -834,9 +1112,20 @@ def _interpret_power_assignment_node( return None pointer = self._interpret(pointers.pop(0)) while pointers: - pointer = getattr(pointer, pointers.pop(0).value) # type: ignore[union-attr] + pointer = ( + getattr(pointer, link.value) # type: ignore[union-attr] + if not isinstance(link := pointers.pop(0), RangeNode) + else pointer[self._interpret(link)] + ) + if not hasattr(pointer, 'environment'): + pointer[self._interpret(target)] **= self._interpret( + node.expression + ) + return None pointer.environment.replace( - target.value, pointer.environment.locate(target.value) ** self._interpret(node.expression) # type: ignore[union-attr] + target.value, # type: ignore[union-attr] + pointer.environment.locate(target.value) # type: ignore[union-attr] + ** self._interpret(node.expression), ) def _interpret_while_node(self, node: WhileNode) -> None: diff --git a/farr/interpreter/objects.py b/farr/interpreter/objects.py index eb477ad..cdeaedd 100644 --- a/farr/interpreter/objects.py +++ b/farr/interpreter/objects.py @@ -149,6 +149,24 @@ def __str__(self) -> str: class IntegerObject(HeterogeneousLiteralObject): + def __lshift__(self, other: 'IntegerObject') -> 'IntegerObject': + """Performs bitwise left shift.""" + if not isinstance(other, IntegerObject): + raise TypeError( + f'Type `{self.__class__.__name__}` does not support ' + f'operator `<<` with type `{other.__class__.__name__}`!' + ) + return IntegerObject(value=self.value << other.value) + + def __rshift__(self, other: 'IntegerObject') -> 'IntegerObject': + """Performs bitwise right shift.""" + if not isinstance(other, IntegerObject): + raise TypeError( + f'Type `{self.__class__.__name__}` does not support ' + f'operator `>>` with type `{other.__class__.__name__}`!' + ) + return IntegerObject(value=self.value >> other.value) + def __add__( self, other: Union['IntegerObject', 'FloatObject'], @@ -478,6 +496,24 @@ def __getitem__( ) ) + def __setitem__( + self, + key: 'RangeObject', + value: FarrObject, + ) -> None: + """Updates the elements based on the given range.""" + if key.from_.value <= 0 or key.by is not None and key.by.value <= 0: # type: ignore[union-attr] + raise IndexError('Non-positive indexes are not allowed!') + elif key.to is None and key.by is None: + self.elements[key.from_.value - 1] = value # type: ignore[union-attr] + return None + self.elements[ # type: ignore[call-overload] + key.from_.value # type: ignore[union-attr] + - 1 : key.to.value if key.to is not None else None : ( + key.by.value if key.by is not None else None + ) + ] = value + def __iter__(self) -> 'ListObject': """Iterates the elements in the list.""" self._index = 0 @@ -553,11 +589,20 @@ def popitem_e(self, value: FarrObject) -> FarrObject: def reverse(self) -> 'ListObject': """Returns the reversed list.""" - return ListObject(elements=sorted(self.elements, reverse=True)) # type: ignore[type-var] + return ListObject(elements=list(reversed(self.elements))) # type: ignore[type-var] def ireverse_e(self) -> 'ListObject': """Reverses the list and returns the new state.""" - self.elements = sorted(self.elements, reverse=True) # type: ignore[type-var] + self.elements = list(reversed(self.elements)) # type: ignore[type-var] + return self + + def sort(self) -> 'ListObject': + """Returns the sorted list.""" + return ListObject(elements=sorted(self.elements)) # type: ignore[type-var] + + def isort_e(self) -> 'ListObject': + """Sorts the list in its own place.""" + self.elements = sorted(self.elements) # type: ignore[type-var] return self def shuffle(self) -> 'ListObject': diff --git a/farr/lexer/__init__.py b/farr/lexer/__init__.py index 7722d65..bfe4c9a 100644 --- a/farr/lexer/__init__.py +++ b/farr/lexer/__init__.py @@ -16,16 +16,19 @@ class FarrRegexLexer(RegexLexer): ], ), GroupedTokens( - r'[\-\+]?(?:\d+\.(?!\.)\d*|\d*\.(?!\.)\d+|\d+)|' + r'0[box]\d+|[\-\+]?(?:\d+\.(?!\.)\d*|\d*\.(?!\.)\d+|\d+)|' r'r?"(?:[^"\\]|\\.)*"', [ + Token('Binary', r'0b\d+'), + Token('Octal', r'0o\d+'), + Token('Hexadecimal', r'0x\d+'), Token('Integer', r'[\-\+]?\d+'), Token('Float', r'[\-\+]?(?:\d+\.(?!\.)\d*|\d*\.(?!\.)\d+)'), Token('String', r'r?"(?:[^"\\]|\\.)*"'), ], ), GroupedTokens( - r'[&\|\=\:\+\-]{2}|[\<\>\!\+\-\*/%\^]\=|\.{2,3}|[\s\W]', + r'[\<\>]{2}\=|[&\|\=\:\+\-\<\>]{2}|[\<\>\!\+\-\*/%\^]\=|\.{2,3}|[\s\W]', [ Token('LineBreaker', r'[\n\r]', ignore=True), Token('Indent', r'[\040\t]', ignore=True), @@ -54,10 +57,14 @@ class FarrRegexLexer(RegexLexer): Token('Equal', r'\='), Token('EqualEqual', r'\={2}'), Token('NotEqual', r'\!\='), + Token('LeftShift', r'\<{2}'), + Token('RightShift', r'\>{2}'), Token('LessThan', r'\<'), Token('GreaterThan', r'\>'), Token('LessThanOrEqual', r'\<\='), Token('GreaterThanOrEqual', r'\>\='), + Token('LeftShiftEqual', r'\<{2}\='), + Token('RightShiftEqual', r'\>{2}\='), Token('AddEqual', r'\+\='), Token('SubtractEqual', r'\-\='), Token('MultiplyEqual', r'\*\='), diff --git a/farr/parser/__init__.py b/farr/parser/__init__.py index 715b4c2..b20861f 100644 --- a/farr/parser/__init__.py +++ b/farr/parser/__init__.py @@ -4,7 +4,6 @@ # https://github.com/sheikhartin/farr from functools import partial -from itertools import takewhile from typing import Optional, Union, Callable, List, Tuple from farr.helpers import partition_a_sequence, normalize_identifier @@ -17,6 +16,9 @@ PassNode, NullNode, HeterogeneousLiteralNode, + BinaryNode, + OctalNode, + HexadecimalNode, IntegerNode, FloatNode, StringNode, @@ -27,6 +29,7 @@ ListNode, HashMapNode, PairNode, + ExpandableArgumentNode, CallNode, GroupedExpressionNode, NegationOperationNode, @@ -41,7 +44,10 @@ StatementNode, UseNode, VariableDeclarationNode, + VariadicParameterDeclarationNode, AssignmentNode, + LeftShiftAssignmentNode, + RightShiftAssignmentNode, AddAssignmentNode, SubtractAssignmentNode, MultiplyAssignmentNode, @@ -65,26 +71,6 @@ class FarrParser(Parser): - def _look_until( - self, - retreat: Tuple[str, ...], - tokens: Tuple[str, ...], - ) -> Optional[bool]: - """Checks that all remaining tokens are acceptable.""" - return all( - map( - lambda x: x.name in tokens, # type: ignore[union-attr] - takewhile( - lambda x: x.name not in retreat if x is not None else None, # type: ignore[union-attr] - [ - self._current_token, - self._next_token, - *self._tokens_state, - ], - ), - ) - ) - def _except_current_and_next_at( self, index: int, @@ -237,6 +223,39 @@ def _parse_null(self) -> NullNode: self.step() return null + def _parse_binary(self) -> BinaryNode: + """Parses a binary value.""" + self.expect('Binary') + binary = BinaryNode( + row=self._current_token.row, # type: ignore[attr-defined] + column=self._current_token.column, # type: ignore[attr-defined] + value=self._current_token.value, # type: ignore[attr-defined] + ) + self.step() + return binary + + def _parse_octal(self) -> OctalNode: + """Parses an octal value.""" + self.expect('Octal') + octal = OctalNode( + row=self._current_token.row, # type: ignore[attr-defined] + column=self._current_token.column, # type: ignore[attr-defined] + value=self._current_token.value, # type: ignore[attr-defined] + ) + self.step() + return octal + + def _parse_hexadecimal(self) -> HexadecimalNode: + """Parses a hexadecimal value.""" + self.expect('Hexadecimal') + hexadecimal = HexadecimalNode( + row=self._current_token.row, # type: ignore[attr-defined] + column=self._current_token.column, # type: ignore[attr-defined] + value=self._current_token.value, # type: ignore[attr-defined] + ) + self.step() + return hexadecimal + def _parse_integer(self) -> IntegerNode: """Parses an integer token.""" self.expect('Integer') @@ -289,6 +308,12 @@ def _process_factor(self) -> Optional[ExpressionNode]: return self._parse_pass() elif self.check('Null'): return self._parse_null() + elif self.check('Binary'): + return self._parse_binary() + elif self.check('Octal'): + return self._parse_octal() + elif self.check('Hexadecimal'): + return self._parse_hexadecimal() elif self.check('Integer'): return self._parse_integer() elif self.check('Float'): @@ -323,7 +348,14 @@ def _parse_pre_increment(self) -> PreIncrementNode: row=operator.row, # type: ignore[attr-defined] column=operator.column, # type: ignore[attr-defined] operator=None, - operand=self._dot_separated_items(self._parse_identifier), # type: ignore[attr-defined] + operand=( + expression.expressions + if isinstance( + expression := self._process_expression(), + ChainedExpressionsNode, + ) + else ItemizedExpressionNode(items=[expression]) + ), ) def _parse_pre_decrement(self) -> PreDecrementNode: @@ -335,33 +367,14 @@ def _parse_pre_decrement(self) -> PreDecrementNode: row=operator.row, # type: ignore[attr-defined] column=operator.column, # type: ignore[attr-defined] operator=None, - operand=self._dot_separated_items(self._parse_identifier), - ) - - def _parse_post_increment(self) -> PostIncrementNode: - """Parses a post-increment operation.""" - operand = self._dot_separated_items(self._parse_identifier) - self.expect('Increment') - operator = self._current_token - self.step() - return PostIncrementNode( - row=operator.row, # type: ignore[attr-defined] - column=operator.column, # type: ignore[attr-defined] - operator=None, - operand=operand, - ) - - def _parse_post_decrement(self) -> PostDecrementNode: - """Parses a post-decrement operation.""" - operand = self._dot_separated_items(self._parse_identifier) - self.expect('Decrement') - operator = self._current_token - self.step() - return PostDecrementNode( - row=operator.row, # type: ignore[attr-defined] - column=operator.column, # type: ignore[attr-defined] - operator=None, - operand=operand, + operand=( + expression.expressions + if isinstance( + expression := self._process_expression(), + ChainedExpressionsNode, + ) + else ItemizedExpressionNode(items=[expression]) + ), ) def _parse_list(self) -> ListNode: @@ -417,18 +430,27 @@ def _parse_hash_map(self) -> HashMapNode: def _parse_range(self) -> RangeNode: """Parses a range expression.""" - from_ = self._validate(self._process_term, ('Term',)) + from_ = self._validate(self._process_expression, ('Expression',)) if (by := None) or self.check('Comma'): self.step() - by = self._validate(self._process_term, ('Term',)) + by = self._validate(self._process_expression, ('Expression',)) if (to := None) or self.check('Between'): self.step() - to = self._validate(self._process_term, ('Term',)) + to = self._validate(self._process_expression, ('Expression',)) return RangeNode(from_=from_, to=to, by=by) # type: ignore[arg-type] def _parse_arithmetic_operation(self) -> ArithmeticOperationNode: """Parses an arithmetic operation.""" - self.expect('Add', 'Subtract', 'Multiply', 'Divide', 'Modulus', 'Power') + self.expect( + 'LeftShift', + 'RightShift', + 'Add', + 'Subtract', + 'Multiply', + 'Divide', + 'Modulus', + 'Power', + ) operator = self._current_token self.step() return ArithmeticOperationNode( @@ -436,23 +458,49 @@ def _parse_arithmetic_operation(self) -> ArithmeticOperationNode: column=operator.column, # type: ignore[attr-defined] operator=operator.name, # type: ignore[attr-defined] left=self._validate( # type: ignore[arg-type] - self._process_term, - ('Term',), + self._process_expression, + ('Expression',), ), right=self._validate( # type: ignore[arg-type] - self._process_term, - ('Term',), + self._process_expression, + ('Expression',), ), ) + def _parse_keyword_assignment(self) -> AssignmentNode: + """Assigns to an optional parameter when calling.""" + references = self._dot_separated_items(self._parse_identifier) + self.expect('Equal') + self.step() + return AssignmentNode( + references=references, + expression=self._validate( # type: ignore[arg-type] + self._process_expression, + ('Expression',), + ), + ) + + def _parse_expandable_argument(self) -> ExpandableArgumentNode: + """Parses an expandable argument for calls.""" + self.expect('Pass') + self.step() + return ExpandableArgumentNode( + expression=self._validate(self._process_expression, ('Expression',)) # type: ignore[arg-type] + ) + def _resolve_call_argument( self, ) -> Optional[Union[ExpressionNode, AssignmentNode]]: """Resolves a call argument expression.""" return ( - self._parse_assignment() + self._parse_keyword_assignment() if self.check('Identifier', 'Symbol') and self.peek('Equal') - else self._process_expression() + else ( + self._parse_expandable_argument() + if self.check('Pass') + and not self.peek('Comma', 'RightParenthesis') + else self._process_expression() + ) ) def _parse_call(self, invoke: IdentifierNode) -> CallNode: @@ -523,10 +571,6 @@ def _process_term(self) -> Optional[ExpressionNode]: return self._parse_pre_increment() elif self.check('Decrement'): return self._parse_pre_decrement() - elif self._look_until(('Increment',), ('Identifier', 'Symbol', 'Dot')): - return self._parse_post_increment() - elif self._look_until(('Decrement',), ('Identifier', 'Symbol', 'Dot')): - return self._parse_post_decrement() elif (factor := self._process_factor()) is None: if self.check('Not'): return self._parse_negation_operation() @@ -548,7 +592,14 @@ def _process_term(self) -> Optional[ExpressionNode]: elif self.check('LeftBracket'): return self._bracketed(self._parse_range) # type: ignore[return-value] elif self.check( - 'Add', 'Subtract', 'Multiply', 'Divide', 'Modulus', 'Power' + 'LeftShift', + 'RightShift', + 'Add', + 'Subtract', + 'Multiply', + 'Divide', + 'Modulus', + 'Power', ): return self._parse_arithmetic_operation() elif isinstance(factor, HeterogeneousLiteralNode): @@ -561,61 +612,90 @@ def _process_term(self) -> Optional[ExpressionNode]: def _process_expression(self) -> Optional[ExpressionNode]: """Processes an expression.""" - if (left := self._process_term()) is not None: - while self.check( - 'EqualEqual', - 'NotEqual', - 'GreaterThan', - 'LessThan', - 'GreaterThanOrEqual', - 'LessThanOrEqual', - ): - operator = self._current_token - self.step() - left = RelationalOperationNode( - row=operator.row, # type: ignore[attr-defined] - column=operator.column, # type: ignore[attr-defined] - operator=operator.name, # type: ignore[attr-defined] - left=left, - right=self._validate( # type: ignore[arg-type] - self._process_term, - ('Term',), - ), - ) - while self.check('And', 'Or'): - operator = self._current_token - self.step() - left = LogicalOperationNode( - row=operator.row, # type: ignore[attr-defined] - column=operator.column, # type: ignore[attr-defined] - operator=operator.name, # type: ignore[attr-defined] - left=left, - right=self._validate( # type: ignore[arg-type] - self._process_expression, - ('Expression',), - ), - ) - while self.check('If'): - if_ = self._current_token - self.step() - condition = self._validate( + if (left := self._process_term()) is None: + return None + + if self.check( + 'EqualEqual', + 'NotEqual', + 'GreaterThan', + 'LessThan', + 'GreaterThanOrEqual', + 'LessThanOrEqual', + ): + operator = self._current_token + self.step() + left = RelationalOperationNode( + row=operator.row, # type: ignore[attr-defined] + column=operator.column, # type: ignore[attr-defined] + operator=operator.name, # type: ignore[attr-defined] + left=left, + right=self._validate( # type: ignore[arg-type] self._process_expression, ('Expression',), - ) - self.expect('Else') - self.step() - left = TernaryOperationNode( - row=if_.row, # type: ignore[attr-defined] - column=if_.column, # type: ignore[attr-defined] - then=left, - condition=condition, # type: ignore[arg-type] - orelse=self._validate( # type: ignore[arg-type] - self._process_expression, - ('Expression',), - ), - ) - return left - return None + ), + ) + if self.check('And', 'Or'): + operator = self._current_token + self.step() + left = LogicalOperationNode( + row=operator.row, # type: ignore[attr-defined] + column=operator.column, # type: ignore[attr-defined] + operator=operator.name, # type: ignore[attr-defined] + left=left, + right=self._validate( # type: ignore[arg-type] + self._process_expression, + ('Expression',), + ), + ) + + if self.check('Increment'): + operator = self._current_token + self.step() + left = PostIncrementNode( + row=operator.row, # type: ignore[attr-defined] + column=operator.column, # type: ignore[attr-defined] + operator=None, + operand=( + left.expressions + if isinstance(left, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[left]) + ), + ) + elif self.check('Decrement'): + operator = self._current_token + self.step() + left = PostDecrementNode( + row=operator.row, # type: ignore[attr-defined] + column=operator.column, # type: ignore[attr-defined] + operator=None, + operand=( + left.expressions + if isinstance(left, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[left]) + ), + ) + + if self.check('If'): + if_ = self._current_token + self.step() + condition = self._validate( + self._process_expression, + ('Expression',), + ) + self.expect('Else') + self.step() + left = TernaryOperationNode( + row=if_.row, # type: ignore[attr-defined] + column=if_.column, # type: ignore[attr-defined] + then=left, + condition=condition, # type: ignore[arg-type] + orelse=self._validate( # type: ignore[arg-type] + self._process_expression, + ('Expression',), + ), + ) + return left def _parse_use(self) -> UseNode: """Parses an use statement.""" @@ -642,91 +722,153 @@ def _parse_variable_declaration(self) -> VariableDeclarationNode: identifier=identifier, expression=expression ) # type: ignore[arg-type] - def _parse_assignment(self) -> AssignmentNode: + def _parse_variadic_parameter_declaration( + self, + ) -> VariadicParameterDeclarationNode: + """Parses a declaration of a variadic parameter.""" + self.expect('Variable') + self.step() + identifier = self._parse_identifier() + self.expect('Pass') + self.step() + if (expression := None) or self.check('Equal'): + self.step() + expression = self._validate( + self._process_expression, ('Expression',) + ) + return VariadicParameterDeclarationNode( + identifier=identifier, expression=expression + ) # type: ignore[arg-type] + + def _parse_assignment( + self, + references: ItemizedExpressionNode, + ) -> AssignmentNode: """Parses an assignment statement.""" - variables = self._dot_separated_items(self._parse_identifier) self.expect('Equal') self.step() return AssignmentNode( - variables=variables, + references=references, + expression=self._validate( # type: ignore[arg-type] + self._process_expression, + ('Expression',), + ), + ) + + def _parse_left_shift_assignment( + self, + references: ItemizedExpressionNode, + ) -> LeftShiftAssignmentNode: + """Parses a left shift assignment.""" + self.expect('LeftShiftEqual') + self.step() + return LeftShiftAssignmentNode( + references=references, + expression=self._validate( # type: ignore[arg-type] + self._process_expression, + ('Expression',), + ), + ) + + def _parse_right_shift_assignment( + self, + references: ItemizedExpressionNode, + ) -> RightShiftAssignmentNode: + """Parses a right shift assignment.""" + self.expect('RightShiftEqual') + self.step() + return RightShiftAssignmentNode( + references=references, expression=self._validate( # type: ignore[arg-type] self._process_expression, ('Expression',), ), ) - def _parse_add_assignment(self) -> AddAssignmentNode: + def _parse_add_assignment( + self, + references: ItemizedExpressionNode, + ) -> AddAssignmentNode: """Parses an addition assignment.""" - variables = self._dot_separated_items(self._parse_identifier) self.expect('AddEqual') self.step() return AddAssignmentNode( - variables=variables, + references=references, expression=self._validate( # type: ignore[arg-type] self._process_expression, ('Expression',), ), ) - def _parse_subtract_assignment(self) -> SubtractAssignmentNode: + def _parse_subtract_assignment( + self, + references: ItemizedExpressionNode, + ) -> SubtractAssignmentNode: """Parses a subtraction assignment.""" - variables = self._dot_separated_items(self._parse_identifier) self.expect('SubtractEqual') self.step() return SubtractAssignmentNode( - variables=variables, + references=references, expression=self._validate( # type: ignore[arg-type] self._process_expression, ('Expression',), ), ) - def _parse_multiply_assignment(self) -> MultiplyAssignmentNode: + def _parse_multiply_assignment( + self, + references: ItemizedExpressionNode, + ) -> MultiplyAssignmentNode: """Parses a multiplication assignment.""" - variables = self._dot_separated_items(self._parse_identifier) self.expect('MultiplyEqual') self.step() return MultiplyAssignmentNode( - variables=variables, + references=references, expression=self._validate( # type: ignore[arg-type] self._process_expression, ('Expression',), ), ) - def _parse_divide_assignment(self) -> DivideAssignmentNode: + def _parse_divide_assignment( + self, + references: ItemizedExpressionNode, + ) -> DivideAssignmentNode: """Parses a division assignment.""" - variables = self._dot_separated_items(self._parse_identifier) self.expect('DivideEqual') self.step() return DivideAssignmentNode( - variables=variables, + references=references, expression=self._validate( # type: ignore[arg-type] self._process_expression, ('Expression',), ), ) - def _parse_modulus_assignment(self) -> ModulusAssignmentNode: + def _parse_modulus_assignment( + self, + references: ItemizedExpressionNode, + ) -> ModulusAssignmentNode: """Parses a modulus assignment.""" - variables = self._dot_separated_items(self._parse_identifier) self.expect('ModulusEqual') self.step() return ModulusAssignmentNode( - variables=variables, + references=references, expression=self._validate( # type: ignore[arg-type] self._process_expression, ('Expression',), ), ) - def _parse_power_assignment(self) -> PowerAssignmentNode: + def _parse_power_assignment( + self, + references: ItemizedExpressionNode, + ) -> PowerAssignmentNode: """Parses a power assignment.""" - variables = self._dot_separated_items(self._parse_identifier) self.expect('PowerEqual') self.step() return PowerAssignmentNode( - variables=variables, + references=references, expression=self._validate( # type: ignore[arg-type] self._process_expression, ('Expression',), @@ -796,7 +938,7 @@ def _parse_for(self) -> ForNode: ) self.expect('In') self.step() - condition = self._validate(self._process_term, ('Term',)) + condition = self._validate(self._process_expression, ('Expression',)) body = self._parse_block(self._process_expression_or_statement) if (orelse := None) or self.check('Else'): self.step() @@ -921,12 +1063,21 @@ def _parse_try(self) -> TryNode: catch = self._parse_catch() return TryNode(body=body, catch=catch) - def _resolve_parameter(self) -> Optional[VariableDeclarationNode]: + def _resolve_parameter( + self, + ) -> Optional[ + Union[VariableDeclarationNode, VariadicParameterDeclarationNode] + ]: """Resolves a parameter declaration.""" return ( - self._parse_variable_declaration() + self._parse_variadic_parameter_declaration() if self.check('Variable') - else None + and self._except_current_and_next_at(0, ('Pass',)) + else ( + self._parse_variable_declaration() + if self.check('Variable') + else None + ) ) def _parse_function(self) -> FunctionDefinitionNode: @@ -1002,50 +1153,16 @@ def _parse_return(self) -> ReturnNode: expression=self._process_expression(), ) - def _process_statement(self) -> Optional[StatementNode]: - """Processes a statement.""" + def _process_expression_or_statement( + self, + ) -> Optional[Union[ExpressionNode, StatementNode]]: + """Processes either an expression or a statement.""" if self.check('Use'): return self._followed_by_semicolon(self._parse_use) # type: ignore[return-value] elif self.check('Variable'): return self._followed_by_semicolon( # type: ignore[return-value] self._parse_variable_declaration ) - elif self._look_until(('Equal',), ('Identifier', 'Symbol', 'Dot')): - return self._followed_by_semicolon( # type: ignore[return-value] - self._parse_assignment - ) - elif self._look_until(('AddEqual',), ('Identifier', 'Symbol', 'Dot')): - return self._followed_by_semicolon( # type: ignore[return-value] - self._parse_add_assignment - ) - elif self._look_until( - ('SubtractEqual',), ('Identifier', 'Symbol', 'Dot') - ): - return self._followed_by_semicolon( # type: ignore[return-value] - self._parse_subtract_assignment - ) - elif self._look_until( - ('MultiplyEqual',), ('Identifier', 'Symbol', 'Dot') - ): - return self._followed_by_semicolon( # type: ignore[return-value] - self._parse_multiply_assignment - ) - elif self._look_until( - ('DivideEqual',), ('Identifier', 'Symbol', 'Dot') - ): - return self._followed_by_semicolon( # type: ignore[return-value] - self._parse_divide_assignment - ) - elif self._look_until( - ('ModulusEqual',), ('Identifier', 'Symbol', 'Dot') - ): - return self._followed_by_semicolon( # type: ignore[return-value] - self._parse_modulus_assignment - ) - elif self._look_until(('PowerEqual',), ('Identifier', 'Symbol', 'Dot')): - return self._followed_by_semicolon( # type: ignore[return-value] - self._parse_power_assignment - ) elif self.check('While'): return self._parse_while() elif self.check('For'): @@ -1070,15 +1187,108 @@ def _process_statement(self) -> Optional[StatementNode]: return self._parse_struct() elif self.check('Return'): return self._followed_by_semicolon(self._parse_return) # type: ignore[return-value] - return None - - def _process_expression_or_statement( - self, - ) -> Optional[Union[ExpressionNode, StatementNode]]: - """Processes either an expression or a statement.""" - return self._process_statement() or self._followed_by_semicolon( - self._process_expression - ) + elif ( + expression := self._process_expression() + ) is not None and self.check('Equal'): + return self._followed_by_semicolon( + partial( + self._parse_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + elif expression is not None and self.check('LeftShiftEqual'): + return self._followed_by_semicolon( + partial( + self._parse_left_shift_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + elif expression is not None and self.check('RightShiftEqual'): + return self._followed_by_semicolon( + partial( + self._parse_right_shift_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + elif expression is not None and self.check('AddEqual'): + return self._followed_by_semicolon( + partial( + self._parse_add_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + elif expression is not None and self.check('SubtractEqual'): + return self._followed_by_semicolon( + partial( + self._parse_subtract_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + elif expression is not None and self.check('MultiplyEqual'): + return self._followed_by_semicolon( + partial( + self._parse_multiply_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + elif expression is not None and self.check('DivideEqual'): + return self._followed_by_semicolon( + partial( + self._parse_divide_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + elif expression is not None and self.check('ModulusEqual'): + return self._followed_by_semicolon( + partial( + self._parse_modulus_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + elif expression is not None and self.check('PowerEqual'): + return self._followed_by_semicolon( + partial( + self._parse_power_assignment, + references=( + expression.expressions + if isinstance(expression, ChainedExpressionsNode) + else ItemizedExpressionNode(items=[expression]) + ), + ) + ) + return self._followed_by_semicolon(lambda: expression) def parse(self, tokens_state: List[TokenState]) -> ModuleNode: """Parses the whole module and returns the root AST node.""" diff --git a/farr/parser/nodes.py b/farr/parser/nodes.py index 54619ea..aaa631c 100644 --- a/farr/parser/nodes.py +++ b/farr/parser/nodes.py @@ -43,6 +43,18 @@ class HeterogeneousLiteralNode(PositionedNode, ExpressionNode): value: str = field(kw_only=True) +class BinaryNode(HeterogeneousLiteralNode): + pass + + +class OctalNode(HeterogeneousLiteralNode): + pass + + +class HexadecimalNode(HeterogeneousLiteralNode): + pass + + class IntegerNode(HeterogeneousLiteralNode): pass @@ -103,6 +115,11 @@ class PairNode(DataStructureNode): value: ExpressionNode = field(kw_only=True) +@dataclass +class ExpandableArgumentNode(ExpressionNode): + expression: ListNode = field(kw_only=True) + + @dataclass class CallNode(ExpressionNode): invoke: IdentifierNode = field(kw_only=True) @@ -192,9 +209,13 @@ class VariableDeclarationNode(StatementNode): expression: Optional[ExpressionNode] = field(kw_only=True) +class VariadicParameterDeclarationNode(VariableDeclarationNode): + pass + + @dataclass class AssignmentNode(StatementNode): - variables: ItemizedExpressionNode = field(kw_only=True) + references: ItemizedExpressionNode = field(kw_only=True) expression: ExpressionNode = field(kw_only=True) @@ -202,6 +223,14 @@ class AggregateAssignmentNode(AssignmentNode): pass +class LeftShiftAssignmentNode(AggregateAssignmentNode): + pass + + +class RightShiftAssignmentNode(AggregateAssignmentNode): + pass + + class AddAssignmentNode(AggregateAssignmentNode): pass diff --git a/libs/algorithms/funda.farr b/libs/algorithms/funda.farr deleted file mode 100644 index 7ff9f21..0000000 --- a/libs/algorithms/funda.farr +++ /dev/null @@ -1 +0,0 @@ -// Nothing, just a start! diff --git a/libs/algorithms/searching.farr b/libs/algorithms/searching.farr deleted file mode 100644 index ea69f13..0000000 --- a/libs/algorithms/searching.farr +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Linear search algorithm to find the index of the target element. - * - * @param list - The list to search. - * @param target - The target element to find. - */ -fn linear_search(let list, let target) = { - let index = 1; - while index < list.length = { - if list.[index] == target = { - return! index; - } - index++; - } - return! -1; -} - -/** - * Binary search algorithm to find the index of the target element. - * - * @param list - The list to search. - * @param target - The target element to find. - */ -fn binary_search(let list, let target) = { - let left = 1; - let right = list.length; - - while left <= right = { - let mid = (+ / - right left 2 left).toint(); - let result = list.[mid]; - - if result < target = { - left = + mid 1; - } else if result > target = { - right = - mid 1; - } else = { - return! mid; - } - } - - return! -1; -} diff --git a/libs/functools.farr b/libs/functools.farr new file mode 100644 index 0000000..f47db77 --- /dev/null +++ b/libs/functools.farr @@ -0,0 +1,78 @@ +/** + * Checks if all elements in the list are truthy. + * + * @param list - The list to check. + */ +fn all(let list) = { + for let i in list = { + if ! i = { + return! false; + } + } + return! true; +} + +/** + * Checks if any element in the list is truthy. + * + * @param list - The list to check. + */ +fn any(let list) = { + for let i in list = { + if i = { + return! true; + } + } + return! false; +} + +/** + * Applies a function to each item in the object and collects the results. + * + * @param func - The function to apply. + * @param object - The object to iterate over. + */ +fn map(let func, let object) = { + let result = {}; + for let i in object = { + result.iappend!(func(i)); + } + return! result; +} + +/** + * Represents a partially applied function. + * + * @attr func - The function to be partially applied. + * @attr arg - The single argument to be applied to the function. + */ +struct Partial = { + let func, + let arg +} + +/** + * Converts the partial application to a string representation. + */ +fn Partial::tostring() = { + return! "${func}(${arg})"; +} + +/** + * Invokes the partial application. + */ +fn Partial::invoke() = { + // The language currently does not support argument unpacking, which means + // functions are limited to a single argument. + return! func(arg); +} + +/** + * Creates a new partial application of the given function. + * + * @param func - The function to partially apply. + * @param arg - The arguments to apply. + */ +fn partial(let func, let arg) = { + return! Partial(func, arg); +} diff --git a/pyproject.toml b/pyproject.toml index 7baf5ac..b32912b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "farr" -version = "1.1.0" +version = "1.4.2" description = "The gathering place of utilitarians and empiricists!" license = "MIT" authors = ["Artin Mohammadi "] diff --git a/tests/test_interpreter.py b/tests/test_interpreter.py index 46b7c82..b2a1b12 100644 --- a/tests/test_interpreter.py +++ b/tests/test_interpreter.py @@ -3,6 +3,8 @@ # We understand that beauty is not objective... # https://github.com/sheikhartin/farr +import textwrap + import pytest from farr.lexer import FarrRegexLexer @@ -20,15 +22,24 @@ def test_binary_operations_interpretation( farr_interpreter_fixture.interpret( farr_parser_fixture.parse( farr_regex_lexer_fixture.tokenize( - 'println(+ 13 8, - 2 4, * 6 3, / 9 10, % 12 1, ^ 6 4);\n\n' - 'println("73." == 73., "Hi!" != "Hello!", 7 < 15, 11 > 5,' - '9 <= 6, 13 >= 13);\n\nprintln("Farr" || 67, 11 && 1);' + textwrap.dedent( + """ + println(+ 13 8, - 2 4, * 6 3, / 9 10, % 12 1, ^ 6 4); + println("73." == 73., "Hi!" != "Hello!", 7 < 15, 11 > 5, + 9 <= 6, 13 >= 13); + println("Farr" || 67, 11 && 1); + """ + ) ) ) ) captured = capsys.readouterr() - assert captured.out == ( - '21 -2 18 0.9 0 1296\nfalse true true true false true\nFarr 1\n' + assert captured.out == textwrap.dedent( + """\ + 21 -2 18 0.9 0 1296 + false true true true false true + Farr 1 + """ ) @@ -42,27 +53,117 @@ def test_definitions_and_manipulations_interpretation( farr_interpreter_fixture.interpret( farr_parser_fixture.parse( farr_regex_lexer_fixture.tokenize( - 'let var; println(var); var = 42; println(var); var += 3;' - 'println(var); var -= 5; println(var); var *= .5; println(var);' - 'var /= 5; println(var); var %= 10; println(var); var ^= 0;' - 'println(var); var++; println(var); var--; println(var);\n\n' - 'fn add_one(let x) = { return! + x 1; } println(add_one(x=9));' - 'fn add_one(let x = 5) = { println(+ x 1); } add_one();\n' - 'struct Person = { let fullname, let birthyear } let person = ' - 'Person("John Doe", 1997); println(person, person.fullname, ' - 'person.birthyear); person.fullname = "Artin Mohammadi"; ' - 'person.birthyear += 5; println(person, person.fullname, ' - 'person.birthyear);\nfn Person::calculate_age(let curryear) = {' - 'return! - curryear birthyear; } let person_age = person.' - 'calculate_age(2024); print("I think u r", person_age);' + textwrap.dedent( + """ + let var; + println(var); + var = 42; + println(var); + var += 3; + println(var); + var -= 5; + println(var); + var *= .5; + println(var); + var /= 5; + println(var); + var %= 10; + println(var); + var ^= 0; + println(var); + var++; + println(var); + var--; + println(var); + + fn add_one(let x) = { + return! + x 1; + } + println(add_one(x=9)); + + fn add_one(let x = 5) = { // It will replace the previous function... + println(+ x 1); + } + add_one(); + + struct Person = { + let fullname, + let birthyear + } + let person = Person("John Doe", 1997); + println(person, person.fullname, person.birthyear); + person.fullname = "Artin Mohammadi"; + person.birthyear += 5; + println(person, person.fullname, person.birthyear); + + fn Person::calculate_age(let curryear) = { + return! - curryear birthyear; + } + let person_age = person.calculate_age(2024); + println("I think u r", person_age); + """ + ) ) ) ) captured = capsys.readouterr() - assert captured.out == ( - 'null\n42\n45\n40\n20.0\n4.0\n4.0\n1.0\n2.0\n1.0\n10\nnull\n6\n' - 'StructInstanceObject() John Doe 1997\nStructInstanceObject() Artin ' - 'Mohammadi 2002\nI think u r 22' + assert captured.out == textwrap.dedent( + """\ + null + 42 + 45 + 40 + 20.0 + 4.0 + 4.0 + 1.0 + 2.0 + 1.0 + 10 + null + 6 + StructInstanceObject() John Doe 1997 + StructInstanceObject() Artin Mohammadi 2002 + I think u r 22 + """ + ) + + +@pytest.mark.parametrize( + ('invocation',), + [ + pytest.param('add()', marks=pytest.mark.xfail), + pytest.param('add(1, y=2)', marks=pytest.mark.xfail), + pytest.param('add(...{1, 2, 3})', marks=pytest.mark.xfail), + pytest.param('add_one(1)', marks=pytest.mark.xfail), + pytest.param('add_one(...{1,})', marks=pytest.mark.xfail), + ], +) +def test_type_error_on_invocation( + invocation: str, + farr_regex_lexer_fixture: FarrRegexLexer, + farr_parser_fixture: FarrParser, + farr_interpreter_fixture: FarrInterpreter, +) -> None: + """Validates proper handling of invalid call arguments.""" + farr_interpreter_fixture.interpret( + farr_parser_fixture.parse( + farr_regex_lexer_fixture.tokenize( + textwrap.dedent( + f""" + fn add(let x, let y) = {{ + return! + x y; + }} + + fn add_one(let x = 10) = {{ + return! + x 1; + }} + + {invocation}; + """ + ) + ) + ) ) @@ -76,30 +177,57 @@ def test_data_types_and_data_structures_stuff_interpretation( farr_interpreter_fixture.interpret( farr_parser_fixture.parse( farr_regex_lexer_fixture.tokenize( - 'let a = 1; let b = 1.; let c = "1."; println(typeof?(a), ' - 'typeof?(b), typeof?(c), similartypes?(a, b));\n\nlet d = {' - '"weirdness", "impeded", 56, "waterfall", "geed", 65, 23, "A"};' - 'println(d, "::", typeof?(d), d.[1], d.[2..4], similartypes?(' - 'd.[1..3], {}), d.length, d.first, d.last, d.isempty?(), ' - 'typeof?(d.shuffle()) == "ListObject"); d.clear!(); println(' - 'typeof?(d), d.isempty?()); d.iappend!(1); d.iprepend!(0); ' - 'println(d, d.join("-"), d.nearest?(1)); d.pop!(1); ' - 'd.popitem!(1); println(d.isempty?()); \nlet e = {:"One" 1, ' - ':"Two" 2}; println(e, e.first, e.last, e.length, ' - 'e.isempty?(), e.get("Three", 3)); e.popitem!("Two"); ' - 'e.pop!(1); println(e.isempty?()); let f = "Hello"; println(' - 'f.concat(", world!")); println(f.concat(", guys!"), ' - 'f.split("l"));' + textwrap.dedent( + """ + let a = 1; + let b = 1.; + let c = "1."; + println(typeof?(a), typeof?(b), typeof?(c), + similartypes?(a, b)); + + let d = {"weirdness", "impeded", 56, "waterfall", "geed", + 65, 23, "A"}; + println(d, "::", typeof?(d), d.[1], d.[2..4], + similartypes?(d.[1..3], {}), d.length, d.first, + d.last, d.isempty?(), + typeof?(d.shuffle()) == "ListObject"); + d.clear!(); + println(typeof?(d), d.isempty?()); + d.iappend!(1); + d.iprepend!(0); + println(d, d.join("-"), d.nearest?(1)); + d.pop!(1); + d.popitem!(1); + println(d.isempty?()); + + let e = {:"One" 1, :"Two" 2}; + println(e, e.first, e.last, e.length, e.isempty?(), + e.get("Three", 3)); + e.popitem!("Two"); + e.pop!(1); + println(e.isempty?()); + + let f = "Hello"; + println(f.concat(", world!")); + println(f.concat(", guys!"), f.split("l")); + """ + ) ) ) ) captured = capsys.readouterr() - assert captured.out == ( - 'IntegerObject FloatObject StringObject false\nweirdness; impeded; 56; ' - 'waterfall; geed; 65; 23; A :: ListObject weirdness impeded; 56; ' - 'waterfall true 8 weirdness A false true\nListObject true\n0; 1 0-1 2\n' - 'true\nOne->1; Two->2 One->1 Two->2 2 false 3\ntrue\nHello, world!\n' - 'Hello, guys! He; o\n' + assert captured.out == textwrap.dedent( + """\ + IntegerObject FloatObject StringObject false + weirdness; impeded; 56; waterfall; geed; 65; 23; A :: ListObject weirdness impeded; 56; waterfall true 8 weirdness A false true + ListObject true + 0; 1 0-1 2 + true + One->1; Two->2 One->1 Two->2 2 false 3 + true + Hello, world! + Hello, guys! He; o + """ ) @@ -113,11 +241,22 @@ def test_import_system_interpretation( farr_interpreter_fixture.interpret( farr_parser_fixture.parse( farr_regex_lexer_fixture.tokenize( - 'use math/random; println(similartypes?(random.random(), .59), ' - 'similartypes?(random.uniform(10, 20), 10.20), typeof?(random.' - 'uniform(10, 20, size=2)) != "ListObject");' + textwrap.dedent( + """ + use math/random; + println( + similartypes?(random.random(), .59), + similartypes?(random.uniform(10, 20), 10.20), + typeof?(random.uniform(10, 20, size=2)) != "ListObject", + ); + """ + ) ) ) ) captured = capsys.readouterr() - assert captured.out == 'true true false\n' + assert captured.out == textwrap.dedent( + """\ + true true false + """ + ) diff --git a/tests/test_lexer.py b/tests/test_lexer.py index 7bf066c..229b9bd 100644 --- a/tests/test_lexer.py +++ b/tests/test_lexer.py @@ -3,6 +3,8 @@ # We understand that beauty is not objective... # https://github.com/sheikhartin/farr +import textwrap + import pytest from farr.lexer import FarrRegexLexer @@ -13,10 +15,11 @@ def test_an_arithmetic_operation_tokenization( farr_regex_lexer_fixture: FarrRegexLexer, ) -> None: """Checks the output of three plus five.""" - assert farr_regex_lexer_fixture.tokenize('+ 3 5.') == [ + assert farr_regex_lexer_fixture.tokenize('+ 3 5.;') == [ TokenState(row=1, column=1, name='Add', value='+'), TokenState(row=1, column=3, name='Integer', value='3'), TokenState(row=1, column=5, name='Float', value='5.'), + TokenState(row=1, column=7, name='Semicolon', value=';'), ] @@ -24,48 +27,58 @@ def test_a_meaningless_code_tokenization( farr_regex_lexer_fixture: FarrRegexLexer, ) -> None: """Checks the generated tokens of a meaningless program.""" - assert farr_regex_lexer_fixture.tokenize( - 'let mylist = {-1, 0, .2, +1, 2., 3.0}; for let i in mylist = { ' - 'break!; }\nprintln("Sorry, nothing from the list was printed!");' - ) == [ - TokenState(row=1, column=1, name='Variable', value='let'), - TokenState(row=1, column=5, name='Identifier', value='mylist'), - TokenState(row=1, column=12, name='Equal', value='='), - TokenState(row=1, column=14, name='LeftBrace', value='{'), - TokenState(row=1, column=15, name='Integer', value='-1'), - TokenState(row=1, column=17, name='Comma', value=','), - TokenState(row=1, column=19, name='Integer', value='0'), - TokenState(row=1, column=20, name='Comma', value=','), - TokenState(row=1, column=22, name='Float', value='.2'), - TokenState(row=1, column=24, name='Comma', value=','), - TokenState(row=1, column=26, name='Integer', value='+1'), - TokenState(row=1, column=28, name='Comma', value=','), - TokenState(row=1, column=30, name='Float', value='2.'), - TokenState(row=1, column=32, name='Comma', value=','), - TokenState(row=1, column=34, name='Float', value='3.0'), - TokenState(row=1, column=37, name='RightBrace', value='}'), - TokenState(row=1, column=38, name='Semicolon', value=';'), - TokenState(row=1, column=40, name='For', value='for'), - TokenState(row=1, column=44, name='Variable', value='let'), - TokenState(row=1, column=48, name='Identifier', value='i'), - TokenState(row=1, column=50, name='In', value='in'), - TokenState(row=1, column=53, name='Identifier', value='mylist'), - TokenState(row=1, column=60, name='Equal', value='='), - TokenState(row=1, column=62, name='LeftBrace', value='{'), - TokenState(row=1, column=64, name='Break', value='break!'), - TokenState(row=1, column=70, name='Semicolon', value=';'), - TokenState(row=1, column=72, name='RightBrace', value='}'), - TokenState(row=2, column=1, name='Identifier', value='println'), - TokenState(row=2, column=8, name='LeftParenthesis', value='('), - TokenState( - row=2, - column=9, - name='String', - value='"Sorry, nothing from the list was printed!"', - ), - TokenState(row=2, column=52, name='RightParenthesis', value=')'), - TokenState(row=2, column=53, name='Semicolon', value=';'), - ] + assert ( + farr_regex_lexer_fixture.tokenize( + textwrap.dedent( + """ + let mylist = {-1, 0, .2, +1, 2., 3.0}; + for let i in mylist = { + break!; + println("Sorry, nothing from the list was printed!"); + } + """ + ) + ) + == [ + TokenState(row=2, column=1, name='Variable', value='let'), + TokenState(row=2, column=5, name='Identifier', value='mylist'), + TokenState(row=2, column=12, name='Equal', value='='), + TokenState(row=2, column=14, name='LeftBrace', value='{'), + TokenState(row=2, column=15, name='Integer', value='-1'), + TokenState(row=2, column=17, name='Comma', value=','), + TokenState(row=2, column=19, name='Integer', value='0'), + TokenState(row=2, column=20, name='Comma', value=','), + TokenState(row=2, column=22, name='Float', value='.2'), + TokenState(row=2, column=24, name='Comma', value=','), + TokenState(row=2, column=26, name='Integer', value='+1'), + TokenState(row=2, column=28, name='Comma', value=','), + TokenState(row=2, column=30, name='Float', value='2.'), + TokenState(row=2, column=32, name='Comma', value=','), + TokenState(row=2, column=34, name='Float', value='3.0'), + TokenState(row=2, column=37, name='RightBrace', value='}'), + TokenState(row=2, column=38, name='Semicolon', value=';'), + TokenState(row=3, column=1, name='For', value='for'), + TokenState(row=3, column=5, name='Variable', value='let'), + TokenState(row=3, column=9, name='Identifier', value='i'), + TokenState(row=3, column=11, name='In', value='in'), + TokenState(row=3, column=14, name='Identifier', value='mylist'), + TokenState(row=3, column=21, name='Equal', value='='), + TokenState(row=3, column=23, name='LeftBrace', value='{'), + TokenState(row=4, column=3, name='Break', value='break!'), + TokenState(row=4, column=9, name='Semicolon', value=';'), + TokenState(row=5, column=3, name='Identifier', value='println'), + TokenState(row=5, column=10, name='LeftParenthesis', value='('), + TokenState( + row=5, + column=11, + name='String', + value='"Sorry, nothing from the list was printed!"', + ), + TokenState(row=5, column=54, name='RightParenthesis', value=')'), + TokenState(row=5, column=55, name='Semicolon', value=';'), + TokenState(row=6, column=1, name='RightBrace', value='}'), + ] + ) def test_tokenization_output_types( @@ -76,7 +89,13 @@ def test_tokenization_output_types( map( lambda x: isinstance(x, TokenState), farr_regex_lexer_fixture.tokenize( - 'fn add_one(let x) = { return! + x 1; }' + textwrap.dedent( + """ + fn add_one(let x) = { + return! + x 1; + } + """ + ) ), ) ) diff --git a/tests/test_parser.py b/tests/test_parser.py index df87f16..e494f64 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -3,6 +3,8 @@ # We understand that beauty is not objective... # https://github.com/sheikhartin/farr +import textwrap + import pytest from farr.lexer import FarrRegexLexer @@ -16,8 +18,8 @@ IdentifierNode, RangeNode, ItemizedExpressionNode, - ChainedExpressionsNode, CallNode, + GroupedExpressionNode, PostIncrementNode, PostDecrementNode, ArithmeticOperationNode, @@ -42,77 +44,90 @@ def test_even_number_filter_syntax_tree( assert ( farr_parser_fixture.parse( farr_regex_lexer_fixture.tokenize( - 'let n = 30; for let i in [1..n] = { ' - 'if % i 2 == 0 = { println(i); } }' + textwrap.dedent( + """ + let n = 30; + for let i in [1..n] = { + if ((% i 2) == 0) = { + println(i); + } + } + """ + ) ) ) - ) == ModuleNode( - body=[ - VariableDeclarationNode( - identifier=IdentifierNode(row=1, column=5, value='n'), - expression=IntegerNode(row=1, column=9, value='30'), - ), - ForNode( - condition=RangeNode( - from_=IntegerNode(row=1, column=27, value='1'), - to=IdentifierNode(row=1, column=30, value='n'), - by=None, + == ModuleNode( + body=[ + VariableDeclarationNode( + identifier=IdentifierNode(row=2, column=5, value='n'), + expression=IntegerNode(row=2, column=9, value='30'), ), - body=BlockNode( - body=[ - IfNode( - condition=RelationalOperationNode( - row=1, - column=46, - operator='EqualEqual', - left=ArithmeticOperationNode( - row=1, - column=40, - operator='Modulus', - left=IdentifierNode( - row=1, column=42, value='i' + ForNode( + condition=RangeNode( + from_=IntegerNode(row=3, column=15, value='1'), + to=IdentifierNode(row=3, column=18, value='n'), + by=None, + ), + body=BlockNode( + body=[ + IfNode( + condition=RelationalOperationNode( + row=4, + column=15, + operator='EqualEqual', + left=GroupedExpressionNode( + expression=ArithmeticOperationNode( + row=4, + column=8, + operator='Modulus', + left=IdentifierNode( + row=4, column=10, value='i' + ), + right=IntegerNode( + row=4, column=12, value='2' + ), + ) ), right=IntegerNode( - row=1, column=44, value='2' + row=4, column=18, value='0' ), ), - right=IntegerNode(row=1, column=49, value='0'), - ), - body=BlockNode( - body=[ - CallNode( - invoke=IdentifierNode( - row=1, - column=55, - value='println', - ), - args=ItemizedExpressionNode( - items=[ - IdentifierNode( - row=1, column=63, value='i' - ) - ] - ), - ) - ] - ), - orelse=None, - ) - ] - ), - orelse=None, - initial=ItemizedExpressionNode( - items=[ - VariableDeclarationNode( - identifier=IdentifierNode( - row=1, column=21, value='i' - ), - expression=None, - ) - ] + body=BlockNode( + body=[ + CallNode( + invoke=IdentifierNode( + row=5, column=5, value='println' + ), + args=ItemizedExpressionNode( + items=[ + IdentifierNode( + row=5, + column=13, + value='i', + ) + ] + ), + ) + ] + ), + orelse=None, + ) + ] + ), + orelse=None, + initial=ItemizedExpressionNode( + items=[ + VariableDeclarationNode( + identifier=IdentifierNode( + row=3, column=9, value='i' + ), + expression=None, + ) + ] + ), ), - ), - ] + ] + ) ) @@ -124,63 +139,75 @@ def test_factorial_calculator_syntax_tree( assert ( farr_parser_fixture.parse( farr_regex_lexer_fixture.tokenize( - 'let i = 5; let result = 1; while i >= 1 = { result *= i; ' - 'i--; } println(result);' + textwrap.dedent( + """ + let i = 5; + let result = 1; + while i >= 1 = { + result *= i; + i--; + } + println(result); + """ + ) ) ) - ) == ModuleNode( - body=[ - VariableDeclarationNode( - identifier=IdentifierNode(row=1, column=5, value='i'), - expression=IntegerNode(row=1, column=9, value='5'), - ), - VariableDeclarationNode( - identifier=IdentifierNode(row=1, column=16, value='result'), - expression=IntegerNode(row=1, column=25, value='1'), - ), - WhileNode( - condition=RelationalOperationNode( - row=1, - column=36, - operator='GreaterThanOrEqual', - left=IdentifierNode(row=1, column=34, value='i'), - right=IntegerNode(row=1, column=39, value='1'), + == ModuleNode( + body=[ + VariableDeclarationNode( + identifier=IdentifierNode(row=2, column=5, value='i'), + expression=IntegerNode(row=2, column=9, value='5'), ), - body=BlockNode( - body=[ - MultiplyAssignmentNode( - variables=ItemizedExpressionNode( - items=[ - IdentifierNode( - row=1, column=45, value='result' - ) - ] - ), - expression=IdentifierNode( - row=1, column=55, value='i' + VariableDeclarationNode( + identifier=IdentifierNode(row=3, column=5, value='result'), + expression=IntegerNode(row=3, column=14, value='1'), + ), + WhileNode( + condition=RelationalOperationNode( + row=4, + column=9, + operator='GreaterThanOrEqual', + left=IdentifierNode(row=4, column=7, value='i'), + right=IntegerNode(row=4, column=12, value='1'), + ), + body=BlockNode( + body=[ + MultiplyAssignmentNode( + references=ItemizedExpressionNode( + items=[ + IdentifierNode( + row=5, column=3, value='result' + ) + ] + ), + expression=IdentifierNode( + row=5, column=13, value='i' + ), ), - ), - PostDecrementNode( - row=1, - column=59, - operator=None, - operand=ItemizedExpressionNode( - items=[ - IdentifierNode(row=1, column=58, value='i') - ] + PostDecrementNode( + row=6, + column=4, + operator=None, + operand=ItemizedExpressionNode( + items=[ + IdentifierNode( + row=6, column=3, value='i' + ) + ] + ), ), - ), - ] + ] + ), + orelse=None, ), - orelse=None, - ), - CallNode( - invoke=IdentifierNode(row=1, column=65, value='println'), - args=ItemizedExpressionNode( - items=[IdentifierNode(row=1, column=73, value='result')] + CallNode( + invoke=IdentifierNode(row=8, column=1, value='println'), + args=ItemizedExpressionNode( + items=[IdentifierNode(row=8, column=9, value='result')] + ), ), - ), - ] + ] + ) ) @@ -192,37 +219,46 @@ def test_zero_division_error_handling_syntax_tree( assert ( farr_parser_fixture.parse( farr_regex_lexer_fixture.tokenize( - 'try = { / 0 5; } catch ZeroDivisionError = { ...; }' + textwrap.dedent( + """ + try = { + / 10 0; + } catch ArithmeticError = { + ...; + } + """ + ) ) ) - ) == ModuleNode( - body=[ - TryNode( - body=BlockNode( - body=[ - ArithmeticOperationNode( - row=1, - column=9, - operator='Divide', - left=IntegerNode(row=1, column=11, value='0'), - right=IntegerNode(row=1, column=13, value='5'), - ) - ] - ), - catch=CatchNode( - excepts=ItemizedExpressionNode( - items=[ - IdentifierNode( - row=1, column=24, value='ZeroDivisionError' + == ModuleNode( + body=[ + TryNode( + body=BlockNode( + body=[ + ArithmeticOperationNode( + row=3, + column=3, + operator='Divide', + left=IntegerNode(row=3, column=5, value='10'), + right=IntegerNode(row=3, column=8, value='0'), ) ] ), - as_=None, - body=BlockNode(body=[PassNode(row=1, column=46)]), - orelse=None, - ), - ) - ] + catch=CatchNode( + excepts=ItemizedExpressionNode( + items=[ + IdentifierNode( + row=4, column=9, value='ArithmeticError' + ) + ] + ), + as_=None, + body=BlockNode(body=[PassNode(row=5, column=3)]), + orelse=None, + ), + ) + ] + ) ) @@ -234,100 +270,85 @@ def test_birthday_greetings_sender_syntax_tree( assert ( farr_parser_fixture.parse( farr_regex_lexer_fixture.tokenize( - '\n\nstruct Person = { let full_name, let age } fn Person::' - 'happy_birthday!() = { age++; println("Happy, birthday!"); }\n' - 'let person = Person("John Doe", 99);\nperson.happy_birthday!();' + textwrap.dedent( + """ + struct Person = { + let full_name, + let age + } + fn Person::happy_birthday!() = { + age++; + println("Happy, birthday!"); + } + """ + ) ) ) - ) == ModuleNode( - body=[ - StructDefinitionNode( - identifier=IdentifierNode(row=3, column=8, value='Person'), - body=BlockNode( - body=[ - ItemizedExpressionNode( - items=[ - VariableDeclarationNode( - identifier=IdentifierNode( - row=3, column=23, value='full_name' + == ModuleNode( + body=[ + StructDefinitionNode( + identifier=IdentifierNode(row=2, column=8, value='Person'), + body=BlockNode( + body=[ + ItemizedExpressionNode( + items=[ + VariableDeclarationNode( + identifier=IdentifierNode( + row=3, column=7, value='full_name' + ), + expression=None, ), - expression=None, - ), - VariableDeclarationNode( - identifier=IdentifierNode( - row=3, column=38, value='age' + VariableDeclarationNode( + identifier=IdentifierNode( + row=4, column=7, value='age' + ), + expression=None, ), - expression=None, - ), - ] - ) - ] - ), - parents=None, - ), - MemberFunctionDefinitionNode( - identifier=IdentifierNode( - row=3, column=55, value='happy_birthday_e' - ), - body=BlockNode( - body=[ - PostIncrementNode( - row=3, - column=80, - operator=None, - operand=ItemizedExpressionNode( - items=[ - IdentifierNode( - row=3, column=77, value='age' - ) ] + ) + ] + ), + parents=None, + ), + MemberFunctionDefinitionNode( + identifier=IdentifierNode( + row=6, column=12, value='happy_birthday_e' + ), + body=BlockNode( + body=[ + PostIncrementNode( + row=7, + column=6, + operator=None, + operand=ItemizedExpressionNode( + items=[ + IdentifierNode( + row=7, column=3, value='age' + ) + ] + ), ), - ), - CallNode( - invoke=IdentifierNode( - row=3, column=84, value='println' - ), - args=ItemizedExpressionNode( - items=[ - StringNode( - row=3, - column=92, - value='"Happy, ' 'birthday!"', - ) - ] + CallNode( + invoke=IdentifierNode( + row=8, column=3, value='println' + ), + args=ItemizedExpressionNode( + items=[ + StringNode( + row=8, + column=11, + value='"Happy, birthday!"', + ) + ] + ), ), - ), - ] - ), - params=ItemizedExpressionNode(items=[]), - struct=IdentifierNode(row=3, column=47, value='Person'), - ), - VariableDeclarationNode( - identifier=IdentifierNode(row=4, column=5, value='person'), - expression=CallNode( - invoke=IdentifierNode(row=4, column=14, value='Person'), - args=ItemizedExpressionNode( - items=[ - StringNode(row=4, column=21, value='"John ' 'Doe"'), - IntegerNode(row=4, column=33, value='99'), ] ), + params=ItemizedExpressionNode(items=[]), + struct=IdentifierNode(row=6, column=4, value='Person'), ), - ), - ChainedExpressionsNode( - expressions=ItemizedExpressionNode( - items=[ - IdentifierNode(row=5, column=1, value='person'), - CallNode( - invoke=IdentifierNode( - row=5, column=8, value='happy_birthday_e' - ), - args=ItemizedExpressionNode(items=[]), - ), - ] - ) - ), - ] + ] + ) )