Improve errors

This commit is contained in:
Bartłomiej Pluta
2019-06-30 20:05:23 +02:00
parent 65353a80f2
commit d0b3a8b3da
8 changed files with 105 additions and 94 deletions

49
AST.py
View File

@@ -1,8 +1,9 @@
from enum import Enum
class Node:
def __init__(self):
self.children = []
def __init__(self, pos):
self.children = []
self.pos = pos
def __repr__(self):
return self.__str__()
@@ -11,7 +12,7 @@ class Node:
return len(self.children)
def __getitem__(self, index):
return self.children[index]
return self.children[index]
def append(self, node):
self.children.append(node)
@@ -21,36 +22,38 @@ class Node:
class Program(Node):
def __init__(self):
Node.__init__(self)
Node.__init__(self, (-1, -1))
def __str__(self):
return "Program:\n" + "\n".join([str(e) for e in self.children])
class BlockNode(Node):
def __init__(self):
Node.__init__(self)
def __init__(self, pos):
Node.__init__(self, pos)
def __str__(self):
return "B{\n" + "\n".join([str(e) for e in self.children]) + "\n}"
class ListNode(Node):
def __init__(self):
Node.__init__(self)
def __init__(self, pos):
Node.__init__(self, pos)
def __str__(self):
return "@(" + ", ".join([str(e) for e in self.children]) + ")"
class IdentifierNode(Node):
def __init__(self, identifier):
def __init__(self, identifier, pos):
Node.__init__(self, pos)
self.identifier = identifier
def __str__(self):
return f"L'{self.identifier}'"
class AssignExpression(Node):
def __init__(self, target, value):
def __init__(self, target, value, pos):
Node.__init__(self, pos)
self.target = target
self.value = value
@@ -58,7 +61,8 @@ class AssignExpression(Node):
return f"A[{self.target} = {self.value}]"
class AsteriskStatementNode(Node):
def __init__(self, iterator, statement):
def __init__(self, iterator, statement, pos):
Node.__init__(self, pos)
self.iterator = iterator
self.statement = statement
@@ -66,7 +70,8 @@ class AsteriskStatementNode(Node):
return f"*({self.iterator}: {self.statement})"
class ColonNode(Node):
def __init__(self, a, b):
def __init__(self, a, b, pos):
Node.__init__(self, pos)
self.a = a
self.b = b
@@ -74,33 +79,40 @@ class ColonNode(Node):
return f":({self.a}, {self.b})"
class ExpressionNode(Node):
def __init__(self, pos):
Node.__init__(self, pos)
def __str__(self):
return f"{self.__class__.__name__}('{self.value}')"
class IntegerLiteralNode(ExpressionNode):
def __init__(self, value):
def __init__(self, value, pos):
Node.__init__(self, pos)
self.value = value
def __str__(self):
return f"i'{self.value}'"
class StringLiteralNode(ExpressionNode):
def __init__(self, value):
def __init__(self, value, pos):
Node.__init__(self, pos)
self.value = value
def __str__(self):
return f"s'{self.value}'"
class NoteLiteralNode(ExpressionNode):
def __init__(self, value):
def __init__(self, value, pos):
Node.__init__(self, pos)
self.value = value
def __str__(self):
return f"n'{self.value.note}[{self.value.octave}, {self.value.duration}]'"
class FunctionCallNode(Node):
def __init__(self, identifier, arguments):
def __init__(self, identifier, arguments, pos):
Node.__init__(self, pos)
self.identifier = identifier
self.arguments = arguments
@@ -108,11 +120,14 @@ class FunctionCallNode(Node):
return f"F({self.identifier}: {self.arguments})"
class CommaNode(Node):
def __init__(self, pos):
Node.__init__(self, pos)
def __str__(self):
return "[,]"
class PercentNode(Node):
def __init__(self, value):
def __init__(self, value, pos):
Node.__init__(self, pos)
self.value = value
def __str__(self):

View File

@@ -1,9 +1,10 @@
import sys
from Evaluator import RuntimeException, objectString
from Evaluator import objectString
from Note import *
import random
import Synth
import time
from Error import RuntimeException
types = {
int: 'integer',
@@ -28,7 +29,7 @@ class Environment():
return value
else:
return value
raise RuntimeException(f"Variable '{name}' is not declared" + ("" if type is None else f" (expected type: {types[type]})"))
raise RuntimeException(None, f"Variable '{name}' is not declared" + ("" if type is None else f" (expected type: {types[type]})"))
def findVariableScope(self, name, type=None):
for scope in reversed(self.scopes):

View File

@@ -1,2 +1,9 @@
class ParseError(Exception):
pass
class SyntaxException(Exception):
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}"
class RuntimeException(Exception):
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}"

