# Reusable parts of Treetop (http://treetop.rubyforge.org/) grammar for package # definitions in v1 format. # 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. # # Just say "NO!" to PEG parsers. require 'treetop' module Fig module Grammar # Consumers of this need to mix in Fig::Grammar::Base and # Fig::Grammar::Version and have a "rule config_statement". grammar V1Base # Shim between "package" and "package_statement" rules to allow the # compiled v0 and v1 grammars to have the same interface. rule package_statement_with_ws package_statement:package_statement ws_or_comment+ { def to_package_statement(build_state) return package_statement.to_package_statement(build_state) end } end rule package_statement archive / resource / retrieve / config end rule archive statement_start:'archive' ws_or_comment+ location:quoted_or_bare_string { def to_package_statement(build_state) return build_state.new_asset_statement( Statement::Archive, statement_start, location ) end } end rule resource statement_start:'resource' ws_or_comment+ location:quoted_or_bare_string { def to_package_statement(build_state) return build_state.new_asset_statement( Statement::Resource, statement_start, location ) end } end rule retrieve statement_start:'retrieve' ws_or_comment+ variable:environment_variable_name '->' path:quoted_or_bare_string { def to_package_statement(build_state) return build_state.new_retrieve_statement( statement_start, variable, path ) end } end rule config statement_start:'config' ws_or_comment+ config_name ws_or_comment+ statements:config_statement_with_ws* 'end' { def to_package_statement(build_state) return build_state.new_configuration_statement( statement_start, config_name, statements ) end } end # Shim between "config" and "config_statement" rules to allow the # compiled v0 and v1 grammars to have the same interface. rule config_statement_with_ws config_statement:config_statement ws_or_comment+ { def to_config_statement(build_state) return config_statement.to_config_statement(build_state) end } end # Need rule config_statement rule include statement_start:'include' ws_or_comment+ descriptor_string { def to_config_statement(build_state) return build_state.new_include_statement( statement_start, descriptor_string ) end } end rule override statement_start:'override' ws_or_comment+ descriptor_string { def to_config_statement(build_state) return build_state.new_override_statement( statement_start, descriptor_string ) end } end rule set statement_start:'set' ws_or_comment+ environment_variable_name_value { 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_or_comment+ environment_variable_name_value { 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_or_comment+ command_line ws_or_comment+ 'end' { def to_config_statement(build_state) return build_state.new_v1_command_statement( statement_start, gather_command_argument_nodes(command_line) ) end def gather_command_argument_nodes(node, arguments = []) if node.respond_to? 'quoted_or_bare_string?' arguments << node return arguments end return arguments if not node.elements node.elements.each do |element| gather_command_argument_nodes(element, arguments) end return arguments end } end rule command_line quoted_or_bare_string ! { |sequence| sequence[-1].text_value == 'end' } ( ws_or_comment+ quoted_or_bare_string ! { |sequence| sequence[-1].text_value == 'end' } )* end # Terminals rule descriptor_string [^\s#]+ end rule config_name [a-zA-Z0-9_.-]+ end rule quoted_or_bare_string # In order to deal with the hierarchy of nodes that the command_line # rule above generates, we tag each of the expressions here so that # they can be found in the syntax tree. [^\s#\\"]* '"' ( [^"\\] / '\\' . )* '"' { def quoted_or_bare_string?() return true end } / [^\s#\\']* "'" ( [^'\\] / '\\' . )* "'" { def quoted_or_bare_string?() return true end } / [^\s#]+ { def quoted_or_bare_string?() return true end } end rule environment_variable_name [a-zA-Z0-9_]+ end rule environment_variable_name_value # This is like quoted_or_bare_string, but allows for the unquoted # variable name followed by equals sign prior to the quotation marks # coming in. # # Should result in somewhat reasonable handling by Treetop when # encountering mis-quoted constructs. [^\s#\\'"]* '"' ( [^"\\] / '\\' . )* '"' / [^\s#\\'"]* "'" ( [^'\\] / '\\' . )* "'" / [^\s#]+ end end end end