Allow version to accept the '=' token without activating lexer switch (#32257)

This commit is contained in:
psakievich 2022-09-14 11:16:28 -06:00 committed by GitHub
parent 01c9780577
commit 045a5e80cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 128 additions and 56 deletions

View file

@ -4981,7 +4981,7 @@ def __missing__(self, key):
#: These are possible token types in the spec grammar.
HASH, DEP, AT, COLON, COMMA, ON, OFF, PCT, EQ, ID, VAL, FILE = range(12)
HASH, DEP, VER, COLON, COMMA, ON, OFF, PCT, EQ, ID, VAL, FILE = range(12)
#: Regex for fully qualified spec names. (e.g., builtin.hdf5)
spec_id_re = r"\w[\w.-]*"
@ -5001,10 +5001,13 @@ def __init__(self):
)
super(SpecLexer, self).__init__(
[
(r"\^", lambda scanner, val: self.token(DEP, val)),
(r"\@", lambda scanner, val: self.token(AT, val)),
(
r"\@([\w.\-]*\s*)*(\s*\=\s*\w[\w.\-]*)?",
lambda scanner, val: self.token(VER, val),
),
(r"\:", lambda scanner, val: self.token(COLON, val)),
(r"\,", lambda scanner, val: self.token(COMMA, val)),
(r"\^", lambda scanner, val: self.token(DEP, val)),
(r"\+", lambda scanner, val: self.token(ON, val)),
(r"\-", lambda scanner, val: self.token(OFF, val)),
(r"\~", lambda scanner, val: self.token(OFF, val)),
@ -5142,7 +5145,7 @@ def do_parse(self):
else:
# If the next token can be part of a valid anonymous spec,
# create the anonymous spec
if self.next.type in (AT, ON, OFF, PCT):
if self.next.type in (VER, ON, OFF, PCT):
# Raise an error if the previous spec is already concrete
if specs and specs[-1].concrete:
raise RedundantSpecError(specs[-1], "compiler, version, " "or variant")
@ -5250,7 +5253,7 @@ def spec(self, name):
spec.name = spec_name
while self.next:
if self.accept(AT):
if self.accept(VER):
vlist = self.version_list()
spec._add_versions(vlist)
@ -5268,7 +5271,6 @@ def spec(self, name):
elif self.accept(ID):
self.previous = self.token
if self.accept(EQ):
# We're adding a key-value pair to the spec
self.expect(VAL)
spec._add_flag(self.previous.value, self.token.value)
self.previous = None
@ -5304,16 +5306,24 @@ def variant(self, name=None):
return self.token.value
def version(self):
start = None
end = None
if self.accept(ID):
start = self.token.value
if self.accept(EQ):
# This is for versions that are associated with a hash
# i.e. @[40 char hash]=version
start += self.token.value
self.expect(VAL)
start += self.token.value
def str_translate(value):
# return None for empty strings since we can end up with `'@'.strip('@')`
if not (value and value.strip()):
return None
else:
return value
if self.token.type is COMMA:
# need to increment commas, could be ID or COLON
self.accept(ID)
if self.token.type in (VER, ID):
version_spec = self.token.value.lstrip("@")
start = str_translate(version_spec)
if self.accept(COLON):
if self.accept(ID):
@ -5323,10 +5333,10 @@ def version(self):
else:
end = self.token.value
elif start:
# No colon, but there was a version.
# No colon, but there was a version
return vn.Version(start)
else:
# No colon and no id: invalid version.
# No colon and no id: invalid version
self.next_token_error("Invalid version specifier")
if start:
@ -5349,7 +5359,7 @@ def compiler(self):
compiler = CompilerSpec.__new__(CompilerSpec)
compiler.name = self.token.value
compiler.versions = vn.VersionList()
if self.accept(AT):
if self.accept(VER):
vlist = self.version_list()
compiler._add_versions(vlist)
else:

View file

@ -31,63 +31,97 @@
)
from spack.variant import DuplicateVariantError
# Sample output for a complex lexing.
complex_lex = [
# Building blocks for complex lexing.
complex_root = [
Token(sp.ID, "mvapich_foo"),
Token(sp.DEP),
Token(sp.ID, "_openmpi"),
Token(sp.AT),
Token(sp.ID, "1.2"),
Token(sp.COLON),
Token(sp.ID, "1.4"),
Token(sp.COMMA),
Token(sp.ID, "1.6"),
Token(sp.PCT),
Token(sp.ID, "intel"),
Token(sp.AT),
Token(sp.ID, "12.1"),
Token(sp.COLON),
Token(sp.ID, "12.6"),
Token(sp.ON),
Token(sp.ID, "debug"),
Token(sp.OFF),
Token(sp.ID, "qt_4"),
Token(sp.DEP),
Token(sp.ID, "stackwalker"),
Token(sp.AT),
Token(sp.ID, "8.1_1e"),
]
# Another sample lexer output with a kv pair.
kv_lex = [
kv_root = [
Token(sp.ID, "mvapich_foo"),
Token(sp.ID, "debug"),
Token(sp.EQ),
Token(sp.VAL, "4"),
]
complex_compiler = [
Token(sp.PCT),
Token(sp.ID, "intel"),
]
complex_compiler_v = [
Token(sp.VER, "@12.1"),
Token(sp.COLON),
Token(sp.ID, "12.6"),
]
complex_compiler_v_space = [
Token(sp.VER, "@"),
Token(sp.ID, "12.1"),
Token(sp.COLON),
Token(sp.ID, "12.6"),
]
complex_dep1 = [
Token(sp.DEP),
Token(sp.ID, "_openmpi"),
Token(sp.AT),
Token(sp.VER, "@1.2"),
Token(sp.COLON),
Token(sp.ID, "1.4"),
Token(sp.COMMA),
Token(sp.ID, "1.6"),
]
complex_dep1_space = [
Token(sp.DEP),
Token(sp.ID, "_openmpi"),
Token(sp.VER, "@"),
Token(sp.ID, "1.2"),
Token(sp.COLON),
Token(sp.ID, "1.4"),
Token(sp.COMMA),
Token(sp.ID, "1.6"),
Token(sp.PCT),
Token(sp.ID, "intel"),
Token(sp.AT),
Token(sp.ID, "12.1"),
Token(sp.COLON),
Token(sp.ID, "12.6"),
]
complex_dep1_var = [
Token(sp.ON),
Token(sp.ID, "debug"),
Token(sp.OFF),
Token(sp.ID, "qt_4"),
]
complex_dep2 = [
Token(sp.DEP),
Token(sp.ID, "stackwalker"),
Token(sp.AT),
Token(sp.VER, "@8.1_1e"),
]
complex_dep2_space = [
Token(sp.DEP),
Token(sp.ID, "stackwalker"),
Token(sp.VER, "@"),
Token(sp.ID, "8.1_1e"),
]
# Sample output from complex lexing
complex_lex = (
complex_root
+ complex_dep1
+ complex_compiler
+ complex_compiler_v
+ complex_dep1_var
+ complex_dep2
)
# Another sample lexer output with a kv pair.
kv_lex = (
kv_root
+ complex_dep1
+ complex_compiler
+ complex_compiler_v_space
+ complex_dep1_var
+ complex_dep2_space
)
class TestSpecSyntax(object):
# ========================================================================
@ -120,7 +154,7 @@ def check_lex(self, tokens, spec):
lex_output = sp.SpecLexer().lex(spec)
assert len(tokens) == len(lex_output), "unexpected number of tokens"
for tok, spec_tok in zip(tokens, lex_output):
if tok.type == sp.ID or tok.type == sp.VAL:
if tok.type in (sp.ID, sp.VAL, sp.VER):
assert tok == spec_tok
else:
# Only check the type for non-identifiers.
@ -716,14 +750,22 @@ def test_minimal_spaces(self):
)
def test_spaces_between_dependences(self):
lex_key = (
complex_root
+ complex_dep1
+ complex_compiler
+ complex_compiler_v
+ complex_dep1_var
+ complex_dep2_space
)
self.check_lex(
complex_lex,
lex_key,
"mvapich_foo "
"^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug -qt_4 "
"^stackwalker @ 8.1_1e",
)
self.check_lex(
complex_lex,
lex_key,
"mvapich_foo "
"^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug~qt_4 "
"^stackwalker @ 8.1_1e",
@ -738,14 +780,30 @@ def test_spaces_between_options(self):
)
def test_way_too_many_spaces(self):
lex_key = (
complex_root
+ complex_dep1
+ complex_compiler
+ complex_compiler_v_space
+ complex_dep1_var
+ complex_dep2_space
)
self.check_lex(
complex_lex,
lex_key,
"mvapich_foo "
"^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 "
"^ stackwalker @ 8.1_1e",
)
lex_key = (
complex_root
+ complex_dep1
+ complex_compiler
+ complex_compiler_v_space
+ complex_dep1_var
+ complex_dep2_space
)
self.check_lex(
complex_lex,
lex_key,
"mvapich_foo "
"^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug ~ qt_4 "
"^ stackwalker @ 8.1_1e",
@ -838,6 +896,10 @@ def test_compare_abstract_specs(self):
# Check that we can compare without raising an error
assert a <= b or b < a
def test_git_ref_specs_with_variants(self):
spec_str = "develop-branch-version@git.{h}=develop+var1+var2".format(h="a" * 40)
self.check_parse(spec_str)
def test_git_ref_spec_equivalences(self, mock_packages, mock_stage):
s1 = sp.Spec("develop-branch-version@git.{hash}=develop".format(hash="a" * 40))
s2 = sp.Spec("develop-branch-version@git.{hash}=develop".format(hash="b" * 40))