mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-06-22 15:59:45 +08:00
Add non-strict parsing and the "strict" argument.
This commit is contained in:
parent
277e75f66f
commit
5a673ee930
@ -6,7 +6,7 @@ import random
|
|||||||
import comfy.parse
|
import comfy.parse
|
||||||
from comfy.parse import ParseError
|
from comfy.parse import ParseError
|
||||||
|
|
||||||
class LogicError(Exception):
|
class ParseLogicError(ParseError):
|
||||||
# something that shouldn't be possible occurred in the code
|
# something that shouldn't be possible occurred in the code
|
||||||
# not the user's fault
|
# not the user's fault
|
||||||
pass
|
pass
|
||||||
@ -15,7 +15,7 @@ class LogicError(Exception):
|
|||||||
def get_random_seed():
|
def get_random_seed():
|
||||||
return int.from_bytes(os.urandom(8))
|
return int.from_bytes(os.urandom(8))
|
||||||
|
|
||||||
def translate(text, seed=None, reescape=frozenset()):
|
def translate(text, seed=None, strict=True, reescape=frozenset()):
|
||||||
'''
|
'''
|
||||||
Parses the text, translating "{A|B|C}" choices into a single chosen option.
|
Parses the text, translating "{A|B|C}" choices into a single chosen option.
|
||||||
An option is chosen randomly from the available options.
|
An option is chosen randomly from the available options.
|
||||||
@ -26,6 +26,8 @@ def translate(text, seed=None, reescape=frozenset()):
|
|||||||
"a woman wearing a realistic police uniform".
|
"a woman wearing a realistic police uniform".
|
||||||
All random choices are governed by the supplied random seed value, ensuring repeatability.
|
All random choices are governed by the supplied random seed value, ensuring repeatability.
|
||||||
|
|
||||||
|
If strict is True, exceptions will be thrown if the input doesn't conform to expectations.
|
||||||
|
|
||||||
reescape indicates the set of metacharacters that, if escaped with a backslash in the input, should be re-escaped in the output.
|
reescape indicates the set of metacharacters that, if escaped with a backslash in the input, should be re-escaped in the output.
|
||||||
This is useful to avoid need for multi-escaping when incorporating this parser as a single phase in a multi-phase parsing operation.
|
This is useful to avoid need for multi-escaping when incorporating this parser as a single phase in a multi-phase parsing operation.
|
||||||
Note that while the default is a frozenset, you can pass anything that works with the "in" operator, such as a string or a set.
|
Note that while the default is a frozenset, you can pass anything that works with the "in" operator, such as a string or a set.
|
||||||
@ -35,14 +37,14 @@ def translate(text, seed=None, reescape=frozenset()):
|
|||||||
options = []
|
options = []
|
||||||
while True:
|
while True:
|
||||||
options.append(parse_text_with_choices(input))
|
options.append(parse_text_with_choices(input))
|
||||||
if 0: pass
|
if m := input.match(r'\|'):
|
||||||
elif m := input.match(r'\|'):
|
|
||||||
# loop around for another choice
|
# loop around for another choice
|
||||||
pass
|
pass
|
||||||
elif m := input.match(r'\}'):
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
raise ParseError(input, f"Expected '|' or '}}' after choice text")
|
# at this point, the input must be }
|
||||||
|
# although for incorrectly-formed input, it could be end of input too
|
||||||
|
# regardless, the correct action here is to break and return to the caller
|
||||||
|
break
|
||||||
|
|
||||||
# choose one of the options
|
# choose one of the options
|
||||||
text = rng.choice(options)
|
text = rng.choice(options)
|
||||||
@ -53,35 +55,47 @@ def translate(text, seed=None, reescape=frozenset()):
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
if 0: pass
|
if 0: pass
|
||||||
elif m := input.match(r'\\'): # \ = escape character
|
elif m := input.match(r'\\'):
|
||||||
|
# \ = escape character
|
||||||
if m := input.match(r'.'):
|
if m := input.match(r'.'):
|
||||||
ch = m.group(0)
|
ch = m.group(0)
|
||||||
if ch in reescape:
|
if ch in reescape:
|
||||||
out.append('\\')
|
out.append('\\')
|
||||||
out.append(ch)
|
out.append(ch)
|
||||||
else:
|
else:
|
||||||
raise ParseError(input, f'Unexpected end of input after backslash')
|
if strict:
|
||||||
elif m := input.match(r'\{'): # {
|
raise ParseError(input, f'Unexpected end of input after backslash')
|
||||||
# choice
|
elif m := input.match(r'\{'):
|
||||||
|
# { ... | ... } choice
|
||||||
|
openbrace = input.prior()
|
||||||
chosen_text = parse_choice(input)
|
chosen_text = parse_choice(input)
|
||||||
|
if not input.match(r'\}'):
|
||||||
|
if strict:
|
||||||
|
raise ParseError(openbrace, f"Missing matching closing brace '}}' for earlier open brace '{{'")
|
||||||
out.append(chosen_text)
|
out.append(chosen_text)
|
||||||
elif m := input.match(r'\/'): # /
|
elif m := input.match(r'\/'):
|
||||||
# C-style block "/* */" and line "//" comments
|
# C-style block "/* */" and line "//" comments
|
||||||
|
comment = input.prior()
|
||||||
if 0: pass
|
if 0: pass
|
||||||
elif m := input.match(r'\/'): # /
|
elif m := input.match(r'\/'):
|
||||||
# line comment
|
# // line comment
|
||||||
if not input.match(r'.*?(?:\n|$)'):
|
if not input.match(r'.*?(?:\n|$)'):
|
||||||
raise ParseError(input, f"Failed to find end of comment")
|
if strict:
|
||||||
|
raise ParseLogicError(comment, f"Failed to find end of C-style // line comment")
|
||||||
|
input.match(r'.*') # consume unterminated comment (however that might be possible)
|
||||||
out.append('\n')
|
out.append('\n')
|
||||||
elif m := input.match(r'\*'): # /
|
elif m := input.match(r'\*'):
|
||||||
# block comment
|
# /* ... */ block comment
|
||||||
if not input.match(r'.*?\*\/'):
|
if not input.match(r'.*?\*\/'):
|
||||||
raise ParseError(input, f"Unterminated comment")
|
if strict:
|
||||||
|
raise ParseError(comment, f"Unterminated C-style /* ... */ block comment")
|
||||||
|
input.match(r'.*') # consume unterminated comment
|
||||||
out.append(' ')
|
out.append(' ')
|
||||||
else:
|
else:
|
||||||
# it was a literal /, not a comment after all
|
# it was a literal /, not a comment after all
|
||||||
out.append('/');
|
out.append('/');
|
||||||
elif m := input.match(r'[^\\\{\}\|\/]+'): # 1 or more non-metacharacters
|
elif m := input.match(r'[^\\\{\}\|\/]+'):
|
||||||
|
# 1 or more non-metacharacters
|
||||||
out.append(m.group(0))
|
out.append(m.group(0))
|
||||||
else:
|
else:
|
||||||
# didn't match \, {, / or non-metacharacters
|
# didn't match \, {, / or non-metacharacters
|
||||||
@ -90,6 +104,29 @@ def translate(text, seed=None, reescape=frozenset()):
|
|||||||
|
|
||||||
return ''.join(out)
|
return ''.join(out)
|
||||||
|
|
||||||
|
def parse_text_with_choices_outer(input):
|
||||||
|
# this function and the contained loop is required to support the non-strict parsing mode
|
||||||
|
# it catches the case where we exit parse_text_with_choices upon encountering | or }, and don't find ourselves withing a calling instance of parse_choice
|
||||||
|
out = []
|
||||||
|
while True:
|
||||||
|
out.append(parse_text_with_choices(input))
|
||||||
|
if 0:pass
|
||||||
|
elif input.match(r'$'):
|
||||||
|
break
|
||||||
|
elif input.match(r'\|'):
|
||||||
|
if strict:
|
||||||
|
raise ParseError(input.prior(), f"Encountered a choice delimiter '|' outside any choice block")
|
||||||
|
elif input.match(r'\}'):
|
||||||
|
if strict:
|
||||||
|
raise ParseError(input.prior(), f"Encountered a closing brace '}}' without a matching open brace")
|
||||||
|
else:
|
||||||
|
if strict:
|
||||||
|
raise ParseLogicError(input, f'Failed to parse up to the end of the prompt text')
|
||||||
|
break
|
||||||
|
|
||||||
|
return ''.join(out)
|
||||||
|
|
||||||
|
|
||||||
if seed == None:
|
if seed == None:
|
||||||
seed = get_random_seed()
|
seed = get_random_seed()
|
||||||
|
|
||||||
@ -98,12 +135,9 @@ def translate(text, seed=None, reescape=frozenset()):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
input = comfy.parse.Cursor(text)
|
input = comfy.parse.Cursor(text)
|
||||||
out = parse_text_with_choices(input)
|
out = parse_text_with_choices_outer(input)
|
||||||
if not input.match(r'$'):
|
|
||||||
raise ParseError(input, f'Failed to parse up to the end of the prompt text')
|
|
||||||
|
|
||||||
return out
|
return out
|
||||||
except (ParseError, LogicError) as e:
|
except (ParseError) as e:
|
||||||
# alternative: re-throw the error
|
# alternative: re-throw the error
|
||||||
stdout.write(f'Error parsing prompt: {e}');
|
stdout.write(f'Error parsing prompt: {e}');
|
||||||
return text
|
return text
|
||||||
|
|||||||
@ -24,6 +24,13 @@ class Cursor:
|
|||||||
self.consume = consume
|
self.consume = consume
|
||||||
self.space = space
|
self.space = space
|
||||||
|
|
||||||
|
def prior(self):
|
||||||
|
# returns a cursor pointing at the position prior to the last match
|
||||||
|
prior = self.clone()
|
||||||
|
prior.end = prior.start
|
||||||
|
prior.pos = prior.start
|
||||||
|
return prior
|
||||||
|
|
||||||
def loc(self):
|
def loc(self):
|
||||||
# describe the cursor position in a human-readable form, suitable for error messages
|
# describe the cursor position in a human-readable form, suitable for error messages
|
||||||
|
|
||||||
|
|||||||
2
nodes.py
2
nodes.py
@ -1479,7 +1479,7 @@ class DynamicPrompt:
|
|||||||
CATEGORY = "conditioning"
|
CATEGORY = "conditioning"
|
||||||
|
|
||||||
def dynamic_prompt(self, text, seed):
|
def dynamic_prompt(self, text, seed):
|
||||||
translated_prompt_text = comfy.choices.translate(text, seed=seed, reescape=r'\()')
|
translated_prompt_text = comfy.choices.translate(text, seed=seed, strict=False, reescape=r'\()')
|
||||||
return (translated_prompt_text,)
|
return (translated_prompt_text,)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user