| @@ -8,7 +8,7 @@ HDRS = \ | |||
| lib/toolchain.h lib/bufio.h | |||
| BUILD = build | |||
| OBJS = $(patsubst %,$(BUILD)/%.o,$(SRCS)) | |||
| CFLAGS = -std=c++14 -Wall -Wextra -Wno-unused-parameter -O3 -g | |||
| CFLAGS = -std=c++14 -Wall -Wextra -Wpedantic -Wno-unused-parameter -Ilib -O3 -g | |||
| LDLIBS = -lpthread | |||
| $(BUILD)/%.cc.o: %.cc $(HDRS) | |||
| @@ -1,7 +1,7 @@ | |||
| target := box | |||
| files := lib cmd | |||
| includes := lib | |||
| warnings := all extra no-unused-parameter | |||
| warnings := all extra pedantic no-unused-parameter | |||
| std := c++14 | |||
| optimize := 3 | |||
| cxxflags := -g | |||
| @@ -16,22 +16,29 @@ | |||
| #include "build.h" | |||
| #include "compdb.h" | |||
| static void run(std::vector<std::string> args, std::vector<std::pair<std::string, std::string>> kwargs) { | |||
| struct Conf { | |||
| std::vector<std::string> args; | |||
| std::vector<std::string> args2; | |||
| std::vector<std::pair<std::string, std::string>> kvargs; | |||
| bool exec = false; | |||
| }; | |||
| static void run(Conf conf) { | |||
| std::string op; | |||
| std::string path; | |||
| if (args.size() == 0) { | |||
| if (conf.args.size() == 0) { | |||
| op = "build"; | |||
| path = "./bx-out"; | |||
| } else if (args[0][0] == '.' || args[0][0] == '/') { | |||
| } else if (conf.args[0][0] == '.' || conf.args[0][0] == '/') { | |||
| op = "build"; | |||
| path = args[0]; | |||
| } else if (args.size() == 1) { | |||
| op = args[0]; | |||
| path = conf.args[0]; | |||
| } else if (conf.args.size() == 1) { | |||
| op = conf.args[0]; | |||
| path = "./bx-out"; | |||
| } else if (args.size() == 2) { | |||
| op = args[0]; | |||
| path = args[1]; | |||
| } else if (conf.args.size() == 2) { | |||
| op = conf.args[0]; | |||
| path = conf.args[1]; | |||
| } else { | |||
| // TODO: Print usage instead? | |||
| throw std::runtime_error("Incorrect number of arguments"); | |||
| @@ -43,19 +50,19 @@ static void run(std::vector<std::string> args, std::vector<std::pair<std::string | |||
| BXVariables variables; | |||
| if (sys::fileExists(path + "/.config.bx")) { | |||
| bufio::IFStream f(path + "/.config.bx"); | |||
| BXParser parser(f, BXParser::FLAG_NONE); | |||
| BXParser parser(f); | |||
| parser.parse(variables); | |||
| } | |||
| for (auto &pair: kwargs) { | |||
| for (auto &pair: conf.kvargs) { | |||
| bufio::ISStream ss(pair.second); | |||
| BXParser parser(ss, BXParser::FLAG_NONE); | |||
| BXParser parser(ss); | |||
| auto &list = variables[pair.first]; | |||
| list.clear(); | |||
| parser.parseList(variables, list); | |||
| } | |||
| if (kwargs.size() > 0) { | |||
| if (conf.kvargs.size() > 0) { | |||
| bufio::OFStream f(path + "/.config.bx"); | |||
| BXWriter w(f); | |||
| w.write(variables); | |||
| @@ -65,7 +72,7 @@ static void run(std::vector<std::string> args, std::vector<std::pair<std::string | |||
| }; | |||
| auto buildTree = [&](BXVariables vars) -> std::unique_ptr<DepNode> { | |||
| return buildDepTree(path, std::move(vars)); | |||
| return build::buildDepTree(path, std::move(vars)); | |||
| }; | |||
| auto buildCompileCommands = [&](DepNode &root) { | |||
| @@ -76,7 +83,9 @@ static void run(std::vector<std::string> args, std::vector<std::pair<std::string | |||
| }; | |||
| if (op == "build") { | |||
| auto root = buildTree(buildVariables()); | |||
| auto vars = buildVariables(); | |||
| std::string targetName = build::findTargetName(vars); | |||
| auto root = buildTree(vars); | |||
| buildCompileCommands(*root); | |||
| if (root->hasChanged(path)) { | |||
| @@ -86,6 +95,23 @@ static void run(std::vector<std::string> args, std::vector<std::pair<std::string | |||
| logger::log("Nothing to do."); | |||
| } | |||
| if (conf.exec) { | |||
| std::vector<std::string> argv; | |||
| argv.reserve(conf.args2.size() + 1); | |||
| argv.push_back(path + '/' + targetName); | |||
| for (const auto &arg: conf.args2) { | |||
| argv.push_back(std::move(arg)); | |||
| } | |||
| try { | |||
| sys::ProcConf conf; | |||
| conf.forwardSignals = true; | |||
| sys::execute(argv, conf); | |||
| } catch (std::exception &ex) { | |||
| logger::log(ex.what()); | |||
| } | |||
| } | |||
| } else if (op == "config") { | |||
| BXVariables variables = buildVariables(); | |||
| @@ -122,6 +148,7 @@ static void run(std::vector<std::string> args, std::vector<std::pair<std::string | |||
| int main(int argc, char **argv) { | |||
| int jobs = parallel::coreCount() * 1.2 + 2; | |||
| Conf conf; | |||
| std::string workDir = ""; | |||
| std::string target = ""; | |||
| @@ -131,6 +158,7 @@ int main(int argc, char **argv) { | |||
| { "verbose", no_argument, NULL, 'v' }, | |||
| { "jobs", required_argument, NULL, 'j' }, | |||
| { "directory", required_argument, NULL, 'C' }, | |||
| { "run", no_argument, NULL, 'R' }, | |||
| {}, | |||
| }; | |||
| @@ -145,10 +173,13 @@ int main(int argc, char **argv) { | |||
| "Set the number of jobs run simultaneously. " | |||
| "Default: the number of cores in the machine.\n" | |||
| " -C, --directory <dir> " | |||
| "Change directory before doing anything else.\n"; | |||
| "Change directory before doing anything else.\n" | |||
| " -R, --run <args...> " | |||
| "Run executable after building.\n"; | |||
| // Parse options from argv | |||
| while (1) { | |||
| bool parsingOpts = true; | |||
| while (parsingOpts) { | |||
| int optidx; | |||
| int c = getopt_long(argc, argv, shortopts, opts, &optidx); | |||
| @@ -177,6 +208,14 @@ int main(int argc, char **argv) { | |||
| workDir = optarg; | |||
| break; | |||
| case 'R': | |||
| conf.exec = true; | |||
| parsingOpts = false; | |||
| for (; optind < argc; ++optind) { | |||
| conf.args2.push_back(argv[optind]); | |||
| } | |||
| break; | |||
| default: | |||
| printf("Unknown option: '%c'.\n", (char)c); | |||
| printf(usage, argv[0]); | |||
| @@ -193,18 +232,16 @@ int main(int argc, char **argv) { | |||
| } | |||
| // Find args and keyword args | |||
| std::vector<std::string> args; | |||
| std::vector<std::pair<std::string, std::string>> kwargs; | |||
| while (optind < argc) { | |||
| for (; optind < argc; ++optind) { | |||
| char *arg = argv[optind++]; | |||
| char *eq = strchr(arg, '='); | |||
| if (eq == nullptr) { | |||
| args.push_back(arg); | |||
| conf.args.push_back(arg); | |||
| } else { | |||
| kwargs.push_back(std::make_pair( | |||
| conf.kvargs.push_back(std::make_pair( | |||
| std::string(arg, eq - arg), std::string(eq + 1))); | |||
| } | |||
| } | |||
| run(std::move(args), std::move(kwargs)); | |||
| run(std::move(conf)); | |||
| } | |||
| @@ -16,29 +16,6 @@ int BXParser::get() { | |||
| return c; | |||
| } | |||
| BXParser::Operator BXParser::readOperator() { | |||
| int ch2 = peek(2); | |||
| if (peek() == ':' && ch2 == '=') { | |||
| skip(); // ':' | |||
| skip(); // '=' | |||
| return Operator::COLON_EQUALS; | |||
| } else if (peek() == '+' && ch2 == '=') { | |||
| skip(); // '+' | |||
| skip(); // '=' | |||
| return Operator::PLUS_EQUALS; | |||
| } else if (peek() == '=' && ch2 == '+') { | |||
| skip(); // '=' | |||
| skip(); // '+' | |||
| return Operator::EQUALS_PLUS; | |||
| } else if (peek() == '|' && ch2 == '=') { | |||
| skip(); // '|' | |||
| skip(); // '=' | |||
| return Operator::BAR_EQUALS; | |||
| } | |||
| return Operator::NONE; | |||
| } | |||
| void BXParser::skip(char expected) { | |||
| int ch = get(); | |||
| if (ch == EOF) { | |||
| @@ -52,340 +29,251 @@ void BXParser::skip(char expected) { | |||
| throw BXParseError(std::to_string(line_) + ":" + std::to_string(ch_) + ": " + msg); | |||
| } | |||
| static bool isWhitespace(int ch) { | |||
| if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') | |||
| return true; | |||
| return false; | |||
| } | |||
| void BXParser::skipWhitespace() { | |||
| if (flags_ & FLAG_ONE_LINE) { | |||
| int ch; | |||
| while (isWhitespace(ch = peek()) && ch != '\r' && ch != '\n') | |||
| get(); | |||
| } else { | |||
| while (isWhitespace(peek())) | |||
| get(); | |||
| } | |||
| } | |||
| [[noreturn]] void BXParser::error(std::string msg, TokenKind kind) { | |||
| switch (kind) { | |||
| case TokenKind::E_O_F: | |||
| msg += " EOF"; | |||
| break; | |||
| char BXParser::parseEscape() { | |||
| skip(); // '\' | |||
| int ch; | |||
| switch (ch = get()) { | |||
| case EOF: | |||
| error("Unexpected EOF"); | |||
| case TokenKind::INDENTATION: | |||
| msg += " indentation"; | |||
| break; | |||
| case 'n': | |||
| return '\n'; | |||
| case TokenKind::NEWLINE: | |||
| msg += " newline"; | |||
| break; | |||
| case 'r': | |||
| return '\r'; | |||
| case TokenKind::COMMA: | |||
| msg += " comma ','"; | |||
| break; | |||
| case 't': | |||
| return '\t'; | |||
| case TokenKind::COLON_EQUALS: | |||
| msg += " colon equals ':='"; | |||
| break; | |||
| default: | |||
| return (char)ch; | |||
| } | |||
| } | |||
| case TokenKind::PLUS_EQUALS: | |||
| msg += " plus equals '+='"; | |||
| break; | |||
| static void appendVariableToString( | |||
| const BXVariables &vars, std::string &name, | |||
| std::string &value) { | |||
| if (name.size() == 0) | |||
| return; | |||
| auto it = vars.find(name); | |||
| if (it == vars.end()) | |||
| return; | |||
| auto &vec = it->second; | |||
| bool first = true; | |||
| for (auto &part: vec) { | |||
| if (!first) { | |||
| value += ' '; | |||
| } | |||
| case TokenKind::EQUALS_PLUS: | |||
| msg += " equals plus '=+'"; | |||
| break; | |||
| first = false; | |||
| value += part; | |||
| } | |||
| } | |||
| case TokenKind::BAR_EQUALS: | |||
| msg += " bar equals '|='"; | |||
| break; | |||
| static void appendVariableToArray( | |||
| const BXVariables &vars, const std::string &name, | |||
| std::vector<std::string> &values) { | |||
| if (name.size() == 0) | |||
| return; | |||
| case TokenKind::EXPANSION: | |||
| msg += " expansion"; | |||
| break; | |||
| auto it = vars.find(name); | |||
| if (it == vars.end()) | |||
| return; | |||
| case TokenKind::STRING: | |||
| msg += " string"; | |||
| break; | |||
| auto &vec = it->second; | |||
| for (auto &part: vec) { | |||
| values.push_back(part); | |||
| case TokenKind::NONE: | |||
| msg += " none"; | |||
| break; | |||
| } | |||
| } | |||
| void BXParser::parseExpansion(const BXVariables &vars, std::vector<std::string> &values) { | |||
| skip(); // '$' | |||
| std::string str; | |||
| switch (peek()) { | |||
| case '{': | |||
| skip(); | |||
| parseString(vars, str, '}'); | |||
| skip('}'); | |||
| appendVariableToArray(vars, str, values); | |||
| break; | |||
| default: | |||
| if (!parseIdentifier(str)) { | |||
| error("No identifier after $."); | |||
| } | |||
| appendVariableToArray(vars, str, values); | |||
| break; | |||
| } | |||
| error(msg); | |||
| } | |||
| void BXParser::parseQuotedExpansion(const BXVariables &vars, std::string &content) { | |||
| skip(); // '$' | |||
| std::string BXParser::readIdent(const BXVariables &vars) { | |||
| std::string str; | |||
| switch (peek()) { | |||
| case '{': | |||
| skip(); | |||
| parseString(vars, str, '}'); | |||
| skip('}'); | |||
| appendVariableToString(vars, str, content); | |||
| break; | |||
| default: | |||
| if (!parseIdentifier(str)) { | |||
| error("No identifier after $."); | |||
| } | |||
| appendVariableToString(vars, str, content); | |||
| break; | |||
| } | |||
| } | |||
| void BXParser::parseQuotedString(const BXVariables &vars, std::string &content) { | |||
| skip(); // '"' | |||
| int ch; | |||
| while ((ch = peek()) != EOF) { | |||
| switch (ch) { | |||
| case EOF: | |||
| error("Unexpected EOF"); | |||
| case '\\': | |||
| content.push_back(parseEscape()); | |||
| break; | |||
| case '$': | |||
| parseQuotedExpansion(vars, content); | |||
| break; | |||
| case '"': | |||
| skip(); | |||
| return; | |||
| default: | |||
| content.push_back(get()); | |||
| break; | |||
| if ( | |||
| (ch >= 'a' && ch <= 'z') || | |||
| (ch >= 'A' && ch <= 'Z') || | |||
| (ch == '_')) { | |||
| str.push_back(ch); | |||
| get(); | |||
| } else { | |||
| break; | |||
| } | |||
| } | |||
| return str; | |||
| } | |||
| bool BXParser::parseString(const BXVariables &vars, std::string &content, int sep) { | |||
| bool success = false; | |||
| void BXParser::skipWhitespace() { | |||
| int ch; | |||
| while (1) { | |||
| ch = peek(); | |||
| if ((sep > 0 && ch == sep) || isWhitespace(ch)) { | |||
| return success; | |||
| } | |||
| switch (ch) { | |||
| case EOF: | |||
| return success; | |||
| case '\\': | |||
| content.push_back(parseEscape()); | |||
| success = true; | |||
| break; | |||
| case '$': | |||
| parseQuotedExpansion(vars, content); | |||
| success = true; | |||
| break; | |||
| case '"': | |||
| parseQuotedString(vars, content); | |||
| success = true; | |||
| break; | |||
| default: | |||
| int ch2 = peek(2); | |||
| if ( | |||
| (ch == ':' && ch2 == '=') || | |||
| (ch == '+' && ch2 == '=') || | |||
| (ch == '=' && ch2 == '+') || | |||
| (ch == '|' && ch2 == '=')) { | |||
| return success; | |||
| } | |||
| content.push_back(get()); | |||
| success = true; | |||
| break; | |||
| } | |||
| while ((ch = peek()) == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { | |||
| get(); | |||
| } | |||
| } | |||
| bool BXParser::parseIdentifier(std::string &content) { | |||
| int ch = peek(); | |||
| if (!( | |||
| (ch >= 'a' && ch <= 'z') || | |||
| (ch >= 'A' && ch <= 'Z') || | |||
| (ch == '_'))) { | |||
| return false; | |||
| } | |||
| content += get(); | |||
| while (1) { | |||
| ch = peek(); | |||
| if (!( | |||
| (ch >= '0' && ch <= '9') || | |||
| (ch >= 'a' && ch <= 'z') || | |||
| (ch >= 'A' && ch <= 'Z') || | |||
| (ch == '_'))) { | |||
| return true; | |||
| } | |||
| content += get(); | |||
| char BXParser::readEscape() { | |||
| int ch = get(); | |||
| if (ch == EOF) { | |||
| error("Unexpected EOF"); | |||
| } else if (ch == 'n') { | |||
| return '\n'; | |||
| } else if (ch == 'r') { | |||
| return '\r'; | |||
| } else if (ch == 't') { | |||
| return '\t'; | |||
| } else { | |||
| return (char)ch; | |||
| } | |||
| } | |||
| void BXParser::parse(BXVariables &vars) { | |||
| std::string key, value; | |||
| std::vector<std::string> values; | |||
| skipWhitespace(); | |||
| if (!parseString(vars, key)) { | |||
| return; | |||
| std::string BXParser::readStringExpansion(const BXVariables &vars) { | |||
| bool braced = peek() == '{'; | |||
| std::string key; | |||
| if (braced) { | |||
| get(); | |||
| skipWhitespace(); | |||
| key = readString(vars); | |||
| } else { | |||
| key = readIdent(vars); | |||
| } | |||
| skipWhitespace(); | |||
| Operator prevOper = readOperator(); | |||
| if (prevOper == Operator::NONE) { | |||
| error("Expected operator."); | |||
| auto it = vars.find(key); | |||
| if (it == vars.end()) { | |||
| error("Key '" + key + "' doesn't exist"); | |||
| } | |||
| auto doAssignment = [&] { | |||
| switch (prevOper) { | |||
| case Operator::COLON_EQUALS: | |||
| vars[key] = std::move(values); | |||
| values.clear(); | |||
| break; | |||
| if (braced) { | |||
| skipWhitespace(); | |||
| case Operator::PLUS_EQUALS: | |||
| { | |||
| auto &vec = vars[key]; | |||
| vec.reserve(vec.size() + values.size()); | |||
| for (size_t i = 0; i < values.size(); ++i) { | |||
| vec.push_back(std::move(values[i])); | |||
| } | |||
| } | |||
| values.clear(); | |||
| break; | |||
| if (peek() != '}') { | |||
| error("Expected a '}' after a '${' expansion"); | |||
| } | |||
| case Operator::EQUALS_PLUS: | |||
| { | |||
| auto &vec = vars[key]; | |||
| vec.reserve(vec.size() + values.size()); | |||
| for (size_t i = 0; i < vec.size(); ++i) { | |||
| values.push_back(std::move(vec[i])); | |||
| } | |||
| vec = std::move(values); | |||
| } | |||
| values.clear(); | |||
| break; | |||
| get(); | |||
| } | |||
| case Operator::BAR_EQUALS: | |||
| { | |||
| auto &vec = vars[key]; | |||
| for (size_t i = 0; i < vec.size(); ++i) { | |||
| bool exists = false; | |||
| for (auto &val: values) { | |||
| if (val == vec[i]) { | |||
| exists = true; | |||
| break; | |||
| } | |||
| } | |||
| // TODO: Use BXValue.asString() | |||
| return it->second[0]; | |||
| } | |||
| if (!exists) { | |||
| values.push_back(std::move(vec[i])); | |||
| } | |||
| } | |||
| vec = std::move(values); | |||
| } | |||
| values.clear(); | |||
| break; | |||
| std::string BXParser::readQuotedString(const BXVariables &vars) { | |||
| std::string str; | |||
| case Operator::NONE: | |||
| int ch; | |||
| while ((ch = peek()) != EOF) { | |||
| if (ch == '\\') { | |||
| get(); | |||
| str.push_back(readEscape()); | |||
| } else if (ch == '$') { | |||
| get(); | |||
| str += readStringExpansion(vars); | |||
| } else if (ch == '"') { | |||
| get(); | |||
| break; | |||
| } else { | |||
| str.push_back(ch); | |||
| get(); | |||
| } | |||
| }; | |||
| } | |||
| while (true) { | |||
| skipWhitespace(); | |||
| return str; | |||
| } | |||
| std::string BXParser::readString(const BXVariables &vars) { | |||
| std::string str; | |||
| // Parse next value | |||
| if (peek() == '$') { | |||
| parseExpansion(vars, values); | |||
| value.clear(); | |||
| continue; // We can't have an assignment after an expansion | |||
| } else if (!parseString(vars, value)) { | |||
| int ch; | |||
| while ((ch = peek()) != EOF) { | |||
| if (ch == '\\') { | |||
| get(); | |||
| str.push_back(readEscape()); | |||
| } else if (ch == '$') { | |||
| get(); | |||
| str += readStringExpansion(vars); | |||
| } else if (ch == '"') { | |||
| get(); | |||
| str += readQuotedString(vars); | |||
| } else if ( | |||
| ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || | |||
| ch == '}' || ch == ',') { | |||
| break; | |||
| } else { | |||
| str.push_back(ch); | |||
| get(); | |||
| } | |||
| } | |||
| skipWhitespace(); | |||
| return str; | |||
| } | |||
| // If there's an operator next, the value we just read was a actually a key. | |||
| // Otherwise, it was just another value. | |||
| Operator op = readOperator(); | |||
| if (op == Operator::NONE) { | |||
| values.push_back(std::move(value)); | |||
| value.clear(); | |||
| } else { | |||
| if (value.size() == 0) { | |||
| error("Expected string before assignment operator"); | |||
| } | |||
| BXParser::Token BXParser::getToken(const BXVariables &vars) { | |||
| Token tok; | |||
| tok.line = line(); | |||
| tok.ch = ch(); | |||
| doAssignment(); | |||
| prevOper = op; | |||
| key = std::move(value); | |||
| value.clear(); | |||
| } | |||
| int ch = peek(); | |||
| if (ch == EOF) { | |||
| tok.kind = TokenKind::E_O_F; | |||
| return tok; | |||
| } | |||
| doAssignment(); | |||
| } | |||
| if (ch == '\t' || ch == ' ') { | |||
| tok.kind = TokenKind::INDENTATION; | |||
| do { | |||
| get(); | |||
| ch = peek(); | |||
| } while (ch == '\t' || ch == ' '); | |||
| return tok; | |||
| } else if (ch == '\n' || ch == '\r') { | |||
| tok.kind = TokenKind::NEWLINE; | |||
| do { | |||
| get(); | |||
| ch = peek(); | |||
| } while (ch == '\n' || ch == '\r'); | |||
| return tok; | |||
| } | |||
| void BXParser::parseList(const BXVariables &vars, std::vector<std::string> &values) { | |||
| while (true) { | |||
| int ch2 = peek(2); | |||
| if (ch == ',') { | |||
| get(); | |||
| tok.kind = TokenKind::COMMA; | |||
| } else if (ch == ':' && ch2 == '=') { | |||
| get(); get(); | |||
| tok.kind = TokenKind::COLON_EQUALS; | |||
| } else if (ch == '+' && ch2 == '=') { | |||
| get(); get(); | |||
| tok.kind = TokenKind::PLUS_EQUALS; | |||
| } else if (ch == '=' && ch2 == '+') { | |||
| get(); get(); | |||
| tok.kind = TokenKind::EQUALS_PLUS; | |||
| } else if (ch == '|' && ch2 == '=') { | |||
| get(); get(); | |||
| tok.kind = TokenKind::BAR_EQUALS; | |||
| } else if (ch == '$' && ch2 == '{') { | |||
| get(); get(); | |||
| skipWhitespace(); | |||
| std::string value; | |||
| if (!parseString(vars, value)) { | |||
| break; | |||
| tok.kind = TokenKind::EXPANSION; | |||
| tok.str = readString(vars); | |||
| skipWhitespace(); | |||
| if (peek() != '}') { | |||
| error("Expected a '}' after a '${' expansion."); | |||
| } | |||
| get(); | |||
| } else if (ch == '$') { | |||
| get(); | |||
| tok.kind = TokenKind::EXPANSION; | |||
| tok.str = readString(vars); | |||
| } else { | |||
| tok.kind = TokenKind::STRING; | |||
| tok.str = readString(vars); | |||
| } | |||
| values.push_back(std::move(value)); | |||
| while ((ch = peek()) == '\t' || ch == ' ') { | |||
| get(); | |||
| } | |||
| return tok; | |||
| } | |||
| BXParser::Token BXParser::readToken(const BXVariables &vars) { | |||
| Token t = tok_; | |||
| tok_ = getToken(vars); | |||
| return t; | |||
| } | |||
| void BXWriter::escape(const std::string &str) { | |||
| @@ -419,3 +307,110 @@ void BXWriter::write(const BXVariables &vars) { | |||
| buf_.put('\n'); | |||
| } | |||
| } | |||
| void BXParser::parse(BXVariables &vars, bool oneLine) { | |||
| readToken(vars); | |||
| while (true) { | |||
| if (peekToken().kind == TokenKind::E_O_F) { | |||
| break; | |||
| } else if (peekToken().kind != TokenKind::STRING) { | |||
| error("Expected string, got", peekToken().kind); | |||
| } | |||
| Token t = readToken(vars); | |||
| std::string key = t.str; | |||
| std::vector<std::string> &var = vars[key]; | |||
| void (*addVal)(std::vector<std::string> &var, std::string val); | |||
| switch (peekToken().kind) { | |||
| case TokenKind::COLON_EQUALS: | |||
| var.clear(); | |||
| // Fallthrough | |||
| case TokenKind::PLUS_EQUALS: | |||
| addVal = [](auto &var, auto val) { | |||
| var.push_back(std::move(val)); | |||
| }; | |||
| break; | |||
| case TokenKind::EQUALS_PLUS: | |||
| addVal = [](auto &var, auto val) { | |||
| var.insert(var.begin(), std::move(val)); | |||
| }; | |||
| break; | |||
| case TokenKind::BAR_EQUALS: | |||
| addVal = [](auto &var, auto val) { | |||
| for (auto &v: var) { | |||
| if (v == val) { | |||
| return; | |||
| } | |||
| } | |||
| var.push_back(val); | |||
| }; | |||
| break; | |||
| default: | |||
| error("Expected operator, got", peekToken().kind); | |||
| } | |||
| readToken(vars); | |||
| parseList(vars, var, addVal, oneLine); | |||
| } | |||
| } | |||
| void BXParser::parseList( | |||
| BXVariables &vars, std::vector<std::string> &var, | |||
| void (*addVal)(std::vector<std::string> &var, std::string val), | |||
| bool oneLine) { | |||
| while (true) { | |||
| Token tok = peekToken(); | |||
| switch (tok.kind) { | |||
| case TokenKind::NEWLINE: | |||
| if (oneLine) { | |||
| return; | |||
| } | |||
| readToken(vars); | |||
| if (peekToken().kind != TokenKind::INDENTATION) { | |||
| return; | |||
| } | |||
| readToken(vars); // Read indentation | |||
| break; | |||
| case TokenKind::STRING: | |||
| addVal(var, std::move(tok.str)); | |||
| readToken(vars); | |||
| break; | |||
| case TokenKind::COMMA: | |||
| readToken(vars); | |||
| return; | |||
| case TokenKind::E_O_F: | |||
| return; | |||
| case TokenKind::EXPANSION: | |||
| for (auto &v: vars[tok.str]) { | |||
| addVal(var, v); | |||
| } | |||
| readToken(vars); | |||
| break; | |||
| default: | |||
| error("Unexpected token", tok.kind); | |||
| } | |||
| } | |||
| } | |||
| void BXParser::parseList(BXVariables &vars, std::vector<std::string> &var) { | |||
| auto addVal = [](auto &var, auto val) { | |||
| var.push_back(std::move(val)); | |||
| }; | |||
| readToken(vars); | |||
| parseList(vars, var, addVal, false); | |||
| } | |||
| @@ -21,14 +21,29 @@ using BXVariables = std::unordered_map<std::string, std::vector<std::string>>; | |||
| class BXParser { | |||
| public: | |||
| static const int FLAG_NONE = 0; | |||
| static const int FLAG_ONE_LINE = 1 << 0; | |||
| enum class TokenKind { | |||
| E_O_F, | |||
| INDENTATION, | |||
| NEWLINE, | |||
| COMMA, | |||
| COLON_EQUALS, | |||
| PLUS_EQUALS, | |||
| EQUALS_PLUS, | |||
| BAR_EQUALS, | |||
| EXPANSION, | |||
| STRING, | |||
| NONE, | |||
| }; | |||
| BXParser(bufio::IStream &stream, int flags = FLAG_NONE, int line = 1, int ch = 1): | |||
| flags_(flags), line_(line), ch_(ch), buf_(stream) {} | |||
| struct Token { | |||
| TokenKind kind; | |||
| std::string str; | |||
| int line; | |||
| int ch; | |||
| }; | |||
| void parse(BXVariables &vars); | |||
| void parseList(const BXVariables &vars, std::vector<std::string> &values); | |||
| BXParser(bufio::IStream &stream, int line = 1, int ch = 1): | |||
| line_(line), ch_(ch), buf_(stream) {} | |||
| int get(); | |||
| int peek(size_t count = 1) { return buf_.peek(count); } | |||
| @@ -38,31 +53,32 @@ public: | |||
| int line() const { return line_; } | |||
| int ch() const { return ch_; } | |||
| private: | |||
| enum class Operator { | |||
| COLON_EQUALS, | |||
| PLUS_EQUALS, | |||
| EQUALS_PLUS, | |||
| BAR_EQUALS, | |||
| NONE, | |||
| }; | |||
| Token readToken(const BXVariables &vars); | |||
| Token &peekToken() { return tok_; } | |||
| void parse(BXVariables &vars, bool oneLine = false); | |||
| void parseLine(BXVariables &vars) { parse(vars, true); } | |||
| void parseList(BXVariables &vars, std::vector<std::string> &list); | |||
| private: | |||
| [[noreturn]] void error(std::string); | |||
| [[noreturn]] void error(std::string, TokenKind); | |||
| Operator readOperator(); | |||
| void skipWhitespaceLine(); | |||
| std::string readIdent(const BXVariables &vars); | |||
| void skipWhitespace(); | |||
| Token getToken(const BXVariables &vars); | |||
| std::string readString(const BXVariables &vars); | |||
| std::string readQuotedString(const BXVariables &vars); | |||
| std::string readStringExpansion(const BXVariables &vars); | |||
| char readEscape(); | |||
| void parseList( | |||
| BXVariables &vars, std::vector<std::string> &var, | |||
| void (*addVal)(std::vector<std::string> &var, std::string val), | |||
| bool oneLine); | |||
| char parseEscape(); | |||
| void parseExpansion(const BXVariables &vars, std::vector<std::string> &values); | |||
| void parseQuotedExpansion(const BXVariables &vars, std::string &content); | |||
| void parseQuotedString(const BXVariables &vars, std::string &content); | |||
| bool parseString(const BXVariables &vars, std::string &content, int sep = -1); | |||
| bool parseIdentifier(std::string &content); | |||
| int flags_; | |||
| int line_; | |||
| int ch_; | |||
| Token tok_; | |||
| bufio::IBuf<> buf_; | |||
| }; | |||
| @@ -79,10 +79,6 @@ bool CompileStep::checkHasChanged(const std::string &outDir) { | |||
| } | |||
| void CompileStep::doBuild(const std::string &outDir) { | |||
| bool verboseCommand = global::verbose >= 1; | |||
| if (!verboseCommand) { | |||
| logger::log("Compile " + path_); | |||
| } | |||
| std::string objPath = toolchain::objectFilePath(path_, outDir); | |||
| std::string dirPath = sys::dirname(objPath); | |||
| @@ -99,7 +95,20 @@ void CompileStep::doBuild(const std::string &outDir) { | |||
| BXWriter writer(f); | |||
| writer.write(newCachedVars); | |||
| sys::execute(command, nullptr, verboseCommand); | |||
| sys::ProcConf conf; | |||
| conf.print = true; | |||
| switch (type_) { | |||
| case toolchain::FileType::C: | |||
| conf.prefix = "(CC)"; | |||
| break; | |||
| case toolchain::FileType::CXX: | |||
| conf.prefix = "(CXX)"; | |||
| break; | |||
| } | |||
| sys::execute(command, conf); | |||
| } | |||
| void CompileStep::doWriteCompDB(const std::string &outDir, compdb::Writer &w) { | |||
| @@ -130,11 +139,11 @@ BXVariables &CompileStep::variables() { | |||
| } | |||
| bufio::IFStream f(path_); | |||
| BXParser parser(f, BXParser::FLAG_ONE_LINE); | |||
| BXParser parser(f); | |||
| while (parser.peek() != EOF) { | |||
| if (startsWith(parser, "//#bx")) { | |||
| parser.parse(variables_); | |||
| parser.parseLine(variables_); | |||
| } else { | |||
| while (parser.peek() != EOF && parser.get() != '\n'); | |||
| } | |||
| @@ -15,6 +15,7 @@ private: | |||
| toolchain::FileType type_; | |||
| bool checkHasChanged(const std::string &outDir) override; | |||
| toolchain::FileType doGetLinkType() override { return type_; } | |||
| void doBuild(const std::string &outDir) override; | |||
| void doWriteCompDB(const std::string &outDir, compdb::Writer &w) override; | |||
| std::vector<std::string> getPublicLDFlags(const std::string &outDir) override; | |||
| @@ -9,17 +9,20 @@ | |||
| #include <condition_variable> | |||
| #include "compdb.h" | |||
| #include "toolchain.h" | |||
| class DepNode { | |||
| public: | |||
| virtual ~DepNode() = default; | |||
| const std::vector<std::shared_ptr<DepNode>> &children() { return deps_; } | |||
| toolchain::FileType linkType() { return doGetLinkType(); } | |||
| void addChild(std::shared_ptr<DepNode> node); | |||
| bool hasChanged(const std::string &outDir); | |||
| void startBuild(const std::string &outDir); | |||
| void joinBuild(); | |||
| void writeCompDB(const std::string &outDir, compdb::Writer &w); | |||
| virtual std::vector<std::string> publicLDFlags(const std::string &outDir); | |||
| virtual std::vector<std::string> publicLDLibs(const std::string &outDir); | |||
| virtual std::vector<std::string> publicObjects(const std::string &outDir); | |||
| @@ -30,6 +33,7 @@ protected: | |||
| }; | |||
| virtual bool checkHasChanged(const std::string &outDir) = 0; | |||
| virtual toolchain::FileType doGetLinkType() = 0; | |||
| virtual void doBuild(const std::string &outDir) = 0; | |||
| virtual void doWriteCompDB(const std::string &outDir, compdb::Writer &w) {} | |||
| virtual std::vector<std::string> getPublicLDFlags(const std::string &outDir) { return {}; } | |||
| @@ -43,7 +43,6 @@ bool LinkStep::checkHasChanged(const std::string &outDir) { | |||
| } | |||
| void LinkStep::doBuild(const std::string &outDir) { | |||
| logger::log("Link " + path_); | |||
| std::vector<std::string> command = linkCommand(outDir); | |||
| std::string dirPath = sys::dirname(outDir + '/' + path_); | |||
| @@ -56,7 +55,20 @@ void LinkStep::doBuild(const std::string &outDir) { | |||
| writer.write(newCachedVars); | |||
| sys::mkdirp(dirPath); | |||
| sys::execute(command, nullptr, global::verbose >= 1); | |||
| sys::ProcConf conf; | |||
| conf.print = true; | |||
| conf.prefix = "(LD)"; | |||
| sys::execute(command, conf); | |||
| } | |||
| toolchain::FileType LinkStep::doGetLinkType() { | |||
| for (auto &child: children()) { | |||
| if (child->linkType() == toolchain::FileType::CXX) { | |||
| return toolchain::FileType::CXX; | |||
| } | |||
| } | |||
| return toolchain::FileType::C; | |||
| } | |||
| std::vector<std::string> &LinkStep::linkCommand(const std::string &outDir) { | |||
| @@ -74,7 +86,7 @@ std::vector<std::string> &LinkStep::linkCommand(const std::string &outDir) { | |||
| // TODO: Don't use FileType::CXX hard-coded here | |||
| linkCommand_ = toolchain::getLinkCommand( | |||
| publicLDFlags(outDir), publicLDLibs(outDir), | |||
| toolchain::FileType::CXX, type_, objs, path_, outDir); | |||
| linkType(), type_, objs, path_, outDir); | |||
| hasLinkCommand_ = true; | |||
| return linkCommand_; | |||
| } | |||
| @@ -15,6 +15,7 @@ private: | |||
| toolchain::TargetType type_; | |||
| bool checkHasChanged(const std::string &outDir) override; | |||
| toolchain::FileType doGetLinkType() override; | |||
| void doBuild(const std::string &outDir) override; | |||
| bool hasLinkCommand_ = false; | |||
| @@ -11,6 +11,8 @@ | |||
| #include "LinkStep.h" | |||
| #include "logger.h" | |||
| namespace build { | |||
| static std::string extension(const std::string &path) { | |||
| size_t idx = path.find_last_of('.'); | |||
| if (idx >= std::string::npos) { | |||
| @@ -87,7 +89,7 @@ static void findDeps( | |||
| } | |||
| } | |||
| static std::string findTargetName(const BXVariables &variables) { | |||
| std::string findTargetName(const BXVariables &variables) { | |||
| auto it = variables.find("target"); | |||
| if (it != variables.end() && it->second.size() != 0) { | |||
| return it->second[0]; | |||
| @@ -132,3 +134,5 @@ std::unique_ptr<DepNode> buildDepTree(const std::string &outDir, BXVariables var | |||
| return link; | |||
| } | |||
| } | |||
| @@ -6,4 +6,9 @@ | |||
| #include "DepNode.h" | |||
| #include "BXParser.h" | |||
| namespace build { | |||
| std::string findTargetName(const BXVariables &variables); | |||
| std::unique_ptr<DepNode> buildDepTree(const std::string &outDir, BXVariables variables); | |||
| } | |||
| @@ -12,6 +12,7 @@ | |||
| #include <stdexcept> | |||
| #include <system_error> | |||
| #include <unordered_set> | |||
| #include <atomic> | |||
| #include "logger.h" | |||
| #include "globals.h" | |||
| @@ -20,6 +21,24 @@ namespace sys { | |||
| static thread_local std::unordered_set<std::string> mkdirp_set; | |||
| static std::atomic<int> signalCounter(0); | |||
| struct SigCtx { | |||
| SigCtx(bool enabled): enabled(enabled) { | |||
| if (enabled && signalCounter++ == 0) { | |||
| signal(SIGINT, SIG_IGN); | |||
| } | |||
| } | |||
| ~SigCtx() { | |||
| if (enabled && --signalCounter == 0) { | |||
| signal(SIGINT, SIG_DFL); | |||
| } | |||
| } | |||
| bool enabled; | |||
| }; | |||
| std::string sanitizePath(const std::string &path) { | |||
| std::string npath; | |||
| size_t idx = 0; | |||
| @@ -79,7 +98,10 @@ void mkdirp(const std::string &path) { | |||
| argv.push_back("-p"); | |||
| argv.push_back("--"); | |||
| argv.push_back(path); | |||
| execute(argv, nullptr, global::verbose >= 2); | |||
| ProcConf conf; | |||
| conf.print = global::verbose >= 1; | |||
| conf.prefix = "(MKDIRP)"; | |||
| execute(argv, conf); | |||
| } | |||
| void rmrf(const std::string &path) { | |||
| @@ -90,11 +112,14 @@ void rmrf(const std::string &path) { | |||
| argv.push_back("-rf"); | |||
| argv.push_back("--"); | |||
| argv.push_back(path); | |||
| execute(argv, nullptr, global::verbose >= 2); | |||
| ProcConf conf; | |||
| conf.print = global::verbose >= 1; | |||
| conf.prefix = "(RMRF)"; | |||
| execute(argv, conf); | |||
| } | |||
| void execute(const std::vector<std::string> &args, std::string *output, bool print) { | |||
| if (print) { | |||
| void execute(const std::vector<std::string> &args, const ProcConf &conf) { | |||
| if (conf.print || global::verbose >= 2) { | |||
| std::string str; | |||
| for (size_t i = 0; i < args.size(); ++i) { | |||
| if (i != 0) { | |||
| @@ -102,6 +127,9 @@ void execute(const std::vector<std::string> &args, std::string *output, bool pri | |||
| } | |||
| str += args[i]; | |||
| } | |||
| if (conf.prefix.size() > 0) { | |||
| str = conf.prefix + " " + str; | |||
| } | |||
| logger::log(str); | |||
| } | |||
| @@ -117,18 +145,25 @@ void execute(const std::vector<std::string> &args, std::string *output, bool pri | |||
| for (size_t i = 1; i < args.size(); ++i) { | |||
| argv.push_back(args[i].c_str()); | |||
| } | |||
| argv.push_back(nullptr); | |||
| int fds[2]; | |||
| if (pipe(fds) < 0) { | |||
| throw std::runtime_error(std::string("fork: ") + strerror(errno)); | |||
| int stdoutPipe[2]; | |||
| if (conf.output == nullptr) { | |||
| stdoutPipe[1] = STDOUT_FILENO; | |||
| } else { | |||
| if (pipe(stdoutPipe) < 0) { | |||
| throw std::runtime_error(std::string("pipe: ") + strerror(errno)); | |||
| } | |||
| } | |||
| SigCtx sigCtx(conf.forwardSignals); | |||
| pid_t child = fork(); | |||
| if (child == 0) { | |||
| close(fds[0]); | |||
| dup2(fds[1], 1); | |||
| signal(SIGINT, SIG_DFL); | |||
| if (conf.output != nullptr) { | |||
| close(stdoutPipe[0]); | |||
| dup2(stdoutPipe[1], 1); | |||
| } | |||
| // So, from what I've read, execvp should never modify its argv; so this should be fine? | |||
| if (execvp(argv[0], (char *const *)argv.data()) < 0) { | |||
| @@ -137,35 +172,32 @@ void execute(const std::vector<std::string> &args, std::string *output, bool pri | |||
| } | |||
| // This shouldn't happen | |||
| exit(0); | |||
| abort(); | |||
| } else if (child < 0) { | |||
| throw std::runtime_error(std::string("fork: ") + strerror(errno)); | |||
| } | |||
| close(fds[1]); | |||
| if (output == nullptr) { | |||
| close(fds[0]); | |||
| } else { | |||
| if (conf.output != nullptr) { | |||
| close(stdoutPipe[1]); | |||
| char buf[1025]; | |||
| while (true) { | |||
| ssize_t num = read(fds[0], buf, sizeof(buf) - 1); | |||
| ssize_t num = read(stdoutPipe[0], buf, sizeof(buf) - 1); | |||
| if (num < 0 && errno != EAGAIN) { | |||
| close(fds[0]); | |||
| close(stdoutPipe[0]); | |||
| throw std::runtime_error( | |||
| std::string("read: ") + strerror(errno) + | |||
| " (fd: " + std::to_string(fds[0]) + ")"); | |||
| " (fd: " + std::to_string(stdoutPipe[0]) + ")"); | |||
| } else if (num < 0) { | |||
| continue; | |||
| } | |||
| if (num == 0) { | |||
| close(fds[0]); | |||
| close(stdoutPipe[0]); | |||
| break; | |||
| } | |||
| buf[num] = '\0'; | |||
| *output += buf; | |||
| *conf.output += buf; | |||
| } | |||
| } | |||
| @@ -181,8 +213,8 @@ void execute(const std::vector<std::string> &args, std::string *output, bool pri | |||
| " terminated due to " + strsignal(WTERMSIG(wstatus))); | |||
| } | |||
| if (output != nullptr && output->back() == '\n') { | |||
| output->pop_back(); | |||
| if (conf.output != nullptr && conf.output->back() == '\n') { | |||
| conf.output->pop_back(); | |||
| } | |||
| } | |||
| @@ -235,7 +267,7 @@ void symlink(const std::string &from, const std::string &to) { | |||
| argv.push_back("--"); | |||
| argv.push_back(from); | |||
| argv.push_back(to); | |||
| sys::execute(argv, nullptr, global::verbose > 2); | |||
| sys::execute(argv, ProcConf{}); | |||
| } | |||
| std::string dirname(const std::string &path) { | |||
| @@ -17,12 +17,19 @@ struct FileInfo { | |||
| } | |||
| }; | |||
| struct ProcConf { | |||
| bool print = false; | |||
| bool forwardSignals = false; | |||
| std::string prefix; | |||
| std::string *output = nullptr; | |||
| }; | |||
| std::string sanitizePath(const std::string &path); | |||
| FileInfo fileInfo(const std::string &path); | |||
| bool fileExists(const std::string &path); | |||
| void mkdirp(const std::string &path); | |||
| void rmrf(const std::string &path); | |||
| void execute(const std::vector<std::string> &args, std::string *output, bool print); | |||
| void execute(const std::vector<std::string> &args, const ProcConf &conf); | |||
| void readDir(const std::string &path, std::vector<std::string> &files); | |||
| void chdir(const std::string &path); | |||
| std::string cwd(); | |||
| @@ -141,7 +141,11 @@ void getFlags(const BXVariables &vars, FileType type, std::vector<std::string> & | |||
| // Execute $(PKG_CONFIG) --cflags $(PKGS) | |||
| std::string output; | |||
| sys::execute(argv, &output, global::verbose >= 2); | |||
| sys::ProcConf conf; | |||
| conf.print = global::verbose >= 1; | |||
| conf.prefix = "(PKGCONFIG)"; | |||
| conf.output = &output; | |||
| sys::execute(argv, conf); | |||
| parseWhitespaceSeparated(output, flags); | |||
| } | |||
| @@ -201,7 +205,11 @@ void getLDLibs(const BXVariables &vars, std::vector<std::string> &flags) { | |||
| // Execute $(PKG_CONFIG) --cflags $(PKGS) | |||
| std::string output; | |||
| sys::execute(argv, &output, global::verbose >= 2); | |||
| sys::ProcConf conf; | |||
| conf.print = global::verbose >= 1; | |||
| conf.prefix = "(PKGCONFIG)"; | |||
| conf.output = &output; | |||
| sys::execute(argv, conf); | |||
| parseWhitespaceSeparated(output, flags); | |||
| } | |||
| @@ -249,7 +257,11 @@ std::vector<std::string> getDependencies( | |||
| // Execute $(compiler) $(flags) -MM $< | |||
| std::string output; | |||
| sys::execute(argv, &output, global::verbose >= 2); | |||
| sys::ProcConf conf; | |||
| conf.print = global::verbose >= 1; | |||
| conf.output = &output; | |||
| conf.prefix = "(DEP)"; | |||
| sys::execute(argv, conf); | |||
| std::vector<std::string> deps; | |||
| size_t idx = output.find(':'); | |||
| @@ -62,13 +62,136 @@ static void expecteq( | |||
| } | |||
| } | |||
| static void lexeq(BXVariables &vars, const char *str, std::initializer_list<BXParser::Token> list) { | |||
| INFO("Source: " << str); | |||
| bufio::ISStream stream(str); | |||
| BXParser parser(stream); | |||
| parser.readToken(vars); | |||
| size_t i = 1; | |||
| for (auto &expected: list) { | |||
| BXParser::Token tok = parser.readToken(vars); | |||
| if (tok.kind != expected.kind) { | |||
| INFO("Expected: " << (int)expected.kind); | |||
| INFO("Actual: " << (int)tok.kind); | |||
| FAIL("Token " << i << " doesn't match"); | |||
| } | |||
| if (tok.str != expected.str) { | |||
| INFO("Expected: '" << expected.str << '\''); | |||
| INFO("Actual: '" << tok.str << '\''); | |||
| FAIL("Token " << i << "'s string doesn't match"); | |||
| } | |||
| if (tok.kind == BXParser::TokenKind::E_O_F) { | |||
| return; | |||
| } | |||
| i += 1; | |||
| } | |||
| } | |||
| static void lexeq(const char *str, std::initializer_list<BXParser::Token> list) { | |||
| BXVariables vars; | |||
| lexeq(vars, str, list); | |||
| } | |||
| TEST_CASE("BXParser lex", "[BXParser][lex]") { | |||
| lexeq("hello world", { | |||
| { BXParser::TokenKind::STRING, "hello" }, | |||
| { BXParser::TokenKind::STRING, "world" }, | |||
| { BXParser::TokenKind::E_O_F }, | |||
| }); | |||
| lexeq("hello := what's up", { | |||
| { BXParser::TokenKind::STRING, "hello" }, | |||
| { BXParser::TokenKind::COLON_EQUALS }, | |||
| { BXParser::TokenKind::STRING, "what's" }, | |||
| { BXParser::TokenKind::STRING, "up" }, | |||
| { BXParser::TokenKind::E_O_F }, | |||
| }); | |||
| lexeq("\t\t \t, := += =+\n\n\r\nhello", { | |||
| { BXParser::TokenKind::INDENTATION }, | |||
| { BXParser::TokenKind::COMMA }, | |||
| { BXParser::TokenKind::COLON_EQUALS }, | |||
| { BXParser::TokenKind::PLUS_EQUALS }, | |||
| { BXParser::TokenKind::EQUALS_PLUS }, | |||
| { BXParser::TokenKind::NEWLINE }, | |||
| { BXParser::TokenKind::STRING, "hello" }, | |||
| { BXParser::TokenKind::E_O_F }, | |||
| }); | |||
| } | |||
| TEST_CASE("BXParser lex string interpolation", "[BXParser][lex]") { | |||
| BXVariables vars = { | |||
| { "foo", { "hello"} }, | |||
| }; | |||
| lexeq(vars, "hey \"$foo\" lol", { | |||
| { BXParser::TokenKind::STRING, "hey" }, | |||
| { BXParser::TokenKind::STRING, "hello" }, | |||
| { BXParser::TokenKind::STRING, "lol" }, | |||
| }); | |||
| lexeq(vars, "hey \"no$foo\" lol", { | |||
| { BXParser::TokenKind::STRING, "hey" }, | |||
| { BXParser::TokenKind::STRING, "nohello" }, | |||
| { BXParser::TokenKind::STRING, "lol" }, | |||
| }); | |||
| lexeq(vars, "hey \"Xx${foo}xX\" lol", { | |||
| { BXParser::TokenKind::STRING, "hey" }, | |||
| { BXParser::TokenKind::STRING, "XxhelloxX" }, | |||
| { BXParser::TokenKind::STRING, "lol" }, | |||
| }); | |||
| } | |||
| TEST_CASE("BXParser lex string interpolation without quotes", "[BXParser][lex]") { | |||
| BXVariables vars = { | |||
| { "foo", { "hello"} }, | |||
| }; | |||
| lexeq(vars, "hey Xx$foo lol", { | |||
| { BXParser::TokenKind::STRING, "hey" }, | |||
| { BXParser::TokenKind::STRING, "Xxhello" }, | |||
| { BXParser::TokenKind::STRING, "lol" }, | |||
| }); | |||
| lexeq(vars, "hey Xx${ foo }xX lol", { | |||
| { BXParser::TokenKind::STRING, "hey" }, | |||
| { BXParser::TokenKind::STRING, "XxhelloxX" }, | |||
| { BXParser::TokenKind::STRING, "lol" }, | |||
| }); | |||
| } | |||
| TEST_CASE("BXParser lex array expansion", "[BXParser][lex]") { | |||
| lexeq("hey $foo lol", { | |||
| { BXParser::TokenKind::STRING, "hey" }, | |||
| { BXParser::TokenKind::EXPANSION, "foo" }, | |||
| { BXParser::TokenKind::STRING, "lol" }, | |||
| }); | |||
| lexeq("hey ${foo} lol", { | |||
| { BXParser::TokenKind::STRING, "hey" }, | |||
| { BXParser::TokenKind::EXPANSION, "foo" }, | |||
| { BXParser::TokenKind::STRING, "lol" }, | |||
| }); | |||
| lexeq("hey ${ \tfoo\n \n} lol", { | |||
| { BXParser::TokenKind::STRING, "hey" }, | |||
| { BXParser::TokenKind::EXPANSION, "foo" }, | |||
| { BXParser::TokenKind::STRING, "lol" }, | |||
| }); | |||
| } | |||
| TEST_CASE("BXParser parsing", "[BXParser][parse]") { | |||
| expecteq("hello := 10 world := 20", { | |||
| expecteq("hello := 10, world := 20", { | |||
| { "hello", { "10" } }, | |||
| { "world", { "20" } }, | |||
| }); | |||
| expecteq("foo := hello world bar := 10 20 30 baz := 0", { | |||
| expecteq("foo := hello world, bar := 10 20 30, baz := 0", { | |||
| { "foo", { "hello", "world" } }, | |||
| { "bar", { "10", "20", "30" } }, | |||
| { "baz", { "0" } }, | |||
| @@ -76,27 +199,33 @@ TEST_CASE("BXParser parsing", "[BXParser][parse]") { | |||
| } | |||
| TEST_CASE("BXParser parsing variables", "[BXParser][parse]") { | |||
| expecteq("foo := \"hello world\" bar := $foo", { | |||
| expecteq("foo := \"hello world\", bar := $foo", { | |||
| { "foo", { "hello world" } }, | |||
| { "bar", { "hello world" } }, | |||
| }); | |||
| expecteq("foo := hello world bar := hey $foo what's up", { | |||
| expecteq("foo := hello world, bar := hey $foo what's up", { | |||
| { "foo", { "hello", "world" } }, | |||
| { "bar", { "hey", "hello", "world", "what's", "up" } }, | |||
| }); | |||
| } | |||
| TEST_CASE("BXParser append/prepend", "[BXParser][parse]") { | |||
| expecteq("foo := 10 foo += 20 foo += 30", { | |||
| expecteq("foo := 10, foo += 20, foo += 30", { | |||
| { "foo", { "10", "20", "30" } }, | |||
| }); | |||
| expecteq("foo := 10 foo =+ 20 foo =+ 30", { | |||
| expecteq("foo := 10, foo =+ 20, foo =+ 30", { | |||
| { "foo", { "30", "20", "10" } }, | |||
| }); | |||
| } | |||
| TEST_CASE("BXParser |=", "[BXParser][parse]") { | |||
| expecteq("foo := 10, foo |= 10, foo |= 20", { | |||
| { "foo", { "10", "20" } }, | |||
| }); | |||
| } | |||
| TEST_CASE("BXParser newline separation", "[BXParser][parse]") { | |||
| expecteq( | |||
| "foo := 10 20\n" | |||
| @@ -105,3 +234,14 @@ TEST_CASE("BXParser newline separation", "[BXParser][parse]") { | |||
| { "bar", { "30", "40" } }, | |||
| }); | |||
| } | |||
| TEST_CASE("BXParser newline continuation", "[BXParser][parse]") { | |||
| expecteq( | |||
| "foo := 10 20\n" | |||
| "\t30 40\n" | |||
| "bar := hello world\n" | |||
| "\tbaz \":=\" 30\n", { | |||
| { "foo", { "10", "20", "30", "40" } }, | |||
| { "bar", { "hello", "world", "baz", ":=", "30" } }, | |||
| }); | |||
| } | |||