#line 2 "common.rules"

/*************************************************************************
 * Copyright (C) 2010 Tavian Barnes <tavianator@gmail.com>               *
 *                                                                       *
 * This file is part of Dimension.                                       *
 *                                                                       *
 * Dimension is free software; you can redistribute it and/or modify it  *
 * under the terms of the GNU General Public License as published by the *
 * Free Software Foundation; either version 3 of the License, or (at     *
 * your option) any later version.                                       *
 *                                                                       *
 * Dimension is distributed in the hope that it will be useful, but      *
 * WITHOUT ANY WARRANTY; without even the implied warranty of            *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 * General Public License for more details.                              *
 *                                                                       *
 * You should have received a copy of the GNU General Public License     *
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 *************************************************************************/

/* Fundamental language elements */

IDENTIFIER: "identifier" {
            const char *id = $1;
            dmnsn_astnode *symbol = dmnsn_find_symbol(symtable, id);
            while (symbol && symbol->type == DMNSN_AST_IDENTIFIER) {
              id = symbol->ptr;
              symbol = dmnsn_find_symbol(symtable, id);
            }
            $$ = dmnsn_new_astnode(DMNSN_AST_IDENTIFIER, @$);
            $$.ptr = dmnsn_strdup(id);
            dmnsn_free($1);
          }
;

STRING: "string" {
        $$ = dmnsn_new_astnode(DMNSN_AST_STRING, @$);
        $$.ptr = $1;
      }
;

/* Transformations */

TRANSFORMATION: "rotate" VECTOR {
                dmnsn_astnode rotation
                  = dmnsn_new_astnode1(DMNSN_AST_ROTATION, @$, $2);
                $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$, rotation);
              }
              | "scale" VECTOR {
                dmnsn_astnode scale
                  = dmnsn_new_astnode1(DMNSN_AST_SCALE, @$, $2);
                $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$, scale);
              }
              | "translate" VECTOR {
                dmnsn_astnode translation
                  = dmnsn_new_astnode1(DMNSN_AST_TRANSLATION, @$, $2);
                $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$,
                                        translation);
              }
              | "matrix" "<" FLOAT "," FLOAT "," FLOAT ","
                             FLOAT "," FLOAT "," FLOAT ","
                             FLOAT "," FLOAT "," FLOAT ","
                             FLOAT "," FLOAT "," FLOAT ">"
              {
                dmnsn_astnode matrix = dmnsn_new_astnode(DMNSN_AST_MATRIX, @$);

                dmnsn_array_push(matrix.children, &$3);
                dmnsn_array_push(matrix.children, &$5);
                dmnsn_array_push(matrix.children, &$7);

                dmnsn_array_push(matrix.children, &$9);
                dmnsn_array_push(matrix.children, &$11);
                dmnsn_array_push(matrix.children, &$13);

                dmnsn_array_push(matrix.children, &$15);
                dmnsn_array_push(matrix.children, &$17);
                dmnsn_array_push(matrix.children, &$19);

                dmnsn_array_push(matrix.children, &$21);
                dmnsn_array_push(matrix.children, &$23);
                dmnsn_array_push(matrix.children, &$25);

                $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$, matrix);
              }
              | "transform" IDENTIFIER {
                dmnsn_astnode *trans = dmnsn_find_symbol(symtable, $2.ptr);
                if (!trans) {
                  dmnsn_diagnostic(@2, "unbound identifier '%s'",
                                   (const char *)$2.ptr);
                  dmnsn_delete_astnode($2);
                  YYERROR;
                }
                if (trans->type != DMNSN_AST_TRANSFORMATION) {
                  dmnsn_diagnostic(
                    @2, "identifier '%s' is a %s; expected a %s",
                    (const char *)$2.ptr,
                    dmnsn_astnode_string(trans->type),
                    dmnsn_astnode_string(DMNSN_AST_TRANSFORMATION)
                  );
                  dmnsn_delete_astnode($2);
                  YYERROR;
                }

                $$ = dmnsn_new_astnode(DMNSN_AST_TRANSFORMATION, @$);
                dmnsn_copy_children($$, *trans);
                dmnsn_delete_astnode($2);
              }
              | "transform" "{"
                  TRANSFORMATION_ITEMS
                "}"
              {
                $$ = $3;
              }
;

TRANSFORMATION_ITEMS: TRANSFORMATION_ITEM {
                      $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$, $1);
                    }
                    | TRANSFORMATION_ITEMS TRANSFORMATION_ITEM {
                      $$ = $1;
                      dmnsn_array_push($$.children, &$2);
                    }
;

TRANSFORMATION_ITEM: IDENTIFIER {
                     dmnsn_astnode *trans = dmnsn_find_symbol(symtable, $1.ptr);
                     if (!trans) {
                       dmnsn_diagnostic(@1, "unbound identifier '%s'",
                                        (const char *)$1.ptr);
                       dmnsn_delete_astnode($1);
                       YYERROR;
                     }
                     if (trans->type != DMNSN_AST_TRANSFORMATION) {
                       dmnsn_diagnostic(
                         @1, "identifier '%s' is a %s; expected a %s",
                         (const char *)$1.ptr,
                         dmnsn_astnode_string(trans->type),
                         dmnsn_astnode_string(DMNSN_AST_TRANSFORMATION)
                       );
                       dmnsn_delete_astnode($1);
                       YYERROR;
                     }

                     dmnsn_array_get(trans->children, 0, &$$);
                     ++*$$.refcount;
                     dmnsn_delete_astnode($1);
                   }
                   | TRANSFORMATION {
                     dmnsn_array_get($1.children, 0, &$$);
                     ++*$$.refcount;
                     dmnsn_delete_astnode($1);
                   }
                   | "inverse" {
                     $$ = dmnsn_new_astnode(DMNSN_AST_INVERSE, @$);
                   }
