5 Commits

Author SHA1 Message Date
Bartłomiej Pluta
a1bc422fba Fix accessing properties and methods in identifiers with EQUAL 2019-07-10 16:25:06 +02:00
Bartłomiej Pluta
98d710ac97 Add equal ('==') operator to strings 2019-07-10 16:02:39 +02:00
Bartłomiej Pluta
d6fb101337 Add equal ('==') operator to both identifiers and bools 2019-07-10 15:58:37 +02:00
Bartłomiej Pluta
e008be7952 Do some refactor with multiple left associative operators 2019-07-10 15:26:35 +02:00
Bartłomiej Pluta
0a7d29d4a1 Working proof of concept of multiple left associative operators 2019-07-10 15:02:17 +02:00
120 changed files with 1457 additions and 2744 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
__pycache__/ __pycache__/
*.mus *.mus
.idea/* .idea/*
venv/

15
Pipfile
View File

@@ -1,15 +0,0 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
sounddevice = "*"
soundfile = "*"
numpy = "*"
matplotlib = "*"
[requires]
python_version = "3.7"

180
Pipfile.lock generated
View File

@@ -1,180 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "92b0c636f6ac9be9b32aa25b1cdc96dcbe85602d5c68a2084dc39d25392540b6"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"cffi": {
"hashes": [
"sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774",
"sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d",
"sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90",
"sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b",
"sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63",
"sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45",
"sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25",
"sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3",
"sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b",
"sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647",
"sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016",
"sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4",
"sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb",
"sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753",
"sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7",
"sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9",
"sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f",
"sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8",
"sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f",
"sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc",
"sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42",
"sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3",
"sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909",
"sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45",
"sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d",
"sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512",
"sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff",
"sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"
],
"version": "==1.12.3"
},
"cycler": {
"hashes": [
"sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d",
"sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"
],
"version": "==0.10.0"
},
"kiwisolver": {
"hashes": [
"sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f",
"sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7",
"sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe",
"sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c",
"sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5",
"sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75",
"sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187",
"sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641",
"sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883",
"sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5",
"sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2",
"sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3",
"sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389",
"sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897",
"sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a",
"sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c",
"sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326",
"sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0",
"sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e",
"sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544",
"sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995",
"sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f",
"sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee",
"sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004",
"sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2",
"sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9",
"sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a",
"sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f"
],
"version": "==1.1.0"
},
"matplotlib": {
"hashes": [
"sha256:1febd22afe1489b13c6749ea059d392c03261b2950d1d45c17e3aed812080c93",
"sha256:31a30d03f39528c79f3a592857be62a08595dec4ac034978ecd0f814fa0eec2d",
"sha256:4442ce720907f67a79d45de9ada47be81ce17e6c2f448b3c64765af93f6829c9",
"sha256:796edbd1182cbffa7e1e7a97f1e141f875a8501ba8dd834269ae3cd45a8c976f",
"sha256:934e6243df7165aad097572abf5b6003c77c9b6c480c3c4de6f2ef1b5fdd4ec0",
"sha256:bab9d848dbf1517bc58d1f486772e99919b19efef5dd8596d4b26f9f5ee08b6b",
"sha256:c1fe1e6cdaa53f11f088b7470c2056c0df7d80ee4858dadf6cbe433fcba4323b",
"sha256:e5b8aeca9276a3a988caebe9f08366ed519fff98f77c6df5b64d7603d0e42e36",
"sha256:ec6bd0a6a58df3628ff269978f4a4b924a0d371ad8ce1f8e2b635b99e482877a"
],
"index": "pypi",
"version": "==3.1.1"
},
"numpy": {
"hashes": [
"sha256:03f2ebcbffcce2dec8860633b89a93e80c6a239d21a77ae8b241450dc21e8c35",
"sha256:078c8025da5ab9e8657edc9c2a1e9642e06e953bc7baa2e65c1aa9d9dfb7e98b",
"sha256:0fbfa98c5d5c3c6489cc1e852ec94395d51f35d9ebe70c6850e47f465038cdf4",
"sha256:1c841033f4fe6801648180c3033c45b3235a8bbd09bc7249010f99ea27bb6790",
"sha256:2c0984a01ddd0aeec89f0ce46ef21d64761048cd76c0074d0658c91f9131f154",
"sha256:4c166dcb0fff7cb3c0bbc682dfb5061852a2547efb6222e043a7932828c08fb5",
"sha256:8c2d98d0623bd63fb883b65256c00454d5f53127a5a7bcdaa8bdc582814e8cb4",
"sha256:8cb4b6ae45aad6d26712a1ce0a3f2556c5e1484867f9649e03496e45d6a5eba4",
"sha256:93050e73c446c82065b7410221b07682e475ac51887cd9368227a5d944afae80",
"sha256:a3f6b3024f8826d8b1490e6e2a9b99e841cd2c375791b1df62991bd8f4c00b89",
"sha256:bede70fd8699695363f39e86c1e869b2c8b74fb5ef135a67b9e1eeebff50322a",
"sha256:c304b2221f33489cd15a915237a84cdfe9420d7e4d4828c78a0820f9d990395c",
"sha256:f11331530f0eff69a758d62c2461cd98cdc2eae0147279d8fc86e0464eb7e8ca",
"sha256:fa5f2a8ef1e07ba258dc07d4dd246de23ef4ab920ae0f3fa2a1cc5e90f0f1888",
"sha256:fb6178b0488b0ce6a54bc4accbdf5225e937383586555604155d64773f6beb2b",
"sha256:fd5e830d4dc31658d61a6452cd3e842213594d8c15578cdae6829e36ad9c0930"
],
"index": "pypi",
"version": "==1.17.1"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
},
"pyparsing": {
"hashes": [
"sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80",
"sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"
],
"version": "==2.4.2"
},
"python-dateutil": {
"hashes": [
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
],
"version": "==2.8.0"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"sounddevice": {
"hashes": [
"sha256:8d0571349f9a438a97f2c69da760f195cf5ddf2351072199cc1dfede4785a207",
"sha256:a0dda9dea9a5038ec0dafee4cdebd6d9a09086405d4833a7e3f6b18c109f89f3",
"sha256:a71cd3daabc8cb13e0ec9a29e7d4ba9b707386f3c81b3501b20f64209bcaece7",
"sha256:b038315e3497e01b1cb22630bc9b5025fcfad40e5ef7c125a9d565befa7220e0",
"sha256:c340d0a68945439571a13b287bd11b2d2884b61cd47cc5de8356d88221edf580"
],
"index": "pypi",
"version": "==0.3.13"
},
"soundfile": {
"hashes": [
"sha256:181393f8daac38bda997c4284b760bcd726dc0e847f620020d7bf2d3d538a985",
"sha256:3f90971bb1884575ada9a0731e878c91cc1ffde14a778f0857deaa2fd04849ac",
"sha256:637f6218c867b8cae80f6989634a0813b416b3e6132480d056e6e5a89a921571",
"sha256:7fe04272cbfb94ab648acb0db31ace0800f5a4cf46e9d45b4d71ddb15abce1a0",
"sha256:80a3d2ed37199ffa7066c097d7b82b8413922e22870b62b54dc264bf0169f4c2"
],
"index": "pypi",
"version": "==0.10.2"
}
},
"develop": {}
}

View File

@@ -1,2 +0,0 @@
You have to have installed `PortAudio` in order to use app.

56
smnp/ast/node/access.py Normal file
View File

@@ -0,0 +1,56 @@
from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.none import NoneNode
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 = [ NoneNode(), NoneNode() ]
@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 accessParser(cls):
def createNode(left, operator, right):
node = AccessNode(right.pos)
node.left = left
node.right = right
return node
return Parser.leftAssociativeOperatorParser(
cls._accessLhs(),
TokenType.DOT,
cls._accessRhs(),
createNode=createNode
)
@classmethod
def _accessLhs(cls):
raise RuntimeError(f"_accessLhs() is not implemented in {cls.__name__} class")
@staticmethod
def _accessRhs():
from smnp.ast.node.identifier import IdentifierNode
return Parser.oneOf(
IdentifierNode.functionCallParser(),
IdentifierNode.identifierParser(),
exception=lambda input: SyntaxException(f"Expected property name or method call, found '{input.current().rawValue}'", input.currentPos())
)

View File

@@ -0,0 +1,28 @@
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")

28
smnp/ast/node/asterisk.py Normal file
View File

@@ -0,0 +1,28 @@
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")

View File

@@ -1,109 +0,0 @@
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 FloatLiteral(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 FloatParser(input):
return Parser.terminal(TokenType.FLOAT, createNode=FloatLiteral.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,
FloatParser,
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
from smnp.ast.node.expression import ExpressionParser
parentheses = Parser.allOf(
Parser.terminal(TokenType.OPEN_PAREN),
Parser.doAssert(ExpressionParser, "expression"),
Parser.terminal(TokenType.CLOSE_PAREN),
createNode=lambda open, expr, close: expr,
name="grouping parentheses"
)
return Parser.oneOf(
parentheses,
LiteralParser,
IdentifierParser,
ListParser,
MapParser,
name="atom"
)(input)

View File

@@ -1,17 +1,20 @@
from smnp.ast.node.model import Node from smnp.ast.node.statement import StatementNode
from smnp.ast.node.statement import StatementParser
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class Block(Node): class BlockNode(StatementNode):
pass
@classmethod
def _parse(cls, input):
def createNode(start, items, end):
node = BlockNode(start.pos)
node.children = items
return node
def BlockParser(input):
return Parser.loop( return Parser.loop(
Parser.terminal(TokenType.OPEN_CURLY), Parser.terminalParser(TokenType.OPEN_CURLY),
Parser.doAssert(StatementParser, f"statement or '{TokenType.CLOSE_CURLY.key}'"), Parser.doAssert(StatementNode.parse, f"statement or '{TokenType.CLOSE_CURLY.key}'"),
Parser.terminal(TokenType.CLOSE_CURLY), Parser.terminalParser(TokenType.CLOSE_CURLY),
createNode=lambda open, statements, close: Block.withChildren(statements, open.pos) createNode=createNode,
)(input) )(input)

32
smnp/ast/node/bool.py Normal file
View File

@@ -0,0 +1,32 @@
from smnp.ast.node.access import AccessNode
from smnp.ast.node.literal import LiteralNode
from smnp.ast.node.relation import RelationOperatorNode
from smnp.ast.parser import Parser
from smnp.token.type import TokenType
class BoolLiteralNode(LiteralNode, AccessNode, RelationOperatorNode):
def __init__(self, pos):
super().__init__(pos)
del self.children[1]
@classmethod
def _getTokenType(cls):
return TokenType.BOOL
@classmethod
def _parse(cls, input):
x = Parser.oneOf(
cls.accessParser(),
cls.relationParser(),
cls.literalParser()
)(input)
return x
@classmethod
def _accessLhs(cls):
return cls.literalParser()
@classmethod
def _relationLhs(cls):
return cls.literalParser()

View File

@@ -1,74 +0,0 @@
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)

View File

@@ -1,128 +1,66 @@
from smnp.ast.node.asterisk import AsteriskNode
from smnp.ast.node.model import Node from smnp.ast.node.model import Node
from smnp.ast.node.none import NoneNode from smnp.ast.node.none import NoneNode
from smnp.ast.node.operator import BinaryOperator, Operator from smnp.ast.node.statement import StatementNode
from smnp.ast.node.term import TermParser
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class Sum(BinaryOperator): class ExpressionNode(Node):
pass
class Relation(BinaryOperator):
pass
class And(BinaryOperator):
pass
class Or(BinaryOperator):
pass
class Loop(BinaryOperator):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos, [NoneNode()])
self.children.extend([NoneNode(), NoneNode()])
@property @property
def parameters(self): def value(self):
return self[3] return self[0]
@parameters.setter
def parameters(self, value):
self[3] = value
@property @value.setter
def filter(self): def value(self, v):
return self[4] self[0] = v
@filter.setter
def filter(self, value):
self[4] = value
@classmethod @classmethod
def loop(cls, left, parameters, operator, right, filter): def withValue(cls, val, pos):
node = cls(left.pos) node = cls(pos)
node.left = left node.value = val
node.parameters = parameters
node.operator = operator
node.right = right
node.filter = filter
return node return node
@classmethod
class LoopParameters(Node): def _parse(cls, input):
pass return Parser.oneOf(
cls._asteriskParser(),
cls._expressionParser(),
def ExpressionWithoutLoopParser(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) )(input)
@classmethod
def LoopParser(input): def _asteriskParser(cls):
from smnp.ast.node.identifier import IdentifierLiteralParser def createNode(iterator, asterisk, statement):
from smnp.ast.node.iterable import abstractIterableParser node = AsteriskNode(asterisk.pos)
from smnp.ast.node.statement import StatementParser node.iterator = iterator
node.statement = statement
loopParameters = Parser.allOf( return node
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"
)
loopFilter = Parser.allOf(
Parser.terminal(TokenType.PERCENT),
Parser.doAssert(ExpressionWithoutLoopParser, "filter as bool expression"),
createNode=lambda percent, expr: expr,
name="loop filter"
)
return Parser.allOf( return Parser.allOf(
ExpressionWithoutLoopParser, cls._expressionParser(),
Parser.optional(loopParameters), Parser.terminalParser(TokenType.ASTERISK),
Parser.terminal(TokenType.DASH, createNode=Operator.withValue), Parser.doAssert(StatementNode.parse, 'statement'),
StatementParser, createNode=createNode
Parser.optional(loopFilter), )
createNode=Loop.loop,
name="dash-loop"
)(input)
@classmethod
def _expressionParser(cls):
from smnp.ast.node.bool import BoolLiteralNode
def ExpressionParser(input): from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.string import StringLiteralNode
return Parser.oneOf( return Parser.oneOf(
LoopParser, #IntegerLiteralNode.parse,
ExpressionWithoutLoopParser StringLiteralNode.parse,
)(input) #NoteLiteralNode.parse,
BoolLiteralNode.parse,
IdentifierNode.parse,
#MapNode.parse,
#ListNode.parse,
#TypeNode.parse,
)

View File

@@ -1,14 +1,14 @@
from smnp.ast.node.block import Block from smnp.ast.node.block import BlockNode
from smnp.ast.node.function import FunctionDefinitionParser from smnp.ast.node.function import FunctionDefinitionNode
from smnp.ast.node.identifier import IdentifierLiteralParser from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.model import Node
from smnp.ast.node.none import NoneNode from smnp.ast.node.none import NoneNode
from smnp.ast.node.type import TypeParser from smnp.ast.node.statement import StatementNode
from smnp.ast.node.type import TypeNode
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class Extend(Node): class ExtendNode(StatementNode):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
self.children = [NoneNode(), NoneNode(), NoneNode()] self.children = [NoneNode(), NoneNode(), NoneNode()]
@@ -38,51 +38,33 @@ class Extend(Node):
self[2] = value self[2] = value
@classmethod @classmethod
def withValues(cls, pos, type, variable, methods): def _parse(cls, input):
node = cls(pos) def createNode(extend, type, asKeyword, variable, methods):
node = ExtendNode(extend.pos)
node.type = type node.type = type
node.variable = variable node.variable = variable
node.methods = methods node.methods = methods
return node return node
return Parser.allOf(
def ExtendParser(input): Parser.terminalParser(TokenType.EXTEND),
Parser.doAssert(TypeNode.parse, "type being extended"),
simpleExtend = Parser.allOf( Parser.terminalParser(TokenType.AS, doAssert=True),
Parser.terminal(TokenType.EXTEND), Parser.doAssert(IdentifierNode.identifierParser(), "variable name"),
TypeParser, Parser.doAssert(cls._methodsDeclarationsParser(), "methods declarations"),
Parser.terminal(TokenType.AS), createNode=createNode
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) )(input)
@classmethod
def _methodsDeclarationsParser(cls):
def createNode(openBracket, items, closeBracket):
node = BlockNode(openBracket.pos)
node.children = items
return node
def MethodsDeclarationParser(input):
return Parser.loop( return Parser.loop(
Parser.terminal(TokenType.OPEN_CURLY), Parser.terminalParser(TokenType.OPEN_CURLY),
Parser.doAssert(FunctionDefinitionParser, f"method definition or '{TokenType.CLOSE_CURLY.key}'"), Parser.doAssert(FunctionDefinitionNode.parse, f"method declaration or '{TokenType.CLOSE_CURLY.key}'"),
Parser.terminal(TokenType.CLOSE_CURLY), Parser.terminalParser(TokenType.CLOSE_CURLY),
createNode=lambda open, methods, close: Block.withChildren(methods, open.pos), createNode=createNode
name="methods block" )
)(input)

View File

@@ -1,34 +0,0 @@
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
def FactorParser(input):
powerFactor = Parser.leftAssociativeOperatorParser(
UnitParser,
[TokenType.DOUBLE_ASTERISK],
UnitParser,
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"
)
return Parser.oneOf(
notOperator,
powerFactor,
name="factor"
)(input)

View File

@@ -1,67 +1,83 @@
from smnp.ast.node.block import BlockParser from smnp.ast.node.block import BlockNode
from smnp.ast.node.expression import ExpressionParser from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.identifier import IdentifierLiteralParser from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.iterable import abstractIterableParser from smnp.ast.node.iterable import abstractIterableParser
from smnp.ast.node.model import Node from smnp.ast.node.model import Node
from smnp.ast.node.none import NoneNode from smnp.ast.node.none import NoneNode
from smnp.ast.node.type import TypeParser, Type from smnp.ast.node.statement import StatementNode
from smnp.ast.node.type import TypeNode, TypeSpecifier
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class ArgumentsDeclaration(Node): class ArgumentsDeclarationNode(Node):
@classmethod
def _parse(cls, input):
raise RuntimeError("This class is not supposed to be automatically called")
class VarargNode(Node):
pass pass
class Argument(Node): class ArgumentDefinitionNode(ExpressionNode):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
self.children = [NoneNode(), NoneNode(), False, NoneNode()] self.children.extend([NoneNode(), False])
@property @property
def type(self): def type(self):
return self[0] return self[0]
@type.setter @type.setter
def type(self, value): def type(self, value):
self[0] = value self[0] = value
@property @property
def variable(self): def variable(self):
return self[1] return self[1]
@variable.setter @variable.setter
def variable(self, value): def variable(self, value):
self[1] = value self[1] = value
@property @property
def vararg(self): def vararg(self):
return self[2] return self[2]
@vararg.setter @vararg.setter
def vararg(self, value): def vararg(self, value):
self[2] = value self[2] = value
@property @classmethod
def optionalValue(self): def parser(cls):
return self[3] def createNode(type, variable, dots):
node = ArgumentDefinitionNode(type.pos)
node.type = type
node.variable = variable
node.vararg = isinstance(dots, VarargNode)
return node
@optionalValue.setter return Parser.allOf(
def optionalValue(self, value): Parser.optional(Parser.oneOf(
self[3] = value TypeNode.parse,
TypeSpecifier.parse
)),
Parser.doAssert(IdentifierNode.identifierParser(), "variable name"),
Parser.optional(Parser.terminalParser(TokenType.DOTS, lambda val, pos: VarargNode(pos))),
createNode=createNode
)
class VarargNode(Node): @classmethod
pass def _parse(cls, input):
#TODO
raise RuntimeError("Not implemented yet. There is still required work to correctly build AST related to IdentifierNode")
class FunctionDefinition(Node):
class FunctionDefinitionNode(StatementNode):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
self.children = [NoneNode(), NoneNode(), NoneNode()] self.children = [NoneNode(), NoneNode(), NoneNode()]
@@ -91,75 +107,22 @@ class FunctionDefinition(Node):
self[2] = value self[2] = value
@classmethod @classmethod
def withValues(cls, name, arguments, body): def _parse(cls, input):
node = cls(name.pos) def createNode(function, name, arguments, body):
node = FunctionDefinitionNode(function.pos)
node.name = name node.name = name
node.arguments = arguments node.arguments = arguments
node.body = body node.body = body
return node return node
def RegularArgumentParser(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( return Parser.allOf(
Parser.optional(TypeParser), Parser.terminalParser(TokenType.FUNCTION),
Parser.doAssert(IdentifierLiteralParser, "argument name"), Parser.doAssert(IdentifierNode.identifierParser(), "function name"),
Parser.optional(Parser.terminal(TokenType.DOTS, lambda val, pos: True)), Parser.doAssert(cls._argumentsDeclarationParser(), "arguments list"),
createNode=createNode, Parser.doAssert(BlockNode.parse, "function body"),
name="regular function argument" createNode=createNode
)(input)
def OptionalArgumentParser(input):
def createNode(type, variable, _, optional):
pos = type.pos if isinstance(type, Type) else variable.pos
node = Argument(pos)
node.type = type
node.variable = variable
node.optionalValue = optional
return node
return Parser.allOf(
Parser.optional(TypeParser),
Parser.doAssert(IdentifierLiteralParser, "argument name"),
Parser.terminal(TokenType.ASSIGN),
Parser.doAssert(ExpressionParser, "expression"),
createNode=createNode,
name="optional function argument"
)(input)
def ArgumentParser(input):
return Parser.oneOf(
OptionalArgumentParser,
RegularArgumentParser,
name="function argument"
)(input)
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) )(input)
@staticmethod
def _argumentsDeclarationParser():
return abstractIterableParser(ArgumentsDeclarationNode, TokenType.OPEN_PAREN, TokenType.CLOSE_PAREN, ArgumentDefinitionNode.parser())

View File

@@ -1,74 +1,73 @@
from smnp.ast.node.atom import Atom from smnp.ast.node.access import AccessNode
from smnp.ast.node.expression import ExpressionParser from smnp.ast.node.assignment import AssignmentNode
from smnp.ast.node.iterable import abstractIterableParser from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.model import Node from smnp.ast.node.invocation import FunctionCallNode, ArgumentsListNode
from smnp.ast.node.none import NoneNode from smnp.ast.node.relation import RelationOperatorNode
from smnp.ast.node.operator import BinaryOperator, Operator
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class Identifier(Atom): class IdentifierNode(AccessNode, RelationOperatorNode):
pass
class FunctionCall(Node):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
self.children = [NoneNode(), NoneNode()] del self.children[1]
@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 @classmethod
def withChildren(cls, name, arguments): def _parse(cls, input):
node = cls(name.pos) return Parser.oneOf(
cls.relationParser(),
cls.accessParser(),
cls.literalParser()
)(input)
@classmethod
def _accessLhs(cls):
return cls.literalParser()
@classmethod
def _relationLhs(cls):
return Parser.oneOf(
cls.accessParser(),
cls.literalParser()
)
@classmethod
def literalParser(cls):
return Parser.oneOf(
IdentifierNode.functionCallParser(),
IdentifierNode._assignmentParser(),
IdentifierNode.identifierParser()
)
@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
)
@staticmethod
def functionCallParser():
def createNode(name, arguments):
node = FunctionCallNode(name.pos)
node.name = name node.name = name
node.arguments = arguments node.arguments = arguments
return node return node
return Parser.allOf(
class ArgumentsList(Node): IdentifierNode.identifierParser(),
pass ArgumentsListNode.parse,
createNode=createNode
class Assignment(BinaryOperator):
pass
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( @staticmethod
IdentifierLiteralParser, def identifierParser():
Parser.terminal(TokenType.ASSIGN, createNode=Operator.withValue), return Parser.terminalParser(TokenType.IDENTIFIER, lambda val, pos: IdentifierNode.withValue(val, pos))
Parser.doAssert(ExpressionParser, "expression"),
createNode=lambda identifier, assign, expr: Assignment.withValues(identifier, assign, expr)
)
return Parser.oneOf(
assignmentParser,
functionCallParser,
IdentifierLiteralParser
)(input)

View File

@@ -1,13 +1,16 @@
from smnp.ast.node.atom import StringParser from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.model import Node 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.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class Import(Node): class ImportNode(Node):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
self.children = [None] self.children = [NoneNode(), NoneNode(), NoneNode()]
@property @property
def source(self): def source(self):
@@ -17,17 +20,57 @@ class Import(Node):
def source(self, value): def source(self, value):
self[0] = 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 @classmethod
def withValue(cls, value): def _parse(cls, input):
node = cls(value.pos) return Parser.oneOf(
node.source = value cls._literalImportParser(),
cls._fileImportParser()
)(input)
@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 node
def ImportParser(input):
return Parser.allOf( return Parser.allOf(
Parser.terminal(TokenType.IMPORT), Parser.terminalParser(TokenType.IMPORT),
Parser.doAssert(StringParser, "import source as string"), TypeNode.parse,
createNode=lambda imp, source: Import.withValue(source), Parser.doAssert(Parser.terminalParser(TokenType.FROM), "'from <source> as <variable name>'"),
name="import" Parser.doAssert(StringLiteralNode.literalParser(), "source as a string"),
)(input) Parser.doAssert(Parser.terminalParser(TokenType.AS), "'as <variable name>'"),
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
)

37
smnp/ast/node/integer.py Normal file
View File

@@ -0,0 +1,37 @@
from smnp.ast.node.expression import ExpressionNode
from smnp.ast.parser import Parser
from smnp.token.type import TokenType
class IntegerLiteralNode(ExpressionNode):
def __init__(self, pos):
super().__init__(pos)
#TODO del self.children[1]
# TODO: To Remove
@classmethod
def _parse(cls, input):
return cls._literalParser()(input)
@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))

View File

@@ -0,0 +1,40 @@
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")

View File

@@ -1,3 +1,4 @@
from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.ignore import IgnoredNode from smnp.ast.node.ignore import IgnoredNode
from smnp.ast.node.model import Node, ParseResult from smnp.ast.node.model import Node, ParseResult
from smnp.ast.node.none import NoneNode from smnp.ast.node.none import NoneNode
@@ -6,19 +7,11 @@ from smnp.token.type import TokenType
def abstractIterableParser(iterableNodeType, openTokenType, closeTokenType, itemParser): def abstractIterableParser(iterableNodeType, openTokenType, closeTokenType, itemParser):
class AbstractIterableTailNode(ExpressionNode):
class AbstractIterable(Node):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
self.children = [NoneNode(), NoneNode()]
@property self.children.append(NoneNode())
def value(self):
return self[0]
@value.setter
def value(self, value):
self[0] = value
@property @property
def next(self): def next(self):
@@ -28,67 +21,83 @@ def abstractIterableParser(iterableNodeType, openTokenType, closeTokenType, item
def next(self, value): def next(self, value):
self[1] = value self[1] = value
class AbstractIterableTail(AbstractIterable): @classmethod
pass def _parse(cls, input):
def abstractIterableParser(input):
return Parser.oneOf( return Parser.oneOf(
emptyIterable, AbstractIterableTailNode._parser1(),
openIterable AbstractIterableTailNode._parser2(),
)(input) )(input)
def emptyIterable(input): @staticmethod
def createNode(open, close): def _parser1():
node = AbstractIterable(open.pos) return Parser.terminalParser(closeTokenType)
node.value = open
node.next = close @staticmethod
def _parser2():
def createNode(comma, expr, iterableTail):
node = AbstractIterableTailNode(expr.pos)
node.value = expr
node.next = iterableTail
return node return node
return Parser.allOf( return Parser.allOf(
Parser.terminal(openTokenType), Parser.terminalParser(TokenType.COMMA, doAssert=True),
Parser.terminal(closeTokenType),
createNode=createNode
)(input)
def openIterable(input):
def createNode(open, item, tail):
node = AbstractIterable(open.pos)
node.value = item
node.next = tail
return node
return Parser.allOf(
Parser.terminal(openTokenType),
itemParser, itemParser,
abstractIterableTailParser, AbstractIterableTailNode.parse,
createNode=createNode createNode=createNode
)(input) )
def abstractIterableTailParser(input): class AbstractIterableNode(ExpressionNode):
def __init__(self, pos):
super().__init__(pos)
self.children.append(NoneNode())
@property
def next(self):
return self[1]
@next.setter
def next(self, value):
self[1] = value
@classmethod
def _parse(cls, input):
return Parser.oneOf( return Parser.oneOf(
closeIterable, AbstractIterableNode._parser1(),
nextItem, AbstractIterableNode._parser2()
)(input) )(input)
def nextItem(input): @staticmethod
def createNode(comma, item, tail): def _parser1():
node = AbstractIterableTail(item.pos) def emptyIterable(openToken, closeToken):
node.value = item node = AbstractIterableNode(openToken.pos)
node.next = tail node.value = openToken
node.next = closeToken
return node return node
return Parser.allOf( return Parser.allOf(
Parser.doAssert(Parser.terminal(TokenType.COMMA), f"'{TokenType.COMMA.key}' or '{closeTokenType.key}'"), 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, itemParser,
abstractIterableTailParser, AbstractIterableTailNode.parse,
createNode=createNode createNode=createNode
)(input) )
def closeIterable(input): return toFlatDesiredNode(iterableNodeType, AbstractIterableNode.parse)
return Parser.terminal(closeTokenType)(input)
return toFlatDesiredNode(iterableNodeType, abstractIterableParser)
def toFlatDesiredNode(iterableNodeType, parser): def toFlatDesiredNode(iterableNodeType, parser):
@@ -105,7 +114,7 @@ def toFlatDesiredNode(iterableNodeType, parser):
return ParseResult.FAIL() return ParseResult.FAIL()
return Parser(parse, "flat", [parser]) return parse
def flattenList(node, output=None): def flattenList(node, output=None):

View File

@@ -1,19 +1,17 @@
from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.iterable import abstractIterableParser from smnp.ast.node.iterable import abstractIterableParser
from smnp.ast.node.model import Node
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class List(Node): class ListNode(ExpressionNode):
pass
# TODO: To Remove
@classmethod
def _parse(cls, input):
return cls._literalParser()(input)
def ListParser(input): @classmethod
from smnp.ast.node.expression import ExpressionParser def _literalParser(cls):
return abstractIterableParser(ListNode, TokenType.OPEN_SQUARE, TokenType.CLOSE_SQUARE,
return abstractIterableParser( Parser.doAssert(ExpressionNode.parse, "expression"))
List,
TokenType.OPEN_SQUARE,
TokenType.CLOSE_SQUARE,
Parser.doAssert(ExpressionParser, "expression")
)(input)

18
smnp/ast/node/literal.py Normal file
View File

@@ -0,0 +1,18 @@
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)

View File

@@ -1,13 +1,19 @@
from smnp.ast.node.atom import LiteralParser from smnp.ast.node.access import AccessNode
from smnp.ast.node.identifier import IdentifierLiteralParser from smnp.ast.node.bool import BoolLiteralNode
from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.integer import IntegerLiteralNode
from smnp.ast.node.iterable import abstractIterableParser 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.node.note import NoteLiteralNode
from smnp.ast.node.string import StringLiteralNode
from smnp.ast.node.type import TypeNode
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class MapEntry(ExpressionNode):
class MapEntry(BinaryOperator): def __init__(self, pos):
super().__init__(pos)
self.children = [NoneNode(), NoneNode()]
@property @property
def key(self): def key(self):
@@ -19,36 +25,39 @@ class MapEntry(BinaryOperator):
@property @property
def value(self): def value(self):
return self[2] return self[1]
@value.setter @value.setter
def value(self, value): def value(self, value):
self[2] = value self[1] = value
class MapNode(AccessNode):
class Map(Node): @classmethod
pass def _literalParser(cls):
return abstractIterableParser(MapNode, TokenType.OPEN_CURLY, TokenType.CLOSE_CURLY, cls._entryParser())
@classmethod
def _entryParser(cls):
def createNode(key, arrow, value):
node = MapEntry(key.pos)
node.key = key
node.value = value
return node
def MapParser(input): return Parser.allOf(
from smnp.ast.node.expression import ExpressionParser cls._keyParser(),
keyParser = Parser.oneOf( Parser.terminalParser(TokenType.ARROW),
LiteralParser, ExpressionNode.parse,
IdentifierLiteralParser createNode=createNode
)
valueParser = ExpressionParser
mapEntryParser = Parser.allOf(
keyParser,
Parser.terminal(TokenType.ARROW, createNode=Operator.withValue),
Parser.doAssert(valueParser, "expression"),
createNode=MapEntry.withValues
) )
return abstractIterableParser( @classmethod
Map, def _keyParser(cls):
TokenType.OPEN_CURLY, return Parser.oneOf(
TokenType.CLOSE_CURLY, IntegerLiteralNode._literalParser(),
mapEntryParser StringLiteralNode._literalParser(),
)(input) NoteLiteralNode._literalParser(),
BoolLiteralNode._literalParser(),
TypeNode.parse
)

View File

@@ -28,11 +28,26 @@ class Node:
def pop(self, index): def pop(self, index):
return self.children.pop(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): def print(self):
self._print(first=True) self._print(first=True)
def _print(self, prefix="", last=True, first=False): def _print(self, prefix="", last=True, first=False):
print(prefix, '' if first else '└─' if last else '├─', self.__class__.__name__, f" (line {self.pos[0]+1}, col {self.pos[1]+1})", sep="") print(prefix, '' if first else '└─' if last else '├─', self.__class__.__name__, sep="")
prefix += ' ' if last else '' prefix += ' ' if last else ''
for i, child in enumerate(self.children): for i, child in enumerate(self.children):
last = i == len(self.children) - 1 last = i == len(self.children) - 1

18
smnp/ast/node/note.py Normal file
View File

@@ -0,0 +1,18 @@
from smnp.ast.node.literal import LiteralNode
from smnp.token.type import TokenType
class NoteLiteralNode(LiteralNode):
def __init__(self, pos):
super().__init__(pos)
#TODO del self.children[1]
# TODO: To Remove
@classmethod
def _parse(cls, input):
return cls.literalParser()(input)
@classmethod
def _getTokenType(cls):
return TokenType.NOTE

View File

@@ -1,75 +1,7 @@
from smnp.ast.node.model import Node from smnp.ast.node.model import Node
from smnp.ast.node.none import NoneNode
class UnaryOperator(Node): class OperatorNode(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): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
self.children = [None] self.children = [None]
@@ -81,9 +13,3 @@ class Operator(Node):
@value.setter @value.setter
def value(self, value): def value(self, value):
self[0] = value self[0] = value
@classmethod
def withValue(cls, value, pos):
node = cls(pos)
node.value = value
return node

View File

@@ -1,8 +1,9 @@
from smnp.ast.node.extend import ExtendParser from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.function import FunctionDefinitionParser from smnp.ast.node.extend import ExtendNode
from smnp.ast.node.imports import ImportParser from smnp.ast.node.function import FunctionDefinitionNode
from smnp.ast.node.imports import ImportNode
from smnp.ast.node.model import Node, ParseResult from smnp.ast.node.model import Node, ParseResult
from smnp.ast.node.statement import StatementParser from smnp.ast.node.statement import StatementNode
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.error.syntax import SyntaxException from smnp.error.syntax import SyntaxException
@@ -11,26 +12,21 @@ class Program(Node):
def __init__(self): def __init__(self):
super().__init__((-1, -1)) super().__init__((-1, -1))
def ProgramParser(input): @classmethod
def parse(input): 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)
root = Program() 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(): while input.hasCurrent():
result = startSymbolParser(input) result = parseToken(input)
if result.result: if result.result:
root.append(result.node) root.append(result.node)
return ParseResult.OK(root) return ParseResult.OK(root)
return Parser(parse, name="program")(input)

68
smnp/ast/node/relation.py Normal file
View File

@@ -0,0 +1,68 @@
from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.none import NoneNode
from smnp.ast.parser import Parser
from smnp.token.type import TokenType
class RelationOperatorNode(ExpressionNode):
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 relationParser(cls):
def createNode(left, operator, right):
node = RelationOperatorNode(right.pos)
node.left = left
node.operator = operator
node.right = right
return node
return Parser.leftAssociativeOperatorParser(
cls._relationLhs(),
TokenType.EQUAL,
cls._relationRhs(),
createNode=createNode
)
@classmethod
def _relationLhs(cls):
raise RuntimeError(f"_relationLhs() is not implemented in {cls.__name__} class")
@staticmethod
def _relationRhs():
from smnp.ast.node.bool import BoolLiteralNode
from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.string import StringLiteralNode
return Parser.doAssert(Parser.oneOf(
BoolLiteralNode.accessParser(),
BoolLiteralNode.literalParser(),
IdentifierNode.parse,
StringLiteralNode.accessParser(),
StringLiteralNode.literalParser()
), "expression")

View File

@@ -1,17 +1,32 @@
from smnp.ast.node.expression import ExpressionParser from smnp.ast.node.expression import ExpressionNode
from smnp.ast.node.valuable import Valuable from smnp.ast.node.none import NoneNode
from smnp.ast.node.statement import StatementNode
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
class Return(Valuable): class ReturnNode(StatementNode):
pass def __init__(self, pos):
super().__init__(pos)
self.children.append(NoneNode())
@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
def ReturnParser(input):
return Parser.allOf( return Parser.allOf(
Parser.terminal(TokenType.RETURN), Parser.terminalParser(TokenType.RETURN),
Parser.optional(ExpressionParser), Parser.doAssert(ExpressionNode.parse, "expression"),
createNode=lambda ret, val: Return.withValue(val, ret.pos), createNode=createNode
name="return"
)(input) )(input)

View File

@@ -1,37 +1,17 @@
from smnp.ast.node.model import Node from smnp.ast.node.model import Node
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType
class Statement(Node): class StatementNode(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
def StatementParser(input): return Parser.oneOf(
from smnp.ast.node.block import BlockParser ExpressionNode.parse,
from smnp.ast.node.condition import IfElseStatementParser BlockNode.parse,
from smnp.ast.node.expression import ExpressionParser ReturnNode.parse,
from smnp.ast.node.ret import ReturnParser )(input)
from smnp.ast.node.throw import ThrowParser
return withSemicolon(
Parser.oneOf(
IfElseStatementParser,
ExpressionParser, # Must be above BlockParser because of Map's syntax with curly braces
BlockParser,
ReturnParser,
ThrowParser,
name="statement"
), optional=True)(input)
def withSemicolon(parser, optional=False, doAssert=False):
semicolonParser = Parser.optional(Parser.terminal(TokenType.SEMICOLON)) if optional else Parser.terminal(
TokenType.SEMICOLON, doAssert=doAssert)
return Parser.allOf(
parser,
semicolonParser,
createNode=lambda stmt, semicolon: stmt,
name="semicolon" + "?" if optional else ""
)

31
smnp/ast/node/string.py Normal file
View File

@@ -0,0 +1,31 @@
from smnp.ast.node.access import AccessNode
from smnp.ast.node.literal import LiteralNode
from smnp.ast.node.relation import RelationOperatorNode
from smnp.ast.parser import Parser
from smnp.token.type import TokenType
class StringLiteralNode(AccessNode, RelationOperatorNode, LiteralNode):
def __init__(self, pos):
super().__init__(pos)
del self.children[1]
@classmethod
def _parse(cls, input):
return Parser.oneOf(
cls.accessParser(),
cls.relationParser(),
cls.literalParser()
)(input)
@classmethod
def _accessLhs(cls):
return cls.literalParser()
@classmethod
def _relationLhs(cls):
return cls.literalParser()
@classmethod
def _getTokenType(cls):
return TokenType.STRING

View File

@@ -1,17 +0,0 @@
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)

View File

@@ -1,17 +0,0 @@
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 Throw(Valuable):
pass
def ThrowParser(input):
return Parser.allOf(
Parser.terminal(TokenType.THROW),
Parser.doAssert(ExpressionParser, "error message as string"),
createNode=lambda throw, message: Throw.withValue(message, throw.pos),
name="throw"
)(input)

View File

@@ -1,15 +1,32 @@
from smnp.ast.node.atom import TypeLiteralParser from smnp.ast.node.access import AccessNode
from smnp.ast.node.iterable import abstractIterableParser from smnp.ast.node.iterable import abstractIterableParser
from smnp.ast.node.model import Node from smnp.ast.node.model import Node
from smnp.ast.node.none import NoneNode
from smnp.ast.parser import Parser from smnp.ast.parser import Parser
from smnp.token.type import TokenType from smnp.token.type import TokenType
from smnp.type.model import Type
class Type(Node): 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):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
self.children = [NoneNode(), NoneNode()]
@property @property
def type(self): def type(self):
@@ -28,40 +45,19 @@ class Type(Node):
self[1] = value self[1] = value
@classmethod @classmethod
def withValues(cls, pos, type, specifiers=NoneNode()): def _parse(cls, input):
node = cls(pos) def createNode(type, specifiers):
node.type = type node = TypeNode(type.pos)
node.type = Type[type.value.upper()]
node.specifiers = specifiers node.specifiers = specifiers
return node return node
return Parser.allOf(
class TypesList(Node): cls._rawTypeParser(),
pass Parser.many(TypeSpecifier.parse, lambda specifiers, pos: TypeSpecifiers.withChildren(specifiers, pos)),
createNode=createNode
def TypesListParser(input):
return abstractIterableParser(
TypesList,
TokenType.OPEN_ANGLE,
TokenType.CLOSE_ANGLE,
TypeParser
)(input) )(input)
@classmethod
class TypeSpecifiers(Node): def _rawTypeParser(cls):
pass return Parser.terminalParser(TokenType.TYPE, lambda val, pos: TypeNode.withValue(val, pos))
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)

View File

@@ -1,36 +0,0 @@
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)

View File

@@ -1,22 +0,0 @@
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

View File

@@ -1,47 +1,20 @@
from smnp.ast.node.ignore import IgnoredNode from smnp.ast.node.ignore import IgnoredNode
from smnp.ast.node.model import ParseResult, Node from smnp.ast.node.model import ParseResult, Node
from smnp.ast.node.none import NoneNode from smnp.ast.node.none import NoneNode
from smnp.ast.node.operator import OperatorNode
from smnp.error.syntax import SyntaxException from smnp.error.syntax import SyntaxException
def parse(input): def parse(input):
from smnp.ast.node.program import ProgramParser from smnp.ast.node.program import Program
return ProgramParser(input).node return Program.parse(input).node
class Parser: 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 # a -> A
@staticmethod @staticmethod
def terminal(expectedType, createNode=None, doAssert=False): def terminalParser(expectedType, createNode=None, doAssert=False):
def provideNode(value, pos): def provideNode(value, pos):
if createNode is None: if createNode is None:
return IgnoredNode(pos) return IgnoredNode(pos)
@@ -58,11 +31,11 @@ class Parser:
return ParseResult.FAIL() return ParseResult.FAIL()
return Parser(parse, name=expectedType.name.lower()) return parse
# oneOf -> a | b | c | ... # oneOf -> a | b | c | ...
@staticmethod @staticmethod
def oneOf(*parsers, assertExpected=None, exception=None, name="or"): def oneOf(*parsers, exception=None):
def combinedParser(input): def combinedParser(input):
snap = input.snapshot() snap = input.snapshot()
for parser in parsers: for parser in parsers:
@@ -71,24 +44,21 @@ class Parser:
return value return value
input.reset(snap) 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 exception is not None:
if callable(exception): if callable(exception):
raise exception(input) raise exception(input)
else: else:
raise exception raise exception
input.reset(snap) input.reset(snap)
return ParseResult.FAIL() return ParseResult.FAIL()
return Parser(combinedParser, name=name, parsers=parsers) return combinedParser
# allOf -> a b c ... # allOf -> a b c ...
@staticmethod @staticmethod
def allOf(*parsers, createNode, exception=None, name="all"): def allOf(*parsers, createNode, exception=None):
if len(parsers) == 0: if len(parsers) == 0:
raise RuntimeError("Pass one parser at least") raise RuntimeError("Pass one parser at least")
@@ -119,36 +89,33 @@ class Parser:
return ParseResult.OK(node) return ParseResult.OK(node)
return Parser(extendedParser, name=name, parsers=parsers)
return extendedParser
# leftAssociative -> left | left OP right # leftAssociative -> left | left OP right
@staticmethod @staticmethod
def leftAssociativeOperatorParser(leftParser, operatorTokenTypes, rightParser, createNode, name="leftAssoc"): def leftAssociativeOperatorParser(leftParser, operatorTokenType, rightParser, createNode):
from smnp.ast.node.operator import Operator
def parse(input): def parse(input):
operatorParser = Parser.oneOfTerminals(*operatorTokenTypes, createNode=lambda val, pos: Operator.withChildren([val], pos))
left = leftParser(input) left = leftParser(input)
oneAtLeast = False
if left.result: if left.result:
operator = operatorParser(input) operator = Parser.terminalParser(operatorTokenType, lambda val, pos: OperatorNode.withChildren([val], pos))(input)
while operator.result: while operator.result:
oneAtLeast = True
right = rightParser(input) right = rightParser(input)
left = ParseResult.OK(createNode(left.node, operator.node, right.node)) left = ParseResult.OK(createNode(left.node, operator.node, right.node))
operator = operatorParser(input) operator = Parser.terminalParser(operatorTokenType, lambda val, pos: OperatorNode.withChildren([val], pos))(input)
if oneAtLeast:
return left return left
return ParseResult.FAIL() return ParseResult.FAIL()
return Parser(parse, name=name, parsers=[leftParser, '|'.join([t.value for t in operatorTokenTypes]), rightParser]) return parse
@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 # loop -> start item* end
@staticmethod @staticmethod
def loop(startParser, itemParser, endParser, createNode, name="loop"): def loop(startParser, itemParser, endParser, createNode):
def parse(input): def parse(input):
items = [] items = []
start = startParser(input) start = startParser(input)
@@ -164,10 +131,10 @@ class Parser:
return ParseResult.FAIL() return ParseResult.FAIL()
return Parser(parse, name, parsers=[startParser, itemParser, endParser]) return parse
@staticmethod @staticmethod
def doAssert(parser, expected, name="!!"): def doAssert(parser, expected):
def parse(input): def parse(input):
result = parser(input) result = parser(input)
@@ -178,10 +145,10 @@ class Parser:
return result return result
return Parser(parse, name, parsers=parser) return parse
@staticmethod @staticmethod
def optional(parser, name="??"): def optional(parser):
def parse(input): def parse(input):
result = parser(input) result = parser(input)
if result.result: if result.result:
@@ -189,14 +156,10 @@ class Parser:
return ParseResult.OK(NoneNode()) return ParseResult.OK(NoneNode())
return Parser(parse, name, parsers=[parser]) return parse
@staticmethod @staticmethod
def epsilon(): def many(parser, createNode):
return lambda *args: ParseResult.OK(NoneNode())
@staticmethod
def many(parser, createNode, name="*"):
def parse(input): def parse(input):
results = [] results = []
snap = input.snapshot() snap = input.snapshot()
@@ -210,15 +173,4 @@ class Parser:
input.reset(snap) input.reset(snap)
return ParseResult.OK(createNode(results, pos) if len(results) > 0 else NoneNode()) return ParseResult.OK(createNode(results, pos) if len(results) > 0 else NoneNode())
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 return parse

View File

@@ -10,9 +10,6 @@ class Sound:
def play(self): def play(self):
sd.play(self.data, self.fs, blocking=True) 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): def __str__(self):
return f"sound[{self.file}]" return f"sound[{self.file}]"

View File

@@ -1,66 +0,0 @@
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()

View File

View File

@@ -1,23 +0,0 @@
import argparse
VERSION = "0.1"
DESCRIPTION = """
Simple Music Notation Processor is a command line tool enabling you to do some music stuff using custom domain-specific language.
"""
class CliParser(object):
def __init__(self):
self.parser = argparse.ArgumentParser(description=DESCRIPTION)
self.parser.add_argument('file', nargs='*', help='a file containing SMNP code')
self.parser.add_argument('-c', '--code', action='append', default=[], type=str, help='a string with SMNP code')
self.parser.add_argument('-m', '--mic', action='store_true', help='test microphone level')
self.parser.add_argument('-C', '--config', type=argparse.FileType('-r'), help='a file containing settings (not implemented yet)')
self.parser.add_argument('-p', '--params', action='append', help='pass arguments to program (not implemented yet)')
self.parser.add_argument('-v', '--version', action='version')
self.parser.add_argument('--tokens', action='store_true', help='print tokens of parsed code')
self.parser.add_argument('--ast', action='store_true', help='print abstract syntax tree of parsed code')
self.parser.add_argument('--dry-run', action='store_true', help='don\'t execute passed code')
self.parser.version = VERSION
def parse(self):
return self.parser.parse_args()

View File

@@ -1,19 +1,17 @@
from smnp.error.function import FunctionNotFoundException, MethodNotFoundException, IllegalFunctionInvocationException from smnp.error.function import FunctionNotFoundException, MethodNotFoundException, IllegalFunctionInvocationException
from smnp.error.runtime import RuntimeException from smnp.error.runtime import RuntimeException
from smnp.function.tools import argsTypesToString from smnp.function.tools import argsTypesToString
from smnp.runtime.evaluators.function import BodyEvaluator, Return from smnp.runtime.evaluators.function import BodyEvaluator
from smnp.type.model import Type
class Environment(): class Environment():
def __init__(self, scopes, functions, methods, source): def __init__(self, scopes, functions, methods):
self.scopes = scopes self.scopes = scopes
self.functions = functions self.functions = functions
self.methods = methods self.methods = methods
self.customFunctions = [] self.customFunctions = []
self.customMethods = [] self.customMethods = []
self.callStack = [] self.callStack = []
self.source = source
def invokeMethod(self, object, name, args): def invokeMethod(self, object, name, args):
builtinMethodResult = self._invokeBuiltinMethod(object, name, args) builtinMethodResult = self._invokeBuiltinMethod(object, name, args)
@@ -40,15 +38,10 @@ class Environment():
if method.typeSignature.check([object])[0] and method.name == name: #Todo sprawdzic sygnature typu if method.typeSignature.check([object])[0] and method.name == name: #Todo sprawdzic sygnature typu
signatureCheckresult = method.signature.check(args) signatureCheckresult = method.signature.check(args)
if signatureCheckresult[0]: if signatureCheckresult[0]:
self.scopes.append(method.defaultArgs) self.scopes.append({argName: argValue for argName, argValue in zip(method.arguments, list(signatureCheckresult[1:]))})
self.scopes[-1].update({argName: argValue for argName, argValue in zip(method.arguments, list(signatureCheckresult[1:]))})
self.scopes[-1][method.alias] = object self.scopes[-1][method.alias] = object
self.callStack.append(CallStackItem(name)) self.callStack.append(CallStackItem(name))
result = Type.void() result = BodyEvaluator.evaluate(method.body, self).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
try:
BodyEvaluator.evaluate(method.body, self).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
except Return as r:
result = r.value
self.callStack.pop(-1) self.callStack.pop(-1)
self.scopes.pop(-1) self.scopes.pop(-1)
return (True, result) return (True, result)
@@ -81,27 +74,20 @@ class Environment():
if function.name == name: if function.name == name:
signatureCheckresult = function.signature.check(args) signatureCheckresult = function.signature.check(args)
if signatureCheckresult[0]: if signatureCheckresult[0]:
self.appendScope(function.defaultArgs) self.scopes.append({ argName: argValue for argName, argValue in zip(function.arguments, list(signatureCheckresult[1:])) })
appendedScopeIndex = len(self.scopes)-1
self.scopes[-1].update({ argName: argValue for argName, argValue in zip(function.arguments, list(signatureCheckresult[1:])) })
self.callStack.append(CallStackItem(name)) self.callStack.append(CallStackItem(name))
result = Type.void() result = BodyEvaluator.evaluate(function.body, self).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
try:
BodyEvaluator.evaluate(function.body, self).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
except Return as r:
result = r.value
self.callStack.pop(-1) self.callStack.pop(-1)
self.popScope(mergeVariables=False) self.scopes.pop(-1)
self.removeScopesAfter(appendedScopeIndex)
return (True, result) return (True, result)
raise IllegalFunctionInvocationException(f"{function.name}{function.signature.string}", f"{name}{argsTypesToString(args)}") raise IllegalFunctionInvocationException(f"{function.name}{function.signature.string}", f"{name}{argsTypesToString(args)}")
return (False, None) return (False, None)
def addCustomFunction(self, name, signature, arguments, body, defaultArguments): def addCustomFunction(self, name, signature, arguments, body):
if len([fun for fun in self.functions + self.customFunctions if fun.name == name]) > 0: if len([fun for fun in self.functions + self.customFunctions if fun.name == name]) > 0:
raise RuntimeException(f"Cannot redeclare function '{name}'", None) raise RuntimeException(f"Cannot redeclare function '{name}'", None)
self.customFunctions.append(CustomFunction(name, signature, arguments, body, defaultArguments)) self.customFunctions.append(CustomFunction(name, signature, arguments, body))
# TODO: # TODO:
# There is still problem with checking existing of generic types, like lists: # There is still problem with checking existing of generic types, like lists:
@@ -112,14 +98,14 @@ class Environment():
# function foo() { return 2 } # function foo() { return 2 }
# } # }
# Then calling [1, 2, 3, 4].foo() will produce 1, when the second method is more suitable # Then calling [1, 2, 3, 4].foo() will produce 1, when the second method is more suitable
def addCustomMethod(self, typeSignature, alias, name, signature, arguments, body, defaultArguments): def addCustomMethod(self, typeSignature, alias, name, signature, arguments, body):
if len([m for m in self.methods if m.name == name and m.signature.matchers[0] == typeSignature.matchers[0]]) > 0: if len([m for m in self.methods if m.name == name and m.signature.matchers[0] == typeSignature.matchers[0]]) > 0:
raise RuntimeException(f"Cannot redeclare method '{name}' for type '{typeSignature.matchers[0]}'", None) raise RuntimeException(f"Cannot redeclare method '{name}' for type '{typeSignature.matchers[0]}'", None)
if len([m for m in self.customMethods if m.name == name and m.typeSignature.matchers[0] == typeSignature.matchers[0]]) > 0: if len([m for m in self.customMethods if m.name == name and m.typeSignature.matchers[0] == typeSignature.matchers[0]]) > 0:
raise RuntimeException(f"Cannot redeclare method '{name}' for type '{typeSignature.matchers[0]}'", None) raise RuntimeException(f"Cannot redeclare method '{name}' for type '{typeSignature.matchers[0]}'", None)
self.customMethods.append(CustomMethod(typeSignature, alias, name, signature, arguments, body, defaultArguments)) self.customMethods.append(CustomMethod(typeSignature, alias, name, signature, arguments, body))
def findVariable(self, name, type=None, pos=None): def findVariable(self, name, type=None, pos=None):
for scope in reversed(self.scopes): for scope in reversed(self.scopes):
@@ -142,20 +128,6 @@ class Environment():
else: else:
return scope return scope
def appendScope(self, variables=None):
if variables is None:
variables = {}
self.scopes.append(variables)
def popScope(self, mergeVariables=True):
lastScope = self.scopes.pop(-1)
if mergeVariables:
self.scopes[-1].update(lastScope)
def removeScopesAfter(self, index):
del self.scopes[index:]
def scopesToString(self): def scopesToString(self):
return "Scopes:\n" + ("\n".join([ f" [{i}]: {scope}" for i, scope in enumerate(self.scopes) ])) return "Scopes:\n" + ("\n".join([ f" [{i}]: {scope}" for i, scope in enumerate(self.scopes) ]))
@@ -190,23 +162,22 @@ class Environment():
class CallStackItem: class CallStackItem:
def __init__(self, function): def __init__(self, function):
self.function = function self.function = function
self.value = None
class CustomFunction: class CustomFunction:
def __init__(self, name, signature, arguments, body, defaultArgs): def __init__(self, name, signature, arguments, body):
self.name = name self.name = name
self.signature = signature self.signature = signature
self.arguments = arguments self.arguments = arguments
self.body = body self.body = body
self.defaultArgs = defaultArgs
class CustomMethod: class CustomMethod:
def __init__(self, typeSignature, alias, name, signature, arguments, body, defaultArgs): def __init__(self, typeSignature, alias, name, signature, arguments, body):
self.typeSignature = typeSignature self.typeSignature = typeSignature
self.alias = alias self.alias = alias
self.name = name self.name = name
self.signature = signature self.signature = signature
self.arguments = arguments self.arguments = arguments
self.body = body self.body = body
self.defaultArgs = defaultArgs

View File

@@ -1,3 +1,7 @@
def createEnvironment(): from smnp.environment.environment import Environment
return from smnp.module import functions, methods
def createEnvironment():
return Environment([{}], functions, methods)

View File

@@ -2,7 +2,6 @@ class SmnpException(Exception):
def __init__(self, msg, pos): def __init__(self, msg, pos):
self.msg = msg self.msg = msg
self.pos = pos self.pos = pos
self.file = None
def _title(self): def _title(self):
pass pass
@@ -13,8 +12,5 @@ class SmnpException(Exception):
def _position(self): def _position(self):
return "" if self.pos is None else f" [line {self.pos[0]+1}, col {self.pos[1]+1}]" return "" if self.pos is None else f" [line {self.pos[0]+1}, col {self.pos[1]+1}]"
def _file(self):
return "" if self.file is None else f"File: {self.file}"
def message(self): def message(self):
return f"{self._title()}\n{self._file()} {self._position()}\n\n{self.msg}\n{self._postMessage()}" return f"{self._title()}{self._position()}:\n{self.msg}\n{self._postMessage()}"

View File

@@ -1,12 +0,0 @@
from smnp.error.runtime import RuntimeException
class CustomException(RuntimeException):
def __init__(self, message, pos):
super().__init__(message, pos)
def _title(self):
return "Execution Error"
def _postMessage(self):
return "\n" + self.environment.callStackToString() if len(self.environment.callStack) > 0 else ""

View File

@@ -12,9 +12,6 @@ class Signature:
def varargSignature(varargMatcher, *basicSignature, wrapVarargInValue=False): def varargSignature(varargMatcher, *basicSignature, wrapVarargInValue=False):
def check(args): def check(args):
if any([ matcher.optional for matcher in [ varargMatcher, *basicSignature ]]):
raise RuntimeError("Vararg signature can't have optional arguments")
if len(basicSignature) > len(args): if len(basicSignature) > len(args):
return doesNotMatchVararg(basicSignature) return doesNotMatchVararg(basicSignature)
@@ -41,7 +38,7 @@ def doesNotMatchVararg(basicSignature):
def signature(*signature): def signature(*signature):
def check(args): def check(args):
if len(args) > len(signature) or len(args) < len([ matcher for matcher in signature if not matcher.optional ]): if len(signature) != len(args):
return doesNotMatch(signature) return doesNotMatch(signature)
for s, a in zip(signature, args): for s, a in zip(signature, args):
@@ -55,12 +52,6 @@ def signature(*signature):
return Signature(check, string, signature) return Signature(check, string, signature)
def optional(matcher):
matcher.optional = True
matcher.string += "?"
return matcher
def doesNotMatch(sign): def doesNotMatch(sign):
return (False, *[None for n in sign]) return (False, *[None for n in sign])

View File

@@ -5,5 +5,7 @@ from smnp.program.interpreter import Interpreter
def loadStandardLibrary(): def loadStandardLibrary():
mainSource = resource_string('smnp.library.code', 'main.mus').decode("utf-8") mainSource = resource_string('smnp.library.code', 'main.mus').decode("utf-8")
env = Interpreter.interpretString(mainSource, "<stdlib>") boolSource = resource_string('smnp.library.code', 'bool.mus').decode("utf-8")
return env env = Interpreter.interpretString(mainSource)
return Interpreter.interpretString(boolSource, baseEnvironment=env)

View File

@@ -1,38 +1,16 @@
from smnp.cli.parser import CliParser import sys
from smnp.error.base import SmnpException from smnp.error.base import SmnpException
from smnp.library.loader import loadStandardLibrary
from smnp.module.mic.lib.detector.noise import NoiseDetector
from smnp.program.interpreter import Interpreter from smnp.program.interpreter import Interpreter
def interpretFile(args, file):
stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None
Interpreter.interpretFile(file, printTokens=args.tokens, printAst=args.ast, execute=not args.dry_run, baseEnvironment=stdLibraryEnv)
def interpretString(args, string):
stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None
Interpreter.interpretString(string, printTokens=args.tokens, printAst=args.ast, execute=not args.dry_run, baseEnvironment=stdLibraryEnv, source='<cli>')
def main(): def main():
try: try:
parser = CliParser() #stdLibraryEnv = loadStandardLibrary()
args = parser.parse() Interpreter.interpretFile(sys.argv[1], printTokens=False, printAst=True, baseEnvironment=None)
if args.mic:
nd = NoiseDetector()
nd.test()
for code in args.code:
interpretString(args, code)
for file in args.file:
interpretFile(args, file)
except SmnpException as e: except SmnpException as e:
print(e.message()) print(e.message())
except KeyboardInterrupt: except KeyboardInterrupt:
print("Program interrupted") print("Program interrupted")

View File

@@ -1,4 +1,4 @@
from smnp.module import system, mic, note, iterable, sound, synth, string, util, integer, float from smnp.module import system, mic, note, iterable, sound, synth, string, util
functions = [ *system.functions, *mic.functions, *note.functions, *iterable.functions, *sound.functions, *synth.functions, *string.functions, *util.functions, *integer.functions, *float.functions ] functions = [ *system.functions, *mic.functions, *note.functions, *iterable.functions, *sound.functions, *synth.functions, *string.functions, *util.functions ]
methods = [ *system.methods, *mic.methods, *note.methods, *iterable.methods, *sound.methods, *synth.methods, *string.methods, *util.methods, *integer.methods, *float.methods ] methods = [ *system.methods, *mic.methods, *note.methods, *iterable.methods, *sound.methods, *synth.methods, *string.functions, *util.methods ]

View File

@@ -1,4 +0,0 @@
from smnp.module.float.function import float
functions = [ float.function ]
methods = []

View File

@@ -1,26 +0,0 @@
from smnp.function.model import CombinedFunction, Function
from smnp.function.signature import signature
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
_signature1 = signature(ofType(Type.INTEGER))
def _function1(env, value):
return Type.float(float(value.value))
_signature2 = signature(ofType(Type.STRING))
def _function2(env, value):
return Type.float(float(value.value))
_signature3 = signature(ofType(Type.FLOAT))
def _function3(env, value):
return value
function = CombinedFunction(
'Float',
Function(_signature1, _function1),
Function(_signature2, _function2),
Function(_signature3, _function3),
)

View File

@@ -1,4 +0,0 @@
from smnp.module.integer.function import integer
functions = [ integer.function ]
methods = []

View File

@@ -1,25 +0,0 @@
from smnp.function.model import CombinedFunction, Function
from smnp.function.signature import signature
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
_signature1 = signature(ofType(Type.FLOAT))
def _function1(env, value):
return Type.integer(int(value.value))
_signature2 = signature(ofType(Type.STRING))
def _function2(env, value):
return Type.integer(int(value.value))
_signature3 = signature(ofType(Type.INTEGER))
def _function3(env, value):
return value
function = CombinedFunction(
'Integer',
Function(_signature1, _function1),
Function(_signature2, _function2),
Function(_signature3, _function3),
)

View File

@@ -1,4 +1,4 @@
from smnp.module.iterable.function import map, get from smnp.module.iterable.function import combine, flat, map, range, get
functions = [ map.function ] functions = [ combine.function, flat.function, map.function, range.function ]
methods = [ get.function ] methods = [ get.function ]

View File

@@ -0,0 +1,18 @@
from functools import reduce
from smnp.function.model import Function
from smnp.function.signature import varargSignature
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofTypes
_signature = varargSignature(ofTypes(Type.LIST))
def _function(env, vararg):
if len(vararg) == 1:
return vararg[0]
combined = reduce(lambda x, y: x.value + y.value, vararg)
return Type.list(combined)
function = Function(_signature, _function, 'combine')

View File

@@ -0,0 +1,23 @@
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')

View File

@@ -0,0 +1,36 @@
from smnp.function.model import CombinedFunction, Function
from smnp.function.signature import signature
from smnp.note.model import Note
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
_signature1 = signature(ofType(Type.INTEGER))
def _function1(env, upper):
return Type.list(list(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)))
_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)))
_signature4 = signature(ofType(Type.NOTE), ofType(Type.NOTE))
def _function4(env, lower, upper):
return Type.list([Type.note(n) for n in Note.range(lower.value, upper.value)])
# TODO
# signature5 = range(note lower, note upper, integer step) OR step = "diatonic" | "chromatic" | "augmented" | "diminish"
function = CombinedFunction(
'range',
Function(_signature1, _function1),
Function(_signature2, _function2),
Function(_signature3, _function3),
Function(_signature4, _function4),
)

View File

@@ -1,4 +1,4 @@
from smnp.module.note.function import note from smnp.module.note.function import tuplet, transpose, semitones, octave, duration, interval
functions = [ note.function ] functions = [ semitones.function, interval.function, transpose.function, tuplet.function ]
methods = [] methods = [ duration.function, octave.function ]

View File

@@ -0,0 +1,11 @@
from smnp.function.model import Function
from smnp.function.signature import signature
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
_signature = signature(ofType(Type.NOTE), ofType(Type.INTEGER))
def _function(env, note, duration):
return Type.note(note.value.withDuration(duration.value))
function = Function(_signature, _function, 'withDuration')

View File

@@ -0,0 +1,27 @@
from smnp.function.model import Function, CombinedFunction
from smnp.function.signature import varargSignature
from smnp.note.interval import intervalToString
from smnp.note.model import Note
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes
_signature1 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
def _function1(env, vararg):
withoutPauses = [note.value for note in vararg if note.type == Type.NOTE]
if len(withoutPauses) < 2:
return Type.list([])
semitones = [Note.checkInterval(withoutPauses[i-1], withoutPauses[i]) for i in range(1, len(withoutPauses))]
return Type.list([Type.string(intervalToString(s)) for s in semitones]).decompose()
_signature2 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
def _function2(env, vararg):
return Type.list([_function1(env, arg.value) for arg in vararg]).decompose()
function = CombinedFunction(
'interval',
Function(_signature1, _function1),
Function(_signature2, _function2)
)

View File

@@ -1,11 +0,0 @@
from smnp.function.model import Function
from smnp.function.signature import signature
from smnp.note.model import Note
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
_signature = signature(ofType(Type.STRING), ofType(Type.INTEGER), ofType(Type.INTEGER), ofType(Type.BOOL))
def _function(env, note, octave, duration, dot):
return Type.note(Note(note.value, octave.value, duration.value, dot.value))
function = Function(_signature, _function, 'Note')

View File

@@ -0,0 +1,11 @@
from smnp.function.model import Function
from smnp.function.signature import signature
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
_signature = signature(ofType(Type.NOTE), ofType(Type.INTEGER))
def _function(env, note, octave):
return Type.note(note.value.withOctave(octave.value))
function = Function(_signature, _function, 'withOctave')

View File

@@ -0,0 +1,25 @@
from smnp.function.model import Function, CombinedFunction
from smnp.function.signature import varargSignature
from smnp.note.model import Note
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes
_signature1 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
def _function1(env, vararg):
withoutPauses = [note.value for note in vararg if note.type == Type.NOTE]
if len(withoutPauses) < 2:
return Type.list([])
return Type.list([Type.integer(Note.checkInterval(withoutPauses[i-1], withoutPauses[i])) for i in range(1, len(withoutPauses))]).decompose()
_signature2 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
def _function2(env, vararg):
return Type.list([_function1(env, arg.value) for arg in vararg]).decompose()
function = CombinedFunction(
"semitones",
Function(_signature1, _function1),
Function(_signature2, _function2),
)

View File

@@ -0,0 +1,22 @@
from smnp.function.model import CombinedFunction, Function
from smnp.function.signature import varargSignature
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes
_signature1 = varargSignature(ofTypes(Type.INTEGER, Type.NOTE), ofTypes(Type.INTEGER))
def _function1(env, value, vararg):
transposed = [Type.note(arg.value.transpose(value.value)) if arg.type == Type.NOTE else arg for arg in vararg]
return Type.list(transposed).decompose()
_signature2 = varargSignature(listOf(Type.INTEGER, Type.NOTE), ofTypes(Type.INTEGER))
def _function2(env, value, vararg):
return Type.list([_function1(env, value, arg.value) for arg in vararg]).decompose()
function = CombinedFunction(
'transpose',
Function(_signature1, _function1),
Function(_signature2, _function2)
)

View File

@@ -0,0 +1,23 @@
from smnp.function.model import CombinedFunction, Function
from smnp.function.signature import signature, varargSignature
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes
_signature1 = varargSignature(ofTypes(Type.NOTE), ofTypes(Type.INTEGER), ofTypes(Type.INTEGER))
def _function1(env, n, m, vararg):
t = [Type.note(arg.value.withDuration(int(arg.value.duration * n.value / m.value))) for arg in vararg]
return Type.list(t).decompose()
_signature2 = signature(ofTypes(Type.INTEGER), ofTypes(Type.INTEGER), listOf(Type.NOTE))
def _function2(env, n, m, notes):
return _function1(env, n, m, notes.value)
function = CombinedFunction(
'tuplet',
Function(_signature1, _function1),
Function(_signature2, _function2)
)

View File

@@ -1,4 +1,4 @@
from smnp.module.string.function import stringify from smnp.module.string.function import concat
functions = [] functions = [ concat.function ]
methods = [ stringify.function ] methods = []

View File

@@ -0,0 +1,11 @@
from smnp.function.model import Function
from smnp.function.signature import varargSignature
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
_signature = varargSignature(ofType(Type.STRING))
def _function(env, vararg):
return Type.string("".join([ arg.value for arg in vararg ]))
function = Function(_signature, _function, 'concat')

View File

@@ -1,10 +0,0 @@
from smnp.function.model import Function
from smnp.function.signature import signature
from smnp.type.model import Type
from smnp.type.signature.matcher.type import allTypes
_signature = signature(allTypes())
def _function(env, object):
return Type.string(object.stringify())
function = Function(_signature, _function, 'toString')

View File

@@ -1,4 +1,4 @@
from smnp.module.synth.function import synth, pause, plot, compile from smnp.module.synth.function import synth, pause
functions = [ synth.function, pause.function, plot.function, compile.function ] functions = [ synth.function, pause.function ]
methods = [] methods = []

View File

@@ -1,157 +0,0 @@
from smnp.error.runtime import RuntimeException
from smnp.function.model import Function, CombinedFunction
from smnp.function.signature import varargSignature
from smnp.module.synth.lib.wave import compilePolyphony
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes, ofType
DEFAULT_BPM = 120
DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1]
DEFAULT_DECAY = 4
DEFAULT_ATTACK = 100
DEFAULT_TUNING = 440
def getBpm(config):
key = Type.string("bpm")
if key in config.value:
bpm = config.value[key]
if bpm.type != Type.INTEGER or bpm.value <= 0:
raise RuntimeException("The 'bpm' property must be positive integer", None)
return bpm.value
return DEFAULT_BPM
def getOvertones(config):
key = Type.string("overtones")
if key in config.value:
overtones = config.value[key]
rawOvertones = [overtone.value for overtone in overtones.value]
if overtones.type != Type.LIST or not all(overtone.type in [Type.FLOAT, Type.INTEGER] for overtone in overtones.value):
raise RuntimeException("The 'overtones' property must be list of floats", None)
if len(rawOvertones) < 1:
raise RuntimeException("The 'overtones' property must contain one overtone at least", None)
if any(overtone < 0 for overtone in rawOvertones):
raise RuntimeException("The 'overtones' property mustn't contain negative values", None)
if sum(rawOvertones) > 1.0:
raise RuntimeException("The 'overtones' property must contain overtones which sum is not greater than 1.0", None)
return rawOvertones
return DEFAULT_OVERTONES
def getDecay(config):
key = Type.string("decay")
if key in config.value:
decay = config.value[key]
if not decay.type in [Type.INTEGER, Type.FLOAT] or decay.value < 0:
raise RuntimeException("The 'decay' property must be non-negative integer or float", None)
return decay.value
return DEFAULT_DECAY
def getAttack(config):
key = Type.string("attack")
if key in config.value:
attack = config.value[key]
if not attack.type in [Type.INTEGER, Type.FLOAT] or attack.value < 0:
raise RuntimeException("The 'attack' property must be non-negative integer or float", None)
return attack.value
return DEFAULT_ATTACK
def getTuning(config):
key = Type.string("tuning")
if key in config.value:
tuning = config.value[key]
if not tuning.type in [Type.INTEGER, Type.FLOAT] or tuning.value < 0:
raise RuntimeException("The 'tuning' property must be non-negative integer or float", None)
return tuning.value
return DEFAULT_TUNING
class Config:
def __init__(self, bpm, overtones, decay, attack, tuning):
self.bpm = bpm
self.overtones = overtones
self.decay = decay
self.attack = attack
self.tuning = tuning
@staticmethod
def default():
return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK, DEFAULT_TUNING)
_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
def _function1(env, notes):
return Type.list([Type.float(float(m)) for m in __function1(notes)])
def __function1(notes):
return compilePolyphony([note.value for note in notes], Config.default())
_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
def _function2(env, notes):
return Type.list([Type.float(float(m)) for m in __function2(notes)])
def __function2(notes):
return compilePolyphony([ notes ], Config.default())
_signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function3(env, config, notes):
return Type.list([ Type.float(float(m)) for m in __function3(config, notes) ])
def __function3(config, notes):
rawNotes = [note.value for note in notes]
bpm = getBpm(config)
overtones = getOvertones(config)
decay = getDecay(config)
attack = getAttack(config)
tuning = getTuning(config)
return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack, tuning))
_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function4(env, config, notes):
return Type.list([ Type.float(float(m)) for m in __function4(config, notes) ])
def __function4(config, notes):
bpm = getBpm(config)
overtones = getOvertones(config)
decay = getDecay(config)
attack = getAttack(config)
tuning = getTuning(config)
return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack, tuning))
function = CombinedFunction(
'wave',
Function(_signature1, _function1),
Function(_signature2, _function2),
Function(_signature3, _function3),
Function(_signature4, _function4),
)

View File

@@ -1,13 +1,13 @@
from smnp.function.model import Function from smnp.function.model import Function
from smnp.function.signature import signature from smnp.function.signature import signature
from smnp.module.synth.lib.wave import pause from smnp.module.synth.lib import player
from smnp.type.model import Type from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofTypes from smnp.type.signature.matcher.type import ofTypes
_signature = signature(ofTypes(Type.INTEGER)) _signature = signature(ofTypes(Type.INTEGER))
def _function(env, value): def _function(env, value):
bpm = env.findVariable('bpm') bpm = env.findVariable('bpm')
pause(value.value, bpm.value) player.pause(value.value, bpm.value)
function = Function(_signature, _function, 'pause') function = Function(_signature, _function, 'pause')

View File

@@ -1,13 +0,0 @@
from smnp.function.model import Function
from smnp.function.signature import signature
from smnp.module.synth.lib.wave import plot
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
_signature = signature(listOf(Type.FLOAT))
def _function(env, wave):
rawWave = [ m.value for m in wave.value ]
plot(rawWave)
function = Function(_signature, _function, 'plotWave')

View File

@@ -1,47 +1,25 @@
from smnp.function.model import Function, CombinedFunction from smnp.function.model import CombinedFunction, Function
from smnp.function.signature import varargSignature from smnp.function.signature import varargSignature
from smnp.module.synth.function import compile from smnp.module.synth.lib.player import playNotes
from smnp.module.synth.lib.wave import play
from smnp.type.model import Type from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes, ofType from smnp.type.signature.matcher.type import ofTypes
_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER)) _signature1 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
def _function1(env, notes): def _function1(env, vararg):
wave = compile.__function1(notes) notes = [arg.value for arg in vararg]
play(wave) bpm = env.findVariable('bpm')
playNotes(notes, bpm.value)
_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER)) _signature2 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
def _function2(env, notes): def _function2(env, vararg):
wave = compile.__function2(notes) for arg in vararg:
play(wave) _function1(env, arg.value)
_signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function3(env, config, notes):
wave = compile.__function3(config, notes)
play(wave)
_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function4(env, config, notes):
wave = compile.__function4(config, notes)
play(wave)
_signature5 = varargSignature(listOf(Type.FLOAT))
def _function5(env, waves):
for wave in waves:
rawWave = [m.value for m in wave.value]
play(rawWave)
function = CombinedFunction( function = CombinedFunction(
'synth', 'synth',
Function(_signature1, _function1), Function(_signature1, _function1),
Function(_signature2, _function2), Function(_signature2, _function2)
Function(_signature3, _function3),
Function(_signature4, _function4),
Function(_signature5, _function5)
) )

View File

@@ -0,0 +1,24 @@
import time
from smnp.module.synth.lib.wave import sine
from smnp.note.model import Note
def playNotes(notes, bpm):
for note in notes:
{
Note: play,
int: pause
}[type(note)](note, bpm)
def play(note, bpm):
frequency = note.toFrequency()
duration = 60 * 4 / note.duration / bpm
duration *= 1.5 if note.dot else 1
sine(frequency, duration)
def pause(value, bpm):
time.sleep(60 * 4 / value / bpm)

View File

@@ -1,80 +1,12 @@
import time import time
import matplotlib.pyplot as plt
import numpy as np import numpy as np
import sounddevice as sd import sounddevice as sd
from smnp.note.pitch import Tuning
from smnp.type.model import Type
FS = 44100 FS = 44100
def pause(value, bpm):
time.sleep(60 * 4 / value / bpm)
def plot(wave):
X = np.arange(len(wave))
plt.plot(X, wave)
plt.show()
def play(wave):
sd.play(wave)
time.sleep(len(wave) / FS)
def compilePolyphony(notes, config):
tuning = Tuning(config.tuning)
compiledLines = [1 / len(notes) * compileNotes(line, config, tuning) for line in notes]
return sum(adjustSize(compiledLines))
def adjustSize(compiledLines):
maxSize = max(len(line) for line in compiledLines)
return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines]
def compileNotes(notes, config, tuning):
dispatcher = {
Type.NOTE: lambda note, overtones: sineForNote(note.value, config, tuning),
Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config)
}
return np.concatenate([dispatcher[note.type](note, config) for note in notes])
def sineForNote(note, config, tuning):
frequency = note.toFrequency(tuning)
duration = 60 * 4 / note.duration / config.bpm
duration *= 1.5 if note.dot else 1
return sound(frequency, duration, config)
def sound(frequency, duration, config):
return attack(decay(sum(overtone * sine((i+1) * frequency, duration) for i, overtone in enumerate(config.overtones) if overtone > 0), config), config)
def decay(wave, config):
magnitude = np.exp(-config.decay/len(wave) * np.arange(len(wave)))
return magnitude * wave
def attack(wave, config):
magnitude = -np.exp(-config.attack / len(wave) * np.arange(len(wave)))+1 \
if config.attack > 0 \
else np.ones(len(wave))
return magnitude * wave
def sine(frequency, duration): def sine(frequency, duration):
return (np.sin(2 * np.pi * np.arange(FS * duration) * frequency / FS)).astype(np.float32) samples = (np.sin(2*np.pi*np.arange(FS*duration)*frequency/FS)).astype(np.float32)
sd.play(samples, FS)
time.sleep(duration)
def silenceForPause(value, config):
duration = 60 * 4 / value / config.bpm
return np.zeros(int(FS * duration))

View File

@@ -1,4 +1,4 @@
from smnp.module.system.function import sleep, display, displayln, debug, exit, type, read from smnp.module.system.function import sleep, display, debug, exit, type
functions = [ debug.function, display.function, displayln.function, exit.function, sleep.function, type.function, read.function ] functions = [ debug.function, display.function, exit.function, sleep.function, type.function ]
methods = [] methods = []

View File

@@ -4,7 +4,7 @@ from smnp.type.signature.matcher.type import allTypes
_signature = varargSignature(allTypes()) _signature = varargSignature(allTypes())
def _function(env, vararg): def _function(env, vararg):
print("".join([arg.stringify() for arg in vararg]), end="") print("".join([arg.stringify() for arg in vararg]))
function = Function(_signature, _function, 'print') function = Function(_signature, _function, 'print')

View File

@@ -1,10 +0,0 @@
from smnp.function.model import Function
from smnp.function.signature import varargSignature
from smnp.type.signature.matcher.type import allTypes
_signature = varargSignature(allTypes())
def _function(env, vararg):
print("".join([arg.stringify() for arg in vararg]))
function = Function(_signature, _function, 'println')

View File

@@ -1,72 +1,3 @@
from smnp.error.runtime import RuntimeException
from smnp.function.model import CombinedFunction, Function
from smnp.function.signature import signature
from smnp.token.tokenizers.bool import boolTokenizer
from smnp.token.tokenizers.note import noteTokenizer
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
_signature1 = signature()
def _function1(env):
value = input()
return Type.string(value)
_signature2 = signature(ofType(Type.STRING))
def _function2(env, prompt):
print(prompt.value, end="")
value = input()
return Type.string(value)
_signature3 = signature(ofType(Type.TYPE))
def _function3(env, type):
value = input()
return getValueAccordingToType(value, type)
def getValueAccordingToType(value, type):
try:
if type.value == Type.STRING:
return Type.string(value)
if type.value == Type.INTEGER:
return Type.integer(int(value))
if type.value == Type.BOOL:
consumedChars, token = boolTokenizer(value, 0, 0)
if consumedChars > 0:
return Type.bool(token.value)
return ValueError()
if type.value == Type.NOTE:
consumedChars, token = noteTokenizer(value, 0, 0)
if consumedChars > 0:
return Type.note(token.value)
raise ValueError()
raise RuntimeException(f"Type {type.value.name.lower()} is not suuported", None)
except ValueError:
raise RuntimeException(f"Invalid value '{value}' for type {type.value.name.lower()}", None)
_signature4 = signature(ofType(Type.STRING), ofType(Type.TYPE))
def _function4(env, prompt, type):
print(prompt.value, end="")
value = input()
return getValueAccordingToType(value, type)
function = CombinedFunction(
'read',
Function(_signature1, _function1),
Function(_signature2, _function2),
Function(_signature3, _function3),
Function(_signature4, _function4)
)
# TODO read function # TODO read function
# def read(args, env): # def read(args, env):

View File

@@ -1,12 +1,29 @@
import random import random as r
from smnp.function.model import Function from smnp.error.function import IllegalArgumentException
from smnp.function.signature import signature from smnp.function.model import Function, CombinedFunction
from smnp.function.signature import varargSignature
from smnp.type.model import Type from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType from smnp.type.signature.matcher.list import listMatches
from smnp.type.signature.matcher.type import ofTypes
_signature = signature(ofType(Type.INTEGER), ofType(Type.INTEGER))
def _function(env, min, max):
return Type.integer(random.randint(min.value, max.value))
function = Function(_signature, _function, 'rand') def forType(t):
_signature = varargSignature(listMatches(ofTypes(Type.PERCENT), ofTypes(t)))
def _function(env, vararg):
choice = r.random()
acc = 0
if sum(arg.value[0].value for arg in vararg) != 1.0:
raise IllegalArgumentException("Sum of all percentage values must be equal 100%")
for arg in vararg:
percent, item = arg.value
acc += percent.value
if choice <= acc:
return item
return Function(_signature, _function)
function = CombinedFunction('random', *[ forType(t) for t in Type if t != Type.VOID ])
#TODO: sample

View File

@@ -16,8 +16,8 @@ class Note:
self.duration = duration self.duration = duration
self.dot = dot self.dot = dot
def toFrequency(self, tuning): def toFrequency(self):
return tuning[self.note] * 2 ** self.octave return self.note.toFrequency() * 2 ** self.octave
def transpose(self, interval): def transpose(self, interval):
origIntRepr = self._intRepr() origIntRepr = self._intRepr()

View File

@@ -2,7 +2,6 @@ from enum import Enum
from smnp.error.note import NoteException from smnp.error.note import NoteException
_semitone = 2**(1/12)
class NotePitch(Enum): class NotePitch(Enum):
C = 0 C = 0
@@ -18,21 +17,21 @@ class NotePitch(Enum):
AIS = 10 AIS = 10
H = 11 H = 11
# def toFrequency(self): def toFrequency(self):
# return { return {
# NotePitch.C: 16.35, NotePitch.C: 16.35,
# NotePitch.CIS: 17.32, NotePitch.CIS: 17.32,
# NotePitch.D: 18.35, NotePitch.D: 18.35,
# NotePitch.DIS: 19.45, NotePitch.DIS: 19.45,
# NotePitch.E: 20.60, NotePitch.E: 20.60,
# NotePitch.F: 21.83, NotePitch.F: 21.83,
# NotePitch.FIS: 23.12, NotePitch.FIS: 23.12,
# NotePitch.G: 24.50, NotePitch.G: 24.50,
# NotePitch.GIS: 25.96, NotePitch.GIS: 25.96,
# NotePitch.A: 27.50, NotePitch.A: 27.50,
# NotePitch.AIS: 29.17, NotePitch.AIS: 29.17,
# NotePitch.H: 30.87 NotePitch.H: 30.87
# }[self] }[self]
def __str__(self): def __str__(self):
return self.name return self.name
@@ -42,23 +41,11 @@ class NotePitch(Enum):
@staticmethod @staticmethod
def toPitch(string): def toPitch(string):
if string.lower() in stringToPitch: try:
return stringToPitch[string.lower()] return stringToPitch[string.lower()]
except KeyError as e:
if string.upper() in NotePitch.__members__:
return NotePitch[string.upper()]
raise NoteException(f"Note '{string}' does not exist") raise NoteException(f"Note '{string}' does not exist")
class Tuning(object):
def __init__(self, a4=440):
self.tuning = { value: a4/_semitone**(57-i) for i, value in enumerate(NotePitch.__members__) }
def __getitem__(self, item):
return self.tuning[str(item)]
stringToPitch = { stringToPitch = {
'cb': NotePitch.H, 'cb': NotePitch.H,
'c': NotePitch.C, 'c': NotePitch.C,

View File

@@ -1,7 +1,6 @@
from smnp.ast.parser import parse from smnp.ast.parser import parse
from smnp.environment.environment import Environment from smnp.environment.factory import createEnvironment
from smnp.error.runtime import RuntimeException from smnp.error.runtime import RuntimeException
from smnp.module import functions, methods
from smnp.program.FileReader import readLines from smnp.program.FileReader import readLines
from smnp.runtime.evaluator import evaluate from smnp.runtime.evaluator import evaluate
from smnp.token.tokenizer import tokenize from smnp.token.tokenizer import tokenize
@@ -10,31 +9,16 @@ from smnp.token.tokenizer import tokenize
class Interpreter: class Interpreter:
@staticmethod @staticmethod
def interpretString(string, source, printTokens=False, printAst=False, execute=True, baseEnvironment=None): def interpretString(string, printTokens=False, printAst=False, baseEnvironment=None):
return Interpreter._interpret( return Interpreter._interpret(string.splitlines(), printTokens, printAst, baseEnvironment)
string.splitlines(),
source,
printTokens,
printAst,
execute,
baseEnvironment,
)
@staticmethod @staticmethod
def interpretFile(file, printTokens=False, printAst=False, execute=True, baseEnvironment=None, source=None): def interpretFile(file, printTokens=False, printAst=False, baseEnvironment=None):
return Interpreter._interpret( return Interpreter._interpret(readLines(file), printTokens, printAst, baseEnvironment)
readLines(file),
source if source is not None else file,
printTokens,
printAst,
execute,
baseEnvironment,
)
@staticmethod @staticmethod
def _interpret(lines, source, printTokens=False, printAst=False, execute=True, baseEnvironment=None): def _interpret(lines, printTokens=False, printAst=False, baseEnvironment=None):
environment = Environment([{}], functions, methods, source=source) environment = createEnvironment()
if baseEnvironment is not None: if baseEnvironment is not None:
environment.extend(baseEnvironment) environment.extend(baseEnvironment)
@@ -47,11 +31,9 @@ class Interpreter:
if printAst: if printAst:
ast.print() ast.print()
if execute:
evaluate(ast, environment) evaluate(ast, environment)
return environment return environment
except RuntimeException as e: except RuntimeException as e:
e.environment = environment e.environment = environment
e.file = environment.source
raise e raise e

View File

@@ -1,11 +1,4 @@
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.program import Program
from smnp.ast.node.ret import Return
from smnp.ast.node.throw import Throw
from smnp.error.runtime import RuntimeException from smnp.error.runtime import RuntimeException
from smnp.type.model import Type from smnp.type.model import Type
@@ -71,23 +64,9 @@ class EvaluationResult():
def evaluate(node, environment): def evaluate(node, environment):
from smnp.runtime.evaluators.program import ProgramEvaluator from smnp.runtime.evaluators.program import ProgramEvaluator
from smnp.runtime.evaluators.expression import expressionEvaluator 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
from smnp.runtime.evaluators.throw import ThrowEvaluator
result = Evaluator.oneOf( result = Evaluator.oneOf(
Evaluator.forNodes(ProgramEvaluator.evaluate, Program), Evaluator.forNodes(ProgramEvaluator.evaluate, Program),
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(ThrowEvaluator.evaluate, Throw),
#Evaluator.forNodes(ImportEvaluator.evaluate, ImportNode), #Evaluator.forNodes(ImportEvaluator.evaluate, ImportNode),
#Evaluator.forNodes(FunctionDefinitionEvaluator.evaluate, FunctionDefinitionNode), #Evaluator.forNodes(FunctionDefinitionEvaluator.evaluate, FunctionDefinitionNode),
#Evaluator.forNodes(ExtendEvaluator.evaluate, ExtendNode), #Evaluator.forNodes(ExtendEvaluator.evaluate, ExtendNode),

View File

@@ -1,4 +1,5 @@
from smnp.ast.node.identifier import Identifier, FunctionCall from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.invocation import FunctionCallNode
from smnp.error.runtime import RuntimeException from smnp.error.runtime import RuntimeException
from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluator import Evaluator
from smnp.runtime.evaluators.expression import expressionEvaluator from smnp.runtime.evaluators.expression import expressionEvaluator
@@ -13,15 +14,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 left = expressionEvaluator(doAssert=True)(node.left, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
right = node.right right = node.right
if type(right) == Identifier: if type(right) == IdentifierNode:
try: try:
return left.properties[right.value] return left.properties[right.value]
except KeyError: except KeyError:
raise RuntimeException(f"Unknown property '{right.value}' of type '{left.type.name.lower()}'", right.pos) raise RuntimeException(f"Unknown property '{right.value}' of type '{left.type.name.lower()}'", right.pos)
if type(right) == FunctionCall: if type(right) == FunctionCallNode:
try: try:
arguments = abstractIterableEvaluator(expressionEvaluator(doAssert=True))(right.arguments, environment) arguments = abstractIterableEvaluator(expressionEvaluator(True))(right.arguments, environment)
return environment.invokeMethod(left, right.name.value, arguments) return environment.invokeMethod(left, right.name.value, arguments)
except RuntimeException as e: except RuntimeException as e:
raise updatePos(e, right) raise updatePos(e, right)

View File

@@ -1,3 +1,4 @@
from smnp.error.runtime import RuntimeException
from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluator import Evaluator
from smnp.runtime.evaluators.expression import expressionEvaluator from smnp.runtime.evaluators.expression import expressionEvaluator
@@ -6,9 +7,16 @@ class AssignmentEvaluator(Evaluator):
@classmethod @classmethod
def evaluator(cls, node, environment): def evaluator(cls, node, environment):
target = node.left.value target = node.target.value
value = expressionEvaluator(doAssert=True)(node.right, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult 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
scopeOfExistingVariable = environment.findVariableScope(target)
if scopeOfExistingVariable is None:
environment.scopes[-1][target] = value environment.scopes[-1][target] = value
else:
scopeOfExistingVariable[target] = value
return value return value

View File

@@ -0,0 +1,104 @@
from smnp.ast.node.identifier import IdentifierNode
from smnp.runtime.evaluator import evaluate, Evaluator, EvaluationResult
from smnp.runtime.evaluators.expression import expressionEvaluator
from smnp.type.model import Type
class AsteriskEvaluator(Evaluator):
@classmethod
def evaluator(cls, node, environment):
iterator = expressionEvaluator(doAssert=True)(node.iterator, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
return Evaluator.oneOf(
cls._numberIteratorAsteriskEvaluator(iterator),
cls._listIteratorAsteriskEvaluator(iterator),
cls._mapIteratorAsteriskEvaluator(iterator)
)(node, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
@classmethod
def _numberIteratorAsteriskEvaluator(cls, evaluatedIterator):
def evaluator(node, environment):
if evaluatedIterator.type == Type.INTEGER:
results = []
automaticVariable = cls._automaticNamedVariable(node.iterator, environment, "_")
for i in range(evaluatedIterator.value):
environment.scopes[-1][automaticVariable] = Type.integer(i + 1)
result = evaluate(node.statement, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
if result is None or result.type == Type.VOID:
results = None
if results is not None:
results.append(result)
del environment.scopes[-1][automaticVariable]
return EvaluationResult.OK(Type.list(results).decompose() if results is not None else Type.void())
return EvaluationResult.FAIL()
return evaluator
@classmethod
def _automaticNamedVariable(cls, iteratorNode, environment, prefix=''):
if type(iteratorNode) == IdentifierNode:
return cls._automaticVariableName(environment, prefix, iteratorNode.value, False)
else:
return cls._automaticVariableName(environment, prefix, '', True)
@classmethod
def _automaticVariableName(cls, environment, prefix='', suffix='', startWithNumber=False):
number = 1 if startWithNumber else ''
variableName = lambda x: f"{prefix}{x}{suffix}"
while environment.findVariableScope(variableName(number)) is not None:
if number == '':
number = 1
else:
number += 1
return variableName(number)
@classmethod
def _listIteratorAsteriskEvaluator(cls, evaluatedIterator):
def evaluator(node, environment):
if evaluatedIterator.type == Type.LIST:
results = []
automaticVariableKey = cls._automaticNamedVariable(node.iterator, environment, "_")
automaticVariableValue = cls._automaticNamedVariable(node.iterator, environment, "__")
for i, v in enumerate(evaluatedIterator.value):
environment.scopes[-1][automaticVariableKey] = Type.integer(i + 1)
environment.scopes[-1][automaticVariableValue] = v
result = evaluate(node.statement, environment).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
if result is not None and result.type != Type.VOID:
results.append(result)
del environment.scopes[-1][automaticVariableKey]
del environment.scopes[-1][automaticVariableValue]
return EvaluationResult.OK(Type.list(results).decompose())
return EvaluationResult.FAIL()
return evaluator
@classmethod
def _mapIteratorAsteriskEvaluator(cls, evaluatedIterator):
def evaluator(node, environment):
if evaluatedIterator.type == Type.MAP:
results = []
automaticVariableKey = cls._automaticNamedVariable(node.iterator, environment, "_")
automaticVariableValue = cls._automaticNamedVariable(node.iterator, environment, "__")
for k, v in evaluatedIterator.value.items():
environment.scopes[-1][automaticVariableKey] = k
environment.scopes[-1][automaticVariableValue] = v
result = evaluate(node.statement, environment).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
if result is not None and result.type != Type.VOID:
results.append(result)
del environment.scopes[-1][automaticVariableKey]
del environment.scopes[-1][automaticVariableValue]
return EvaluationResult.OK(Type.list(results).decompose())
return EvaluationResult.FAIL()
return evaluator

View File

@@ -1,99 +0,0 @@
from smnp.ast.node.atom import StringLiteral, IntegerLiteral, NoteLiteral, BoolLiteral, TypeLiteral, FloatLiteral
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, EvaluationResult
from smnp.runtime.evaluators.expression import expressionEvaluator
from smnp.runtime.evaluators.float import FloatEvaluator
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 = {}
keyEvaluator = Evaluator.oneOf(
Evaluator.forNodes(lambda node, environment: EvaluationResult.OK(Type.string(node.value)), Identifier),
expressionEvaluator(doAssert=True)
)
for entry in node.children:
key = keyEvaluator(entry.key, environment).value
if key in map:
raise RuntimeException(f"Duplicated key '{key.stringify()}' found in map", entry.pos)
map[key] = expressionEvaluator(doAssert=True)(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(FloatEvaluator.evaluate, FloatLiteral),
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

View File

@@ -5,12 +5,12 @@ class BlockEvaluator(Evaluator):
@classmethod @classmethod
def evaluator(cls, node, environment): def evaluator(cls, node, environment):
environment.appendScope() environment.scopes.append({})
for child in node.children: for child in node.children:
evaluate(child, environment) evaluate(child, environment)
environment.popScope() environment.scopes.pop(-1)
# #
# def evaluateBlock(block, environment): # def evaluateBlock(block, environment):

View File

@@ -1,35 +0,0 @@
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)

View File

@@ -1,9 +1,15 @@
from smnp.ast.node.condition import IfElse from smnp.ast.node.access import AccessNode
from smnp.ast.node.expression import Sum, Relation, And, Or, Loop from smnp.ast.node.assignment import AssignmentNode
from smnp.ast.node.factor import NotOperator, Power from smnp.ast.node.asterisk import AsteriskNode
from smnp.ast.node.identifier import FunctionCall, Assignment from smnp.ast.node.bool import BoolLiteralNode
from smnp.ast.node.term import Product from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.unit import MinusOperator, Access 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.error.runtime import RuntimeException from smnp.error.runtime import RuntimeException
from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluator import Evaluator
from smnp.type.model import Type from smnp.type.model import Type
@@ -11,36 +17,32 @@ from smnp.type.model import Type
def expressionEvaluator(doAssert=False): def expressionEvaluator(doAssert=False):
def evaluateExpression(node, environment): 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.sum import SumEvaluator from smnp.runtime.evaluators.access import AccessEvaluator
from smnp.runtime.evaluators.relation import RelationEvaluator from smnp.runtime.evaluators.assignment import AssignmentEvaluator
from smnp.runtime.evaluators.condition import IfElseEvaluator from smnp.runtime.evaluators.asterisk import AsteriskEvaluator
from smnp.runtime.evaluators.logic import AndEvaluator from smnp.runtime.evaluators.map import MapEvaluator
from smnp.runtime.evaluators.logic import OrEvaluator from smnp.runtime.evaluators.type import TypeEvaluator
from smnp.runtime.evaluators.bool import BoolEvaluator
result = Evaluator.oneOf( result = Evaluator.oneOf(
Evaluator.forNodes(FunctionCallEvaluator.evaluate, FunctionCall), Evaluator.forNodes(FunctionCallEvaluator.evaluate, FunctionCallNode),
Evaluator.forNodes(MinusEvaluator.evaluate, MinusOperator), Evaluator.forNodes(StringEvaluator.evaluate, StringLiteralNode),
Evaluator.forNodes(AccessEvaluator.evaluate, Access), Evaluator.forNodes(IntegerEvaluator.evaluate, IntegerLiteralNode),
Evaluator.forNodes(NotEvaluator.evaluate, NotOperator), Evaluator.forNodes(NoteEvaluator.evaluate, NoteLiteralNode),
Evaluator.forNodes(PowerEvaluator.evaluate, Power), Evaluator.forNodes(BoolEvaluator.evaluate, BoolLiteralNode),
Evaluator.forNodes(LoopEvaluator.evaluate, Loop), Evaluator.forNodes(TypeEvaluator.evaluate, TypeNode),
Evaluator.forNodes(AssignmentEvaluator.evaluate, Assignment), Evaluator.forNodes(IdentifierEvaluator.evaluate, IdentifierNode),
Evaluator.forNodes(ProductEvaluator.evaluate, Product), Evaluator.forNodes(ListEvaluator.evaluate, ListNode),
Evaluator.forNodes(SumEvaluator.evaluate, Sum), Evaluator.forNodes(AccessEvaluator.evaluate, AccessNode),
Evaluator.forNodes(RelationEvaluator.evaluate, Relation), Evaluator.forNodes(AssignmentEvaluator.evaluate, AssignmentNode),
Evaluator.forNodes(IfElseEvaluator.evaluate, IfElse), Evaluator.forNodes(AsteriskEvaluator.evaluate, AsteriskNode),
Evaluator.forNodes(AndEvaluator.evaluate, And), Evaluator.forNodes(MapEvaluator.evaluate, MapNode)
Evaluator.forNodes(OrEvaluator.evaluate, Or),
AtomEvaluator.evaluate
)(node, environment) )(node, environment)
if doAssert and result.result and result.value.type == Type.VOID: if doAssert and result.result and result.value.type == Type.VOID:
@@ -48,17 +50,5 @@ def expressionEvaluator(doAssert=False):
return result return result
return evaluateExpression return evaluateExpression
def expressionEvaluatorWithMatcher(matcher, exceptionProvider, doAssert=True):
def evaluate(node, environment):
value = expressionEvaluator(doAssert=doAssert)(node, environment).value
if not matcher.match(value):
raise exceptionProvider(value)
return value
return evaluate

View File

@@ -17,11 +17,11 @@ class ExtendEvaluator(Evaluator):
@classmethod @classmethod
def _typeToMethodSignature(cls, node): def _typeToMethodSignature(cls, node):
if type(node.specifiers) == NoneNode: if type(node.specifiers) == NoneNode:
return signature(ofType(node.type.value)) return signature(ofType(node.type))
elif node.type.value == Type.LIST: elif node.type == Type.LIST:
return signature(listSpecifier(node.specifiers[0])) return signature(listSpecifier(node.specifiers[0]))
elif node.type.value == Type.MAP: elif node.type == Type.MAP:
return signature(mapSpecifier(node.specifiers[0], node.specifiers[1])) return signature(mapSpecifier(node.specifiers[0], node.specifiers[1]))
@classmethod @classmethod
@@ -32,8 +32,8 @@ class ExtendEvaluator(Evaluator):
@classmethod @classmethod
def _evaluateMethodDefinition(cls, node, environment, type, variable): def _evaluateMethodDefinition(cls, node, environment, type, variable):
name = node.name.value name = node.name.value
defaultArguments, signature = argumentsNodeToMethodSignature(node.arguments, environment) signature = argumentsNodeToMethodSignature(node.arguments)
arguments = [arg.variable.value for arg in node.arguments] arguments = [arg.variable.value for arg in node.arguments]
body = node.body body = node.body
environment.addCustomMethod(type, variable, name, signature, arguments, body, defaultArguments) environment.addCustomMethod(type, variable, name, signature, arguments, body)

View File

@@ -1,9 +0,0 @@
from smnp.runtime.evaluator import Evaluator
from smnp.type.model import Type
class FloatEvaluator(Evaluator):
@classmethod
def evaluator(cls, node, environment):
return Type.float(node.value)

View File

@@ -4,7 +4,6 @@ from smnp.runtime.evaluators.expression import expressionEvaluator
from smnp.runtime.evaluators.iterable import abstractIterableEvaluator from smnp.runtime.evaluators.iterable import abstractIterableEvaluator
from smnp.runtime.tools.error import updatePos from smnp.runtime.tools.error import updatePos
from smnp.runtime.tools.signature import argumentsNodeToMethodSignature from smnp.runtime.tools.signature import argumentsNodeToMethodSignature
from smnp.type.model import Type
class FunctionCallEvaluator(Evaluator): class FunctionCallEvaluator(Evaluator):
@@ -25,10 +24,10 @@ class FunctionDefinitionEvaluator(Evaluator):
def evaluator(cls, node, environment): def evaluator(cls, node, environment):
try: try:
name = node.name.value name = node.name.value
defaultArguments, signature = argumentsNodeToMethodSignature(node.arguments, environment) signature = argumentsNodeToMethodSignature(node.arguments)
arguments = [ arg.variable.value for arg in node.arguments ] arguments = [ arg.variable.value for arg in node.arguments ]
body = node.body body = node.body
environment.addCustomFunction(name, signature, arguments, body, defaultArguments) environment.addCustomFunction(name, signature, arguments, body)
except RuntimeException as e: except RuntimeException as e:
raise updatePos(e, node) raise updatePos(e, node)
@@ -39,6 +38,8 @@ class BodyEvaluator(Evaluator):
def evaluator(cls, node, environment): def evaluator(cls, node, environment):
for child in node.children: for child in node.children:
evaluate(child, environment) evaluate(child, environment)
if environment.callStack[-1].value is not None:
return environment.callStack[-1].value
class ReturnEvaluator(Evaluator): class ReturnEvaluator(Evaluator):
@@ -46,21 +47,7 @@ class ReturnEvaluator(Evaluator):
@classmethod @classmethod
def evaluator(cls, node, environment): def evaluator(cls, node, environment):
if len(environment.callStack) > 0: if len(environment.callStack) > 0:
returnValue = expressionEvaluator()(node.value, environment).value returnValue = expressionEvaluator(doAssert=True)(node.value, environment)
raise Return(returnValue) environment.callStack[-1].value = returnValue.value
# Disclaimer
# Exception system usage to control program execution flow is really bad idea.
# However because of lack of 'goto' instruction equivalent in Python
# there is to need to use some mechanism to break function execution on 'return' statement
# and immediately go to Environment's method 'invokeFunction()' or 'invokeMethod()',
# which can handle value that came with exception and return it to code being executed.
else: else:
raise RuntimeException("Cannot use 'return' statement outside a function or method", node.pos, environment) raise RuntimeException("Cannot use 'return' statement outside a function or method", node.pos, environment)
class Return(Exception):
def __init__(self, value):
if value is None:
value = Type.void()
self.value = value

View File

@@ -1,3 +1,4 @@
from smnp.ast.node.none import NoneNode
from smnp.program.interpreter import Interpreter from smnp.program.interpreter import Interpreter
from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluator import Evaluator
@@ -6,6 +7,13 @@ class ImportEvaluator(Evaluator):
@classmethod @classmethod
def evaluator(cls, node, environment): 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 source = node.source
newEnvironment = Interpreter.interpretFile(source.value, baseEnvironment=environment) newEnvironment = Interpreter.interpretFile(source.value)
environment.extend(newEnvironment) environment.extend(newEnvironment)

View File

@@ -1,21 +0,0 @@
from smnp.runtime.evaluator import Evaluator
from smnp.runtime.evaluators.expression import expressionEvaluator
from smnp.type.model import Type
class AndEvaluator(Evaluator):
@classmethod
def evaluator(cls, node, environment):
left = expressionEvaluator(doAssert=True)(node.left, environment).value
right = expressionEvaluator(doAssert=True)(node.right, environment).value
return Type.bool(left.value and right.value)
class OrEvaluator(Evaluator):
@classmethod
def evaluator(cls, node, environment):
left = expressionEvaluator(doAssert=True)(node.left, environment).value
right = expressionEvaluator(doAssert=True)(node.right, environment).value
return Type.bool(left.value or right.value)

View File

@@ -1,121 +0,0 @@
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.appendScope()
output = {
Type.INTEGER: cls.numberEvaluator,
Type.BOOL: cls.boolEvaluator,
Type.LIST: cls.listEvaluator,
Type.MAP: cls.mapEvaluator
}[iterator.type](node, environment, iterator, parameters, node.filter)
environment.popScope()
except KeyError:
raise RuntimeException(f"The {iterator.type.name.lower()} type cannot stand as an iterator for loop statement", node.left.pos)
return Type.list(output)
@classmethod
def numberEvaluator(cls, node, environment, evaluatedIterator, parameters, filter):
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)
if cls.doFilter(filter, environment):
output.append(evaluate(node.right, environment).value)
return output
@classmethod
def doFilter(cls, filter, environment):
if type(filter) is not NoneNode:
evaluation = expressionEvaluator(doAssert=True)(filter, environment).value
if evaluation.type != Type.BOOL:
raise RuntimeException(f"Expected {Type.BOOL.name.lower()} as filter expression, found {evaluation.type.name.lower()}", filter.pos)
return evaluation.value
return True
@classmethod
def boolEvaluator(cls, node, environment, evaluatedIterator, parameters, filter):
output = []
if len(parameters) > 0:
raise RuntimeException(f"Loop with logic iterator can't' handle any parameters", node.parameters.pos)
condition = evaluatedIterator
while condition.value:
if cls.doFilter(filter, environment):
output.append(evaluate(node.right, environment).value)
condition = expressionEvaluator(doAssert=True)(node.left, environment).value
return output
@classmethod
def listEvaluator(cls, node, environment, evaluatedIterator, parameters, filter):
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
if cls.doFilter(filter, environment):
output.append(evaluate(node.right, environment).value)
return output
@classmethod
def mapEvaluator(cls, node, environment, evaluatedIterator, parameters, filter):
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
if cls.doFilter(filter, environment):
output.append(evaluate(node.right, environment).value)
return output

View File

@@ -0,0 +1,19 @@
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 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)

Some files were not shown because too many files have changed in this diff Show More