Reformat evaluator #1

This commit is contained in:
Bartłomiej Pluta
2019-07-04 17:57:12 +02:00
parent f0cbf37fe9
commit 34a0eda199
36 changed files with 470 additions and 270 deletions

View File

@@ -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)

View File

@@ -1,4 +1,5 @@
from smnp.ast.node.block import BlockNode, CloseBlockNode, BlockItemNode from smnp.ast.node.block import BlockNode, CloseBlockNode, BlockItemNode
from smnp.ast.parsers.statement import parseStatement
from smnp.token.type import TokenType from smnp.token.type import TokenType

View File

@@ -1,4 +1,5 @@
from smnp.ast.node.colon import ColonNode from smnp.ast.node.colon import ColonNode
from smnp.ast.parsers.expression import parseExpression
from smnp.error.syntax import SyntaxException from smnp.error.syntax import SyntaxException
from smnp.token.type import TokenType from smnp.token.type import TokenType
@@ -11,7 +12,7 @@ def parseColon(expr1, input, parent):
expr2 = parseExpression(input, parent) expr2 = parseExpression(input, parent)
if expr2 is None: 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) colon = ColonNode(expr1, expr2, parent, token.pos)
expr1.parent = colon expr1.parent = colon
expr2.parent = colon expr2.parent = colon

View File

@@ -7,6 +7,6 @@ def parseToken(input, parent):
value = combineParsers([ parseStatement ])(input, parent) value = combineParsers([ parseStatement ])(input, parent)
if value is None: if value is None:
raise SyntaxException(None, "Unknown statement") # TODO raise SyntaxException("Unknown statement") # TODO
return value return value

View File

@@ -15,9 +15,9 @@ def rollup(parser):
def assertToken(expected, input): def assertToken(expected, input):
if not input.hasCurrent(): if not input.hasCurrent():
raise SyntaxException(None, f"Expected '{expected}'") raise SyntaxException(f"Expected '{expected}'")
if expected != input.current().type: 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): def combineParsers(parsers):

View File

@@ -36,8 +36,8 @@ class Environment():
return value return value
else: else:
return value return value
raise RuntimeException(pos, f"Variable '{name}' is not declared" + ( raise RuntimeException(f"Variable '{name}' is not declared" + (
"" if type is None else f" (expected type: {type})")) "" if type is None else f" (expected type: {type})"), pos)
def findVariableScope(self, name, type=None): def findVariableScope(self, name, type=None):
for scope in reversed(self.scopes): for scope in reversed(self.scopes):
@@ -46,4 +46,20 @@ class Environment():
if isinstance(scope[name], type): if isinstance(scope[name], type):
return scope return scope
else: else:
return scope 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__()

View File

@@ -1,6 +1,8 @@
from smnp.environment.environment import Environment from smnp.environment.environment import Environment
from smnp.library.function import display, sleep, semitones, interval, combine, flat, wait, rand, tuplet, synth, pause, \ 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(): def createEnvironment():
@@ -18,7 +20,8 @@ def createEnvironment():
tuplet.function, tuplet.function,
synth.function, synth.function,
pause.function, pause.function,
transpose.function transpose.function,
debug.function
] ]
methods = [ methods = [
@@ -27,7 +30,7 @@ def createEnvironment():
] ]
variables = { variables = {
"bpm": 120 "bpm": Value(Type.INTEGER, 120)
} }
return Environment([ variables ], functions, methods) return Environment([ variables ], functions, methods)

View File

@@ -1,2 +1,13 @@
class SmnpException(Exception): class SmnpException(Exception):
pass 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}"

View File

@@ -2,20 +2,32 @@ from smnp.error.base import SmnpException
class IllegalFunctionInvocationException(SmnpException): class IllegalFunctionInvocationException(SmnpException):
def __init__(self, expected, found): def __init__(self, expected, found, pos=None):
self.msg = f"Illegal function invocation\n\nExpected signature:\n{expected}\n\nFound:\n{found}" super().__init__(f"Expected signature:\n{expected}\n\nFound:\n{found}", pos)
def _title(self):
return "Invocation Error"
class FunctionNotFoundException(SmnpException): class FunctionNotFoundException(SmnpException):
def __init__(self, function): def __init__(self, function, pos=None):
self.msg = f"Function '{function}' not found" super().__init__(f"Function '{function}' not found", pos)
def _title(self):
return "Invocation Error"
class MethodNotFoundException(SmnpException): class MethodNotFoundException(SmnpException):
def __init__(self, object, method): def __init__(self, object, method, pos=None):
self.msg = f"Method '{method}' of type '{object}' not found" super().__init__(f"Method '{method}' of type '{object}' not found", pos)
def _title(self):
return "Invocation Error"
class IllegalArgumentException(SmnpException): class IllegalArgumentException(SmnpException):
def __init__(self, msg): def __init__(self, msg, pos=None):
self.msg = msg super().__init__(msg, pos)
def _title(self):
return "Argument Error"

View File

@@ -2,6 +2,12 @@ from smnp.error.base import SmnpException
class RuntimeException(SmnpException): class RuntimeException(SmnpException):
def __init__(self, pos, msg): def __init__(self, msg, pos):
posStr = "" if pos is None else f" [line {pos[0]+1}, col {pos[1]+1}]" super().__init__(msg, pos)
self.msg = f"Runtime error{posStr}:\n{msg}"
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}"

