Repository: neogeny/TatSu

Issue:
No boolean literals
```
`10` -> 10
`string` -> 'string'
`'long string'` -> 'long string'
`True` -> 'True'
```
Since number literals are passed on I would expect the same to happen for boolean literals, but instead they get converted to strings. The same happens for `None` as well. I can use 0 and 1 instead, but it's not really the same.

Workaround:
```
t_bool::bool = k_true @:`1` | k_false @:`0`;
t_bool_lit::Boolean = VALUE:t_bool;
```

Discussion:

Patch:
diff --git a/grammar/tatsu.ebnf b/grammar/tatsu.ebnf
index d087e40..3ea4896 100644
--- a/grammar/tatsu.ebnf
+++ b/grammar/tatsu.ebnf
@@ -378,7 +378,7 @@ token::Token
 
 literal
     =
-    string | raw_string | word | hex | float | int
+    string | raw_string | boolean | word | hex | float | int
     ;
 
 
diff --git a/tatsu/bootstrap.py b/tatsu/bootstrap.py
index 50ecd1f..5fcba66 100644
--- a/tatsu/bootstrap.py
+++ b/tatsu/bootstrap.py
@@ -152,7 +152,7 @@ class EBNFBootstrapParser(Parser):
                                 self._boolean_()
                                 self.name_last_node('value')
                             with self._option():
-                                self._constant('True')
+                                self._constant(True)
                                 self.name_last_node('value')
                             self._error('no available options')
                 with self._option():
@@ -728,7 +728,7 @@ class EBNFBootstrapParser(Parser):
     def _special_(self):  # noqa
         self._token('?(')
         self._cut()
-        self._pattern(r'.*?(?!\)\?)')
+        self._pattern('.*?(?!\\)\\?)')
         self.name_last_node('@')
         self._token(')?')
         self._cut()
@@ -803,11 +803,11 @@ class EBNFBootstrapParser(Parser):
 
     @tatsumasu('Constant')
     def _constant_(self):  # noqa
-        self._pattern(r'`')
+        self._pattern('`')
         self._cut()
         self._literal_()
         self.name_last_node('@')
-        self._pattern(r'`')
+        self._pattern('`')
 
     @tatsumasu('Token')
     def _token_(self):  # noqa
@@ -825,6 +825,8 @@ class EBNFBootstrapParser(Parser):
                 self._string_()
             with self._option():
                 self._raw_string_()
+            with self._option():
+                self._boolean_()
             with self._option():
                 self._word_()
             with self._option():
@@ -851,14 +853,14 @@ class EBNFBootstrapParser(Parser):
             with self._option():
                 self._token('"')
                 self._cut()
-                self._pattern(r'([^"\n]|\\"|\\\\)*')
+                self._pattern('([^"\\n]|\\\\"|\\\\\\\\)*')
                 self.name_last_node('@')
                 self._token('"')
                 self._cut()
             with self._option():
                 self._token("'")
                 self._cut()
-                self._pattern(r"([^'\n]|\\'|\\\\)*")
+                self._pattern("([^'\\n]|\\\\'|\\\\\\\\)*")
                 self.name_last_node('@')
                 self._token("'")
                 self._cut()
@@ -866,23 +868,23 @@ class EBNFBootstrapParser(Parser):
 
     @tatsumasu()
     def _hex_(self):  # noqa
-        self._pattern(r'0[xX](\d|[a-fA-F])+')
+        self._pattern('0[xX](\\d|[a-fA-F])+')
 
     @tatsumasu()
     def _float_(self):  # noqa
-        self._pattern(r'[-+]?(?:\d+\.\d*|\d*\.\d+)(?:[Ee][-+]?\d+)?')
+        self._pattern('[-+]?(?:\\d+\\.\\d*|\\d*\\.\\d+)(?:[Ee][-+]?\\d+)?')
 
     @tatsumasu()
     def _int_(self):  # noqa
-        self._pattern(r'[-+]?\d+')
+        self._pattern('[-+]?\\d+')
 
     @tatsumasu()
     def _path_(self):  # noqa
-        self._pattern(r'(?!\d)\w+(::(?!\d)\w+)+')
+        self._pattern('(?!\\d)\\w+(::(?!\\d)\\w+)+')
 
     @tatsumasu()
     def _word_(self):  # noqa
-        self._pattern(r'(?!\d)\w+')
+        self._pattern('(?!\\d)\\w+')
 
     @tatsumasu('Any')
     def _any_(self):  # noqa
