TODO: change to OPERATION only
NOTE: Formula may contain additional/extra parentheses
-EXPRESSION = '"' ( ECHAR | '{' VARNAME '}' )* '"' | re"'[^'\\n]*'"
+EXPRESSION = '"' ( ECHAR | '{' VARNAME '}' )* '"' | re"'[^'\\n]*'" | VARNAME
ECHAR = re('([^\\{}]|\\\\|\\{|\\}|\\n)*')
VARNAME = re('[a-zA-Z0-9-_]+(\.[a-zA-Z0-9-_]+)*')
"""
def p_VARNAME(self):
self.dbg() # Debug
vnl = []
- while self.peek().isalnum() or self.peek() in u'-_.':
+ while self.preread(1) and (self.peek().isalnum() or self.peek() in u'-_.'):
vnl.append(self.next())
vn = u''.join(vnl)
if not conf.re_VARNAME.match(vn):
def p_EXPRESSION(self):
self.dbg() # Debug
+ if self.peek() not in '\'"':
+ # Expect a variable name
+ varname = self.p_VARNAME()
+ return conf.ConfigExpression((self.tree.lookup(varname),), varname)
op = self.next()
- if op not in '\'"':
- self.syntax_error('Invalid start of expression')
# Parse literal expression
if op == u'\'':
exl = []
return s.var(varname, create=False).value()
def eqparse(s, string, *args, **kwargs):
+ "Parse expression `s`, return parts that should be equal to anoter run of `eqparse(s)`."
return [(i[0], i[1].operation) for i in s.parse(string, *args, **kwargs)]
class TestParser(TestConfig):
- s1 = r"""a="1";z{b='2';w{S.Tr_an-g.e='""'}};c.d='\n';e="{a}{b}";e+='{c.d}';a+="\"\n\{\}";f+='Z{a.b}'"""
+ s1 = r"""a="1";z{b='2';w{S.Tr_an-g.e='"'}};c.d='\n';e="{a}{b}";e+='{c.d}';a+="\"\n\{\}";f+='Z{a.b}';e+=c.d;e+=c;e+=a"""
s2 = '\t\n \n ' + s1.replace('=', '= \n ').replace(';', '\t \n\t \n ').replace('+=',' \n += ') + '\n\n '
def test_noWS(s):
- assert len(s.parse(s.s1)) == 8
+ assert len(s.parse(s.s1)) == 11
def test_noWS_COMMENT(s):
assert s.eqparse(s.s1+'#COMMENT') == s.eqparse(s.s1+'#') == s.eqparse(s.s1+'#\n') == s.eqparse(s.s1+'\n#')
s.assertRaises(ConfigSyntaxError, s.parse, "a='\"")
s.assertRaises(ConfigSyntaxError, s.parse, 'a="{a@b}"')
s.assertRaises(ConfigSyntaxError, s.parse, 'a="A{A"')
+ s.assertRaises(ConfigSyntaxError, s.parse, 'a=b"42"')
+ s.assertRaises(ConfigSyntaxError, s.parse, 'a=b.c.d.')
def test_error_location(s):
try: s.parse('\t \n \n { \n \n ')
s.assertRaises(ConfigSyntaxError, s.parse, "if '{a}'=='{b}' and ''!='' {}")
s.parse('if ((#C\n (\n (not not not""!="")\n#C\n)\t ) ) {}')
s.parse('if (""=="" and not (not not ""!="" or ""=="")){}')
+ s.parse('if ((a=="{b}" and a.b.c!=c.b.a)or x!=y){}')
s.parse('if(""==""){a{if(""==""){if(""==""){b{if(""==""){if(""==""){}}}}}}}')
s.assertRaises(ConfigSyntaxError, s.parse, "if notnot'{a}'=='{b}' {}")
s.assertRaises(ConfigSyntaxError, s.parse, "if ('{a}'=='{b}' not and ''!='') {}")
def test_escape_chars(s):
s.parse(r"""a='{a}\\\\#\n'; b="{a}'\"\{\}"; c='\'; c+="\{{b}\}";""")
assert s.val('c') == r"""\{{a}\\\\#\n'"{}}"""
+
+ def test_expressions(s):
+ s.parse('A="4"; B.B=\'2\'; B.A=A; B.C="{B.A}{B.B}"; if B.C=="42" {D=C}; C="OK"')
+ assert s.val('D') == 'OK'
+
ts = 'a="A:"; if "{c1}"=="1" {a+="C1"; b="B"; if ("{c2a}"=="1" or not "{c2b}"=="1") { a+="C2"; '\
'if ("{c3a}"=="1" and "{c3b}"=="1") { a+="C3" }}}'
-# TODO: Test conditions
+# TODO: Test priority
# TODO: Fail on 1st April
# TODO: Somehow add log.debug('Maximum encountered depth: %d', conf.debug_maxdepth)