View File

@@ -2,6 +2,9 @@ from smnp.error.base import SmnpException
class SyntaxException(SmnpException): class SyntaxException(SmnpException):
def __init__(self, pos, msg): def __init__(self, msg, pos=None):
posStr = "" if pos is None else f" [line {pos[0]+1}, col {pos[1]+1}]" super().__init__(msg, pos)
self.msg = f"Syntax error{posStr}:\n{msg}"
def _title(self):
return "Syntax Error"

View File

@@ -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')

View File

@@ -7,7 +7,7 @@ from smnp.type.model import Type
_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')
player.pause(value.value, bpm) player.pause(value.value, bpm.value)
function = Function(_signature, _function, 'pause') function = Function(_signature, _function, 'pause')

View File

@@ -8,7 +8,7 @@ _signature1 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
def _function1(env, vararg): def _function1(env, vararg):
notes = [arg.value for arg in vararg] notes = [arg.value for arg in vararg]
bpm = env.findVariable('bpm') bpm = env.findVariable('bpm')
playNotes(notes, bpm) playNotes(notes, bpm.value)
_signature2 = varargSignature(listOf(Type.NOTE, Type.INTEGER)) _signature2 = varargSignature(listOf(Type.NOTE, Type.INTEGER))

View File

@@ -18,6 +18,6 @@ def _function2(env, n, m, notes):
function = CombinedFunction( function = CombinedFunction(
'tuplet', 'tuplet',
Function(_function1, _function1), Function(_signature1, _function1),
Function(_function2, _function2) Function(_signature2, _function2)
) )

View File

@@ -1,29 +1,26 @@
import sys 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 smnp.ast.parser import parse
#from Tokenizer import tokenize from smnp.environment.factory import createEnvironment
#from Evaluator import evaluate from smnp.error.base import SmnpException
#from Environment import createEnvironment from smnp.runtime.evaluator import evaluate
#from Error import SyntaxException, RuntimeException from smnp.token.tokenizer import tokenize
def main(): def main():
try: try:
with open(sys.argv[1], 'r') as source: with open(sys.argv[1], 'r') as source:
lines = [line.rstrip('\n') for line in source.readlines()] lines = [line.rstrip('\n') for line in source.readlines()]
#env = createEnvironment()
tokens = tokenize(lines) tokens = tokenize(lines)
ast = parse(tokens) ast = parse(tokens)
#evaluate(ast, env) env = createEnvironment()
except SyntaxException as e:
print(e.msg) evaluate(ast, env)
except RuntimeException as e:
print(e.msg) except SmnpException as e:
print(e.message())
except KeyboardInterrupt: except KeyboardInterrupt:
print("Program interrupted") print("Program interrupted")

View File

@@ -1,6 +1,8 @@
from enum import Enum from enum import Enum
from smnp.error.syntax import SyntaxException from smnp.error.syntax import SyntaxException
class NotePitch(Enum): class NotePitch(Enum):
C = 0 C = 0
CIS = 1 CIS = 1
@@ -46,7 +48,7 @@ class NotePitch(Enum):
try: try:
return stringToPitch[string.lower()] return stringToPitch[string.lower()]
except KeyError as e: 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 = { stringToPitch = {
'c': NotePitch.C, 'c': NotePitch.C,

57
smnp/runtime/evaluator.py Normal file
View File

@@ -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)

View File

View File

@@ -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)

View File

@@ -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

View File

@@ -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]["__"]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1,6 @@
from smnp.runtime.evaluator import evaluate
def evaluateProgram(program, environment):
for node in program.children:
evaluate(node, environment)

View File

@@ -0,0 +1,5 @@
from smnp.runtime.evaluator import evaluate
def evaluateReturn(returnNode, environment):
return evaluate(returnNode.value, environment)

View File

@@ -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

13
smnp/runtime/tools.py Normal file
View File

@@ -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

View File

@@ -1,27 +1,24 @@
import sys
import time
import re
from smnp.error.syntax import SyntaxException from smnp.error.syntax import SyntaxException
from smnp.token.type import TokenType from smnp.error.syntax import SyntaxException
from smnp.token.model import Token, TokenList from smnp.token.model import 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.token.tokenizers.assign import tokenizeAssign 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.colon import tokenizeColon
from smnp.token.tokenizers.comma import tokenizeComma
from smnp.token.tokenizers.comment import tokenizeComment 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.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 = ( tokenizers = (
tokenizeOpenParen, tokenizeOpenParen,
@@ -58,7 +55,7 @@ def tokenize(lines):
consumedChars, token = combinedTokenizer(line, current, lineNumber) consumedChars, token = combinedTokenizer(line, current, lineNumber)
if consumedChars == 0: if consumedChars == 0:
raise SyntaxException((lineNumber, current), f"Unknown symbol '{line[current]}'") raise SyntaxException(f"Unknown symbol '{line[current]}'", (lineNumber, current))
current += consumedChars current += consumedChars
tokens.append(token) tokens.append(token)

View File

@@ -17,7 +17,7 @@ class Type(Enum):
def _failStringify(t): def _failStringify(t):
raise RuntimeException(None, f"Not able to interpret {t.name}'") raise RuntimeException(f"Not able to interpret {t.name}'")