package CParse::Parser::Perl;

use 5.6.0;
use strict;
use warnings;

no warnings "recursion";

use Carp;

use CParse::Attribute;
use CParse::AttributeList;
use CParse::Declaration;
use CParse::Declarator;
use CParse::Declarator::Array;
use CParse::Declarator::Direct;
use CParse::Declarator::Function;
use CParse::Enum;
use CParse::EnumRef;
use CParse::Enumerator;
use CParse::Extension;
use CParse::Function;
use CParse::FunctionSpecifier;
use CParse::Op;
use CParse::Op::Add;
use CParse::Op::ArraySubscript;
use CParse::Op::Assign;
use CParse::Op::BitAnd;
use CParse::Op::BitOr;
use CParse::Op::BitXor;
use CParse::Op::BoolAnd;
use CParse::Op::BoolOr;
use CParse::Op::Cast;
use CParse::Op::Call;
use CParse::Op::Conditional;
use CParse::Op::Equal;
use CParse::Op::Expression;
use CParse::Op::Member;
use CParse::Op::MemberIndirect;
use CParse::Op::Multiply;
use CParse::Op::Preinc;
use CParse::Op::Predec;
use CParse::Op::Postinc;
use CParse::Op::Postdec;
use CParse::Op::Postfix;
use CParse::Op::Relation;
use CParse::Op::Shift;
use CParse::Op::Alignof;
use CParse::Op::Sizeof;
use CParse::Op::SizeofExpr;
use CParse::Op::Unary;
use CParse::ParameterDeclaration;
use CParse::Pointer;
use CParse::StorageClass;
use CParse::Struct;
use CParse::StructDeclaration;
use CParse::StructDeclarator;
use CParse::StructRef;
use CParse::TypeName;
use CParse::TypeQualifier;
use CParse::TypeSpecifier;
use CParse::Union;
use CParse::UnionRef;

use CParse::Parser::Token::Keyword;
use CParse::Parser::Token::Identifier;
use CParse::Parser::Token::Integer;
use CParse::Parser::Token::Float;
use CParse::Parser::Token::Character;
use CParse::Parser::Token::String;
use CParse::Parser::Token::Punctuator;

sub new
  {
    my $this = shift;
    my $class = ref($this) || $this;

    my $self = {
               };
    bless $self, $class;
    return $self;
  }

sub unit
  {
    my $self = shift;
    my $data = shift;
    my $linemap = shift;

    $self->{data} = $data;
    $self->{linemap} = $linemap;
    $self->{line} = 0;
    $self->{pos} = 0;
    $self->{errors} = 0;
    $self->{commit} = 0;
    $self->{skip_errors} = 0;
    $self->{token_queue} = [];
    $self->{trying_tokens} = [];

    my @external_decls;

    DECL: while (1)
      {
        if ($self->no_data_left)
          {
            last;
          }

        my $decl;

        foreach my $thing (qw/declaration function/)
          {
            $self->{skip_errors} = 0;
            $decl = $self->try_parse($thing);
            if ($decl)
              {
                my $data_line = $self->{trying_tokens}[0]->line;
                my $data_pos = $self->{trying_tokens}[0]->pos;

                my $file = $self->{linemap}{$data_line}{file} || "<unknown>";
                my $line = $self->{linemap}{$data_line}{line} || "<unknown>";

                $decl->set_location($file, $line, $data_pos);

                $self->{trying_tokens} = [];
                push @external_decls, $decl;
                next DECL;
              }

            last if $self->{errors};
          }

        die if scalar @{$self->{trying_tokens}};

        $self->syntax_error;
        last;
      }

    return undef if $self->{errors};
    return \@external_decls;
  }

sub error
  {
    my $self = shift;
    my $msg = shift;

    return if $self->{skip_errors};

    my $data_line = scalar @{$self->{token_queue}} ? $self->{token_queue}[0]->line : $self->{line};
    my $data_pos = scalar @{$self->{token_queue}} ? $self->{token_queue}[0]->pos : $self->{pos};

    my $file = $self->{linemap}{$data_line}{file} || "<unknown>";
    my $line = $self->{linemap}{$data_line}{line} || "<unknown>";
    print STDERR "$file:$line:$data_pos: $msg\n";

    foreach my $l ($data_line - 2 .. $data_line + 2)
      {
        next if $l < 0;
        last if $l >= scalar @{$self->{data}};

        print STDERR $self->{data}[$l] . "\n";
        print STDERR ' ' x $data_pos . "^-- Here\n" if $l == $data_line;
      }

    $self->{errors}++;
  }

sub syntax_error
  {
    my $self = shift;
    my $thing = shift;
    if (defined $thing)
      {
        $self->error("syntax error while trying to parse $thing");
      }
    else
      {
        $self->error("syntax error");
      }
  }

sub no_data_left
  {
    my $self = shift;
    return 0 if scalar @{$self->{token_queue}};
    my $token = $self->next_token;
    if ($token)
      {
        $self->retry_tokens($token);
        return 0;
      }
    else
      {
        return 1;
      }
  }

