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):

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;
```
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)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-flake8 pytest-mypy pytest-pylint
3.9
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
astroid==3.3.9
dill==0.3.9
exceptiongroup==1.2.2
filelock==3.18.0
flake8==7.2.0
iniconfig==2.1.0
isort==6.0.1
mccabe==0.7.0
mypy==1.15.0
mypy-extensions==1.0.0
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
pycodestyle==2.13.0
pyflakes==3.3.1
pylint==3.3.6
pytest==8.3.5
pytest-flake8==1.3.0
pytest-mypy==1.0.0
pytest-pylint==0.21.0
-e git+https://github.com/neogeny/TatSu.git@4aa9636ab1a77a24a5b60eeb06575aee5cf20dd7#egg=TatSu
tomli==2.2.1
tomlkit==0.13.2
typing_extensions==4.13.0

name: TatSu
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - astroid==3.3.9
      - dill==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==7.2.0
      - iniconfig==2.1.0
      - isort==6.0.1
      - mccabe==0.7.0
      - mypy==1.15.0
      - mypy-extensions==1.0.0
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.1
      - pylint==3.3.6
      - pytest==8.3.5
      - pytest-flake8==1.3.0
      - pytest-mypy==1.0.0
      - pytest-pylint==0.21.0
      - tomli==2.2.1
      - tomlkit==0.13.2
      - typing-extensions==4.13.0
prefix: /opt/conda/envs/TatSu

test/grammar/syntax_test.py::SyntaxTests::test_constant
test/grammar/parameter_test.py::ParameterTests::test_35_only_keyword_params test/grammar/parameter_test.py::ParameterTests::test_36_param_combinations test/grammar/parameter_test.py::ParameterTests::test_36_params_and_keyword_params test/grammar/parameter_test.py::ParameterTests::test_36_unichars test/grammar/parameter_test.py::ParameterTests::test_keyword_params test/grammar/parameter_test.py::ParameterTests::test_numbers_and_unicode test/grammar/syntax_test.py::SyntaxTests::test_48_rule_override test/grammar/syntax_test.py::SyntaxTests::test_any test/grammar/syntax_test.py::SyntaxTests::test_ast_assignment test/grammar/syntax_test.py::SyntaxTests::test_based_rule test/grammar/syntax_test.py::SyntaxTests::test_empty_closure test/grammar/syntax_test.py::SyntaxTests::test_empty_match_token test/grammar/syntax_test.py::SyntaxTests::test_failed_ref test/grammar/syntax_test.py::SyntaxTests::test_group_ast test/grammar/syntax_test.py::SyntaxTests::test_include_and_override test/grammar/syntax_test.py::SyntaxTests::test_list_override test/grammar/syntax_test.py::SyntaxTests::test_new_override test/grammar/syntax_test.py::SyntaxTests::test_optional_closure test/grammar/syntax_test.py::SyntaxTests::test_optional_sequence test/grammar/syntax_test.py::SyntaxTests::test_parseinfo test/grammar/syntax_test.py::SyntaxTests::test_partial_choice test/grammar/syntax_test.py::SyntaxTests::test_partial_options test/grammar/syntax_test.py::SyntaxTests::test_raw_string test/grammar/syntax_test.py::SyntaxTests::test_rule_include test/grammar/syntax_test.py::SyntaxTests::test_update_ast
tatsu/objectmodel.py tatsu/parser_semantics.py tatsu/bootstrap.py grammar/tatsu.ebnf
tatsu/objectmodel.py tatsu/parser_semantics.py tatsu/bootstrap.py grammar/tatsu.ebnf