# Treetop (http://treetop.rubyforge.org/) grammar for package definitions from
# the early days.

# Some aspects of this grammar are significantly dumber than they could be
# because:
#
#   * We want to treat statements as identically as possible to their
#     command-line equivalents.
#   * Treetop parse errors are pretty inscrutable at times and we can make
#     error messages clearer by validating a lot of the terminals ourselves.

require 'treetop'

require 'fig/grammar/base'
require 'fig/grammar/version'

module Fig
  module Grammar
    grammar V0
      include Fig::Grammar::Base
      include Fig::Grammar::Version

      rule package
        optional_ws
        grammar_version:grammar_version?
        ws*
        statements:(package_statement*)
        optional_ws
        {
          def to_package(unparsed_package, build_state)
            return build_state.new_package_statement(
              unparsed_package, grammar_version, statements
            )
          end
        }
      end

      rule package_statement
        archive / resource / retrieve / config
      end

      rule archive
        statement_start:'archive' ws+ location:asset_location {
          def to_package_statement(build_state)
            return build_state.new_asset_statement(
              Statement::Archive, statement_start, location.location
            )
          end
        }
      end

      rule resource
        statement_start:'resource' ws+ location:asset_location {
          def to_package_statement(build_state)
            return build_state.new_asset_statement(
              Statement::Resource, statement_start, location.location
            )
          end
        }
      end

      rule retrieve
        statement_start:'retrieve'
        ws+
        variable:environment_variable_name '->' path:retrieve_path
        ws+
        {
          def to_package_statement(build_state)
            return build_state.new_retrieve_statement(
              statement_start, variable, path
            )
          end
        }
      end

      rule config
        statement_start:'config'
        ws+
        config_name
        ws+
        statements:config_statement*
        'end'
        ws+
        {
          def to_package_statement(build_state)
            return build_state.new_configuration_statement(
              statement_start, config_name, statements
            )
          end
        }
      end

      rule config_statement
        override / include / command / path / set
      end

      rule include
        statement_start:'include' ws+ descriptor_string ws+ {
          def to_config_statement(build_state)
            return build_state.new_include_statement(
              statement_start, descriptor_string
            )
          end
        }
      end

      rule override
        statement_start:'override' ws+ descriptor_string ws+ {
          def to_config_statement(build_state)
            return build_state.new_override_statement(
              statement_start, descriptor_string
            )
          end
        }
      end

      rule set
        statement_start:'set' ws+ environment_variable_name_value ws+ {
          def to_config_statement(build_state)
            return build_state.new_environment_variable_statement(
              Statement::Set, statement_start, environment_variable_name_value
            )
          end
        }
      end

      rule path
        statement_start:('add' / 'append' / 'path')
        ws+
        environment_variable_name_value
        ws+
        {
          def to_config_statement(build_state)
            return build_state.new_environment_variable_statement(
              Statement::Path, statement_start, environment_variable_name_value
            )
          end
        }
      end

      rule command
        statement_start:'command' ws+ command_line ws+ {
          def to_config_statement(build_state)
            return build_state.new_v0_command_statement(
              statement_start, command_line
            )
          end
        }
      end

      rule command_line
        '"' [^"]* '"'
      end

      # Terminals

      rule descriptor_string
        [\S]+
      end

      rule config_name
        [a-zA-Z0-9_.-]+
      end

      rule environment_variable_name
        [a-zA-Z0-9_]+
      end

      rule environment_variable_name_value
        [\S]+
      end

      rule asset_location
        # Unquoted allows globbing for files, quoted does not.
        #
        # Unquoted, anything but:
        #    @      - To allow for package substitution
        #    '      - Future expansion
        #    "<>|   - Characters not allowed in filenames on Windows
        #    \s     - Necessary for the "ws" token to work
        (location:[^@'"<>|\s]+ ws)

        # Quoted, anything but:
        #    @        - To allow for package substitution
        #    '        - Future expansion
        #    "<>|     - Characters not allowed in filenames on Windows
        #    *?\[\]{} - Characters significant to Dir.glob()
        #    \s       - We just don't want these. :]
        / ('"' location:[^@'"<>|*?\[\]{}\s]+ '"' ws)
      end

      rule retrieve_path
        [a-zA-Z0-9_/.\[\]-]+
      end
    end
  end
end