1
0
mirror of https://github.com/MarkParker5/STARK.git synced 2025-02-17 11:55:35 +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
No known key found for this signature in database
GPG Key ID: C10A60786A07A300
15 changed files with 160 additions and 146 deletions

View File

@ -57,7 +57,7 @@ class CommandsContext:
def root_context(self):
return CommandsContextLayer(self.commands_manager.commands, {})
def process_string(self, string: str):
async def process_string(self, string: str):
if not self._context_queue:
self._context_queue.append(self.root_context)
@ -66,7 +66,7 @@ class CommandsContext:
while self._context_queue:
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:
break

View File

@ -1,6 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
import inspect
from asyncer import create_task_group
from .patterns import Pattern, MatchResult
from .types import Object
@ -23,18 +24,31 @@ class CommandsManager:
self.name = name or 'CommandsManager'
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:
commands = self.commands
objects_cache: dict[str, Object] = {}
results: list[SearchResult] = []
# origin = String(string)
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(
command = command,
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:
# 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
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
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.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:
objects_cache = {}
@ -64,7 +64,7 @@ class Pattern:
parameter_str = parsed_substr
break
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
parameters[name] = parse_result.obj
parameter_str = parse_result.substring

View File

@ -25,7 +25,7 @@ class Object(ABC):
'''Just init with wrapped 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.
You will very rarely, if ever, need to call this method directly.
@ -42,7 +42,7 @@ class Object(ABC):
return from_string
@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.
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):
continue
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)

View File

@ -49,7 +49,7 @@ class VoiceAssistant(SpeechRecognizerDelegate, CommandsContextDelegate):
print(f'\nYou: {result}')
# check explicit interaction if needed
if pattern_str := self.mode.explicit_interaction_pattern:
if not Pattern(pattern_str).match(result):
if not await Pattern(pattern_str).match(result):
return
# reset context if timeout reached
@ -62,8 +62,7 @@ class VoiceAssistant(SpeechRecognizerDelegate, CommandsContextDelegate):
if self.mode.mode_on_interaction:
self.mode = self.mode.mode_on_interaction()
# main part: start command; response will come by delegate
self.commands_context.process_string(result) # TODO: async
await self.commands_context.process_string(result)
async def speech_recognizer_did_receive_partial_result(self, result: str):
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._context_queue) == 1
context.process_string('lorem ipsum dolor')
await context.process_string('lorem ipsum dolor')
await anyio.sleep(5)
assert len(context_delegate.responses) == 1
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 with commands_context_flow_filled() as (context, context_delegate):
context.process_string('hello world')
await context.process_string('hello world')
await anyio.sleep(5)
assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Hello, world!'
assert len(context._context_queue) == 2
context_delegate.responses.clear()
context.process_string('hello')
await context.process_string('hello')
await anyio.sleep(5)
assert len(context_delegate.responses) == 1
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 with commands_context_flow_filled() as (context, context_delegate):
context.process_string('hello world')
await context.process_string('hello world')
await anyio.sleep(5)
assert len(context._context_queue) == 2
assert len(context_delegate.responses) == 1
context_delegate.responses.clear()
context.process_string('lorem ipsum dolor')
await context.process_string('lorem ipsum dolor')
await anyio.sleep(5)
assert len(context._context_queue) == 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 with commands_context_flow_filled() as (context, context_delegate):
context.process_string('hello world')
await context.process_string('hello world')
await anyio.sleep(5)
assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Hello, world!'
assert len(context._context_queue) == 2
context_delegate.responses.clear()
context.process_string('bye')
await context.process_string('bye')
await anyio.sleep(5)
assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Bye, world!'
assert len(context._context_queue) == 1
context_delegate.responses.clear()
context.process_string('hello')
await context.process_string('hello')
await anyio.sleep(5)
assert len(context_delegate.responses) == 0
async def test_repeat_last_answer_response_action(commands_context_flow_filled, autojump_clock):
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)
assert len(context_delegate.responses) == 1
assert context_delegate.responses[0].text == 'Hello, world!'
context_delegate.responses.clear()
assert len(context_delegate.responses) == 0
context.process_string('repeat')
await context.process_string('repeat')
await anyio.sleep(5)
assert len(context_delegate.responses) == 1
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:
return Response(text = 'foo!')
context.process_string('foo')
await context.process_string('foo')
await anyio.sleep(5)
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):
handler.respond(Response(text = 'foo!'))
context.process_string('foo')
await context.process_string('foo')
await anyio.sleep(5)
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):
await handler.respond(Response(text = 'foo!'))
context.process_string('foo')
await context.process_string('foo')
await anyio.sleep(5)
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 'was never awaited' in str(warnings_list[0].message)
context.process_string('foo')
await context.process_string('foo')
await anyio.sleep(5)
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'):
handler.respond(Response(text = 'foo!'))
context.process_string('foo')
await context.process_string('foo')
await anyio.sleep(5)
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)
return Response(text = 'foo4')
context.process_string('foo')
await context.process_string('foo')
last_count = 0
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')
def test(name: Word): pass
def test_search():
async def test_search():
manager = CommandsManager()
@manager.new('test')
@ -35,13 +35,13 @@ def test_search():
def hello(name: Word): pass
# test
result = manager.search('test')
result = await manager.search('test')
assert result is not None
assert len(result) == 1
assert result[0].command.name == 'CommandsManager.test'
# hello
result = manager.search('hello world')
result = await manager.search('hello world')
assert result is not None
assert len(result) == 1
assert result[0].command.name == 'CommandsManager.hello'
@ -50,7 +50,7 @@ def test_search():
assert result[0].match_result.parameters['name'].value == 'world'
# hello2
result = manager.search('hello new world')
result = await manager.search('hello new world')
assert result is not None
assert len(result) == 1
assert result[0].command == hello2

View File

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

View File

@ -16,20 +16,20 @@ class ExtraParameterInPattern(Object):
def pattern(cls) -> Pattern:
return Pattern('$word1:Word $word2:Word $word3:Word')
def test_typed_parameters():
async def test_typed_parameters():
p = Pattern('lorem $name:Word dolor')
assert p.parameters == {'name': Word}
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[0].substring == 'lorem ipsum dolor'
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')
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[0].substring == 'lorem ipsum foo bar dolor'
assert m[0].parameters == {'name': String('ipsum foo bar')}

View File

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

View File

@ -10,21 +10,22 @@ class Lorem(Object):
def pattern(cls):
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:
raise ParseError('lorem not found')
self.value = 'lorem'
return 'lorem'
def test_complex_parsing_failed():
async def test_complex_parsing_failed():
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'
match = Lorem.parse(string)
match = await Lorem.parse(string)
assert match
assert match.obj
assert match.obj.value == '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:
return Pattern('$word1:Word $word2:Word')
def test_nested_objects():
async def test_nested_objects():
Pattern.add_parameter_type(FullName)
p = Pattern('$name:FullName')
assert p
assert p.compiled
m = p.match('John Galt')
m = await p.match('John Galt')
assert m
assert set(m[0].parameters.keys()) == {'name'}
assert m[0].parameters['name'].first == Word('John')
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)
p = Pattern('$name:ExtraParameterInAnnotation')
assert p
assert p.compiled
m = p.match('John Galt')
m = await p.match('John Galt')
assert m
assert set(m[0].parameters.keys()) == {'name'}
assert m[0].parameters['name'].word1 == Word('John')

View File

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