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

45
AST.py
View File

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

View File

@@ -1,9 +1,10 @@
import sys import sys
from Evaluator import RuntimeException, objectString from Evaluator import objectString
from Note import * from Note import *
import random import random
import Synth import Synth
import time import time
from Error import RuntimeException
types = { types = {
int: 'integer', int: 'integer',
@@ -28,7 +29,7 @@ class Environment():
return value return value
else: else:
return value 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): def findVariableScope(self, name, type=None):
for scope in reversed(self.scopes): for scope in reversed(self.scopes):

View File

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

View File

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

View File

@@ -1,19 +1,22 @@
from Tokenizer import * from Tokenizer import *
from Note import * from Note import *
from AST import * from AST import *
from Error import ParseError from Error import SyntaxException
def expectedFound(expected, found): def expectedFound(expected, found):
raise ParseError(f"Expected: {expected}, found: {found}") raise SyntaxException(None, f"Expected: {expected}, found: {found}")
def parseInteger(input, parent): 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): def parseString(input, parent):
return StringLiteralNode(input.pop(0).value[1:-1]) token = input.pop(0)
return StringLiteralNode(token.value[1:-1], token.pos)
def parseNote(input, parent): def parseNote(input, parent):
value = input.pop(0).value token = input.pop(0)
value = token.value
consumedChars = 1 consumedChars = 1
notePitch = value[consumedChars] notePitch = value[consumedChars]
consumedChars += 1 consumedChars += 1
@@ -33,21 +36,21 @@ def parseNote(input, parent):
consumedChars += 1 consumedChars += 1
duration = int(durationString) duration = int(durationString)
return NoteLiteralNode(Note(notePitch, octave, duration)) return NoteLiteralNode(Note(notePitch, octave, duration), token.pos)
def parseComma(input, parent): def parseComma(input, parent):
input.pop(0) token = input.pop(0)
return CommaNode() return CommaNode(token.pos)
def parseList(input, parent): def parseList(input, parent):
input.pop(0) token = input.pop(0)
node = ListNode() node = ListNode(token.pos)
while input[0].type != TokenType.CLOSE_PAREN: while input[0].type != TokenType.CLOSE_PAREN:
element = parseArrayElement(input, node) element = parseArrayElement(input, node)
if element is None: 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) node.append(element)
if input[0].type != TokenType.CLOSE_PAREN: if input[0].type != TokenType.CLOSE_PAREN:
@@ -57,9 +60,9 @@ def parseList(input, parent):
return node return node
def parseBlock(input, parent): def parseBlock(input, parent):
input.pop(0) token = input.pop(0)
block = BlockNode() block = BlockNode(token.pos)
while input[0].type != TokenType.CLOSE_BRACKET: while input[0].type != TokenType.CLOSE_BRACKET:
block.append(parseToken(input, block)) block.append(parseToken(input, block))
@@ -72,62 +75,63 @@ def parseBlock(input, parent):
def parseAsterisk(input, parent): def parseAsterisk(input, parent):
input.pop(0) token = input.pop(0)
iterator = parent.pop(-1) iterator = parent.pop(-1)
value = parseStatement(input, parent) #TODO: only statements! (?) value = parseStatement(input, parent) #TODO: only statements! (?)
return AsteriskStatementNode(iterator, value) return AsteriskStatementNode(iterator, value, token.pos)
def parseNoteOrColon(input, parent): def parseNoteOrColon(input, parent):
note = parseNote(input, parent) note = parseNote(input, parent)
if len(input) > 1 and input[0].type == TokenType.COLON: if len(input) > 1 and input[0].type == TokenType.COLON:
input.pop(0) token = input.pop(0)
b = parseNote(input, parent) #TODO: only expressions! b = parseNote(input, parent) #TODO: only expressions!
if b is None: 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}'") raise SyntaxException(input[0].pos, f"Invalid colon argument '{input[0].value}'")
return ColonNode(note, b) return ColonNode(note, b, token.pos)
return note return note
def parseIntegerOrColon(input, parent): def parseIntegerOrColon(input, parent):
integer = parseInteger(input, parent) integer = parseInteger(input, parent)
if len(input) > 1 and input[0].type == TokenType.COLON: if len(input) > 1 and input[0].type == TokenType.COLON:
input.pop(0) token = input.pop(0)
b = parseInteger(input, parent) #TODO: only expressions! b = parseInteger(input, parent) #TODO: only expressions!
if b is None: 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}'") raise SyntaxException(input[0].pos, f"Invalid colon argument '{input[0].value}'")
return ColonNode(integer, b) return ColonNode(integer, b, token.pos)
return integer return integer
def parseFunctionCallOrAssignOrIdentifier(input, parent): def parseFunctionCallOrAssignOrIdentifier(input, parent):
identifier = IdentifierNode(input.pop(0).value) token = input.pop(0)
identifier = IdentifierNode(token.value, token.pos)
# Function call # Function call
if len(input) > 0 and input[0].type == TokenType.OPEN_PAREN: if len(input) > 0 and input[0].type == TokenType.OPEN_PAREN:
arguments = parseList(input, parent) arguments = parseList(input, parent)
return FunctionCallNode(identifier, arguments) return FunctionCallNode(identifier, arguments, token.pos)
# Assign # Assign
if len(input) > 1 and input[0].type == TokenType.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! value = parseExpression(input, parent) #TODO: only expressions!
return AssignExpression(identifier, value) return AssignExpression(identifier, value, token.pos)
return identifier return identifier
def parsePercent(input, parent): def parsePercent(input, parent):
input.pop(0) token = input.pop(0)
value = parent.pop(-1) value = parent.pop(-1)
return PercentNode(value) return PercentNode(value, token.pos)
def parseMinus(input, parent): def parseMinus(input, parent):
input.pop(0) token = input.pop(0)
value = parseInteger(input, parent) value = parseInteger(input, parent)
return IntegerLiteralNode(-value.value) return IntegerLiteralNode(-value.value, token.pos)
def parseExpression(input, parent): def parseExpression(input, parent):
type = input[0].type type = input[0].type
@@ -145,7 +149,7 @@ def parseExpression(input, parent):
return parsePercent(input, parent) return parsePercent(input, parent)
if type == TokenType.OPEN_PAREN: if type == TokenType.OPEN_PAREN:
return parseList(input, parent) 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): def parseArrayElement(input, parent):
type = input[0].type type = input[0].type
@@ -172,20 +176,3 @@ def parse(input):
while len(input) > 0: while len(input) > 0:
root.append(parseToken(input, root)) root.append(parseToken(input, root))
return 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 time
import re import re
import sys import sys
from Error import SyntaxException
class TokenType(Enum): class TokenType(Enum):
OPEN_PAREN = 1 OPEN_PAREN = 1
@@ -20,10 +21,6 @@ class TokenType(Enum):
PERCENT = 14 PERCENT = 14
MINUS = 15 MINUS = 15
class TokenizerError(Exception):
pass
class Token: class Token:
def __init__(self, type, value, pos): def __init__(self, type, value, pos):
self.type = type self.type = type
@@ -188,7 +185,7 @@ def doTokenize(lines):
break break
if not tokenized: 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] return [token for token in tokens if token.type is not None]

View File

@@ -2,9 +2,11 @@ from Tokenizer import tokenize
from Parser import parse from Parser import parse
from Evaluator import evaluate from Evaluator import evaluate
from Environment import createEnvironment from Environment import createEnvironment
from Error import SyntaxException, RuntimeException
import sys import sys
if __name__ == "__main__": if __name__ == "__main__":
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()]
@@ -15,3 +17,7 @@ if __name__ == "__main__":
ast = parse(tokens) ast = parse(tokens)
evaluate(ast, env) evaluate(ast, env)
except SyntaxException as e:
print(e.msg)
except RuntimeException as e:
print(e.msg)