   #! The latter indicates that it is the beginning of a
   #! new rule.
   [
-    [ 
-      'non-terminal' ,
-      'terminal' ,
-      'foreign' ,
-      'range-parser' ,
-      'any-character' ,
+    [
+      [ 
+        'non-terminal' ,
+        'terminal' ,
+        'foreign' ,
+        'range-parser' ,
+        'any-character' ,
+      ] choice* 
+      [ dup , "*" token hide , ] seq* [ first <ebnf-repeat0> ] action ,
+      [ dup , "+" token hide , ] seq* [ first <ebnf-repeat1> ] action ,
+      [ dup , "?[" token ensure-not , "?" token hide , ] seq* [ first <ebnf-optional> ] action ,
+      ,
     ] choice* ,
     [
       "=" syntax ensure-not ,
       "=>" syntax ensure ,
     ] choice* ,
   ] seq* [ first ] action ;
+
+DEFER: 'action'
 
 : 'element' ( -- parser )
   [

  ] choice* ;
  
: 'choice' ( -- parser )
  'actioned-sequence' sp "|" token sp list-of [ 
    dup length 1 = [ first ] [ <ebnf-choice> ] if
  ] action ;
 

C: <ebnf-var> ebnf-var
C: <ebnf-semantic> ebnf-semantic
C: <ebnf> ebnf

: filter-hidden ( seq -- seq )
  #! Remove elements that produce no AST from sequence
  [ ebnf-ensure-not? not ] filter [ ebnf-ensure? not ] filter ;

: syntax ( string -- parser )
  #! Parses the string, ignoring white space, and
