Create ast package

This commit is contained in:
Bartłomiej Pluta
2019-07-03 11:27:51 +02:00
parent 2823fd1896
commit c8ff5ce38f
38 changed files with 622 additions and 11 deletions

0
smnp/ast/__init__.py Normal file
View File

View File

@@ -0,0 +1,10 @@
from smnp.ast.node.model import Node
class AccessNode(Node):
def __init__(self, element, property, parent, pos):
Node.__init__(self, parent, pos)
self.children.extend([element, property])
self.element = self.children[0]
self.property = self.children[1]

View File

@@ -0,0 +1,10 @@
from smnp.ast.node.model import Node
class AssignmentNode(Node):
def __init__(self, target, value, parent, pos):
Node.__init__(self, parent, pos)
self.children.extend([target, value])
self.target = self.children[0]
self.value = self.children[1]

View File

@@ -0,0 +1,10 @@
from smnp.ast.node.model import Node
class AsteriskNode(Node):
def __init__(self, iterator, statement, parent, pos):
Node.__init__(self, parent, pos)
self.children.extend([iterator, statement])
self.iterator = self.children[0]
self.statement = self.children[1]

View File

@@ -0,0 +1,17 @@
from smnp.ast.node.model import Node
class BlockNode(Node):
pass
class BlockItemNode(Node):
def __init__(self, statement, parent, pos):
Node.__init__(self, parent, pos)
self.children.append(statement)
self.statement = self.children[0]
class CloseBlockNode(Node):
pass

View File

@@ -0,0 +1,10 @@
from smnp.ast.node.model import Node
class ColonNode(Node):
def __init__(self, a, b, parent, pos):
Node.__init__(self, parent, pos)
self.children.extend([a, b])
self.a = self.children[0]
self.b = self.children[1]

View File

@@ -0,0 +1,20 @@
from smnp.ast.node.model import Node
class FunctionCallNode(Node):
def __init__(self, identifier, arguments, parent, pos):
Node.__init__(self, parent, pos)
self.children.extend([identifier, arguments])
self.identifier = self.children[0]
self.arguments = self.children[1]
class FunctionDefinitionNode(Node):
def __init__(self, name, parameters, body, parent, pos):
Node.__init__(self, parent, pos)
self.children.extend([name, parameters, body])
self.name = self.children[0]
self.parameters = self.children[1]
self.body = self.children[2]

View File

@@ -0,0 +1,9 @@
from smnp.ast.node.model import Node
class IdentifierNode(Node):
def __init__(self, identifier, parent, pos):
Node.__init__(self, parent, pos)
self.children.append(identifier)
self.identifier = self.children[0]

View File

@@ -0,0 +1,9 @@
from smnp.ast.node.model import Node
class IntegerLiteralNode(Node):
def __init__(self, value, parent, pos):
Node.__init__(self, parent, pos)
self.children.append(value)
self.value = self.children[0]

View File

@@ -0,0 +1,17 @@
from smnp.ast.node.model import Node
class ListNode(Node):
pass
class ListItemNode(Node):
def __init__(self, value, parent, pos):
Node.__init__(self, parent, pos)
self.children.append(value)
self.value = self.children[0]
class CloseListNode(Node):
pass

View File

@@ -24,6 +24,9 @@ class Node:
def pop(self, index):
return self.children.pop(index)
def print(self):
print(self._print(0))
def _print(self, level):
string = f"{pad(level)}{self.__class__.__name__}({self.parent.__class__.__name__}):\n"
for child in self.children:
@@ -38,4 +41,4 @@ class Node:
def pad(level):
return (" " * level)
return " " * level

View File

@@ -0,0 +1,9 @@
from smnp.ast.node.model import Node
class NoteLiteralNode(Node):
def __init__(self, value, parent, pos):
Node.__init__(self, parent, pos)
self.children.append(value)
self.value = self.children[0]

View File