;

/* Cameras */

CAMERA: "camera" "{"
          CAMERA_ITEMS
        "}"
      {
        $$ = $3;
      }
;

CAMERA_ITEMS: /* empty */ {
              $$ = dmnsn_new_astnode(DMNSN_AST_CAMERA, @$);
            }
            | CAMERA_ITEMS CAMERA_ITEM {
              $$ = $1;
              dmnsn_array_push($$.children, &$2);
            }
;

CAMERA_ITEM: CAMERA_TYPE
           | CAMERA_VECTOR
           | CAMERA_MODIFIER
;

CAMERA_TYPE: "perspective" {
             $$ = dmnsn_new_astnode(DMNSN_AST_PERSPECTIVE, @$);
           }
;

CAMERA_VECTOR: "location" VECTOR {
               $$ = dmnsn_new_astnode1(DMNSN_AST_LOCATION, @$, $2);
             }
             | "right" VECTOR {
               $$ = dmnsn_new_astnode1(DMNSN_AST_RIGHT, @$, $2);
             }
             | "up" VECTOR {
               $$ = dmnsn_new_astnode1(DMNSN_AST_UP, @$, $2);
             }
             | "sky" VECTOR {
               $$ = dmnsn_new_astnode1(DMNSN_AST_SKY, @$, $2);
             }
             | "direction" VECTOR {
               $$ = dmnsn_new_astnode1(DMNSN_AST_DIRECTION, @$, $2);
             }
;

CAMERA_MODIFIER: "angle" FLOAT {
                 $$ = dmnsn_new_astnode1(DMNSN_AST_ANGLE, @$, $2);
               }
               | "look_at" VECTOR {
                 $$ = dmnsn_new_astnode1(DMNSN_AST_LOOK_AT, @$, $2);
               }
               | TRANSFORMATION
;

/* Objects */

OBJECT: FINITE_SOLID_OBJECT
      | INFINITE_SOLID_OBJECT
      | CSG_OBJECT
      | LIGHT_SOURCE
      | "object" "{"
          IDENTIFIER
          OBJECT_MODIFIERS
        "}"
      {
        dmnsn_astnode *object = dmnsn_find_symbol(symtable, $3.ptr);
        if (!object) {
          dmnsn_diagnostic(@3, "unbound identifier '%s'", (const char *)$3.ptr);
          dmnsn_delete_astnode($3);
          dmnsn_delete_astnode($4);
          YYERROR;
        }

        if (object->type == DMNSN_AST_OBJECT) {
          dmnsn_delete_astnode($3);

          $$ = dmnsn_new_astnode(object->type, @$);
          dmnsn_copy_children($$, *object);

          dmnsn_astnode *modifiers, orig_modifiers;
          modifiers = dmnsn_array_at($$.children, 1);
          dmnsn_array_get(object->children,
                          dmnsn_array_size(object->children) - 1,
                          &orig_modifiers);
          dmnsn_delete_astnode(*modifiers);
          *modifiers = dmnsn_new_astnode(DMNSN_AST_OBJECT_MODIFIERS, @4);
          dmnsn_copy_children(*modifiers, orig_modifiers);

          DMNSN_ARRAY_FOREACH (dmnsn_astnode *, astnode, $4.children) {
            ++*astnode->refcount;
            dmnsn_array_push(modifiers->children, astnode);
          }
          dmnsn_delete_astnode($4);
        } else {
          dmnsn_diagnostic(@3,
                           "identifier '%s' is a %s; expected an %s",
                           (const char *)$3.ptr,
                           dmnsn_astnode_string(object->type),
                           dmnsn_astnode_string(DMNSN_AST_OBJECT));
          dmnsn_delete_astnode($3);
          dmnsn_delete_astnode($4);
          YYERROR;
        }
      }
      | "object" "{"
          OBJECT
          OBJECT_MODIFIERS
        "}"
      {
        $$ = $3;

        dmnsn_astnode modifiers;
        dmnsn_array_get($$.children,
                        dmnsn_array_size($$.children) - 1,
                        &modifiers);

        DMNSN_ARRAY_FOREACH (dmnsn_astnode *, astnode, $4.children) {
          ++*astnode->refcount;
          dmnsn_array_push(modifiers.children, astnode);
        }

        dmnsn_delete_astnode($4);
      }
;

FINITE_SOLID_OBJECT: BOX
                   | CONE
                   | CYLINDER
                   | SPHERE
                   | TORUS
;

BOX: "box" "{"
       VECTOR "," VECTOR
       OBJECT_MODIFIERS
     "}"
   {
     dmnsn_astnode object = dmnsn_new_astnode2(DMNSN_AST_BOX, @$, $3, $5);
     $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, object, $6);
   }
;

CONE: "cone" "{"
        VECTOR "," FLOAT "," VECTOR "," FLOAT
        MAYBE_OPEN
        OBJECT_MODIFIERS
      "}"
    {
      dmnsn_astnode object
        = dmnsn_new_astnode5(DMNSN_AST_CONE, @$, $3, $5, $7, $9, $10);
      $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, object, $11);
    }