sub next_token
  {
    my $self = shift;

    if (scalar @{$self->{token_queue}})
      {
        return shift @{$self->{token_queue}};
      }

    while (1)
      {
        if ($self->{line} >= scalar @{$self->{data}})
          {
            # No lines left, this is EOF
            return undef;
          }

        my $line = substr $self->{data}[$self->{line}], $self->{pos};

        # Try to move to the next line
        if (length $line == 0)
          {
            $self->{line}++;
            $self->{pos} = 0;
            next;
          }

        # Try to consume whitespace
        if ($line =~ /^(\s+)/)
          {
            $self->{pos} += length $1;
            next;
          }

        # Try for a keyword
        if ($line =~ /^(auto|break|case|char|const|continue|default|do|double|
                        else|enum|extern|float|for|goto|if|inline|int|long|
                        register|restrict|return|short|signed|sizeof|static|
                        struct|switch|typedef|union|unsigned|void|volatile|
                        while|_Bool|_Complex|_Imaginary|
                        __const|__restrict|__volatile|__const__|__restrict__|
                        __volatile__|__attribute__|__attribute|__inline|
                        __inline__|__extension__|__alignof__|asm|__asm__)\b/x)
          {
            my $len = length $1;
            my $token = new CParse::Parser::Token::Keyword $1, $self->{line}, $self->{pos};
            $self->{pos} += $len;
            return $token;
          }

        # Try for an identifier
        if ($line =~ /^((?:[A-Za-z_]|\\u[[:xdigit:]]{4}|\\U[[:xdigit:]]{8})(?:\w|\\u[[:xdigit:]]{4}|\\U[[:xdigit:]]{8})*)/)
          {
            my $len = length $1;
            my $token = new CParse::Parser::Token::Identifier $1, $self->{line}, $self->{pos};
            $self->{pos} += $len;
            return $token;
          }

        # Try for a constant
        if ($line =~ /^((0[xX][[:xdigit:]]+|[1-9]\d*|0[0-7]*)([uU](?:l|L|ll|LL)?|[lL][uU]?|ll[uU]?|LL[uU]?)?)/)
          {
            my $len = length $1;
            my $token = new CParse::Parser::Token::Integer $2, $3, $self->{line}, $self->{pos};
            $self->{pos} += $len;
            return $token;
          }
        if ($line =~ /^(((?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+))([flFL])?)/)
          {
            my $len = length $1;
            my $token = new CParse::Parser::Token::Float $2, $3, $self->{line}, $self->{pos};
            $self->{pos} += $len;
            return $token;
          }
        if ($line =~ /^((0x(?:[[:xdigit:]]+\.[[:xdigit:]]*|\.[[:xdigit:]]+|[[:xdigit:]]+)[pP][+-]?\d+)([flFL])?)/)
          {
            my $len = length $1;
            my $token = new CParse::Parser::Token::Float $2, $3, $self->{line}, $self->{pos};
            $self->{pos} += $len;
            return $token;
          }
        if ($line =~ /^(L?'(?:[^'\\\n]|\\['"?abfnrtv\\]|\\[0-7]{1,3}|\\x[[:xdigit:]]+)*')/)
          {
            my $len = length $1;
            my $token = new CParse::Parser::Token::Character $1, $self->{line}, $self->{pos};
            $self->{pos} += $len;
            return $token;
          }

        # Try for a string literal
        if ($line =~ /^(L?"(?:[^"\\\n]|\\['"?abfnrtv\\]|\\[0-7]{1,3}|\\x[[:xdigit:]]+)*")/)
          {
            my $len = length $1;
            my $token = new CParse::Parser::Token::String $1, $self->{line}, $self->{pos};
            $self->{pos} += $len;
            return $token;
          }

        # Try for a punctuator
        if ($line =~ /^(==|!=|=|\*=|\/=|\%=|\+=|-=|>>=|<<=|&=|\^=|!=|\[|\]|
                        \(|\)|{|}|\.\.\.|\.|->|\+\+|--|&|\*|\+|-|~|\/|
                        \%|<<|>>|<=|>=|<|>|\^|&&|\|\||\||\?|:|;|
                        ,|\#|\#\#|!|<:|:>|<\%|\%>|\%:|\%\%:)/x)
          {
            my $len = length $1;
            my $token = new CParse::Parser::Token::Punctuator $1, $self->{line}, $self->{pos};
            $self->{pos} += $len;
            return $token;
          }

        $self->syntax_error;
        return undef;
      }
  }

sub retry_tokens
  {
    my $self = shift;
    unshift @{$self->{token_queue}}, @_;
  }

# Everything calls either try_token, or try_parse; all other try_
# calls get indirected through try_parse. These between them implement
# backtracking.

sub try_token
  {
    my $self = shift;

    my $token = $self->next_token;
    return undef unless $token;
    push @{$self->{trying_tokens}}, $token;
    return $token;
  }

my %parse_funcs;

sub try_parse
  {
    my $self = shift;
    my $thing = shift;

    local $self->{commit} = 0;

    # On leaving this function, either the tokens have been consumed
    # by us, and are therefore being tried by our parent, or pushed
    # back into the token stream. We start a fresh list of tokens to
    # be tried at this context, and reconstruct our parent's list
    # later.
    my $old_trying_tokens = $self->{trying_tokens};
    $self->{trying_tokens} = [];

    my $description = join(' ', $thing, grep {defined $_} @_);

    unless ($self->can('try_' . $thing))
      {
        confess "No such function try_$thing";
      }

    print STDERR "CParse::Parser::Perl::try_parse: trying $description\n" if $ENV{ICHECK_DEBUG} > 0;
    $parse_funcs{$thing} = eval "\\\&try_${thing}" unless defined $parse_funcs{$thing};
    die "No function for $thing found" unless $parse_funcs{$thing};
    my $parsed = $parse_funcs{$thing}->($self, @_);
    die if $@;

    print STDERR "CParse::Parser::Perl::try_parse: " . ($parsed ? "found" : "failed") . " $description\n" if $ENV{ICHECK_DEBUG} > 0;
    print STDERR "CParse::Parser::Perl::try_parse: tokens: " . join(' ', map {$_->dump_c} @{$self->{trying_tokens}}) . "\n"
      if $ENV{ICHECK_DEBUG} > 1;

    if ($parsed)
      {
        # If we succeed, then push the tokens we consumed onto the try stack of our parent
        push @$old_trying_tokens, @{$self->{trying_tokens}};
        $self->{trying_tokens} = $old_trying_tokens;
      }
    else
      {
        if ($self->committed)
          {
            $self->syntax_error($thing);
            $self->{skip_errors} = 1;
          }

        # If we failed, then push back all the tokens we tried into the token stream
        $self->retry_tokens(@{$self->{trying_tokens}});
        $self->{trying_tokens} = $old_trying_tokens;
      }

    return $parsed;
  }

sub commit
  {
    my $self = shift;

    $self->{commit} = 1;
  }

sub uncommit
  {
    my $self = shift;

    $self->{commit} = 0;
  }

sub committed
  {
    my $self = shift;

    return $self->{commit};
  }

sub try_op_list
  {
    my $self = shift;
    my $thing = shift;
    my $op = shift;
    my $op_arg = shift;
    my $end = shift;

    my $arg1 = $self->try_parse($thing);
    return undef unless $arg1;

    my @args = ($arg1);

    while (1)
      {
        if ($end)
          {
            last if $self->try_parse('punctuator', $end);

            my $op = $self->try_parse($op, $op_arg);
            return undef unless $op;
            push @args, $op;
          }
        else
          {
            my $op = $self->try_parse($op, $op_arg);
            last unless $op;
            push @args, $op;
          }

        my $arg = $self->try_parse($thing);
        return undef unless $arg;
        push @args, $arg;
      }

    return \@args;
  }

sub try_identifier
  {
    my $self = shift;
    my $name = shift;

    my $token = $self->try_token;

    return undef unless $token;
    return undef unless $token->isa('CParse::Parser::Token::Identifier');
    return $token unless $name;
    return undef unless $token->string eq $name;
    return $token;
  }

sub try_keyword
  {
    my $self = shift;
    my $name = shift;

    my $token = $self->try_token;

    return undef unless $token;
    return undef unless $token->isa('CParse::Parser::Token::Keyword');
    return $token unless $name;
    return undef unless $token->string eq $name;
    return $token;
  }

sub try_punctuator
  {
    my $self = shift;
    my $name = shift;

    my $token = $self->try_token;

    return undef unless $token;
    return undef unless $token->isa('CParse::Parser::Token::Punctuator');
    return $token unless $name;
    return undef unless $token->string eq $name;
    return $token;
  }

sub try_integer_constant
  {
    my $self = shift;

    my $token = $self->try_token;

    return undef unless $token->isa('CParse::Parser::Token::Integer');
    return $token;
  }

sub try_floating_constant
  {
    my $self = shift;

    my $token = $self->try_token;

    return undef unless $token;
    return undef unless $token->isa('CParse::Parser::Token::Float');
    return $token;
  }

sub try_character_constant
  {
    my $self = shift;

    my $token = $self->try_token;

    return undef unless $token;
    return undef unless $token->isa('CParse::Parser::Token::Character');
    return $token;
  }

sub try_one_string_literal
  {
    my $self = shift;

    my $token = $self->try_token;

    return undef unless $token;
    return undef unless $token->isa('CParse::Parser::Token::String');
    return $token;
  }

sub try_string_literal
  {
    my $self = shift;

    my $string = $self->try_parse('one_string_literal');
    return undef unless $string;

    my $next;
    while ($next = $self->try_parse('one_string_literal'))
      {
        $string->concatenate($next);
      }
    return $string;
  }

my %storage_classes = map {$_=>1} qw/typedef extern static auto register/;

sub try_storage_class_specifier
  {
    my $self = shift;
    my $name = shift;

    my $keyword = $self->try_parse('keyword', $name);
    return undef unless $keyword;

    return undef unless $storage_classes{$keyword->string};

    return new CParse::StorageClass $keyword->string;
  }

my %basic_types = map {$_=>1} qw/void char short int long float double signed unsigned _Bool _Complex _Imaginary/;

sub try_type_specifier
  {
    my $self = shift;
    my $name = shift;

    my $identifier = $self->try_parse('identifier', $name);
    return $identifier->process if $identifier;

    my $keyword = $self->try_parse('keyword', $name);
    return undef unless $keyword;

    if ($basic_types{$keyword->string})
      {
        return new CParse::TypeSpecifier $keyword->string;
      }

    if ($keyword->string eq '__extension__')
      {
        return new CParse::Extension;
      }

    if ($keyword->string eq 'struct')
      {
        return $self->try_parse('struct_specifier', @_);
      }

    if ($keyword->string eq 'union')
      {
        return $self->try_parse('union_specifier', @_);
      }

    if ($keyword->string eq 'enum')
      {
        return $self->try_parse('enum_specifier', @_);
      }

    return undef;
  }

my %type_qualifiers = map {$_=>1} qw/const restrict volatile/;

sub try_type_qualifier
  {
    my $self = shift;
    my $name = shift;

    my $keyword = $self->try_parse('keyword', $name);
    return undef unless $keyword;

    return undef unless $type_qualifiers{$keyword->string};

    return new CParse::TypeQualifier $keyword->string;
  }

my %function_specifiers = map {$_=>1} qw/inline/;

sub try_function_specifier
  {
    my $self = shift;
    my $name = shift;

    my $keyword = $self->try_parse('keyword', $name);
    return undef unless $keyword;

    return undef unless $function_specifiers{$keyword->string};

    return new CParse::FunctionSpecifier $keyword->string;
  }

my %assignment_operators = map {$_=>1} qw/= *= \/= %= += -= <<= >>= &= ^= |=/;

sub try_assignment_operator
  {
    my $self = shift;
    my $name = shift;

    my $punctuator = $self->try_parse('punctuator', $name);
    return undef unless $punctuator;

    return undef unless $assignment_operators{$punctuator->string};

    return $punctuator->string;
  }

my %unary_operators = map {$_=>1} qw/& * + - ~ !/;

sub try_unary_operator
  {
    my $self = shift;
    my $name = shift;

    my $punctuator = $self->try_parse('punctuator', $name);
    return undef unless $punctuator;

    return undef unless $unary_operators{$punctuator->string};

    return $punctuator->string;
  }

my %equality_operators = map {$_=>1} qw/== !=/;

sub try_equality_operator
  {
    my $self = shift;
    my $name = shift;

    my $punctuator = $self->try_parse('punctuator', $name);
    return undef unless $punctuator;

    return undef unless $equality_operators{$punctuator->string};

    return $punctuator->string;
  }

my %relational_operators = map {$_=>1} qw/< > <= >=/;

sub try_relational_operator
  {
    my $self = shift;
    my $name = shift;

    my $punctuator = $self->try_parse('punctuator', $name);
    return undef unless $punctuator;

    return undef unless $relational_operators{$punctuator->string};

    return $punctuator->string;
  }

my %shift_operators = map {$_=>1} qw/<< >>/;

sub try_shift_operator
  {
    my $self = shift;
    my $name = shift;

    my $punctuator = $self->try_parse('punctuator', $name);
    return undef unless $punctuator;

    return undef unless $shift_operators{$punctuator->string};

    return $punctuator->string;
  }

my %additive_operators = map {$_=>1} qw/+ -/;

sub try_additive_operator
  {
    my $self = shift;
    my $name = shift;

    my $punctuator = $self->try_parse('punctuator', $name);
    return undef unless $punctuator;

    return undef unless $additive_operators{$punctuator->string};

    return $punctuator->string;
  }

my %multiplicative_operators = map {$_=>1} qw/* % \//;

sub try_multiplicative_operator
  {
    my $self = shift;
    my $name = shift;

    my $punctuator = $self->try_parse('punctuator', $name);
    return undef unless $punctuator;

    return undef unless $multiplicative_operators{$punctuator->string};

    return $punctuator->string;
  }

sub try_declaration
  {
    my $self = shift;

    my @declaration_specifiers;
    my $declarators;

    while (1)
      {
        $declarators = $self->try_parse('declaration_declarator_list');
        last if $declarators;

        my $specifier = $self->try_parse('declaration_specifier');
        if ($specifier)
          {
            push @declaration_specifiers, $specifier;
            next;
          }

        return undef;
      }

    return new CParse::Declaration \@declaration_specifiers, $declarators;
  }

sub try_declaration_declarator_list
  {
    my $self = shift;

    my @declarators;
    my $first = 1;
    while (1)
      {
        # Stop when we reach a ';'
        last if $self->try_parse('punctuator', ';');

        # If this isn't the first parameter, we need a ','
        return undef if not $first and not $self->try_parse('punctuator', ',');
        $first = 0;

        my $declarator = $self->try_parse('init_declarator');
        return undef unless $declarator;
        push @declarators, $declarator;
      }

    return \@declarators;
  }

sub try_init_declarator
  {
    my $self = shift;

    my $declarator = $self->try_parse('declarator');
    return undef unless $declarator;

    my $initialiser;
    if ($self->try_parse('punctuator', '='))
      {
        $initialiser = $self->try_parse('initialiser');
        return undef unless $initialiser;
      }

    return $declarator;
  }

sub try_initialiser
  {
    my $self = shift;

    if ($self->try_parse('punctuator', '{'))
      {
        my @initialisers;
        my $first = 1;
        while (1)
          {
            # Stop when we reach a '}'
            last if $self->try_parse('punctuator', '}');

            # If this isn't the first parameter, we need a ','
            return undef if not $first and not $self->try_parse('punctuator', ',');
            $first = 0;

            my $initialiser = $self->try_parse('designated_initialiser');
            return undef unless $initialiser;
            push @initialisers, $initialiser;
          }
        return \@initialisers;
      }

    return $self->try_parse('assignment_expression');
  }

sub try_designated_initialiser
  {
    my $self = shift;

    if ($self->try_parse('punctuator', '['))
      {
        $self->commit;

        my $expr = $self->try_parse('constant_expression');
        return undef unless $expr;
        return undef unless $self->try_parse('punctuator', ']');
        my $initialiser = $self->try_parse('initialiser');
        return $initialiser;
      }

    if ($self->try_parse('punctuator', '.'))
      {
        $self->commit;

        my $id = $self->try_parse('identifier');
        return undef unless $id;
        my $initialiser = $self->try_parse('initialiser');
        return $initialiser;
      }

    my $initialiser = $self->try_parse('initialiser');
    return $initialiser;
  }

sub try_function
  {
    my $self = shift;

    my @declaration_specifiers;
    my $declarator;

    while (1)
      {
        $declarator = $self->try_parse('function_declarator');
        last if $declarator;

        my $specifier = $self->try_parse('declaration_specifier');
        if ($specifier)
          {
            push @declaration_specifiers, $specifier;
            next;
          }

        return undef;
      }

    my @declarations;
    while (my $declaration = $self->try_parse('declaration'))
      {
        push @declarations, $declaration;
      }

    my $body = $self->try_parse('compound_statement');
    return undef unless $body;

    return new CParse::Function \@declaration_specifiers, $declarator, \@declarations;
  }

sub try_function_declarator
  {
    my $self = shift;

    my $pointer = $self->try_parse('pointer');
    my $prefix = $self->try_parse('direct_declarator_prefix');
    return undef unless $prefix;

    my $suffix = $self->try_parse('direct_declarator_function_suffix');
    return undef unless $suffix;

    return new CParse::Declarator((new CParse::Declarator::Direct $prefix, [$suffix]), $pointer);
  }

sub try_direct_declarator_array_suffix
  {
    my $self = shift;

    my $restrict;
    my $expr;

    return undef unless $self->try_parse('punctuator', '[');
    $self->commit;
    if ($self->try_parse('punctuator', '*'))
      {
        die "Unhandled foo[*] construct";
      }
    elsif ($self->try_parse('type_qualifier', 'restrict'))
      {
        # This is an undocumented gcc extension. I'm guessing at the
        # syntax here.
        $restrict = 1;
      }
    else
      {
        $expr = $self->try_parse('assignment_expression');
      }
    return undef unless $self->try_parse('punctuator', ']');
    return new CParse::Declarator::Array $expr, $restrict;
  }

sub try_direct_declarator_function_suffix
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '(');
    if ($self->try_parse('punctuator', ')'))
      {
        return new CParse::Declarator::Function [], 1;
      }

    my @parameters;
    my $variadic = 0;
    my $first = 1;
    while (1)
      {
        # Stop when we reach a ')'
        last if $self->try_parse('punctuator', ')');

        # If this isn't the first parameter, we need a ','
        return undef if not $first and not $self->try_parse('punctuator', ',');
        $first = 0;

        if ($self->try_parse('punctuator', '...'))
          {
            # ... has to be the last parameter
            return undef unless $self->try_parse('punctuator', ')');
            $variadic = 1;
            last;
          }

        my $parameter = $self->try_parse('parameter_declaration');
        return undef unless $parameter;
        push @parameters, $parameter;
      }

    return new CParse::Declarator::Function \@parameters, $variadic;
  }

sub try_parameter_declaration
  {
    my $self = shift;

    my @specifiers;

    while (1)
      {
        my $specifier = $self->try_parse('declaration_specifier');
        last unless $specifier;
        push @specifiers, $specifier;
      }

    my $declarator = $self->try_parse('declarator');
    $declarator = $self->try_parse('abstract_declarator') unless $declarator;
    # Stick a null declarator in here if necessary
    $declarator ||= new CParse::Declarator;

    return new CParse::ParameterDeclaration \@specifiers, $declarator;
  }

sub try_direct_declarator_prefix
  {
    my $self = shift;

    my $prefix;
    if ($self->try_parse('punctuator', '('))
      {
        $prefix = $self->try_parse('declarator');
        return undef unless $self->try_parse('punctuator', ')');
        return $prefix;
      }
    else
      {
        $prefix = $self->try_parse('identifier');
        return undef unless $prefix;
        return $prefix->process;
      }

    return undef;
  }

sub try_declarator
  {
    my $self = shift;

    my $attributes1 = $self->try_parse('attribute_specifier_list');
    my $pointer = $self->try_parse('pointer');

    my $prefix = $self->try_parse('direct_declarator_prefix');
    return undef unless $prefix;

    my $had_function_suffix = 0;
    my @suffixes;
    while (1)
      {
        my $suffix = $self->try_parse('direct_declarator_array_suffix');
        if ($suffix)
          {
            push @suffixes, $suffix;
            next;
          }

        unless ($had_function_suffix)
          {
            $suffix = $self->try_parse('direct_declarator_function_suffix');
            if ($suffix)
              {
                $had_function_suffix = 1;
                push @suffixes, $suffix;
                next;
              }
          }

        last;
      }

    my $attributes2 = $self->try_parse('attribute_specifier_list');

    return new CParse::Declarator((new CParse::Declarator::Direct $prefix, \@suffixes), $pointer, $attributes1, $attributes2);
  }

sub try_declaration_specifier
  {
    my $self = shift;

    return
      $self->try_parse('storage_class_specifier') || $self->try_parse('type_qualifier')
        || $self->try_parse('function_specifier') || $self->try_parse('attribute_specifier')
          || $self->try_parse('type_specifier');
  }

sub try_pointer
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '*');

    my @qualifiers;
    while (1)
      {
        my $attribute = $self->try_parse('attribute_specifier');
        if ($attribute)
          {
            push @qualifiers, $attribute;
            next;
          }

        my $qualifier = $self->try_parse('type_qualifier');
        last unless $qualifier;
        push @qualifiers, $qualifier;
      }

    my $pointer = $self->try_parse('pointer');

    return new CParse::Pointer \@qualifiers, $pointer;
  }

sub try_abstract_declarator
  {
    my $self = shift;

    my $attributes1 = $self->try_parse('attribute_specifier_list');
    my $pointer = $self->try_parse('pointer');
    my $direct_declarator = $self->try_parse('direct_abstract_declarator');
    my $attributes2 = $self->try_parse('attribute_specifier_list');

    return undef unless $pointer or $direct_declarator;

    return new CParse::Declarator $direct_declarator, $pointer, $attributes1, $attributes2;
  }

sub try_direct_abstract_declarator
  {
    my $self = shift;

    # Note how $prefix can be undef
    my $prefix = $self->try_parse('abstract_declarator_prefix');

    my @suffixes;
    while (1)
      {
        my $suffix = $self->try_parse('direct_declarator_array_suffix');
        if ($suffix)
          {
            push @suffixes, $suffix;
            next;
          }

        $suffix = $self->try_parse('direct_declarator_function_suffix');
        if ($suffix)
          {
            push @suffixes, $suffix;
            next;
          }

        last;
      }

    return new CParse::Declarator::Direct $prefix, \@suffixes;
  }

sub try_abstract_declarator_prefix
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '(');
    my $declarator = $self->try_parse('abstract_declarator');
    return undef unless $self->try_parse('punctuator', ')');
    return $declarator;
  }

sub try_struct_specifier
  {
    my $self = shift;

    my $attributes1 = $self->try_parse('attribute_specifier_list');

    my $id = $self->try_parse('identifier');

    if ($self->try_parse('punctuator', '{'))
      {
        $self->commit;

        my @declarations;

        while (1)
          {
            last if $self->try_parse('punctuator', '}');

            my $declaration = $self->try_parse('struct_declaration');
            return undef unless $declaration;
            push @declarations, $declaration;
          }

        my $attributes2 = $self->try_parse('attribute_specifier_list');

        return new CParse::Struct $id ? $id->string : undef, \@declarations, $attributes1, $attributes2;
      }
    else
      {
        return undef unless $id;
        return new CParse::StructRef $id->string;
      }
  }

sub try_union_specifier
  {
    my $self = shift;

    my $attributes1 = $self->try_parse('attribute_specifier_list');

    my $id = $self->try_parse('identifier');

    if ($self->try_parse('punctuator', '{'))
      {
        $self->commit;

        my @declarations;

        while (1)
          {
            last if $self->try_parse('punctuator', '}');

            my $declaration = $self->try_parse('struct_declaration');
            return undef unless $declaration;
            push @declarations, $declaration;
          }

        my $attributes2 = $self->try_parse('attribute_specifier_list');

        return new CParse::Union $id ? $id->string : undef, \@declarations, $attributes1, $attributes2;
      }
    else
      {
        return undef unless $id;
        return new CParse::UnionRef $id->string;
      }
  }

sub try_specifier_qualifier
  {
    my $self = shift;
    return $self->try_parse('attribute_specifier')
      || $self->try_parse('type_specifier') || $self->try_parse('type_qualifier');
  }

sub try_struct_declaration
  {
    my $self = shift;

    my @specifiers;
    my $declarators;

    while (1)
      {
        $declarators = $self->try_parse('struct_declaration_declarator_list');
        last if $declarators;

        my $specifier = $self->try_parse('specifier_qualifier');
        if ($specifier)
          {
            push @specifiers, $specifier;
            next;
          }

        return undef;
      }

    return new CParse::StructDeclaration \@specifiers, $declarators;
  }

sub try_struct_declaration_declarator_list
  {
    my $self = shift;

    my @declarators;
    my $first = 1;
    while (1)
      {
        # Stop when we reach a ';'
        last if $self->try_parse('punctuator', ';');

        # If this isn't the first parameter, we need a ','
        return undef if not $first and not $self->try_parse('punctuator', ',');
        $first = 0;

        my $declarator = $self->try_parse('struct_declarator');
        return undef unless $declarator;
        push @declarators, $declarator;
      }

    return \@declarators;
  }

sub try_struct_declarator
  {
    my $self = shift;

    my $attributes1 = $self->try_parse('attribute_specifier_list');
    my $declarator = $self->try_parse('declarator');
    $declarator ||= new CParse::Declarator;

    if ($self->try_parse('punctuator', ':'))
      {
        my $expr = $self->try_parse('constant_expression');
        my $attributes2 = $self->try_parse('attribute_specifier_list');
        return undef unless $expr;
        return new CParse::StructDeclarator $declarator, $expr, $attributes1, $attributes2;
      }
    else
      {
        return undef unless $declarator;
        my $attributes2 = $self->try_parse('attribute_specifier_list');
        return new CParse::StructDeclarator $declarator, undef, $attributes1, $attributes2;
      }
  }

sub try_enum_specifier
  {
    my $self = shift;

    my $attributes1 = $self->try_parse('attribute_specifier_list');

    my $id = $self->try_parse('identifier');

    if ($self->try_parse('punctuator', '{'))
      {
        $self->commit;

        my @declarations;

        my $first = 1;
        while (1)
          {
            last if $self->try_parse('punctuator', '}');

            return undef if not $first and not $self->try_parse('punctuator', ',');
            $first = 0;

            # enum is odd, it permits a trailing comma - so we could
            # also finish here
            last if $self->try_parse('punctuator', '}');

            my $declaration = $self->try_parse('enumerator');
            return undef unless $declaration;
            push @declarations, $declaration;
          }

        my $attributes2 = $self->try_parse('attribute_specifier_list');

        return new CParse::Enum $id ? $id->string : undef, \@declarations, $attributes1, $attributes2;
      }
    else
      {
        return undef unless $id;
        return new CParse::EnumRef $id->string;
      }
  }

sub try_enumerator
  {
    my $self = shift;

    my $identifier = $self->try_parse('identifier');
    return undef unless $identifier;

    my $expr = undef;

    if ($self->try_parse('punctuator', '='))
      {
        $expr = $self->try_parse('constant_expression');
        return undef unless $expr;
      }

    return new CParse::Enumerator $identifier->string, $expr;
  }

sub try_constant_expression
  {
    my $self = shift;
    return $self->try_parse('conditional_expression');
  }

sub try_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'assignment_expression', 'punctuator', ',');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::Expression';
  }

sub try_assignment_expression
  {
    my $self = shift;

    return $self->try_parse('assignment') || $self->try_parse('conditional_expression');
  }

sub try_assignment
  {
    my $self = shift;

    my $left_expr = $self->try_parse('unary_expression');
    return undef unless $left_expr;
    my $op = $self->try_parse('assignment_operator');
    return undef unless $op;
    my $right_expr = $self->try_parse('assignment_expression');
    return undef unless $right_expr;

    return new CParse::Op::Assign $left_expr, $right_expr, $op;
  }

sub try_conditional_expression
  {
    my $self = shift;

    my $cond_expr = $self->try_parse('logical_or_expression');
    return undef unless $cond_expr;

    if ($self->try_parse('punctuator', '?'))
      {
        $self->commit;

        my $true_expr = $self->try_parse('expression');
        return undef unless $true_expr;
        return undef unless $self->try_parse('punctuator', ':');
        my $false_expr = $self->try_parse('conditional_expression');
        return undef unless $false_expr;
        return new CParse::Op::Conditional $cond_expr, $true_expr, $false_expr;
      }
    else
      {
        return $cond_expr;
      }
  }

sub try_logical_or_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'logical_and_expression', 'punctuator', '||');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::BoolOr';
  }

sub try_logical_and_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'inclusive_or_expression', 'punctuator', '&&');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::BoolAnd';
  }

sub try_inclusive_or_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'exclusive_or_expression', 'punctuator', '|');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::BitOr';
  }

sub try_exclusive_or_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'and_expression', 'punctuator', '^');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::BitXor';
  }

sub try_and_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'equality_expression', 'punctuator', '&');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::BitAnd';
  }

sub try_equality_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'relational_expression', 'equality_operator');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::Equal';
  }

sub try_relational_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'shift_expression', 'relational_operator');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::Relation';
  }

sub try_shift_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'additive_expression', 'shift_operator');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::Shift';
  }

sub try_additive_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'multiplicative_expression', 'additive_operator');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::Add';
  }

sub try_multiplicative_expression
  {
    my $self = shift;

    my $list = $self->try_parse('op_list', 'cast_expression', 'multiplicative_operator');
    return undef unless $list;
    return new CParse::Op $list, 'CParse::Op::Multiply';
  }

sub try_cast_expression
  {
    my $self = shift;

    return $self->try_parse('explicit_cast') || $self->try_parse('unary_expression');
  }

sub try_explicit_cast
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '(');
    my $type = $self->try_parse('type_name');
    return undef unless $type;
    return undef unless $self->try_parse('punctuator', ')');
    my $expr = $self->try_parse('cast_expression');
    return undef unless $expr;
    return new CParse::Op::Cast $expr, $type;
  }

sub try_unary_expression
  {
    my $self = shift;

    if ($self->try_parse('keyword', 'sizeof'))
      {
        $self->commit;

        if ($self->try_parse('punctuator', '('))
          {
            my $type = $self->try_parse('type_name');
            return undef unless $type;
            return undef unless $self->try_parse('punctuator', ')');
            return new CParse::Op::Sizeof $type;
          }
        my $expr = $self->try_parse('unary_expression');
        return undef unless $expr;
        return new CParse::Op::SizeofExpr $expr;
      }

    if ($self->try_parse('keyword', '__alignof__'))
      {
        $self->commit;

        return unless $self->try_parse('punctuator', '(');

        my $arg = $self->try_parse('type_name');
        if (not $arg)
          {
            $arg = $self->try_parse('unary_expression');
          }
        return undef unless $arg;
        return undef unless $self->try_parse('punctuator', ')');
        return new CParse::Op::Alignof $arg;
      }

    if ($self->try_parse('punctuator', '++'))
      {
        my $expr = $self->try_parse('unary_expression');
        return undef unless $expr;
        return new CParse::Op::Preinc $expr;
      }

    if ($self->try_parse('punctuator', '--'))
      {
        my $expr = $self->try_parse('unary_expression');
        return undef unless $expr;
        return new CParse::Op::Predec $expr;
      }

    if ($self->try_parse('keyword', '__extension__'))
      {
        $self->commit;
        return $self->try_parse('cast_expression');
      }

    my $op = $self->try_parse('unary_operator');
    if ($op)
      {
        my $expr = $self->try_parse('cast_expression');
        return new CParse::Op::Unary $expr, $op;
      }

    return $self->try_parse('postfix_expression');
  }

sub try_postfix_expression
  {
    my $self = shift;

    my $prefix = $self->try_parse('postfix_expression_prefix');
    return undef unless $prefix;

    my @suffixes;
    SUFFIX: while (1)
      {
        foreach my $thing (qw/array call member member_indirect post_increment post_decrement/)
          {
            my $suffix = $self->try_parse('postfix_expression_' . $thing . '_suffix');
            if ($suffix)
              {
                push @suffixes, $suffix;
                next SUFFIX;
              }
          }

        last;
      }

    return new CParse::Op::Postfix $prefix, \@suffixes;
  }

sub try_postfix_expression_prefix
  {
    my $self = shift;

    my $compound = $self->try_parse('compound_literal');
    return $compound if $compound;

    my $primary = $self->try_parse('primary_expression');
    return $primary if $primary;

    return undef;
  }

sub try_compound_literal
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '(');
    my $type = $self->try_parse('type_name');
    return undef unless $type;
    return undef unless $self->try_parse('punctuator', ')');
    return undef unless $self->try_parse('punctuator', '{');

    my @initialisers;
    my $first = 1;
    while (1)
      {
        last if $self->try_parse('punctuator', '}');

        # If this isn't the first parameter, we need a ','
        return undef if not $first and not $self->try_parse('punctuator', ',');
        $first = 0;

        last if $self->try_parse('punctuator', '}');

        my $initialiser = $self->try_parse('designated_initialiser');
        return undef unless $initialiser;
        push @initialisers, $initialiser;
      }

    return new CParse::Op::CompoundLiteral $type, \@initialisers;
  }

sub try_type_name
  {
    my $self = shift;

    my @specifiers;
    my $declarator;

    while (1)
      {
        $declarator = $self->try_parse('abstract_declarator');
        last if $declarator;

        my $specifier = $self->try_parse('specifier_qualifier');
        if ($specifier)
          {
            push @specifiers, $specifier;
            next;
          }

        last;
      }

    return undef unless scalar @specifiers;

    $declarator ||= new CParse::Declarator;

    return new CParse::TypeName \@specifiers, $declarator;
  }

sub try_primary_expression
  {
    my $self = shift;

    if ($self->try_parse('punctuator', '('))
      {
        my $expression = $self->try_parse('expression');
        return undef unless $expression;
        return undef unless $self->try_parse('punctuator', ')');
        return $expression;
      }

    my $string = $self->try_parse('string_literal');
    return $string->process if $string;

    my $identifier = $self->try_parse('identifier');
    return $identifier->process if $identifier;

    return $self->try_parse('constant');
  }

sub try_constant
  {
    my $self = shift;

    my $constant;

    $constant = $self->try_parse('integer_constant');
    return $constant->process if $constant;

    $constant = $self->try_parse('floating_constant');
    return $constant->process if $constant;

    $constant = $self->try_parse('character_constant');
    return $constant->process if $constant;

    return undef;
  }

sub try_postfix_expression_array_suffix
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '[');
    my $expr = $self->try_parse('expression');
    return undef unless $expr;
    return undef unless $self->try_parse('punctuator', ']');
    return new CParse::Op::ArraySubscript $expr;
  }

sub try_postfix_expression_call_suffix
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '(');
    my $args = $self->try_parse('op_list', 'assignment_expression', 'punctuator', ',');
    $args ||= [];
    return undef unless $self->try_parse('punctuator', ')');
    return new CParse::Op::Call $args;
  }

sub try_postfix_expression_member_suffix
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '.');
    my $member = $self->try_parse('identifier');
    return undef unless $member;
    return new CParse::Op::Member $member->string;
  }

sub try_postfix_expression_member_indirect_suffix
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '->');
    my $member = $self->try_parse('identifier');
    return undef unless $member;
    return new CParse::Op::MemberIndirect $member->string;
  }

sub try_postfix_expression_post_increment_suffix
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '++');
    return new CParse::Op::Postinc;
  }

sub try_postfix_expression_post_decrement_suffix
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '--');
    return new CParse::Op::Postdec;
  }

sub try_compound_statement
  {
    my $self = shift;

    return undef unless $self->try_parse('punctuator', '{');

    $self->commit;

    my @items;

    ITEM: while (1)
      {
        last if $self->try_parse('punctuator', '}');

        foreach my $thing (qw/statement declaration/)
          {
            my $item = $self->try_parse($thing);
            if ($item)
              {
                push @items, $item;
                next ITEM;
              }
          }

        return undef;
      }

    return \@items;
  }

sub try_statement
  {
    my $self = shift;

    return $self->try_parse('null_statement') || $self->try_parse('labelled_statement')
      || $self->try_parse('compound_statement') || $self->try_parse('expression_statement')
      || $self->try_parse('selection_statement') || $self->try_parse('iteration_statement')
      || $self->try_parse('jump_statement') || $self->try_parse('asm_statement');
  }

sub try_null_statement
  {
    my $self = shift;
    return undef unless $self->try_parse('punctuator', ';');
    return {};
  }

sub try_labelled_statement
  {
    my $self = shift;

    if ($self->try_parse('keyword', 'case'))
      {
        $self->commit;

        my $case = $self->try_parse('constant_expression');
        return undef unless $case;
        return undef unless $self->try_parse('punctuator', ':');
        my $statement = $self->try_parse('statement');
        return undef unless $statement;
        return $statement;
      }

    if ($self->try_parse('keyword', 'default'))
      {
        $self->commit;

        return undef unless $self->try_parse('punctuator', ':');
        my $statement = $self->try_parse('statement');
        return undef unless $statement;
        return $statement;
      }

    my $identifier = $self->try_parse('identifier');
    return undef unless $identifier;
    return undef unless $self->try_parse('punctuator', ':');
    my $attributes = $self->try_parse('attribute_specifier_list');
    my $statement = $self->try_parse('statement');
    return undef unless $statement;
    return $statement;
  }

sub try_expression_statement
  {
    my $self = shift;

    my $expr = $self->try_parse('expression');
    return undef unless $self->try_parse('punctuator', ';');

    return $expr;
  }

sub try_selection_statement
  {
    my $self = shift;

    if ($self->try_parse('keyword', 'if'))
      {
        $self->commit;

        return undef unless $self->try_parse('punctuator', '(');
        my $expr = $self->try_parse('expression');
        return undef unless defined $expr;
        return undef unless $self->try_parse('punctuator', ')');

        my $true_statement = $self->try_parse('statement');
        return undef unless defined $true_statement;

        my $false_statement;
        if ($self->try_parse('keyword', 'else'))
          {
            $false_statement = $self->try_parse('statement');
            return undef unless defined $false_statement;
          }

        return $expr;
      }

    if ($self->try_parse('keyword', 'switch'))
      {
        $self->commit;

        return undef unless $self->try_parse('punctuator', '(');
        my $expr = $self->try_parse('expression');
        return undef unless defined $expr;
        return undef unless $self->try_parse('punctuator', ')');

        my $statement = $self->try_parse('statement');
        return undef unless defined $statement;

        return $expr;
      }

    return undef;
  }

sub try_iteration_statement
  {
    my $self = shift;

    if ($self->try_parse('keyword', 'while'))
      {
        $self->commit;

        return undef unless $self->try_parse('punctuator', '(');
        my $expr = $self->try_parse('expression');
        return undef unless defined $expr;
        return undef unless $self->try_parse('punctuator', ')');

        my $statement = $self->try_parse('statement');
        return undef unless defined $statement;

        return $expr;
      }

    if ($self->try_parse('keyword', 'do'))
      {
        $self->commit;

        my $statement = $self->try_parse('statement');
        return undef unless defined $statement;

        return undef unless $self->try_parse('keyword', 'while');

        return undef unless $self->try_parse('punctuator', '(');
        my $expr = $self->try_parse('expression');
        return undef unless defined $expr;
        return undef unless $self->try_parse('punctuator', ')');

        return undef unless $self->try_parse('punctuator', ';');

        return $expr;
      }

    if ($self->try_parse('keyword', 'for'))
      {
        $self->commit;

        return undef unless $self->try_parse('punctuator', '(');
        my $expr1 = $self->try_parse('declaration');
        unless ($expr1)
          {
            $expr1 = $self->try_parse('expression');
            return undef unless $self->try_parse('punctuator', ';');
          }
        my $expr2 = $self->try_parse('expression');
        return undef unless $self->try_parse('punctuator', ';');
        my $expr3 = $self->try_parse('expression');
        return undef unless $self->try_parse('punctuator', ')');

        my $statement = $self->try_parse('statement');
        return undef unless defined $statement;

        return $statement;
      }

    return undef;
  }

sub try_jump_statement
  {
    my $self = shift;

    if ($self->try_parse('keyword', 'goto'))
      {
        $self->commit;

        my $label = $self->try_parse('identifier');
        return undef unless $label;
        return undef unless $self->try_parse('punctuator', ';');

        return $label;
      }

    if ($self->try_parse('keyword', 'continue'))
      {
        $self->commit;

        return undef unless $self->try_parse('punctuator', ';');

        return [];
      }

    if ($self->try_parse('keyword', 'break'))
      {
        $self->commit;

        return undef unless $self->try_parse('punctuator', ';');

        return [];
      }

    if ($self->try_parse('keyword', 'return'))
      {
        $self->commit;

        my $expr = $self->try_parse('expression');
        return undef unless $self->try_parse('punctuator', ';');

        return [];
      }

    return undef;
  }

sub try_asm_statement
  {
    my $self = shift;

    return undef unless $self->try_parse('keyword', '__asm__');
    $self->commit;
    my $volatile = $self->try_parse('keyword', 'volatile');
    return undef unless $self->try_parse('punctuator', '(');
    my $expr = $self->try_parse('expression');
    return undef unless $expr;
    if ($self->try_parse('punctuator', ':'))
      {
        return undef unless $self->try_parse('asm_operands');
        if ($self->try_parse('punctuator', ':'))
          {
            return undef unless $self->try_parse('asm_operands');
            if ($self->try_parse('punctuator', ':'))
              {
                return undef unless $self->try_parse('asm_clobbers');
              }
          }
      }
    return undef unless $self->try_parse('punctuator', ')');
    return undef unless $self->try_parse('punctuator', ';');

    return $expr;
  }

sub try_asm_operands
  {
    my $self = shift;
    return $self->try_parse('op_list', 'asm_operand', 'punctuator', ',');
  }

sub try_asm_operand
  {
    my $self = shift;

    if ($self->try_parse('punctuator', '['))
      {
        $self->commit;
        return undef unless $self->try_parse('identifier');
        return undef unless $self->try_parse('punctuator', ']');
      }
    my $op = $self->try_parse('string_literal');
    return undef unless $op;
    $self->commit;
    return undef unless $self->try_parse('punctuator', '(');
    return undef unless $self->try_parse('expression');
    return undef unless $self->try_parse('punctuator', ')');
    return $op;
  }

sub try_asm_clobbers
  {
    my $self = shift;
    return $self->try_parse('op_list', 'string_literal', 'punctuator', ',');
  }

sub try_attribute_specifier_list
  {
    my $self = shift;

    my @specifiers;
    while (1)
      {
        my $specifier = $self->try_parse('attribute_specifier');
        last unless $specifier;
        push @specifiers, $specifier;
      }

    return undef unless scalar @specifiers;
    # We'll flatten these into one list
    return new CParse::AttributeList [map {$_->attributes} @specifiers];
  }

sub try_attribute_specifier
  {
    my $self = shift;

    if ($self->try_parse('keyword', '__asm__'))
      {
        # We'll just treat this as another attribute here
        $self->commit;
        return undef unless $self->try_parse('punctuator', '(');
        my $name = $self->try_parse('string_literal');
        return undef unless $self->try_parse('punctuator', ')');
        return new CParse::AttributeList [new CParse::Attribute 'asm_name', $name];
      }

    return undef unless $self->try_parse('keyword', '__attribute__');
    $self->commit;

    return undef unless $self->try_parse('punctuator', '(');
    return undef unless $self->try_parse('punctuator', '(');

    my $list = $self->try_parse('op_list', 'attribute', 'punctuator', ',', ')');
    return undef unless $list;
    return undef unless $self->try_parse('punctuator', ')');

    # Discard the operators, keep the rest
    return new CParse::AttributeList [grep {not $_->isa('CParse::Parser::Token::Punctuator')} @$list];
  }

sub try_attribute
  {
    my $self = shift;

    my $name = $self->try_parse('attribute_name');
    return undef unless $name;

    my @args;
    if ($self->try_parse('punctuator', '('))
      {
        if (!$self->try_parse('punctuator', ')'))
          {
            my $identifier = $self->try_parse('identifier');
            if ($identifier)
              {
                push @args, $identifier->process if $identifier;
                my $p = $self->try_parse('punctuator');
                return undef unless $p->string eq ')' or $p->string eq ',';
                if ($p->string eq ',')
                  {
                    my $list = $self->try_parse('op_list', 'assignment_expression', 'punctuator', ',', ')');
                    return undef unless $list;
                    push @args, @$list;
                  }
              }
            else
              {
                my $list = $self->try_parse('op_list', 'assignment_expression', 'punctuator', ',', ')');
                return undef unless $list;
                push @args, @$list;
              }
          }
      }

    return new CParse::Attribute $name, \@args;
  }

sub try_attribute_name
  {
    my $self = shift;

    my $keyword = $self->try_parse('keyword');
    return $keyword->string if $keyword;

    my $identifier = $self->try_parse('identifier');
    return $identifier->string if $identifier;

    return undef;
  }

1;
