diff --git a/smnp/ast/node/atom.py b/smnp/ast/node/atom.py index 4b53b6d..95eda19 100644 --- a/smnp/ast/node/atom.py +++ b/smnp/ast/node/atom.py @@ -78,8 +78,18 @@ def AtomParser(input): from smnp.ast.node.identifier import IdentifierParser from smnp.ast.node.list import ListParser from smnp.ast.node.map import MapParser + from smnp.ast.node.expression import ExpressionParser + + parentheses = Parser.allOf( + Parser.terminal(TokenType.OPEN_PAREN), + Parser.doAssert(ExpressionParser, "expression"), + Parser.terminal(TokenType.CLOSE_PAREN), + createNode=lambda open, expr, close: expr, + name="grouping parentheses" + ) return Parser.oneOf( + parentheses, LiteralParser, IdentifierParser, ListParser, 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/ast/node/factor.py b/smnp/ast/node/factor.py index 404a4f3..7d9ac98 100644 --- a/smnp/ast/node/factor.py +++ b/smnp/ast/node/factor.py @@ -11,26 +11,11 @@ class Power(BinaryOperator): pass def FactorParser(input): - from smnp.ast.node.expression import ExpressionParser - - parentheses = Parser.allOf( - Parser.terminal(TokenType.OPEN_PAREN), - Parser.doAssert(ExpressionParser, "expression"), - Parser.terminal(TokenType.CLOSE_PAREN), - createNode=lambda open, expr, close: expr, - name="grouping parentheses" - ) - - factorOperands = Parser.oneOf( - parentheses, - UnitParser, - name="factor operands" - ) powerFactor = Parser.leftAssociativeOperatorParser( - factorOperands, + UnitParser, [TokenType.DOUBLE_ASTERISK], - factorOperands, + UnitParser, lambda left, op, right: Power.withValues(left, op, right), name="power operator" ) diff --git a/smnp/environment/environment.py b/smnp/environment/environment.py index 3a34531..786f15d 100644 --- a/smnp/environment/environment.py +++ b/smnp/environment/environment.py @@ -81,7 +81,7 @@ class Environment(): if function.name == name: signatureCheckresult = function.signature.check(args) if signatureCheckresult[0]: - self.scopes.append(function.defaultArgs) + self.appendScope(function.defaultArgs) self.scopes[-1].update({ argName: argValue for argName, argValue in zip(function.arguments, list(signatureCheckresult[1:])) }) self.callStack.append(CallStackItem(name)) result = Type.void() @@ -90,7 +90,7 @@ class Environment(): except Return as r: result = r.value self.callStack.pop(-1) - self.scopes.pop(-1) + self.popScope(mergeVariables=False) return (True, result) raise IllegalFunctionInvocationException(f"{function.name}{function.signature.string}", f"{name}{argsTypesToString(args)}") return (False, None) @@ -140,6 +140,17 @@ class Environment(): else: return scope + def appendScope(self, variables=None): + if variables is None: + variables = {} + + self.scopes.append(variables) + + def popScope(self, mergeVariables=True): + lastScope = self.scopes.pop(-1) + if mergeVariables: + self.scopes[-1].update(lastScope) + def scopesToString(self): return "Scopes:\n" + ("\n".join([ f" [{i}]: {scope}" for i, scope in enumerate(self.scopes) ])) diff --git a/smnp/module/iterable/__init__.py b/smnp/module/iterable/__init__.py index d302af9..da210ea 100644 --- a/smnp/module/iterable/__init__.py +++ b/smnp/module/iterable/__init__.py @@ -1,4 +1,4 @@ -from smnp.module.iterable.function import combine, map, get +from smnp.module.iterable.function import map, get -functions = [ combine.function, map.function ] +functions = [ map.function ] methods = [ get.function ] \ No newline at end of file diff --git a/smnp/module/iterable/function/combine.py b/smnp/module/iterable/function/combine.py deleted file mode 100644 index e007909..0000000 --- a/smnp/module/iterable/function/combine.py +++ /dev/null @@ -1,18 +0,0 @@ -from functools import reduce - -from smnp.function.model import Function -from smnp.function.signature import varargSignature -from smnp.type.model import Type -from smnp.type.signature.matcher.type import ofTypes - -_signature = varargSignature(ofTypes(Type.LIST)) -def _function(env, vararg): - if len(vararg) == 1: - return vararg[0] - - combined = reduce(lambda x, y: x.value + y.value, vararg) - return Type.list(combined) - - -function = Function(_signature, _function, 'combine') - diff --git a/smnp/module/iterable/function/range.py b/smnp/module/iterable/function/range.py deleted file mode 100644 index 8f0198e..0000000 --- a/smnp/module/iterable/function/range.py +++ /dev/null @@ -1,36 +0,0 @@ -from smnp.function.model import CombinedFunction, Function -from smnp.function.signature import signature -from smnp.note.model import Note -from smnp.type.model import Type -from smnp.type.signature.matcher.type import ofType - -_signature1 = signature(ofType(Type.INTEGER)) -def _function1(env, upper): - return Type.list([ Type.integer(i) for i in range(upper.value + 1)]) - - -_signature2 = signature(ofType(Type.INTEGER), ofType(Type.INTEGER)) -def _function2(env, lower, upper): - return Type.list([ Type.integer(i) for i in range(lower.value, upper.value + 1)]) - - -_signature3 = signature(ofType(Type.INTEGER), ofType(Type.INTEGER), ofType(Type.INTEGER)) -def _function3(env, lower, upper, step): - return Type.list([ Type.integer(i) for i in range(lower.value, upper.value + 1, step.value)]) - - -_signature4 = signature(ofType(Type.NOTE), ofType(Type.NOTE)) -def _function4(env, lower, upper): - return Type.list([Type.note(n) for n in Note.range(lower.value, upper.value)]) - - -# TODO -# signature5 = range(note lower, note upper, integer step) OR step = "diatonic" | "chromatic" | "augmented" | "diminish" - -function = CombinedFunction( - 'range', - Function(_signature1, _function1), - Function(_signature2, _function2), - Function(_signature3, _function3), - Function(_signature4, _function4), -) diff --git a/smnp/module/string/__init__.py b/smnp/module/string/__init__.py index 3ba0e49..1910f8f 100644 --- a/smnp/module/string/__init__.py +++ b/smnp/module/string/__init__.py @@ -1,4 +1,4 @@ -from smnp.module.string.function import concat, stringify +from smnp.module.string.function import stringify -functions = [ concat.function ] +functions = [] methods = [ stringify.function ] \ No newline at end of file diff --git a/smnp/module/string/function/concat.py b/smnp/module/string/function/concat.py deleted file mode 100644 index e82b892..0000000 --- a/smnp/module/string/function/concat.py +++ /dev/null @@ -1,11 +0,0 @@ -from smnp.function.model import Function -from smnp.function.signature import varargSignature -from smnp.type.model import Type -from smnp.type.signature.matcher.type import ofType - -_signature = varargSignature(ofType(Type.STRING)) -def _function(env, vararg): - return Type.string("".join([ arg.value for arg in vararg ])) - - -function = Function(_signature, _function, 'concat') \ No newline at end of file diff --git a/smnp/module/system/__init__.py b/smnp/module/system/__init__.py index ced0515..28d9297 100644 --- a/smnp/module/system/__init__.py +++ b/smnp/module/system/__init__.py @@ -1,4 +1,4 @@ -from smnp.module.system.function import sleep, display, displayln, debug, exit, type +from smnp.module.system.function import sleep, display, displayln, debug, exit, type, read -functions = [ debug.function, display.function, displayln.function, exit.function, sleep.function, type.function ] +functions = [ debug.function, display.function, displayln.function, exit.function, sleep.function, type.function, read.function ] methods = [] \ No newline at end of file diff --git a/smnp/module/system/function/read.py b/smnp/module/system/function/read.py index e24877f..07d67f9 100644 --- a/smnp/module/system/function/read.py +++ b/smnp/module/system/function/read.py @@ -1,3 +1,72 @@ +from smnp.error.runtime import RuntimeException +from smnp.function.model import CombinedFunction, Function +from smnp.function.signature import signature +from smnp.token.tokenizers.bool import boolTokenizer +from smnp.token.tokenizers.note import noteTokenizer +from smnp.type.model import Type +from smnp.type.signature.matcher.type import ofType + +_signature1 = signature() +def _function1(env): + value = input() + return Type.string(value) + + +_signature2 = signature(ofType(Type.STRING)) +def _function2(env, prompt): + print(prompt.value, end="") + value = input() + return Type.string(value) + + +_signature3 = signature(ofType(Type.TYPE)) +def _function3(env, type): + value = input() + return getValueAccordingToType(value, type) + + +def getValueAccordingToType(value, type): + try: + if type.value == Type.STRING: + return Type.string(value) + + if type.value == Type.INTEGER: + return Type.integer(int(value)) + + if type.value == Type.BOOL: + consumedChars, token = boolTokenizer(value, 0, 0) + if consumedChars > 0: + return Type.bool(token.value) + + return ValueError() + + if type.value == Type.NOTE: + consumedChars, token = noteTokenizer(value, 0, 0) + if consumedChars > 0: + return Type.note(token.value) + + raise ValueError() + + raise RuntimeException(f"Type {type.value.name.lower()} is not suuported", None) + + except ValueError: + raise RuntimeException(f"Invalid value '{value}' for type {type.value.name.lower()}", None) + + +_signature4 = signature(ofType(Type.STRING), ofType(Type.TYPE)) +def _function4(env, prompt, type): + print(prompt.value, end="") + value = input() + return getValueAccordingToType(value, type) + + +function = CombinedFunction( + 'read', + Function(_signature1, _function1), + Function(_signature2, _function2), + Function(_signature3, _function3), + Function(_signature4, _function4) +) # TODO read function # def read(args, env): diff --git a/smnp/runtime/evaluators/assignment.py b/smnp/runtime/evaluators/assignment.py index f9e9234..1edfe28 100644 --- a/smnp/runtime/evaluators/assignment.py +++ b/smnp/runtime/evaluators/assignment.py @@ -8,11 +8,7 @@ class AssignmentEvaluator(Evaluator): def evaluator(cls, node, environment): target = node.left.value value = expressionEvaluator(doAssert=True)(node.right, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult - scopeOfExistingVariable = environment.findVariableScope(target) - if scopeOfExistingVariable is None: - environment.scopes[-1][target] = value - else: - scopeOfExistingVariable[target] = value + environment.scopes[-1][target] = value return value diff --git a/smnp/runtime/evaluators/asterisk.py b/smnp/runtime/evaluators/asterisk.py deleted file mode 100644 index 2d4223e..0000000 --- a/smnp/runtime/evaluators/asterisk.py +++ /dev/null @@ -1,104 +0,0 @@ -from smnp.ast.node.identifier import Identifier -from smnp.runtime.evaluator import evaluate, Evaluator, EvaluationResult -from smnp.runtime.evaluators.expression import expressionEvaluator -from smnp.type.model import Type - - -class AsteriskEvaluator(Evaluator): - - @classmethod - def evaluator(cls, node, environment): - iterator = expressionEvaluator(doAssert=True)(node.iterator, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult - return Evaluator.oneOf( - cls._numberIteratorAsteriskEvaluator(iterator), - cls._listIteratorAsteriskEvaluator(iterator), - cls._mapIteratorAsteriskEvaluator(iterator) - )(node, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult - - @classmethod - def _numberIteratorAsteriskEvaluator(cls, evaluatedIterator): - def evaluator(node, environment): - if evaluatedIterator.type == Type.INTEGER: - results = [] - automaticVariable = cls._automaticNamedVariable(node.iterator, environment, "_") - for i in range(evaluatedIterator.value): - environment.scopes[-1][automaticVariable] = Type.integer(i + 1) - result = evaluate(node.statement, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult - if result is None or result.type == Type.VOID: - results = None - - if results is not None: - results.append(result) - - del environment.scopes[-1][automaticVariable] - - return EvaluationResult.OK(Type.list(results).decompose() if results is not None else Type.void()) - - return EvaluationResult.FAIL() - - return evaluator - - @classmethod - def _automaticNamedVariable(cls, iteratorNode, environment, prefix=''): - if type(iteratorNode) == Identifier: - return cls._automaticVariableName(environment, prefix, iteratorNode.value, False) - else: - return cls._automaticVariableName(environment, prefix, '', True) - - @classmethod - def _automaticVariableName(cls, environment, prefix='', suffix='', startWithNumber=False): - number = 1 if startWithNumber else '' - variableName = lambda x: f"{prefix}{x}{suffix}" - while environment.findVariableScope(variableName(number)) is not None: - if number == '': - number = 1 - else: - number += 1 - - return variableName(number) - - @classmethod - def _listIteratorAsteriskEvaluator(cls, evaluatedIterator): - def evaluator(node, environment): - if evaluatedIterator.type == Type.LIST: - results = [] - automaticVariableKey = cls._automaticNamedVariable(node.iterator, environment, "_") - automaticVariableValue = cls._automaticNamedVariable(node.iterator, environment, "__") - for i, v in enumerate(evaluatedIterator.value): - environment.scopes[-1][automaticVariableKey] = Type.integer(i + 1) - environment.scopes[-1][automaticVariableValue] = v - result = evaluate(node.statement, environment).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult - if result is not None and result.type != Type.VOID: - results.append(result) - - del environment.scopes[-1][automaticVariableKey] - del environment.scopes[-1][automaticVariableValue] - - return EvaluationResult.OK(Type.list(results).decompose()) - - return EvaluationResult.FAIL() - - return evaluator - - @classmethod - def _mapIteratorAsteriskEvaluator(cls, evaluatedIterator): - def evaluator(node, environment): - if evaluatedIterator.type == Type.MAP: - results = [] - automaticVariableKey = cls._automaticNamedVariable(node.iterator, environment, "_") - automaticVariableValue = cls._automaticNamedVariable(node.iterator, environment, "__") - for k, v in evaluatedIterator.value.items(): - environment.scopes[-1][automaticVariableKey] = k - environment.scopes[-1][automaticVariableValue] = v - result = evaluate(node.statement, environment).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult - if result is not None and result.type != Type.VOID: - results.append(result) - - del environment.scopes[-1][automaticVariableKey] - del environment.scopes[-1][automaticVariableValue] - - return EvaluationResult.OK(Type.list(results).decompose()) - - return EvaluationResult.FAIL() - - return evaluator \ No newline at end of file diff --git a/smnp/runtime/evaluators/block.py b/smnp/runtime/evaluators/block.py index c4f4c6e..af0b99b 100644 --- a/smnp/runtime/evaluators/block.py +++ b/smnp/runtime/evaluators/block.py @@ -5,12 +5,12 @@ class BlockEvaluator(Evaluator): @classmethod def evaluator(cls, node, environment): - environment.scopes.append({}) + environment.appendScope() for child in node.children: evaluate(child, environment) - environment.scopes.pop(-1) + environment.popScope() # # def evaluateBlock(block, environment): diff --git a/smnp/runtime/evaluators/loop.py b/smnp/runtime/evaluators/loop.py index e9392bd..c386db8 100644 --- a/smnp/runtime/evaluators/loop.py +++ b/smnp/runtime/evaluators/loop.py @@ -13,23 +13,23 @@ class LoopEvaluator(Evaluator): parameters = [ identifier.value for identifier in node.parameters ] if type(node.parameters) != NoneNode() else [] try: - environment.scopes.append({}) + environment.appendScope() output = { Type.INTEGER: cls.numberEvaluator, 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) + environment.popScope() except KeyError: raise RuntimeException(f"The {iterator.type.name.lower()} type cannot stand as an iterator for loop statement", node.left.pos) 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):