View File

@@ -2,9 +2,7 @@ from Tokenizer import tokenize, TokenType
from Parser import parse
from AST import *
from Note import Note
class RuntimeException(Exception):
pass
from Error import RuntimeException
def evaluateProgram(program, environment):
for node in program.children:
@@ -39,8 +37,8 @@ def objectString(obj):
if isinstance(obj, float):
return f"{int(obj*100)}%"
if obj is None:
raise RuntimeException(f"Trying to interpret void")
raise RuntimeException(f"Don't know how to interpret {str(obj)}")
raise RuntimeException(None, f"Trying to interpret void")
raise RuntimeException(None, f"Don't know how to interpret {str(obj)}")
def evaluateNote(note, environment):
return note.value
@@ -51,7 +49,7 @@ def evaluateFunctionCall(functionCall, environment):
for name, definition in environment.functions.items():
if name == function:
return definition(arguments, environment)
raise RuntimeException(f"Function '{function}' does not exist")
raise RuntimeException(functionCall.pos, f"Function '{function}' does not exist")
def evaluateComma(comma, environment):
@@ -109,7 +107,7 @@ def evaluateColon(colon, environment):
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("Invalid colon arguments")
raise RuntimeException(colon.pos, "Invalid colon arguments")
def evaluate(input, environment):
if isinstance(input, Program):

View File

@@ -1,6 +1,6 @@
from enum import Enum
from Error import ParseError
import math
from Error import SyntaxException
class NotePitch(Enum):
C = 0
@@ -52,7 +52,7 @@ class NotePitch(Enum):
}
return map[string.lower()]
except KeyError as e:
raise ParseError(f"Note '{string}' does not exist")
raise SyntaxException(None, f"Note '{string}' does not exist")
class Note:
def __init__(self, note, octave = 4, duration = 4):

View File

