/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2021 gatecat * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "log.h" #include "nextpnr.h" #include "util.h" #include NEXTPNR_NAMESPACE_BEGIN namespace { struct QsfOption { std::string name; // name, excluding the initial '-' int arg_count; // number of arguments that follow the option bool required; // error out if this option isn't passed }; typedef dict> option_map_t; struct QsfCommand { std::string name; // name of the command std::vector options; // list of "-options" int pos_arg_count; // number of positional arguments expected to follow the command, -1 for any std::function &pos_args)> func; }; void set_location_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) { ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0); } void set_instance_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) { ctx->io_attr[ctx->id(options.at("to").at(0))][ctx->id(options.at("name").at(0))] = pos_args.at(0); } void set_global_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) { // TODO } static const std::vector commands = { {"set_location_assignment", {{"to", 1, true}}, 1, set_location_assignment_cmd}, {"set_instance_assignment", {{"to", 1, true}, {"name", 1, true}, {"section_id", 1, false}}, 1, set_instance_assignment_cmd}, {"set_global_assignment", {{"name", 1, true}, {"section_id", 1, false}, {"rise", 0, false}, {"fall", 0, false}}, 1, set_global_assignment_cmd}, }; struct QsfParser { std::string buf; int pos = 0; int lineno = 0; Context *ctx; QsfParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){}; inline bool eof() const { return pos == int(buf.size()); } inline char peek() const { return buf.at(pos); } inline char get() { char c = buf.at(pos++); if (c == '\n') ++lineno; return c; } std::string get(int n) { std::string s = buf.substr(pos, n); pos += n; return s; } // If next char matches c, take it from the stream and return true bool check_get(char c) { if (peek() == c) { get(); return true; } else { return false; } } // If next char matches any in chars, take it from the stream and return true bool check_get_any(const std::string &chrs) { char c = peek(); if (chrs.find(c) != std::string::npos) { get(); return true; } else { return false; } } inline void skip_blank(bool nl = false) { while (!eof() && check_get_any(nl ? " \t\n\r" : " \t")) ; } // Return true if end of line (or file) inline bool skip_check_eol() { skip_blank(false); if (eof()) return true; char c = peek(); // Comments count as end of line if (c == '#') { get(); while (!eof() && peek() != '\n' && peek() != '\r') get(); return true; } if (c == ';') { // Forced end of line get(); return true; } return (c == '\n' || c == '\r'); } // We need to distinguish between quoted and unquoted strings, the former don't count as options struct StringVal { std::string str; bool is_quoted = false; }; inline StringVal get_str() { StringVal s; skip_blank(false); if (eof()) return {"", false}; bool in_quotes = false, in_braces = false, escaped = false; char c = get(); if (c == '"') { in_quotes = true; s.is_quoted = true; } else if (c == '{') { in_braces = true; s.is_quoted = true; } else { s.str += c; } while (!eof()) { char c = peek(); if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == '\n' || c == '\r')) { break; } get(); if (escaped) { s.str += c; escaped = false; } else if ((in_quotes && c == '"') || (in_braces && c == '}')) { break; } else if (c == '\\') { escaped = true; } else { s.str += c; } } return s; } std::vector get_arguments() { std::vector args; while (!skip_check_eol()) { args.push_back(get_str()); } skip_blank(true); return args; } void evaluate(const std::vector &args) { if (args.empty()) return; auto cmd_name = args.at(0).str; auto fnd_cmd = std::find_if(commands.begin(), commands.end(), [&](const QsfCommand &c) { return c.name == cmd_name; }); if (fnd_cmd == commands.end()) { log_warning("Ignoring unknown command '%s' (line %d)\n", cmd_name.c_str(), lineno); return; } option_map_t opt; std::vector pos_args; for (size_t i = 1; i < args.size(); i++) { auto arg = args.at(i); if (arg.str.at(0) == '-' && !arg.is_quoted) { for (auto &opt_data : fnd_cmd->options) { if (arg.str.compare(1, std::string::npos, opt_data.name) != 0) continue; opt[opt_data.name]; // create empty entry, even if 0 arguments for (int j = 0; j < opt_data.arg_count; j++) { ++i; if (i >= args.size()) log_error("Unexpected end of argument list to option '%s' (line %d)\n", arg.str.c_str(), lineno); opt[opt_data.name].push_back(args.at(i).str); } goto done; } log_error("Unknown option '%s' to command '%s' (line %d)\n", arg.str.c_str(), cmd_name.c_str(), lineno); done:; } else { // positional argument pos_args.push_back(arg.str); } } // Check positional argument count if (int(pos_args.size()) != fnd_cmd->pos_arg_count && fnd_cmd->pos_arg_count != -1) { log_error("Expected %d positional arguments to command '%s', got %d (line %d)\n", fnd_cmd->pos_arg_count, cmd_name.c_str(), int(pos_args.size()), lineno); } // Check required options for (auto &opt_data : fnd_cmd->options) { if (opt_data.required && !opt.count(opt_data.name)) log_error("Missing required option '%s' to command '%s' (line %d)\n", opt_data.name.c_str(), cmd_name.c_str(), lineno); } // Execute fnd_cmd->func(ctx, opt, pos_args); } void operator()() { while (!eof()) { skip_blank(true); auto args = get_arguments(); evaluate(args); } } }; }; // namespace void Arch::read_qsf(std::istream &in) { std::string buf(std::istreambuf_iterator(in), {}); QsfParser(buf, getCtx())(); } NEXTPNR_NAMESPACE_END