diff --git a/smnp/ast/node/expression.py b/smnp/ast/node/expression.py index c1c4382..950ccc8 100644 --- a/smnp/ast/node/expression.py +++ b/smnp/ast/node/expression.py @@ -25,7 +25,7 @@ class Or(BinaryOperator): class Loop(BinaryOperator): def __init__(self, pos): super().__init__(pos) - self.children.append(NoneNode()) + self.children.extend([NoneNode(), NoneNode()]) @property def parameters(self): @@ -35,13 +35,22 @@ class Loop(BinaryOperator): def parameters(self, value): self[3] = value + @property + def filter(self): + return self[4] + + @filter.setter + def filter(self, value): + self[4] = value + @classmethod - def loop(cls, left, parameters, operator, right): + def loop(cls, left, parameters, operator, right, filter): node = cls(left.pos) node.left = left node.parameters = parameters node.operator = operator node.right = right + node.filter = filter return node @@ -94,11 +103,19 @@ def LoopParser(input): name="loop parameters" ) + loopFilter = Parser.allOf( + Parser.terminal(TokenType.PERCENT), + Parser.doAssert(ExpressionWithoutLoopParser, "filter as bool expression"), + createNode=lambda percent, expr: expr, + name="loop filter" + ) + return Parser.allOf( ExpressionWithoutLoopParser, Parser.optional(loopParameters), Parser.terminal(TokenType.DASH, createNode=Operator.withValue), StatementParser, + Parser.optional(loopFilter), createNode=Loop.loop, name="dash-loop" )(input) diff --git a/smnp/runtime/evaluators/loop.py b/smnp/runtime/evaluators/loop.py index e9392bd..eafb343 100644 --- a/smnp/runtime/evaluators/loop.py +++ b/smnp/runtime/evaluators/loop.py @@ -20,7 +20,7 @@ class LoopEvaluator(Evaluator): Type.BOOL: cls.boolEvaluator, Type.LIST: cls.listEvaluator, Type.MAP: cls.mapEvaluator - }[iterator.type](node, environment, iterator, parameters) + }[iterator.type](node, environment, iterator, parameters, node.filter) environment.scopes.pop(-1) except KeyError: @@ -29,7 +29,7 @@ class LoopEvaluator(Evaluator): return Type.list(output) @classmethod - def numberEvaluator(cls, node, environment, evaluatedIterator, parameters): + def numberEvaluator(cls, node, environment, evaluatedIterator, parameters, filter): output = [] @@ -40,14 +40,28 @@ class LoopEvaluator(Evaluator): if len(parameters) > 0: environment.scopes[-1][parameters[0]] = Type.integer(i) - output.append(evaluate(node.right, environment).value) + if cls.doFilter(filter, environment): + output.append(evaluate(node.right, environment).value) return output @classmethod - def boolEvaluator(cls, node, environment, evaluatedIterator, parameters): + def doFilter(cls, filter, environment): + if type(filter) is not NoneNode: + evaluation = expressionEvaluator(doAssert=True)(filter, environment).value + if evaluation.type != Type.BOOL: + raise RuntimeException(f"Expected {Type.BOOL.name.lower()} as filter expression, found {evaluation.type.name.lower()}", filter.pos) + + return evaluation.value + + return True + + + + @classmethod + def boolEvaluator(cls, node, environment, evaluatedIterator, parameters, filter): output = [] if len(parameters) > 0: @@ -55,13 +69,14 @@ class LoopEvaluator(Evaluator): condition = evaluatedIterator while condition.value: - output.append(evaluate(node.right, environment).value) + if cls.doFilter(filter, environment): + output.append(evaluate(node.right, environment).value) condition = expressionEvaluator(doAssert=True)(node.left, environment).value return output @classmethod - def listEvaluator(cls, node, environment, evaluatedIterator, parameters): + def listEvaluator(cls, node, environment, evaluatedIterator, parameters, filter): output = [] if len(parameters) > 2: @@ -74,13 +89,14 @@ class LoopEvaluator(Evaluator): environment.scopes[-1][parameters[0]] = Type.integer(i) environment.scopes[-1][parameters[1]] = value - output.append(evaluate(node.right, environment).value) + if cls.doFilter(filter, environment): + output.append(evaluate(node.right, environment).value) return output @classmethod - def mapEvaluator(cls, node, environment, evaluatedIterator, parameters): + def mapEvaluator(cls, node, environment, evaluatedIterator, parameters, filter): output = [] if len(parameters) > 3: @@ -99,6 +115,7 @@ class LoopEvaluator(Evaluator): environment.scopes[-1][parameters[2]] = value i += 1 - output.append(evaluate(node.right, environment).value) + if cls.doFilter(filter, environment): + output.append(evaluate(node.right, environment).value) return output diff --git a/smnp/token/tokenizer.py b/smnp/token/tokenizer.py index 7c91ab1..277badb 100644 --- a/smnp/token/tokenizer.py +++ b/smnp/token/tokenizer.py @@ -29,6 +29,7 @@ tokenizers = ( defaultTokenizer(TokenType.CLOSE_ANGLE), defaultTokenizer(TokenType.SEMICOLON), defaultTokenizer(TokenType.ASTERISK), + defaultTokenizer(TokenType.PERCENT), defaultTokenizer(TokenType.ASSIGN), defaultTokenizer(TokenType.COMMA), defaultTokenizer(TokenType.SLASH), diff --git a/smnp/token/type.py b/smnp/token/type.py index 91b4811..c14d1c5 100644 --- a/smnp/token/type.py +++ b/smnp/token/type.py @@ -14,6 +14,7 @@ class TokenType(Enum): CLOSE_ANGLE = '>' SEMICOLON = ';' ASTERISK = '*' + PERCENT = '%' ASSIGN = '=' ARROW = '->' COMMA = ',' @@ -44,7 +45,6 @@ class TokenType(Enum): AS = 'as' IDENTIFIER = 'identifier' COMMENT = 'comment' - PERCENT = 'percent' @property def key(self):