use 5.010;
use strict;
use warnings;
use English qw( -no_match_vars );
use Data::Dumper;

use Marpa::R2 6.000;

# This code uses as its grammar reference the code in
# the arvo repo: https://github.com/urbit/arvo
# File sys/hoon.hoon: https://github.com/urbit/arvo/blob/master/sys/hoon.hoon
# as of commit 7dc3eb1cfacaaafd917697a544bdcf7f22e09eeb

package MarpaX::Hoonlint::YAHC;

use English qw( -no_match_vars );

sub deprecated {
    my $slg      = $Marpa::R2::Context::slg;
    my $rule_id  = $Marpa::R2::Context::rule;
    my ($lhs_id) = $slg->rule_expand($rule_id);
    return [ 'deprecated', $slg->symbol_display_form($lhs_id) ];
}


# === Automatically generated Marpa rules ===

# Here is meta-programming to write piece 2

# ace and gap are not really char names,
# and are omitted
my %glyphs = (
    bar => '|',
    bas => '\x5c', # '\'
    buc => '$',
    cab => '_',
    cen => '%',
    col => ':',
    com => ',',
    doq => '"',
    dot => '.',
    fas => '/',
    gal => '<',
    gar => '>',
    hax => '#',
    hep => '-',
    kel => '{',
    ker => '}',
    ket => '\\^',
    lus => '+',
    pal => '(',
    pam => '&',
    par => ')',
    pat => '@',
    pel => '(',
    per => ')',
    sel => '\x5b', # '['
    sem => ';',
    ser => '\x5d', # ']'
    sig => '~',
    soq => '\'',
    tar => '*',
    tec => '`',
    tis => '=',
    wut => '?',
    zap => '!',
);

my @glyphRules = ();
for my $glyphName (sort keys %glyphs) {
    my $glyph = $glyphs{$glyphName};
    my $ucGlyphName = uc $glyphName;
    my $uc4hGlyphName = $ucGlyphName . '4H';
    my $lcGlyphName = $glyphName . '4h';
    push @glyphRules, "$ucGlyphName ~ $lcGlyphName";
    push @glyphRules, "$uc4hGlyphName ~ $lcGlyphName";
    push @glyphRules, "$lcGlyphName ~ [" . $glyph . q{]};
    push @glyphRules, "inaccessible_ok ::= $ucGlyphName";
    push @glyphRules, "inaccessible_ok ::= $uc4hGlyphName";
}
my $glyphAutoRules = join "\n", @glyphRules;

my $mainDSL = do { $RS = undef; <DATA> };

