diff --git a/smnp/Evaluator.py b/smnp/Evaluator.py deleted file mode 100644 index f89c9f1..0000000 --- a/smnp/Evaluator.py +++ /dev/null @@ -1,204 +0,0 @@ -from AST import * -from Note import Note -from Error import RuntimeException - -def evaluateProgram(program, environment): - for node in program.children: - evaluate(node, environment) - -def evaluateInteger(integer, environment): - return integer.value - -def evaluatePercent(percent, environment): - return percent.value.value * 0.01 - -def evaluateIdentifier(identifier, environment): - value = environment.findVariable(identifier.identifier) - return value - -def evaluateString(string, environment): - value = string.value - for scope in reversed(environment.scopes): - for k, v in scope.items(): - value = value.replace('{' + k + '}', objectString(v)) #TODO: poprawic - return value - - - -def evaluateNote(note, environment): - return note.value - -def evaluateFunctionDefinition(definition, environment): - name = definition.name - params = list([p for p in flatListNode(definition.parameters) if not isinstance(p, CommaNode)]) - body = definition.body - - if not isinstance(definition.parent, Program): - raise RuntimeException(name.pos, f"Functions can be defined only on the top level of script") - - for p in params: - if not isinstance(p, IdentifierNode): - raise RuntimeException(p.pos, "Parameter of function definition must be an identifier") - - if name.identifier in environment.customFunctions or name.identifier in environment.functions: - raise RuntimeException(name.pos, f"Function '{name.identifier}' already exists") - - environment.customFunctions[name.identifier] = { - 'params': params, - 'body': flatListNode(body) - } - -def flatListNode(listNode): - if len(listNode.children[0].children) == 1: - return [] - return _flatListNode(listNode.children[0], []) - -def _flatListNode(listItemNode, list = []): - if len(listItemNode.children) == 2: - child1 = listItemNode.children[0] - child2 = listItemNode.children[1] - list.append(child1) - _flatListNode(child2, list) - return list - -def evaluateAccess(access, environment): - - element = evaluate(access.element, environment) - #TODO: narazie tylko metody działają - e = evaluateMethodCall(element, access.property, environment) - return e - -def evaluateMethodCall(element, functionCall, environment): - funcName = functionCall.identifier.identifier - arguments = evaluateList(functionCall.arguments, environment) - arguments.insert(0, element) - #for name, function in environment.customFunctions.items(): - #if funcName == name: - #if len(function['params']) != len(arguments): - #raise RuntimeException(functionCall.pos, f"Calling '{funcName}' requires {len(function['params'])} and {len(arguments)} was passed") - #environment.scopes.append({ function['params'][i].identifier: v for i, v in enumerate(arguments) }) - #returnValue = None - #for node in function['body']: - #if not isinstance(node, ReturnNode): - #evaluate(node, environment) - #else: - #returnValue = evaluateReturn(node, environment) - #environment.scopes.pop(-1) - #return returnValue - for name, definition in environment.methods[type(element)].items(): - if name == funcName: - return definition(arguments, environment) - raise RuntimeException(functionCall.pos, f"Method '{funcName}' does not exist") - -def evaluateFunctionCall(functionCall, environment): - funcName = functionCall.identifier.identifier - arguments = evaluateList(functionCall.arguments, environment) - for name, function in environment.customFunctions.items(): - if funcName == name: - if len(function['params']) != len(arguments): - raise RuntimeException(functionCall.pos, f"Calling '{funcName}' requires {len(function['params'])} and {len(arguments)} was passed") - environment.scopes.append({ function['params'][i].identifier: v for i, v in enumerate(arguments) }) - returnValue = None - for node in function['body']: - if not isinstance(node, ReturnNode): - evaluate(node, environment) - else: - returnValue = evaluateReturn(node, environment) - environment.scopes.pop(-1) - return returnValue - for name, definition in environment.functions.items(): - if name == funcName: - return definition(arguments, environment) - raise RuntimeException(functionCall.pos, f"Function '{funcName}' does not exist") - -def evaluateReturn(returnNode, environment): - return evaluate(returnNode.value, environment) - -def evaluateComma(comma, environment): - pass - -def evaluateBlock(block, environment): - environment.scopes.append({}) - for node in flatListNode(block): - evaluate(node, environment) - environment.scopes.pop(-1) - -def evaluateList(list, environment): - return [evaluate(e, environment) for e in flatListNode(list) if not isinstance(e, CommaNode)] - -def evaluateAssignment(assignment, environment): - target = assignment.target.identifier - value = evaluate(assignment.value, environment) - scopeOfExistingVariable = environment.findVariableScope(target) - if scopeOfExistingVariable is not None: - scopeOfExistingVariable[target] = value - else: - environment.scopes[-1][target] = value - -def evaluateAsterisk(asterisk, environment): - count = evaluate(asterisk.iterator, environment) - if isinstance(count, int): - for i in range(count): - if isinstance(asterisk.iterator, IdentifierNode): - environment.scopes[-1][f"_{asterisk.iterator.identifier}"] = i+1 - else: - environment.scopes[-1]["_"] = i+1 - evaluate(asterisk.statement, environment) - if isinstance(asterisk.iterator, IdentifierNode): - del environment.scopes[-1][f"_{asterisk.iterator.identifier}"] - else: - environment.scopes[-1]["_"] = i+1 - elif isinstance(count, list): - for i, v in enumerate(count): - if isinstance(asterisk.iterator, IdentifierNode): - environment.scopes[-1][f"_{asterisk.iterator.identifier}"] = i+1 - environment.scopes[-1][f"{asterisk.iterator.identifier}_"] = v - else: - environment.scopes[-1]["_"] = i+1 - environment.scopes[-1]["__"] = v - evaluate(asterisk.statement, environment) - if isinstance(asterisk.iterator, IdentifierNode): - del environment.scopes[-1][f"_{asterisk.iterator.identifier}"] - del environment.scopes[-1][f"{asterisk.iterator.identifier}_"] - else: - del environment.scopes[-1]["_"] - del environment.scopes[-1]["__"] - -def evaluateColon(colon, environment): - if isinstance(colon.a, NoteLiteralNode) and isinstance(colon.b, NoteLiteralNode): - return Note.range(colon.a.value, colon.b.value) - elif isinstance(colon.a, IntegerLiteralNode) and isinstance(colon.b, IntegerLiteralNode): - return list(range(colon.a.value, colon.b.value+1)) - raise RuntimeException(colon.pos, "Invalid colon arguments") - -def evaluate(input, environment): - if isinstance(input, Program): - return evaluateProgram(input, environment) - if isinstance(input, IntegerLiteralNode): - return evaluateInteger(input, environment) - if isinstance(input, PercentNode): - return evaluatePercent(input, environment) - if isinstance(input, StringLiteralNode): - return evaluateString(input, environment) - if isinstance(input, NoteLiteralNode): - return evaluateNote(input, environment) - if isinstance(input, FunctionDefinitionNode): - return evaluateFunctionDefinition(input, environment) - if isinstance(input, FunctionCallNode): - return evaluateFunctionCall(input, environment) - if isinstance(input, AccessNode): - return evaluateAccess(input, environment) - if isinstance(input, CommaNode): - return evaluateComma(input, environment) - if isinstance(input, BlockNode): - return evaluateBlock(input, environment) - if isinstance(input, ListNode): - return evaluateList(input, environment) - if isinstance(input, AssignmentNode): - return evaluateAssignment(input, environment) - if isinstance(input, AsteriskNode): - return evaluateAsterisk(input, environment) - if isinstance(input, ColonNode): - return evaluateColon(input, environment) - if isinstance(input, IdentifierNode): - return evaluateIdentifier(input, environment) diff --git a/smnp/ast/parsers/block.py b/smnp/ast/parsers/block.py index a6320b8..1abd442 100644 --- a/smnp/ast/parsers/block.py +++ b/smnp/ast/parsers/block.py @@ -1,4 +1,5 @@ from smnp.ast.node.block import BlockNode, CloseBlockNode, BlockItemNode +from smnp.ast.parsers.statement import parseStatement from smnp.token.type import TokenType diff --git a/smnp/ast/parsers/colon.py b/smnp/ast/parsers/colon.py index 9ee7333..b093154 100644 --- a/smnp/ast/parsers/colon.py +++ b/smnp/ast/parsers/colon.py @@ -1,4 +1,5 @@ from smnp.ast.node.colon import ColonNode +from smnp.ast.parsers.expression import parseExpression from smnp.error.syntax import SyntaxException from smnp.token.type import TokenType @@ -11,7 +12,7 @@ def parseColon(expr1, input, parent): expr2 = parseExpression(input, parent) if expr2 is None: - raise SyntaxException(input.current().pos, f"Expected expression '{input.current().value}'") + raise SyntaxException(f"Expected expression '{input.current().value}'", input.current().pos) colon = ColonNode(expr1, expr2, parent, token.pos) expr1.parent = colon expr2.parent = colon diff --git a/smnp/ast/parsers/token.py b/smnp/ast/parsers/token.py index afaaad6..ac76411 100644 --- a/smnp/ast/parsers/token.py +++ b/smnp/ast/parsers/token.py @@ -7,6 +7,6 @@ def parseToken(input, parent): value = combineParsers([ parseStatement ])(input, parent) if value is None: - raise SyntaxException(None, "Unknown statement") # TODO + raise SyntaxException("Unknown statement") # TODO return value \ No newline at end of file diff --git a/smnp/ast/tools.py b/smnp/ast/tools.py index 058afb5..4de9edd 100644 --- a/smnp/ast/tools.py +++ b/smnp/ast/tools.py @@ -15,9 +15,9 @@ def rollup(parser): def assertToken(expected, input): if not input.hasCurrent(): - raise SyntaxException(None, f"Expected '{expected}'") + raise SyntaxException(f"Expected '{expected}'") if expected != input.current().type: - raise SyntaxException(input.current().pos, f"Expected '{expected}', found '{input.current().value}'") + raise SyntaxException(f"Expected '{expected}', found '{input.current().value}'", input.current().pos) def combineParsers(parsers): diff --git a/smnp/environment/environment.py b/smnp/environment/environment.py index aec354a..3878374 100644 --- a/smnp/environment/environment.py +++ b/smnp/environment/environment.py @@ -36,8 +36,8 @@ class Environment(): return value else: return value - raise RuntimeException(pos, f"Variable '{name}' is not declared" + ( - "" if type is None else f" (expected type: {type})")) + raise RuntimeException(f"Variable '{name}' is not declared" + ( + "" if type is None else f" (expected type: {type})"), pos) def findVariableScope(self, name, type=None): for scope in reversed(self.scopes): @@ -46,4 +46,20 @@ class Environment(): if isinstance(scope[name], type): return scope else: - return scope \ No newline at end of file + return scope + + def scopesToString(self): + return "Scopes:\n" + ("\n".join([ f" [{i}]: {scope}" for i, scope in enumerate(self.scopes) ])) + + def functionsToString(self): + return "Functions:\n" + ("\n".join([ f" {function.name}(...)" for function in self.functions ])) + + def methodsToString(self): + return "Methods:\n" + ("\n".join([f" {function.name}(...)" for function in self.methods])) + + + def __str__(self): + return self.scopesToString() + self.functionsToString() + self.methodsToString() + + def __repr__(self): + return self.__str__() \ No newline at end of file diff --git a/smnp/environment/factory.py b/smnp/environment/factory.py index 2a10ed1..9ee701d 100644 --- a/smnp/environment/factory.py +++ b/smnp/environment/factory.py @@ -1,6 +1,8 @@ from smnp.environment.environment import Environment from smnp.library.function import display, sleep, semitones, interval, combine, flat, wait, rand, tuplet, synth, pause, \ - transpose, type, exit, duration, octave + transpose, type, exit, duration, octave, debug +from smnp.type.model import Type +from smnp.type.value import Value def createEnvironment(): @@ -18,7 +20,8 @@ def createEnvironment(): tuplet.function, synth.function, pause.function, - transpose.function + transpose.function, + debug.function ] methods = [ @@ -27,7 +30,7 @@ def createEnvironment(): ] variables = { - "bpm": 120 + "bpm": Value(Type.INTEGER, 120) } return Environment([ variables ], functions, methods) diff --git a/smnp/error/base.py b/smnp/error/base.py index e7aaa6f..751e110 100644 --- a/smnp/error/base.py +++ b/smnp/error/base.py @@ -1,2 +1,13 @@ class SmnpException(Exception): - pass \ No newline at end of file + def __init__(self, msg, pos): + self.msg = msg + self.pos = pos + + def _title(self): + pass + + def _position(self): + return "" if self.pos is None else f" [line {self.pos[0]+1}, col {self.pos[1]+1}]" + + def message(self): + return f"{self._title()}{self._position()}:\n{self.msg}" diff --git a/smnp/error/function.py b/smnp/error/function.py index 1d14789..d8eb957 100644 --- a/smnp/error/function.py +++ b/smnp/error/function.py @@ -2,20 +2,32 @@ from smnp.error.base import SmnpException class IllegalFunctionInvocationException(SmnpException): - def __init__(self, expected, found): - self.msg = f"Illegal function invocation\n\nExpected signature:\n{expected}\n\nFound:\n{found}" + def __init__(self, expected, found, pos=None): + super().__init__(f"Expected signature:\n{expected}\n\nFound:\n{found}", pos) + + def _title(self): + return "Invocation Error" class FunctionNotFoundException(SmnpException): - def __init__(self, function): - self.msg = f"Function '{function}' not found" + def __init__(self, function, pos=None): + super().__init__(f"Function '{function}' not found", pos) + + def _title(self): + return "Invocation Error" class MethodNotFoundException(SmnpException): - def __init__(self, object, method): - self.msg = f"Method '{method}' of type '{object}' not found" + def __init__(self, object, method, pos=None): + super().__init__(f"Method '{method}' of type '{object}' not found", pos) + + def _title(self): + return "Invocation Error" class IllegalArgumentException(SmnpException): - def __init__(self, msg): - self.msg = msg \ No newline at end of file + def __init__(self, msg, pos=None): + super().__init__(msg, pos) + + def _title(self): + return "Argument Error" \ No newline at end of file diff --git a/smnp/error/runtime.py b/smnp/error/runtime.py index dbf2745..852b397 100644 --- a/smnp/error/runtime.py +++ b/smnp/error/runtime.py @@ -2,6 +2,12 @@ from smnp.error.base import SmnpException class RuntimeException(SmnpException): - def __init__(self, pos, msg): - posStr = "" if pos is None else f" [line {pos[0]+1}, col {pos[1]+1}]" - self.msg = f"Runtime error{posStr}:\n{msg}" + def __init__(self, msg, pos): + super().__init__(msg, pos) + + def _title(self): + return "Runtime Error" + + # def message(self): + # posStr = "" if self.pos is None else f" [line {self.pos[0] + 1}, col {self.pos[1] + 1}]" + # return f"Runtime error{posStr}:\n{self.mmsg}" diff --git a/smnp/error/syntax.py b/smnp/error/syntax.py index 4358a86..8ebbd64 100644 --- a/smnp/error/syntax.py +++ b/smnp/error/syntax.py @@ -2,6 +2,9 @@ from smnp.error.base import SmnpException class SyntaxException(SmnpException): - def __init__(self, pos, msg): - posStr = "" if pos is None else f" [line {pos[0]+1}, col {pos[1]+1}]" - self.msg = f"Syntax error{posStr}:\n{msg}" + def __init__(self, msg, pos=None): + super().__init__(msg, pos) + + def _title(self): + return "Syntax Error" + diff --git a/smnp/library/function/debug.py b/smnp/library/function/debug.py new file mode 100644 index 0000000..941e8b3 --- /dev/null +++ b/smnp/library/function/debug.py @@ -0,0 +1,24 @@ +from smnp.error.function import IllegalArgumentException +from smnp.library.model import Function +from smnp.library.signature import signature, ofTypes +from smnp.type.model import Type + +_signature = signature(ofTypes(Type.STRING)) +def _function(env, parameter): + if parameter.value == "environment": + print(env) + return + elif parameter.value == "variables": + print(env.scopesToString()) + return + elif parameter.value == "functions": + print(env.functionsToString()) + return + elif parameter.value == "methods": + print(env.methodsToString()) + return + + raise IllegalArgumentException(f"Unknown parameter '{parameter.value}'") + + +function = Function(_signature, _function, 'debug') \ No newline at end of file diff --git a/smnp/library/function/pause.py b/smnp/library/function/pause.py index e3edd5a..3e7b9bf 100644 --- a/smnp/library/function/pause.py +++ b/smnp/library/function/pause.py @@ -7,7 +7,7 @@ from smnp.type.model import Type _signature = signature(ofTypes(Type.INTEGER)) def _function(env, value): bpm = env.findVariable('bpm') - player.pause(value.value, bpm) + player.pause(value.value, bpm.value) function = Function(_signature, _function, 'pause') \ No newline at end of file diff --git a/smnp/library/function/synth.py b/smnp/library/function/synth.py index 263009e..21bf1bf 100644 --- a/smnp/library/function/synth.py +++ b/smnp/library/function/synth.py @@ -8,7 +8,7 @@ _signature1 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER)) def _function1(env, vararg): notes = [arg.value for arg in vararg] bpm = env.findVariable('bpm') - playNotes(notes, bpm) + playNotes(notes, bpm.value) _signature2 = varargSignature(listOf(Type.NOTE, Type.INTEGER)) diff --git a/smnp/library/function/tuplet.py b/smnp/library/function/tuplet.py index 47608ec..616953a 100644 --- a/smnp/library/function/tuplet.py +++ b/smnp/library/function/tuplet.py @@ -18,6 +18,6 @@ def _function2(env, n, m, notes): function = CombinedFunction( 'tuplet', - Function(_function1, _function1), - Function(_function2, _function2) + Function(_signature1, _function1), + Function(_signature2, _function2) ) diff --git a/smnp/main.py b/smnp/main.py index 2febc1b..008df09 100644 --- a/smnp/main.py +++ b/smnp/main.py @@ -1,29 +1,26 @@ import sys -from smnp.error.syntax import SyntaxException -from smnp.error.runtime import RuntimeException -from smnp.token.tokenizer import tokenize + from smnp.ast.parser import parse -#from Tokenizer import tokenize -#from Evaluator import evaluate -#from Environment import createEnvironment -#from Error import SyntaxException, RuntimeException +from smnp.environment.factory import createEnvironment +from smnp.error.base import SmnpException +from smnp.runtime.evaluator import evaluate +from smnp.token.tokenizer import tokenize + def main(): try: with open(sys.argv[1], 'r') as source: lines = [line.rstrip('\n') for line in source.readlines()] - #env = createEnvironment() - tokens = tokenize(lines) ast = parse(tokens) - #evaluate(ast, env) - except SyntaxException as e: - print(e.msg) - except RuntimeException as e: - print(e.msg) + env = createEnvironment() + + evaluate(ast, env) + + except SmnpException as e: + print(e.message()) except KeyboardInterrupt: print("Program interrupted") - diff --git a/smnp/note/pitch.py b/smnp/note/pitch.py index c9ef7d9..333b5e8 100644 --- a/smnp/note/pitch.py +++ b/smnp/note/pitch.py @@ -1,6 +1,8 @@ from enum import Enum + from smnp.error.syntax import SyntaxException + class NotePitch(Enum): C = 0 CIS = 1 @@ -46,7 +48,7 @@ class NotePitch(Enum): try: return stringToPitch[string.lower()] except KeyError as e: - raise SyntaxException(None, f"Note '{string}' does not exist") + raise SyntaxException(f"Note '{string}' does not exist") #TODO jakis inny exception stringToPitch = { 'c': NotePitch.C, diff --git a/smnp/runtime/evaluator.py b/smnp/runtime/evaluator.py new file mode 100644 index 0000000..40a93cb --- /dev/null +++ b/smnp/runtime/evaluator.py @@ -0,0 +1,57 @@ +from smnp.ast.node.access import AccessNode +from smnp.ast.node.assignment import AssignmentNode +from smnp.ast.node.asterisk import AsteriskNode +from smnp.ast.node.block import BlockNode +from smnp.ast.node.colon import ColonNode +from smnp.ast.node.function import FunctionDefinitionNode, FunctionCallNode +from smnp.ast.node.identifier import IdentifierNode +from smnp.ast.node.integer import IntegerLiteralNode +from smnp.ast.node.list import ListNode +from smnp.ast.node.note import NoteLiteralNode +from smnp.ast.node.percent import PercentNode +from smnp.ast.node.program import Program +from smnp.ast.node.string import StringLiteralNode + +def evaluate(input, environment): + from smnp.runtime.evaluators.access import evaluateAccess + from smnp.runtime.evaluators.assignment import evaluateAssignment + from smnp.runtime.evaluators.asterisk import evaluateAsterisk + from smnp.runtime.evaluators.block import evaluateBlock + from smnp.runtime.evaluators.colon import evaluateColon + from smnp.runtime.evaluators.function import evaluateFunctionDefinition, evaluateFunctionCall + from smnp.runtime.evaluators.identifier import evaluateIdentifier + from smnp.runtime.evaluators.integer import evaluateInteger + from smnp.runtime.evaluators.list import evaluateList + from smnp.runtime.evaluators.note import evaluateNote + from smnp.runtime.evaluators.percent import evaluatePercent + from smnp.runtime.evaluators.program import evaluateProgram + from smnp.runtime.evaluators.string import evaluateString + + if isinstance(input, Program): + return evaluateProgram(input, environment) + if isinstance(input, IntegerLiteralNode): + return evaluateInteger(input, environment) + if isinstance(input, PercentNode): + return evaluatePercent(input, environment) + if isinstance(input, StringLiteralNode): + return evaluateString(input, environment) + if isinstance(input, NoteLiteralNode): + return evaluateNote(input, environment) + if isinstance(input, FunctionDefinitionNode): + return evaluateFunctionDefinition(input, environment) + if isinstance(input, FunctionCallNode): + return evaluateFunctionCall(input, environment) + if isinstance(input, AccessNode): + return evaluateAccess(input, environment) + if isinstance(input, BlockNode): + return evaluateBlock(input, environment) + if isinstance(input, ListNode): + return evaluateList(input, environment) + if isinstance(input, AssignmentNode): + return evaluateAssignment(input, environment) + if isinstance(input, AsteriskNode): + return evaluateAsterisk(input, environment) + if isinstance(input, ColonNode): + return evaluateColon(input, environment) + if isinstance(input, IdentifierNode): + return evaluateIdentifier(input, environment) \ No newline at end of file diff --git a/smnp/runtime/evaluators/__init__.py b/smnp/runtime/evaluators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/runtime/evaluators/access.py b/smnp/runtime/evaluators/access.py new file mode 100644 index 0000000..06ece67 --- /dev/null +++ b/smnp/runtime/evaluators/access.py @@ -0,0 +1,33 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluators.list import evaluateList + + +def evaluateAccess(access, environment): + pass + #element = evaluate(access.element, environment) + # TODO: narazie tylko metody działają + #e = evaluateMethodCall(element, access.property, environment) + + + +def evaluateMethodCall(element, functionCall, environment): + funcName = functionCall.identifier.identifier + arguments = evaluateList(functionCall.arguments, environment) + arguments.insert(0, element) + # for name, library in environment.customFunctions.items(): + # if funcName == name: + # if len(library['params']) != len(arguments): + # raise RuntimeException(functionCall.pos, f"Calling '{funcName}' requires {len(library['params'])} and {len(arguments)} was passed") + # environment.scopes.append({ library['params'][i].identifier: v for i, v in enumerate(arguments) }) + # returnValue = None + # for node in library['body']: + # if not isinstance(node, ReturnNode): + # evaluate(node, environment) + # else: + # returnValue = evaluateReturn(node, environment) + # environment.scopes.pop(-1) + # return returnValue + for name, definition in environment.methods[type(element)].items(): + if name == funcName: + return definition(arguments, environment) + raise RuntimeException(f"Method '{funcName}' does not exist", functionCall.pos) \ No newline at end of file diff --git a/smnp/runtime/evaluators/assignment.py b/smnp/runtime/evaluators/assignment.py new file mode 100644 index 0000000..3dcca70 --- /dev/null +++ b/smnp/runtime/evaluators/assignment.py @@ -0,0 +1,15 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import evaluate +from smnp.type.model import Type + + +def evaluateAssignment(assignment, environment): + target = assignment.target.identifier + value = evaluate(assignment.value, environment) + if value.type == Type.VOID: + raise RuntimeException(f"Expected expression, found '{value.type.name}'", assignment.value.pos) + scopeOfExistingVariable = environment.findVariableScope(target) + if scopeOfExistingVariable is not None: + scopeOfExistingVariable[target] = value + else: + environment.scopes[-1][target] = value \ No newline at end of file diff --git a/smnp/runtime/evaluators/asterisk.py b/smnp/runtime/evaluators/asterisk.py new file mode 100644 index 0000000..ce390d2 --- /dev/null +++ b/smnp/runtime/evaluators/asterisk.py @@ -0,0 +1,48 @@ +from smnp.ast.node.identifier import IdentifierNode +from smnp.runtime.evaluator import evaluate +from smnp.type.model import Type +from smnp.type.value import Value + + +def evaluateAsterisk(asterisk, environment): + iterator = evaluate(asterisk.iterator, environment) + if iterator.type == Type.INTEGER: + evaluateAsteriskForNumber(asterisk, environment, iterator) + + if iterator.type == Type.LIST: + evaluateAsteriskForList(asterisk, environment, iterator) + + + +def evaluateAsteriskForNumber(asterisk, environment, count): + for i in range(count.value): + if type(asterisk.iterator) == IdentifierNode: + environment.scopes[-1][f"_{asterisk.iterator.identifier}"] = Value(Type.INTEGER, i+1) + else: + environment.scopes[-1]["_"] = Value(Type.INTEGER, i+1) + + evaluate(asterisk.statement, environment) + + if type(asterisk.iterator) == IdentifierNode: + del environment.scopes[-1][f"_{asterisk.iterator.identifier}"] + else: + del environment.scopes[-1]["_"] + + +def evaluateAsteriskForList(asterisk, environment, list): + for i, v in enumerate(list.value): + if type(asterisk.iterator) == IdentifierNode: + environment.scopes[-1][f"_{asterisk.iterator.identifier}"] = Value(Type.INTEGER, i+1) + environment.scopes[-1][f"{asterisk.iterator.identifier}_"] = v + else: + environment.scopes[-1]["_"] = Value(Type.INTEGER, i+1) + environment.scopes[-1]["__"] = v + + evaluate(asterisk.statement, environment) + + if type(asterisk.iterator) == IdentifierNode: + del environment.scopes[-1][f"_{asterisk.iterator.identifier}"] + del environment.scopes[-1][f"{asterisk.iterator.identifier}_"] + else: + del environment.scopes[-1]["_"] + del environment.scopes[-1]["__"] \ No newline at end of file diff --git a/smnp/runtime/evaluators/block.py b/smnp/runtime/evaluators/block.py new file mode 100644 index 0000000..f8b2b1e --- /dev/null +++ b/smnp/runtime/evaluators/block.py @@ -0,0 +1,11 @@ +from smnp.runtime.evaluator import evaluate +from smnp.runtime.tools import flatListNode + + +def evaluateBlock(block, environment): + environment.scopes.append({}) + for node in flatListNode(block): + evaluate(node, environment) + environment.scopes.pop(-1) + + diff --git a/smnp/runtime/evaluators/colon.py b/smnp/runtime/evaluators/colon.py new file mode 100644 index 0000000..ff1bbc0 --- /dev/null +++ b/smnp/runtime/evaluators/colon.py @@ -0,0 +1,16 @@ +from smnp.ast.node.integer import IntegerLiteralNode +from smnp.ast.node.note import NoteLiteralNode +from smnp.error.runtime import RuntimeException +from smnp.note.model import Note +from smnp.type.model import Type +from smnp.type.value import Value + + +def evaluateColon(colon, environment): + if isinstance(colon.a, NoteLiteralNode) and isinstance(colon.b, NoteLiteralNode): + return Value(Type.LIST, [Value(Type.NOTE, n) for n in Note.range(colon.a.value, colon.b.value)]) + + elif isinstance(colon.a, IntegerLiteralNode) and isinstance(colon.b, IntegerLiteralNode): + return Value(Type.LIST, [Value(Type.INTEGER, i) for i in range(colon.a.value, colon.b.value + 1)]) + + raise RuntimeException("Invalid colon arguments", colon.pos) \ No newline at end of file diff --git a/smnp/runtime/evaluators/function.py b/smnp/runtime/evaluators/function.py new file mode 100644 index 0000000..2ff6ab3 --- /dev/null +++ b/smnp/runtime/evaluators/function.py @@ -0,0 +1,54 @@ +from smnp.ast.node.identifier import IdentifierNode +from smnp.ast.node.program import Program +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluators.list import evaluateList +from smnp.runtime.tools import flatListNode + + +def evaluateFunctionDefinition(definition, environment): + name = definition.name + params = list([p for p in flatListNode(definition.parameters)]) + body = definition.body + + if not isinstance(definition.parent, Program): + raise RuntimeException(f"Functions can be defined only on the top level of script", name.pos) + + for p in params: + if not isinstance(p, IdentifierNode): + raise RuntimeException("Parameter of function definition must be an identifier", p.pos, ) + + if name.identifier in environment.customFunctions or name.identifier in environment.functions: + raise RuntimeException(f"Function '{name.identifier}' already exists", name.pos) + + environment.customFunctions[name.identifier] = { + 'params': params, + 'body': flatListNode(body) + } + + +def evaluateFunctionCall(functionCall, environment): + functionName = functionCall.identifier.identifier + arguments = evaluateList(functionCall.arguments, environment).value + return environment.invokeFunction(functionName, arguments) + + +# def evaluateFunctionCall(functionCall, environment): +# funcName = functionCall.identifier.identifier +# arguments = evaluateList(functionCall.arguments, environment) +# for name, function in environment.customFunctions.items(): +# if funcName == name: +# if len(function['params']) != len(arguments): +# raise RuntimeException(functionCall.pos, f"Calling '{funcName}' requires {len(function['params'])} and {len(arguments)} was passed") +# environment.scopes.append({ function['params'][i].identifier: v for i, v in enumerate(arguments) }) +# returnValue = None +# for node in function['body']: +# if not isinstance(node, ReturnNode): +# evaluate(node, environment) +# else: +# returnValue = evaluateReturn(node, environment) +# environment.scopes.pop(-1) +# return returnValue +# for name, definition in environment.functions.items(): +# if name == funcName: +# return definition(arguments, environment) +# raise RuntimeException(functionCall.pos, f"Function '{funcName}' does not exist") \ No newline at end of file diff --git a/smnp/runtime/evaluators/identifier.py b/smnp/runtime/evaluators/identifier.py new file mode 100644 index 0000000..7cd3a41 --- /dev/null +++ b/smnp/runtime/evaluators/identifier.py @@ -0,0 +1,10 @@ +from smnp.error.runtime import RuntimeException + + +def evaluateIdentifier(identifier, environment): + try: + value = environment.findVariable(identifier.identifier) + return value + except RuntimeException as e: + e.pos = identifier.pos + raise e \ No newline at end of file diff --git a/smnp/runtime/evaluators/integer.py b/smnp/runtime/evaluators/integer.py new file mode 100644 index 0000000..ff3b3f6 --- /dev/null +++ b/smnp/runtime/evaluators/integer.py @@ -0,0 +1,6 @@ +from smnp.type.model import Type +from smnp.type.value import Value + + +def evaluateInteger(integer, environment): + return Value(Type.INTEGER, integer.value) diff --git a/smnp/runtime/evaluators/list.py b/smnp/runtime/evaluators/list.py new file mode 100644 index 0000000..74def39 --- /dev/null +++ b/smnp/runtime/evaluators/list.py @@ -0,0 +1,17 @@ +from smnp.error.runtime import RuntimeException +from smnp.runtime.evaluator import evaluate +from smnp.runtime.tools import flatListNode +from smnp.type.model import Type +from smnp.type.value import Value + + +def evaluateList(list, environment): + newList = [] + for elem in flatListNode(list): + item = evaluate(elem, environment) + if item.type == Type.VOID: + raise RuntimeException(f"Expected expression, found '{item.type.name}'", elem.pos) + newList.append(item) + return Value(Type.LIST, newList) + + diff --git a/smnp/runtime/evaluators/note.py b/smnp/runtime/evaluators/note.py new file mode 100644 index 0000000..77a3fc6 --- /dev/null +++ b/smnp/runtime/evaluators/note.py @@ -0,0 +1,6 @@ +from smnp.type.model import Type +from smnp.type.value import Value + + +def evaluateNote(note, environment): + return Value(Type.NOTE, note.value) \ No newline at end of file diff --git a/smnp/runtime/evaluators/percent.py b/smnp/runtime/evaluators/percent.py new file mode 100644 index 0000000..41bacd5 --- /dev/null +++ b/smnp/runtime/evaluators/percent.py @@ -0,0 +1,6 @@ +from smnp.type.model import Type +from smnp.type.value import Value + + +def evaluatePercent(percent, environment): + return Value(Type.PERCENT, percent.value.value * 0.01) \ No newline at end of file diff --git a/smnp/runtime/evaluators/program.py b/smnp/runtime/evaluators/program.py new file mode 100644 index 0000000..a9e9b20 --- /dev/null +++ b/smnp/runtime/evaluators/program.py @@ -0,0 +1,6 @@ +from smnp.runtime.evaluator import evaluate + + +def evaluateProgram(program, environment): + for node in program.children: + evaluate(node, environment) \ No newline at end of file diff --git a/smnp/runtime/evaluators/ret.py b/smnp/runtime/evaluators/ret.py new file mode 100644 index 0000000..b85ed79 --- /dev/null +++ b/smnp/runtime/evaluators/ret.py @@ -0,0 +1,5 @@ +from smnp.runtime.evaluator import evaluate + + +def evaluateReturn(returnNode, environment): + return evaluate(returnNode.value, environment) \ No newline at end of file diff --git a/smnp/runtime/evaluators/string.py b/smnp/runtime/evaluators/string.py new file mode 100644 index 0000000..1da5f09 --- /dev/null +++ b/smnp/runtime/evaluators/string.py @@ -0,0 +1,28 @@ +import re + +from smnp.error.runtime import RuntimeException +from smnp.type.model import Type +from smnp.type.value import Value + + +def evaluateString(string, environment): + value = interpolate(string, environment) + return Value(Type.STRING, value) + + +def interpolate(string, environment): + interpolated = string.value + for scope in reversed(environment.scopes): + for name, value in scope.items(): + interpolated = interpolated.replace('{' + name + '}', value.stringify()) + + nonMatchedVariables = re.findall(r"\{\w+\}", interpolated) + if len(nonMatchedVariables) > 0: + raise RuntimeException(f"Variable '{nonMatchedVariables[0][1:len(nonMatchedVariables[0])-1]}' is not declared", + (string.pos[0], string.pos[1] + string.value.find(nonMatchedVariables[0])+1)) + + return interpolated + +# or scope in reversed(environment.scopes): +# for k, v in scope.items(): +# value = value.replace('{' + k + '}', v) #TODO: poprawic \ No newline at end of file diff --git a/smnp/runtime/tools.py b/smnp/runtime/tools.py new file mode 100644 index 0000000..75ca159 --- /dev/null +++ b/smnp/runtime/tools.py @@ -0,0 +1,13 @@ +def flatListNode(listNode): + if len(listNode.children[0].children) == 1: + return [] + return _flatListNode(listNode.children[0], []) + + +def _flatListNode(listItemNode, list = []): + if len(listItemNode.children) == 2: + value = listItemNode.children[0] + next = listItemNode.children[1] + list.append(value) + _flatListNode(next, list) + return list diff --git a/smnp/token/tokenizer.py b/smnp/token/tokenizer.py index 7e3322d..c49b47e 100644 --- a/smnp/token/tokenizer.py +++ b/smnp/token/tokenizer.py @@ -1,27 +1,24 @@ -import sys -import time -import re from smnp.error.syntax import SyntaxException -from smnp.token.type import TokenType -from smnp.token.model import Token, TokenList -from smnp.token.tools import tokenizeChar, tokenizeRegexPattern -from smnp.token.tokenizers.paren import tokenizeOpenParen, tokenizeCloseParen -from smnp.token.tokenizers.asterisk import tokenizeAsterisk -from smnp.token.tokenizers.whitespace import tokenizeWhitespaces -from smnp.token.tokenizers.identifier import tokenizeIdentifier -from smnp.token.tokenizers.comma import tokenizeComma -from smnp.token.tokenizers.string import tokenizeString -from smnp.token.tokenizers.integer import tokenizeInteger -from smnp.token.tokenizers.bracket import tokenizeOpenBracket, tokenizeCloseBracket +from smnp.error.syntax import SyntaxException +from smnp.token.model import TokenList from smnp.token.tokenizers.assign import tokenizeAssign +from smnp.token.tokenizers.asterisk import tokenizeAsterisk +from smnp.token.tokenizers.bracket import tokenizeOpenBracket, tokenizeCloseBracket from smnp.token.tokenizers.colon import tokenizeColon +from smnp.token.tokenizers.comma import tokenizeComma from smnp.token.tokenizers.comment import tokenizeComment -from smnp.token.tokenizers.note import tokenizeNote -from smnp.token.tokenizers.function import tokenizeFunction -from smnp.token.tokenizers.ret import tokenizeReturn -from smnp.token.tokenizers.percent import tokenizePercent -from smnp.token.tokenizers.minus import tokenizeMinus from smnp.token.tokenizers.dot import tokenizeDot +from smnp.token.tokenizers.function import tokenizeFunction +from smnp.token.tokenizers.identifier import tokenizeIdentifier +from smnp.token.tokenizers.integer import tokenizeInteger +from smnp.token.tokenizers.minus import tokenizeMinus +from smnp.token.tokenizers.note import tokenizeNote +from smnp.token.tokenizers.paren import tokenizeOpenParen, tokenizeCloseParen +from smnp.token.tokenizers.percent import tokenizePercent +from smnp.token.tokenizers.ret import tokenizeReturn +from smnp.token.tokenizers.string import tokenizeString +from smnp.token.tokenizers.whitespace import tokenizeWhitespaces +from smnp.token.type import TokenType tokenizers = ( tokenizeOpenParen, @@ -58,7 +55,7 @@ def tokenize(lines): consumedChars, token = combinedTokenizer(line, current, lineNumber) if consumedChars == 0: - raise SyntaxException((lineNumber, current), f"Unknown symbol '{line[current]}'") + raise SyntaxException(f"Unknown symbol '{line[current]}'", (lineNumber, current)) current += consumedChars tokens.append(token) diff --git a/smnp/type/model.py b/smnp/type/model.py index b0bb010..73a2122 100644 --- a/smnp/type/model.py +++ b/smnp/type/model.py @@ -17,7 +17,7 @@ class Type(Enum): def _failStringify(t): - raise RuntimeException(None, f"Not able to interpret {t.name}'") + raise RuntimeException(f"Not able to interpret {t.name}'")