@@ -908,16 +910,16 @@ class EBNFBootstrapParser(Parser):
             with self._option():
                 self._token('/')
                 self._cut()
-                self._pattern(r'([^/\\]|\\/|\\.)+')
+                self._pattern('([^/\\\\]|\\\\/|\\\\.)+')
                 self.name_last_node('@')
                 self._token('/')
                 self._cut()
             with self._option():
                 self._token('?/')
                 self._cut()
-                self._pattern(r'(.|\n)+?(?=/\?)')
+                self._pattern('(.|\\n)+?(?=/\\?)')
                 self.name_last_node('@')
-                self._pattern(r'/\?+')
+                self._pattern('/\\?+')
                 self._cut()
             with self._option():
                 self._token('?')
@@ -1140,14 +1142,16 @@ class EBNFBootstrapSemantics(object):
         return ast
 
 
-def main(filename, start='start', **kwargs):
+def main(filename, start=None, **kwargs):
+    if start is None:
+        start = 'start'
     if not filename or filename == '-':
         text = sys.stdin.read()
     else:
         with open(filename) as f:
             text = f.read()
     parser = EBNFBootstrapParser()
-    return parser.parse(text, start=start, filename=filename, **kwargs)
+    return parser.parse(text, rule_name=start, filename=filename, **kwargs)
 
 
 if __name__ == '__main__':
diff --git a/tatsu/objectmodel.py b/tatsu/objectmodel.py
index 5be7337..67c0fd0 100644
--- a/tatsu/objectmodel.py
+++ b/tatsu/objectmodel.py
@@ -25,8 +25,8 @@ class Node(object):
             parseinfo = ast.parseinfo if not parseinfo else None
         self._parseinfo = parseinfo
 
-        attributes = ast or {}
-        # asume that kwargs contains node attributes of interest
+        attributes = ast if ast is not None else {}
+        # assume that kwargs contains node attributes of interest
         if isinstance(attributes, MutableMapping):
             attributes.update(kwargs)
 
diff --git a/tatsu/parser_semantics.py b/tatsu/parser_semantics.py
index c15b48b..3d8daf8 100644
--- a/tatsu/parser_semantics.py
+++ b/tatsu/parser_semantics.py
@@ -46,7 +46,7 @@ class EBNFGrammarSemantics(ModelBuilderSemantics):
     def string(self, ast):
         return eval_escapes(ast)
 
-    def hext(self, ast):
+    def hex(self, ast):
         return int(ast, 16)
 
     def float(self, ast):


Test patch:
diff --git a/test/grammar/parameter_test.py b/test/grammar/parameter_test.py
index 7c7b777..a8c4b94 100644
--- a/test/grammar/parameter_test.py
+++ b/test/grammar/parameter_test.py
@@ -192,7 +192,7 @@ class ParameterTests(unittest.TestCase):
 
     def test_numbers_and_unicode(self):
         grammar = '''
-            rúle(1, -23, 4.56, 7.89e-11, 0xABCDEF, Añez)
+            rúle(1, -23, 4.56, 7.89e-11, Añez)
                 =
                 'a'
                 ;
diff --git a/test/grammar/syntax_test.py b/test/grammar/syntax_test.py
index b733b19..5f007dc 100644
--- a/test/grammar/syntax_test.py
+++ b/test/grammar/syntax_test.py
@@ -320,3 +320,26 @@ class SyntaxTests(unittest.TestCase):
         model = compile(grammar, "start")
         ast = model.parse("1xx 2 yy")
         self.assertEqual(['1', 'xx', ' ', '2', 'yy'], ast)
+
+    def test_constant(self):
+        grammar = '''
+            start = ()
+                _0:`0` _1:`+1` _n123:`-123`
+                _xF:`0xF`
+                _string:`string`
+                _string_space:`'string space'`
+                _true:`True` _false:`False`
+                $;
+        '''
+
+        model = compile(grammar)
+        ast = model.parse("")
+
+        self.assertEqual(ast._0, 0)
+        self.assertEqual(ast._1, 1)
+        self.assertEqual(ast._n123, -123)
+        self.assertEqual(ast._xF, 0xF)
+        self.assertEqual(ast._string, "string")
+        self.assertEqual(ast._string_space, "string space")
+        self.assertEqual(ast._true, True)
+        self.assertEqual(ast._false, False)