@@ -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" } }, | |||
}); | |||
} |