diff --git a/grammar b/grammar index b89deb0..e044cca 100644 --- a/grammar +++ b/grammar @@ -9,7 +9,8 @@ PITCH_MODIFIER = 'b' | '#' ::= '"' CHAR* '"' ::= '@' PITCH PITCH_MODIFIER? DIGIT? [':' DIGIT+ 'd'?]? ::= ID [ID|DIGIT]* - ::= 'integer' | 'string' | 'note' | 'list' | 'void' + ::= 'true' | 'false' + ::= 'integer' | 'string' | 'note' | 'list' | 'map' | 'sound' | 'bool' | 'void' # Parser ::= | @@ -18,9 +19,10 @@ PITCH_MODIFIER = 'b' | '#' ::= | '=' | | ::= | ::= | + ::= | ::= '.' | '.' ::= '*' - ::= | | | | + ::= | | | | | ::= diff --git a/smnp/ast/node/access.py b/smnp/ast/node/access.py deleted file mode 100644 index 8ef0f51..0000000 --- a/smnp/ast/node/access.py +++ /dev/null @@ -1,56 +0,0 @@ -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.node.ignore import IgnoredNode -from smnp.ast.parser import Parser -from smnp.error.syntax import SyntaxException -from smnp.token.type import TokenType - - -class AccessNode(ExpressionNode): - def __init__(self, pos): - super().__init__(pos) - self.children.append(IgnoredNode(pos)) - - @property - def left(self): - return self[0] - - @left.setter - def left(self, value): - self[0] = value - - @property - def right(self): - return self[1] - - @right.setter - def right(self, value): - self[1] = value - - @classmethod - def _parse(cls, input): - def createNode(left, right): - node = AccessNode(right.pos) - node.left = left - node.right = right - return node - - return Parser.leftAssociativeOperatorParser( - cls._literalParser(), - TokenType.DOT, - cls._parseAccessingProperty(), - createNode=createNode - )(input) - - @classmethod - def _literalParser(cls): - pass - - @staticmethod - def _parseAccessingProperty(): - from smnp.ast.node.identifier import IdentifierNode - - return Parser.oneOf( - IdentifierNode._literalParser(), - IdentifierNode._functionCallParser(), - exception=lambda input: SyntaxException(f"Expected property name or method call, found '{input.current().rawValue}'", input.currentPos()) - ) diff --git a/smnp/ast/node/assignment.py b/smnp/ast/node/assignment.py deleted file mode 100644 index 6b64921..0000000 --- a/smnp/ast/node/assignment.py +++ /dev/null @@ -1,28 +0,0 @@ -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.node.none import NoneNode - - -class AssignmentNode(ExpressionNode): - def __init__(self, pos): - super().__init__(pos) - self.children.append(NoneNode()) - - @property - def target(self): - return self[0] - - @target.setter - def target(self, value): - self[0] = value - - @property - def value(self): - return self[1] - - @value.setter - def value(self, value): - self[1] = value - - @classmethod - def _parse(cls, input): - raise RuntimeError("This class is not supposed to be automatically called") \ No newline at end of file diff --git a/smnp/ast/node/asterisk.py b/smnp/ast/node/asterisk.py deleted file mode 100644 index cfd6cb3..0000000 --- a/smnp/ast/node/asterisk.py +++ /dev/null @@ -1,28 +0,0 @@ -from smnp.ast.node.none import NoneNode -from smnp.ast.node.statement import StatementNode - - -class AsteriskNode(StatementNode): - def __init__(self, pos): - super().__init__(pos) - self.children = [NoneNode(), NoneNode()] - - @property - def iterator(self): - return self[0] - - @iterator.setter - def iterator(self, value): - self[0] = value - - @property - def statement(self): - return self[1] - - @statement.setter - def statement(self, value): - self[1] = value - - @classmethod - def _parse(cls, input): - raise RuntimeError("This class is not supposed to be automatically called") \ No newline at end of file diff --git a/smnp/ast/node/atom.py b/smnp/ast/node/atom.py new file mode 100644 index 0000000..4b53b6d --- /dev/null +++ b/smnp/ast/node/atom.py @@ -0,0 +1,90 @@ +from smnp.ast.node.model import Node +from smnp.ast.parser import Parser +from smnp.token.type import TokenType + + +class Atom(Node): + def __init__(self, pos): + super().__init__(pos) + self.children = [None] + + @property + def value(self): + return self[0] + + @value.setter + def value(self, value): + self[0] = value + + @classmethod + def withValue(cls, value, pos): + node = cls(pos) + node.value = value + return node + + +class IntegerLiteral(Atom): + pass + + +class StringLiteral(Atom): + pass + + +class NoteLiteral(Atom): + pass + + +class BoolLiteral(Atom): + pass + + +class TypeLiteral(Atom): + pass + + +def IntegerParser(input): + return Parser.terminal(TokenType.INTEGER, createNode=IntegerLiteral.withValue)(input) + + +def StringParser(input): + return Parser.terminal(TokenType.STRING, createNode=StringLiteral.withValue)(input) + + +def NoteParser(input): + return Parser.terminal(TokenType.NOTE, createNode=NoteLiteral.withValue)(input) + + +def BoolParser(input): + return Parser.terminal(TokenType.BOOL, createNode=BoolLiteral.withValue)(input) + + +def TypeLiteralParser(input): + return Parser.terminal(TokenType.TYPE, createNode=TypeLiteral.withValue)(input) + + +def LiteralParser(input): + return Parser.oneOf( + IntegerParser, + StringParser, + NoteParser, + BoolParser, + TypeLiteralParser, + name="literal" + )(input) + + +def AtomParser(input): + from smnp.ast.node.identifier import IdentifierParser + from smnp.ast.node.list import ListParser + from smnp.ast.node.map import MapParser + + return Parser.oneOf( + LiteralParser, + IdentifierParser, + ListParser, + MapParser, + name="atom" + )(input) + + diff --git a/smnp/ast/node/block.py b/smnp/ast/node/block.py index 4169fab..4ade903 100644 --- a/smnp/ast/node/block.py +++ b/smnp/ast/node/block.py @@ -1,20 +1,17 @@ -from smnp.ast.node.statement import StatementNode +from smnp.ast.node.model import Node +from smnp.ast.node.statement import StatementParser from smnp.ast.parser import Parser from smnp.token.type import TokenType -class BlockNode(StatementNode): +class Block(Node): + pass - @classmethod - def _parse(cls, input): - def createNode(start, items, end): - node = BlockNode(start.pos) - node.children = items - return node - return Parser.loop( - Parser.terminalParser(TokenType.OPEN_CURLY), - Parser.doAssert(StatementNode.parse, f"statement or '{TokenType.CLOSE_CURLY.key}'"), - Parser.terminalParser(TokenType.CLOSE_CURLY), - createNode=createNode, - )(input) \ No newline at end of file +def BlockParser(input): + return Parser.loop( + Parser.terminal(TokenType.OPEN_CURLY), + Parser.doAssert(StatementParser, f"statement or '{TokenType.CLOSE_CURLY.key}'"), + Parser.terminal(TokenType.CLOSE_CURLY), + createNode=lambda open, statements, close: Block.withChildren(statements, open.pos) + )(input) diff --git a/smnp/ast/node/condition.py b/smnp/ast/node/condition.py new file mode 100644 index 0000000..770be9d --- /dev/null +++ b/smnp/ast/node/condition.py @@ -0,0 +1,74 @@ +from smnp.ast.node.expression import ExpressionParser +from smnp.ast.node.model import Node +from smnp.ast.node.none import NoneNode +from smnp.ast.node.statement import StatementParser +from smnp.ast.parser import Parser +from smnp.token.type import TokenType + + +class IfElse(Node): + def __init__(self, pos): + super().__init__(pos) + self.children = [NoneNode(), NoneNode(), NoneNode()] + + @property + def condition(self): + return self[0] + + @condition.setter + def condition(self, value): + self[0] = value + + @property + def ifNode(self): + return self[1] + + @ifNode.setter + def ifNode(self, value): + self[1] = value + + @property + def elseNode(self): + return self[2] + + @elseNode.setter + def elseNode(self, value): + self[2] = value + + @classmethod + def createNode(cls, ifNode, condition, elseNode=NoneNode()): + node = cls(ifNode.pos) + node.ifNode = ifNode + node.condition = condition + node.elseNode = elseNode + return node + + +def IfElseStatementParser(input): + ifStatementParser = Parser.allOf( + Parser.terminal(TokenType.IF), + Parser.terminal(TokenType.OPEN_PAREN), + ExpressionParser, + Parser.terminal(TokenType.CLOSE_PAREN), + StatementParser, + createNode=lambda _, __, condition, ___, ifStatement: IfElse.createNode(ifStatement, condition), + name="if statement" + ) + + ifElseStatementParser = Parser.allOf( + Parser.terminal(TokenType.IF), + Parser.terminal(TokenType.OPEN_PAREN, doAssert=True), + Parser.doAssert(ExpressionParser, "expression"), + Parser.terminal(TokenType.CLOSE_PAREN, doAssert=True), + Parser.doAssert(StatementParser, "statement"), + Parser.terminal(TokenType.ELSE), + Parser.doAssert(StatementParser, "statement"), + createNode=lambda _, __, condition, ___, ifStatement, ____, elseStatement: IfElse.createNode(ifStatement, condition, elseStatement), + name="if-else statement" + ) + + return Parser.oneOf( + ifElseStatementParser, + ifStatementParser, + name="if-else/if statement" + )(input) \ No newline at end of file diff --git a/smnp/ast/node/expression.py b/smnp/ast/node/expression.py index 39bb80e..db939bc 100644 --- a/smnp/ast/node/expression.py +++ b/smnp/ast/node/expression.py @@ -1,69 +1,51 @@ -from smnp.ast.node.asterisk import AsteriskNode -from smnp.ast.node.model import Node -from smnp.ast.node.none import NoneNode -from smnp.ast.node.statement import StatementNode +from smnp.ast.node.operator import BinaryOperator +from smnp.ast.node.term import TermParser from smnp.ast.parser import Parser from smnp.token.type import TokenType -class ExpressionNode(Node): - def __init__(self, pos): - super().__init__(pos, [NoneNode()]) - - @property - def value(self): - return self[0] +class Sum(BinaryOperator): + pass - @value.setter - def value(self, v): - self[0] = v +class Relation(BinaryOperator): + pass - @classmethod - def withValue(cls, val, pos): - node = cls(pos) - node.value = val - return node +class And(BinaryOperator): + pass - @classmethod - def _parse(cls, input): - return Parser.oneOf( - cls._asteriskParser(), - cls._expressionParser(), - )(input) - @classmethod - def _asteriskParser(cls): - def createNode(iterator, asterisk, statement): - node = AsteriskNode(asterisk.pos) - node.iterator = iterator - node.statement = statement - return node +class Or(BinaryOperator): + pass - return Parser.allOf( - cls._expressionParser(), - Parser.terminalParser(TokenType.ASTERISK), - Parser.doAssert(StatementNode.parse, 'statement'), - createNode=createNode - ) - @classmethod - def _expressionParser(cls): - from smnp.ast.node.integer import IntegerLiteralNode - from smnp.ast.node.string import StringLiteralNode - from smnp.ast.node.note import NoteLiteralNode - from smnp.ast.node.identifier import IdentifierNode - from smnp.ast.node.list import ListNode - from smnp.ast.node.map import MapNode - from smnp.ast.node.type import TypeNode +def ExpressionParser(input): + expr1 = Parser.leftAssociativeOperatorParser( + TermParser, + [TokenType.PLUS, TokenType.MINUS], + TermParser, + lambda left, op, right: Sum.withValues(left, op, right) + ) + + expr2 = Parser.leftAssociativeOperatorParser( + expr1, + [TokenType.RELATION, TokenType.OPEN_ANGLE, TokenType.CLOSE_ANGLE], + expr1, + lambda left, op, right: Relation.withValues(left, op, right) + ) + + expr3 = Parser.leftAssociativeOperatorParser( + expr2, + [TokenType.AND], + expr2, + lambda left, op, right: And.withValues(left, op, right) + ) + + return Parser.leftAssociativeOperatorParser( + expr3, + [TokenType.OR], + expr3, + lambda left, op, right: Or.withValues(left, op, right) + )(input) - return Parser.oneOf( - IntegerLiteralNode.parse, - StringLiteralNode.parse, - NoteLiteralNode.parse, - IdentifierNode.parse, - MapNode.parse, - ListNode.parse, - TypeNode.parse, - ) \ No newline at end of file diff --git a/smnp/ast/node/extend.py b/smnp/ast/node/extend.py index c052067..2ebdfbe 100644 --- a/smnp/ast/node/extend.py +++ b/smnp/ast/node/extend.py @@ -1,14 +1,14 @@ -from smnp.ast.node.block import BlockNode -from smnp.ast.node.function import FunctionDefinitionNode -from smnp.ast.node.identifier import IdentifierNode +from smnp.ast.node.block import Block +from smnp.ast.node.function import FunctionDefinitionParser +from smnp.ast.node.identifier import IdentifierLiteralParser +from smnp.ast.node.model import Node from smnp.ast.node.none import NoneNode -from smnp.ast.node.statement import StatementNode -from smnp.ast.node.type import TypeNode +from smnp.ast.node.type import TypeParser from smnp.ast.parser import Parser from smnp.token.type import TokenType -class ExtendNode(StatementNode): +class Extend(Node): def __init__(self, pos): super().__init__(pos) self.children = [NoneNode(), NoneNode(), NoneNode()] @@ -38,33 +38,51 @@ class ExtendNode(StatementNode): self[2] = value @classmethod - def _parse(cls, input): - def createNode(extend, type, asKeyword, variable, methods): - node = ExtendNode(extend.pos) - node.type = type - node.variable = variable - node.methods = methods - return node + def withValues(cls, pos, type, variable, methods): + node = cls(pos) + node.type = type + node.variable = variable + node.methods = methods + return node - return Parser.allOf( - Parser.terminalParser(TokenType.EXTEND), - Parser.doAssert(TypeNode.parse, "type being extended"), - Parser.terminalParser(TokenType.AS, doAssert=True), - Parser.doAssert(IdentifierNode.identifierParser(), "variable name"), - Parser.doAssert(cls._methodsDeclarationsParser(), "methods declarations"), - createNode=createNode - )(input) - @classmethod - def _methodsDeclarationsParser(cls): - def createNode(openBracket, items, closeBracket): - node = BlockNode(openBracket.pos) - node.children = items - return node +def ExtendParser(input): + + simpleExtend = Parser.allOf( + Parser.terminal(TokenType.EXTEND), + TypeParser, + Parser.terminal(TokenType.AS), + IdentifierLiteralParser, + Parser.terminal(TokenType.WITH), + Parser.doAssert(Parser.wrap(FunctionDefinitionParser, lambda method: Block.withChildren([ method ], method.pos)), "method definition"), + createNode=lambda extend, type, _, variable, __, methods: Extend.withValues(extend.pos, type, variable, methods), + name="simple extend" + ) + + multiExtend = Parser.allOf( + Parser.terminal(TokenType.EXTEND), + Parser.doAssert(TypeParser, "type being extended"), + Parser.terminal(TokenType.AS, doAssert=True), + Parser.doAssert(IdentifierLiteralParser, "variable name"), + Parser.doAssert(MethodsDeclarationParser, f"block with methods definitions or '{TokenType.WITH.key}' keyword"), + createNode=lambda extend, type, _, variable, methods: Extend.withValues(extend.pos, type, variable, methods), + name="multiple extend" + ) + + + return Parser.oneOf( + simpleExtend, + multiExtend, + name="extend" + )(input) + + +def MethodsDeclarationParser(input): + return Parser.loop( + Parser.terminal(TokenType.OPEN_CURLY), + Parser.doAssert(FunctionDefinitionParser, f"method definition or '{TokenType.CLOSE_CURLY.key}'"), + Parser.terminal(TokenType.CLOSE_CURLY), + createNode=lambda open, methods, close: Block.withChildren(methods, open.pos), + name="methods block" + )(input) - return Parser.loop( - Parser.terminalParser(TokenType.OPEN_CURLY), - Parser.doAssert(FunctionDefinitionNode.parse, f"method declaration or '{TokenType.CLOSE_CURLY.key}'"), - Parser.terminalParser(TokenType.CLOSE_CURLY), - createNode=createNode - ) \ No newline at end of file diff --git a/smnp/ast/node/factor.py b/smnp/ast/node/factor.py new file mode 100644 index 0000000..afc78e4 --- /dev/null +++ b/smnp/ast/node/factor.py @@ -0,0 +1,102 @@ +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 +from smnp.token.type import TokenType + +class NotOperator(UnaryOperator): + pass + + +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), + 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, + [TokenType.DOUBLE_ASTERISK], + factorOperands, + lambda left, op, right: Power.withValues(left, op, right), + name="power operator" + ) + + notOperator = Parser.allOf( + Parser.terminal(TokenType.NOT, Operator.withValue), + powerFactor, + createNode=NotOperator.withValues, + 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" + )(input) diff --git a/smnp/ast/node/function.py b/smnp/ast/node/function.py index 6295f62..c0fcfa3 100644 --- a/smnp/ast/node/function.py +++ b/smnp/ast/node/function.py @@ -1,83 +1,58 @@ -from smnp.ast.node.block import BlockNode -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.node.identifier import IdentifierNode +from smnp.ast.node.block import BlockParser +from smnp.ast.node.identifier import IdentifierLiteralParser 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.statement import StatementNode -from smnp.ast.node.type import TypeNode, TypeSpecifier +from smnp.ast.node.type import TypeParser, Type from smnp.ast.parser import Parser from smnp.token.type import TokenType -class ArgumentsDeclarationNode(Node): - - @classmethod - def _parse(cls, input): - raise RuntimeError("This class is not supposed to be automatically called") - - -class VarargNode(Node): +class ArgumentsDeclaration(Node): pass -class ArgumentDefinitionNode(ExpressionNode): +class Argument(Node): + def __init__(self, pos): super().__init__(pos) - self.children.extend([NoneNode(), False]) + self.children = [NoneNode(), NoneNode(), False] @property def type(self): return self[0] + @type.setter def type(self, value): self[0] = value + @property def variable(self): return self[1] + @variable.setter def variable(self, value): self[1] = value + @property def vararg(self): return self[2] + @vararg.setter def vararg(self, value): self[2] = value - @classmethod - def parser(cls): - def createNode(type, variable, dots): - node = ArgumentDefinitionNode(type.pos) - node.type = type - node.variable = variable - node.vararg = isinstance(dots, VarargNode) - return node - - return Parser.allOf( - Parser.optional(Parser.oneOf( - TypeNode.parse, - TypeSpecifier.parse - )), - Parser.doAssert(IdentifierNode.identifierParser(), "variable name"), - Parser.optional(Parser.terminalParser(TokenType.DOTS, lambda val, pos: VarargNode(pos))), - createNode=createNode - ) - - @classmethod - def _parse(cls, input): - #TODO - raise RuntimeError("Not implemented yet. There is still required work to correctly build AST related to IdentifierNode") +class VarargNode(Node): + pass - -class FunctionDefinitionNode(StatementNode): +class FunctionDefinition(Node): def __init__(self, pos): super().__init__(pos) self.children = [NoneNode(), NoneNode(), NoneNode()] @@ -107,22 +82,48 @@ class FunctionDefinitionNode(StatementNode): self[2] = value @classmethod - def _parse(cls, input): - def createNode(function, name, arguments, body): - node = FunctionDefinitionNode(function.pos) - node.name = name - node.arguments = arguments - node.body = body - return node + def withValues(cls, name, arguments, body): + node = cls(name.pos) + node.name = name + node.arguments = arguments + node.body = body + return node - return Parser.allOf( - Parser.terminalParser(TokenType.FUNCTION), - Parser.doAssert(IdentifierNode.identifierParser(), "function name"), - Parser.doAssert(cls._argumentsDeclarationParser(), "arguments list"), - Parser.doAssert(BlockNode.parse, "function body"), - createNode=createNode - )(input) - @staticmethod - def _argumentsDeclarationParser(): - return abstractIterableParser(ArgumentsDeclarationNode, TokenType.OPEN_PAREN, TokenType.CLOSE_PAREN, ArgumentDefinitionNode.parser()) \ No newline at end of file +def ArgumentParser(input): + def createNode(type, variable, vararg): + pos = type.pos if isinstance(type, Type) else variable.pos + node = Argument(pos) + node.type = type + node.variable = variable + node.vararg = vararg is True + return node + + return Parser.allOf( + Parser.optional(TypeParser), + Parser.doAssert(IdentifierLiteralParser, "argument name"), + Parser.optional(Parser.terminal(TokenType.DOTS, lambda val, pos: True)), + createNode=createNode, + name="function argument" + )(input) + + +def ArgumentsDeclarationParser(input): + return abstractIterableParser( + ArgumentsDeclaration, + TokenType.OPEN_PAREN, + TokenType.CLOSE_PAREN, + Parser.doAssert(ArgumentParser, "function/method argument") + )(input) + + +def FunctionDefinitionParser(input): + return Parser.allOf( + Parser.terminal(TokenType.FUNCTION), + Parser.doAssert(IdentifierLiteralParser, "function/method name"), + Parser.doAssert(ArgumentsDeclarationParser, "function/method arguments"), + Parser.doAssert(BlockParser, "function/method body"), + createNode=lambda _, name, args, body: FunctionDefinition.withValues(name, args, body), + name="function definition" + )(input) + diff --git a/smnp/ast/node/identifier.py b/smnp/ast/node/identifier.py index 0edd0ab..97f0c5f 100644 --- a/smnp/ast/node/identifier.py +++ b/smnp/ast/node/identifier.py @@ -1,53 +1,74 @@ -from smnp.ast.node.access import AccessNode -from smnp.ast.node.assignment import AssignmentNode -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.node.invocation import FunctionCallNode, ArgumentsListNode +from smnp.ast.node.atom import Atom +from smnp.ast.node.expression import ExpressionParser +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 from smnp.ast.parser import Parser from smnp.token.type import TokenType -class IdentifierNode(AccessNode): +class Identifier(Atom): + pass + + +class FunctionCall(Node): def __init__(self, pos): super().__init__(pos) - del self.children[1] + self.children = [NoneNode(), NoneNode()] + + @property + def name(self): + return self[0] + + @name.setter + def name(self, value): + self[0] = value + + @property + def arguments(self): + return self[1] + + @arguments.setter + def arguments(self, value): + self[1] = value @classmethod - def _literalParser(cls): - return Parser.oneOf( - IdentifierNode._functionCallParser(), - IdentifierNode._assignmentParser(), - IdentifierNode.identifierParser() - ) + def withChildren(cls, name, arguments): + node = cls(name.pos) + node.name = name + node.arguments = arguments + return node - @staticmethod - def _assignmentParser(): - def createNode(target, assignment, value): - node = AssignmentNode(assignment.pos) - node.target = target - node.value = value - return node - return Parser.allOf( - IdentifierNode.identifierParser(), - Parser.terminalParser(TokenType.ASSIGN), - Parser.doAssert(ExpressionNode.parse, "expression"), - createNode=createNode - ) +class ArgumentsList(Node): + pass - @staticmethod - def _functionCallParser(): - def createNode(name, arguments): - node = FunctionCallNode(name.pos) - node.name = name - node.arguments = arguments - return node - return Parser.allOf( - IdentifierNode.identifierParser(), - ArgumentsListNode.parse, - createNode=createNode - ) +class Assignment(BinaryOperator): + pass - @staticmethod - def identifierParser(): - return Parser.terminalParser(TokenType.IDENTIFIER, lambda val, pos: IdentifierNode.withValue(val, pos)) + +def IdentifierLiteralParser(input): + return Parser.terminal(TokenType.IDENTIFIER, createNode=Identifier.withValue)(input) + + +def IdentifierParser(input): + functionCallParser = Parser.allOf( + IdentifierLiteralParser, + abstractIterableParser(ArgumentsList, TokenType.OPEN_PAREN, TokenType.CLOSE_PAREN, Parser.doAssert(ExpressionParser, "expression")), + createNode=lambda name, arguments: FunctionCall.withChildren(name, arguments) + ) + + assignmentParser = Parser.allOf( + IdentifierLiteralParser, + Parser.terminal(TokenType.ASSIGN, createNode=Operator.withValue), + Parser.doAssert(ExpressionParser, "expression"), + createNode=lambda identifier, assign, expr: Assignment.withValues(identifier, assign, expr) + ) + + return Parser.oneOf( + assignmentParser, + functionCallParser, + IdentifierLiteralParser + )(input) diff --git a/smnp/ast/node/imports.py b/smnp/ast/node/imports.py index 777ffaf..8305cf9 100644 --- a/smnp/ast/node/imports.py +++ b/smnp/ast/node/imports.py @@ -1,16 +1,13 @@ -from smnp.ast.node.identifier import IdentifierNode +from smnp.ast.node.atom import StringParser from smnp.ast.node.model import Node -from smnp.ast.node.none import NoneNode -from smnp.ast.node.string import StringLiteralNode -from smnp.ast.node.type import TypeNode from smnp.ast.parser import Parser from smnp.token.type import TokenType -class ImportNode(Node): +class Import(Node): def __init__(self, pos): super().__init__(pos) - self.children = [NoneNode(), NoneNode(), NoneNode()] + self.children = [None] @property def source(self): @@ -20,57 +17,17 @@ class ImportNode(Node): def source(self, value): self[0] = value - @property - def type(self): - return self[1] - - @type.setter - def type(self, value): - self[1] = value - - @property - def variable(self): - return self[2] - - @variable.setter - def variable(self, value): - self[2] = value - @classmethod - def _parse(cls, input): - return Parser.oneOf( - cls._literalImportParser(), - cls._fileImportParser() - )(input) + def withValue(cls, value): + node = cls(value.pos) + node.source = value + return node - @classmethod - def _literalImportParser(cls): - def createNode(importKeyword, type, fromKeyword, source, asKeyword, variable): - node = ImportNode(importKeyword.pos) - node.source = source - node.type = type - node.variable = variable - return node - return Parser.allOf( - Parser.terminalParser(TokenType.IMPORT), - TypeNode.parse, - Parser.doAssert(Parser.terminalParser(TokenType.FROM), "'from as '"), - Parser.doAssert(StringLiteralNode._literalParser(), "source as a string"), - Parser.doAssert(Parser.terminalParser(TokenType.AS), "'as '"), - Parser.doAssert(IdentifierNode.identifierParser(), "variable name"), - createNode=createNode - ) - - @classmethod - def _fileImportParser(cls): - def createNode(importKeyword, source): - node = ImportNode(importKeyword.pos) - node.source = source - return node - - return Parser.allOf( - Parser.terminalParser(TokenType.IMPORT), - Parser.doAssert(StringLiteralNode._literalParser(), "source as a string"), - createNode=createNode - ) +def ImportParser(input): + return Parser.allOf( + Parser.terminal(TokenType.IMPORT), + Parser.doAssert(StringParser, "import source as string"), + createNode=lambda imp, source: Import.withValue(source), + name="import" + )(input) diff --git a/smnp/ast/node/integer.py b/smnp/ast/node/integer.py deleted file mode 100644 index fccb86d..0000000 --- a/smnp/ast/node/integer.py +++ /dev/null @@ -1,31 +0,0 @@ -from smnp.ast.node.access import AccessNode -from smnp.ast.parser import Parser -from smnp.token.type import TokenType - - -class IntegerLiteralNode(AccessNode): - def __init__(self, pos): - super().__init__(pos) - del self.children[1] - - @classmethod - def _literalParser(cls): - return Parser.oneOf( - cls._negativeIntegerParser(), - cls._positiveIntegerParser() - ) - - @classmethod - def _negativeIntegerParser(cls): - def createNode(minus, integer): - return IntegerLiteralNode.withValue(-integer.value, minus.pos) - - return Parser.allOf( - Parser.terminalParser(TokenType.MINUS), - Parser.doAssert(cls._positiveIntegerParser(), "integer"), - createNode=createNode - ) - - @classmethod - def _positiveIntegerParser(cls): - return Parser.terminalParser(TokenType.INTEGER, lambda val, pos: IntegerLiteralNode.withValue(int(val), pos)) \ No newline at end of file diff --git a/smnp/ast/node/invocation.py b/smnp/ast/node/invocation.py deleted file mode 100644 index f9ab2f3..0000000 --- a/smnp/ast/node/invocation.py +++ /dev/null @@ -1,40 +0,0 @@ -from smnp.ast.node.access import AccessNode -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.node.iterable import abstractIterableParser -from smnp.ast.node.model import Node -from smnp.ast.parser import Parser -from smnp.token.type import TokenType - - -class ArgumentsListNode(Node): - - @classmethod - def _parse(cls, input): - return abstractIterableParser(ArgumentsListNode, TokenType.OPEN_PAREN, TokenType.CLOSE_PAREN, - Parser.doAssert(ExpressionNode.parse, "expression"))(input) - - -class FunctionCallNode(AccessNode): - def __init__(self, pos): - super().__init__(pos) - - @property - def name(self): - return self[0] - - @name.setter - def name(self, value): - self[0] = value - - @property - def arguments(self): - return self[1] - - @arguments.setter - def arguments(self, value): - self[1] = value - - @classmethod - def _parse(cls, input): - raise RuntimeError("This class is not supposed to be automatically called") - diff --git a/smnp/ast/node/iterable.py b/smnp/ast/node/iterable.py index 53fb7e6..caa0ac8 100644 --- a/smnp/ast/node/iterable.py +++ b/smnp/ast/node/iterable.py @@ -1,4 +1,3 @@ -from smnp.ast.node.expression import ExpressionNode from smnp.ast.node.ignore import IgnoredNode from smnp.ast.node.model import Node, ParseResult from smnp.ast.node.none import NoneNode @@ -7,11 +6,19 @@ from smnp.token.type import TokenType def abstractIterableParser(iterableNodeType, openTokenType, closeTokenType, itemParser): - class AbstractIterableTailNode(ExpressionNode): + + class AbstractIterable(Node): def __init__(self, pos): super().__init__(pos) + self.children = [NoneNode(), NoneNode()] - self.children.append(NoneNode()) + @property + def value(self): + return self[0] + + @value.setter + def value(self, value): + self[0] = value @property def next(self): @@ -21,83 +28,67 @@ def abstractIterableParser(iterableNodeType, openTokenType, closeTokenType, item def next(self, value): self[1] = value - @classmethod - def _parse(cls, input): - return Parser.oneOf( - AbstractIterableTailNode._parser1(), - AbstractIterableTailNode._parser2(), - )(input) + class AbstractIterableTail(AbstractIterable): + pass - @staticmethod - def _parser1(): - return Parser.terminalParser(closeTokenType) + def abstractIterableParser(input): + return Parser.oneOf( + emptyIterable, + openIterable + )(input) - @staticmethod - def _parser2(): - def createNode(comma, expr, iterableTail): - node = AbstractIterableTailNode(expr.pos) - node.value = expr - node.next = iterableTail - return node + def emptyIterable(input): + def createNode(open, close): + node = AbstractIterable(open.pos) + node.value = open + node.next = close + return node - return Parser.allOf( - Parser.terminalParser(TokenType.COMMA, doAssert=True), - itemParser, - AbstractIterableTailNode.parse, - createNode=createNode - ) + return Parser.allOf( + Parser.terminal(openTokenType), + Parser.terminal(closeTokenType), + createNode=createNode + )(input) - class AbstractIterableNode(ExpressionNode): - def __init__(self, pos): - super().__init__(pos) + def openIterable(input): + def createNode(open, item, tail): + node = AbstractIterable(open.pos) + node.value = item + node.next = tail + return node - self.children.append(NoneNode()) + return Parser.allOf( + Parser.terminal(openTokenType), + itemParser, + abstractIterableTailParser, + createNode=createNode + )(input) - @property - def next(self): - return self[1] + def abstractIterableTailParser(input): + return Parser.oneOf( + closeIterable, + nextItem, + )(input) - @next.setter - def next(self, value): - self[1] = value + def nextItem(input): + def createNode(comma, item, tail): + node = AbstractIterableTail(item.pos) + node.value = item + node.next = tail + return node - @classmethod - def _parse(cls, input): - return Parser.oneOf( - AbstractIterableNode._parser1(), - AbstractIterableNode._parser2() - )(input) + return Parser.allOf( + Parser.doAssert(Parser.terminal(TokenType.COMMA), f"'{TokenType.COMMA.key}' or '{closeTokenType.key}'"), + itemParser, + abstractIterableTailParser, + createNode=createNode + )(input) - @staticmethod - def _parser1(): - def emptyIterable(openToken, closeToken): - node = AbstractIterableNode(openToken.pos) - node.value = openToken - node.next = closeToken - return node + def closeIterable(input): + return Parser.terminal(closeTokenType)(input) - return Parser.allOf( - Parser.terminalParser(openTokenType), - Parser.terminalParser(closeTokenType), - createNode=emptyIterable - ) - @staticmethod - def _parser2(): - def createNode(openParen, expr, iterableTail): - node = AbstractIterableNode(openParen.pos) - node.value = expr - node.next = iterableTail - return node - - return Parser.allOf( - Parser.terminalParser(openTokenType, lambda val, pos: Node(pos)), - itemParser, - AbstractIterableTailNode.parse, - createNode=createNode - ) - - return toFlatDesiredNode(iterableNodeType, AbstractIterableNode.parse) + return toFlatDesiredNode(iterableNodeType, abstractIterableParser) def toFlatDesiredNode(iterableNodeType, parser): @@ -114,7 +105,7 @@ def toFlatDesiredNode(iterableNodeType, parser): return ParseResult.FAIL() - return parse + return Parser(parse, "flat", [parser]) def flattenList(node, output=None): diff --git a/smnp/ast/node/list.py b/smnp/ast/node/list.py index 0316918..ed77d62 100644 --- a/smnp/ast/node/list.py +++ b/smnp/ast/node/list.py @@ -1,13 +1,19 @@ -from smnp.ast.node.access import AccessNode -from smnp.ast.node.expression import ExpressionNode from smnp.ast.node.iterable import abstractIterableParser +from smnp.ast.node.model import Node from smnp.ast.parser import Parser from smnp.token.type import TokenType -class ListNode(AccessNode): +class List(Node): + pass - @classmethod - def _literalParser(cls): - return abstractIterableParser(ListNode, TokenType.OPEN_SQUARE, TokenType.CLOSE_SQUARE, - Parser.doAssert(ExpressionNode.parse, "expression")) + +def ListParser(input): + from smnp.ast.node.expression import ExpressionParser + + return abstractIterableParser( + List, + TokenType.OPEN_SQUARE, + TokenType.CLOSE_SQUARE, + Parser.doAssert(ExpressionParser, "expression") + )(input) diff --git a/smnp/ast/node/literal.py b/smnp/ast/node/literal.py deleted file mode 100644 index bdfc76b..0000000 --- a/smnp/ast/node/literal.py +++ /dev/null @@ -1,18 +0,0 @@ -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.parser import Parser - - -class LiteralNode(ExpressionNode): - - @classmethod - def _getTokenType(cls): - pass - - @classmethod - def _processValue(cls, value): - return value - - @classmethod - def _literalParser(cls): - createNode = lambda val, pos: cls.withValue(cls._processValue(val), pos) - return Parser.terminalParser(cls._getTokenType(), createNode) diff --git a/smnp/ast/node/map.py b/smnp/ast/node/map.py index 419eb95..47784dd 100644 --- a/smnp/ast/node/map.py +++ b/smnp/ast/node/map.py @@ -1,18 +1,12 @@ -from smnp.ast.node.access import AccessNode -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.node.integer import IntegerLiteralNode +from smnp.ast.node.atom import LiteralParser from smnp.ast.node.iterable import abstractIterableParser -from smnp.ast.node.none import NoneNode -from smnp.ast.node.note import NoteLiteralNode -from smnp.ast.node.string import StringLiteralNode -from smnp.ast.node.type import TypeNode +from smnp.ast.node.model import Node +from smnp.ast.node.operator import BinaryOperator, Operator from smnp.ast.parser import Parser from smnp.token.type import TokenType -class MapEntry(ExpressionNode): - def __init__(self, pos): - super().__init__(pos) - self.children = [NoneNode(), NoneNode()] + +class MapEntry(BinaryOperator): @property def key(self): @@ -24,38 +18,33 @@ class MapEntry(ExpressionNode): @property def value(self): - return self[1] + return self[2] @value.setter def value(self, value): - self[1] = value + self[2] = value -class MapNode(AccessNode): - @classmethod - def _literalParser(cls): - return abstractIterableParser(MapNode, TokenType.OPEN_CURLY, TokenType.CLOSE_CURLY, cls._entryParser()) +class Map(Node): + pass - @classmethod - def _entryParser(cls): - def createNode(key, arrow, value): - node = MapEntry(key.pos) - node.key = key - node.value = value - return node - return Parser.allOf( - cls._keyParser(), - Parser.terminalParser(TokenType.ARROW), - ExpressionNode.parse, - createNode=createNode - ) +def MapParser(input): + from smnp.ast.node.expression import ExpressionParser + keyParser = LiteralParser + valueParser = ExpressionParser + + mapEntryParser = Parser.allOf( + keyParser, + Parser.terminal(TokenType.ARROW, createNode=Operator.withValue, doAssert=True), + Parser.doAssert(valueParser, "expression"), + createNode=MapEntry.withValues + ) + + return abstractIterableParser( + Map, + TokenType.OPEN_CURLY, + TokenType.CLOSE_CURLY, + mapEntryParser + )(input) - @classmethod - def _keyParser(cls): - return Parser.oneOf( - IntegerLiteralNode._literalParser(), - StringLiteralNode._literalParser(), - NoteLiteralNode._literalParser(), - TypeNode.parse - ) \ No newline at end of file diff --git a/smnp/ast/node/model.py b/smnp/ast/node/model.py index 0aa143a..9d11f1f 100644 --- a/smnp/ast/node/model.py +++ b/smnp/ast/node/model.py @@ -28,26 +28,11 @@ class Node: def pop(self, index): return self.children.pop(index) - @classmethod - def _parse(cls, input): - pass - - @classmethod - def parse(cls, input): - result = cls._parse(input) - if result is None: - return ParseResult.FAIL() - - if not isinstance(result, ParseResult): - raise RuntimeError(f"_parse() method of '{cls.__name__}' class haven't returned ParseResult object") - - return result - def print(self): self._print(first=True) def _print(self, prefix="", last=True, first=False): - print(prefix, '' if first else '└─' if last else '├─', self.__class__.__name__, sep="") + print(prefix, '' if first else '└─' if last else '├─', self.__class__.__name__, f" (line {self.pos[0]+1}, col {self.pos[1]+1})", sep="") prefix += ' ' if last else '│ ' for i, child in enumerate(self.children): last = i == len(self.children) - 1 diff --git a/smnp/ast/node/note.py b/smnp/ast/node/note.py deleted file mode 100644 index e4c02b0..0000000 --- a/smnp/ast/node/note.py +++ /dev/null @@ -1,13 +0,0 @@ -from smnp.ast.node.access import AccessNode -from smnp.ast.node.literal import LiteralNode -from smnp.token.type import TokenType - - -class NoteLiteralNode(LiteralNode, AccessNode): - def __init__(self, pos): - super().__init__(pos) - del self.children[1] - - @classmethod - def _getTokenType(cls): - return TokenType.NOTE \ No newline at end of file diff --git a/smnp/ast/node/operator.py b/smnp/ast/node/operator.py new file mode 100644 index 0000000..fa312d7 --- /dev/null +++ b/smnp/ast/node/operator.py @@ -0,0 +1,89 @@ +from smnp.ast.node.model import Node +from smnp.ast.node.none import NoneNode + + +class UnaryOperator(Node): + def __init__(self, pos): + super().__init__(pos) + self.children=[NoneNode(), NoneNode()] + + @property + def operator(self): + return self[0] + + @operator.setter + def operator(self, value): + self[0] = value + + @property + def value(self): + return self[1] + + @value.setter + def value(self, value): + self[1] = value + + @classmethod + def withValues(cls, operator, value): + node = cls(operator.pos) + node.operator = operator + node.value = value + return node + + +class BinaryOperator(Node): + def __init__(self, pos): + super().__init__(pos) + self.children = [NoneNode(), NoneNode(), NoneNode()] + + @property + def left(self): + return self[0] + + @left.setter + def left(self, value): + self[0] = value + + @property + def operator(self): + return self[1] + + @operator.setter + def operator(self, value): + self[1] = value + + @property + def right(self): + return self[2] + + @right.setter + def right(self, value): + self[2] = value + + @classmethod + def withValues(cls, left, operator, right): + node = cls(operator.pos) + node.left = left + node.operator = operator + node.right = right + return node + + +class Operator(Node): + def __init__(self, pos): + super().__init__(pos) + self.children = [None] + + @property + def value(self): + return self[0] + + @value.setter + def value(self, value): + self[0] = value + + @classmethod + def withValue(cls, value, pos): + node = cls(pos) + node.value = value + return node \ No newline at end of file diff --git a/smnp/ast/node/program.py b/smnp/ast/node/program.py index a6991ac..4c26a8f 100644 --- a/smnp/ast/node/program.py +++ b/smnp/ast/node/program.py @@ -1,9 +1,8 @@ -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.node.extend import ExtendNode -from smnp.ast.node.function import FunctionDefinitionNode -from smnp.ast.node.imports import ImportNode +from smnp.ast.node.extend import ExtendParser +from smnp.ast.node.function import FunctionDefinitionParser +from smnp.ast.node.imports import ImportParser from smnp.ast.node.model import Node, ParseResult -from smnp.ast.node.statement import StatementNode +from smnp.ast.node.statement import StatementParser from smnp.ast.parser import Parser from smnp.error.syntax import SyntaxException @@ -12,21 +11,26 @@ class Program(Node): def __init__(self): super().__init__((-1, -1)) - @classmethod - def _parse(cls, input): - def parseToken(input): - return Parser.oneOf( - FunctionDefinitionNode.parse, - ExtendNode.parse, - ExpressionNode.parse, - ImportNode.parse, - StatementNode.parse, - exception = SyntaxException(f"Invalid statement: {input.currentToEndOfLine()}", input.current().pos) - )(input) - +def ProgramParser(input): + def parse(input): root = Program() + + # Start Symbol + startSymbolParser = Parser.oneOf( + ImportParser, + FunctionDefinitionParser, + ExtendParser, + StatementParser, + exception=lambda inp: SyntaxException(f"Invalid statement: {inp.currentToEndOfLine()}", inp.current().pos), + name="start symbol" + ) + while input.hasCurrent(): - result = parseToken(input) + result = startSymbolParser(input) + if result.result: root.append(result.node) - return ParseResult.OK(root) \ No newline at end of file + + return ParseResult.OK(root) + + return Parser(parse, name="program")(input) diff --git a/smnp/ast/node/ret.py b/smnp/ast/node/ret.py index d963287..cd3b80f 100644 --- a/smnp/ast/node/ret.py +++ b/smnp/ast/node/ret.py @@ -1,32 +1,17 @@ -from smnp.ast.node.expression import ExpressionNode -from smnp.ast.node.none import NoneNode -from smnp.ast.node.statement import StatementNode +from smnp.ast.node.expression import ExpressionParser +from smnp.ast.node.valuable import Valuable from smnp.ast.parser import Parser from smnp.token.type import TokenType -class ReturnNode(StatementNode): - def __init__(self, pos): - super().__init__(pos) - self.children.append(NoneNode()) +class Return(Valuable): + pass - @property - def value(self): - return self[0] - @value.setter - def value(self, value): - self[0] = value - - @classmethod - def _parse(cls, input): - def createNode(ret, value): - node = ReturnNode(ret.pos) - node.value = value - return node - - return Parser.allOf( - Parser.terminalParser(TokenType.RETURN), - Parser.doAssert(ExpressionNode.parse, "expression"), - createNode=createNode - )(input) \ No newline at end of file +def ReturnParser(input): + return Parser.allOf( + Parser.terminal(TokenType.RETURN), + Parser.optional(ExpressionParser), + createNode=lambda ret, val: Return.withValue(val, ret.pos), + name="return" + )(input) \ No newline at end of file diff --git a/smnp/ast/node/statement.py b/smnp/ast/node/statement.py index 3405c79..f3b78ba 100644 --- a/smnp/ast/node/statement.py +++ b/smnp/ast/node/statement.py @@ -2,16 +2,20 @@ from smnp.ast.node.model import Node from smnp.ast.parser import Parser -class StatementNode(Node): +class Statement(Node): + pass - @classmethod - def _parse(cls, input): - from smnp.ast.node.block import BlockNode - from smnp.ast.node.expression import ExpressionNode - from smnp.ast.node.ret import ReturnNode - return Parser.oneOf( - ExpressionNode.parse, - BlockNode.parse, - ReturnNode.parse, - )(input) \ No newline at end of file +def StatementParser(input): + from smnp.ast.node.block import BlockParser + from smnp.ast.node.condition import IfElseStatementParser + from smnp.ast.node.expression import ExpressionParser + + from smnp.ast.node.ret import ReturnParser + return Parser.oneOf( + IfElseStatementParser, + ExpressionParser, + BlockParser, + ReturnParser, + name="statement" + )(input) diff --git a/smnp/ast/node/string.py b/smnp/ast/node/string.py deleted file mode 100644 index 4034d3d..0000000 --- a/smnp/ast/node/string.py +++ /dev/null @@ -1,13 +0,0 @@ -from smnp.ast.node.access import AccessNode -from smnp.ast.node.literal import LiteralNode -from smnp.token.type import TokenType - - -class StringLiteralNode(LiteralNode, AccessNode): - def __init__(self, pos): - super().__init__(pos) - del self.children[1] - - @classmethod - def _getTokenType(cls): - return TokenType.STRING \ No newline at end of file diff --git a/smnp/ast/node/term.py b/smnp/ast/node/term.py new file mode 100644 index 0000000..e0012cf --- /dev/null +++ b/smnp/ast/node/term.py @@ -0,0 +1,17 @@ +from smnp.ast.node.factor import FactorParser +from smnp.ast.node.operator import BinaryOperator +from smnp.ast.parser import Parser +from smnp.token.type import TokenType + + +class Product(BinaryOperator): + pass + + +def TermParser(input): + return Parser.leftAssociativeOperatorParser( + FactorParser, + [TokenType.ASTERISK, TokenType.SLASH], + FactorParser, + lambda left, op, right: Product.withValues(left, op, right) + )(input) \ No newline at end of file diff --git a/smnp/ast/node/type.py b/smnp/ast/node/type.py index 847f27b..955a0a4 100644 --- a/smnp/ast/node/type.py +++ b/smnp/ast/node/type.py @@ -1,32 +1,15 @@ -from smnp.ast.node.access import AccessNode +from smnp.ast.node.atom import TypeLiteralParser from smnp.ast.node.iterable import abstractIterableParser from smnp.ast.node.model import Node +from smnp.ast.node.none import NoneNode from smnp.ast.parser import Parser from smnp.token.type import TokenType -from smnp.type.model import Type -class TypeSpecifier(Node): - - @classmethod - def _parse(cls, input): - return abstractIterableParser(TypeSpecifier, TokenType.OPEN_ANGLE, TokenType.CLOSE_ANGLE, - Parser.doAssert(cls._specifierItem(), "type"))(input) - - @classmethod - def _specifierItem(cls): - return Parser.oneOf( - TypeNode.parse, - cls.parse - ) - -class TypeSpecifiers(Node): - pass - - -class TypeNode(AccessNode): +class Type(Node): def __init__(self, pos): super().__init__(pos) + self.children = [NoneNode(), NoneNode()] @property def type(self): @@ -45,19 +28,40 @@ class TypeNode(AccessNode): self[1] = value @classmethod - def _parse(cls, input): - def createNode(type, specifiers): - node = TypeNode(type.pos) - node.type = Type[type.value.upper()] - node.specifiers = specifiers - return node + def withValues(cls, pos, type, specifiers=NoneNode()): + node = cls(pos) + node.type = type + node.specifiers = specifiers + return node - return Parser.allOf( - cls._rawTypeParser(), - Parser.many(TypeSpecifier.parse, lambda specifiers, pos: TypeSpecifiers.withChildren(specifiers, pos)), - createNode=createNode - )(input) - @classmethod - def _rawTypeParser(cls): - return Parser.terminalParser(TokenType.TYPE, lambda val, pos: TypeNode.withValue(val, pos)) \ No newline at end of file +class TypesList(Node): + pass + + +def TypesListParser(input): + return abstractIterableParser( + TypesList, + TokenType.OPEN_ANGLE, + TokenType.CLOSE_ANGLE, + TypeParser + )(input) + + +class TypeSpecifiers(Node): + pass + + +def TypeParser(input): + typeWithSpecifier = Parser.allOf( + TypeLiteralParser, + Parser.many(TypesListParser, createNode=TypeSpecifiers.withChildren), + createNode=lambda type, specifiers: Type.withValues(type.pos, type, specifiers), + name="type with specifiers?" + ) + + return Parser.oneOf( + typeWithSpecifier, + TypesListParser, + name="mult. types or type with specifier" + )(input) diff --git a/smnp/ast/node/unit.py b/smnp/ast/node/unit.py new file mode 100644 index 0000000..2a1b6cd --- /dev/null +++ b/smnp/ast/node/unit.py @@ -0,0 +1,36 @@ +from smnp.ast.node.atom import AtomParser +from smnp.ast.node.operator import BinaryOperator, UnaryOperator, Operator +from smnp.ast.parser import Parser +from smnp.token.type import TokenType + + +class MinusOperator(UnaryOperator): + pass + + +class Access(BinaryOperator): + pass + + +def UnitParser(input): + minusOperator = Parser.allOf( + Parser.terminal(TokenType.MINUS, createNode=Operator.withValue), + Parser.doAssert(AtomParser, "atom"), + createNode=MinusOperator.withValues, + name="minus" + ) + + atom2 = Parser.oneOf( + minusOperator, + AtomParser, + name="atom2" + ) + + return Parser.leftAssociativeOperatorParser( + atom2, + [TokenType.DOT], + Parser.doAssert(atom2, "atom"), + createNode=lambda left, op, right: Access.withValues(left, op, right), + name="unit" + )(input) + diff --git a/smnp/ast/node/valuable.py b/smnp/ast/node/valuable.py new file mode 100644 index 0000000..8e9466e --- /dev/null +++ b/smnp/ast/node/valuable.py @@ -0,0 +1,22 @@ +from smnp.ast.node.model import Node +from smnp.ast.node.none import NoneNode + + +class Valuable(Node): + def __init__(self, pos): + super().__init__(pos) + self.children = [NoneNode()] + + @property + def value(self): + return self[0] + + @value.setter + def value(self, value): + self[0] = value + + @classmethod + def withValue(cls, value, pos=None): + node = cls(value.pos if pos is None else pos) + node.value = value + return node \ No newline at end of file diff --git a/smnp/ast/parser.py b/smnp/ast/parser.py index bad5af7..1dc6f00 100644 --- a/smnp/ast/parser.py +++ b/smnp/ast/parser.py @@ -5,15 +5,43 @@ from smnp.error.syntax import SyntaxException def parse(input): - from smnp.ast.node.program import Program - return Program.parse(input).node + from smnp.ast.node.program import ProgramParser + return ProgramParser(input).node class Parser: + def __init__(self, parse, name=None, parsers=None): + if parsers is None: + parsers = [] + self.parsers = parsers + + self._parse = parse + if name is None: + name = parse.__name__ + self.name = name + + def parse(self, input): + result = self._parse(input) + if result is None: + return ParseResult.FAIL() + + if not isinstance(result, ParseResult): + raise RuntimeError(f"_parse() method of '{self.__class__.__name__}' class haven't returned ParseResult object") + + return result + + def __call__(self, input): + return self.parse(input) + + def __str__(self): + return self.name + + def __repr__(self): + return self.__str__() # a -> A @staticmethod - def terminalParser(expectedType, createNode=None, doAssert=False): + def terminal(expectedType, createNode=None, doAssert=False): def provideNode(value, pos): if createNode is None: return IgnoredNode(pos) @@ -30,17 +58,22 @@ class Parser: return ParseResult.FAIL() - return parse + return Parser(parse, name=expectedType.name.lower()) # oneOf -> a | b | c | ... @staticmethod - def oneOf(*parsers, exception=None): + def oneOf(*parsers, assertExpected=None, exception=None, name="or"): def combinedParser(input): snap = input.snapshot() for parser in parsers: value = parser(input) if value.result: return value + input.reset(snap) + + if assertExpected is not None: + found = f", found '{input.current().rawValue}'" if input.hasCurrent() else "" + raise SyntaxException(f"Expected {assertExpected}{found}", input.currentPos()) if exception is not None: if callable(exception): @@ -48,15 +81,14 @@ class Parser: else: raise exception - input.reset(snap) return ParseResult.FAIL() - return combinedParser + return Parser(combinedParser, name=name, parsers=parsers) # allOf -> a b c ... @staticmethod - def allOf(*parsers, createNode, exception=None): + def allOf(*parsers, createNode, exception=None, name="all"): if len(parsers) == 0: raise RuntimeError("Pass one parser at least") @@ -87,29 +119,36 @@ class Parser: return ParseResult.OK(node) - - return extendedParser + return Parser(extendedParser, name=name, parsers=parsers) # leftAssociative -> left | left OP right @staticmethod - def leftAssociativeOperatorParser(leftParser, operatorTokenType, rightParser, createNode): + def leftAssociativeOperatorParser(leftParser, operatorTokenTypes, rightParser, createNode, name="leftAssoc"): + from smnp.ast.node.operator import Operator + def parse(input): + operatorParser = Parser.oneOfTerminals(*operatorTokenTypes, createNode=lambda val, pos: Operator.withChildren([val], pos)) left = leftParser(input) if left.result: - while Parser.terminalParser(operatorTokenType)(input).result: + operator = operatorParser(input) + while operator.result: right = rightParser(input) - left = ParseResult.OK(createNode(left.node, right.node)) - + left = ParseResult.OK(createNode(left.node, operator.node, right.node)) + operator = operatorParser(input) return left return ParseResult.FAIL() - return parse + return Parser(parse, name=name, parsers=[leftParser, '|'.join([t.value for t in operatorTokenTypes]), rightParser]) + + @staticmethod + def oneOfTerminals(*tokenTypes, createNode=None): + return Parser.oneOf(*[Parser.terminal(expectedType, createNode=createNode) for expectedType in tokenTypes], name='|'.join([t.value for t in tokenTypes])) # loop -> start item* end @staticmethod - def loop(startParser, itemParser, endParser, createNode): + def loop(startParser, itemParser, endParser, createNode, name="loop"): def parse(input): items = [] start = startParser(input) @@ -125,10 +164,10 @@ class Parser: return ParseResult.FAIL() - return parse + return Parser(parse, name, parsers=[startParser, itemParser, endParser]) @staticmethod - def doAssert(parser, expected): + def doAssert(parser, expected, name="!!"): def parse(input): result = parser(input) @@ -139,10 +178,10 @@ class Parser: return result - return parse + return Parser(parse, name, parsers=parser) @staticmethod - def optional(parser): + def optional(parser, name="??"): def parse(input): result = parser(input) if result.result: @@ -150,10 +189,14 @@ class Parser: return ParseResult.OK(NoneNode()) - return parse + return Parser(parse, name, parsers=[parser]) @staticmethod - def many(parser, createNode): + def epsilon(): + return lambda *args: ParseResult.OK(NoneNode()) + + @staticmethod + def many(parser, createNode, name="*"): def parse(input): results = [] snap = input.snapshot() @@ -167,4 +210,15 @@ class Parser: input.reset(snap) return ParseResult.OK(createNode(results, pos) if len(results) > 0 else NoneNode()) - return parse + return Parser(parse, name, parsers=[parser]) + + @staticmethod + def wrap(parser, createNode): + def parse(input): + result = parser(input) + if result.result: + return ParseResult.OK(createNode(result.node)) + + return result + + return parse \ No newline at end of file diff --git a/smnp/audio/sound.py b/smnp/audio/sound.py index d46aadb..fed571e 100644 --- a/smnp/audio/sound.py +++ b/smnp/audio/sound.py @@ -10,6 +10,9 @@ class Sound: def play(self): sd.play(self.data, self.fs, blocking=True) + def __eq__(self, other): + return self.file == other.file and self.data == other.data + def __str__(self): return f"sound[{self.file}]" diff --git a/smnp/calc.py b/smnp/calc.py new file mode 100644 index 0000000..e5f8115 --- /dev/null +++ b/smnp/calc.py @@ -0,0 +1,66 @@ +from smnp.ast.node.model import Node +from smnp.ast.parser import Parser +from smnp.token.tokenizer import tokenize +from smnp.token.type import TokenType + + +class Atom(Node): + def __init__(self, value, pos): + super().__init__(pos) + self.children = [value] + + @property + def value(self): + return self[0] + +class Operation(Node): + def __init__(self, left, op, right, pos): + super().__init__(pos) + self.children = [left, op, right] + + @property + def left(self): + return self[0] + + @property + def operator(self): + return self[1] + + @property + def right(self): + return self[2] + +def atom(): + return Parser.oneOfTerminals(TokenType.INTEGER, TokenType.NOTE, TokenType.STRING, createNode=lambda val, pos: Atom(val, pos)) + +def chain(): + return Parser.leftAssociativeOperatorParser(atom(), [TokenType.DOT], atom(), lambda left, op, right: Operation(left, op, right, op.pos), name="chain") + +def factor(): + return Parser.leftAssociativeOperatorParser(chain(), [TokenType.DOUBLE_ASTERISK], chain(), lambda left, op, right: Operation(left, op, right, op.pos), name="factor") + +def term(): + return Parser.leftAssociativeOperatorParser(factor(), [TokenType.ASTERISK, TokenType.SLASH], factor(), lambda left, op, right: Operation(left, op, right, op.pos), name="term") + +def expr(): + return Parser.leftAssociativeOperatorParser(term(), [TokenType.PLUS, TokenType.MINUS], term(), lambda left, op, right: Operation(left, op, right, op.pos), name="expr") +# +def evaluate(node): + if type(node) == Atom: + return node.value + lhs = evaluate(node.left) + rhs = evaluate(node.right) + return { + "+": int(lhs) + int(rhs), + "*": int(lhs) * int(rhs), + "-": int(lhs) - int(rhs), + "/": int(lhs) / int(rhs), + "**": int(lhs) ** int(rhs) + }[node.operator.value] + +def draft(): + + tokens = tokenize(['"fesf fe" + "fsefsef" + "fsefs"']) + e = expr() + node = e(tokens).node + node.print() diff --git a/smnp/library/loader.py b/smnp/library/loader.py index 37eac21..c40f6f4 100644 --- a/smnp/library/loader.py +++ b/smnp/library/loader.py @@ -4,6 +4,6 @@ from smnp.program.interpreter import Interpreter def loadStandardLibrary(): - source = resource_string('smnp.library.code', 'main.mus').decode("utf-8") - return Interpreter.interpretString(source) - + mainSource = resource_string('smnp.library.code', 'main.mus').decode("utf-8") + env = Interpreter.interpretString(mainSource) + return env diff --git a/smnp/main.py b/smnp/main.py index 71cb648..d0b5b55 100644 --- a/smnp/main.py +++ b/smnp/main.py @@ -8,7 +8,10 @@ from smnp.program.interpreter import Interpreter def main(): try: stdLibraryEnv = loadStandardLibrary() - Interpreter.interpretFile(sys.argv[1], printAst=True, baseEnvironment=stdLibraryEnv) + 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 ecb7f65..a3fc3b2 100644 --- a/smnp/module/iterable/__init__.py +++ b/smnp/module/iterable/__init__.py @@ -1,4 +1,4 @@ -from smnp.module.iterable.function import combine, flat, map, range, get +from smnp.module.iterable.function import combine, map, range, get -functions = [ combine.function, flat.function, map.function, range.function ] +functions = [ combine.function, map.function, range.function ] methods = [ get.function ] \ No newline at end of file diff --git a/smnp/module/iterable/function/flat.py b/smnp/module/iterable/function/flat.py deleted file mode 100644 index f83c1ee..0000000 --- a/smnp/module/iterable/function/flat.py +++ /dev/null @@ -1,23 +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 allTypes - -_signature = varargSignature(allTypes()) -def _function(env, vararg): - return Type.list(doFlat(vararg, [])).decompose() - - -def doFlat(input, output=None): - if output is None: - output = [] - - for item in input: - if item.type == Type.LIST: - doFlat(item.value, output) - else: - output.append(item) - return output - - -function = Function(_signature, _function, 'flat') \ No newline at end of file diff --git a/smnp/module/iterable/function/get.py b/smnp/module/iterable/function/get.py index 30b5409..3381e68 100644 --- a/smnp/module/iterable/function/get.py +++ b/smnp/module/iterable/function/get.py @@ -12,7 +12,7 @@ def _function1(env, list, index): raise RuntimeException(f"Attempt to access item which is outside the list", None) -_signature2 = signature(ofType(Type.MAP), ofTypes(Type.INTEGER, Type.STRING, Type.NOTE, Type.TYPE)) +_signature2 = signature(ofType(Type.MAP), ofTypes(Type.INTEGER, Type.STRING, Type.NOTE, Type.BOOL, Type.TYPE)) def _function2(env, map, key): try: return map.value[key] diff --git a/smnp/module/iterable/function/map.py b/smnp/module/iterable/function/map.py index 7d27b5c..b65e6eb 100644 --- a/smnp/module/iterable/function/map.py +++ b/smnp/module/iterable/function/map.py @@ -4,7 +4,7 @@ from smnp.type.model import Type from smnp.type.signature.matcher.list import listOfMatchers, listMatches from smnp.type.signature.matcher.type import allTypes, ofTypes -_signature1 = varargSignature(listMatches(ofTypes(Type.INTEGER, Type.STRING, Type.NOTE, Type.TYPE), allTypes())) +_signature1 = varargSignature(listMatches(ofTypes(Type.INTEGER, Type.STRING, Type.NOTE, Type.BOOL, Type.TYPE), allTypes())) def _function1(env, vararg): map = {} for entry in vararg: @@ -14,7 +14,7 @@ def _function1(env, vararg): return Type.map(map) -_signature2 = signature(listOfMatchers(listMatches(ofTypes(Type.INTEGER, Type.STRING, Type.NOTE, Type.TYPE), allTypes()))) +_signature2 = signature(listOfMatchers(listMatches(ofTypes(Type.INTEGER, Type.STRING, Type.NOTE, Type.BOOL, Type.TYPE), allTypes()))) def _function2(env, list): map = {} for entry in list.value: diff --git a/smnp/module/iterable/function/range.py b/smnp/module/iterable/function/range.py index 2fe7191..8f0198e 100644 --- a/smnp/module/iterable/function/range.py +++ b/smnp/module/iterable/function/range.py @@ -6,17 +6,17 @@ from smnp.type.signature.matcher.type import ofType _signature1 = signature(ofType(Type.INTEGER)) def _function1(env, upper): - return Type.list(list(range(upper.value + 1))) + 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(list(range(lower.value, upper.value + 1))) + 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(list(range(lower.value, upper.value + 1, step.value))) + 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)) diff --git a/smnp/program/interpreter.py b/smnp/program/interpreter.py index fdd33e0..7bbe79e 100644 --- a/smnp/program/interpreter.py +++ b/smnp/program/interpreter.py @@ -9,15 +9,15 @@ from smnp.token.tokenizer import tokenize class Interpreter: @staticmethod - def interpretString(string, printTokens=False, printAst=False, baseEnvironment=None): - return Interpreter._interpret(string.splitlines(), printTokens, printAst, baseEnvironment) + def interpretString(string, printTokens=False, printAst=False, execute=True, baseEnvironment=None): + return Interpreter._interpret(string.splitlines(), printTokens, printAst, execute, baseEnvironment) @staticmethod - def interpretFile(file, printTokens=False, printAst=False, baseEnvironment=None): - return Interpreter._interpret(readLines(file), printTokens, printAst, baseEnvironment) + def interpretFile(file, printTokens=False, printAst=False, execute=True, baseEnvironment=None): + return Interpreter._interpret(readLines(file), printTokens, printAst, execute, baseEnvironment) @staticmethod - def _interpret(lines, printTokens=False, printAst=False, baseEnvironment=None): + def _interpret(lines, printTokens=False, printAst=False, execute=True, baseEnvironment=None): environment = createEnvironment() if baseEnvironment is not None: environment.extend(baseEnvironment) @@ -31,7 +31,8 @@ class Interpreter: if printAst: ast.print() - evaluate(ast, environment) + if execute: + evaluate(ast, environment) return environment except RuntimeException as e: diff --git a/smnp/runtime/evaluator.py b/smnp/runtime/evaluator.py index f83f1e6..d722577 100644 --- a/smnp/runtime/evaluator.py +++ b/smnp/runtime/evaluator.py @@ -1,9 +1,10 @@ -from smnp.ast.node.block import BlockNode -from smnp.ast.node.extend import ExtendNode -from smnp.ast.node.function import FunctionDefinitionNode -from smnp.ast.node.imports import ImportNode +from smnp.ast.node.block import Block +from smnp.ast.node.condition import IfElse +from smnp.ast.node.extend import Extend +from smnp.ast.node.function import FunctionDefinition +from smnp.ast.node.imports import Import from smnp.ast.node.program import Program -from smnp.ast.node.ret import ReturnNode +from smnp.ast.node.ret import Return from smnp.error.runtime import RuntimeException from smnp.type.model import Type @@ -68,20 +69,27 @@ class EvaluationResult(): def evaluate(node, environment): from smnp.runtime.evaluators.program import ProgramEvaluator - from smnp.runtime.evaluators.expression import expressionEvaluator - from smnp.runtime.evaluators.function import FunctionDefinitionEvaluator - from smnp.runtime.evaluators.extend import ExtendEvaluator + from smnp.runtime.evaluators.expression import expressionEvaluator + from smnp.runtime.evaluators.condition import IfElseStatementEvaluator from smnp.runtime.evaluators.block import BlockEvaluator from smnp.runtime.evaluators.imports import ImportEvaluator + from smnp.runtime.evaluators.function import FunctionDefinitionEvaluator from smnp.runtime.evaluators.function import ReturnEvaluator + from smnp.runtime.evaluators.extend import ExtendEvaluator result = Evaluator.oneOf( Evaluator.forNodes(ProgramEvaluator.evaluate, Program), - Evaluator.forNodes(ImportEvaluator.evaluate, ImportNode), - Evaluator.forNodes(FunctionDefinitionEvaluator.evaluate, FunctionDefinitionNode), - Evaluator.forNodes(ExtendEvaluator.evaluate, ExtendNode), - Evaluator.forNodes(BlockEvaluator.evaluate, BlockNode), - Evaluator.forNodes(ReturnEvaluator.evaluate, ReturnNode), + Evaluator.forNodes(IfElseStatementEvaluator.evaluate, IfElse), + Evaluator.forNodes(BlockEvaluator.evaluate, Block), + Evaluator.forNodes(ImportEvaluator.evaluate, Import), + Evaluator.forNodes(FunctionDefinitionEvaluator.evaluate, FunctionDefinition), + Evaluator.forNodes(ReturnEvaluator.evaluate, Return), + Evaluator.forNodes(ExtendEvaluator.evaluate, Extend), + #Evaluator.forNodes(ImportEvaluator.evaluate, ImportNode), + #Evaluator.forNodes(FunctionDefinitionEvaluator.evaluate, FunctionDefinitionNode), + #Evaluator.forNodes(ExtendEvaluator.evaluate, ExtendNode), + #Evaluator.forNodes(BlockEvaluator.evaluate, BlockNode), + #Evaluator.forNodes(ReturnEvaluator.evaluate, ReturnNode), expressionEvaluator() )(node, environment) diff --git a/smnp/runtime/evaluators/access.py b/smnp/runtime/evaluators/access.py index bb610cb..85f5924 100644 --- a/smnp/runtime/evaluators/access.py +++ b/smnp/runtime/evaluators/access.py @@ -1,5 +1,4 @@ -from smnp.ast.node.identifier import IdentifierNode -from smnp.ast.node.invocation import FunctionCallNode +from smnp.ast.node.identifier import Identifier, FunctionCall from smnp.error.runtime import RuntimeException from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluators.expression import expressionEvaluator @@ -14,15 +13,15 @@ class AccessEvaluator(Evaluator): left = expressionEvaluator(doAssert=True)(node.left, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult right = node.right - if type(right) == IdentifierNode: + if type(right) == Identifier: try: return left.properties[right.value] except KeyError: raise RuntimeException(f"Unknown property '{right.value}' of type '{left.type.name.lower()}'", right.pos) - if type(right) == FunctionCallNode: + if type(right) == FunctionCall: try: - arguments = abstractIterableEvaluator(expressionEvaluator(True))(right.arguments, environment) + arguments = abstractIterableEvaluator(expressionEvaluator(doAssert=True))(right.arguments, environment) return environment.invokeMethod(left, right.name.value, arguments) except RuntimeException as e: raise updatePos(e, right) diff --git a/smnp/runtime/evaluators/assignment.py b/smnp/runtime/evaluators/assignment.py index 4e89c7f..f9e9234 100644 --- a/smnp/runtime/evaluators/assignment.py +++ b/smnp/runtime/evaluators/assignment.py @@ -1,4 +1,3 @@ -from smnp.error.runtime import RuntimeException from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluators.expression import expressionEvaluator @@ -7,11 +6,8 @@ class AssignmentEvaluator(Evaluator): @classmethod def evaluator(cls, node, environment): - target = node.target.value - if target.startswith("_"): - raise RuntimeException("Declaration and assignation variables with names starting with '_' is not allowed", node.target.pos) - - value = expressionEvaluator(doAssert=True)(node.value, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult + 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 diff --git a/smnp/runtime/evaluators/asterisk.py b/smnp/runtime/evaluators/asterisk.py index 2c634ae..2d4223e 100644 --- a/smnp/runtime/evaluators/asterisk.py +++ b/smnp/runtime/evaluators/asterisk.py @@ -1,4 +1,4 @@ -from smnp.ast.node.identifier import IdentifierNode +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 @@ -40,7 +40,7 @@ class AsteriskEvaluator(Evaluator): @classmethod def _automaticNamedVariable(cls, iteratorNode, environment, prefix=''): - if type(iteratorNode) == IdentifierNode: + if type(iteratorNode) == Identifier: return cls._automaticVariableName(environment, prefix, iteratorNode.value, False) else: return cls._automaticVariableName(environment, prefix, '', True) diff --git a/smnp/runtime/evaluators/atom.py b/smnp/runtime/evaluators/atom.py new file mode 100644 index 0000000..0be1822 --- /dev/null +++ b/smnp/runtime/evaluators/atom.py @@ -0,0 +1,94 @@ +from smnp.ast.node.atom import StringLiteral, IntegerLiteral, NoteLiteral, BoolLiteral, TypeLiteral +from smnp.ast.node.identifier import Identifier +from smnp.ast.node.list import List +from smnp.ast.node.map import Map +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.runtime.evaluators.iterable import abstractIterableEvaluator +from smnp.runtime.tools.error import updatePos +from smnp.type.model import Type + + +class IntegerEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + return Type.integer(node.value) + + +class StringEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + return Type.string(node.value) + + +class NoteEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + return Type.note(node.value) + + +class BoolEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + return Type.bool(node.value) + + +class TypeEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + return Type.type(node.value) + + +class ListEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + list = abstractIterableEvaluator(expressionEvaluator(doAssert=True))(node, environment) + return Type.list(list) + + +class MapEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + map = {} + exprEvaluator = expressionEvaluator(doAssert=True) + for entry in node.children: + key = exprEvaluator(entry.key, environment).value + if key in map: + raise RuntimeException(f"Duplicated key '{key.stringify()}' found in map", entry.pos) + map[key] = exprEvaluator(entry.value, environment).value + + return Type.map(map) + + +class IdentifierEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + try: + return environment.findVariable(node.value) + except RuntimeException as e: + raise updatePos(e, node) + + +class AtomEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + return Evaluator.oneOf( + Evaluator.forNodes(StringEvaluator.evaluate, StringLiteral), + Evaluator.forNodes(IntegerEvaluator.evaluate, IntegerLiteral), + Evaluator.forNodes(NoteEvaluator.evaluate, NoteLiteral), + Evaluator.forNodes(BoolEvaluator.evaluate, BoolLiteral), + Evaluator.forNodes(TypeEvaluator.evaluate, TypeLiteral), + Evaluator.forNodes(IdentifierEvaluator.evaluate, Identifier), + Evaluator.forNodes(ListEvaluator.evaluate, List), + Evaluator.forNodes(MapEvaluator.evaluate, Map) + )(node, environment).value diff --git a/smnp/runtime/evaluators/bool.py b/smnp/runtime/evaluators/bool.py new file mode 100644 index 0000000..66e624c --- /dev/null +++ b/smnp/runtime/evaluators/bool.py @@ -0,0 +1,9 @@ +from smnp.runtime.evaluator import Evaluator +from smnp.type.model import Type + + +class BoolEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + return Type.bool(node.value) \ No newline at end of file diff --git a/smnp/runtime/evaluators/condition.py b/smnp/runtime/evaluators/condition.py new file mode 100644 index 0000000..62b5d79 --- /dev/null +++ b/smnp/runtime/evaluators/condition.py @@ -0,0 +1,35 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator, evaluate +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.type.model import Type + + +class IfElseEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + condition = expressionEvaluator(doAssert=True)(node.condition, environment).value + + if condition.type != Type.BOOL: + raise RuntimeException(f"Only {Type.BOOL.name.lower()} types can be used as conditions in conditional expression", node.condition.pos) + + if condition.value: + return expressionEvaluator(doAssert=True)(node.ifNode, environment).value + else: + return expressionEvaluator(doAssert=True)(node.elseNode, environment).value + + +class IfElseStatementEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + condition = expressionEvaluator(doAssert=True)(node.condition, environment).value + + if condition.type != Type.BOOL: + raise RuntimeException( + f"Only {Type.BOOL.name.lower()} types can be used as conditions in conditional expression", node.condition.pos) + + if condition.value: + evaluate(node.ifNode, environment) + else: + evaluate(node.elseNode, environment) \ No newline at end of file diff --git a/smnp/runtime/evaluators/expression.py b/smnp/runtime/evaluators/expression.py index fee2031..d9ea964 100644 --- a/smnp/runtime/evaluators/expression.py +++ b/smnp/runtime/evaluators/expression.py @@ -1,14 +1,9 @@ -from smnp.ast.node.access import AccessNode -from smnp.ast.node.assignment import AssignmentNode -from smnp.ast.node.asterisk import AsteriskNode -from smnp.ast.node.identifier import IdentifierNode -from smnp.ast.node.integer import IntegerLiteralNode -from smnp.ast.node.invocation import FunctionCallNode -from smnp.ast.node.list import ListNode -from smnp.ast.node.map import MapNode -from smnp.ast.node.note import NoteLiteralNode -from smnp.ast.node.string import StringLiteralNode -from smnp.ast.node.type import TypeNode +from smnp.ast.node.condition import IfElse +from smnp.ast.node.expression import Sum, Relation +from smnp.ast.node.factor import NotOperator, Power, Loop +from smnp.ast.node.identifier import FunctionCall, Assignment +from smnp.ast.node.term import Product +from smnp.ast.node.unit import MinusOperator, Access from smnp.error.runtime import RuntimeException from smnp.runtime.evaluator import Evaluator from smnp.type.model import Type @@ -16,36 +11,38 @@ from smnp.type.model import Type def expressionEvaluator(doAssert=False): def evaluateExpression(node, environment): - from smnp.runtime.evaluators.string import StringEvaluator - from smnp.runtime.evaluators.integer import IntegerEvaluator - from smnp.runtime.evaluators.note import NoteEvaluator - from smnp.runtime.evaluators.identifier import IdentifierEvaluator - from smnp.runtime.evaluators.list import ListEvaluator - from smnp.runtime.evaluators.function import FunctionCallEvaluator + from smnp.runtime.evaluators.function import FunctionCallEvaluator + from smnp.runtime.evaluators.minus import MinusEvaluator + from smnp.runtime.evaluators.atom import AtomEvaluator + from smnp.runtime.evaluators.access import AccessEvaluator + from smnp.runtime.evaluators.negation import NotEvaluator + from smnp.runtime.evaluators.power import PowerEvaluator + from smnp.runtime.evaluators.loop import LoopEvaluator + from smnp.runtime.evaluators.assignment import AssignmentEvaluator + from smnp.runtime.evaluators.product import ProductEvaluator - from smnp.runtime.evaluators.access import AccessEvaluator - from smnp.runtime.evaluators.assignment import AssignmentEvaluator - from smnp.runtime.evaluators.asterisk import AsteriskEvaluator - from smnp.runtime.evaluators.map import MapEvaluator - from smnp.runtime.evaluators.type import TypeEvaluator - result = Evaluator.oneOf( - Evaluator.forNodes(FunctionCallEvaluator.evaluate, FunctionCallNode), - Evaluator.forNodes(StringEvaluator.evaluate, StringLiteralNode), - Evaluator.forNodes(IntegerEvaluator.evaluate, IntegerLiteralNode), - Evaluator.forNodes(NoteEvaluator.evaluate, NoteLiteralNode), - Evaluator.forNodes(TypeEvaluator.evaluate, TypeNode), - Evaluator.forNodes(IdentifierEvaluator.evaluate, IdentifierNode), - Evaluator.forNodes(ListEvaluator.evaluate, ListNode), - Evaluator.forNodes(AccessEvaluator.evaluate, AccessNode), - Evaluator.forNodes(AssignmentEvaluator.evaluate, AssignmentNode), - Evaluator.forNodes(AsteriskEvaluator.evaluate, AsteriskNode), - Evaluator.forNodes(MapEvaluator.evaluate, MapNode) - )(node, environment) + from smnp.runtime.evaluators.sum import SumEvaluator + from smnp.runtime.evaluators.relation import RelationEvaluator + from smnp.runtime.evaluators.condition import IfElseEvaluator + result = Evaluator.oneOf( + Evaluator.forNodes(FunctionCallEvaluator.evaluate, FunctionCall), + Evaluator.forNodes(MinusEvaluator.evaluate, MinusOperator), + Evaluator.forNodes(AccessEvaluator.evaluate, Access), + Evaluator.forNodes(NotEvaluator.evaluate, NotOperator), + Evaluator.forNodes(PowerEvaluator.evaluate, Power), + Evaluator.forNodes(LoopEvaluator.evaluate, Loop), + Evaluator.forNodes(AssignmentEvaluator.evaluate, Assignment), + Evaluator.forNodes(ProductEvaluator.evaluate, Product), + Evaluator.forNodes(SumEvaluator.evaluate, Sum), + Evaluator.forNodes(RelationEvaluator.evaluate, Relation), + Evaluator.forNodes(IfElseEvaluator.evaluate, IfElse), + AtomEvaluator.evaluate + )(node, environment) - if doAssert and result.result and result.value.type == Type.VOID: - raise RuntimeException(f"Expected expression", node.pos) + if doAssert and result.result and result.value.type == Type.VOID: + raise RuntimeException(f"Expected expression", node.pos) + + return result - return result return evaluateExpression - diff --git a/smnp/runtime/evaluators/extend.py b/smnp/runtime/evaluators/extend.py index f1747f9..546487c 100644 --- a/smnp/runtime/evaluators/extend.py +++ b/smnp/runtime/evaluators/extend.py @@ -17,11 +17,11 @@ class ExtendEvaluator(Evaluator): @classmethod def _typeToMethodSignature(cls, node): if type(node.specifiers) == NoneNode: - return signature(ofType(node.type)) + return signature(ofType(node.type.value)) - elif node.type == Type.LIST: + elif node.type.value == Type.LIST: return signature(listSpecifier(node.specifiers[0])) - elif node.type == Type.MAP: + elif node.type.value == Type.MAP: return signature(mapSpecifier(node.specifiers[0], node.specifiers[1])) @classmethod diff --git a/smnp/runtime/evaluators/imports.py b/smnp/runtime/evaluators/imports.py index b971650..88419f4 100644 --- a/smnp/runtime/evaluators/imports.py +++ b/smnp/runtime/evaluators/imports.py @@ -1,4 +1,3 @@ -from smnp.ast.node.none import NoneNode from smnp.program.interpreter import Interpreter from smnp.runtime.evaluator import Evaluator @@ -7,13 +6,6 @@ class ImportEvaluator(Evaluator): @classmethod def evaluator(cls, node, environment): - if type(node.type) == NoneNode: - cls._evaluateCodeImport(node, environment) - else: - raise RuntimeError("Importing types is not implemented yet") - - @classmethod - def _evaluateCodeImport(cls, node, environment): source = node.source - newEnvironment = Interpreter.interpretFile(source.value) + newEnvironment = Interpreter.interpretFile(source.value, baseEnvironment=environment) environment.extend(newEnvironment) \ No newline at end of file diff --git a/smnp/runtime/evaluators/loop.py b/smnp/runtime/evaluators/loop.py new file mode 100644 index 0000000..e9392bd --- /dev/null +++ b/smnp/runtime/evaluators/loop.py @@ -0,0 +1,104 @@ +from smnp.ast.node.none import NoneNode +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator, evaluate +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.type.model import Type + + +class LoopEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + iterator = expressionEvaluator(doAssert=True)(node.left, environment).value + parameters = [ identifier.value for identifier in node.parameters ] if type(node.parameters) != NoneNode() else [] + + try: + environment.scopes.append({}) + + output = { + Type.INTEGER: cls.numberEvaluator, + Type.BOOL: cls.boolEvaluator, + Type.LIST: cls.listEvaluator, + Type.MAP: cls.mapEvaluator + }[iterator.type](node, environment, iterator, parameters) + + environment.scopes.pop(-1) + 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): + output = [] + + + if len(parameters) > 1: + raise RuntimeException(f"Loop with numeric iterator can handle only one parameter", node.parameters.pos) + + for i in range(evaluatedIterator.value): + if len(parameters) > 0: + environment.scopes[-1][parameters[0]] = Type.integer(i) + + output.append(evaluate(node.right, environment).value) + + + + return output + + @classmethod + def boolEvaluator(cls, node, environment, evaluatedIterator, parameters): + output = [] + + if len(parameters) > 0: + raise RuntimeException(f"Loop with logic iterator can't' handle any parameters", node.parameters.pos) + + condition = evaluatedIterator + while condition.value: + 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): + output = [] + + if len(parameters) > 2: + raise RuntimeException(f"Loop with list iterator can handle only two parameters", node.parameters.pos) + + for i, value in enumerate(evaluatedIterator.value): + if len(parameters) == 1: + environment.scopes[-1][parameters[0]] = value + if len(parameters) == 2: + environment.scopes[-1][parameters[0]] = Type.integer(i) + environment.scopes[-1][parameters[1]] = value + + output.append(evaluate(node.right, environment).value) + + return output + + + @classmethod + def mapEvaluator(cls, node, environment, evaluatedIterator, parameters): + output = [] + + if len(parameters) > 3: + raise RuntimeException(f"Loop with map iterator can handle only three parameters", node.parameters.pos) + + i = 0 + for key, value in evaluatedIterator.value.items(): + if len(parameters) == 1: + environment.scopes[-1][parameters[0]] = value + if len(parameters) == 2: + environment.scopes[-1][parameters[0]] = key + environment.scopes[-1][parameters[1]] = value + if len(parameters) == 3: + environment.scopes[-1][parameters[0]] = Type.integer(i) + environment.scopes[-1][parameters[1]] = key + environment.scopes[-1][parameters[2]] = value + i += 1 + + output.append(evaluate(node.right, environment).value) + + return output diff --git a/smnp/runtime/evaluators/minus.py b/smnp/runtime/evaluators/minus.py new file mode 100644 index 0000000..5c20118 --- /dev/null +++ b/smnp/runtime/evaluators/minus.py @@ -0,0 +1,31 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator, evaluate +from smnp.type.model import Type + + +class MinusEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + value = evaluate(node.value, environment).value + try: + return { + Type.INTEGER: cls.evaluateForInteger, + Type.STRING: cls.evaluateForString, + Type.LIST: cls.evaluateForList + }[value.type](value.value) + except KeyError: + raise RuntimeException(f"Type {value.type.name.lower()} does not support '{node.operator.value}' operator", node.pos) + + @classmethod + def evaluateForInteger(cls, value): + + return Type.integer(-value) + + @classmethod + def evaluateForString(cls, value): + return Type.string(value[::-1]) + + @classmethod + def evaluateForList(cls, value): + return Type.list(value[::-1]) \ No newline at end of file diff --git a/smnp/runtime/evaluators/negation.py b/smnp/runtime/evaluators/negation.py new file mode 100644 index 0000000..86206ab --- /dev/null +++ b/smnp/runtime/evaluators/negation.py @@ -0,0 +1,16 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.type.model import Type + + +class NotEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + value = expressionEvaluator(doAssert=True)(node.value, environment).value + + if value.type != Type.BOOL: + raise RuntimeException(f"Operator '{node.operator.value}' is supported only by {Type.BOOL.name.lower()} type", node.value.pos) + + return Type.bool(not value.value) \ No newline at end of file diff --git a/smnp/runtime/evaluators/power.py b/smnp/runtime/evaluators/power.py new file mode 100644 index 0000000..7168d48 --- /dev/null +++ b/smnp/runtime/evaluators/power.py @@ -0,0 +1,20 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.type.model import Type + + +class PowerEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + left = expressionEvaluator(doAssert=True)(node.left, environment).value + right = expressionEvaluator(doAssert=True)(node.right, environment).value + + if left.type != Type.INTEGER: + raise RuntimeException( f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.left.pos) + + if right.type != Type.INTEGER: + raise RuntimeException( f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.right.pos) + + return Type.integer(int(left.value ** right.value)) \ No newline at end of file diff --git a/smnp/runtime/evaluators/product.py b/smnp/runtime/evaluators/product.py new file mode 100644 index 0000000..ecf513f --- /dev/null +++ b/smnp/runtime/evaluators/product.py @@ -0,0 +1,31 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.type.model import Type + + +class ProductEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + left = expressionEvaluator(doAssert=True)(node.left, environment).value + right = expressionEvaluator(doAssert=True)(node.right, environment).value + + if left.type != Type.INTEGER: + raise RuntimeException( + f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.left.pos) + + if right.type != Type.INTEGER: + raise RuntimeException( + f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.right.pos) + + if node.operator.value == "*": + return Type.integer(int(left.value * right.value)) + + if node.operator.value == "/": + if right.value == 0: + raise RuntimeException("Attempt to divide by 0", node.right.pos) + return Type.integer(int(left.value / right.value)) + + raise RuntimeError("This line should never be reached") + diff --git a/smnp/runtime/evaluators/relation.py b/smnp/runtime/evaluators/relation.py new file mode 100644 index 0000000..0708d65 --- /dev/null +++ b/smnp/runtime/evaluators/relation.py @@ -0,0 +1,46 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.type.model import Type + + +class RelationEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + left = expressionEvaluator(doAssert=True)(node.left, environment).value + right = expressionEvaluator(doAssert=True)(node.right, environment).value + + if node.operator.value == "==": + return cls.equalOperatorEvaluator(left, node.operator, right) + + if node.operator.value == "!=": + return cls.notEqualOperatorEvaluator(left, node.operator, right) + + return cls.otherRelationOperatorsEvaluator(left, node.operator, right) + + @classmethod + def equalOperatorEvaluator(cls, left, operator, right): + return Type.bool(left.value == right.value) + + @classmethod + def notEqualOperatorEvaluator(cls, left, operator, right): + return Type.bool(left.value != right.value) + + @classmethod + def otherRelationOperatorsEvaluator(cls, left, operator, right): + if left.type == right.type == Type.INTEGER: + if operator.value == ">": + return Type.bool(left.value > right.value) + + if operator.value == ">=": + return Type.bool(left.value >= right.value) + + if operator.value == "<": + return Type.bool(left.value < right.value) + + if operator.value == "<=": + return Type.bool(left.value < right.value) + + raise RuntimeException(f"Operator {operator.value} is not supported by {left.type.name.lower()} and {right.type.name.lower()} types", operator.pos) + diff --git a/smnp/runtime/evaluators/sum.py b/smnp/runtime/evaluators/sum.py new file mode 100644 index 0000000..1f1bdcd --- /dev/null +++ b/smnp/runtime/evaluators/sum.py @@ -0,0 +1,66 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.type.model import Type + + +class SumEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + left = expressionEvaluator(doAssert=True)(node.left, environment).value + right = expressionEvaluator(doAssert=True)(node.right, environment).value + + if left.type == right.type == Type.INTEGER: + return cls.integerEvaluator(left, node.operator, right) + + if left.type == right.type == Type.STRING: + return cls.stringEvaluator(left, node.operator, right) + + if left.type == right.type == Type.LIST: + return cls.listEvaluator(left, node.operator, right) + + if left.type == right.type == Type.MAP: + return cls.mapEvaluator(left, node.operator, right) + + raise RuntimeException(f"Operator {node.operator.value} is not supported by {left.type.name.lower()} and {right.type.name.lower()} types", node.operator.pos) + + @classmethod + def integerEvaluator(cls, left, operator, right): + if operator.value == "+": + return Type.integer(left.value + right.value) + + if operator.value == "-": + return Type.integer(left.value - right.value) + + raise RuntimeError("This line should never be reached") + + @classmethod + def stringEvaluator(cls, left, operator, right): + if operator.value == "+": + return Type.string(left.value + right.value) + + if operator.value == "-": + raise RuntimeException(f"Operator {operator.value} is not supported by string types", operator.pos) + + raise RuntimeError("This line should never be reached") + + @classmethod + def listEvaluator(cls, left, operator, right): + if operator.value == "+": + return Type.list(left.value + right.value) + + if operator.value == "-": + raise RuntimeException(f"Operator {operator.value} is not supported by list types", operator.pos) + + raise RuntimeError("This line should never be reached") + + @classmethod + def mapEvaluator(cls, left, operator, right): + if operator.value == "+": + return Type.map({**left.value, **right.value}) + + if operator.value == "-": + raise RuntimeException(f"Operator {operator.value} is not supported by map types", operator.pos) + + raise RuntimeError("This line should never be reached") \ No newline at end of file diff --git a/smnp/runtime/evaluators/unit.py b/smnp/runtime/evaluators/unit.py new file mode 100644 index 0000000..d6a6a76 --- /dev/null +++ b/smnp/runtime/evaluators/unit.py @@ -0,0 +1,34 @@ +from smnp.ast.node.identifier import Identifier, FunctionCall +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import Evaluator +from smnp.runtime.evaluators.expression import expressionEvaluator +from smnp.runtime.evaluators.iterable import abstractIterableEvaluator +from smnp.runtime.tools.error import updatePos + + +class UnitEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + return + + +class AccessEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + left = expressionEvaluator(doAssert=True)(node.left, environment).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult + right = node.right + + if type(right) == Identifier: + try: + return left.properties[right.value] + except KeyError: + raise RuntimeException(f"Unknown property '{right.value}' of type '{left.type.name.lower()}'", right.pos) + + if type(right) == FunctionCall: + try: + arguments = abstractIterableEvaluator(expressionEvaluator(doAssert=True))(right.arguments, environment) + return environment.invokeMethod(left, right.name.value, arguments) + except RuntimeException as e: + raise updatePos(e, right) \ No newline at end of file diff --git a/smnp/runtime/tools/signature.py b/smnp/runtime/tools/signature.py index 934fca8..baf8d35 100644 --- a/smnp/runtime/tools/signature.py +++ b/smnp/runtime/tools/signature.py @@ -1,5 +1,6 @@ +from smnp.ast.node import type as ast from smnp.ast.node.none import NoneNode -from smnp.ast.node.type import TypeNode, TypeSpecifier +from smnp.ast.node.type import TypesList from smnp.error.runtime import RuntimeException from smnp.function.signature import varargSignature, signature from smnp.runtime.tools.error import updatePos @@ -16,9 +17,9 @@ def argumentsNodeToMethodSignature(node): argumentsCount = len(node.children) for i, child in enumerate(node.children): matchers = { - TypeNode: (lambda c: c.type, typeMatcher), + ast.Type: (lambda c: c.type, typeMatcher), NoneNode: (lambda c: c.type, lambda c: allTypes()), - TypeSpecifier: (lambda c: c, multipleTypeMatcher) + TypesList: (lambda c: c, multipleTypeMatcher) } evaluatedMatcher = matchers[type(child.type)][1](matchers[type(child.type)][0](child)) if child.vararg: @@ -49,10 +50,10 @@ def multipleTypeMatcher(typeNode): def typeMatcher(typeNode): if type(typeNode.specifiers) == NoneNode: - return ofType(typeNode.type) - elif typeNode.type == Type.LIST and len(typeNode.specifiers) == 1: + return ofType(typeNode.type.value) + elif typeNode.type.value == Type.LIST and len(typeNode.specifiers) == 1: return listSpecifier(typeNode.specifiers[0]) - elif typeNode.type == Type.MAP and len(typeNode.specifiers) == 2: + elif typeNode.type.value == Type.MAP and len(typeNode.specifiers) == 2: return mapSpecifier(typeNode.specifiers[0], typeNode.specifiers[1]) raise RuntimeException("Unknown type", typeNode.pos) # Todo: Improve pointing position diff --git a/smnp/token/model.py b/smnp/token/model.py index 08ee2e7..cf04827 100644 --- a/smnp/token/model.py +++ b/smnp/token/model.py @@ -8,7 +8,7 @@ class Token: self.rawValue = rawValue def __str__(self): - return "Token(" + str(self.type) + ", '" + str(self.value) + "', " + str(self.pos) + ")" + return "{" + str(self.type.name) + ", '" + str(self.value) + "', " + str(self.pos) + "}" def __repr__(self): return self.__str__() diff --git a/smnp/token/tokenizer.py b/smnp/token/tokenizer.py index bf5dec4..9495dee 100644 --- a/smnp/token/tokenizer.py +++ b/smnp/token/tokenizer.py @@ -1,15 +1,23 @@ from smnp.error.syntax import SyntaxException from smnp.token.model import TokenList +from smnp.token.tokenizers.bool import boolTokenizer from smnp.token.tokenizers.comment import commentTokenizer from smnp.token.tokenizers.identifier import identifierTokenizer from smnp.token.tokenizers.keyword import typeTokenizer from smnp.token.tokenizers.note import noteTokenizer +from smnp.token.tokenizers.relation import relationOperatorTokenizer from smnp.token.tokenizers.string import stringTokenizer from smnp.token.tokenizers.whitespace import whitespacesTokenizer -from smnp.token.tools import defaultTokenizer, separated, regexPatternTokenizer +from smnp.token.tools import defaultTokenizer, separated, regexPatternTokenizer, mapValue from smnp.token.type import TokenType tokenizers = ( + defaultTokenizer(TokenType.ARROW), + + # Double-character operators + relationOperatorTokenizer, + defaultTokenizer(TokenType.DOUBLE_ASTERISK), + # Characters defaultTokenizer(TokenType.OPEN_CURLY), defaultTokenizer(TokenType.CLOSE_CURLY), @@ -21,18 +29,21 @@ tokenizers = ( defaultTokenizer(TokenType.CLOSE_ANGLE), defaultTokenizer(TokenType.ASTERISK), defaultTokenizer(TokenType.ASSIGN), - defaultTokenizer(TokenType.ARROW), defaultTokenizer(TokenType.COMMA), + defaultTokenizer(TokenType.SLASH), defaultTokenizer(TokenType.MINUS), + defaultTokenizer(TokenType.PLUS), + defaultTokenizer(TokenType.DASH), defaultTokenizer(TokenType.DOTS), defaultTokenizer(TokenType.AMP), defaultTokenizer(TokenType.DOT), # Types - separated(regexPatternTokenizer(TokenType.INTEGER, r'\d')), + mapValue(separated(regexPatternTokenizer(TokenType.INTEGER, r'\d')), int), stringTokenizer, - typeTokenizer, noteTokenizer, + boolTokenizer, + typeTokenizer, # Keywords separated(defaultTokenizer(TokenType.FUNCTION)), @@ -40,7 +51,13 @@ tokenizers = ( separated(defaultTokenizer(TokenType.EXTEND)), separated(defaultTokenizer(TokenType.IMPORT)), separated(defaultTokenizer(TokenType.FROM)), + separated(defaultTokenizer(TokenType.WITH)), + separated(defaultTokenizer(TokenType.ELSE)), + separated(defaultTokenizer(TokenType.AND)), + separated(defaultTokenizer(TokenType.NOT)), separated(defaultTokenizer(TokenType.AS)), + separated(defaultTokenizer(TokenType.IF)), + separated(defaultTokenizer(TokenType.OR)), # Identifier (couldn't be before keywords!) identifierTokenizer, @@ -50,7 +67,6 @@ tokenizers = ( commentTokenizer, ) - filters = [ lambda token: token.type is not None, lambda token: token.type != TokenType.COMMENT @@ -58,33 +74,33 @@ filters = [ def tokenize(lines): - tokens = [] - for lineNumber, line in enumerate(lines): + tokens = [] + for lineNumber, line in enumerate(lines): current = 0 while current < len(line): - consumedChars, token = combinedTokenizer(line, current, lineNumber) - + consumedChars, token = combinedTokenizer(line, current, lineNumber) + if consumedChars == 0: raise SyntaxException(f"Unknown symbol '{line[current]}'", (lineNumber, current)) - + current += consumedChars tokens.append(token) - + return TokenList(filterTokens(filters, tokens), lines) def combinedTokenizer(line, current, lineNumber): for tokenizer in tokenizers: consumedChars, token = tokenizer(line, current, lineNumber) - if consumedChars > 0: + if consumedChars > 0: return (consumedChars, token) return (0, None) -def filterTokens(filters, tokens): +def filterTokens(filters, tokens): if not filters: return tokens - + return list(filterTokens(filters[1:], (token for token in tokens if filters[0](token)))) diff --git a/smnp/token/tokenizers/bool.py b/smnp/token/tokenizers/bool.py new file mode 100644 index 0000000..2810060 --- /dev/null +++ b/smnp/token/tokenizers/bool.py @@ -0,0 +1,11 @@ +from smnp.token.tools import keywordsTokenizer, separated +from smnp.token.type import TokenType + + +def boolTokenizer(input, current, line): + consumedChars, token = separated(keywordsTokenizer(TokenType.BOOL, "true", "false"))(input, current, line) + if consumedChars > 0: + token.value = token.value == "true" + return (consumedChars, token) + + return (0, None) diff --git a/smnp/token/tokenizers/keyword.py b/smnp/token/tokenizers/keyword.py index e1d2186..6658aa2 100644 --- a/smnp/token/tokenizers/keyword.py +++ b/smnp/token/tokenizers/keyword.py @@ -3,7 +3,7 @@ from smnp.token.type import TokenType from smnp.type.model import Type -typeTokenizer = separated(keywordsTokenizer(TokenType.TYPE, *[type.name.lower() for type in Type])) +typeTokenizer = separated(keywordsTokenizer(TokenType.TYPE, *[type.name.lower() for type in Type], mapKeyword=lambda value: Type[value.upper()])) diff --git a/smnp/token/tokenizers/note.py b/smnp/token/tokenizers/note.py index 672d66f..6454aba 100644 --- a/smnp/token/tokenizers/note.py +++ b/smnp/token/tokenizers/note.py @@ -14,7 +14,7 @@ def noteTokenizer(input, current, line): rawValue = '' if input[current] == '@': rawValue += input[current+consumedChars] - consumedChars += 1 + consumedChars += 1 # TODO: Check if next item does even exist if input[current+consumedChars] in ('C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g', 'A', 'a', 'H', 'h', 'B', 'b'): rawValue += input[current + consumedChars] notePitch = input[current+consumedChars] diff --git a/smnp/token/tokenizers/relation.py b/smnp/token/tokenizers/relation.py new file mode 100644 index 0000000..485125d --- /dev/null +++ b/smnp/token/tokenizers/relation.py @@ -0,0 +1,6 @@ +from smnp.token.tools import keywordsTokenizer +from smnp.token.type import TokenType + + +def relationOperatorTokenizer(input, current, line): + return keywordsTokenizer(TokenType.RELATION, "==", "!=", ">=", "<=")(input, current, line) \ No newline at end of file diff --git a/smnp/token/tokenizers/string.py b/smnp/token/tokenizers/string.py index 4135036..69924c8 100644 --- a/smnp/token/tokenizers/string.py +++ b/smnp/token/tokenizers/string.py @@ -13,5 +13,5 @@ def stringTokenizer(input, current, line): char = input[current + consumedChars] value += char consumedChars += 1 - return (consumedChars, Token(TokenType.STRING, value[1:len(value)-1], (line, current))) + return (consumedChars, Token(TokenType.STRING, value[1:len(value)-1], (line, current), value)) return (0, None) diff --git a/smnp/token/tools.py b/smnp/token/tools.py index 88d73fa..297a278 100644 --- a/smnp/token/tools.py +++ b/smnp/token/tools.py @@ -16,10 +16,10 @@ def regexPatternTokenizer(type, pattern): return tokenizer -def keywordsTokenizer(type, *keywords): +def keywordsTokenizer(type, *keywords, mapKeyword=lambda x: x): def tokenizer(input, current, line): for keyword in keywords: - result = keywordTokenizer(type, keyword)(input, current, line) + result = keywordTokenizer(type, keyword, mapKeyword)(input, current, line) if result[0] > 0: return result return (0, None) @@ -27,10 +27,10 @@ def keywordsTokenizer(type, *keywords): return tokenizer -def keywordTokenizer(type, keyword): +def keywordTokenizer(type, keyword, mapKeyword=lambda x: x): def tokenizer(input, current, line): if len(input) >= current+len(keyword) and input[current:current+len(keyword)] == keyword: - return (len(keyword), Token(type, keyword, (line, current))) + return (len(keyword), Token(type, mapKeyword(keyword), (line, current))) return (0, None) return tokenizer @@ -50,3 +50,14 @@ def separated(tokenizer, end=r"\W"): return (0, None) return separated + + +def mapValue(tokenizer, mapper): + def tokenize(input, current, line): + consumedChars, token = tokenizer(input, current, line) + if consumedChars > 0: + return (consumedChars, Token(token.type, mapper(token.value), token.pos)) + + return (0, None) + + return tokenize diff --git a/smnp/token/type.py b/smnp/token/type.py index 315b843..8c6aff2 100644 --- a/smnp/token/type.py +++ b/smnp/token/type.py @@ -2,6 +2,8 @@ from enum import Enum class TokenType(Enum): + RELATION = '==, !=, >=, <=' + DOUBLE_ASTERISK = '**' OPEN_CURLY = '{' CLOSE_CURLY = '}' OPEN_PAREN = '(' @@ -14,19 +16,29 @@ class TokenType(Enum): ASSIGN = '=' ARROW = '->' COMMA = ',' + SLASH = '/' MINUS = '-' + PLUS = '+' + DASH = '^' DOTS = '...' AMP = '&' DOT = '.' + AND = 'and' + OR = 'or' + NOT = 'not' INTEGER = 'integer' STRING = 'string' NOTE = 'note' + BOOL = 'bool' TYPE = 'type' FUNCTION = 'function' RETURN = 'return' EXTEND = 'extend' IMPORT = 'import' FROM = 'from' + WITH = 'with' + ELSE = 'else' + IF = 'if' AS = 'as' IDENTIFIER = 'identifier' COMMENT = 'comment' diff --git a/smnp/type/model.py b/smnp/type/model.py index a161ef2..f3e4037 100644 --- a/smnp/type/model.py +++ b/smnp/type/model.py @@ -13,6 +13,7 @@ class Type(Enum): MAP = (dict, lambda x: '{' + ', '.join(f"'{k.stringify()}' -> '{v.stringify()}'" for k, v in x.items()) + '}') PERCENT = (float, lambda x: f"{int(x * 100)}%") NOTE = (Note, lambda x: x.note.name) + BOOL = (bool, lambda x: str(x).lower()) SOUND = (Sound, lambda x: x.file) TYPE = (None, lambda x: x.name.lower()) VOID = (type(None), lambda x: _failStringify(Type.VOID)) @@ -53,6 +54,10 @@ class Type(Enum): "dot": Type.string('.' if value.dot else '') }) + @staticmethod + def bool(value): + return Value(Type.BOOL, value, {}) + @staticmethod def sound(value): return Value(Type.SOUND, value, { @@ -68,6 +73,7 @@ class Type(Enum): def void(): return Value(Type.VOID, None) + def _failStringify(t): raise RuntimeException(f"Not able to interpret {t.name}'", None)