@@ -0,0 +1,9 @@
from smnp.ast.node.model import Node
class PercentNode(Node):
def __init__(self, value, parent, pos):
Node.__init__(self, parent, pos)
self.children.append(value)
self.value = self.children[0]

View File

@@ -0,0 +1,6 @@
from smnp.ast.node.model import Node
class Program(Node):
def __init__(self):
Node.__init__(self, None, (-1, -1))

View File

@@ -0,0 +1,9 @@
from smnp.ast.node.model import Node
class ReturnNode(Node):
def __init__(self, value, parent, pos):
Node.__init__(self, parent, pos)
self.children.append(value)
self.value = self.children[0]

View File

@@ -0,0 +1,9 @@
from smnp.ast.node.model import Node
class StringLiteralNode(Node):
def __init__(self, value, parent, pos):
Node.__init__(self, parent, pos)
self.children.append(value)
self.value = self.children[0]

12
smnp/ast/parser.py Normal file
View File

@@ -0,0 +1,12 @@
from smnp.ast.node.program import Program
from smnp.ast.parsers.token import parseToken
def parse(input):
root = Program()
while input.hasCurrent():
root.append(parseToken(input, root))
return root
__all__ = ["parse"]

View File

View File

@@ -0,0 +1,24 @@
from smnp.ast.node.access import AccessNode
from smnp.ast.parsers.expression import parseExpression
from smnp.token.type import TokenType
# access -> expr '.' expr
# TODO: dodać dziedziczenie wszystkich expressions po jednym typie ExpressionNode
# i potem sprawdzać przy wszystkich parent.pop(-1) czy pobrany z parenta element
# jest rzeczywiście wyrażeniem, bo teraz możliwe jest np. {}.fun()
def parseAccess(input, parent):
if input.current().type == TokenType.DOT:
token = input.current()
input.ahead()
element = parent.pop(-1)
property = parseExpression(input, parent)
node = AccessNode(element, property, parent, token.pos)
element.parent = node
property.parent = node
return node
return None

View File

@@ -0,0 +1,2 @@

View File

@@ -0,0 +1,18 @@
from smnp.ast.node.asterisk import AsteriskNode
from smnp.ast.parsers.statement import parseStatement
from smnp.token.type import TokenType
# asterisk -> expr '*' stmt
def parseAsterisk(expr, input, parent):
if input.hasMore() and input.current().type == TokenType.ASTERISK:
token = input.current()
input.ahead()
stmt = parseStatement(input, parent)
asterisk = AsteriskNode(expr, stmt, parent, token.pos)
expr.parent = asterisk
stmt.parent = asterisk
return asterisk
return None

View File

@@ -0,0 +1,46 @@
from smnp.ast.node.block import BlockNode, CloseBlockNode, BlockItemNode
from smnp.token.type import TokenType
def parseBlock(input, parent):
# '{'
if input.current().type == TokenType.OPEN_BRACKET:
token = input.current()
input.ahead()
node = BlockNode(parent, token.pos)
# '}'
if input.hasCurrent() and input.current().type == TokenType.CLOSE_BRACKET:
input.ahead()
return node
# blockItem
if input.hasCurrent():
item = parseBlockItem(input, node)
node.append(item)
return node
return None
# blockItem -> stmt | '}'
def parseBlockItem(input, parent):
# '}'
if input.hasCurrent() and input.current().type == TokenType.CLOSE_BRACKET:
close = CloseBlockNode(parent, input.current().pos)
input.ahead()
return close
if input.hasCurrent():
stmt = parseStatement(input, parent)
if stmt is not None:
item = BlockItemNode(stmt, parent, stmt.pos)
stmt.parent = item
nextBlockItem = parseBlockItem(input, item)
if nextBlockItem != None:
item.append(nextBlockItem)
return item
return None

View File