;

CYLINDER: "cylinder" "{"
            VECTOR "," VECTOR "," FLOAT
            MAYBE_OPEN
            OBJECT_MODIFIERS
          "}"
        {
          dmnsn_astnode object
            = dmnsn_new_astnode4(DMNSN_AST_CYLINDER, @$, $3, $5, $7, $8);
          $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, object, $9);
        }
;

MAYBE_OPEN: /* empty */ {
            $$ = dmnsn_new_ast_integer(false);
          }
          | "open" {
            $$ = dmnsn_new_ast_integer(true);
          }
;

SPHERE: "sphere" "{"
          VECTOR "," FLOAT
          OBJECT_MODIFIERS
        "}"
      {
        dmnsn_astnode object = dmnsn_new_astnode2(DMNSN_AST_SPHERE, @$, $3, $5);
        $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, object, $6);
      }
;

TORUS: "torus" "{"
         FLOAT "," FLOAT
         TORUS_MODIFIERS
       "}"
     {
       dmnsn_astnode object = dmnsn_new_astnode2(DMNSN_AST_TORUS, @$, $3, $5);
       $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, object, $6);
     }
;

TORUS_MODIFIERS: /* empty */ {
                 $$ = dmnsn_new_astnode(DMNSN_AST_OBJECT_MODIFIERS, @$);
               }
               | TORUS_MODIFIERS OBJECT_MODIFIER {
                 $$ = $1;
                 dmnsn_array_push($$.children, &$2);
               }
               | TORUS_MODIFIERS "sturm" {
                 dmnsn_diagnostic(@2,
                                  "WARNING: Dimension does not use 'sturm';"
                                  " ignored.");
                 dmnsn_astnode on = dmnsn_new_ast_integer(true);
                 dmnsn_astnode sturm = dmnsn_new_astnode1(DMNSN_AST_STURM, @2,
                                                          on);
                 $$ = $1;
                 dmnsn_array_push($$.children, &sturm);
               }
               | TORUS_MODIFIERS "sturm" INT {
                 dmnsn_diagnostic(@2,
                                  "WARNING: Dimension does not use 'sturm';"
                                  " ignored.");
                 dmnsn_astnode sturm = dmnsn_new_astnode1(DMNSN_AST_STURM, @2,
                                                          $3);
                 $$ = $1;
                 dmnsn_array_push($$.children, &sturm);
               }
;

INFINITE_SOLID_OBJECT: PLANE
;

PLANE: "plane" "{"
         VECTOR "," FLOAT
         OBJECT_MODIFIERS
       "}"
     {
       dmnsn_astnode object = dmnsn_new_astnode2(DMNSN_AST_PLANE, @$, $3, $5);
       $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, object, $6);
     }
;

CSG_OBJECT: UNION
          | INTERSECTION
          | DIFFERENCE
          | MERGE
;

UNION: "union" "{"
         OBJECTS
         OBJECT_MODIFIERS
       "}"
     {
       dmnsn_astnode csg_union = $3;
       csg_union.type = DMNSN_AST_UNION;
       $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, csg_union, $4);
     }
;

INTERSECTION: "intersection" "{"
                OBJECTS
                OBJECT_MODIFIERS
              "}"
            {
              dmnsn_astnode intersection = $3;
              intersection.type = DMNSN_AST_INTERSECTION;
              $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, intersection, $4);
            }
;

DIFFERENCE: "difference" "{"
              OBJECTS
              OBJECT_MODIFIERS
            "}"
          {
            dmnsn_astnode difference = $3;
            difference.type = DMNSN_AST_DIFFERENCE;
            $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, difference, $4);
          }
;

MERGE: "merge" "{"
         OBJECTS
         OBJECT_MODIFIERS
       "}"
     {
       dmnsn_astnode merge = $3;
       merge.type = DMNSN_AST_MERGE;
       $$ = dmnsn_new_astnode2(DMNSN_AST_OBJECT, @$, merge, $4);
     }
;

OBJECTS: OBJECT {
         $$ = dmnsn_new_astnode1(DMNSN_AST_ARRAY, @$, $1);
       }
       | OBJECTS OBJECT {
         $$ = $1;
         dmnsn_array_push($$.children, &$2);
       }
;

LIGHT_SOURCE: "light_source" "{"
                VECTOR "," COLOR
                OBJECT_MODIFIERS
              "}"
            {
              $$ = dmnsn_new_astnode3(DMNSN_AST_LIGHT_SOURCE, @$, $3, $5, $6);
            }
;

/* Object modifiers */

OBJECT_MODIFIERS: /* empty */ {
                  $$ = dmnsn_new_astnode(DMNSN_AST_OBJECT_MODIFIERS, @$);
                }
                | OBJECT_MODIFIERS OBJECT_MODIFIER {
                  $$ = $1;
                  dmnsn_array_push($$.children, &$2);
                }
;

OBJECT_MODIFIER: TRANSFORMATION
               | TEXTURE
               | PIGMENT
               | FINISH
               | INTERIOR
;

/* Patterns */

BLOCK_PATTERN_TYPE: "checker" {
                    dmnsn_astnode p
                      = dmnsn_new_astnode(DMNSN_AST_CHECKER, @$);
                    $$ = dmnsn_new_astnode1(DMNSN_AST_PATTERN, @$, p);
                  }
