// This file is part of Cait . // Copyright (C) 2022-2022 Fuwn // // This program 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, version 3. // // This program 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 . // // Copyright (C) 2022-2022 Fuwn // SPDX-License-Identifier: GPL-3.0-only #include #include "parser.hh" #include "utility.hh" namespace cait { parser_exception::parser_exception(const std::string &message) : std::runtime_error(message) {} auto parser_exception::x() -> void {} token_bundle::token_bundle(std::size_t &_j, token_t &bundle_token, std::vector &_parsed_token_line, std::vector &_token_line) : j(_j), token(bundle_token), parsed_token_line(_parsed_token_line), token_line(_token_line) {} template range_t::range_t(T begin, T end) : _begin(begin), _end(end) {} template auto range_t::begin() -> T { return this->_begin; } template auto range_t::end() -> T { return this->_end; } template auto range(T b, T e) -> range_t { return range_t(b, e); } parser::parser(token_tree &token_tree) { this->fix_tree(token_tree); this->prune_empty_token_lines(); } [[nodiscard]] auto parser::tree() const noexcept -> const token_tree & { return this->_tree; } [[nodiscard]] auto parser::nodes() const noexcept -> const node_tree & { return this->_nodes; } auto parser::token_parse_assignment_operator(token_bundle bundle) -> void { auto &local = bundle.token_line[bundle.j - 1]; if (local.type != token_type::pool_depth) { local = local.with_type(token_type::variable_declaration); } bundle.parsed_token_line.push_back(local); bundle.parsed_token_line.push_back(bundle.token); for (auto &token_line_item : range(bundle.token_line.begin() + 2, bundle.token_line.end())) { token_line_item.type = token_type::variable_value; } bundle.parsed_token_line.insert(bundle.parsed_token_line.end(), bundle.token_line.begin() + 2, bundle.token_line.end()); bundle.j += bundle.token_line.size() - 1; } auto parser::token_parse_rule_pool_declaration(token_bundle bundle) -> void { bundle.parsed_token_line.push_back(bundle.token); bundle.parsed_token_line.push_back( bundle.token_line[bundle.j + 1].with_type(token_type::identifier)); bundle.j += bundle.token_line.size(); } auto parser::token_parse_default_declaration(token_bundle bundle) -> void { bundle.parsed_token_line.push_back(bundle.token); for (auto &token_line_item : range(bundle.token_line.begin() + 1, bundle.token_line.end())) { token_line_item.type = token_type::default_target; } bundle.parsed_token_line.insert(bundle.parsed_token_line.end(), bundle.token_line.begin() + 1, bundle.token_line.end()); bundle.j += bundle.token_line.size(); } auto parser::token_parse_include_subninja_declaration(token_bundle bundle) -> void { bundle.parsed_token_line.push_back(bundle.token); bundle.parsed_token_line.push_back( bundle.token_line[bundle.j + 1].with_type(token_type::string_literal)); bundle.j += bundle.token_line.size(); } auto parser::token_parse_rule_variable(token_bundle bundle) -> void { bundle.parsed_token_line.push_back(bundle.token); for (auto &token_line_item : range(bundle.token_line.begin() + 2, bundle.token_line.end())) { token_line_item.type = token_type::variable_value; } bundle.parsed_token_line.insert(bundle.parsed_token_line.end(), bundle.token_line.begin() + 1, bundle.token_line.end()); bundle.j += bundle.token_line.size() - 1; } auto parser::token_parse_build_declaration(token_bundle bundle) -> void { bundle.parsed_token_line.push_back(bundle.token); { auto &output_file_token = bundle.token_line[bundle.j + 1]; output_file_token.word = output_file_token.word.substr(0, output_file_token.word.size() - 1); bundle.parsed_token_line.push_back( output_file_token.with_type(token_type::build_output)); // We can actually just throw away the colon... // parsed_token_line.push_back( // cait::token(output_file_token.line, output_file_token.begin + // 1, // output_file_token.end + 1, ":") // .with_type(token_type::colon)); } bundle.token_line[2].type = token_type::build_rule; for (auto &token_line_item : range(bundle.token_line.begin() + 3, bundle.token_line.end())) { token_line_item.type = token_type::build_input; } bundle.parsed_token_line.insert(bundle.parsed_token_line.end(), bundle.token_line.begin() + 2, bundle.token_line.end()); bundle.j += bundle.token_line.size(); } auto parser::fix_tree(token_tree &token_tree) -> void { for (auto &token_line : token_tree) { std::vector parsed_token_line; for (std::size_t j = 0; j < token_line.size(); ++j) { auto &token = token_line[j]; switch (token.type) { case token_type::assignment_operator: { this->token_parse_assignment_operator( token_bundle(j, token, parsed_token_line, token_line)); } break; case token_type::rule_declaration: case token_type::pool_declaration: { this->token_parse_rule_pool_declaration( token_bundle(j, token, parsed_token_line, token_line)); } break; case token_type::default_declaration: { this->token_parse_default_declaration( token_bundle(j, token, parsed_token_line, token_line)); } break; case token_type::include_declaration: case token_type::subninja_declaration: { this->token_parse_include_subninja_declaration( token_bundle(j, token, parsed_token_line, token_line)); } break; case token_type::rule_command: case token_type::rule_depfile: case token_type::rule_deps: case token_type::rule_msvc_deps_prefix: case token_type::rule_description: case token_type::rule_dyndep: case token_type::rule_generator: case token_type::rule_in: case token_type::rule_in_newline: case token_type::rule_out: case token_type::rule_restat: case token_type::rule_rspfile: case token_type::rule_rspfile_content: { this->token_parse_rule_variable( token_bundle(j, token, parsed_token_line, token_line)); } break; case token_type::build_declaration: { this->token_parse_build_declaration( token_bundle(j, token, parsed_token_line, token_line)); } break; case token_type::string_literal: case token_type::variable_declaration: case token_type::identifier: case token_type::build_output: case token_type::build_input: case token_type::build_rule: case token_type::default_target: case token_type::variable_value: case token_type::pool_depth: break; } this->_tree.push_back(parsed_token_line); } } } auto parser::generate_nodes(const std::string &file_name, const std::vector &lines) -> void { for (std::size_t i = 0; i < this->_tree.size(); ++i) { auto &token_line = this->_tree[i]; for (std::size_t j = 0; j < token_line.size(); ++j) { auto &token = token_line[j]; switch (token.type) { case token_type::variable_declaration: { this->_nodes.push_back( node::variable(token_line[0], std::vector(token_line.begin() + 2, token_line.end()))); j += token_line.size(); } break; case token_type::default_declaration: { this->_nodes.push_back(node::default_( std::vector(token_line.begin() + 1, token_line.end()))); } break; case token_type::include_declaration: { this->_nodes.push_back(node::include(token_line[1])); } break; case token_type::subninja_declaration: { this->_nodes.push_back(node::subninja(token_line[1])); } break; case token_type::build_declaration: { this->_nodes.push_back( node::build(token_line[1], token_line[2], std::vector(token_line.begin() + 3, token_line.end()))); } break; case token_type::rule_declaration: { auto command_line_index = i + 1; auto &command_line = this->_tree[command_line_index]; node::item_map_type item_map; while (command_line[0].type == token_type::rule_command || command_line[0].type == token_type::rule_description || command_line[0].type == token_type::rule_depfile || command_line[0].type == token_type::rule_deps || command_line[0].type == token_type::rule_msvc_deps_prefix || command_line[0].type == token_type::rule_dyndep || command_line[0].type == token_type::rule_generator || command_line[0].type == token_type::rule_in || command_line[0].type == token_type::rule_in_newline || command_line[0].type == token_type::rule_out || command_line[0].type == token_type::rule_restat || command_line[0].type == token_type::rule_rspfile || command_line[0].type == token_type::rule_rspfile_content) { item_map.insert(std::make_pair( command_line[0].word, std::vector(command_line.begin() + 2, command_line.end()))); command_line_index += 1; command_line = this->_tree[command_line_index]; } // Handle unknown rule variable error if (command_line[1].type == token_type::assignment_operator) { std::ostringstream error; error << "cait: error: " << file_name << ":" << command_line[0].line << ": unexpected variable '" << command_line[0].word << "'\n" << lines[command_line[0].line - 1] << "\n"; for (auto &word : command_line) { error << std::string(word.word.size(), ' '); } auto leading_spaces = [](const std::string &string) -> std::size_t { std::size_t count = 0; while (string[count] == ' ') { count += 1; } return count; }; error << std::string(leading_spaces(lines[command_line[0].line - 1]), ' ') << " ^ near here\n"; throw parser_exception(error.str()); } // Handle no command line error if (item_map.count("command") == 0) { std::ostringstream error; error << "cait: error: " << file_name << ":" << command_line[0].line << ": expected 'command =' line\n"; throw parser_exception(error.str()); } this->_nodes.push_back(node::rule(token_line[1], item_map)); i += 1; } break; case token_type::pool_declaration: { this->_nodes.push_back( node::pool(token_line[1], this->_tree[i + 1][2])); i += 1; } break; case token_type::string_literal: std::cout << "string: " << token.word << "\n"; break; case token_type::rule_command: case token_type::assignment_operator: case token_type::identifier: case token_type::build_output: case token_type::rule_depfile: case token_type::rule_deps: case token_type::rule_msvc_deps_prefix: case token_type::rule_description: case token_type::rule_dyndep: case token_type::rule_generator: case token_type::rule_in: case token_type::rule_in_newline: case token_type::rule_out: case token_type::rule_restat: case token_type::rule_rspfile: case token_type::rule_rspfile_content: case token_type::build_input: case token_type::build_rule: case token_type::default_target: case token_type::variable_value: case token_type::pool_depth: break; } } } } auto parser::prune_empty_token_lines() -> void { for (int j = 0; static_cast(j) < this->_tree.size(); ++j) { if (this->_tree[static_cast(j)].empty()) { this->_tree.erase(this->_tree.begin() + j); } } } } // namespace cait