@@ -0,0 +1,19 @@
from smnp.ast.node.colon import ColonNode
from smnp.error.syntax import SyntaxException
from smnp.token.type import TokenType
# colon -> expr ':' expr
def parseColon(expr1, input, parent):
if input.hasCurrent() and input.current().type == TokenType.COLON:
token = input.current()
input.ahead()
expr2 = parseExpression(input, parent)
if expr2 is None:
raise SyntaxException(input.current().pos, f"Expected expression '{input.current().value}'")
colon = ColonNode(expr1, expr2, parent, token.pos)
expr1.parent = colon
expr2.parent = colon
return colon
return None

View File

@@ -0,0 +1,28 @@
def parseExpression(input, parent):
from smnp.ast.tools import combineParsers
from smnp.ast.parsers.access import parseAccess
from smnp.ast.parsers.colon import parseColon
from smnp.ast.parsers.identifier import parseIdentifierOrFunctionCallOrAssignment
from smnp.ast.parsers.integer import parseIntegerAndPercent
from smnp.ast.parsers.list import parseList
from smnp.ast.parsers.minus import parseMinus
from smnp.ast.parsers.note import parseNote
from smnp.ast.parsers.string import parseString
parsers = [
parseIntegerAndPercent,
parseMinus,
parseString,
parseNote,
parseList,
parseIdentifierOrFunctionCallOrAssignment,
parseAccess,
]
expr = combineParsers(parsers)(input, parent)
colon = parseColon(expr, input, parent)
if colon is not None:
return colon
return expr

View File

@@ -0,0 +1,29 @@
from smnp.ast.node.function import FunctionDefinitionNode
from smnp.ast.parsers.block import parseBlock
from smnp.ast.parsers.identifier import parseIdentifier
from smnp.ast.parsers.list import parseList
from smnp.ast.tools import assertToken
from smnp.token.type import TokenType
def parseFunctionDefinition(input, parent):
if input.current().type == TokenType.FUNCTION:
token = input.current()
input.ahead()
assertToken(TokenType.IDENTIFIER, input)
identifier = parseIdentifier(input, parent)
assertToken(TokenType.OPEN_PAREN, input)
args = parseList(input, parent)
assertToken(TokenType.OPEN_BRACKET, input)
body = parseBlock(input, parent)
function = FunctionDefinitionNode(identifier, args, body, parent, token.pos)
identifier.parent = function
args.parent = function
body.parent = function
return function
return None

View File

@@ -0,0 +1,47 @@
from smnp.ast.node.assignment import AssignmentNode
from smnp.ast.node.function import FunctionCallNode
from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.parsers.expression import parseExpression
from smnp.ast.parsers.list import parseList
from smnp.token.type import TokenType
# id -> IDENTIFIER
def parseIdentifier(input, parent):
if input.current().type == TokenType.IDENTIFIER:
identifier = IdentifierNode(input.current().value, parent, input.current().pos)
input.ahead()
return identifier
return None
# identifier -> IDENTIFIER
# functionCall -> identifier list
# assignment -> identifier '=' expr
def parseIdentifierOrFunctionCallOrAssignment(input, parent):
identifier = parseIdentifier(input, parent)
# assignment -> identifier '=' expr
if identifier is not None and input.hasCurrent():
if input.current().type == TokenType.ASSIGN:
token = input.current()
input.ahead()
expr = parseExpression(input, parent)
assignment = AssignmentNode(identifier, expr, parent, token.pos)
identifier.parent = assignment
expr.parent = assignment
return assignment
# functionCall -> identifier list
args = parseList(input, parent)
if args is not None:
functionCall = FunctionCallNode(identifier, args, parent, identifier.pos)
args.parent = functionCall
identifier.parent = functionCall
return functionCall
return identifier

View File

