diff --git a/smnp/ast/node/expression.py b/smnp/ast/node/expression.py index db939bc..c1c4382 100644 --- a/smnp/ast/node/expression.py +++ b/smnp/ast/node/expression.py @@ -1,4 +1,6 @@ -from smnp.ast.node.operator import BinaryOperator +from smnp.ast.node.model import Node +from smnp.ast.node.none import NoneNode +from smnp.ast.node.operator import BinaryOperator, Operator from smnp.ast.node.term import TermParser from smnp.ast.parser import Parser from smnp.token.type import TokenType @@ -20,7 +22,34 @@ class Or(BinaryOperator): pass -def ExpressionParser(input): +class Loop(BinaryOperator): + def __init__(self, pos): + super().__init__(pos) + self.children.append(NoneNode()) + + @property + def parameters(self): + return self[3] + + @parameters.setter + def parameters(self, value): + self[3] = value + + @classmethod + def loop(cls, left, parameters, operator, right): + node = cls(left.pos) + node.left = left + node.parameters = parameters + node.operator = operator + node.right = right + return node + + +class LoopParameters(Node): + pass + + +def ExpressionWithoutLoopParser(input): expr1 = Parser.leftAssociativeOperatorParser( TermParser, [TokenType.PLUS, TokenType.MINUS], @@ -49,3 +78,34 @@ def ExpressionParser(input): lambda left, op, right: Or.withValues(left, op, right) )(input) + +def LoopParser(input): + from smnp.ast.node.identifier import IdentifierLiteralParser + from smnp.ast.node.iterable import abstractIterableParser + from smnp.ast.node.statement import StatementParser + + loopParameters = Parser.allOf( + Parser.terminal(TokenType.AS), + Parser.oneOf( + Parser.wrap(IdentifierLiteralParser, lambda id: LoopParameters.withChildren([id], id.pos)), + abstractIterableParser(LoopParameters, TokenType.OPEN_PAREN, TokenType.CLOSE_PAREN, IdentifierLiteralParser) + ), + createNode=lambda asKeyword, parameters: parameters, + name="loop parameters" + ) + + return Parser.allOf( + ExpressionWithoutLoopParser, + Parser.optional(loopParameters), + Parser.terminal(TokenType.DASH, createNode=Operator.withValue), + StatementParser, + createNode=Loop.loop, + name="dash-loop" + )(input) + + +def ExpressionParser(input): + return Parser.oneOf( + LoopParser, + ExpressionWithoutLoopParser + )(input) diff --git a/smnp/ast/node/factor.py b/smnp/ast/node/factor.py index afc78e4..404a4f3 100644 --- a/smnp/ast/node/factor.py +++ b/smnp/ast/node/factor.py @@ -1,6 +1,3 @@ -from smnp.ast.node.iterable import abstractIterableParser -from smnp.ast.node.model import Node -from smnp.ast.node.none import NoneNode from smnp.ast.node.operator import BinaryOperator, Operator, UnaryOperator from smnp.ast.node.unit import UnitParser from smnp.ast.parser import Parser @@ -13,38 +10,8 @@ class NotOperator(UnaryOperator): class Power(BinaryOperator): pass - -class Loop(BinaryOperator): - def __init__(self, pos): - super().__init__(pos) - self.children.append(NoneNode()) - - @property - def parameters(self): - return self[3] - - @parameters.setter - def parameters(self, value): - self[3] = value - - @classmethod - def loop(cls, left, parameters, operator, right): - node = cls(left.pos) - node.left = left - node.parameters = parameters - node.operator = operator - node.right = right - return node - - -class LoopParameters(Node): - pass - - def FactorParser(input): from smnp.ast.node.expression import ExpressionParser - from smnp.ast.node.statement import StatementParser - from smnp.ast.node.identifier import IdentifierLiteralParser parentheses = Parser.allOf( Parser.terminal(TokenType.OPEN_PAREN), @@ -75,27 +42,7 @@ def FactorParser(input): name="not" ) - loopParameters = Parser.allOf( - Parser.terminal(TokenType.AS), - Parser.oneOf( - Parser.wrap(IdentifierLiteralParser, lambda id: LoopParameters.withChildren([id], id.pos)), - abstractIterableParser(LoopParameters, TokenType.OPEN_PAREN, TokenType.CLOSE_PAREN, IdentifierLiteralParser) - ), - createNode=lambda asKeyword, parameters: parameters, - name="loop parameters" - ) - - loopFactor = Parser.allOf( - powerFactor, - Parser.optional(loopParameters), - Parser.terminal(TokenType.DASH, createNode=Operator.withValue), - StatementParser, - createNode=Loop.loop, - name="dash-loop" - ) - return Parser.oneOf( - loopFactor, notOperator, powerFactor, name="factor" diff --git a/smnp/ast/node/function.py b/smnp/ast/node/function.py index c0fcfa3..148ba8c 100644 --- a/smnp/ast/node/function.py +++ b/smnp/ast/node/function.py @@ -1,4 +1,5 @@ from smnp.ast.node.block import BlockParser +from smnp.ast.node.expression import ExpressionParser from smnp.ast.node.identifier import IdentifierLiteralParser from smnp.ast.node.iterable import abstractIterableParser from smnp.ast.node.model import Node @@ -16,7 +17,7 @@ class Argument(Node): def __init__(self, pos): super().__init__(pos) - self.children = [NoneNode(), NoneNode(), False] + self.children = [NoneNode(), NoneNode(), False, NoneNode()] @property def type(self): @@ -48,6 +49,14 @@ class Argument(Node): self[2] = value + @property + def optionalValue(self): + return self[3] + + @optionalValue.setter + def optionalValue(self, value): + self[3] = value + class VarargNode(Node): pass @@ -90,7 +99,7 @@ class FunctionDefinition(Node): return node -def ArgumentParser(input): +def RegularArgumentParser(input): def createNode(type, variable, vararg): pos = type.pos if isinstance(type, Type) else variable.pos node = Argument(pos) @@ -104,6 +113,33 @@ def ArgumentParser(input): Parser.doAssert(IdentifierLiteralParser, "argument name"), Parser.optional(Parser.terminal(TokenType.DOTS, lambda val, pos: True)), createNode=createNode, + name="regular function argument" + )(input) + + +def OptionalArgumentParser(input): + def createNode(type, variable, _, optional): + pos = type.pos if isinstance(type, Type) else variable.pos + node = Argument(pos) + node.type = type + node.variable = variable + node.optionalValue = optional + return node + + return Parser.allOf( + Parser.optional(TypeParser), + Parser.doAssert(IdentifierLiteralParser, "argument name"), + Parser.terminal(TokenType.ASSIGN), + Parser.doAssert(ExpressionParser, "expression"), + createNode=createNode, + name="optional function argument" + )(input) + + +def ArgumentParser(input): + return Parser.oneOf( + OptionalArgumentParser, + RegularArgumentParser, name="function argument" )(input) diff --git a/smnp/ast/node/statement.py b/smnp/ast/node/statement.py index dc0d922..23d3996 100644 --- a/smnp/ast/node/statement.py +++ b/smnp/ast/node/statement.py @@ -1,5 +1,6 @@ from smnp.ast.node.model import Node from smnp.ast.parser import Parser +from smnp.token.type import TokenType class Statement(Node): @@ -13,11 +14,24 @@ def StatementParser(input): from smnp.ast.node.ret import ReturnParser from smnp.ast.node.throw import ThrowParser - return Parser.oneOf( - IfElseStatementParser, - ExpressionParser, - BlockParser, - ReturnParser, - ThrowParser, - name="statement" - )(input) + return withSemicolon( + Parser.oneOf( + IfElseStatementParser, + ExpressionParser, # Must be above BlockParser because of Map's syntax with curly braces + BlockParser, + ReturnParser, + ThrowParser, + name="statement" + ), optional=True)(input) + + +def withSemicolon(parser, optional=False, doAssert=False): + semicolonParser = Parser.optional(Parser.terminal(TokenType.SEMICOLON)) if optional else Parser.terminal( + TokenType.SEMICOLON, doAssert=doAssert) + + return Parser.allOf( + parser, + semicolonParser, + createNode=lambda stmt, semicolon: stmt, + name="semicolon" + "?" if optional else "" + ) diff --git a/smnp/environment/environment.py b/smnp/environment/environment.py index fff3799..3a34531 100644 --- a/smnp/environment/environment.py +++ b/smnp/environment/environment.py @@ -40,7 +40,8 @@ class Environment(): if method.typeSignature.check([object])[0] and method.name == name: #Todo sprawdzic sygnature typu signatureCheckresult = method.signature.check(args) if signatureCheckresult[0]: - self.scopes.append({argName: argValue for argName, argValue in zip(method.arguments, list(signatureCheckresult[1:]))}) + self.scopes.append(method.defaultArgs) + self.scopes[-1].update({argName: argValue for argName, argValue in zip(method.arguments, list(signatureCheckresult[1:]))}) self.scopes[-1][method.alias] = object self.callStack.append(CallStackItem(name)) result = Type.void() @@ -80,7 +81,8 @@ class Environment(): if function.name == name: signatureCheckresult = function.signature.check(args) if signatureCheckresult[0]: - self.scopes.append({ argName: argValue for argName, argValue in zip(function.arguments, list(signatureCheckresult[1:])) }) + self.scopes.append(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() try: @@ -93,11 +95,11 @@ class Environment(): raise IllegalFunctionInvocationException(f"{function.name}{function.signature.string}", f"{name}{argsTypesToString(args)}") return (False, None) - def addCustomFunction(self, name, signature, arguments, body): + def addCustomFunction(self, name, signature, arguments, body, defaultArguments): if len([fun for fun in self.functions + self.customFunctions if fun.name == name]) > 0: raise RuntimeException(f"Cannot redeclare function '{name}'", None) - self.customFunctions.append(CustomFunction(name, signature, arguments, body)) + self.customFunctions.append(CustomFunction(name, signature, arguments, body, defaultArguments)) # TODO: # There is still problem with checking existing of generic types, like lists: @@ -108,14 +110,14 @@ class Environment(): # function foo() { return 2 } # } # Then calling [1, 2, 3, 4].foo() will produce 1, when the second method is more suitable - def addCustomMethod(self, typeSignature, alias, name, signature, arguments, body): + def addCustomMethod(self, typeSignature, alias, name, signature, arguments, body, defaultArguments): if len([m for m in self.methods if m.name == name and m.signature.matchers[0] == typeSignature.matchers[0]]) > 0: raise RuntimeException(f"Cannot redeclare method '{name}' for type '{typeSignature.matchers[0]}'", None) if len([m for m in self.customMethods if m.name == name and m.typeSignature.matchers[0] == typeSignature.matchers[0]]) > 0: raise RuntimeException(f"Cannot redeclare method '{name}' for type '{typeSignature.matchers[0]}'", None) - self.customMethods.append(CustomMethod(typeSignature, alias, name, signature, arguments, body)) + self.customMethods.append(CustomMethod(typeSignature, alias, name, signature, arguments, body, defaultArguments)) def findVariable(self, name, type=None, pos=None): for scope in reversed(self.scopes): @@ -175,18 +177,20 @@ class CallStackItem: class CustomFunction: - def __init__(self, name, signature, arguments, body): + def __init__(self, name, signature, arguments, body, defaultArgs): self.name = name self.signature = signature self.arguments = arguments self.body = body + self.defaultArgs = defaultArgs class CustomMethod: - def __init__(self, typeSignature, alias, name, signature, arguments, body): + def __init__(self, typeSignature, alias, name, signature, arguments, body, defaultArgs): self.typeSignature = typeSignature self.alias = alias self.name = name self.signature = signature self.arguments = arguments - self.body = body \ No newline at end of file + self.body = body + self.defaultArgs = defaultArgs \ No newline at end of file diff --git a/smnp/function/signature.py b/smnp/function/signature.py index ad23caf..0c0bb9e 100644 --- a/smnp/function/signature.py +++ b/smnp/function/signature.py @@ -12,6 +12,9 @@ class Signature: def varargSignature(varargMatcher, *basicSignature, wrapVarargInValue=False): def check(args): + if any([ matcher.optional for matcher in [ varargMatcher, *basicSignature ]]): + raise RuntimeError("Vararg signature can't have optional arguments") + if len(basicSignature) > len(args): return doesNotMatchVararg(basicSignature) @@ -38,7 +41,7 @@ def doesNotMatchVararg(basicSignature): def signature(*signature): def check(args): - if len(signature) != len(args): + if len(args) > len(signature) or len(args) < len([ matcher for matcher in signature if not matcher.optional ]): return doesNotMatch(signature) for s, a in zip(signature, args): @@ -52,6 +55,12 @@ def signature(*signature): return Signature(check, string, signature) +def optional(matcher): + matcher.optional = True + matcher.string += "?" + return matcher + + def doesNotMatch(sign): return (False, *[None for n in sign]) diff --git a/smnp/main.py b/smnp/main.py index d0b5b55..94025a1 100644 --- a/smnp/main.py +++ b/smnp/main.py @@ -9,9 +9,6 @@ def main(): try: stdLibraryEnv = loadStandardLibrary() Interpreter.interpretFile(sys.argv[1], printTokens=False, printAst=False, execute=True, baseEnvironment=stdLibraryEnv) - #draft() - #tokens = tokenize(['function a(b...) { x+y}']) - #FunctionDefinitionParser(tokens).node.print() except SmnpException as e: print(e.message()) diff --git a/smnp/module/iterable/__init__.py b/smnp/module/iterable/__init__.py index a3fc3b2..d302af9 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, range, get +from smnp.module.iterable.function import combine, map, get -functions = [ combine.function, map.function, range.function ] +functions = [ combine.function, map.function ] methods = [ get.function ] \ No newline at end of file diff --git a/smnp/runtime/evaluators/expression.py b/smnp/runtime/evaluators/expression.py index e0758fa..46b70b6 100644 --- a/smnp/runtime/evaluators/expression.py +++ b/smnp/runtime/evaluators/expression.py @@ -1,6 +1,6 @@ from smnp.ast.node.condition import IfElse -from smnp.ast.node.expression import Sum, Relation, And, Or -from smnp.ast.node.factor import NotOperator, Power, Loop +from smnp.ast.node.expression import Sum, Relation, And, Or, Loop +from smnp.ast.node.factor import NotOperator, Power from smnp.ast.node.identifier import FunctionCall, Assignment from smnp.ast.node.term import Product from smnp.ast.node.unit import MinusOperator, Access @@ -50,3 +50,15 @@ def expressionEvaluator(doAssert=False): return evaluateExpression + + +def expressionEvaluatorWithMatcher(matcher, exceptionProvider, doAssert=True): + def evaluate(node, environment): + value = expressionEvaluator(doAssert=doAssert)(node, environment).value + + if not matcher.match(value): + raise exceptionProvider(value) + + return value + + return evaluate \ No newline at end of file diff --git a/smnp/runtime/evaluators/extend.py b/smnp/runtime/evaluators/extend.py index 546487c..7f8a546 100644 --- a/smnp/runtime/evaluators/extend.py +++ b/smnp/runtime/evaluators/extend.py @@ -32,8 +32,8 @@ class ExtendEvaluator(Evaluator): @classmethod def _evaluateMethodDefinition(cls, node, environment, type, variable): name = node.name.value - signature = argumentsNodeToMethodSignature(node.arguments) + defaultArguments, signature = argumentsNodeToMethodSignature(node.arguments, environment) arguments = [arg.variable.value for arg in node.arguments] body = node.body - environment.addCustomMethod(type, variable, name, signature, arguments, body) + environment.addCustomMethod(type, variable, name, signature, arguments, body, defaultArguments) diff --git a/smnp/runtime/evaluators/function.py b/smnp/runtime/evaluators/function.py index 378598a..6b59ca1 100644 --- a/smnp/runtime/evaluators/function.py +++ b/smnp/runtime/evaluators/function.py @@ -25,10 +25,10 @@ class FunctionDefinitionEvaluator(Evaluator): def evaluator(cls, node, environment): try: name = node.name.value - signature = argumentsNodeToMethodSignature(node.arguments) + defaultArguments, signature = argumentsNodeToMethodSignature(node.arguments, environment) arguments = [ arg.variable.value for arg in node.arguments ] body = node.body - environment.addCustomFunction(name, signature, arguments, body) + environment.addCustomFunction(name, signature, arguments, body, defaultArguments) except RuntimeException as e: raise updatePos(e, node) diff --git a/smnp/runtime/tools/signature.py b/smnp/runtime/tools/signature.py index baf8d35..3cef1b5 100644 --- a/smnp/runtime/tools/signature.py +++ b/smnp/runtime/tools/signature.py @@ -2,7 +2,8 @@ from smnp.ast.node import type as ast from smnp.ast.node.none import NoneNode from smnp.ast.node.type import TypesList from smnp.error.runtime import RuntimeException -from smnp.function.signature import varargSignature, signature +from smnp.function.signature import varargSignature, signature, optional +from smnp.runtime.evaluators.expression import expressionEvaluator, expressionEvaluatorWithMatcher from smnp.runtime.tools.error import updatePos from smnp.type.model import Type from smnp.type.signature.matcher.list import listOfMatchers @@ -10,11 +11,19 @@ from smnp.type.signature.matcher.map import mapOfMatchers from smnp.type.signature.matcher.type import allTypes, oneOf, ofType -def argumentsNodeToMethodSignature(node): +def evaluateDefaultArguments(node, environment): + defaultValues = { arg.variable.value: expressionEvaluator(doAssert=True)(arg.optionalValue, environment).value for arg in node.children if type(arg.optionalValue) != NoneNode } + + return defaultValues + + +def argumentsNodeToMethodSignature(node, environment): try: sign = [] vararg = None argumentsCount = len(node.children) + checkPositionOfOptionalArguments(node) + defaultArgs = {} for i, child in enumerate(node.children): matchers = { ast.Type: (lambda c: c.type, typeMatcher), @@ -22,19 +31,34 @@ def argumentsNodeToMethodSignature(node): TypesList: (lambda c: c, multipleTypeMatcher) } evaluatedMatcher = matchers[type(child.type)][1](matchers[type(child.type)][0](child)) + if child.vararg: if i != argumentsCount - 1: raise RuntimeException("Vararg must be the last argument in signature", child.pos) vararg = evaluatedMatcher else: + if type(child.optionalValue) != NoneNode: + defaultArgs[child.variable.value] = expressionEvaluatorWithMatcher( + evaluatedMatcher, + exceptionProvider=lambda value: RuntimeException( + f"Value '{value.stringify()}' doesn't match declared type: {evaluatedMatcher.string}", child.optionalValue.pos) + )(child.optionalValue, environment) + evaluatedMatcher = optional(evaluatedMatcher) sign.append(evaluatedMatcher) - - return varargSignature(vararg, *sign, wrapVarargInValue=True) if vararg is not None else signature(*sign) + return defaultArgs, (varargSignature(vararg, *sign, wrapVarargInValue=True) if vararg is not None else signature(*sign)) except RuntimeException as e: raise updatePos(e, node) +def checkPositionOfOptionalArguments(node): + firstOptional = next((i for i, v in enumerate(node.children) if type(v.optionalValue) != NoneNode), None) #next(filter(lambda arg: type(arg.optionalValue) != NoneNode, node.children), None) + if firstOptional is not None: + regularAfterOptional = next((i for i, v in enumerate(node.children[firstOptional:]) if type(v.optionalValue) == NoneNode), None) + if regularAfterOptional is not None: + raise RuntimeException(f"Optional arguments should be declared at the end of the arguments list", node.children[regularAfterOptional].pos) + + def multipleTypeMatcher(typeNode): subSignature = [] diff --git a/smnp/token/tokenizer.py b/smnp/token/tokenizer.py index 017b88a..7c91ab1 100644 --- a/smnp/token/tokenizer.py +++ b/smnp/token/tokenizer.py @@ -27,6 +27,7 @@ tokenizers = ( defaultTokenizer(TokenType.CLOSE_SQUARE), defaultTokenizer(TokenType.OPEN_ANGLE), defaultTokenizer(TokenType.CLOSE_ANGLE), + defaultTokenizer(TokenType.SEMICOLON), defaultTokenizer(TokenType.ASTERISK), defaultTokenizer(TokenType.ASSIGN), defaultTokenizer(TokenType.COMMA), diff --git a/smnp/token/type.py b/smnp/token/type.py index 7c47bc2..91b4811 100644 --- a/smnp/token/type.py +++ b/smnp/token/type.py @@ -12,6 +12,7 @@ class TokenType(Enum): CLOSE_SQUARE = ']' OPEN_ANGLE = '<' CLOSE_ANGLE = '>' + SEMICOLON = ';' ASTERISK = '*' ASSIGN = '=' ARROW = '->' diff --git a/smnp/type/signature/matcher/model.py b/smnp/type/signature/matcher/model.py index 4e68860..17535ad 100644 --- a/smnp/type/signature/matcher/model.py +++ b/smnp/type/signature/matcher/model.py @@ -3,6 +3,7 @@ class Matcher: self.type = objectType self.matcher = matcher self.string = string + self.optional = False def match(self, value): if self.type is not None and self.type != value.type: diff --git a/smnp/type/signature/matcher/type.py b/smnp/type/signature/matcher/type.py index 8416a74..f374186 100644 --- a/smnp/type/signature/matcher/type.py +++ b/smnp/type/signature/matcher/type.py @@ -25,4 +25,4 @@ def oneOf(*matchers): def check(value): return any(matcher.match(value) for matcher in matchers) - return Matcher(None, check, f"<{', '.join(m.string for m in matchers)}>") \ No newline at end of file + return Matcher(None, check, f"<{', '.join(m.string for m in matchers)}>")