######################################################################
#
# Tests for the #render method of each Node.
#
######################################################################

require 'test_helper'

class RenderTest < Minitest::Test
  def setup
    @parser = C::Parser.new
    @parser.type_names << 'T'
  end

  #
  # Parse the given string and check that it renders to the same
  # string.
  #
  def check(klass, expected)
    expected = expected.chomp.gsub(/^ *\|/, '')
    node = klass.parse(expected, @parser)
    assert_equal(expected, node.to_s)
  end

  # ------------------------------------------------------------------
  #                          TranslationUnit
  # ------------------------------------------------------------------

  def test_translation_unit
    check(C::TranslationUnit, <<-EOS)
      |int i;
      |
      |int j;
      |
      |int f() {
      |}
      |
      |int g() {
      |}
    EOS
  end

  # ------------------------------------------------------------------
  #                            Declaration
  # ------------------------------------------------------------------

  def test_declaration
    check(C::Declaration, <<-EOS)
      |void f();
    EOS
  end
  def test_declaration_with_specifier
    check(C::Declaration, <<-EOS)
      |inline void f();
    EOS
  end
  def test_declaration_with_storage
    check(C::Declaration, <<-EOS)
      |static void f();
    EOS
  end
  def test_declaration_static_with_all
    check(C::Declaration, <<-EOS)
      |inline static void f();
    EOS
  end

  # ------------------------------------------------------------------
  #                             Declarator
  # ------------------------------------------------------------------

  def test_declarator
    check(C::Declarator, <<-EOS)
      |i
    EOS
  end
  def test_declarator_with_indirect_type
    check(C::Declarator, <<-EOS)
      |*i
    EOS
  end
  def test_declarator_with_init
    check(C::Declarator, <<-EOS)
      |i = 1
    EOS
  end
  def test_declarator_with_num_bits
    check(C::Declarator, <<-EOS)
      |i : 1
    EOS
  end

  # ------------------------------------------------------------------
  #                            FunctionDef
  # ------------------------------------------------------------------

  def test_function_def
    check(C::FunctionDef, <<-EOS)
      |void f() {
      |}
    EOS
  end
  def test_function_def_with_storage
    check(C::FunctionDef, <<-EOS)
      |static void f() {
      |}
    EOS
  end
  def test_function_def_with_inline
    check(C::FunctionDef, <<-EOS)
      |inline void f() {
      |}
    EOS
  end
  def test_function_def_with_no_prototype
    check(C::FunctionDef, <<-EOS)
      |static inline void f(i)
      |    int i;
      |{
      |}
    EOS
  end
  def test_function_def_with_indirect_return_type
    check(C::FunctionDef, <<-EOS)
      |static inline void *f(int *i) {
      |}
    EOS
  end
  def test_function_def_with_all
    check(C::FunctionDef, <<-EOS)
      |static inline void *f(i)
      |    int *i;
      |{
      |}
    EOS
  end

  # ------------------------------------------------------------------
  #                             Parameter
  # ------------------------------------------------------------------

  def test_parameter
    check(C::Parameter, <<-EOS)
      |int i
    EOS
  end
  def test_parameter_with_indirect_type
    check(C::Parameter, <<-EOS)
      |int i
    EOS
  end
  def test_parameter_with_no_type
    check(C::Parameter, <<-EOS)
      |i
    EOS
  end
  def test_parameter_with_no_name
    check(C::Parameter, <<-EOS)
      |int
    EOS
  end
  def test_parameter_with_storage
    check(C::Parameter, <<-EOS)
      |register int
    EOS
  end

  # ------------------------------------------------------------------
  #                             Enumerator
  # ------------------------------------------------------------------

  def test_enumerator
    check(C::Enumerator, <<-EOS)
      |i
    EOS
  end
  def test_enumerator_with_val
    check(C::Enumerator, <<-EOS)
      |i = 1
    EOS
  end

  # ------------------------------------------------------------------
  #                               Member
  # ------------------------------------------------------------------

  def test_member_init_member
    check(C::MemberInit, <<-EOS)
      |.foo = 1
    EOS
  end
  def test_member_init_index
    check(C::MemberInit, <<-EOS)
      |[0] = 1
    EOS
  end
  def test_member_init_multi
    check(C::MemberInit, <<-EOS)
      |.foo [0] = 1
    EOS
  end
  def test_member
    check(C::Member, <<-EOS)
      |foo
    EOS
  end

  # ------------------------------------------------------------------
  #                               Block
  # ------------------------------------------------------------------

  # TODO: don't indent unlabelled statements, and have a block
  # unindent labelled statements
  def test_block_empty
    check(C::Block, <<-EOS)
      |    {
      |    }
    EOS
  end
  def test_block_with_statements
    check(C::Block, <<-EOS)
      |    {
      |        ;
      |    }
    EOS
  end
  def test_block_with_declarations
    check(C::Block, <<-EOS)
      |    {
      |        int i;
      |    }
    EOS
  end
  def test_block_with_labelled_statements
    check(C::Block, <<-EOS)
      |    {
      |    one:
      |        ;
      |    }
    EOS
  end
  def test_block_with_labelled_blocks
    check(C::Block, <<-EOS)
      |    {
      |    one:
      |        {
      |        }
      |    }
    EOS
  end
  def test_block_labelled
    check(C::Block, <<-EOS)
      |one:
      |two:
      |    {
      |    }
    EOS
  end

  # ------------------------------------------------------------------
  #                                 If
  # ------------------------------------------------------------------

  def test_if_then_with_statements
    check(C::If, <<-EOS)
      |    if (cond)
      |        ;
    EOS
  end
  def test_if_then_else_with_statements
    check(C::If, <<-EOS)
      |    if (cond)
      |        ;
      |    else
      |        ;
    EOS
  end
  def test_if_then_elsif_then_with_statements
    # TODO: handle else-if towers
    check(C::If, <<-EOS)
      |    if (cond)
      |        ;
      |    else
      |        if (cond)
      |            ;
      |        else
      |            ;
    EOS
  end
  def test_if_with_labelled_statements
    check(C::If, <<-EOS)
      |    if (cond)
      |    one:
      |        ;
      |    else
      |        if (cond)
      |        two:
      |            ;
      |        else
      |        three:
      |            ;
    EOS
  end
  def test_if_then_with_blocks
    check(C::If, <<-EOS)
      |    if (cond) {
      |    }
    EOS
  end
  def test_if_then_else_with_blocks
    check(C::If, <<-EOS)
      |    if (cond) {
      |    } else {
      |    }
    EOS
  end
  def test_if_then_elsif_then_with_blocks
    # TODO: handle else-if towers
    check(C::If, <<-EOS)
      |    if (cond) {
      |    } else
      |        if (cond) {
      |        } else {
      |        }
    EOS
  end
  def test_if_with_labelled_blocks
    check(C::If, <<-EOS)
      |    if (cond)
      |    one:
      |        {
      |        }
      |    else
      |        if (cond)
      |        two:
      |            {
      |            }
      |        else
      |        three:
      |            {
      |            }
    EOS
  end
  def test_if_labelled
    check(C::If, <<-EOS)
      |one:
      |two:
      |    if (cond)
      |        ;
    EOS
  end

  # ------------------------------------------------------------------
  #                               Switch
  # ------------------------------------------------------------------

  def test_switch_with_statement
    check(C::Switch, <<-EOS)
      |    switch (cond)
      |        ;
    EOS
  end
  def test_switch_with_block
    check(C::Switch, <<-EOS)
      |    switch (cond) {
      |    }
    EOS
  end
  def test_switch_with_labelled_statement
    check(C::Switch, <<-EOS)
      |    switch (cond)
      |    one:
      |        ;
    EOS
  end
  def test_switch_with_labelled_block
    check(C::Switch, <<-EOS)
      |    switch (cond)
      |    one:
      |        {
      |        }
    EOS
  end
  def test_switch_labelled
    check(C::Switch, <<-EOS)
      |one:
      |two:
      |    switch (cond) {
      |    }
    EOS
  end

  # ------------------------------------------------------------------
  #                               While
  # ------------------------------------------------------------------

  def test_while_with_statement
    check(C::While, <<-EOS)
      |    while (cond)
      |        ;
    EOS
  end
  def test_while_with_block
    check(C::While, <<-EOS)
      |    while (cond) {
      |    }
    EOS
  end
  def test_while_with_labelled_statement
    check(C::While, <<-EOS)
      |    while (cond)
      |    one:
      |        ;
    EOS
  end
  def test_while_with_labelled_block
    check(C::While, <<-EOS)
      |    while (cond)
      |    one:
      |        {
      |        }
    EOS
  end
  def test_while_labelled
    check(C::While, <<-EOS)
      |one:
      |    while (cond)
      |        ;
    EOS
  end
  def test_do_while_with_statement
    check(C::While, <<-EOS)
      |    do
      |        ;
      |    while (cond);
    EOS
  end
  def test_do_while_with_block
    check(C::While, <<-EOS)
      |    do {
      |    } while (cond);
    EOS
  end
  def test_do_while_with_labelled_statement
    check(C::While, <<-EOS)
      |    do
      |    one:
      |        ;
      |    while (cond);
    EOS
  end
  def test_do_while_with_labelled_block
    check(C::While, <<-EOS)
      |    do
      |    one:
      |        {
      |        }
      |    while (cond);
    EOS
  end
  def test_do_while_labelled
    check(C::While, <<-EOS)
      |one:
      |    do
      |        ;
      |    while (cond);
    EOS
  end

  # ------------------------------------------------------------------
  #                                For
  # ------------------------------------------------------------------

  def test_for_with_no_header_elements
    check(C::For, <<-EOS)
      |    for (;;)
      |        ;
    EOS
  end
  def test_for_with_init_expression
    check(C::For, <<-EOS)
      |    for (i = 0;;)
      |        ;
    EOS
  end
  def test_for_with_init_declaration
    check(C::For, <<-EOS)
      |    for (int i = 0;;)
      |        ;
    EOS
  end
  def test_for_with_cond
    check(C::For, <<-EOS)
      |    for (; i < 10;)
      |        ;
    EOS
  end
  def test_for_with_iter
    check(C::For, <<-EOS)
      |    for (;; ++i)
      |        ;
    EOS
  end
  def test_for_with_all_header_elements
    check(C::For, <<-EOS)
      |    for (i = 0; i < 10; ++i)
      |        ;
    EOS
  end
  def test_for_with_block
    check(C::For, <<-EOS)
      |    for (;;) {
      |    }
    EOS
  end
  def test_for_with_labelled_statement
    check(C::For, <<-EOS)
      |    for (;;)
      |    one:
      |        ;
    EOS
  end
  def test_for_with_labelled_block
    check(C::For, <<-EOS)
      |    for (;;)
      |    one:
      |        {
      |        }
    EOS
  end
  def test_for_labelled
    check(C::For, <<-EOS)
      |one:
      |    for (;;)
      |        ;
    EOS
  end

  # ------------------------------------------------------------------
  #                                Goto
  # ------------------------------------------------------------------

  def test_goto
    check(C::Goto, <<-EOS)
      |    goto one;
    EOS
  end
  def test_goto_labelled
    check(C::Goto, <<-EOS)
      |one:
      |    goto two;
    EOS
  end

  # ------------------------------------------------------------------
  #                              Continue
  # ------------------------------------------------------------------

  def test_continue
    check(C::Continue, <<-EOS)
      |    continue;
    EOS
  end
  def test_continue_labelled
    check(C::Continue, <<-EOS)
      |one:
      |    continue;
    EOS
  end

  # ------------------------------------------------------------------
  #                               Break
  # ------------------------------------------------------------------

  def test_break
    check(C::Break, <<-EOS)
      |    break;
    EOS
  end
  def test_break_labelled
    check(C::Break, <<-EOS)
      |one:
      |    break;
    EOS
  end

  # ------------------------------------------------------------------
  #                               Return
  # ------------------------------------------------------------------

  def test_return_with_expression
    check(C::Return, <<-EOS)
      |    return 0;
    EOS
  end
  def test_return_with_no_expression
    check(C::Return, <<-EOS)
      |    return;
    EOS
  end
  def test_return_labelled
    check(C::Return, <<-EOS)
      |one:
      |    return 0;
    EOS
  end

  # ------------------------------------------------------------------
  #                        ExpressionStatement
  # ------------------------------------------------------------------

  def test_expression_statement
    check(C::ExpressionStatement, <<-EOS)
      |    ;
    EOS
  end
  def test_expression_statement_with_expression
    check(C::ExpressionStatement, <<-EOS)
      |    1;
    EOS
  end
  def test_expression_statement_labelled
    check(C::ExpressionStatement, <<-EOS)
      |one:
      |    1;
    EOS
  end

  # ------------------------------------------------------------------
  #                             PlainLabel
  # ------------------------------------------------------------------

  def test_plain_label
    check(C::PlainLabel, <<-EOS)
      |one:
    EOS
  end

  # ------------------------------------------------------------------
  #                              Default
  # ------------------------------------------------------------------

  def test_default
    check(C::Default, <<-EOS)
      |default:
    EOS
  end

  # ------------------------------------------------------------------
  #                              TestCase
  # ------------------------------------------------------------------

  def test_case
    check(C::Case, <<-EOS)
      |case 1:
    EOS
  end

  # ------------------------------------------------------------------
  #                               Comma
  # ------------------------------------------------------------------

  def test_comma_two_expressions
    check(C::Comma, <<-EOS)
      |1, 2
    EOS
  end
  def test_comma_three_expressions
    check(C::Comma, <<-EOS)
      |1, 2, 3
    EOS
  end

  # ------------------------------------------------------------------
  #                            Conditional
  # ------------------------------------------------------------------

  def test_conditional
    check(C::Conditional, <<-EOS)
      |a ? b : c
    EOS
  end
  def test_conditional_precedences
    check(C::Or, <<-EOS)
      |a || (b ? c || d : e && f)
    EOS
  end
  def test_conditional_nested
    # TODO: handle else-if towers
    check(C::Conditional, <<-EOS)
      |(a ? b : c) ? (d ? e : f) : (g ? h : i)
    EOS
  end

  # ------------------------------------------------------------------
  #                              Variable
  # ------------------------------------------------------------------

  def test_variable
    check(C::Variable, <<-EOS)
      |x
    EOS
  end

  # ------------------------------------------------------------------
  #                          BlockExpression
  # ------------------------------------------------------------------

  def test_block_expression
    @parser.enable_block_expressions
    check(C::BlockExpression, <<-EOS)
      |({
      |    ;
      |})
    EOS
  end

  # ------------------------------------------------------------------
  #                               Index
  # ------------------------------------------------------------------

  def test_index
    check(C::Index, <<-EOS)
      |a[0]
    EOS
  end
  def test_index_precedences
    check(C::Index, <<-EOS)
      |(*a)[*a]
    EOS
  end
  def test_index_nested
    check(C::Index, <<-EOS)
      |a[a[0]][0]
    EOS
  end

  # ------------------------------------------------------------------
  #                                Call
  # ------------------------------------------------------------------

  def test_call
    check(C::Call, <<-EOS)
      |a()
    EOS
  end
  def test_call_with_args
    check(C::Call, <<-EOS)
      |a(1, (2, 3))
    EOS
  end
  def test_call_precedences
    check(C::Call, <<-EOS)
      |(*a)(*a)
    EOS
  end
  def test_call_nested
    check(C::Call, <<-EOS)
      |(*a())(a())
    EOS
  end

  # ------------------------------------------------------------------
  #                                Dot
  # ------------------------------------------------------------------

  def test_dot
    check(C::Dot, <<-EOS)
      |a.b
    EOS
  end
  def test_dot_precendences
    check(C::Dot, <<-EOS)
      |(a ? b : c).d
    EOS
  end
  def test_dot_nested
    check(C::Dot, <<-EOS)
      |a.b.c
    EOS
  end

  # ------------------------------------------------------------------
  #                               Arrow
  # ------------------------------------------------------------------

  def test_arrow
    check(C::Arrow, <<-EOS)
      |a->b
    EOS
  end
  def test_arrow_precedences
    check(C::Arrow, <<-EOS)
      |(a ? b : c)->d
    EOS
  end
  def test_arrow_nested
    check(C::Arrow, <<-EOS)
      |a->b->c
    EOS
  end

  # ------------------------------------------------------------------
  #                              PostInc
  # ------------------------------------------------------------------

  def test_post_inc
    check(C::PostInc, <<-EOS)
      |a++
    EOS
  end
  def test_post_inc_precedences
    check(C::PostInc, <<-EOS)
      |(a++ ? b++ : c++)++
    EOS
  end
  def test_post_inc_nested
    check(C::PostInc, <<-EOS)
      |a++++
    EOS
  end

  # ------------------------------------------------------------------
  #                              PostDec
  # ------------------------------------------------------------------

  def test_post_dec
    check(C::PostDec, <<-EOS)
      |a--
    EOS
  end
  def test_post_dec_precedences
    check(C::PostDec, <<-EOS)
      |(a-- ? b-- : c--)--
    EOS
  end
  def test_post_dec_nested
    check(C::PostDec, <<-EOS)
      |a----
    EOS
  end

  # ------------------------------------------------------------------
  #                                Cast
  # ------------------------------------------------------------------

  def test_cast
    check(C::Cast, <<-EOS)
      |(int)a
    EOS
  end
  def test_cast_precedences
    check(C::Cast, <<-EOS)
      |(int)((int)a + (int)b)
    EOS
  end
  def test_cast_nested
    check(C::Cast, <<-EOS)
      |(int)(int)a
    EOS
  end

  # ------------------------------------------------------------------
  #                              Address
  # ------------------------------------------------------------------

  def test_address
    check(C::Address, <<-EOS)
      |&a
    EOS
  end
  def test_address_precedences
    check(C::Address, <<-EOS)
      |&(a + 1)
    EOS
  end
  def test_address_nested
    check(C::Address, <<-EOS)
      |& &a
    EOS
  end

  # ------------------------------------------------------------------
  #                            Dereference
  # ------------------------------------------------------------------

  def test_dereference
    check(C::Dereference, <<-EOS)
      |*a
    EOS
  end
  def test_dereference_precedences
    check(C::Dereference, <<-EOS)
      |*(a + 1)
    EOS
  end
  def test_dereference_nested
    check(C::Dereference, <<-EOS)
      |**a
    EOS
  end

  # ------------------------------------------------------------------
  #                               Sizeof
  # ------------------------------------------------------------------

  def test_sizeof
    check(C::Sizeof, <<-EOS)
      |sizeof(a)
    EOS
  end
  def test_sizeof_precedences
    check(C::Index, <<-EOS)
      |(sizeof(a + 1))[b]
    EOS
  end
  def test_sizeof_nested
    check(C::Sizeof, <<-EOS)
      |sizeof(sizeof(a))
    EOS
  end

  # ------------------------------------------------------------------
  #                              Positive
  # ------------------------------------------------------------------

  def test_positive
    check(C::Positive, <<-EOS)
      |+a
    EOS
  end
  def test_positive_precedences
    check(C::Add, <<-EOS)
      |a + +(+a + +b)
    EOS
  end
  def test_positive_nested
    check(C::Positive, <<-EOS)
      |+ +a
    EOS
  end

  # ------------------------------------------------------------------
  #                              Negative
  # ------------------------------------------------------------------

  def test_negative
    check(C::Negative, <<-EOS)
      |-a
    EOS
  end
  def test_negative_precedences
    check(C::Subtract, <<-EOS)
      |a - -(-a - -b)
    EOS
  end
  def test_negative_nested
    check(C::Negative, <<-EOS)
      |- -a
    EOS
  end

  # ------------------------------------------------------------------
  #                               PreInc
  # ------------------------------------------------------------------

  def test_pre_inc
    check(C::PreInc, <<-EOS)
      |++a
    EOS
  end
  def test_pre_inc_precedences
    check(C::Add, <<-EOS)
      |++a + ++(++b + ++c)
    EOS
  end
  def test_pre_inc_nested
    check(C::PreInc, <<-EOS)
      |++++a
    EOS
  end

  # ------------------------------------------------------------------
  #                               PreDec
  # ------------------------------------------------------------------

  def test_pre_dec
    check(C::PreDec, <<-EOS)
      |--a
    EOS
  end
  def test_pre_dec_precedences
    check(C::Subtract, <<-EOS)
      |--a - --(--b - --c)
    EOS
  end
  def test_pre_dec_nested
    check(C::PreDec, <<-EOS)
      |----a
    EOS
  end

  # ------------------------------------------------------------------
  #                               BitNot
  # ------------------------------------------------------------------

  def test_bit_not
    check(C::BitNot, <<-EOS)
      |~a
    EOS
  end
  def test_bit_not_precedences
    check(C::Equal, <<-EOS)
      |~a == (~b | ~c)
    EOS
  end
  def test_bit_not_nested
    check(C::BitNot, <<-EOS)
      |~~a
    EOS
  end

  # ------------------------------------------------------------------
  #                                Not
  # ------------------------------------------------------------------

  def test_not
    check(C::Not, <<-EOS)
      |!a
    EOS
  end
  def test_not_precedences
    check(C::Equal, <<-EOS)
      |!a == !(!b || !c)
    EOS
  end
  def test_not_nested
    check(C::Not, <<-EOS)
      |!!a
    EOS
  end

  # ------------------------------------------------------------------
  #                                Add
  # ------------------------------------------------------------------

  def test_add
    check(C::Add, <<-EOS)
      |a + b
    EOS
  end
  def test_add_precedences
    check(C::Add, <<-EOS)
      |a * b + (c == d)
    EOS
  end
  def test_add_nested
    check(C::Add, <<-EOS)
      |a + b + (c + d)
    EOS
  end

  # ------------------------------------------------------------------
  #                              Subtract
  # ------------------------------------------------------------------

  def test_subtract
    check(C::Subtract, <<-EOS)
      |a - b
    EOS
  end
  def test_subtract_precedences
    check(C::Subtract, <<-EOS)
      |a * b - (c == d)
    EOS
  end
  def test_subtract_nested
    check(C::Subtract, <<-EOS)
      |a - b - (c - d)
    EOS
  end

  # ------------------------------------------------------------------
  #                              Multiply
  # ------------------------------------------------------------------

  def test_multiply
    check(C::Multiply, <<-EOS)
      |a * b
    EOS
  end
  def test_multiply_precedences
    check(C::Multiply, <<-EOS)
      |*a * (b + c)
    EOS
  end
  def test_multiply_nested
    check(C::Multiply, <<-EOS)
      |a * b * (c * d)
    EOS
  end

  # ------------------------------------------------------------------
  #                               Divide
  # ------------------------------------------------------------------

  def test_divide
    check(C::Divide, <<-EOS)
      |a / b
    EOS
  end
  def test_divide_precedences
    check(C::Divide, <<-EOS)
      |*a / (b + c)
    EOS
  end
  def test_divide_nested
    check(C::Divide, <<-EOS)
      |a / b / (c / d)
    EOS
  end

  # ------------------------------------------------------------------
  #                                Mod
  # ------------------------------------------------------------------

  def test_mod
    check(C::Mod, <<-EOS)
      |a % b
    EOS
  end
  def test_mod_precedences
    check(C::Mod, <<-EOS)
      |*a % (b + c)
    EOS
  end
  def test_mod_nested
    check(C::Mod, <<-EOS)
      |a % b % (c % d)
    EOS
  end

  # ------------------------------------------------------------------
  #                               Equal
  # ------------------------------------------------------------------

  def test_equal
    check(C::Equal, <<-EOS)
      |a == b
    EOS
  end
  def test_equal_precedences
    check(C::Equal, <<-EOS)
      |a + b == (c ? d : e)
    EOS
  end
  def test_equal_nested
    check(C::Equal, <<-EOS)
      |a == b == (c == d)
    EOS
  end

  # ------------------------------------------------------------------
  #                              NotEqual
  # ------------------------------------------------------------------

  def test_not_equal
    check(C::NotEqual, <<-EOS)
      |a != b
    EOS
  end
  def test_not_equal_precedences
    check(C::NotEqual, <<-EOS)
      |a + b != (c ? d : e)
    EOS
  end
  def test_not_equal_nested
    check(C::NotEqual, <<-EOS)
      |a != b != (c != d)
    EOS
  end

  # ------------------------------------------------------------------
  #                                Less
  # ------------------------------------------------------------------

  def test_less
    check(C::Less, <<-EOS)
      |a < b
    EOS
  end
  def test_less_precedences
    check(C::Less, <<-EOS)
      |a + b < (c ? d : e)
    EOS
  end
  def test_less_nested
    check(C::Less, <<-EOS)
      |a < b < (c < d)
    EOS
  end

  # ------------------------------------------------------------------
  #                                More
  # ------------------------------------------------------------------

  def test_more
    check(C::More, <<-EOS)
      |a > b
    EOS
  end
  def test_more_precedences
    check(C::More, <<-EOS)
      |a + b > (c ? d : e)
    EOS
  end
  def test_more_nested
    check(C::More, <<-EOS)
      |a > b > (c > d)
    EOS
  end

  # ------------------------------------------------------------------
  #                            LessOrEqual
  # ------------------------------------------------------------------

  def test_less_or_equal
    check(C::LessOrEqual, <<-EOS)
      |a <= b
    EOS
  end
  def test_less_or_equal_precedences
    check(C::LessOrEqual, <<-EOS)
      |a + b <= (c ? d : e)
    EOS
  end
  def test_less_or_equal_nested
    check(C::LessOrEqual, <<-EOS)
      |a <= b <= (c <= d)
    EOS
  end

  # ------------------------------------------------------------------
  #                            MoreOrEqual
  # ------------------------------------------------------------------

  def test_more_or_equal
    check(C::MoreOrEqual, <<-EOS)
      |a >= b
    EOS
  end
  def test_more_or_equal_precedences
    check(C::MoreOrEqual, <<-EOS)
      |a + b >= (c ? d : e)
    EOS
  end
  def test_more_or_equal_nested
    check(C::MoreOrEqual, <<-EOS)
      |a >= b >= (c >= d)
    EOS
  end

  # ------------------------------------------------------------------
  #                               BitAnd
  # ------------------------------------------------------------------

  def test_bit_and
    check(C::BitAnd, <<-EOS)
      |a & b
    EOS
  end
  def test_bit_and_precedences
    check(C::BitAnd, <<-EOS)
      |a + b & (c ? d : e)
    EOS
  end
  def test_bit_and_nested
    check(C::BitAnd, <<-EOS)
      |a & b & (c & d)
    EOS
  end

  # ------------------------------------------------------------------
  #                               BitOr
  # ------------------------------------------------------------------

  def test_bit_or
    check(C::BitOr, <<-EOS)
      |a | b
    EOS
  end
  def test_bit_or_precedences
    check(C::BitOr, <<-EOS)
      |a + b | (c ? d : e)
    EOS
  end
  def test_bit_or_nested
    check(C::BitOr, <<-EOS)
      |a | b | (c | d)
    EOS
  end

  # ------------------------------------------------------------------
  #                               BitXor
  # ------------------------------------------------------------------

  def test_bit_xor
    check(C::BitXor, <<-EOS)
      |a ^ b
    EOS
  end
  def test_bit_xor_precedences
    check(C::BitXor, <<-EOS)
      |a + b ^ (c ? d : e)
    EOS
  end
  def test_bit_xor_nested
    check(C::BitXor, <<-EOS)
      |a ^ b ^ (c ^ d)
    EOS
  end

  # ------------------------------------------------------------------
  #                             ShiftLeft
  # ------------------------------------------------------------------

  def test_shift_left
    check(C::ShiftLeft, <<-EOS)
      |a << b
    EOS
  end
  def test_shift_left_precedences
    check(C::ShiftLeft, <<-EOS)
      |a + b << (c ? d : e)
    EOS
  end
  def test_shift_left_nested
    check(C::ShiftLeft, <<-EOS)
      |a << b << (c << d)
    EOS
  end

  # ------------------------------------------------------------------
  #                             ShiftRight
  # ------------------------------------------------------------------

  def test_shift_right
    check(C::ShiftRight, <<-EOS)
      |a >> b
    EOS
  end
  def test_shift_right_precedences
    check(C::ShiftRight, <<-EOS)
      |a + b >> (c ? d : e)
    EOS
  end
  def test_shift_right_nested
    check(C::ShiftRight, <<-EOS)
      |a >> b >> (c >> d)
    EOS
  end

  # ------------------------------------------------------------------
  #                                And
  # ------------------------------------------------------------------

  def test_and
    check(C::And, <<-EOS)
      |a && b
    EOS
  end
  def test_and_precedences
    check(C::And, <<-EOS)
      |a + b && (c ? d : e)
    EOS
  end
  def test_and_nested
    check(C::And, <<-EOS)
      |a && b && (c && d)
    EOS
  end

  # ------------------------------------------------------------------
  #                                 Or
  # ------------------------------------------------------------------

  def test_or
    check(C::Or, <<-EOS)
      |a || b
    EOS
  end
  def test_or_precedences
    check(C::Or, <<-EOS)
      |a + b || (c ? d : e)
    EOS
  end
  def test_or_nested
    check(C::Or, <<-EOS)
      |a || b || (c || d)
    EOS
  end

  # ------------------------------------------------------------------
  #                               Assign
  # ------------------------------------------------------------------

  def test_assign
    check(C::Assign, <<-EOS)
      |a = b
    EOS
  end
  def test_assign_precedences
    check(C::Assign, <<-EOS)
      |a = b ? c : d
    EOS
  end
  def test_assign_nested
    check(C::Assign, <<-EOS)
      |a = b = (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                           MultiplyAssign
  # ------------------------------------------------------------------

  def test_multiply_assign
    check(C::MultiplyAssign, <<-EOS)
      |a *= b
    EOS
  end
  def test_multiply_assign_precedences
    check(C::MultiplyAssign, <<-EOS)
      |a *= b ? c : d
    EOS
  end
  def test_multiply_assign_nested
    check(C::MultiplyAssign, <<-EOS)
      |a *= b *= (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                            DivideAssign
  # ------------------------------------------------------------------

  def test_divide_assign
    check(C::DivideAssign, <<-EOS)
      |a /= b
    EOS
  end
  def test_divide_assign_precedences
    check(C::DivideAssign, <<-EOS)
      |a /= b ? c : d
    EOS
  end
  def test_divide_assign_nested
    check(C::DivideAssign, <<-EOS)
      |a /= b /= c
    EOS
  end

  # ------------------------------------------------------------------
  #                             ModAssign
  # ------------------------------------------------------------------

  def test_mod_assign
    check(C::ModAssign, <<-EOS)
      |a %= b
    EOS
  end
  def test_mod_assign_precedences
    check(C::ModAssign, <<-EOS)
      |a %= b ? c : d
    EOS
  end
  def test_mod_assign_nested
    check(C::ModAssign, <<-EOS)
      |a %= b %= (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                             AddAssign
  # ------------------------------------------------------------------

  def test_add_assign
    check(C::AddAssign, <<-EOS)
      |a += b
    EOS
  end
  def test_add_assign_precedences
    check(C::AddAssign, <<-EOS)
      |a += b ? c : d
    EOS
  end
  def test_add_assign_nested
    check(C::AddAssign, <<-EOS)
      |a += b += (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                           SubtractAssign
  # ------------------------------------------------------------------

  def test_subtract_assign
    check(C::SubtractAssign, <<-EOS)
      |a -= b
    EOS
  end
  def test_subtract_assign_precedences
    check(C::SubtractAssign, <<-EOS)
      |a -= b ? c : d
    EOS
  end
  def test_subtract_assign_nested
    check(C::SubtractAssign, <<-EOS)
      |a -= b -= (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                          ShiftLeftAssign
  # ------------------------------------------------------------------

  def test_shift_left_assign
    check(C::ShiftLeftAssign, <<-EOS)
      |a <<= b
    EOS
  end
  def test_shift_left_assign_precedences
    check(C::ShiftLeftAssign, <<-EOS)
      |a <<= b ? c : d
    EOS
  end
  def test_shift_left_assign_nested
    check(C::ShiftLeftAssign, <<-EOS)
      |a <<= b <<= (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                          ShiftRightAssign
  # ------------------------------------------------------------------

  def test_shift_right_assign
    check(C::ShiftRightAssign, <<-EOS)
      |a >>= b
    EOS
  end
  def test_shift_right_assign_precedences
    check(C::ShiftRightAssign, <<-EOS)
      |a >>= b ? c : d
    EOS
  end
  def test_shift_right_assign_nested
    check(C::ShiftRightAssign, <<-EOS)
      |a >>= b >>= (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                            BitAndAssign
  # ------------------------------------------------------------------

  def test_bit_and_assign
    check(C::BitAndAssign, <<-EOS)
      |a &= b
    EOS
  end
  def test_bit_and_assign_precedences
    check(C::BitAndAssign, <<-EOS)
      |a &= b ? c : d
    EOS
  end
  def test_bit_and_assign_nested
    check(C::BitAndAssign, <<-EOS)
      |a &= b &= (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                            BitXorAssign
  # ------------------------------------------------------------------

  def test_bit_xor_assign
    check(C::BitXorAssign, <<-EOS)
      |a ^= b
    EOS
  end
  def test_bit_xor_assign_precedences
    check(C::BitXorAssign, <<-EOS)
      |a ^= b ? c : d
    EOS
  end
  def test_bit_xor_assign_nested
    check(C::BitXorAssign, <<-EOS)
      |a ^= b ^= (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                            BitOrAssign
  # ------------------------------------------------------------------

  def test_bit_or_assign
    check(C::BitOrAssign, <<-EOS)
      |a |= b
    EOS
  end
  def test_bit_or_assign_precedences
    check(C::BitOrAssign, <<-EOS)
      |a |= b ? c : d
    EOS
  end
  def test_bit_or_assign_nested
    check(C::BitOrAssign, <<-EOS)
      |a |= b |= (c, d)
    EOS
  end

  # ------------------------------------------------------------------
  #                           StringLiteral
  # ------------------------------------------------------------------

  # TODO: handle unusual characters
  # TODO: handle prefixes (wide)
  def test_string_literal_empty
    check(C::StringLiteral, <<-EOS)
      |""
    EOS
  end
  def test_string_literal_simple
    check(C::StringLiteral, <<-EOS)
      |"hi"
    EOS
  end
  def test_string_literal_complex
    check(C::StringLiteral, <<-EOS)
      |"\0000\0xfff"
    EOS
  end

  # ------------------------------------------------------------------
  #                            CharLiteral
  # ------------------------------------------------------------------

  # TODO: handle unusual characters
  # TODO: handle prefixes (wide)
  def test_char_literal_simple
    check(C::CharLiteral, <<-EOS)
      |'x'
    EOS
  end
  def test_char_literal_complex
    check(C::CharLiteral, <<-EOS)
      |'\0000\0xfff'
    EOS
  end

  # ------------------------------------------------------------------
  #                          CompoundLiteral
  # ------------------------------------------------------------------

  def test_compound_literal_no_type
    check(C::CompoundLiteral, <<-EOS)
      |{
      |    1
      |}
    EOS
  end
  def test_compound_literal_type
    check(C::CompoundLiteral, <<-EOS)
      |(int []) {
      |    1
      |}
    EOS
  end
  def test_compound_literal_one_member
    check(C::CompoundLiteral, <<-EOS)
      |(struct s) {
      |    .one = 1
      |}
    EOS
  end
  def test_compound_literal_two_members
    check(C::CompoundLiteral, <<-EOS)
      |(union u) {
      |    .one = 1,
      |    .two = 2
      |}
    EOS
  end
  def test_compound_literal_nested
    check(C::CompoundLiteral, <<-EOS)
      |(T) {
      |    .one = (T) {
      |        2
      |    }
      |}
    EOS
  end

  # ------------------------------------------------------------------
  #                             IntLiteral
  # ------------------------------------------------------------------

  def test_int_literal_small
    check(C::IntLiteral, <<-EOS)
      |1
    EOS
  end
  def test_int_literal_oct
    check(C::IntLiteral, <<-EOS)
      |010
    EOS
  end
  def test_int_literal_hex
    check(C::IntLiteral, <<-EOS)
      |0x10
    EOS
  end
  def test_int_literal_suffix
    check(C::IntLiteral, <<-EOS)
      |1L
    EOS
  end
  # TODO: handle big ints -- this test fails
  def xtest_int_literal_big
    check(C::IntLiteral, <<-EOS)
      |10000000000
    EOS
  end

  # ------------------------------------------------------------------
  #                            FloatLiteral
  # ------------------------------------------------------------------

  # TODO: handle precisions properly -- probably need to store string,
  # or integer,mantissa,exponent separately
  def test_float_literal
    check(C::FloatLiteral, <<-EOS)
      |1.0
    EOS
  end
  def test_float_literal_exponent
    check(C::FloatLiteral, <<-EOS)
      |1.23e45
    EOS
  end
  def test_float_literal_suffix
    check(C::FloatLiteral, <<-EOS)
      |1.0f
    EOS
  end
  def test_float_literal_hex_exponent
    check(C::FloatLiteral, <<-EOS)
      |0x12.abp3
    EOS
  end
  def test_float_literal_precise
    check(C::FloatLiteral, <<-EOS)
      |1.0000000001
    EOS
  end

  # ------------------------------------------------------------------
  #                              Pointer
  # ------------------------------------------------------------------

  def test_pointer
    check(C::Pointer, <<-EOS)
      |int *
    EOS
  end
  def test_pointer_precedences
    check(C::Pointer, <<-EOS)
      |int *(*)[]
    EOS
  end
  def test_pointer_nested
    check(C::Pointer, <<-EOS)
      |int **
    EOS
  end

  # ------------------------------------------------------------------
  #                               Array
  # ------------------------------------------------------------------
  def test_array
    check(C::Array, <<-EOS)
      |int []
    EOS
  end
  def test_array_precedences
    check(C::Pointer, <<-EOS)
      |int (*)[]
    EOS
  end
  def test_array_nested
    check(C::Array, <<-EOS)
      |int [][]
    EOS
  end

  # ------------------------------------------------------------------
  #                              Function
  # ------------------------------------------------------------------

  def test_function
    check(C::Function, <<-EOS)
      |int ()
    EOS
  end
  def test_function_no_params
    check(C::Function, <<-EOS)
      |int (void)
    EOS
  end
  def test_function_params
    check(C::Function, <<-EOS)
      |int (int, int)
    EOS
  end
  def test_function_precedences
    check(C::Pointer, <<-EOS)
      |int (*)()
    EOS
  end
  def test_function_nested
    check(C::Function, <<-EOS)
      |int ()()
    EOS
  end

  # ------------------------------------------------------------------
  #                               Struct
  # ------------------------------------------------------------------

  def test_struct_basic
    check(C::Struct, <<-EOS)
      |struct {
      |    int i;
      |}
    EOS
  end
  def test_struct_with_name
    check(C::Struct, <<-EOS)
      |struct s {
      |    int i;
      |}
    EOS
  end
  def test_struct_with_two_members
    check(C::Struct, <<-EOS)
      |struct {
      |    int i;
      |    int j;
      |}
    EOS
  end
  def test_struct_with_all
    check(C::Struct, <<-EOS)
      |struct s {
      |    int i;
      |    int j;
      |}
    EOS
  end

  # ------------------------------------------------------------------
  #                               Union
  # ------------------------------------------------------------------

  def test_union_basic
    check(C::Union, <<-EOS)
      |union {
      |    int i;
      |}
    EOS
  end
  def test_union_with_name
    check(C::Union, <<-EOS)
      |union u {
      |    int i;
      |}
    EOS
  end
  def test_union_with_two_members
    check(C::Union, <<-EOS)
      |union {
      |    int i;
      |    int j;
      |}
    EOS
  end
  def test_union_with_all
    check(C::Union, <<-EOS)
      |union u {
      |    int i;
      |    int j;
      |}
    EOS
  end

  # ------------------------------------------------------------------
  #                                Enum
  # ------------------------------------------------------------------

  def test_enum_basic
    check(C::Enum, <<-EOS)
      |enum {
      |    E1
      |}
    EOS
  end
  def test_enum_with_name
    check(C::Enum, <<-EOS)
      |enum e {
      |    E1
      |}
    EOS
  end
  def test_enum_with_two_members
    check(C::Enum, <<-EOS)
      |enum {
      |    E1,
      |    E2
      |}
    EOS
  end
  def test_enum_with_all
    check(C::Enum, <<-EOS)
      |enum {
      |    E1,
      |    E2
      |}
    EOS
  end

  # ------------------------------------------------------------------
  #                             CustomType
  # ------------------------------------------------------------------

  def test_custom_type_unqualified
    check(C::CustomType, <<-EOS)
      |T
    EOS
  end
  def test_custom_type_qualified
    check(C::CustomType, <<-EOS)
      |const restrict volatile T
    EOS
  end

  # ------------------------------------------------------------------
  #                                Void
  # ------------------------------------------------------------------

  def test_void_unqualified
    check(C::Void, <<-EOS)
      |void
    EOS
  end
  def test_void_qualified
    check(C::Void, <<-EOS)
      |const restrict volatile void
    EOS
  end

  # ------------------------------------------------------------------
  #                                Int
  # ------------------------------------------------------------------

  def test_int_unqualified
    check(C::Int, <<-EOS)
      |int
    EOS
  end
  def test_int_qualified
    check(C::Int, <<-EOS)
      |const restrict volatile int
    EOS
  end
  def test_int_qualified_short
    check(C::Int, <<-EOS)
      |const restrict volatile short int
    EOS
  end
  def test_int_qualified_long
    check(C::Int, <<-EOS)
      |const restrict volatile long int
    EOS
  end
  def test_int_qualified_long_long
    check(C::Int, <<-EOS)
      |const restrict volatile long long int
    EOS
  end

  # ------------------------------------------------------------------
  #                               Float
  # ------------------------------------------------------------------

  def test_float_unqualified
    check(C::Float, <<-EOS)
      |float
    EOS
  end
  def test_float_qualified
    check(C::Float, <<-EOS)
      |const restrict volatile float
    EOS
  end
  def test_float_qualified_double
    check(C::Float, <<-EOS)
      |const restrict volatile double
    EOS
  end
  def test_float_qualified_long_double
    check(C::Float, <<-EOS)
      |const restrict volatile long double
    EOS
  end

  # ------------------------------------------------------------------
  #                                Char
  # ------------------------------------------------------------------

  def test_char_unqualified
    check(C::Char, <<-EOS)
      |char
    EOS
  end
  def test_char_qualified
    check(C::Char, <<-EOS)
      |const restrict volatile char
    EOS
  end
  def test_char_qualified_signed
    check(C::Char, <<-EOS)
      |const restrict volatile signed char
    EOS
  end
  def test_char_qualified_unsigned
    check(C::Char, <<-EOS)
      |const restrict volatile unsigned char
    EOS
  end

  # ------------------------------------------------------------------
  #                                Bool
  # ------------------------------------------------------------------

  def test_bool_unqualified
    check(C::Bool, <<-EOS)
      |_Bool
    EOS
  end
  def test_bool_qualified
    check(C::Bool, <<-EOS)
      |const restrict volatile _Bool
    EOS
  end

  # ------------------------------------------------------------------
  #                              Complex
  # ------------------------------------------------------------------

  def test_complex_unqualified
    check(C::Complex, <<-EOS)
      |_Complex float
    EOS
  end
  def test_complex_qualified
    check(C::Complex, <<-EOS)
      |const restrict volatile _Complex float
    EOS
  end
  def test_complex_qualified_double
    check(C::Complex, <<-EOS)
      |const restrict volatile _Complex double
    EOS
  end
  def test_complex_qualified_long_double
    check(C::Complex, <<-EOS)
      |const restrict volatile _Complex long double
    EOS
  end

  # ------------------------------------------------------------------
  #                             Imaginary
  # ------------------------------------------------------------------

  def test_imaginary_unqualified
    check(C::Imaginary, <<-EOS)
      |_Imaginary float
    EOS
  end
  def test_imaginary_qualified
    check(C::Imaginary, <<-EOS)
      |const restrict volatile _Imaginary float
    EOS
  end
  def test_imaginary_qualified_double
    check(C::Imaginary, <<-EOS)
      |const restrict volatile _Imaginary double
    EOS
  end
  def test_imaginary_qualified_long_double
    check(C::Imaginary, <<-EOS)
      |const restrict volatile _Imaginary long double
    EOS
  end
end