;

CONTINUOUS_PATTERN_TYPE: "gradient" VECTOR {
                         dmnsn_astnode p
                           = dmnsn_new_astnode1(DMNSN_AST_GRADIENT, @$, $2);
                         $$ = dmnsn_new_astnode1(DMNSN_AST_PATTERN, @$, p);
                       }
;

PATTERN_TYPE: BLOCK_PATTERN_TYPE
            | CONTINUOUS_PATTERN_TYPE
;

/* Textures */

TEXTURE: "texture" "{"
           TEXTURE_ITEMS
         "}"
       { $$ = $3; }
;

TEXTURE_ITEMS: /* empty */ {
               $$ = dmnsn_new_astnode(DMNSN_AST_TEXTURE, @$);
             }
             | TEXTURE_ITEMS PIGMENT {
               $$ = $1;
               dmnsn_array_push($$.children, &$2);
             }
             | TEXTURE_ITEMS FINISH {
               $$ = $1;
               dmnsn_array_push($$.children, &$2);
             }
             | TEXTURE_ITEMS TRANSFORMATION {
               $$ = $1;
               dmnsn_array_push($$.children, &$2);
             }
;

/* Pigments */

PIGMENT: "pigment" "{"
           PIGMENT_BODY
         "}"
       {
         $$ = $3;
       }
;

PIGMENT_BODY: PIGMENT_TYPE PIGMENT_MODIFIERS {
              $$ = dmnsn_new_astnode2(DMNSN_AST_PIGMENT, @$, $1, $2);
            }
            | "checker" COLOR_LIST2 PIGMENT_MODIFIERS {
              dmnsn_astnode checker = dmnsn_new_astnode(DMNSN_AST_CHECKER, @1);
              dmnsn_astnode pattern = dmnsn_new_astnode1(DMNSN_AST_PATTERN, @1,
                                                         checker);
              dmnsn_array_push($3.children, &$2);
              $$ = dmnsn_new_astnode2(DMNSN_AST_PIGMENT, @$, pattern, $3);
            }
            | "checker" PIGMENT_LIST2 PIGMENT_MODIFIERS {
              dmnsn_astnode checker = dmnsn_new_astnode(DMNSN_AST_CHECKER, @1);
              dmnsn_astnode pattern = dmnsn_new_astnode1(DMNSN_AST_PATTERN, @1,
                                                         checker);
              dmnsn_array_push($3.children, &$2);
              $$ = dmnsn_new_astnode2(DMNSN_AST_PIGMENT, @$, pattern, $3);
            }
;

PIGMENT_TYPE: COLOR
            | CONTINUOUS_PATTERN_TYPE
            | "image_map" "{"
                BITMAP_TYPE STRING
              "}"
            {
              $$ = dmnsn_new_astnode2(DMNSN_AST_IMAGE_MAP, @$, $3, $4);
            }
            | "image_map" "{"
                STRING
              "}"
            {
              dmnsn_astnode type = dmnsn_new_astnode(DMNSN_AST_PNG, @$);
              $$ = dmnsn_new_astnode2(DMNSN_AST_IMAGE_MAP, @$, type, $3);
            }
;

BITMAP_TYPE: "png" {
             $$ = dmnsn_new_astnode(DMNSN_AST_PNG, @$);
           }
;

PIGMENT_MODIFIERS: /* empty */ {
                   $$ = dmnsn_new_astnode(DMNSN_AST_PIGMENT_MODIFIERS, @$);
                 }
                 | PIGMENT_MODIFIERS TRANSFORMATION {
                   $$ = $1;
                   dmnsn_array_push($$.children, &$2);
                 }
                 | PIGMENT_MODIFIERS COLOR_MAP {
                   $$ = $1;
                   dmnsn_array_push($$.children, &$2);
                 }
                 | PIGMENT_MODIFIERS PIGMENT_MAP {
                   $$ = $1;
                   dmnsn_array_push($$.children, &$2);
                 }
                 | PIGMENT_MODIFIERS "quick_color" COLOR {
                   dmnsn_astnode quick_color
                     = dmnsn_new_astnode1(DMNSN_AST_QUICK_COLOR, @2, $3);
                   $$ = $1;
                   dmnsn_array_push($$.children, &quick_color);
                 }
;

COLOR_LIST2: /* empty */ {
             $$ = dmnsn_new_astnode(DMNSN_AST_COLOR_LIST, @$);
           }
           | COLOR {
             $$ = dmnsn_new_astnode1(DMNSN_AST_COLOR_LIST, @$, $1);
           }
           | COLOR "," COLOR {
             $$ = dmnsn_new_astnode2(DMNSN_AST_COLOR_LIST, @$, $1, $3);
           }
;

COLOR_MAP: "color_map" "{"
             COLOR_MAP_ENTRIES
           "}"
         {
           $$ = $3;
         }
;

COLOR_MAP_ENTRIES: COLOR_MAP_ENTRY {
                   $$ = dmnsn_new_astnode1(DMNSN_AST_COLOR_MAP, @$, $1);
                 }
                 | COLOR_MAP_ENTRIES COLOR_MAP_ENTRY {
                   $$ = $1;
                   dmnsn_array_push($$.children, &$2);
                 }
;

