Create ast package
This commit is contained in:
0
smnp/ast/__init__.py
Normal file
0
smnp/ast/__init__.py
Normal 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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -0,0 +1,6 @@
|
||||
from smnp.ast.node.model import Node
|
||||
|
||||
|
||||
class Program(Node):
|
||||
def __init__(self):
|
||||
Node.__init__(self, None, (-1, -1))
|
||||
@@ -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]
|
||||
@@ -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
12
smnp/ast/parser.py
Normal 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"]
|
||||
0
smnp/ast/parsers/__init__.py
Normal file
0
smnp/ast/parsers/__init__.py
Normal 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
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
30
smnp/ast/tools.py
Normal 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
|
||||
Reference in New Issue
Block a user