@@ -1,19 +1,22 @@
from Tokenizer import *
from Note import *
from AST import *
from Error import ParseError
from Error import SyntaxException
def expectedFound(expected, found):
raise ParseError(f"Expected: {expected}, found: {found}")
raise SyntaxException(None, f"Expected: {expected}, found: {found}")
def parseInteger(input, parent):
return IntegerLiteralNode(int(input.pop(0).value))
token = input.pop(0)
return IntegerLiteralNode(int(token.value), token.pos)
def parseString(input, parent):
return StringLiteralNode(input.pop(0).value[1:-1])
def parseString(input, parent):
token = input.pop(0)
return StringLiteralNode(token.value[1:-1], token.pos)
def parseNote(input, parent):
value = input.pop(0).value
token = input.pop(0)
value = token.value
consumedChars = 1
notePitch = value[consumedChars]
consumedChars += 1
@@ -33,21 +36,21 @@ def parseNote(input, parent):
consumedChars += 1
duration = int(durationString)
return NoteLiteralNode(Note(notePitch, octave, duration))
return NoteLiteralNode(Note(notePitch, octave, duration), token.pos)
def parseComma(input, parent):
input.pop(0)
return CommaNode()
token = input.pop(0)
return CommaNode(token.pos)
def parseList(input, parent):
input.pop(0)
token = input.pop(0)
node = ListNode()
node = ListNode(token.pos)
while input[0].type != TokenType.CLOSE_PAREN:
element = parseArrayElement(input, node)
if element is None:
raise ParseError(f"Line: {input[0].pos[0]+1}, col: {input[0].pos[1]+1}: Invalid element '{input[0].value}'")
raise SyntaxException(input[0].pos, "Invalid element '{input[0].value}'")
node.append(element)
if input[0].type != TokenType.CLOSE_PAREN:
@@ -57,9 +60,9 @@ def parseList(input, parent):
return node
def parseBlock(input, parent):
input.pop(0)
token = input.pop(0)
block = BlockNode()
block = BlockNode(token.pos)
while input[0].type != TokenType.CLOSE_BRACKET:
block.append(parseToken(input, block))
@@ -72,62 +75,63 @@ def parseBlock(input, parent):
def parseAsterisk(input, parent):
input.pop(0)
token = input.pop(0)
iterator = parent.pop(-1)
value = parseStatement(input, parent) #TODO: only statements! (?)
return AsteriskStatementNode(iterator, value)
return AsteriskStatementNode(iterator, value, token.pos)
def parseNoteOrColon(input, parent):
note = parseNote(input, parent)
if len(input) > 1 and input[0].type == TokenType.COLON:
input.pop(0)
token = input.pop(0)
b = parseNote(input, parent) #TODO: only expressions!
if b is None:
raise ParseError(f"Line {input[0].pos[0]+1}, col {input[0].pos[1]+1}: Invalid colon argument '{input[0].value}'")
return ColonNode(note, b)
raise SyntaxException(input[0].pos, f"Invalid colon argument '{input[0].value}'")
return ColonNode(note, b, token.pos)
return note
def parseIntegerOrColon(input, parent):
integer = parseInteger(input, parent)
if len(input) > 1 and input[0].type == TokenType.COLON:
input.pop(0)
token = input.pop(0)
b = parseInteger(input, parent) #TODO: only expressions!
if b is None:
raise ParseError(f"Line {input[0].pos[0]+1}, col {input[0].pos[1]+1}: Invalid colon argument '{input[0].value}'")
return ColonNode(integer, b)
raise SyntaxException(input[0].pos, f"Invalid colon argument '{input[0].value}'")
return ColonNode(integer, b, token.pos)
return integer
def parseFunctionCallOrAssignOrIdentifier(input, parent):
identifier = IdentifierNode(input.pop(0).value)
def parseFunctionCallOrAssignOrIdentifier(input, parent):
token = input.pop(0)
identifier = IdentifierNode(token.value, token.pos)
# Function call
if len(input) > 0 and input[0].type == TokenType.OPEN_PAREN:
arguments = parseList(input, parent)
return FunctionCallNode(identifier, arguments)
return FunctionCallNode(identifier, arguments, token.pos)
# Assign
if len(input) > 1 and input[0].type == TokenType.ASSIGN:
input.pop(0)
token = input.pop(0)
value = parseExpression(input, parent) #TODO: only expressions!
return AssignExpression(identifier, value)
return AssignExpression(identifier, value, token.pos)
return identifier
def parsePercent(input, parent):
input.pop(0)
token = input.pop(0)
value = parent.pop(-1)
return PercentNode(value)
return PercentNode(value, token.pos)
def parseMinus(input, parent):
input.pop(0)
token = input.pop(0)
value = parseInteger(input, parent)
return IntegerLiteralNode(-value.value)
return IntegerLiteralNode(-value.value, token.pos)
def parseExpression(input, parent):
type = input[0].type
@@ -145,7 +149,7 @@ def parseExpression(input, parent):
return parsePercent(input, parent)
if type == TokenType.OPEN_PAREN:
return parseList(input, parent)
raise ParseError(f"Line {input[0].pos[0]+1}, col {input[0].pos[1]+1}: Unexpected character '{input[0].value}'")
raise SyntaxException(input[0].pos, f"Unexpected character '{input[0].value}'")
def parseArrayElement(input, parent):
type = input[0].type
@@ -172,20 +176,3 @@ def parse(input):
while len(input) > 0:
root.append(parseToken(input, root))
return root
if __name__ == "__main__":
try:
with open(sys.argv[1], 'r') as source:
lines = [line.rstrip('\n') for line in source.readlines()]
tokens = [token for token in tokenize(lines) if token.type != TokenType.COMMENT]
ast = parse(tokens)
print(ast)
except TokenizerError as e:
print(str(e))
except ParseError as e:
print(str(e))

View File

@@ -2,6 +2,7 @@ from enum import Enum
import time
import re
import sys
from Error import SyntaxException
class TokenType(Enum):
OPEN_PAREN = 1
@@ -19,10 +20,6 @@ class TokenType(Enum):
COMMENT = 13
PERCENT = 14
MINUS = 15
class TokenizerError(Exception):
pass
class Token:
def __init__(self, type, value, pos):
@@ -188,7 +185,7 @@ def doTokenize(lines):
break
if not tokenized:
raise TokenizerError(f"Line {lineNumber+1}, col {current+1}: unknown symbol '{line[current]}'")
raise SyntaxException((lineNumber, current), f"Unknown symbol '{line[current]}'")
return [token for token in tokens if token.type is not None]

26
main.py
View File

@@ -2,16 +2,22 @@ from Tokenizer import tokenize
from Parser import parse
from Evaluator import evaluate
from Environment import createEnvironment
from Error import SyntaxException, RuntimeException
import sys
if __name__ == "__main__":
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)
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)