COLOR_MAP_ENTRY: "[" FLOAT "color" COLOR_BODY "]" {
                 $$ = dmnsn_new_astnode2(DMNSN_AST_COLOR_MAP_ENTRY, @$, $2, $4);
               }
;

PIGMENT_LIST2: PIGMENT {
               $$ = dmnsn_new_astnode1(DMNSN_AST_PIGMENT_LIST, @$, $1);
             }
             | PIGMENT PIGMENT {
               $$ = dmnsn_new_astnode2(DMNSN_AST_PIGMENT_LIST, @$, $1, $2);
             }
             | PIGMENT "," PIGMENT {
               $$ = dmnsn_new_astnode2(DMNSN_AST_PIGMENT_LIST, @$, $1, $3);
             }
;

PIGMENT_MAP: "pigment_map" "{"
               PIGMENT_MAP_ENTRIES
             "}"
           {
             $$ = $3;
           }
;

PIGMENT_MAP_ENTRIES: PIGMENT_MAP_ENTRY {
                     $$ = dmnsn_new_astnode1(DMNSN_AST_PIGMENT_MAP, @$, $1);
                   }
                   | PIGMENT_MAP_ENTRIES PIGMENT_MAP_ENTRY {
                     $$ = $1;
                     dmnsn_array_push($$.children, &$2);
                   }
;

PIGMENT_MAP_ENTRY: "[" FLOAT PIGMENT_BODY "]" {
                   $$ = dmnsn_new_astnode2(DMNSN_AST_PIGMENT_MAP_ENTRY, @$,
                                           $2, $3);
                 }
;

/* Finishes */
FINISH: "finish" "{"
          FINISH_ITEMS
        "}"
      { $$ = $3; }
;

FINISH_ITEMS: /* empty */ {
               $$ = dmnsn_new_astnode(DMNSN_AST_FINISH, @$);
            }
            | FINISH_ITEMS "ambient" COLOR {
              dmnsn_astnode ambient = dmnsn_new_astnode1(DMNSN_AST_AMBIENT,
                                                         @2, $3);
              $$ = $1;
              dmnsn_array_push($$.children, &ambient);
            }
            | FINISH_ITEMS "diffuse" FLOAT {
              dmnsn_astnode diffuse = dmnsn_new_astnode1(DMNSN_AST_DIFFUSE,
                                                         @2, $3);
              $$ = $1;
              dmnsn_array_push($$.children, &diffuse);
            }
            | FINISH_ITEMS "phong" FLOAT {
              dmnsn_astnode phong = dmnsn_new_astnode1(DMNSN_AST_PHONG, @2, $3);
              $$ = $1;
              dmnsn_array_push($$.children, &phong);
            }
            | FINISH_ITEMS "phong_size" FLOAT {
              dmnsn_astnode phong_size
                = dmnsn_new_astnode1(DMNSN_AST_PHONG_SIZE, @2, $3);
              $$ = $1;
              dmnsn_array_push($$.children, &phong_size);
            }
            | FINISH_ITEMS REFLECTION {
              $$ = $1;
              dmnsn_array_push($$.children, &$2);
            }
;

REFLECTION: "reflection" "{"
              COLOR
              REFLECTION_ITEMS
            "}"
          {
            ++*$3.refcount;
            $$ = dmnsn_new_astnode3(DMNSN_AST_REFLECTION, @$, $3, $3, $4);
          }
          | "reflection" "{"
              COLOR "," COLOR
              REFLECTION_ITEMS
            "}"
          {
            $$ = dmnsn_new_astnode3(DMNSN_AST_REFLECTION, @$, $3, $5, $6);
          }
;

REFLECTION_ITEMS: /* empty */ {
                  $$ = dmnsn_new_astnode(DMNSN_AST_REFLECTION_ITEMS, @$);
                }
                | REFLECTION_ITEMS "falloff" FLOAT {
                  dmnsn_astnode falloff
                    = dmnsn_new_astnode1(DMNSN_AST_FALLOFF, @2, $3);
                  $$ = $1;
                  dmnsn_array_push($$.children, &falloff);
                }
;

/* Interiors */
INTERIOR: "interior" "{"
            INTERIOR_ITEMS
          "}"
        { $$ = $3; }
;

INTERIOR_ITEMS: /* empty */ {
                $$ = dmnsn_new_astnode(DMNSN_AST_INTERIOR, @$);
              }
              | INTERIOR_ITEMS "ior" FLOAT {
                dmnsn_astnode diffuse = dmnsn_new_astnode1(DMNSN_AST_IOR,
                                                           @2, $3);
                $$ = $1;
                dmnsn_array_push($$.children, &diffuse);
              }
;

/* Floats */

FLOAT: ARITH_EXPR {
       $$ = dmnsn_eval_scalar($1, symtable);
       dmnsn_delete_astnode($1);

       if ($$.type == DMNSN_AST_NONE) {
         dmnsn_delete_astnode($$);
         YYERROR;
       }
     }
;

INT: FLOAT {
     $$ = $1;
     if ($$.type == DMNSN_AST_FLOAT) {
       dmnsn_diagnostic(@$, "WARNING: float rounded to integer");
     }
   }
;

FLOAT_LITERAL: "integer" {
               dmnsn_astnode string = dmnsn_new_astnode(DMNSN_AST_STRING, @$);
               string.ptr = $1;
               $$ = dmnsn_new_astnode1(DMNSN_AST_VAL, @$, string);
             }
             | "float" {
               dmnsn_astnode string = dmnsn_new_astnode(DMNSN_AST_STRING, @$);
               string.ptr = $1;
               $$ = dmnsn_new_astnode1(DMNSN_AST_VAL, @$, string);
             }