@@ -0,0 +1,26 @@
from smnp.ast.node.integer import IntegerLiteralNode
from smnp.ast.node.percent import PercentNode
from smnp.token.type import TokenType
# int -> INTEGER
def parseInteger(input, parent):
if input.current().type == TokenType.INTEGER:
integer = IntegerLiteralNode(int(input.current().value), parent, input.current().pos)
input.ahead()
return integer
return None
# percent -> int '%'
# int -> int
def parseIntegerAndPercent(input, parent):
integer = parseInteger(input, parent)
if integer is not None and input.hasCurrent() and input.current().type == TokenType.PERCENT:
percent = PercentNode(integer, parent, input.current().pos)
integer.parent = percent
input.ahead()
return percent
return integer

View File

@@ -0,0 +1,54 @@
from smnp.ast.node.list import ListNode, ListItemNode, CloseListNode
from smnp.ast.parsers.expression import parseExpression
from smnp.ast.tools import rollup, assertToken
from smnp.token.type import TokenType
# list -> CLOSE_PAREN | expr listTail
def parseList(input, parent):
if input.current().type == TokenType.OPEN_PAREN:
node = ListNode(parent, input.current().pos)
input.ahead()
# list -> CLOSE_PAREN (end of list)
if input.hasCurrent() and input.current().type == TokenType.CLOSE_PAREN:
close = CloseListNode(node, input.current().pos)
node.append(close)
input.ahead()
return node
# list -> expr listTail
if input.hasCurrent():
token = input.current()
expr = parseExpression(input, node)
item = ListItemNode(expr, node, token.pos)
expr.parent = item
node.append(item)
listTail = parseListTail(input, item)
item.append(listTail)
return node
return None
# listTail -> COMMA expr listTail | CLOSE_PAREN
def parseListTail(input, parent):
# listTail -> CLOSE_PAREN
if input.hasCurrent() and input.current().type == TokenType.CLOSE_PAREN:
close = CloseListNode(parent, input.current().pos)
input.ahead()
return close
# listTail -> COMMA expr listTail
if input.hasCurrent() and input.hasMore():
assertToken(TokenType.COMMA, input)
input.ahead()
expr = rollup(parseExpression)(input, parent)
if expr is not None:
item = ListItemNode(expr, parent, expr.pos)
expr.parent = item
listTail = parseListTail(input, item)
item.append(listTail)
listTail.parent = item
return item
return None

View File

@@ -0,0 +1,14 @@
from smnp.ast.node.integer import IntegerLiteralNode
from smnp.ast.parsers.integer import parseInteger
from smnp.token.type import TokenType
# minus -> '-' int
def parseMinus(input, parent):
if input.current().type == TokenType.MINUS:
token = input.current()
input.ahead()
expr = parseInteger(input, parent)
return IntegerLiteralNode(-expr.value, parent, token.pos)

View File

@@ -0,0 +1,38 @@
import re
from smnp.ast.node.note import NoteLiteralNode
from smnp.note.model import Note
from smnp.token.type import TokenType
# note -> NOTE
def parseNote(input, parent):
if input.current().type == TokenType.NOTE:
token = input.current()
value = token.value
consumedChars = 1
notePitch = value[consumedChars]
consumedChars += 1
octave = 4
duration = 4
dot = False
if consumedChars < len(value) and value[consumedChars] in ('b', '#'):
notePitch += value[consumedChars]
consumedChars += 1
if consumedChars < len(value) and re.match(r'\d', value[consumedChars]):
octave = int(value[consumedChars])
consumedChars += 1
if consumedChars < len(value) and value[consumedChars] == '.':
consumedChars += 1
durationString = ''
while consumedChars < len(value) and re.match(r'\d', value[consumedChars]):
durationString += value[consumedChars]
consumedChars += 1
duration = int(durationString)
if consumedChars < len(value) and value[consumedChars] == 'd':
dot = True
consumedChars += 1
input.ahead()
return NoteLiteralNode(Note(notePitch, octave, duration, dot), parent, token.pos)
return None

View File