my @dslAutoRules = ();
DESC: for my $desc (split "\n", $mainDSL) {
    my $originalDesc = $desc;
    chomp $desc; # remove newline
    next DESC if not $desc =~ s/^[#] FIXED: //;
    $desc =~ s/^\s+//; # eliminate leading spaces
    $desc =~ s/\s+$//; # eliminate trailing spaces
    my ($rune, @samples) = split /\s+/, $desc;
    die $originalDesc if not $rune;
    push @dslAutoRules, doFixedRune( $rune, @samples );
}
my $dslAutoRules = join "\n", @dslAutoRules;

# Assemble the base BSL
my $baseDSL = join "\n", $mainDSL, $glyphAutoRules, $dslAutoRules;

my $defaultSemantics = <<'EOS';
# start and length will be needed for production
# :default ::= action => [name,start,length,values]
:default ::= action => [name,values]
lexeme default = latm => 1
EOS

sub divergence {
    die join '', 'Unrecoverable internal error: ', @_;
}

# Given an input and an offset into that input,
# it reads a triple quote (''').  The return values
# are the parse value and a new offset in the input.
# Errors are thrown.

sub getTripleQuote {
    my ( $input, $offset ) = @_;
    my $input_length = length ${$input};
    my $resume_pos;
    my $this_pos;

    my $nextNL = index ${$input}, "\n", $offset;
    if ($nextNL < 0) {
      die join '', 'Newline missing after triple quotes: "', ${$input}, '"'
    }
    my $initiator = substr ${$input}, $offset, $nextNL-$offset;
    if ($initiator ne "'''" and $initiator !~ m/^''' *::/) {
      die join '', 'Disallowed characters after initial triple quotes: "', $initiator, '"'
    }

    pos ${$input} = $offset;
    my ($indent) = ${$input} =~ /\G( *)[^ ]/g;
    my $terminator = $indent . "'''";

    my $terminatorPos = index ${$input}, $terminator, $nextNL;
    my $value = substr ${$input}, $nextNL+1, ($terminatorPos - $nextNL);

    say STDERR "Left main READ loop" if $MarpaX::Hoonlint::YAHC::DEBUG;

    # Return ref to value and new offset
    return \$value, $terminatorPos + length $terminator;
}

# Given an input and an offset into that input,
# it reads a triple double quote (""").  The return values
# are the parse value and a new offset in the input.
# Errors are thrown.

# TODO: Needs to implement reading of sump(5d)

sub getTripleDoubleQuote {
    my ( $input, $offset ) = @_;
    my $input_length = length ${$input};
    my $resume_pos;
    my $this_pos;

    my $nextNL = index ${$input}, "\n", $offset;
    if ($nextNL < 0) {
      die join '', 'Newline missing after triple double quotes: "',
	${$input}, '"'
    }
    my $initiator = substr ${$input}, $offset, $nextNL-$offset;
    if ($initiator ne q{"""}) {
      die join '',
	'Disallowed characters after initial triple double quotes: "', $initiator, '"'
    }

    pos ${$input} = $offset;
    my ($indent) = ${$input} =~ /\G( *)[^ ]/g;
    my $terminator = $indent . q{"""};

    my $terminatorPos = index ${$input}, $terminator, $nextNL;
    my $value = substr ${$input}, $nextNL+1, ($terminatorPos - $nextNL);

    say STDERR "Left main READ loop" if $MarpaX::Hoonlint::YAHC::DEBUG;

    # Return ref to value and new offset
    return \$value, $terminatorPos + length $terminator;
}

# Given an input and an offset into that input,
# it reads unmarkdown.  The return values
# are the parse value and a new offset in the input.
# Reading is not intelligent -- it finds a terminator, and
# treats the unmarkdown as a string.
# Errors are thrown.

sub getCram {
    # $DB::single = 1;

    my ( $input, $origOffset ) = @_;
    my $input_length = length ${$input};
    my $resume_pos;
    my $this_pos;

    my $semiPos = rindex ${$input}, ';', $origOffset;
    my $previousNlPos = rindex ${$input}, "\n", $semiPos;
    my $indent = $semiPos - ($previousNlPos + 1);
    my $firstNlPos = index ${$input}, "\n", $semiPos;
    my $valueStartPos      = $semiPos + 2;
    my $nextNlPos = $firstNlPos;
    # say STDERR qq{origOffset: }, substr(${$input}, $origOffset, 20);
    # say STDERR qq{First NL pos: }, substr(${$input}, $firstNlPos, 20);

    if ($indent <= 0) {
	# say STDERR "indent=$indent; nextNlPos=$nextNlPos";
        LINE: while ($nextNlPos >= 0) {
            pos ${$input} = $nextNlPos + 1;
            if ( ${$input} =~ m/\G [ ]* == [\n]/xms ) {
                my $terminatorStartPos = $LAST_MATCH_START[0];
                my $terminatorEndPos   = $LAST_MATCH_END[0];
                my $value              = substr( ${$input}, $valueStartPos,
                    $terminatorStartPos - $valueStartPos );
                return \$value, $nextNlPos;
            }
	    $nextNlPos = index ${$input}, "\n", $nextNlPos+1;
        }
	# If here, end of string is EOF
	my $inputLength = length ${$input};
	my $value = substr ${$input}, $valueStartPos, $inputLength - $valueStartPos;
	return \$value, $inputLength;
    }

    # If here, indent > 0
    my $indentString = (' ' x $indent);

    LINE: while ($nextNlPos >= 0) {
	# say STDERR "LINE: indent=$indent; nextNlPos=$nextNlPos";
	pos ${$input} = $nextNlPos + 1;
	# say STDERR qq{Pos set to: }, substr(${$input}, $nextNlPos+1, 20);
	if ( ${$input} =~ m/\G $indentString [ ]* == [\n]/xms ) {
	    my $terminatorStartPos = $LAST_MATCH_START[0];
	    # say STDERR qq{TISTIS found: }, substr(${$input}, $terminatorStartPos, 20);
	    my $value              = substr( ${$input}, $valueStartPos,
		$terminatorStartPos - $valueStartPos );
	    # Continue parsing after TISTIS?  Or before?
	    return \$value, $nextNlPos;
	}
	
	if ( (substr ${$input}, $nextNlPos+1, $indent) eq $indentString ) {
	    $nextNlPos = index ${$input}, "\n", $nextNlPos+1;
	    # say STDERR qq{Continuing cram, nextNlPos=$nextNlPos};
	    # say STDERR qq{Continuing cram: }, substr(${$input}, $nextNlPos, 20);
	    next LINE;
	}
	# If here, outdent
	# say STDERR qq{Outdent, returning at: }, substr(${$input}, $nextNlPos+1, 20);
	my $value              = substr ${$input}, $valueStartPos,
	  ($nextNlPos + 1) - $valueStartPos;
	return \$value, $nextNlPos+1;
    }

    # Premature EOF if here
    return;
}

# The 'semantics' named argument must be considered "internal"
# for now -- any change in the grammar could break any or all of
# apps.  When the grammar can be frozen, the 'semantics' argument
# can become a "documented" feature.
#
# In the meantime, applications which want stability can simply
# copy in this file lexically, losing the advantage of updates,
# but guaranteeing stability.
sub new {
    my ($class, @argHashes) = @_;
    my $self      = {};
    for my $argHash (@argHashes) {
      ARG_NAME: for my $argName ( keys %{$argHash} ) {
            if ( $argName eq 'all_symbols' ) {
                $self->{all_symbols} = $argHash->{all_symbols};
                next ARG_NAME;
            }
            if ( $argName eq 'semantics' ) {
                $self->{semantics} = $argHash->{semantics};
                next ARG_NAME;
            }
            die "MarpaX::Hoonlint::YAHC::new() called with unknown arg name: $argName";
        }
    }
    my $semantics = $self->{semantics} // $defaultSemantics;
    if ( $self->{all_symbols} ) {
        ## show all symbols
        $baseDSL =~ s/[(][-] //g;
        $baseDSL =~ s/ [-][)]//g;
    }
    else {
        ## hide selected symbols
        $baseDSL =~ s/[(][-] /(/g;
        $baseDSL =~ s/ [-][)]/)/g;
    }
    my $dsl = $semantics . $baseDSL;

    my $grammar = Marpa::R2::Scanless::G->new( { source => \$dsl } );
    $self->{dsl} = $dsl;
    $self->{grammar} = $grammar;
    return bless $self, $class;
}

sub recceStart {
    my ($self) = @_;
    my $debug = $MarpaX::Hoonlint::YAHC::DEBUG;
    my $recce = Marpa::R2::Scanless::R->new(
        {
            grammar         => $self->{grammar},
            ranking_method  => 'high_rule_only',
            trace_lexers    => ( $debug ? 1 : 0 ),
            trace_terminals => ( $debug ? 1 : 0 ),
        }
    );
    $self->{recce} = $recce;
    return $self;
}

sub dsl {
    my ($self) = @_;
    return $self->{dsl};
}

sub rawGrammar {
    my ($self) = @_;
    return $self->{grammar};
}

sub rawRecce {
    my ($self) = @_;
    return $self->{recce};
}

sub read {
    my ($self, $input) = @_;
    $self->recceStart();
    my $recce = $self->{recce};
    my $debug = $MarpaX::Hoonlint::YAHC::DEBUG;
    my $input_length = length ${$input};
    my $this_pos;
    my $ok = eval { $this_pos = $recce->read( $input ) ; 1; };
    if (not $ok) {
       say STDERR $recce->show_progress(0, -1) if $debug;
       die $EVAL_ERROR;
    }

    # The main read loop.  Read starting at $offset.
    # If interrupted execute the handler logic,
    # and, possibly, resume.
    say STDERR "this_pos=$this_pos ; input_length=$input_length" if $debug;

  READ:
    while ( $this_pos < $input_length ) {

	my $resume_pos;

        # Only one event at a time is expected -- more
        # than one is an error.  No event means parsing
        # is exhausted.

        my $events      = $recce->events();
        my $event_count = scalar @{$events};
        if ( $event_count < 0 ) {
            last READ;
        }
        if ( $event_count != 1 ) {
            divergence("One event expected, instead got $event_count");
        }

        # Find the event name

        my $event = $events->[0];
        my $eventName  = $event->[0];

	say STDERR "$eventName event" if $MarpaX::Hoonlint::YAHC::DEBUG;

        if ( $eventName eq 'tripleQuote' ) {
            my $value_ref;
            ( $value_ref, $resume_pos ) = getTripleQuote( $input, $this_pos );
	    return if not $value_ref;
            my $result = $recce->lexeme_read(
                'TRIPLE_QUOTE_STRING',
                $this_pos,
                ( length ${$value_ref} ),
                [ ${$value_ref} ]
            );
            say STDERR "lexeme_read('TRIPLE_QUOTE_STRING',...) returned ",
              Data::Dumper::Dumper( \$result )
              if $MarpaX::Hoonlint::YAHC::DEBUG;
        }

	# TODO: tripeDoubleQuote must allow sump(5d)
        if ( $eventName eq 'tripleDoubleQuote' ) {
            my $value_ref;
            ( $value_ref, $resume_pos )
	      = getTripleDoubleQuote( $input, $this_pos );
	    return if not $value_ref;
            my $result = $recce->lexeme_read(
                'TRIPLE_DOUBLE_QUOTE_STRING',
                $this_pos,
                ( length ${$value_ref} ),
                [ ${$value_ref} ]
            );
            say STDERR "lexeme_read('TRIPLE_DOUBLE_QUOTE_STRING',...) returned ",
              Data::Dumper::Dumper( \$result )
              if $MarpaX::Hoonlint::YAHC::DEBUG;
	}

        if ( $eventName eq '^CRAM' ) {
            my $value_ref;
            ( $value_ref, $resume_pos )
	      = getCram( $input, $this_pos );
	    if (not $value_ref) {
		# TODO: After development, add "if $debug"
		say STDERR $recce->show_progress( 0, -1 );
		my $badStart = substr ${$input}, $this_pos, 50;
		die join '', 'Problem in getCram: "', $badStart, '"';
	    }
            my $result = $recce->lexeme_read(
                'CRAM',
                $this_pos,
                ( length ${$value_ref} ),
                [ ${$value_ref} ]
            );
            say STDERR "lexeme_read('CRAM',...) returned ",
              Data::Dumper::Dumper( \$result )
              if $MarpaX::Hoonlint::YAHC::DEBUG;
	}

	if (not $resume_pos) {
	  die "read() ended prematurely\n",
	    "  input length = $input_length\n",
	    "  length read = $this_pos\n",
	    qq{  the cause was an "$eventName" event};
	}

	say STDERR "this_pos=$this_pos ; input_length=$input_length" if $debug;

	# say STDERR qq{Resuming at "}, substr ${$input}, $resume_pos, 50;

        my $ok = eval { $this_pos = $recce->resume($resume_pos); 1; };
        if ( not $ok ) {
            say STDERR $recce->show_progress( 0, -1 ) if $debug;
            die $EVAL_ERROR;
        }

    }
    return;
}

sub parse {
    my ($input) = @_;
    my $debug = $MarpaX::Hoonlint::YAHC::DEBUG;
    my $self = MarpaX::Hoonlint::YAHC->new();
    $self->read($input);
    my $recce = $self->{recce};

    if ( 0 ) {
    # if ( $recce->ambiguity_metric() > 1 ) {

        # The calls in this section are experimental as of Marpa::R2 2.090
        my $asf = Marpa::R2::ASF->new( { slr => $recce } );
        say STDERR 'No ASF' if not defined $asf;
        my $ambiguities = Marpa::R2::Internal::ASF::ambiguities($asf);
        my @ambiguities = grep { defined } @{$ambiguities}[ 0 .. 1 ];
        die
          "Parse of BNF/Scanless source is ambiguous\n",
          Marpa::R2::Internal::ASF::ambiguities_show( $asf, \@ambiguities );
    } ## end if ( $recce->ambiguity_metric() > 1 )
    # }

    my $valueRef = $recce->value();
    if ( !$valueRef ) {
	say STDERR $recce->show_progress( 0, -1 ) if $debug;
        die "input read, but there was no parse";
    }

    return $valueRef;
}

# Takes one argument and returns a ref to an array of acceptable
# nodes.  The array may be empty.  All scalars are acceptable
# leaf nodes.  Acceptable interior nodes have length at least 1.
sub prune {
    no warnings 'recursion';
    my ($v) = @_;

    state $deleteIfEmpty = {
        optKets => 1,
    };

    state $nonSemantic = {
        doubleStringElements => 1,
        fordFile             => 1,
        fordHoop             => 1,
        fordHoopSeq          => 1,
        hoonExpression       => 1,
        wideLong5d           => 1,
        norm5d               => 1,
        norm5dMold           => 1,
        rope5d               => 1,
        rump5d               => 1,
        scad5d               => 1,
        scat5d               => 1,
        tall5d               => 1,
        tall5dSeq            => 1,
        teakChoice           => 1,
        till5d               => 1,
        till5dSeq            => 1,
        togaElements         => 1,
        wedeFirst            => 1,
        wide5d               => 1,
        wide5dChoices        => 1,
        wide5dJog            => 1,
        wide5dJogging        => 1,
        wide5dJogs           => 1,
        wide5dSeq            => 1,
        wideNorm5d           => 1,
        wideNorm5dMold       => 1,
        wideTeakChoice       => 1,
        wyde5d               => 1,
        wyde5dSeq            => 1,
    };

    return [] if not defined $v;
    my $reftype = ref $v;
    return [$v] if not $reftype; # An acceptable leaf node
    return prune($$v) if $reftype eq 'REF';
    divergence("Tree node has reftype $reftype") if $reftype ne 'ARRAY';
    my @source = grep { defined } @{$v};
    my $element_count = scalar @source;
    return [] if $element_count <= 0; # must have at least one element
    my $name = shift @source;
    my $nameReftype = ref $name;
    # divergence("Tree node name has reftype $nameReftype") if $nameReftype;
    if ($nameReftype) {
      my @result = ();
      ELEMENT:for my $element ($name, @source) {
	if (ref $element eq 'ARRAY') {
	  push @result, grep { defined }
		  map { @{$_}; }
		  map { prune($_); }
		  @{$element}
		;
	  next ELEMENT;
	}
	push @result, $_;
      }
      return [@result];
    }
    if (defined $deleteIfEmpty->{$name} and $element_count == 1) {
      return [];
    }
    if (defined $nonSemantic->{$name}) {
      # Not an acceptable branch node, but (hopefully)
      # its children are acceptable
      return [ grep { defined }
	      map { @{$_}; }
	      map { prune($_); }
	      @source
	    ];
    }

    # An acceptable branch node
    my @result = ($name);
    push @result, grep { defined }
	    map { @{$_}; }
	    map { prune($_); }
	    @source;
    return [\@result];
}

# takes LC alphanumeric rune name and samples
# for N-fixed rune and returns the Marpa rules
# for the tall and the 2 regular wide forms.
sub doFixedRune {
    my ($runeName, @samples) = @_;
    my @result = (join ' ', '#', (uc $runeName), @samples);
    my $glyphName1 = substr($runeName, 0, 3);
    my $glyphName2 = substr($runeName, 3, 3);
    my $glyph1 = $glyphs{$glyphName1} or die "no glyph for $glyphName1";
    my $glyph2 = $glyphs{$glyphName2};
    my $glyphLexeme1 = ($glyphName1) . '4h';
    my $glyphLexeme2 = ($glyphName2) . '4h';
    my $tallLHS = 'tall' . ucfirst $runeName;
    my $wideLHS = 'wide' . ucfirst $runeName;
    my $tallRuneLexeme = (uc $runeName) . 'GAP';
    my $wideRuneLexeme = (uc $runeName) . 'PEL';

    # norm5d ::= tallBarhep
    push @result, 'norm5d ::= ' . $tallLHS;

    # wideNorm5d ::= wideBarhep
    push @result, 'wideNorm5d ::= ' . $wideLHS;

    # tallBarhep ::= (- BAR4H HEP4H GAP -) tall5d (- GAP -) tall5d
    push @result, $tallLHS . ' ::= (- '
      . $tallRuneLexeme
      . ' -) ' . (join ' (- GAP -) ', @samples);
    state $wideEquiv = {
        bont5d => 'wideBont5d',
        bonz5d => 'wideBonz5d',
        mold   => 'wyde5d',
        tall5d => 'wide5d',
        rack5d => 'wideRack5d',
        rick5d => 'wideRick5d',
        ruck5d => 'wideRuck5d',
        teak5d => 'wideTeak5d',
    };
    my @wideSamples = map { $wideEquiv->{$_} // $_; } @samples;

    # wideBarhep ::= (- BARHEPPEL -) wide5d (- ACE -) wide5d (- PER -)
    push @result, $wideLHS . ' ::= (- '
    . $wideRuneLexeme
    . ' -) ' . (join ' (- ACE -) ', @wideSamples) . q{ (- PER -)};

    # BARHEPGAP ~ bar4h hep4h gap4k
    # BARHEPPEL ~ bar4h hep4h pel4h
    push @result, "$tallRuneLexeme ~ $glyphLexeme1 $glyphLexeme2 gap4k";
    push @result, "$wideRuneLexeme ~ $glyphLexeme1 $glyphLexeme2 pel4h";

    return join "\n", @result, '';
}

1;

# The "FIXED:" comments lines are descriptons of the fixed length runes
# (1-fixed, 2-fixed, 3-fixed and 4-fixed) for auto-generation
# of Marpa rules for the various regular formats, both
# tall and wide.
#
# The format is
#
#   rune type1 type2 ...

# Organization is by hoon.hoon (and Hoon Library) sections: 4a, 5d, etc.;
# and within that alphabetically by "face" name

__DATA__

# === CHARACTER SET ===

# Unicorn is a non-existence character used for various
# tricks: error rules, TODO rules, inaccessbile symbols,
# etc.
UNICORN ~ unicorn
unicorn ~ [^\d\D]

# === Hoon 4i library ===

DOG4I ~ dog4i
dog4i ~ dot4h gay4i

doh4i ~ hep4h hep4h gay4i

GAY4I ~ gay4i
gay4i ~ # empty
gay4i ~ gap4k

LOW4I ~ low4i
low4i ~ [a-z]

NUD4I ~ nud4i
nud4i ~ [0-9]

# the printable characters
prn4i ~ [\x20-\x7e\x80-\xff]
PRN4I_SEQ ~ prn4i+

# vul4i ~ '::' optNonNLs nl

# === Hoon 4j library ===

bip4j ::= bip4j_Piece
  (- DOG4I -) bip4j_Piece
  (- DOG4I -) bip4j_Piece
  (- DOG4I -) bip4j_Piece
  (- DOG4I -) bip4j_Piece
  (- DOG4I -) bip4j_Piece
  (- DOG4I -) bip4j_Piece
bip4j_Piece ::= ASCII_0
bip4j_Piece ::= QEX4J

# Two hex numbers
bix4j ~ six4j six4j

MOT4J ~ mot4j
mot4j ~ [12] sid4j
mot4j ~ sed4j

dum4j ~ sid4j+

DIM4J ~ dim4j # a natural number
dim4j ~ '0'
dim4j ~ dip4j

DIP4J ~ dip4j
dip4j ~ [1-9] dip4jRest
dip4jRest ~ [0-9]*

fed4j ::= huf4j doh4i hyf4jSeq
fed4j ::= hof4j
fed4j ::= haf4j
fed4j ::= TIQ4J

haf4j ::= TEP4J TIP4J

# In hoon.hoon, hef and hif differ in semantics.
hef4j ::= TIP4J TIQ4J

hex4j ~ '0'
hex4j ~ qex4j
hex4j ~ qex4j dog4i qix4jSeq

# In hoon.hoon, hef and hif differ in semantics.
hif4j ::= TIP4J TIQ4J

hof4j ::= hef4j HEP hif4j
hof4j ::= hef4j HEP hif4j HEP hif4j
hof4j ::= hef4j HEP hif4j HEP hif4j HEP hif4j

huf4j ::= hef4j
huf4j ::= hef4j HEP hif4j
huf4j ::= hef4j HEP hif4j HEP hif4j
huf4j ::= hef4j HEP hif4j HEP hif4j HEP hif4j

hyf4j ::= hif4j HEP hif4j
hyf4jSeq ::= hyf4j+ separator=>DOT proper=>1

lip4j ::= lib4j_Piece
  (- DOG4I -) lib4j_Piece
  (- DOG4I -) lib4j_Piece
  (- DOG4I -) lib4j_Piece
lib4j_Piece ::= ASCII_0
lib4j_Piece ::= ted4j

sed4j ~ [1-9]

sex4j ~ [1-9a-f]

sid4j ~ [0-9]

# hexadecimal digit
six4j ~ [0-9a-f]

siv4j ~ [0-9a-v]

ted4j ~ sed4j
ted4j ~ sed4j sid4j
ted4j ~ sed4j sid4j sid4j
ted4j ~ sed4j sid4j sid4j sid4j

QEX4J ~ qex4j
qex4j ~ sex4j
qex4j ~ sex4j hit4k
qex4j ~ sex4j hit4k hit4k
qex4j ~ sex4j hit4k hit4k hit4k

qix4j ~ six4j six4j six4j six4j
QIX4J_SEQ ~ qix4jSeq
qix4jSeq ~ qix4j+ separator=>dot4h proper=>1

# tep, tip and tiq have different semantics in hoon.hoon
TEP4J ~ low4i low4i low4i
TIP4J ~ low4i low4i low4i
TIQ4J ~ low4i low4i low4i

urs4j ::= ursChoice*
ursChoice ::= NUD4I | LOW4I | HEP | DOT | SIG | CAB

urx4j ::= urxChoice*
urxChoice ::= NUD4I | LOW4I | HEP | CAB | DOT
urxChoice ::= SIG hex4j DOT
urxChoice ::= SIG SIG DOT

VUM4J ~ vum4j
vum4j ~ siv4j+

# === Hoon 4k library ===

SYM4K ~ sym4k
CEN_SYM4K ~ cen4h sym4k
sym4k ~ low4i sym4kRest
hig4k ~ [A-Z]

sym4kRest ~ # empty
sym4kRest ~ sym4kRestChars
sym4kRestChars ~ sym4kRestChar+
sym4kRestChar ~ low4i | nud4i | hep4h

VEN4K ~ ven4k
ven4k ~ carCdr
ven4k ~ carCdrPairs
ven4k ~ carCdrPairs carCdr
carCdrPairs ~ carCdrPair+
carCdrPair ~ [-+][<>]
carCdr ~ [-+]

qut4k ::= (- SOQ -) <singleQuoteCord> (- SOQ -)
<singleQuoteCord> ::= qut4k_Piece* separator=>gon4k proper=>1
qut4k_Piece ::= qit4k+
qit4k ::= <SINGLE_QUOTED_CHARS>
qit4k ::= <SINGLE_QUOTED_BAS>
qit4k ::= <SINGLE_QUOTED_SOQ>
qit4k ::= <SINGLE_QUOTED_HEX_CHAR>

<SINGLE_QUOTED_CHARS> ~ unescapedSingleQuoteChar+
# All the printable (non-control) characters except
# bas (x5c) and soq (x27)
unescapedSingleQuoteChar ~ [\x20-\x26\x28-\x5b\x5d-\x7e\x80-\xff]
<SINGLE_QUOTED_BAS> ~ bas4h bas4h
<SINGLE_QUOTED_SOQ> ~ bas4h soq4h
<SINGLE_QUOTED_HEX_CHAR> ~ bas4h mes4k

# <TRIPLE_QUOTE_START> triggers an event -- the quoted
# string is actually supplies as <TRIPLE_QUOTE_STRING>.
qut4k ::= <TRIPLE_QUOTE_START>
qut4k ::= <TRIPLE_QUOTE_STRING>
:lexeme ~ <TRIPLE_QUOTE_START> event=>tripleQuote pause=>before
<TRIPLE_QUOTE_START> ~ ['] ['] [']
<TRIPLE_QUOTE_STRING> ~ unicorn # implemented with a combinator

dem4k ::= DIT4K_SEQ+ separator=>gon4k proper=>1

DIT4K_SEQ ~ dit4kSeq
dit4kSeq ~ dit4k+
dit4k ~ [0-9]

# MES4K ~ mes4k
mes4k ~ hit4k hit4k

# HIT4K ~ hit4k
hit4k ~ dit4k
hit4k ~ [a-fA-F]

gon4k ~ bas4h gay4i fas4h

# === Hoon 4l library ===

crub4l ::= date
crub4l ::= timePeriod
crub4l ::= fed4j
crub4l ::= DOT urs4j
crub4l ::= SIG urx4j
crub4l ::= HEP urx4j

date ::= date_part1
date ::= date_part1 DOT DOT date_part2
date ::= date_part1 DOT DOT date_part2 DOT DOT date_part3
date_part1 ::= DIM4J optHep DOT MOT4J DOT DIP4J
optHep ::= # empty
optHep ::= HEP
date_part2 ::= dum4j DOT dum4j DOT dum4j
date_part3 ::= QIX4J_SEQ

timePeriod ::= timePeriodKernel timePeriodFraction
timePeriod ::= timePeriodKernel
timePeriodKernel ::= timePeriodByUnit+ separator=>DOT proper=>1
timePeriodByUnit ::= timePeriodDays
timePeriodByUnit ::= timePeriodHours
timePeriodByUnit ::= timePeriodMinutes
timePeriodByUnit ::= timePeriodSeconds
timePeriodDays ::= LAPSE_DAYS
LAPSE_DAYS ~ 'd' dim4j
timePeriodHours ::= LAPSE_HOURS
LAPSE_HOURS ~ 'h' dim4j
timePeriodMinutes ::= LAPSE_MINUTES
LAPSE_MINUTES ~ 'm' dim4j
timePeriodSeconds ::= LAPSE_SECONDS
LAPSE_SECONDS ~ 's' dim4j
timePeriodFraction ::= (- DOT DOT -) QIX4J_SEQ

# nuck(4l) is the coin parser
nuck4l ::= SYM4K

# tash(4l) is the signed dime parser
nuck4l ::= tash4l
tash4l ::= HEP bisk4l
tash4l ::= HEP HEP bisk4l

# perd(4l) parses dimes or tuples without their standard prefixes
nuck4l ::= DOT4H perd4l

# Can be either '$~' or '%~'
# -- both seem to have the same semantics
nuck4l ::= moldNullSig
moldNullSig ::= SIG

# perd(4l) parses sig-prefixed coins after the sig prefix
nuck4l ::= SIG twid4l

# TODO: Finish perd4l
perd4l ::= zust4l

# TODO: royl(4l) NYI
royl4l ::= UNICORN

zust4l ::= bip4j
zust4l ::= lip4j
zust4l ::= royl4l
zust4l ::= ASCII_y
zust4l ::= ASCII_n
ASCII_y ~ 'y'
ASCII_n ~ 'n'

twid4l ::= ASCII_0 VUM4J
twid4l ::= crub4l
ASCII_0 ~ '0'

nuck4l ::= bisk4l
# bisk(4l) parses unsigned dimes of any base
bisk4l ::= NUMBER

#     :~  :-  ['a' 'z']  (cook |=(a/@ta [%$ %tas a]) sym)
#         :-  ['0' '9']  (stag %$ bisk)
#         :-  '-'        (stag %$ tash)
#         :-  '.'        ;~(pfix dot perd)
#         :-  '~'        ;~(pfix sig ;~(pose twid (easy [%$ %n 0])))

# === Hoon 5d library ===

# 5d library: bonk

bonk5d ::= CEN4H SYM4K COL4H SYM4K DOT4H DOT4H dem4k
bonk5d ::= CEN4H SYM4K COL4H SYM4K DOT4H dem4k
bonk5d ::= CEN4H SYM4K DOT4H dem4k
bonk5d ::= CEN4H SYM4K

# 5d library: bont

bont5d ::= CEN4H SYM4K (- DOT GAP -) tall5d
bont5d ::= wideBont5d
wideBont5d ::= CEN4H SYM4K (- DOT -) wide5d
wideBont5d ::= CEN4H SYM4K (- DOT ACE -) wide5d

# 5d library: bony

# one or more equal signs
bony5d ::= TIS+

# 5d library: bonz

bonz5d ::= (- TIS TIS GAP -) optBonzElements (- GAP TIS TIS -)
bonz5d ::= wideBonz5d
wideBonz5d ::= SIG
wideBonz5d ::= (- PEL -) optWideBonzElements (- PER -)
optBonzElements ::= bonzElement* separator=>GAP proper=>1
bonzElement ::= CEN SYM4K (- GAP -) tall5d
optWideBonzElements ::= wideBonzElement* separator=>ACE proper=>1
wideBonzElement ::= CEN SYM4K (- ACE -) wide5d

till5d ::= norm5dMold rank=>20
till5d ::= wyde5d rank=>10
wyde5d ::= wideNorm5dMold rank=>20
wyde5d ::= scad5d
till5dSeq ::= till5d+ separator=>GAP proper=>1
wyde5dSeq ::= wyde5d+ separator=>ACE proper=>1

# 5d library: boog
# Always tall

# TODO: Needs elaboration from hoon.hoon
# TODO: Need to add apse:docs
# TODO: What is the meaning of these various types of battery element?
boog5d ::= LuslusCell
boog5d ::= LushepCell
boog5d ::= LustisCell
LuslusCell ::= (- LUS LUS GAP -) BUC (- GAP -) tall5d
LuslusCell ::= (- LUS LUS GAP -) SYM4K (- GAP -) tall5d
LushepCell ::= (- LUS HEP GAP -) SYM4K (- GAP -) tall5d
LushepCell ::= (- LUS HEP GAP -) BUC (- GAP -) tall5d
LustisCell ::= (- LUS TIS GAP -) SYM4K (- GAP -) till5d

# 5d library: gash

gash5d ::= limp5d* separator=>FAS proper=>1
limp5d ::= (- optFasSeq -) gasp5d
optFasSeq ::= # empty
optFasSeq ::= FAS_SEQ
FAS_SEQ ~ fas4h+
gasp5d ::= tisSeq
tisSeq ~ tis4h+
optTisSeq ::= # empty
optTisSeq ::= TIS_SEQ
TIS_SEQ ~ tis4h+

# 5d library: gasp

gasp5d ::= (- optTisSeq -) hasp5d (- optTisSeq -)

# 5d library: hasp

hasp5d ::= (- SEL -) wide5d (- SER -)
hasp5d ::= (- PEL -) wide5dSeq (- PER -)
hasp5d ::= BUC4H
hasp5d ::= qut4k
hasp5d ::= nuck4l

# 5d library: lute

lute5d ::= (- SEL GAP -) tall5dSeq (- GAP SER -)

# 5d library: long

wideLong5d ::= scat5d rank=>80
wideLong5d ::= infixTis rank=>60
wideLong5d ::= infixCol rank=>50
wideLong5d ::= infixKet rank=>40
wideLong5d ::= infixFas rank=>30
wideLong5d ::= circumScatParen rank=>20

toga ::= rope5d
toga ::= togaSeq
togaSeq ::= (- SEL -) togaElements (- SER -)
togaElements ::= togaElement+ separator=>ACE proper=>1
togaElement ::= toga
togaElement ::= SIG

infixTis ::= toga (- TIS -) wide5d
infixCol ::= scat5d (- COL -) wide5d
infixKet ::= scat5d (- KET -) wide5d
infixFas ::= toga (- FAS -) wide5d
circumScatParen ::= scat5d (- PEL -) lobo5d (- PER -)

lobo5d ::= wide5dJogs
wide5dJogs ::= wide5dJog+ separator=>wide5dJoggingSeparator proper=>1
wide5dJog ::= rope5d (- ACE -) wide5d
wide5dJoggingSeparator ::= COM ACE

# 5d library: mota

# Lexemes cannot be empty so empty
# aura name must be special cased.
mota5d ::= # empty
mota5d ::= AURA_NAME
AURA_NAME ~ optLow4kSeq optHig4kSeq
optLow4kSeq ~ low4i*
optHig4kSeq ~ hig4k*

# 5d library: norm

# Mold runes

# ['_' (rune cab %bccb expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
norm5dMold ::= tallBuccabMold
wideNorm5dMold ::= wideBuccabMold
tallBuccabMold ::= (- BUC CAB GAP -) tall5d
wideBuccabMold ::= (- BUC CAB PEL -) wide5d (- PER -)

# ['%' (rune cen %bccn exqs)]
# ++  exqs  |.((butt hunk))                           ::  closed gapped roots
norm5dMold ::= tallBuccenMold
wideNorm5dMold ::= wideBuccenMold
tallBuccenMold ::= (- BUC CEN GAP -) till5dSeq (- GAP TIS TIS -)
wideBuccenMold ::= (- BUC CEN PEL -) wyde5dSeq (- PER -)

# [':' (rune col %bccl exqs)]
# ++  exqs  |.((butt hunk))                           ::  closed gapped roots
# Running syntax
norm5dMold ::= tallBuccolMold
wideNorm5dMold ::= wideBuccolMold
tallBuccolMold ::= (- BUC COL GAP -) till5dSeq (- GAP TIS TIS -)
wideBuccolMold ::= (- BUC COL PEL -) wyde5dSeq (- PER -)

# ['-' (rune hep %bchp exqb)]
# ++  exqb  |.(;~(gunk loan loan))                    ::  two roots
norm5dMold ::= tallBuchepMold
wideNorm5dMold ::= wideBuchepMold
tallBuchepMold ::= (- BUC HEP GAP -) till5d (- GAP -) till5d
wideBuchepMold ::= (- BUC HEP PEL -) wyde5d (- ACE -) wyde5d (- PER -)

# ['^' (rune ket %bckt exqb)]
# ++  exqb  |.(;~(gunk loan loan))                    ::  two roots
norm5dMold ::= tallBucketMold
wideNorm5dMold ::= wideBucketMold
tallBucketMold ::= (- BUC KET GAP -) till5d (- GAP -) till5d
wideBucketMold ::= (- BUC KET PEL -) wyde5d (- ACE -) wyde5d (- PER -)

# ['@' (rune pat %bcpt exqb)]
# ++  exqb  |.(;~(gunk loan loan))                    ::  two roots
norm5dMold ::= tallBucpatMold
wideNorm5dMold ::= wideBucpatMold
tallBucpatMold ::= (- BUC PAT GAP -) till5d (- GAP -) till5d
wideBucpatMold ::= (- BUC PAT PEL -) wyde5d (- ACE -) wyde5d (- PER -)

# [';' (rune sem %bcsm expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
norm5dMold ::= tallBucsemMold
wideNorm5dMold ::= wideBucsemMold
tallBucsemMold ::= (- BUC SEM GAP -) tall5d
wideBucsemMold ::= (- BUC SEM PEL -) wide5d (- PER -)

# ['=' (rune tis %bcts exqg)]
# ++  exqg  |.(;~(gunk sym loan))                     ::  term and root
norm5dMold ::= tallBuctisMold
wideNorm5dMold ::= wideBuctisMold
tallBuctisMold ::= (- BUC TIS GAP -) SYM4K (- GAP -) till5d
wideBuctisMold ::= (- BUC TIS PEL -) SYM4K (- ACE -) wyde5d (- PER -)

# ['?' (rune wut %bcwt exqs)]
# ++  exqs  |.((butt hunk))                           ::  closed gapped roots
norm5dMold ::= tallBucwutMold
wideNorm5dMold ::= wideBucwutMold
tallBucwutMold ::= (- BUC WUT GAP -) till5dSeq (- GAP TIS TIS -)
wideBucwutMold ::= (- BUC WUT PEL -) wyde5dSeq (- PER -)

# [':' (rune col %cnhp exqz)]
#    ++  exqz  |.(;~(gunk loaf (butt hunk)))             ::  hoon, n roots
norm5dMold ::= tallCencolMold
wideNorm5dMold ::= wideCencolMold
tallCencolMold ::= (- CEN COL GAP -) tall5d (- GAP -) till5dSeq (- GAP TIS TIS -)
wideCencolMold ::= (- CEN COL PEL -) wide5d (- ACE -) wyde5dSeq (- PER -)

# ['-' (rune hep %cnhp exqk)]
#    ++  exqk  |.(;~(gunk loaf ;~(plug loan (easy ~))))  ::  hoon with one root
norm5dMold ::= tallCenhepMold
wideNorm5dMold ::= wideCenhepMold
tallCenhepMold ::= (- CEN HEP GAP -) tall5d (- GAP -) till5d
wideCenhepMold ::= (- CEN HEP PEL -) wide5d (- ACE -) wyde5d (- PER -)

# :~  ['^' (rune ket %cnkt exqy)]
#    ++  exqy  |.(;~(gunk loaf loan loan loan))          ::  hoon, three roots
norm5dMold ::= tallCenketMold
wideNorm5dMold ::= wideCenketMold
tallCenketMold ::= (- CEN KET GAP -) tall5d (- GAP -) till5d (- GAP -) till5d (- GAP -) till5d
wideCenketMold ::= (- CEN KET PEL -) wide5d (- ACE -) wyde5d (- ACE -) wyde5d
  (- ACE -) wyde5d (- PER -)

# ['+' (rune lus %cnls exqx)]
#   ++  exqx  |.(;~(gunk loaf loan loan))               ::  hoon, two roots
norm5dMold ::= tallCenlusMold
wideNorm5dMold ::= wideCenlusMold
tallCenlusMold ::= (- CEN LUS GAP -) tall5d (- GAP -) till5d (- GAP -) till5d
wideCenlusMold ::= (- CEN LUS PEL -) wide5d (- ACE -) wyde5d (- ACE -) wyde5d (- PER -)

# 5d library: norm

# Hoon runes

# ['_' (runo cab %brcb [~ ~] exqr)]
# ++  exqr  |.(;~(gunk loan ;~(plug wasp wisp)))      ::  root/aliases?/tail
# wisp must be tall, therefore wasp and BARCAB must be tall
norm5d ::= tallBarcab
tallBarcab ::= (- BAR CAB GAP -) till5d (- GAP -) wasp5d wisp5d

# ['%' (runo cen %brcn [~ ~] expe)]
# ++  expe  |.(wisp)                                  ::  core tail
norm5d ::= tallBarcen
tallBarcen ::= (- BAR CEN GAP -) wisp5d

# [':' (runo col %brcl [~ ~] expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: barcol tall5d tall5d

# ['.' (runo dot %brdt [~ ~] expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: bardot tall5d

# ['-' (runo hep %brhp [~ ~] expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: barhep tall5d

# ['^' (runo ket %brkt [~ ~] expx)]
# ++  expx  |.  ;~  gunk  loaf [...] wisp ::  hoon and core tail
norm5d ::= tallBarket
tallBarket ::= (- BAR KET GAP -) tall5d (- GAP -) wisp5d

# ['~' (runo sig %brsg [~ ~] exqc)]
#  ++  exqc  |.(;~(gunk loan loaf))                    ::  root then hoon
# FIXED: barsig till5d tall5d

# ['*' (runo tar %brtr [~ ~] exqc)]
#  ++  exqc  |.(;~(gunk loan loaf))                    ::  root then hoon
# FIXED: bartar till5d tall5d

# ['=' (runo tis %brts [~ ~] exqc)]
# ++  exqc  |.(;~(gunk loan loaf))                    ::  root then hoon
# FIXED: bartis till5d tall5d

# ['?' (runo wut %brwt [~ ~] expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: barwut tall5d

# ['_' (rune cab %bccb expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: buccab tall5d

# [':' (rune col %bccl exqs)]
# ++  exqs  |.((butt hunk))                           ::  closed gapped roots
norm5d ::= tallBuccol
tallBuccol ::= (- BUC COL GAP -) till5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideBuccol
wideBuccol ::= (- BUC COL PEL -) wyde5dSeq (- PER -)

# ['%' (rune cen %bccn exqs)]
# ++  exqs  |.((butt hunk))                           ::  closed gapped roots
# Running syntax
norm5d ::= tallBuccen
tallBuccen ::= (- BUC CEN GAP -) till5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideBuccen
wideBuccen ::= (- BUC CEN PEL -) wyde5dSeq (- PER -)

# ['-' (rune hep %bchp exqb)]
# ++  exqb  |.(;~(gunk loan loan))                    ::  two roots
# NOT FIXED: buchep till5d till5d
# No multi-character lexemes, to allow unary $-(...)
norm5d ::= tallBuchep
wideNorm5d ::= wideBuchep
tallBuchep ::= (- BUC HEP GAP -) till5d (- GAP -) till5d
wideBuchep ::= (- BUC HEP PEL -) till5d (- ACE -) till5d (- PER -)

# ['^' (rune ket %bckt exqb)]
# ++  exqb  |.(;~(gunk loan loan))                    ::  two roots
# FIXED: bucket till5d till5d

# ['@' (rune pat %bcpt exqb)]
# ++  exqb  |.(;~(gunk loan loan))                    ::  two roots
# FIXED: bucpat till5d till5d

# [';' (rune sem %bcsm exqa)]
# ++  exqa  |.(loan)                                  ::  one hoon
# Typo in hoon.hoon -- actually "loan" is a mold
# FIXED: bucsem till5d

# ['=' (rune tis %bcts exqg)]
# ++  exqg  |.(;~(gunk sym loan))                     ::  term and root
# FIXED: buctis SYM4K till5d

# ['?' (rune wut %bcwt exqs)]
# ++  exqs  |.((butt hunk))                           ::  closed gapped roots
norm5d ::= tallBucwut
tallBucwut ::= (- BUC WUT GAP -) till5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideBucwut
wideBucwut ::= (- BUC WUT PEL -) wyde5dSeq (- PER -)

# ['_' (rune cab %cncb exph)]
# ++  exph  |.((butt ;~(gunk rope rick)))             ::  wing, [tile hoon]s
norm5d ::= tallCencab
tallCencab ::= (- CEN CAB GAP -) rope5d (- GAP -) rick5d (- GAP TIS TIS -)
wideNorm5d ::= wideCencab
wideCencab ::= (- CEN CAB PEL -) rope5d (- ACE -) wideRick5d (- PAR -)

# ['.' (rune dot %cndt expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: cendot tall5d tall5d

# ['-' (rune hep %cnhp expk)]
# ++  expk  |.(;~(gunk loaf ;~(plug loaf (easy ~))))  ::  list of two hoons
# FIXED: cenhep tall5d tall5d

# ['^' (rune ket %cnkt expd)]
# ++  expd  |.(;~(gunk loaf loaf loaf loaf))          ::  four hoons
# FIXED: cenket tall5d tall5d tall5d tall5d

# ['+' (rune lus %cnls expc)]
# ++  expc  |.(;~(gunk loaf loaf loaf))               ::  three hoons
# FIXED: cenlus tall5d tall5d tall5d

# ['~' (rune sig %cnsg expn)]
# ++  expn  |.  ;~  gunk  rope  loaf                  ::  wing, hoon,
# 		;~(plug loaf (easy ~))              ::  list of one hoon
# 	      ==
# FIXED: censig rope5d tall5d tall5d

# ['*' (rune tar %cntr expm)]
#  ++  expm  |.((butt ;~(gunk rope loaf rick)))        ::  several [tile hoon]s
norm5d ::= tallCentar
tallCentar ::= (- CEN TAR GAP -) rope5d (- GAP -) tall5d (- GAP -) rick5d (- GAP TIS TIS -)
wideNorm5d ::= wideCentar
wideCentar ::= (- CEN TAR PEL -) rope5d (- ACE -) wide5d (- ACE -) wideRick5d (- PAR -)

# ['=' (rune tis %cnts exph)]
# ++  exph  |.((butt ;~(gunk rope rick)))             ::  wing, [tile hoon]s
norm5d ::= tallCentis
tallCentis ::= (- CEN TIS GAP -) rope5d (- GAP -) rick5d (- GAP TIS TIS -)
wideNorm5d ::= wideCentis
wideCentis ::= (- CEN TIS PEL -) rope5d (- ACE -) wideRick5d (- PAR -)

# ['_' (rune cab %clcb expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: colcab tall5d tall5d

# ['-' (rune hep %clhp expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: colhep tall5d tall5d

# ['+' (rune lus %clls expc)]
# ++  expc  |.(;~(gunk loaf loaf loaf))               ::  three hoons
# FIXED: collus tall5d tall5d tall5d

# ['^' (rune ket %clkt expd)]
# ++  expd  |.(;~(gunk loaf loaf loaf loaf))          ::  four hoons
# FIXED: colket tall5d tall5d tall5d tall5d

# ['~' (rune sig %clsg exps)]
#  ++  exps  |.((butt hank))                           ::  closed gapped hoons
norm5d ::= tallColsig
tallColsig ::= (- COL SIG GAP -) tall5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideColsig
wideColsig ::= (- COL SIG PEL -) wide5dSeq (- PER -)

# ['*' (rune tar %cltr exps)]
#  ++  exps  |.((butt hank))                           ::  closed gapped hoons
norm5d ::= tallColtar
tallColtar ::= (- COL TAR GAP -) tall5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideColtar
wideColtar ::= (- COL TAR PEL -) wide5dSeq (PER)

# ['^' (rune ket %dtkt exqn)]
# ++  exqn  |.(;~(gunk loan (stag %cltr (butt hank))))::  autoconsed hoons
# I do not understand hoon.hoon comment ("autoconsed hoons"), but
# follow the code
norm5d ::= tallDotket
tallDotket ::= (- DOT KET GAP -) till5d (- GAP -) tall5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideDotket
wideDotket ::= (- DOT KET PEL -) wyde5d (- ACE -) wide5dSeq (- PER -)

# ['+' (rune lus %dtls expa)]
# :~  ['+' (rune lus %dtls expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: dotlus tall5d

# ['*' (rune tar %dttr expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: dottar tall5d tall5d

# ['=' (rune tis %dtts expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: dottis tall5d tall5d

# ['?' (rune wut %dtwt expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: dotwut tall5d

# ['|' (rune bar %ktbr expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: ketbar tall5d

# ['%' (rune cen %ktcn expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: ketcen tall5d

# ['.' (rune dot %ktdt expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: ketdot tall5d tall5d

# ['-' (rune hep %kthp exqc)]
# ++  exqc  |.(;~(gunk loan loaf))                    ::  root then hoon
# FIXED: kethep till5d tall5d

# ['+' (rune lus %ktls expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: ketlus tall5d tall5d

# ['&' (rune pam %ktpm expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: ketpam tall5d

# ['~' (rune sig %ktsg expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: ketsig tall5d

# ['=' (rune tis %ktts expg)]
# ++  expg  |.(;~(gunk sym loaf))                     ::  term and hoon
# FIXED: kettis SYM4K tall5d

# ['?' (rune wut %ktwt expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: ketwut tall5d

# [':' (rune col %smcl expi)]
# ++  expi  |.((butt ;~(gunk loaf hank)))             ::  one or more hoons
norm5d ::= tallSemcol
tallSemcol ::= (- SEM COL GAP -) tall5d (- GAP -) tall5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideSemcol
wideSemcol ::= (- SEM COL PEL -) wide5d (- ACE -) wide5dSeq (- PER -)

# ['/' (rune fas %smfs expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: semfas tall5d

# [';' (rune sem %smsm expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: semsem tall5d tall5d

# ['~' (rune sig %smsg expi)]
# ++  expi  |.((butt ;~(gunk loaf hank)))             ::  one or more hoons
norm5d ::= tallSemsig
tallSemsig ::= (- SEM SIG GAP -) tall5d (- GAP -) tall5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideSemsig
wideSemsig ::= (- SEM SIG PEL -) wide5d (- ACE -) wide5dSeq (- PER -)

# ['|' (rune bar %sgbr expb)]
# FIXED: sigbar tall5d tall5d

# ['$' (rune buc %sgbc expf)]
# ++  expf  |.(;~(gunk ;~(pfix cen sym) loaf))        ::  %term and hoon
# FIXED: sigbuc CEN_SYM4K tall5d

# ['_' (rune cab %sgcb expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: sigcab tall5d tall5d

# ['%' (rune cen %sgcn hind)]
# ++  hind  |.(;~(gunk bonk loaf bonz loaf))          ::  jet hoon "bon"s hoon
# FIXED: sigcen bonk5d tall5d bonz5d tall5d

# ['/' (rune fas %sgfs hine)]
# ++  hine  |.(;~(gunk bonk loaf))                    ::  jet-hint and hoon
# FIXED: sigfas bonk5d tall5d

# ['<' (rune gal %sggl hinb)]
#  ++  hinb  |.(;~(gunk bont loaf))                    ::  hint and hoon
# FIXED: siggal bont5d tall5d

# ['>' (rune gar %sggr hinb)]
#  ++  hinb  |.(;~(gunk bont loaf))                    ::  hint and hoon
# FIXED: siggar bont5d tall5d

# ['+' (rune lus %sgls hinc)]
# ++  hinc  |.                                        ::  optional =en, hoon
#           ;~(pose ;~(gunk bony loaf) (stag ~ loaf)) ::
norm5d ::= tallSiglus
tallSiglus ::= (- SIG LUS GAP -) bony5d (- GAP -) tall5d
tallSiglus ::= (- SIG LUS GAP -) tall5d
wideNorm5d ::= wideSiglus
wideSiglus ::= (- SIG LUS PEL -) bony5d (- ACE -) wide5d (- PER -)
wideSiglus ::= (- SIG LUS PEL -) wide5d (- PER -)

# ['&' (rune pam %sgpm hinf)]
# ++  hinf  |.                                        ::  0-3 >s, two hoons
#  ;~  pose
#    ;~(gunk (cook lent (stun [1 3] gar)) loaf loaf)
#    (stag 0 ;~(gunk loaf loaf))
#  ==
norm5d ::= tallSigpam
tallSigpam ::= (- SIG PAM GAP -) oneToThreeGars (- GAP -) tall5d (- GAP -) tall5d
tallSigpam ::= (- SIG PAM GAP -) tall5d (- GAP -) tall5d
wideNorm5d ::= wideSigpam
wideSigpam ::= (- SIG PAM PEL -) oneToThreeGars (- ACE -) wide5d (- ACE -) wide5d (- PER -)
wideSigpam ::= (- SIG PAM PEL -) wide5d (- ACE -) wide5d (- PER -)
oneToThreeGars ::= GAR | GAR GAR | GAR GAR GAR

# ['=' (rune tis %sgts expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: sigtis tall5d tall5d

# ['?' (rune wut %sgwt hing)]
# ++  hing  |.                                        ::  0-3 >s, three hoons
#  ;~  pose
#    ;~(gunk (cook lent (stun [1 3] gar)) loaf loaf loaf)
#    (stag 0 ;~(gunk loaf loaf loaf))
#  ==
norm5d ::= tallSigwut
tallSigwut ::= (- SIG WUT GAP -) oneToThreeGars (- GAP -) tall5d (- GAP -) tall5d (- GAP -) tall5d
tallSigwut ::= (- SIG WUT GAP -) tall5d (- GAP -) tall5d (- GAP -) tall5d
wideNorm5d ::= wideSigwut
wideSigwut ::= (- SIG WUT PEL -) oneToThreeGars (- ACE -) wide5d (- ACE -) wide5d (- ACE -) wide5d (- PER -)
wideSigwut ::= (- SIG WUT PEL -) wide5d (- ACE -) wide5d (- ACE -) wide5d (- PER -)

# ['!' (rune zap %sgzp expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: sigzap tall5d tall5d

# ['|' (rune bar %tsbr exqc)]
# ++  exqc  |.(;~(gunk loan loaf))                    ::  root then hoon
# FIXED: tisbar till5d tall5d

# [':' (rune col %tscl expp)]
# ++  expp  |.(;~(gunk (butt rick) loaf))             ::  [wing hoon]s, hoon
norm5d ::= tallTiscol
tallTiscol ::= (- TIS COL GAP -) rick5d (- GAP TIS TIS GAP -) tall5d
wideNorm5d ::= wideTiscol
wideTiscol ::= (- TIS COL PEL -) wideRick5d (- ACE -) wide5d (- PAR -)

# [',' (rune com %tscm expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: tiscom tall5d tall5d

# ['.' (rune dot %tsdt expq)]
# ++  expq  |.(;~(gunk rope loaf loaf))               ::  wing and two hoons
# FIXED: tisdot rope5d tall5d tall5d

# ['-' (rune hep %tshp expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: tishep tall5d tall5d

# ['/' (rune fas %tsfs expo)]
# ++  expo  |.(;~(gunk wise loaf loaf))               ::  =;
# FIXED: tisfas wise5d tall5d tall5d

# ['<' (rune gal %tsgl expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: tisgal tall5d tall5d

# ['>' (rune gar %tsgr expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: tisgar tall5d tall5d

# ['^' (rune ket %tskt expt)]
#     ++  expt  |.(;~(gunk wise rope loaf loaf))          ::  =^
# FIXED: tisket wise5d rope5d tall5d tall5d

# ['+' (rune lus %tsls expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: tislus tall5d tall5d

# [';' (rune sem %tssm expo)]
# ++  expo  |.(;~(gunk wise loaf loaf))               ::  =;
# FIXED: tissem wise5d tall5d tall5d

# ['~' (rune sig %tssg expi)]
# ++  expi  |.((butt ;~(gunk loaf hank)))             ::  one or more hoons
norm5d ::= tallTissig
tallTissig ::= (- TIS SIG GAP -) tall5d (- GAP -) tall5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideTissig
wideTissig ::= (- TIS SIG PEL -) wide5d (- ACE -) wide5dSeq (- PER -)

# ['*' (rune tar %tstr expl)]
# ++  expl  |.(;~(gunk (stag ~ sym) loaf loaf))       ::  term, two hoons
# FIXED: tistar SYM4K tall5d tall5d

# ['?' (rune wut %tswt expw)]
#     ++  expw  |.(;~(gunk rope loaf loaf loaf))          ::  wing and three hoons
# FIXED: tiswut rope5d tall5d tall5d tall5d

# ['|' (rune bar %wtbr exps)]
#  ++  exps  |.((butt hank))                           ::  closed gapped hoons
norm5d ::= tallWutbar
tallWutbar ::= (- WUT BAR GAP -) tall5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideWutbar
wideWutbar ::= (- WUT BAR PEL -) wide5dSeq (- PER -)

# [':' (rune col %wtcl expc)]
# ++  expc  |.(;~(gunk loaf loaf loaf))               ::  three hoons
# FIXED: wutcol tall5d tall5d tall5d

# ['.' (rune dot %wtdt expc)]
# ++  expc  |.(;~(gunk loaf loaf loaf))               ::  three hoons
# FIXED: wutdot tall5d tall5d tall5d

# ['<' (rune gal %wtgl expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: wutgal tall5d tall5d

# ['>' (rune gar %wtgr expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: wutgar tall5d tall5d

# ['-' ;~(pfix hep (toad tkhp))]
# ++  tkhp  |.  %+  cook  |=  {a/tiki b/(list (pair root hoon))}
#			(~(wthp ah a) b)
#	      (butt ;~(gunk teak ruck))
norm5d ::= tallWuthep
tallWuthep ::= (- WUT HEP GAP -) teak5d (- GAP -) ruck5d (- GAP TIS TIS -)
wideNorm5d ::= wideWuthep
wideWuthep ::= (- WUT HEP PEL -) teak5d (- ACE -) wideRuck5d (- PER -)

# ['^' ;~(pfix ket (toad tkkt))]
# ++  tkkt  |.  %+  cook  |=  {a/tiki b/hoon c/hoon}
#			(~(wtkt ah a) b c)
#	      ;~(gunk teak loaf loaf)
# FIXED: wutket teak5d tall5d tall5d

# ['+' ;~(pfix lus (toad tkls))]
# ++  tkls  |.  %+  cook  |=  {a/tiki b/hoon c/(list (pair root hoon))}
# 			(~(wtls ah a) b c)
#	      (butt ;~(gunk teak loaf ruck))
norm5d ::= tallWutlus
tallWutlus ::= (- WUT LUS GAP -) teak5d (- GAP -) tall5d (- GAP -) ruck5d (- GAP TIS TIS -)
wideNorm5d ::= wideWutlus
wideWutlus ::= (- WUT LUS PEL -) teak5d (- ACE -) tall5d (- ACE -) wideRuck5d (- PAR -)

# ['&' (rune pam %wtpm exps)]
#  ++  exps  |.((butt hank))                           ::  closed gapped hoons
norm5d ::= tallWutpam
tallWutpam ::= (- WUT PAM GAP -) tall5dSeq (- GAP TIS TIS -)
wideNorm5d ::= wideWutpam
wideWutpam ::= (- WUT PAM PEL -) wide5dSeq (- PER -)

# ['@' ;~(pfix pat (toad tkpt))]
# ++  tkpt  |.  %+  cook  |=  {a/tiki b/hoon c/hoon}
#			(~(wtpt ah a) b c)
#	      ;~(gunk teak loaf loaf)
# FIXED: wutpat teak5d tall5d tall5d

# ['~' ;~(pfix sig (toad tksg))]
# ++  tksg  |.  %+  cook  |=  {a/tiki b/hoon c/hoon}
# 			  (~(wtsg ah a) b c)
# 		;~(gunk teak loaf loaf)
# FIXED: wutsig teak5d tall5d tall5d

# ['=' ;~(pfix tis (toad tkts))]
#    ++  tkts  |.  %+  cook  |=  {a/root b/tiki}
#                            (~(wtts ah b) a)
#                  ;~(gunk loan teak)
# FIXED: wuttis till5d teak5d

# ['!' (rune zap %wtzp expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: wutzap tall5d

# [':' ;~(pfix col (toad expz))]
#    ++  expz  |.(loaf(bug &))                           ::  hoon with tracing
# FIXED: zapcol tall5d

# ['.' ;~(pfix dot (toad |.(loaf(bug |))))]
# FIXED: zapdot tall5d

# [',' (rune com %zpcm expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED: zapcom tall5d tall5d

# ['>' (rune gar %zpgr expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# FIXED: zapgar tall5d

# [';' (rune sem %zpsm expb)]
# ++  expb  |.(;~(gunk loaf loaf))                    ::  two hoons
# FIXED zapsem tall5d tall5d

# ['=' (rune tis %zpts expa)]
# ++  expa  |.(loaf)                                  ::  one hoon
# NOT FIXED: zaptis tall5d
# Write this out to allow for parsing binary !=(a b), which is different
norm5d ::= tallZaptis
wideNorm5d ::= wideZaptis
tallZaptis ::= (- ZAP TIS GAP -) tall5d
wideZaptis ::= (- ZAP TIS PEL -) wide5d (- PER -)

# ['?' (rune wut %zpwt hinh)]
# ++  hinh  |.                                        ::  1/2 numbers, hoon
#         ;~  gunk
#           ;~  pose
#             dem
#             (ifix [sel ser] ;~(plug dem ;~(pfix ace dem)))
#           ==
#           loaf
#         ==
norm5d ::= tallZapwut
wideNorm5d ::= wideZapwut
tallZapwut ::= (- ZAPWUTGAP -) dem4k (- GAP -) tall5d
tallZapwut ::= (- ZAPWUTGAP SEL -) dem4k (- ACE -) dem4k (- SER GAP -) tall5d
wideZapwut ::= (- ZAPWUTPEL -) dem4k (- ACE -) wide5d
wideZapwut ::= (- ZAPWUTPEL SEL -) dem4k (- ACE -) dem4k (- SER ACE -) wide5d


# Multi-character lexemes to allow zapwut rune to take
# priority over irregular unary zap, especially
# "!?", which is unary zap followed by wut
ZAPWUTGAP ~ zap4h wut4h gap4k
ZAPWUTPEL ~ zap4h wut4h gap4k

# zapzap (= crash) is implemented in scat5d

# 5d library: poor

poor5d ::= gash5d
poor5d ::= gash5d CEN4H porc5d

# 5d library: porc

porc5d ::= optCen4hSeq FAS gash5d
optCen4hSeq ::= # empty
optCen4hSeq ::= CEN4H_SEQ
CEN4H_SEQ ~ cen4h+

# 5d library: rood
# rood is the path parser

rood5d ::= FAS poor5d

# 5d library: rope

# the wing type is parsed by the rope(5d)
rope5d ::= limb+ separator=>DOT proper=>1
limb ::= COM
limb ::= optKets BUC
limb ::= optKets SYM4K
optKets ::= KET*
limb ::= BAR4H DIM4J
limb ::= LUS DIM4J
limb ::= PAM4H DIM4J
limb ::= VEN4K
limb ::= DOT

# 5d library: rick

rick5d ::= rick5dJog+ separator=>GAP proper=>1
rick5dJog ::= rope5d (- GAP -) tall5d

wideRick5d ::= wideRick5dJog+ separator=>commaAce proper=>1
wideRick5dJog ::= rope5d (- ACE -) wide5d

# 5d library: ruck

ruck5d ::= ruck5dJog+ separator=>GAP proper=>1
ruck5dJog ::= till5d (- GAP -) tall5d

wideRuck5d ::= wideRuck5dJog+ separator=>commaAce proper=>1
wideRuck5dJog ::= till5d (- ACE -) wide5d
commaAce ::= COM ACE

# 5d library: rump

rump5d ::= rope5d
rump5d ::= rope5d wede5d

# 5d library: rupl
# rupl(5d) seems to implement the hoon '[...]', ~[...], and [...]~
# syntaxes.

rupl5d ::= circumBracket
rupl5d ::= sigCircumBracket
rupl5d ::= circumBracketSig
rupl5d ::= sigCircumBracketSig

# Initial ACE of tall form is intended -- it
# distinguishes this from lute(5d)
circumBracket ::= (- SEL ACE -) tall5dSeq (- GAP SER -)
circumBracket ::= (- SEL -) wide5dSeq (- SER -)
sigCircumBracket ::= (- SIG SEL ACE -) tall5dSeq (- GAP SER -)
sigCircumBracket ::= (- SIG SEL -) wide5dSeq (- SER -)
circumBracketSig ::= (- SEL ACE -) tall5dSeq (- GAP SER SIG -)
circumBracketSig ::= (- SEL -) wide5dSeq (- SER SIG -)
sigCircumBracketSig ::= (- SIG SEL ACE -) tall5dSeq (- GAP SER SIG -)
sigCircumBracketSig ::= (- SIG SEL -) wide5dSeq (- SER SIG -)

# 5d library: sail

sailApex5d ::= (- SEM -) tallTopSail
wideSailApex5d ::= (- SEM -) wideTopSail

tallTopSail ::= ACES optWideQuoteInnards rank=>100
tallTopSail ::= scriptOrStyle scriptStyleTail rank=>80
tallTopSail ::= tallElem rank=>70
tallTopSail ::= wideQuote rank=>60
tallTopSail ::= (- TIS -) tallTailOfTop rank=>50
tallTopSail ::= (- GAR GAP -) CRAM rank=>40
tallTopSail ::= tunaMode (- GAP -) tall5d rank=>30
# TODO: can tallTopSail (= tall-top ) also be empty?

event '^CRAM' = predicted CRAM
CRAM ~ unicorn # supplied by a combinator

wideTopSail ::= wideQuote rank=>20
wideTopSail ::= wideParenElems rank=>10
wideTopSail ::= tagHead wideTail rank=>0

tallElem ::= tagHead optTallAttrs tallTailOfElem

tagHead ::= aMane optTagHeadInitial optTagHeadKernel optTagHeadFinal optWideAttrs
optTagHeadInitial ::= # empty
optTagHeadInitial ::= tagHeadInitial
tagHeadInitial ::= # empty
tagHeadInitial ::= HAX SYM4K
optTagHeadKernel ::= # empty
optTagHeadKernel ::= tagHeadKernel
tagHeadKernel ::= tagHeadKernelElements
tagHeadKernelElements ::= tagHeadKernelElement+
tagHeadKernelElement ::= DOT SYM4K
optTagHeadFinal ::= # empty
optTagHeadFinal ::= tagHeadFinal
tagHeadFinal ::= FAS soil5d
tagHeadFinal ::= PAT soil5d

optTallAttrs ::= # empty
optTallAttrs ::= tallAttributes
tallAttributes ::= tallAttribute+
tallAttribute ::= (- GAP TIS -) aMane (- GAP -) hopefullyQuote

tallTailCommon ::= # empty
tallTailCommon ::= SEM
tallTailCommon ::= COL wrappedElems
tallTailCommon ::= COL ACE optWideQuoteInnards

tallTailOfTop ::= tallTailCommon
tallTailOfTop ::= tallKidsOfTop (- GAP TIS TIS -)

tallTailOfElem ::= tallTailCommon
tallTailOfElem ::= tallKidsOfElem (- GAP TIS TIS -)

# hoon.hoon seems to allow "cram" items anywhere "tall-kids"
# occurs -- not just after SEMTIS (;=).  And it defines them
# as kids, so multiple "cram" items may occur after a SEMTIS.
# This would be hard to implement here, and I wonder if it does
# not cause problems in hoon.hoon.
#
# This implements what (I think) was the intent: "cram" items
# only after SEMTIS or SEMGAR (;>), and only one -- not a
# sequence of them.  This does pass the test.

# <tallKid> includes its preceding gap -kids to allow lookahead
# to differentiate among the <tallKid> choices.
#
# <GAP_SEM>, if a semi-colon is the next non-whitespace character,
# will beat <GAP> in LATM.
#
tallKidsOfTop  ::= (- GAP_SEM -) tallTopKidSeq rank=>20
tallKidsOfTop  ::= (- GAP -) CRAM rank=>0
tallTopKidSeq  ::= tallTopSail+ separator=>GAP_SEM proper=>1
GAP_SEM ~ gap4k sem4h

tallKidsOfElem ::= tallKidOfElem+
tallKidOfElem  ::= (- GAP_SEM -) tallTopSail

wideTail ::= # empty
wideTail ::= SEM
wideTail ::= COL wrappedElems

# wideParenElems are produced by <wideTopSail>
# wrappedElems ::= wideParenElems
wrappedElems ::= qut4k
wrappedElems ::= wideTopSail

wideParenElems ::= (- PEL -) (- PER -)
wideParenElems ::= (- PEL -) wideInnerTops (- PER -)

wideInnerTops ::= wideInnerTop+ separator=>ACE proper=>1
wideInnerTop  ::= wideTopSail
wideInnerTop  ::= tunaMode wide5d

tunaMode ::= HEP | LUS | TAR | CEN

scriptOrStyle ::= 'script' wideAttrs
scriptOrStyle ::= 'style' wideAttrs

optWideAttrs ::= # empty
optWideAttrs ::= wideAttrs
wideAttrs ::= (- PEL PER -)
wideAttrs ::= (- PEL -) wideAttrBody (- PER -)
wideAttrBody ::= wideAttribute+ separator=>commaAce proper=>1
wideAttribute ::= aMane (- ACE -) hopefullyQuote

aMane ::= SYM4K
aMane ::= SYM4K CAB SYM4K

# TODO: hoon.hoon comment expresses dissatisfaction at
# this solution
hopefullyQuote ::= wide5d

scriptStyleTail ::= (- GAP -) scriptStyleTailElements (- GAP TIS TIS -)
scriptStyleTailElements ::= scriptStyleTailElement+ separator=>GAP proper=>1
scriptStyleTailElement ::= (- SEM -) ACE PRN4I_SEQ
scriptStyleTailElement ::= (- SEM -)

wideQuote ::= (- DOQ -) optWideQuoteInnards (- DOQ -)
# TODO: Triple double quote form of wide-quote NYI

optWideQuoteInnards ::=
optWideQuoteInnards ::= optWideQuoteEmbedFreeStretch
optWideQuoteInnards ::= wideQuoteEmbedTerminatedStretches optWideQuoteEmbedFreeStretch
optWideQuoteEmbedFreeStretch ::=
optWideQuoteEmbedFreeStretch ::= wideQuoteEmbedFreeStretch
wideQuoteEmbedFreeStretch ::= wideQuoteEmbedFreeElement+
wideQuoteEmbedFreeElement ::= <ESCAPED_WIDE_INNARD_CHAR>
wideQuoteEmbedFreeElement ::= <NORMAL_WIDE_INNARD_CHARS>

<ESCAPED_WIDE_INNARD_CHAR> ~
  bas4h hep4h | bas4h lus4h | bas4h tar4h | bas4h cen4h |
  bas4h sem4h | bas4h kel4h |
  bas4h bas4h | bas4h doq4h | bas4h bix4j

# All the printable (non-control) characters except
# doq (x22), sem (x3b), bas (x5c) and kel (x7b)
# For efficiency we want to slurp in as many "normal"
# characters at once as we can.
<NORMAL_WIDE_INNARD_CHARS> ~ unescapedWideInnardsChar+
unescapedWideInnardsChar ~ [\x20-\x21\x23-\x3a\x3c-\x5b\x5d-\x7a\x7c-\x7e\x80-\xff]

wideQuoteEmbedTerminatedStretches ::= wideQuoteEmbedTerminatedStretch+
wideQuoteEmbedTerminatedStretch ::= optWideQuoteEmbedFreeStretch (- SEM -) wideBracketedElem rank=>30
wideQuoteEmbedTerminatedStretch ::= optWideQuoteEmbedFreeStretch tunaMode sump5d rank=>20
wideQuoteEmbedTerminatedStretch ::= optWideQuoteEmbedFreeStretch sump5d rank=>10

wideBracketedElem ::= (- KEL -) tagHead wideElems (- KER -)

wideElems ::=
wideElems ::= <sailWideElements>
<sailWideElements> ::= <sailWideElement>+
<sailWideElement> ::= (- ACE -) wideInnerTop

# 5d library: scad

# scad(5d) implements the irregular mold syntaxes

# Cases given in hoon.hoon order.  Unfortunately
# not the same as the order in scat(5d).

# '_'
# Same as scat(5d)
scad5d ::= moldPrefixCab
moldPrefixCab ::= (- CAB -) wide5d

# ','
# Differs from scat(5d)
scad5d ::= moldPrefixCom
moldPrefixCom ::= (- COM -) wide5d

# '$'
# Differs from scat(5d)
scad5d ::= moldBucbuc
moldBucbuc ::= (- BUC BUC -)

scad5d ::= moldBucpam
moldBucpam ::= (- BUC PAM -)

scad5d ::= moldBucbar
moldBucbar ::= (- BUC BAR -)

scad5d ::= moldBucSingleString
moldBucSingleString ::= (- BUC -) qut4k

scad5d ::= moldBucNuck4l
moldBucNuck4l ::= (- BUC -) nuck4l

scad5d ::= rump5d

# '%'
# Differs from scat(5d)
scad5d ::= moldCenbuc
moldCenbuc ::= CEN BUC

scad5d ::= moldCenpam
moldCenpam ::= CEN PAM

scad5d ::= moldCenbar
moldCenbar ::= CEN BAR

scad5d ::= moldCenSingleString
moldCenSingleString ::= (- CEN -) qut4k

scad5d ::= moldCenNuck4l
moldCenNuck4l ::= CEN nuck4l

# '('
# Differs from scat(5d)
scad5d ::= moldCircumParen
moldCircumParen ::= (- PEL -) wide5d (- ACE -) wyde5dSeq (- PER -)
moldCircumParen ::= (- PEL -) wide5d (- PER -)

# '{'
# Same as scat(5d)
scad5d ::= moldCircumBrace
moldCircumBrace ::= (- KEL -) wyde5dSeq (- KER -)

# '['
# Differs from scat(5d)
scad5d ::= moldCircumBracket
moldCircumBracket ::= (- SEL -) wyde5dSeq (- SER -)

# '*'
# Subset of scat(5d)
scad5d ::= moldTar
moldTar ::= TAR

# '@'
# Same as scat(5d)
scad5d ::= moldAura
moldAura ::= PAT mota5d

# '?'
# Same as scat(5d)
scad5d ::= moldPrefixWut
moldPrefixWut ::= (- WUT PEL -) wyde5dSeq (- PER -)

scad5d ::= moldWut
moldWut ::= WUT

# '~'
# Differs from scat(5d)
scad5d ::= moldSig
moldSig ::= SIG

# '^'
# Differs from scat(5d)
scad5d ::= moldKet
moldKet ::= KET

# <moldInfixCol> can start with either KET (^) or lowercase char
# This is scab(5d)
scad5d ::= moldInfixCol
moldInfixCol ::= rope5d (- COL -) moldInfixCol2
moldInfixCol2 ::= rope5d+ separator=>COL proper=>1

# '='
# Differs from scat(5d)
scad5d ::= moldPrefixTis
moldPrefixTis ::= (- TIS -) wyde5d (- PER -) action=>MarpaX::Hoonlint::YAHC::deprecated

# ['a' 'z']
# Differs from scat(5d)
# for scab(5d), see the KET subcase
scad5d ::= moldInfixFas rank=>1
scad5d ::= moldInfixTis rank=>1
moldInfixFas ::= SYM4K FAS wyde5d
moldInfixTis ::= SYM4K TIS wyde5d

# End of scad(5d)

# 5d library: scat
# scat(5d) implements the irregular hoon syntaxes

# For convenience in referring back
# to hoon.hoon, I use scat(5d)'s order, as is.
# Unfortunately this is not in the same
# order as in scad.

# ','
# Differs from scad(5)
# For rope(5d), see subcase ['a' 'z'] and rump(5d)
wideBuccol ::= (- COM SEL -) wyde5dSeq (- SER -)

# '!'
# Not in scad(5)
scat5d ::= prefixZap
prefixZap ::= (- ZAP -) wide5d
scat5d ::= wideZapzap
wideZapzap ~ zap4h zap4h

# '_'
# Same as scad(5)
scat5d ::= prefixCab
prefixCab ::= (- CAB -) wide5d

# '$'
# For rump, see subcase ['a' 'z']
# Differs from scad(5)
scat5d ::= bucBuc
scat5d ::= bucPam
scat5d ::= bucBar
scat5d ::= dollarTerm
bucBuc ::= BUC4H BUC4H
bucPam ::= BUC4H PAM4H
bucBar ::= BUC4H BAR4H
dollarTerm ::= BUC4H qut4k
dollarTerm ::= BUC4H nuck4l

# '%'
# Differs from scad(5)
scat5d ::= cenPath
scat5d ::= cenBuc
scat5d ::= cenPam
scat5d ::= cenBar
scat5d ::= cenTerm
scat5d ::= cenDirectories
cenPath ::= CEN4H porc5d
cenBuc ::= CEN4H BUC4H
cenPam ::= CEN4H PAM4H
cenBar ::= CEN4H BAR4H
cenTerm ::= CEN4H qut4k
cenTerm ::= CEN4H nuck4l
cenDirectories ::= CEN4H+

# '&'
# Not in scad(5)
# For rope(5d), see subcase ['a' 'z'] and rump(5d)
scat5d ::= prefixPam
scat5d ::= pamPlusPrefix
scat5d ::= soloPam
prefixPam ::= (- PAM4H PEL -) wide5dSeq (- PER -)
pamPlusPrefix ::= (- PAM4H -) wede5d
soloPam ::= PAM4H

# '\''
# Not in scad(5)
scat5d ::= qut4k

# '('
# Differs from scad(5)
# See https://raw.githubusercontent.com/urbit/old-urbit.org/master/doc/hoon/lan/irregular.markdown
# and cenhep in https://urbit.org/docs/hoon/irregular/
scat5d ::= circumParen1
scat5d ::= circumParen2
circumParen1 ::= (- PEL -) wide5d (- PER -)
circumParen2 ::= (- PEL -) wide5d (- ACE -) wide5dSeq (- PER -)

# '{'
# Same as scad(5)
scat5d ::= circumBraces
circumBraces ::= (- KEL -) wyde5dSeq (- KER -)

# '*'
# Superset of scad(5)
scat5d ::= prefixTar
scat5d ::= soloTar
prefixTar ::= TAR wyde5d
soloTar ::= TAR

# '@'
# Same as scad(5)
scat5d ::= aura
aura ::= PAT mota5d

# '+'
# Not in scad(5)
# For rope(5d) see ['a' 'z'] subcase and rump(5d)
scat5d ::= wideDotlus
scat5d ::= lusSoilSeq
wideDotlus ::= (- LUS PEL -) wide5d (- PER -)
lusSoilSeq ::= lusSolSeqItem+ separator=>DOG4I proper=>1
lusSolSeqItem ::= LUS soil5d

# '-'
# For rope(5d) see ['a' 'z'] subcase and rump(5d)
# Not in scad(5)
scat5d ::= tash4l
scat5d ::= hepSoilSeq
hepSoilSeq ::= hepSolSeqItem+ separator=>DOG4I proper=>1
hepSolSeqItem ::= HEP soil5d

# '.'
# For rope(5d) see ['a' 'z'] subcase and rump(5d)
# Not in scad(5)
scat5d ::= DOT perd4l

# ['0' '9']
# Not in scad(5)
# This subcase handles infix expressions
# starting with a digit.
scat5d ::= bisk4l
scat5d ::= bisk4l wede5d

# ':'
# Not in scad(5)
scat5d ::= circumColParen
scat5d ::= prefixColFas
circumColParen ::= (- COL PEL -) wide5dSeq (- PER -)
prefixColFas ::= (- COL FAS -) wide5d

# '='
# Differs from scad(5)
tallDottis ::= (- TIS GAP -) tall5d
scat5d ::= irrDottis
irrDottis ::= (- TIS PEL -) wide5d (- ACE -) wide5d (- PER -)
irrDottis ::= (- TIS PEL -) wide5d (- PER -)

# '?'
# Same as scad(5)
scat5d ::= circumWutParen
scat5d ::= soloWut
circumWutParen ::= (- WUT PEL -) wyde5dSeq (- PER -)
soloWut ::= WUT

# '['
# Differs from scad(5)
scat5d ::= rupl5d

# '^'
# Differs from scad(5)
# For rope(5d) see ['a' 'z'] subcase and rump(5d)
scat5d ::= soloKet
soloKet ::= KET

# '`'
# Not in scad(5)
scat5d ::= prefixTecChoices
prefixTecChoices ::= prefixTecAura rank=>5
prefixTecChoices ::= prefixTecTar rank=>4
prefixTecChoices ::= prefixTecMold rank=>3
prefixTecChoices ::= prefixTecHoon rank=>2
prefixTecChoices ::= prefixSoloTec rank=>1
prefixTecAura ::= (- TEC PAT -) mota5d (- TEC -) wide5d
prefixTecTar ::= (- TEC TAR TEC -) wide5d
prefixTecMold ::= (- TEC -) wyde5d (- TEC -) wide5d
prefixTecHoon ::= (- TEC LUS -) wide5d (- TEC -) wide5d
prefixSoloTec ::= (- TEC -) wide5d

# '"'
# Not in scad(5)
scat5d ::= infixDot
infixDot ::= soil5d+ separator=>DOG4I proper=>1

# ['a' 'z']
# Differs from scad(5)
scat5d ::= rump5d

# '|'
# Not in scad(5)
# For rope(5d) see ['a' 'z'] subcase and rump(5d)
scat5d ::= prefixBar rank=>1
scat5d ::= circumBarParen rank=>1
scat5d ::= soloBar
prefixBar ::= (- BAR4H -) wede5d
circumBarParen ::= (- BAR4H PEL -) wide5dSeq (- PER -)
soloBar ::= BAR4H

# '~'
# Differs from scad(5)
# See also rupl(5d) in the '[' subcase
scat5d ::= circumSigParen
scat5d ::= (- SIG -) twid4l
scat5d ::= (- SIG -) wede5d
scat5d ::= soloSig
circumSigParen ::= (- SIG PEL -) rope5d (- ACE -) wide5d (- ACE -) wide5dSeq (- PER -)
soloSig ::= SIG

# This seems to be redundant with rupl(5d)
# scat5d ::= circumSigBracket
# circumSigBracket ::= (- SIG SEL -) wide5dSeq (- SER -)

# '/'
# Not in scad(5)
scat5d ::= rood5d

# '<'
# Not in scad(5)
scat5d ::= circumGalgar
circumGalgar ::= (- GAL -) wide5dSeq (- GAR -)

# '>'
# Not in scad(5)
scat5d ::= circumGargal
circumGargal ::= (- GAR -) wide5dSeq (- GAL -)

# 5d library: soil

soil5d ::= doubleQuoteString
doubleQuoteString ::= (- DOQ -) <doubleQuoteCord> (- DOQ -)
<doubleQuoteCord> ::= <doubleQuoteElement>*
<doubleQuoteElement> ::= <UNESCAPED_DOUBLE_QUOTE_CHARS>
<doubleQuoteElement> ::= <ESCAPED_DOUBLE_QUOTE_CHAR>
<doubleQuoteElement> ::= sump5d

# All the printable (non-control) characters except
# bas (x5c) kel (x7b) and doq (x22)
<UNESCAPED_DOUBLE_QUOTE_CHARS> ~ unescapedDoubleQuoteChar+
unescapedDoubleQuoteChar ~ [\x20-\x21\x23-\x5b\x5d-\x7a\x7c-\x7e\x80-\xff]
<ESCAPED_DOUBLE_QUOTE_CHAR> ~ bas4h bas4h | bas4h doq4h | bas4h kel4h | bas4h bix4j

soil5d ::= <TRIPLE_DOUBLE_QUOTE_STRING>
soil5d ::= TRIPLE_DOUBLE_START
:lexeme ~ TRIPLE_DOUBLE_START event=>tripleDoubleQuote pause=>before
TRIPLE_DOUBLE_START ~ doq4h doq4h doq4h nl
<TRIPLE_DOUBLE_QUOTE_STRING> ~ unicorn

sump5d ::= KEL wide5dSeq KER

# 5d library: teak

# teak is
#
# 1) a mold, if possible, hoon otherwise,
#
# 2) an assignment to <SYM4K>, which is again, of a mold,
# if possible, of a hoon otherwise

teak5d ::= teakChoice
teakChoice ::= (- KET TIS GAP -) SYM4K (- GAP -) rope5d rank=>2
teakChoice ::= (- KET TIS GAP -) SYM4K (- GAP -) tall5d rank=>1
teakChoice ::= tall5d rank=>1
teakChoice ::= wideTeak5d rank=>0
wideTeak5d ::= wideTeakChoice
wideTeakChoice ::= SYM4K (- TIS -) rope5d rank=>2
wideTeakChoice ::= rope5d rank=>2
wideTeakChoice ::= SYM4K (- TIS -) wide5d rank=>1
wideTeakChoice ::= wide5d rank=>1

# 5d library: wasp
# Always occurs with wisp(5d), which must be tall,
# so wisp is always tall

wasp5d ::= # empty
wasp5d ::= (- LUS TAR GAP -) waspElements (- GAP -)
waspElements ::= waspElement+ separator=>GAP proper=>1
waspElement  ::= SYM4K (- GAP -) tall5d

# 5d library: wede

wede5d ::= (- FAS -) wide5d
wede5d ::= (- LUS -) wide5d

# 5d library: wise

wise5d ::= SYM4K
wise5d ::= (- TIS -) wyde5d
wise5d ::= SYM4K (- TIS -) wyde5d
wise5d ::= SYM4K (- FAS -) wyde5d

# 5d library: wisp

wisp5d ::= (- HEP HEP -)
wisp5d ::= whap5d GAP (- HEP HEP -)

# 5d library: whap
# Always tall

whap5d ::= boog5d+ separator=>GAP proper=>1

# End of 5d library

# === HOON FILE ===
:start ::= fordFile

# A hack to allow inaccessible symbols
fordFile ::= UNICORN inaccessible_ok

fordFile ::=
  (- optGay4i -)
  optFordFaswut
  optFordFashep
  optFordFaslus
  optHornSeq
  fordHoopSeq
  (- optGay4i -)

optGay4i ::= # empty
optGay4i ::= GAY4I

optHornSeq ::= # empty
optHornSeq ::= hornSeq
hornSeq ::= horn+ separator=>GAP proper=>1
wideHornSeq ::= wideHorn+ separator=>ACE proper=>1

fordHoopSeq ::= fordHoop+ separator=>GAP proper=>1

fordHoop ::= FAS FAS GAP fordHave rank=>60
fordHoop ::= hornRune rank=>40
fordHoop ::= tall5d rank=>0

fordHave ::= FAS fordHath
fordHath ::= poor5d

fordHive ::= (- FAS -) gash5d
fordHive ::= (- FAS -) gash5d CEN porc5d

# === WHITESPACE ===

optClassicWhitespace ::= # empty
optClassicWhitespace ::= classicWhitespace
classicWhitespace ::= GAP
classicWhitespace ::= ACE

optHorizontalWhitespace ~ horizontalWhitespaceElement*
horizontalWhitespaceElements ~ horizontalWhitespaceElement+
horizontalWhitespaceElement ~ ace

GAP ~ gap4k

gap4k ~ ace horizontalWhitespaceElements # a "wide" gap
gap4k ~ tallGapPrefix optGapLines optHorizontalWhitespace
# The prefix must contain an <NL> to ensure that this *is* a tall gap
tallGapPrefix ~ optHorizontalWhitespace nl
tallGapPrefix ~ optHorizontalWhitespace comment
optGapLines ~ gapLine*
gapLine ~ optHorizontalWhitespace comment
gapLine ~ optHorizontalWhitespace nl

ACES ~ ace+
ACE ~ ace
ace ~ ' '
comment ~ '::' optNonNLs nl

# TODO: Is this treatment of documentation runes OK?
# Documentation decorations treated as comments
comment ~ ':>' optNonNLs nl
comment ~ ':<' optNonNLs nl
comment ~ '+|' optNonNLs nl

inaccessible_ok ::= NL
NL ~ nl
nl ~ [\n]
optNonNLs ~ nonNL*
nonNL ~ [^\n]

wsChars ~ wsChar*
wsChar ~ [ \n]

# @ub   unsigned binary          0b10          (2)
NUMBER ~ binaryNumber
# syn match       hoonNumber        "0b[01]\{1,4\}\%(\.\_s*[01]\{4\}\)*"
binaryNumber ~ '0b' binaryPrefix binaryGroups
binaryPrefix ~ binaryDigit
binaryPrefix ~ binaryDigit binaryDigit
binaryPrefix ~ binaryDigit binaryDigit binaryDigit
binaryPrefix ~ binaryDigit binaryDigit binaryDigit binaryDigit
binaryDigit ~ [01]
binaryGroups ~ binaryGroup*
binaryGroup ~ [.] wsChars binaryDigit binaryDigit binaryDigit binaryDigit

# @uc   bitcoin address          0c1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
# @ud   unsigned decimal         42            (42)
#                                1.420         (1420)
NUMBER ~ decimalNumber
# syn match       hoonNumber        "\d\{1,3\}\%(\.\_s\?\d\{3\}\)*"
decimalNumber ~ decimalPrefix decimalGroups
decimalPrefix ~ decimalDigit
decimalPrefix ~ decimalDigit decimalDigit
decimalPrefix ~ decimalDigit decimalDigit decimalDigit
decimalDigit ~ [0-9]
decimalGroups ~ decimalGroup*
decimalGroup ~ [.] wsChars decimalDigit decimalDigit decimalDigit

# @uv   unsigned base32          0v3ic5h.6urr6
NUMBER ~ vNumber
# syn match       hoonNumber        "0v[0-9a-v]\{1,5\}\%(\.\_s*[0-9a-v]\{5\}\)*"
vNumber ~ '0v' vNumPrefix vNumGroups
vNumPrefix ~ vNumDigit
vNumPrefix ~ vNumDigit vNumDigit
vNumPrefix ~ vNumDigit vNumDigit vNumDigit
vNumPrefix ~ vNumDigit vNumDigit vNumDigit vNumDigit
vNumPrefix ~ vNumDigit vNumDigit vNumDigit vNumDigit vNumDigit
vNumDigit ~ [0-9a-v]
vNumGroups ~ vNumGroup*
vNumGroup ~ [.] wsChars vNumDigit vNumDigit vNumDigit vNumDigit vNumDigit

# @uw   unsigned base64          0wsC5.yrSZC
NUMBER ~ wNumber
# syn match       hoonNumber        "0w[-~0-9a-zA-Z]\{1,5\}\%(\.\_s*[-~0-9a-zA-Z]\{5\}\)*"
wNumber ~ '0w' wNumPrefix wNumGroups
wNumPrefix ~ wNumDigit
wNumPrefix ~ wNumDigit wNumDigit
wNumPrefix ~ wNumDigit wNumDigit wNumDigit
wNumPrefix ~ wNumDigit wNumDigit wNumDigit wNumDigit
wNumPrefix ~ wNumDigit wNumDigit wNumDigit wNumDigit wNumDigit
wNumDigit ~ [-~0-9a-zA-Z]
wNumGroups ~ wNumGroup*
wNumGroup ~ [.] wsChars wNumDigit wNumDigit wNumDigit wNumDigit wNumDigit

# @ux   unsigned hexadecimal     0xcafe.babe
NUMBER ~ hexNumber
# syn match       hoonNumber        "0x\x\{1,4\}\%(\.\_s*\x\{4\}\)*"
hexNumber ~ '0x' hexPrefix hexGroups
hexPrefix ~ hexDigit
hexPrefix ~ hexDigit hexDigit
hexPrefix ~ hexDigit hexDigit hexDigit
hexPrefix ~ hexDigit hexDigit hexDigit hexDigit
hexDigit ~ [0-9a-fA-F]
hexGroups ~ hexGroup*
hexGroup ~ [.] wsChars hexDigit hexDigit hexDigit hexDigit

# === CELLS BY TYPE ==

tall5dSeq ::= tall5d+ separator=>GAP proper=>1
tall5d ::= norm5d rank=>30
tall5d ::= sailApex5d rank=>25
tall5d ::= wideNorm5d rank=>20
tall5d ::= wideLong5d rank=>18
tall5d ::= wideSailApex5d rank=>15
tall5d ::= lute5d rank=>10

# TODO: Precedence needs to be tested

wide5d ::= wide5dChoices
wide5dChoices ::= wideNorm5d rank=>10
wide5dChoices ::= wideLong5d rank=>8
wide5dChoices ::= wideSailApex5d

wide5dSeq ::= wide5d+ separator=>ACE proper=>1

# === FORD RUNES ===

horn ::= hornRune
wideHorn ::= wideHornRune

hornRune ::= wideHornRune

hornRune ::= fordFasbar
fordFasbar ::= (- FAS BAR GAP -) hornSeq (- GAP TIS TIS -)
wideHornRune ::= wideFordFasbar
wideFordFasbar ::= (- FAS BAR PEL -) wideHornSeq (- PER -)

hornRune ::= fordFasbuc
fordFasbuc ::= (- FAS BUC GAP -) tall5d
wideHornRune ::= wideFordFasbuc
wideFordFasbuc ::= (- FAS BUC SEL -) wide5dSeq (- SER -)

hornRune ::= fordFascab
fordFascab ::= (- FAS CAB GAP -) horn
wideHornRune ::= wideFordFascab
wideFordFascab ::= (- FAS CAB -) horn

hornRune ::= fordFascen
fordFascen ::= (- FAS CEN GAP -) horn
wideHornRune ::= wideFordFascen
wideFordFascen ::= (- FAS CEN -) horn

hornRune ::= fordFascol
fordFascol ::= (- FAS COL GAP -) fordHive (- GAP -) horn
wideHornRune ::= wideFordFascol
wideFordFascol ::= (- FAS COL -) fordHive (- COL -) horn

hornRune ::= fordFascom
fordFascom ::= (- FAS COM GAP -) fordFascomBody (- GAP TIS TIS -)
fordFascomBody ::= # empty
fordFascomBody ::= fordFascomElements
fordFascomElements ::= fordFascomElement+ separator=>GAP proper=>1
fordFascomElement ::= (- FAS -) fordHith (- GAP -) horn

hornRune ::= fordFasdot
fordFasdot ::= (- FAS DOT GAP -) optHornSeq (- GAP TIS TIS -)

hornRune ::= fordFashax
fordFashax ::= (- FAS HAX GAP -) horn
wideHornRune ::= wideFordFashax
wideFordFashax ::= (- FAS HAX -) horn

optFordFashep ::= # empty
optFordFashep ::= (- FAS HEP GAP -) fordHoofSeq (- GAP -)

hornRune ::= fordFasket
fordFasket ::= (- FAS KET GAP -) tall5d (- GAP -) horn
wideHornRune ::= wideFordFasket
wideFordFasket ::= (- FAS KET -) wide5d (- KET -) horn

optFordFaslus ::= # empty
optFordFaslus ::= (- FAS LUS GAP -) fordHoofSeq (- GAP -)

hornRune ::= fordFaspam
fordFaspam ::= (- FAS PAM GAP -) SYM4K (- GAP -) horn
wideHornRune ::= wideFordFaspam
wideFordFaspam ::= (- FAS PAM -) faspamSyms horn
faspamSyms ::= faspamSym+
faspamSym ::= SYM4K PAM

hornRune ::= fordFassem
fordFassem ::= (- FAS SEM GAP -) tall5d (- GAP -) horn
wideHornRune ::= wideFordFassem
wideFordFassem ::= (- FAS SEM -) wide5d (- SEM -) horn

hornRune ::= fordFassig
fordFassig ::= (- FAS SIG GAP -) tall5d
wideHornRune ::= wideFordFassig
wideFordFassig ::= (- FAS SIG SEL -) wide5dSeq (- SER -)

hornRune ::= fordFastis
fordFastis ::= (- FASTISGAP -) SYM4K (- GAP -) horn
wideHornRune ::= wideFordFastis
wideFordFastis ::= (- FAS TIS -) SYM4K '=' wideHorn
# Long lexeme to allow ford rune to take priority
# over /= path
FASTISGAP ~ fas4h tis4h gap4k

optFordFaswut ::= # empty
optFordFaswut ::= fordFaswut
fordFaswut ::= (- FAS WUT GAP -) DIT4K_SEQ (- GAP -)

wideHornRune ::= wideFaszap
wideFaszap ::= (- FAS ZAP -) SYM4K (- FAS -)

commaWS ::= COM
commaWS ::= COM optClassicWhitespace

fordHith ::= optFordHithElements
optFordHithElements ::= hasp5d* separator=>FAS proper=>1

fordHoofSeq ::= fordHoof+ separator=>commaWS proper=>1
fordHoof ::= TAR fordHoot
fordHoof ::= fordHoot

fordHoot ::= SYM4K
fordHoot ::= SYM4K (- FAS -) fordHoodCase (- FAS -) fordHoodShip

fordHoodCase ::= nuck4l

fordHoodShip ::= SIG fed4j

wideHornRune ::= wideCircumFas
wideCircumFas ::= (- FAS -) SYM4K (- FAS  -)