;

/* Vectors */

VECTOR: ARITH_EXPR {
        $$ = dmnsn_eval_vector($1, symtable);
        dmnsn_delete_astnode($1);

        if ($$.type == DMNSN_AST_NONE) {
          dmnsn_delete_astnode($$);
          YYERROR;
        }
      }
;

VECTOR_LITERAL: "<" ARITH_EXPR "," ARITH_EXPR ">" {
                $$ = dmnsn_new_astnode2(DMNSN_AST_VECTOR, @$, $2, $4);
              }
              |  "<" ARITH_EXPR "," ARITH_EXPR "," ARITH_EXPR ">" {
                $$ = dmnsn_new_astnode3(DMNSN_AST_VECTOR, @$, $2, $4, $6);
              }
              |  "<" ARITH_EXPR "," ARITH_EXPR "," ARITH_EXPR ","
                     ARITH_EXPR ">" {
                $$ = dmnsn_new_astnode4(DMNSN_AST_VECTOR, @$, $2, $4, $6, $8);
              }
              |  "<" ARITH_EXPR "," ARITH_EXPR "," ARITH_EXPR ","
                     ARITH_EXPR "," ARITH_EXPR ">" {
                $$ = dmnsn_new_astnode5(DMNSN_AST_VECTOR, @$,
                                        $2, $4, $6, $8, $10);
              }
;

/* Generalized arithmetic expressions */

ARITH_EXPR: FLOAT_LITERAL
          | VECTOR_LITERAL
          | ARITH_EXPR "+" ARITH_EXPR {
            $$ = dmnsn_new_astnode2(DMNSN_AST_ADD, @$, $1, $3);
          }
          | ARITH_EXPR "-" ARITH_EXPR {
            $$ = dmnsn_new_astnode2(DMNSN_AST_SUB, @$, $1, $3);
          }
          | ARITH_EXPR "*" ARITH_EXPR {
            $$ = dmnsn_new_astnode2(DMNSN_AST_MUL, @$, $1, $3);
          }
          | ARITH_EXPR "/" ARITH_EXPR {
            $$ = dmnsn_new_astnode2(DMNSN_AST_DIV, @$, $1, $3);
          }
          | "+" ARITH_EXPR %prec DMNSN_T_NEGATE { $$ = $2; }
          | "-" ARITH_EXPR %prec DMNSN_T_NEGATE {
            $$ = dmnsn_new_astnode1(DMNSN_AST_NEGATE, @$, $2);
          }
          | ARITH_EXPR "." "x" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_X, @$, $1);
          }
          | ARITH_EXPR "." "u" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_X, @$, $1);
          }
          | ARITH_EXPR "." "red" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_X, @$, $1);
          }
          | ARITH_EXPR "." "y" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_Y, @$, $1);
          }
          | ARITH_EXPR "." "v" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_Y, @$, $1);
          }
          | ARITH_EXPR "." "green" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_Y, @$, $1);
          }
          | ARITH_EXPR "." "z" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_Z, @$, $1);
          }
          | ARITH_EXPR "." "blue" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_Z, @$, $1);
          }
          | ARITH_EXPR "." "t" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_T, @$, $1);
          }
          | ARITH_EXPR "." "filter" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_T, @$, $1);
          }
          | ARITH_EXPR "." "transmit" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DOT_TRANSMIT, @$, $1);
          }
          | "(" ARITH_EXPR ")" %dprec 2 { $$ = $2; }
          | "(" CONDITIONAL "?" ARITH_EXPR ":" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode3(DMNSN_AST_TERNARY, @$, $2, $4, $6);
          }
          | "abs" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_ABS, @$, $3);
          }
          | "acos" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_ACOS, @$, $3);
          }
          | "acosh" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_ACOSH, @$, $3);
          }
          | "asc" "(" STRING ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_ASC, @$, $3);
          }
          | "asin" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_ASIN, @$, $3);
          }
          | "asinh" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_ASINH, @$, $3);
          }
          | "atan" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_ATAN, @$, $3);
          }
          | "atan2" "(" ARITH_EXPR "," ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode2(DMNSN_AST_ATAN2, @$, $3, $5);
          }
          | "atanh" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_ATANH, @$, $3);
          }
          | "ceil" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_CEIL, @$, $3);
          }
          | "cos" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_COS, @$, $3);
          }
          | "cosh" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_COSH, @$, $3);
          }
          | "degrees" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_DEGREES, @$, $3);
          }
          | "div" "(" ARITH_EXPR "," ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode2(DMNSN_AST_INT_DIV, @$, $3, $5);
          }
          | "exp" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_EXP, @$, $3);
          }
          | "floor" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_FLOOR, @$, $3);
          }
          | "int" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_INT, @$, $3);
          }
          | "ln" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_LN, @$, $3);
          }
          | "log" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_LOG, @$, $3);
          }
          | "max" "(" MAX_LIST ")" {
            $$ = $3;
          }
          | "min" "(" MIN_LIST ")" {
            $$ = $3;
          }
          | "mod" "(" ARITH_EXPR "," ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode2(DMNSN_AST_MOD, @$, $3, $5);
          }
          | "pow" "(" ARITH_EXPR "," ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode2(DMNSN_AST_POW, @$, $3, $5);
          }
          | "radians" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_RADIANS, @$, $3);
          }
          | "sin" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_SIN, @$, $3);
          }
          | "sinh" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_SINH, @$, $3);
          }
          | "sqrt" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_SQRT, @$, $3);
          }
          | "strcmp" "(" STRING "," STRING ")" {
            $$ = dmnsn_new_astnode2(DMNSN_AST_STRCMP, @$, $3, $5);
          }
          | "strlen" "(" STRING ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_STRLEN, @$, $3);
          }
          | "tan" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_TAN, @$, $3);
          }
          | "tanh" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_TANH, @$, $3);
          }
          | "val" "(" STRING ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_VAL, @$, $3);
          }
          | "vaxis_rotate" "(" ARITH_EXPR "," ARITH_EXPR "," ARITH_EXPR ")" {
            dmnsn_astnode axis
              = dmnsn_new_astnode1(DMNSN_AST_VNORMALIZE, @$, $5);
            axis = dmnsn_new_astnode2(DMNSN_AST_MUL, @$, $7, axis);
            $$ = dmnsn_new_astnode2(DMNSN_AST_VAXIS_ROTATE, @$, $3, axis);
          }
          | "vcross" "(" ARITH_EXPR "," ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode2(DMNSN_AST_VCROSS, @$, $3, $5);
          }
          | "vdot" "(" ARITH_EXPR "," ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode2(DMNSN_AST_VDOT, @$, $3, $5);
          }
          | "vlength" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_VLENGTH, @$, $3);
          }
          | "vnormalize" "(" ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode1(DMNSN_AST_VNORMALIZE, @$, $3);
          }
          | "vrotate" "(" ARITH_EXPR "," ARITH_EXPR ")" {
            $$ = dmnsn_new_astnode2(DMNSN_AST_VROTATE, @$, $3, $5);
          }
          | "image_height" {
            $$ = dmnsn_new_astnode(DMNSN_AST_IDENTIFIER, @$);
            $$.ptr = dmnsn_strdup("image_height");
          }
          | "image_width" {
            $$ = dmnsn_new_astnode(DMNSN_AST_IDENTIFIER, @$);
            $$.ptr = dmnsn_strdup("image_width");
          }
          | "pi"    { $$ = dmnsn_new_astnode(DMNSN_AST_PI,    @$); }
          | "true"  { $$ = dmnsn_new_astnode(DMNSN_AST_TRUE,  @$); }
          | "on"    { $$ = dmnsn_new_astnode(DMNSN_AST_TRUE,  @$); }
          | "yes"   { $$ = dmnsn_new_astnode(DMNSN_AST_TRUE,  @$); }
          | "false" { $$ = dmnsn_new_astnode(DMNSN_AST_FALSE, @$); }
          | "off"   { $$ = dmnsn_new_astnode(DMNSN_AST_FALSE, @$); }
          | "no"    { $$ = dmnsn_new_astnode(DMNSN_AST_FALSE, @$); }
          | "x"     { $$ = dmnsn_new_astnode(DMNSN_AST_X,     @$); }
          | "u"     { $$ = dmnsn_new_astnode(DMNSN_AST_X,     @$); }
          | "y"     { $$ = dmnsn_new_astnode(DMNSN_AST_Y,     @$); }
          | "v"     { $$ = dmnsn_new_astnode(DMNSN_AST_Y,     @$); }
          | "z"     { $$ = dmnsn_new_astnode(DMNSN_AST_Z,     @$); }
          | "t"     { $$ = dmnsn_new_astnode(DMNSN_AST_T,     @$); }
          | IDENTIFIER