@@ -0,0 +1,17 @@
from smnp.ast.node.ret import ReturnNode
from smnp.ast.parsers.expression import parseExpression
from smnp.token.type import TokenType
def parseReturn(input, parent):
if input.current().type == TokenType.RETURN:
token = input.current()
input.ahead()
expr = parseExpression(input, parent)
node = ReturnNode(expr, parent, token.pos)
expr.parent = node
return node
return None

View File

@@ -0,0 +1,22 @@
def parseStatement(input, parent):
from smnp.ast.tools import combineParsers
from smnp.ast.parsers.asterisk import parseAsterisk
from smnp.ast.parsers.block import parseBlock
from smnp.ast.parsers.expression import parseExpression
from smnp.ast.parsers.function import parseFunctionDefinition
from smnp.ast.parsers.ret import parseReturn
parsers = [
parseBlock,
parseFunctionDefinition,
parseReturn,
parseExpression,
]
stmt = combineParsers(parsers)(input, parent)
asterisk = parseAsterisk(stmt, input, parent)
if asterisk is not None:
return asterisk
return stmt

View File

@@ -0,0 +1,12 @@
from smnp.ast.node.string import StringLiteralNode
from smnp.token.type import TokenType
# string -> STRING
def parseString(input, parent):
if input.current().type == TokenType.STRING:
string = StringLiteralNode(input.current().value[1:len(input.current().value) - 1], parent, input.current().pos)
input.ahead()
return string
return None

View File

@@ -0,0 +1,12 @@
from smnp.ast.parsers.statement import parseStatement
from smnp.ast.tools import combineParsers
from smnp.error.syntax import SyntaxException
def parseToken(input, parent):
value = combineParsers([ parseStatement ])(input, parent)
if value is None:
raise SyntaxException(None, "Unknown statement") # TODO
return value

30
smnp/ast/tools.py Normal file
View File

@@ -0,0 +1,30 @@
from smnp.ast.node.model import Node
from smnp.error.syntax import SyntaxException
def rollup(parser):
def _rollup(input, parent):
node = Node(None, (-1, -1))
elem = parser(input, node)
while elem is not None:
node.append(elem)
elem = parser(input, node)
return node.children[0] if len(node.children) > 0 else None
return _rollup
def assertToken(expected, input):
if not input.hasCurrent():
raise SyntaxException(None, f"Expected '{expected}'")
if expected != input.current().type:
raise SyntaxException(input.current().pos, f"Expected '{expected}', found '{input.current().value}'")
def combineParsers(parsers):
def combinedParser(input, parent):
for parser in parsers:
value = parser(input, parent)
if value is not None:
return value
return None
return combinedParser

View File

@@ -2,23 +2,23 @@ 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 Parser import parse
#from Evaluator import evaluate
#from Environment import createEnvironment
#from Error import SyntaxException, RuntimeException
def main():
try:
try:
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)
print(tokens)
#ast = parse(tokens)
tokens = tokenize(lines)
ast = parse(tokens)
#evaluate(ast, env)
except SyntaxException as e:
print(e.msg)

View File

@@ -8,6 +8,7 @@ class Token:
def __repr__(self):
return self.__str__()
class TokenList:
def __init__(self, tokens = []):
self.tokens = tokens

View File

@@ -65,6 +65,7 @@ def tokenize(lines):
return TokenList(filterTokens(filters, tokens))
def combinedTokenizer(line, current, lineNumber):
for tokenizer in tokenizers:
consumedChars, token = tokenizer(line, current, lineNumber)
@@ -72,10 +73,12 @@ def combinedTokenizer(line, current, lineNumber):
return (consumedChars, token)
return (0, None)
def filterTokens(filters, tokens):
if not filters:
return tokens
return filterTokens(filters[1:], (token for token in tokens if filters[0](token)))
return list(filterTokens(filters[1:], (token for token in tokens if filters[0](token))))
__all__ = ["tokenize"]