1
0
mirror of https://github.com/MarkParker5/STARK.git synced 2025-07-12 22:50:22 +02:00

async pattern/object parse

This commit is contained in:
Mark Parker
2023-09-16 16:27:28 +02:00
parent ab233ab878
commit 1249e24342
15 changed files with 160 additions and 146 deletions

View File

@ -57,7 +57,7 @@ class CommandsContext:
def root_context(self): def root_context(self):
return CommandsContextLayer(self.commands_manager.commands, {}) return CommandsContextLayer(self.commands_manager.commands, {})
def process_string(self, string: str): async def process_string(self, string: str):
if not self._context_queue: if not self._context_queue:
self._context_queue.append(self.root_context) self._context_queue.append(self.root_context)
@ -66,7 +66,7 @@ class CommandsContext:
while self._context_queue: while self._context_queue:
current_context = self._context_queue[0] current_context = self._context_queue[0]
search_results = self.commands_manager.search(string = string, commands = current_context.commands) search_results = await self.commands_manager.search(string = string, commands = current_context.commands)
if search_results: if search_results:
break break

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import inspect import inspect
from asyncer import create_task_group
from .patterns import Pattern, MatchResult from .patterns import Pattern, MatchResult
from .types import Object from .types import Object
@ -23,18 +24,31 @@ class CommandsManager:
self.name = name or 'CommandsManager' self.name = name or 'CommandsManager'
self.commands = [] self.commands = []
def search(self, string: str, commands: list[Command] | None = None) -> list[SearchResult]: async def search(self, string: str, commands: list[Command] | None = None) -> list[SearchResult]:
if not commands: if not commands:
commands = self.commands commands = self.commands
objects_cache: dict[str, Object] = {} objects_cache: dict[str, Object] = {}
results: list[SearchResult] = [] results: list[SearchResult] = []
# origin = String(string)
i = 0 i = 0
for command in commands:
for match in command.pattern.match(string, objects_cache): # async with create_task_group() as group:
# for command in commands:
# async def match_command():
# nonlocal i
# for match in await command.pattern.match(string, objects_cache):
# results.append(SearchResult(
# command = command,
# match_result = match,
# index = i
# ))
# i += 1
# group.soonify(match_command)()
for command in commands: # TODO: concurrent
for match in await command.pattern.match(string, objects_cache):
results.append(SearchResult( results.append(SearchResult(
command = command, command = command,
match_result = match, match_result = match,
@ -51,9 +65,9 @@ class CommandsManager:
if prev.match_result.start == current.match_result.start or prev.match_result.end > current.match_result.start: if prev.match_result.start == current.match_result.start or prev.match_result.end > current.match_result.start:
# constrain prev end to current start # constrain prev end to current start
prev_cut = prev.command.pattern.match(string[prev.match_result.start:current.match_result.start], objects_cache) prev_cut = await prev.command.pattern.match(string[prev.match_result.start:current.match_result.start], objects_cache)
# constrain current start to prev end # constrain current start to prev end
current_cut = current.command.pattern.match(string[prev.match_result.end:current.match_result.end], objects_cache) current_cut = await current.command.pattern.match(string[prev.match_result.end:current.match_result.end], objects_cache)
# less index = more priority to save full match # less index = more priority to save full match
priority1, priority2 = (prev, current) if prev.index < current.index else (current, prev) priority1, priority2 = (prev, current) if prev.index < current.index else (current, prev)

View File

@ -31,7 +31,7 @@ class Pattern:
self.parameters = dict(self._get_parameters()) self.parameters = dict(self._get_parameters())
self.compiled = self._compile() self.compiled = self._compile()
def match(self, string: str, objects_cache: dict[str, Object] | None = None) -> list[MatchResult]: async def match(self, string: str, objects_cache: dict[str, Object] | None = None) -> list[MatchResult]:
if objects_cache is None: if objects_cache is None:
objects_cache = {} objects_cache = {}
@ -64,7 +64,7 @@ class Pattern:
parameter_str = parsed_substr parameter_str = parsed_substr
break break
else: else:
parse_result = object_type.parse(from_string = parameter_str, parameters = match_str_groups) parse_result = await object_type.parse(from_string = parameter_str, parameters = match_str_groups) # TODO: concurrent parsing
objects_cache[parse_result.substring] = parse_result.obj objects_cache[parse_result.substring] = parse_result.obj
parameters[name] = parse_result.obj parameters[name] = parse_result.obj
parameter_str = parse_result.substring parameter_str = parse_result.substring

View File

@ -25,7 +25,7 @@ class Object(ABC):
'''Just init with wrapped value.''' '''Just init with wrapped value.'''
self.value = value self.value = value
def did_parse(self, from_string: str) -> str: async def did_parse(self, from_string: str) -> str:
''' '''
This method is called after parsing from string and setting parameters found in pattern. This method is called after parsing from string and setting parameters found in pattern.
You will very rarely, if ever, need to call this method directly. You will very rarely, if ever, need to call this method directly.
@ -42,7 +42,7 @@ class Object(ABC):
return from_string return from_string
@classmethod @classmethod
def parse(cls, from_string: str, parameters: dict[str, str] | None = None) -> ParseResult: async def parse(cls, from_string: str, parameters: dict[str, str] | None = None) -> ParseResult:
''' '''
For internal use only. For internal use only.
You will very rarely, if ever, need to override or even call this method. You will very rarely, if ever, need to override or even call this method.
@ -59,9 +59,9 @@ class Object(ABC):
if not parameters.get(name): if not parameters.get(name):
continue continue
value = parameters.pop(name) value = parameters.pop(name)
setattr(obj, name, object_type.parse(from_string = value, parameters = parameters).obj) setattr(obj, name, (await object_type.parse(from_string = value, parameters = parameters)).obj)
substring = obj.did_parse(from_string) substring = await obj.did_parse(from_string)
return ParseResult(obj, substring) return ParseResult(obj, substring)

View File

@ -49,7 +49,7 @@ class VoiceAssistant(SpeechRecognizerDelegate, CommandsContextDelegate):
print(f'\nYou: {result}') print(f'\nYou: {result}')
# check explicit interaction if needed # check explicit interaction if needed
if pattern_str := self.mode.explicit_interaction_pattern: if pattern_str := self.mode.explicit_interaction_pattern:
if not Pattern(pattern_str).match(result): if not await Pattern(pattern_str).match(result):
return return
# reset context if timeout reached # reset context if timeout reached
@ -62,8 +62,7 @@ class VoiceAssistant(SpeechRecognizerDelegate, CommandsContextDelegate):
if self.mode.mode_on_interaction: if self.mode.mode_on_interaction:
self.mode = self.mode.mode_on_interaction() self.mode = self.mode.mode_on_interaction()
# main part: start command; response will come by delegate await self.commands_context.process_string(result)
self.commands_context.process_string(result) # TODO: async
async def speech_recognizer_did_receive_partial_result(self, result: str): async def speech_recognizer_did_receive_partial_result(self, result: str):
pass # print(f'\rYou: \x1B[3m{result}\x1B[0m', end = '') pass # print(f'\rYou: \x1B[3m{result}\x1B[0m', end = '')

View File

@ -7,7 +7,7 @@ async def test_basic_search(commands_context_flow_filled, autojump_clock):
assert len(context_delegate.responses) == 0 assert len(context_delegate.responses) == 0
assert len(context._context_queue) == 1 assert len(context._context_queue) == 1
context.process_string('lorem ipsum dolor') await context.process_string('lorem ipsum dolor')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Lorem!' assert context_delegate.responses[0].text == 'Lorem!'
@ -16,14 +16,14 @@ async def test_basic_search(commands_context_flow_filled, autojump_clock):
async def test_second_context_layer(commands_context_flow_filled, autojump_clock): async def test_second_context_layer(commands_context_flow_filled, autojump_clock):
async with commands_context_flow_filled() as (context, context_delegate): async with commands_context_flow_filled() as (context, context_delegate):
context.process_string('hello world') await context.process_string('hello world')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Hello, world!' assert context_delegate.responses[0].text == 'Hello, world!'
assert len(context._context_queue) == 2 assert len(context._context_queue) == 2
context_delegate.responses.clear() context_delegate.responses.clear()
context.process_string('hello') await context.process_string('hello')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Hi, world!' assert context_delegate.responses[0].text == 'Hi, world!'
@ -33,13 +33,13 @@ async def test_second_context_layer(commands_context_flow_filled, autojump_clock
async def test_context_pop_on_not_found(commands_context_flow_filled, autojump_clock): async def test_context_pop_on_not_found(commands_context_flow_filled, autojump_clock):
async with commands_context_flow_filled() as (context, context_delegate): async with commands_context_flow_filled() as (context, context_delegate):
context.process_string('hello world') await context.process_string('hello world')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context._context_queue) == 2 assert len(context._context_queue) == 2
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
context_delegate.responses.clear() context_delegate.responses.clear()
context.process_string('lorem ipsum dolor') await context.process_string('lorem ipsum dolor')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context._context_queue) == 1 assert len(context._context_queue) == 1
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
@ -47,35 +47,35 @@ async def test_context_pop_on_not_found(commands_context_flow_filled, autojump_c
async def test_context_pop_context_response_action(commands_context_flow_filled, autojump_clock): async def test_context_pop_context_response_action(commands_context_flow_filled, autojump_clock):
async with commands_context_flow_filled() as (context, context_delegate): async with commands_context_flow_filled() as (context, context_delegate):
context.process_string('hello world') await context.process_string('hello world')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Hello, world!' assert context_delegate.responses[0].text == 'Hello, world!'
assert len(context._context_queue) == 2 assert len(context._context_queue) == 2
context_delegate.responses.clear() context_delegate.responses.clear()
context.process_string('bye') await context.process_string('bye')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Bye, world!' assert context_delegate.responses[0].text == 'Bye, world!'
assert len(context._context_queue) == 1 assert len(context._context_queue) == 1
context_delegate.responses.clear() context_delegate.responses.clear()
context.process_string('hello') await context.process_string('hello')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 0 assert len(context_delegate.responses) == 0
async def test_repeat_last_answer_response_action(commands_context_flow_filled, autojump_clock): async def test_repeat_last_answer_response_action(commands_context_flow_filled, autojump_clock):
async with commands_context_flow_filled() as (context, context_delegate): async with commands_context_flow_filled() as (context, context_delegate):
context.process_string('hello world') await context.process_string('hello world')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Hello, world!' assert context_delegate.responses[0].text == 'Hello, world!'
context_delegate.responses.clear() context_delegate.responses.clear()
assert len(context_delegate.responses) == 0 assert len(context_delegate.responses) == 0
context.process_string('repeat') await context.process_string('repeat')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Hello, world!' assert context_delegate.responses[0].text == 'Hello, world!'

View File

@ -11,7 +11,7 @@ async def test_command_return_respond(commands_context_flow, autojump_clock):
async def foo() -> Response: async def foo() -> Response:
return Response(text = 'foo!') return Response(text = 'foo!')
context.process_string('foo') await context.process_string('foo')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
@ -24,7 +24,7 @@ async def test_sync_command_call_sync_respond(commands_context_flow, autojump_cl
def foo(handler: ResponseHandler): def foo(handler: ResponseHandler):
handler.respond(Response(text = 'foo!')) handler.respond(Response(text = 'foo!'))
context.process_string('foo') await context.process_string('foo')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
@ -37,7 +37,7 @@ async def test_async_command_call_sync_respond(commands_context_flow, autojump_c
async def foo(handler: AsyncResponseHandler): async def foo(handler: AsyncResponseHandler):
await handler.respond(Response(text = 'foo!')) await handler.respond(Response(text = 'foo!'))
context.process_string('foo') await context.process_string('foo')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 1 assert len(context_delegate.responses) == 1
@ -56,7 +56,7 @@ async def test_sync_command_call_async_respond(commands_context_flow, autojump_c
assert issubclass(warnings_list[0].category, RuntimeWarning) assert issubclass(warnings_list[0].category, RuntimeWarning)
assert 'was never awaited' in str(warnings_list[0].message) assert 'was never awaited' in str(warnings_list[0].message)
context.process_string('foo') await context.process_string('foo')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 0 assert len(context_delegate.responses) == 0
@ -70,7 +70,7 @@ async def test_async_command_call_async_respond(commands_context_flow, autojump_
with pytest.raises(RuntimeError, match = 'can only be run from an AnyIO worker thread'): with pytest.raises(RuntimeError, match = 'can only be run from an AnyIO worker thread'):
handler.respond(Response(text = 'foo!')) handler.respond(Response(text = 'foo!'))
context.process_string('foo') await context.process_string('foo')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 0 assert len(context_delegate.responses) == 0
@ -91,7 +91,7 @@ async def test_command_multiple_respond(commands_context_flow, autojump_clock):
await anyio.sleep(2) await anyio.sleep(2)
return Response(text = 'foo4') return Response(text = 'foo4')
context.process_string('foo') await context.process_string('foo')
last_count = 0 last_count = 0
while last_count < 5: while last_count < 5:

View File

@ -22,7 +22,7 @@ def test_new_with_extra_parameters_in_pattern():
@manager.new('test $name:Word, $secondName:Word') @manager.new('test $name:Word, $secondName:Word')
def test(name: Word): pass def test(name: Word): pass
def test_search(): async def test_search():
manager = CommandsManager() manager = CommandsManager()
@manager.new('test') @manager.new('test')
@ -35,13 +35,13 @@ def test_search():
def hello(name: Word): pass def hello(name: Word): pass
# test # test
result = manager.search('test') result = await manager.search('test')
assert result is not None assert result is not None
assert len(result) == 1 assert len(result) == 1
assert result[0].command.name == 'CommandsManager.test' assert result[0].command.name == 'CommandsManager.test'
# hello # hello
result = manager.search('hello world') result = await manager.search('hello world')
assert result is not None assert result is not None
assert len(result) == 1 assert len(result) == 1
assert result[0].command.name == 'CommandsManager.hello' assert result[0].command.name == 'CommandsManager.hello'
@ -50,7 +50,7 @@ def test_search():
assert result[0].match_result.parameters['name'].value == 'world' assert result[0].match_result.parameters['name'].value == 'world'
# hello2 # hello2
result = manager.search('hello new world') result = await manager.search('hello new world')
assert result is not None assert result is not None
assert len(result) == 1 assert len(result) == 1
assert result[0].command == hello2 assert result[0].command == hello2

View File

@ -15,7 +15,7 @@ async def test_multiple_commands(commands_context_flow, autojump_clock):
def lorem(): def lorem():
return Response(text = 'lorem!') return Response(text = 'lorem!')
context.process_string('foo bar lorem ipsum dolor') await context.process_string('foo bar lorem ipsum dolor')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 2 assert len(context_delegate.responses) == 2
@ -28,7 +28,7 @@ async def test_repeating_command(commands_context_flow, autojump_clock):
def lorem(): def lorem():
return Response(text = 'lorem!') return Response(text = 'lorem!')
context.process_string('lorem pisum dolor lorem ipsutest_repeating_commanduum dolor sit amet') await context.process_string('lorem pisum dolor lorem ipsutest_repeating_commanduum dolor sit amet')
await anyio.sleep(5) await anyio.sleep(5)
assert len(context_delegate.responses) == 2 assert len(context_delegate.responses) == 2
@ -46,7 +46,7 @@ async def test_overlapping_commands_less_priority_cut(commands_context_flow, aut
def baz(): def baz():
return Response(text = 'baz!') return Response(text = 'baz!')
result = manager.search('foo bar test baz') result = await manager.search('foo bar test baz')
assert len(result) == 2 assert len(result) == 2
assert result[0].match_result.substring == 'foo bar test' assert result[0].match_result.substring == 'foo bar test'
assert result[1].match_result.substring == 'baz' assert result[1].match_result.substring == 'baz'
@ -62,7 +62,7 @@ async def test_overlapping_commands_priority_cut(commands_context_flow, autojump
def baz(): def baz():
return Response(text = 'baz!') return Response(text = 'baz!')
result = manager.search('foo bar test baz') result = await manager.search('foo bar test baz')
assert len(result) == 2 assert len(result) == 2
assert result[0].match_result.substring == 'foo bar' assert result[0].match_result.substring == 'foo bar'
@ -79,7 +79,7 @@ async def test_overlapping_commands_remove(commands_context_flow, autojump_clock
def barbaz(): def barbaz():
return Response(text = 'baz!') return Response(text = 'baz!')
result = manager.search('foo bar baz') result = await manager.search('foo bar baz')
assert len(result) == 1 assert len(result) == 1
assert result[0].command == foobar assert result[0].command == foobar
@ -94,7 +94,7 @@ async def test_overlapping_commands_remove_inverse(commands_context_flow, autoju
def foobar(): def foobar():
return Response(text = 'foo!') return Response(text = 'foo!')
result = manager.search('foo bar baz') result = await manager.search('foo bar baz')
assert len(result) == 1 assert len(result) == 1
assert result[0].command == barbaz assert result[0].command == barbaz
@ -107,7 +107,7 @@ async def test_objects_parse_caching(commands_context_flow, autojump_clock):
def pattern(cls): def pattern(cls):
return Pattern('*') return Pattern('*')
def did_parse(self, from_string: str) -> str: async def did_parse(self, from_string: str) -> str:
Mock.parsing_counter += 1 Mock.parsing_counter += 1
return from_string return from_string
@ -130,7 +130,7 @@ async def test_objects_parse_caching(commands_context_flow, autojump_clock):
async def test(mock: Mock): pass async def test(mock: Mock): pass
assert Mock.parsing_counter == 0 assert Mock.parsing_counter == 0
manager.search('hello foobar 22') await manager.search('hello foobar 22')
assert Mock.parsing_counter == 1 assert Mock.parsing_counter == 1
manager.search('hello foobar 22') await manager.search('hello foobar 22')
assert Mock.parsing_counter == 2 assert Mock.parsing_counter == 2

View File

@ -16,20 +16,20 @@ class ExtraParameterInPattern(Object):
def pattern(cls) -> Pattern: def pattern(cls) -> Pattern:
return Pattern('$word1:Word $word2:Word $word3:Word') return Pattern('$word1:Word $word2:Word $word3:Word')
def test_typed_parameters(): async def test_typed_parameters():
p = Pattern('lorem $name:Word dolor') p = Pattern('lorem $name:Word dolor')
assert p.parameters == {'name': Word} assert p.parameters == {'name': Word}
assert p.compiled == fr'lorem (?P<name>{word}) dolor' assert p.compiled == fr'lorem (?P<name>{word}) dolor'
m = p.match('lorem ipsum dolor') m = await p.match('lorem ipsum dolor')
assert m assert m
assert m[0].substring == 'lorem ipsum dolor' assert m[0].substring == 'lorem ipsum dolor'
assert m[0].parameters == {'name': Word('ipsum')} assert m[0].parameters == {'name': Word('ipsum')}
assert not p.match('lorem ipsum foo dolor') assert not await p.match('lorem ipsum foo dolor')
p = Pattern('lorem $name:String dolor') p = Pattern('lorem $name:String dolor')
assert p.parameters == {'name': String} assert p.parameters == {'name': String}
m = p.match('lorem ipsum foo bar dolor') m = await p.match('lorem ipsum foo bar dolor')
assert m assert m
assert m[0].substring == 'lorem ipsum foo bar dolor' assert m[0].substring == 'lorem ipsum foo bar dolor'
assert m[0].parameters == {'name': String('ipsum foo bar')} assert m[0].parameters == {'name': String('ipsum foo bar')}

View File

@ -5,114 +5,114 @@ from core.patterns import expressions
word = fr'[{expressions.alphanumerics}]*' word = fr'[{expressions.alphanumerics}]*'
words = fr'[{expressions.alphanumerics}\s]*' words = fr'[{expressions.alphanumerics}\s]*'
def test_leading_star(): async def test_leading_star():
p = Pattern('*text') p = Pattern('*text')
assert p.compiled == fr'{word}text' assert p.compiled == fr'{word}text'
assert p.match('text') assert await p.match('text')
assert p.match('aaatext') assert await p.match('aaatext')
assert p.match('bbb aaaatext cccc')[0].substring == 'aaaatext' assert (await p.match('bbb aaaatext cccc'))[0].substring == 'aaaatext'
assert not p.match('aaaaext') assert not await p.match('aaaaext')
p = Pattern('Some *text here') p = Pattern('Some *text here')
assert p.compiled == fr'Some {word}text here' assert p.compiled == fr'Some {word}text here'
assert p.match('Some text here') assert await p.match('Some text here')
assert p.match('Some aaatext here') assert await p.match('Some aaatext here')
assert p.match('bbb Some aaatext here cccc')[0].substring == 'Some aaatext here' assert (await p.match('bbb Some aaatext here cccc'))[0].substring == 'Some aaatext here'
assert not p.match('aaatext here') assert not await p.match('aaatext here')
def test_trailing_star(): async def test_trailing_star():
p = Pattern('text*') p = Pattern('text*')
assert p.compiled == fr'text{word}' assert p.compiled == fr'text{word}'
assert p.match('text') assert await p.match('text')
assert p.match('textaaa') assert await p.match('textaaa')
assert p.match('bbb textaaa cccc')[0].substring == 'textaaa' assert (await p.match('bbb textaaa cccc'))[0].substring == 'textaaa'
p = Pattern('Some text* here') p = Pattern('Some text* here')
assert p.compiled == fr'Some text{word} here' assert p.compiled == fr'Some text{word} here'
assert p.match('Some text here') assert await p.match('Some text here')
assert p.match('Some textaaa here') assert await p.match('Some textaaa here')
assert p.match('bbb Some textaaa here cccc')[0].substring == 'Some textaaa here' assert (await p.match('bbb Some textaaa here cccc'))[0].substring == 'Some textaaa here'
assert not p.match('Some textaaa ') assert not await p.match('Some textaaa ')
def test_middle_star(): async def test_middle_star():
p = Pattern('te*xt') p = Pattern('te*xt')
assert p.compiled == fr'te{word}xt' assert p.compiled == fr'te{word}xt'
assert p.match('text') assert await p.match('text')
assert p.match('teaaaaaxt') assert await p.match('teaaaaaxt')
assert p.match('bbb teaaaaaxt cccc')[0].substring == 'teaaaaaxt' assert (await p.match('bbb teaaaaaxt cccc'))[0].substring == 'teaaaaaxt'
p = Pattern('Some te*xt here') p = Pattern('Some te*xt here')
assert p.compiled == fr'Some te{word}xt here' assert p.compiled == fr'Some te{word}xt here'
assert p.match('Some text here') assert await p.match('Some text here')
assert p.match('Some teaaaaaxt here') assert await p.match('Some teaaaaaxt here')
assert p.match('bbb Some teaaeaaaxt here cccc')[0].substring == 'Some teaaeaaaxt here' assert (await p.match('bbb Some teaaeaaaxt here cccc'))[0].substring == 'Some teaaeaaaxt here'
assert not p.match('Some teaaaaaxt') assert not await p.match('Some teaaaaaxt')
def test_double_star(): async def test_double_star():
p = Pattern('**') p = Pattern('**')
assert p.compiled == fr'{words}' assert p.compiled == fr'{words}'
assert p.match('bbb teaaaaaxt cccc')[0].substring == 'bbb teaaaaaxt cccc' assert (await p.match('bbb teaaaaaxt cccc'))[0].substring == 'bbb teaaaaaxt cccc'
p = Pattern('Some ** here') p = Pattern('Some ** here')
assert p.compiled == fr'Some {words} here' assert p.compiled == fr'Some {words} here'
assert p.match('Some text here') assert await p.match('Some text here')
assert p.match('Some lorem ipsum dolor here') assert await p.match('Some lorem ipsum dolor here')
assert p.match('bbb Some lorem ipsum dolor here cccc')[0].substring == 'Some lorem ipsum dolor here' assert (await p.match('bbb Some lorem ipsum dolor here cccc'))[0].substring == 'Some lorem ipsum dolor here'
def test_one_of(): async def test_one_of():
p = Pattern('(foo|bar)') p = Pattern('(foo|bar)')
assert p.compiled == r'(?:foo|bar)' assert p.compiled == r'(?:foo|bar)'
assert p.match('foo') assert await p.match('foo')
assert p.match('bar') assert await p.match('bar')
assert p.match('bbb foo cccc')[0].substring == 'foo' assert (await p.match('bbb foo cccc'))[0].substring == 'foo'
assert p.match('bbb bar cccc')[0].substring == 'bar' assert (await p.match('bbb bar cccc'))[0].substring == 'bar'
p = Pattern('Some (foo|bar) here') p = Pattern('Some (foo|bar) here')
assert p.compiled == r'Some (?:foo|bar) here' assert p.compiled == r'Some (?:foo|bar) here'
assert p.match('Some foo here') assert await p.match('Some foo here')
assert p.match('Some bar here') assert await p.match('Some bar here')
assert p.match('bbb Some foo here cccc')[0].substring == 'Some foo here' assert (await p.match('bbb Some foo here cccc'))[0].substring == 'Some foo here'
assert p.match('bbb Some bar here cccc')[0].substring == 'Some bar here' assert (await p.match('bbb Some bar here cccc'))[0].substring == 'Some bar here'
assert not p.match('Some foo') assert not await p.match('Some foo')
def test_optional_one_of(): async def test_optional_one_of():
p = Pattern('(foo|bar)?') p = Pattern('(foo|bar)?')
assert p.compiled == r'(?:foo|bar)?' assert p.compiled == r'(?:foo|bar)?'
assert p.match('foo') assert await p.match('foo')
assert p.match('bar') assert await p.match('bar')
assert not p.match('') assert not await p.match('')
assert not p.match('bbb cccc') assert not await p.match('bbb cccc')
assert p.match('bbb foo cccc')[0].substring == 'foo' assert (await p.match('bbb foo cccc'))[0].substring == 'foo'
assert p.match('bbb bar cccc')[0].substring == 'bar' assert (await p.match('bbb bar cccc'))[0].substring == 'bar'
p = Pattern('Some (foo|bar)? here') p = Pattern('Some (foo|bar)? here')
assert p.compiled == r'Some (?:foo|bar)? here' assert p.compiled == r'Some (?:foo|bar)? here'
assert p.match('Some foo here') assert await p.match('Some foo here')
assert p.match('Some bar here') assert await p.match('Some bar here')
assert p.match('Some here') assert await p.match('Some here')
assert p.match('bbb Some foo here cccc')[0].substring == 'Some foo here' assert (await p.match('bbb Some foo here cccc'))[0].substring == 'Some foo here'
assert p.match('bbb Some bar here cccc')[0].substring == 'Some bar here' assert (await p.match('bbb Some bar here cccc'))[0].substring == 'Some bar here'
assert p.match('bbb Some here cccc')[0].substring == 'Some here' assert (await p.match('bbb Some here cccc'))[0].substring == 'Some here'
# assert Pattern('[foo|bar]').compiled == Pattern('(foo|bar)?').compiled # assert Pattern('[foo|bar]').compiled == Pattern('(foo|bar)?').compiled
def test_one_or_more_of(): async def test_one_or_more_of():
p = Pattern('{foo|bar}') p = Pattern('{foo|bar}')
assert p.compiled == r'(?:(?:foo|bar)\s?)+' assert p.compiled == r'(?:(?:foo|bar)\s?)+'
assert p.match('foo') assert await p.match('foo')
assert p.match('bar') assert await p.match('bar')
assert not p.match('') assert not await p.match('')
assert p.match('bbb foo cccc')[0].substring == 'foo' assert (await p.match('bbb foo cccc'))[0].substring == 'foo'
assert p.match('bbb bar cccc')[0].substring == 'bar' assert (await p.match('bbb bar cccc'))[0].substring == 'bar'
assert p.match('bbb foo bar cccc')[0].substring == 'foo bar' assert (await p.match('bbb foo bar cccc'))[0].substring == 'foo bar'
assert not p.match('bbb cccc') assert not await p.match('bbb cccc')
p = Pattern('Some {foo|bar} here') p = Pattern('Some {foo|bar} here')
assert p.compiled == r'Some (?:(?:foo|bar)\s?)+ here' assert p.compiled == r'Some (?:(?:foo|bar)\s?)+ here'
assert p.match('Some foo here') assert await p.match('Some foo here')
assert p.match('Some bar here') assert await p.match('Some bar here')
assert not p.match('Some here') assert not await p.match('Some here')
assert p.match('bbb Some foo here cccc')[0].substring == 'Some foo here' assert (await p.match('bbb Some foo here cccc'))[0].substring == 'Some foo here'
assert p.match('bbb Some bar here cccc')[0].substring == 'Some bar here' assert (await p.match('bbb Some bar here cccc'))[0].substring == 'Some bar here'
assert p.match('bbb Some foo bar here cccc')[0].substring == 'Some foo bar here' assert (await p.match('bbb Some foo bar here cccc'))[0].substring == 'Some foo bar here'
assert not p.match('Some foo') assert not await p.match('Some foo')

View File

@ -10,21 +10,22 @@ class Lorem(Object):
def pattern(cls): def pattern(cls):
return Pattern('* ipsum') return Pattern('* ipsum')
def did_parse(self, from_string: str) -> str: async def did_parse(self, from_string: str) -> str:
if 'lorem' not in from_string: if 'lorem' not in from_string:
raise ParseError('lorem not found') raise ParseError('lorem not found')
self.value = 'lorem' self.value = 'lorem'
return 'lorem' return 'lorem'
def test_complex_parsing_failed(): async def test_complex_parsing_failed():
with pytest.raises(ParseError): with pytest.raises(ParseError):
Lorem.parse('some lor ipsum') await Lorem.parse('some lor ipsum')
def test_complex_parsing(): async def test_complex_parsing():
string = 'some lorem ipsum' string = 'some lorem ipsum'
match = Lorem.parse(string) match = await Lorem.parse(string)
assert match assert match
assert match.obj assert match.obj
assert match.obj.value == 'lorem' assert match.obj.value == 'lorem'
assert match.substring == 'lorem' assert match.substring == 'lorem'
assert Lorem.pattern.match(string)[0].substring == 'lorem ipsum' assert (await Lorem.pattern.match(string))[0].substring == 'lorem ipsum'

View File

@ -20,27 +20,27 @@ class ExtraParameterInAnnotation(Object):
def pattern(cls) -> Pattern: def pattern(cls) -> Pattern:
return Pattern('$word1:Word $word2:Word') return Pattern('$word1:Word $word2:Word')
def test_nested_objects(): async def test_nested_objects():
Pattern.add_parameter_type(FullName) Pattern.add_parameter_type(FullName)
p = Pattern('$name:FullName') p = Pattern('$name:FullName')
assert p assert p
assert p.compiled assert p.compiled
m = p.match('John Galt') m = await p.match('John Galt')
assert m assert m
assert set(m[0].parameters.keys()) == {'name'} assert set(m[0].parameters.keys()) == {'name'}
assert m[0].parameters['name'].first == Word('John') assert m[0].parameters['name'].first == Word('John')
assert m[0].parameters['name'].second == Word('Galt') assert m[0].parameters['name'].second == Word('Galt')
def test_extra_parameter_in_annotation(): async def test_extra_parameter_in_annotation():
Pattern.add_parameter_type(ExtraParameterInAnnotation) Pattern.add_parameter_type(ExtraParameterInAnnotation)
p = Pattern('$name:ExtraParameterInAnnotation') p = Pattern('$name:ExtraParameterInAnnotation')
assert p assert p
assert p.compiled assert p.compiled
m = p.match('John Galt') m = await p.match('John Galt')
assert m assert m
assert set(m[0].parameters.keys()) == {'name'} assert set(m[0].parameters.keys()) == {'name'}
assert m[0].parameters['name'].word1 == Word('John') assert m[0].parameters['name'].word1 == Word('John')

View File

@ -4,23 +4,23 @@ from core import String, Pattern
def test_pattern(): def test_pattern():
assert String.pattern == Pattern('**') assert String.pattern == Pattern('**')
def test_parse(): async def test_parse():
assert String.parse('') assert await String.parse('')
assert String.parse('foo bar baz').obj.value == 'foo bar baz' assert (await String.parse('foo bar baz')).obj.value == 'foo bar baz'
def test_match(): async def test_match():
p = Pattern('foo $bar:String baz') p = Pattern('foo $bar:String baz')
assert p assert p
m = p.match('foo qwerty baz') m = await p.match('foo qwerty baz')
assert m assert m
assert m[0].parameters['bar'] == String('qwerty') assert m[0].parameters['bar'] == String('qwerty')
m = p.match('foo lorem ipsum dolor sit amet baz') m = await p.match('foo lorem ipsum dolor sit amet baz')
assert m assert m
assert m[0].parameters['bar'] == String('lorem ipsum dolor sit amet') assert m[0].parameters['bar'] == String('lorem ipsum dolor sit amet')
def test_formatted(): async def test_formatted():
string = String.parse('foo bar baz').obj string = (await String.parse('foo bar baz')).obj
assert str(string) == '<String value: "foo bar baz">' assert str(string) == '<String value: "foo bar baz">'
assert f'{string}' == 'foo bar baz' assert f'{string}' == 'foo bar baz'

View File

@ -4,23 +4,23 @@ from core import Word, Pattern
def test_pattern(): def test_pattern():
assert Word.pattern == Pattern('*') assert Word.pattern == Pattern('*')
def test_parse(): async def test_parse():
word = Word.parse('foo').obj word = (await Word.parse('foo')).obj
assert word assert word
assert word.value == 'foo' assert word.value == 'foo'
def test_match(): async def test_match():
p = Pattern('foo $bar:Word baz') p = Pattern('foo $bar:Word baz')
assert p assert p
m = p.match('foo qwerty baz') m = await p.match('foo qwerty baz')
assert m assert m
assert m[0].parameters['bar'] == Word('qwerty') assert m[0].parameters['bar'] == Word('qwerty')
m = p.match('foo lorem ipsum dolor sit amet baz') m = await p.match('foo lorem ipsum dolor sit amet baz')
assert not m assert not m
def test_formatted(): async def test_formatted():
string = Word.parse('foo').obj string = (await Word.parse('foo')).obj
assert str(string) == '<Word value: "foo">' assert str(string) == '<Word value: "foo">'
assert f'{string}' == 'foo' assert f'{string}' == 'foo'