;

MAX_LIST: ARITH_EXPR "," ARITH_EXPR {
          $$ = dmnsn_new_astnode2(DMNSN_AST_MAX, @$, $1, $3);
        }
        | MAX_LIST "," ARITH_EXPR {
          $$ = dmnsn_new_astnode2(DMNSN_AST_MAX, @$, $1, $3);
        }
;

MIN_LIST: ARITH_EXPR "," ARITH_EXPR {
          $$ = dmnsn_new_astnode2(DMNSN_AST_MIN, @$, $1, $3);
        }
        | MIN_LIST "," ARITH_EXPR {
          $$ = dmnsn_new_astnode2(DMNSN_AST_MIN, @$, $1, $3);
        }
;

CONDITIONAL: ARITH_EXPR {
             /* Force the expression to be evaluated logically */
             dmnsn_astnode zero = dmnsn_new_ast_integer(0);
             $$ = dmnsn_new_astnode2(DMNSN_AST_OR, @$, zero, $1);
           }
           | ARITH_EXPR "="  ARITH_EXPR {
             $$ = dmnsn_new_astnode2(DMNSN_AST_EQUAL, @$, $1, $3);
           }
           | ARITH_EXPR "!=" ARITH_EXPR {
             $$ = dmnsn_new_astnode2(DMNSN_AST_NOT_EQUAL, @$, $1, $3);
           }
           | ARITH_EXPR "<"  ARITH_EXPR {
             $$ = dmnsn_new_astnode2(DMNSN_AST_LESS, @$, $1, $3);
           }
           | ARITH_EXPR "<=" ARITH_EXPR {
             $$ = dmnsn_new_astnode2(DMNSN_AST_LESS_EQUAL, @$, $1, $3);
           }
           | ARITH_EXPR ">"  ARITH_EXPR {
             $$ = dmnsn_new_astnode2(DMNSN_AST_GREATER, @$, $1, $3);
           }
           | ARITH_EXPR ">=" ARITH_EXPR {
             $$ = dmnsn_new_astnode2(DMNSN_AST_GREATER_EQUAL, @$, $1, $3);
           }
           | CONDITIONAL "&" CONDITIONAL {
             $$ = dmnsn_new_astnode2(DMNSN_AST_AND, @$, $1, $3);
           }
           | CONDITIONAL "|" CONDITIONAL {
             $$ = dmnsn_new_astnode2(DMNSN_AST_OR, @$, $1, $3);
           }
           | "(" CONDITIONAL ")" %dprec 1 {
             $$ = $2;
           }
           | "!" CONDITIONAL {
             $$ = dmnsn_new_astnode1(DMNSN_AST_NOT, @$, $2);
           }
;

/* Colors */

COLOR: COLOR_BODY {
       $$ = $1;
     }
     | "color" COLOR_BODY {
       $$ = $2;
     }
;

COLOR_BODY: COLOR_VECTOR        %dprec 1
          | COLOR_KEYWORD_GROUP %dprec 2
;

COLOR_VECTOR: "rgb" VECTOR {
              dmnsn_astnode f, t;
              dmnsn_array_get($2.children, 3, &f);
              dmnsn_array_get($2.children, 4, &t);
              dmnsn_delete_astnode(f);
              dmnsn_delete_astnode(t);

              dmnsn_array_resize($2.children, 3);

              $$ = dmnsn_eval_vector($2, symtable);
              dmnsn_delete_astnode($2);
            }
            | "rgbf" VECTOR {
              dmnsn_astnode t;
              dmnsn_array_get($2.children, 4, &t);
              dmnsn_delete_astnode(t);

              dmnsn_array_resize($2.children, 4);

              $$ = dmnsn_eval_vector($2, symtable);
              dmnsn_delete_astnode($2);
            }
            | "rgbt" VECTOR {
              /* Chop off the fifth component */
              dmnsn_astnode t;
              dmnsn_array_get($2.children, 4, &t);
              dmnsn_delete_astnode(t);

              dmnsn_array_resize($2.children, 4);

              $$ = dmnsn_eval_vector($2, symtable);
              dmnsn_delete_astnode($2);

              /* Swap the transmit and filter components */
              dmnsn_astnode temp;
              dmnsn_array_get($$.children, 4, &temp);
              dmnsn_array_set($$.children, 4, dmnsn_array_at($$.children, 3));
              dmnsn_array_set($$.children, 3, &temp);
            }
            | "rgbft" VECTOR { $$ = $2; }
            | VECTOR
;

COLOR_KEYWORD_GROUP: COLOR_KEYWORD_GROUP_INIT COLOR_KEYWORD_ITEM
                   | COLOR_KEYWORD_GROUP      COLOR_KEYWORD_ITEM
;

COLOR_KEYWORD_GROUP_INIT: /* empty */ {
                          dmnsn_astnode zero = dmnsn_new_ast_integer(0);
                          $$ = dmnsn_eval_vector(zero, symtable);
                          dmnsn_delete_astnode(zero);
                        }
;

COLOR_KEYWORD_ITEM: IDENTIFIER {
                    dmnsn_astnode *symbol = dmnsn_find_symbol(symtable, $1.ptr);
                    if (!symbol) {
                      dmnsn_diagnostic(@1, "unbound identifier '%s'",
                                       (const char *)$1.ptr);
                      dmnsn_delete_astnode($1);
                      YYERROR;
                    } else {
                      dmnsn_astnode eval = dmnsn_eval_vector(*symbol, symtable);
                      if (eval.type == DMNSN_AST_NONE) {
                        dmnsn_delete_astnode($1);
                        YYERROR;
                      }

                      dmnsn_copy_children($<astnode>0, eval);
                      dmnsn_delete_astnode(eval);
                    }

                    dmnsn_delete_astnode($1);
                  }
                  | "red" FLOAT {
                    dmnsn_astnode old;
                    dmnsn_array_get($<astnode>0.children, 0, &old);
                    dmnsn_array_set($<astnode>0.children, 0, &$2);
                    dmnsn_delete_astnode(old);
                  }
                  | "green" FLOAT {
                    dmnsn_astnode old;
                    dmnsn_array_get($<astnode>0.children, 1, &old);
                    dmnsn_array_set($<astnode>0.children, 1, &$2);
                    dmnsn_delete_astnode(old);
                  }
                  | "blue" FLOAT {
                    dmnsn_astnode old;
                    dmnsn_array_get($<astnode>0.children, 2, &old);
                    dmnsn_array_set($<astnode>0.children, 2, &$2);
                    dmnsn_delete_astnode(old);
                  }
                  | "filter" FLOAT {
                    dmnsn_astnode old;
                    dmnsn_array_get($<astnode>0.children, 3, &old);
                    dmnsn_array_set($<astnode>0.children, 3, &$2);
                    dmnsn_delete_astnode(old);
                  }
                  | "transmit" FLOAT {
                    dmnsn_astnode old;
                    dmnsn_array_get($<astnode>0.children, 4, &old);
                    dmnsn_array_set($<astnode>0.children, 4, &$2);
                    dmnsn_delete_astnode(old);
                  }
;