Browse Source

Merge branch 'feature/new-parser'

master
Martin Dørum 3 years ago
parent
commit
df2beb2650
16 changed files with 672 additions and 397 deletions
  1. 1
    1
      Makefile
  2. 1
    1
      build.bx
  3. 60
    23
      cmd/main.cc
  4. 298
    303
      lib/BXParser.cc
  5. 40
    24
      lib/BXParser.h
  6. 16
    7
      lib/CompileStep.cc
  7. 1
    0
      lib/CompileStep.h
  8. 4
    0
      lib/DepNode.h
  9. 15
    3
      lib/LinkStep.cc
  10. 1
    0
      lib/LinkStep.h
  11. 5
    1
      lib/build.cc
  12. 5
    0
      lib/build.h
  13. 56
    24
      lib/sys.cc
  14. 8
    1
      lib/sys.h
  15. 15
    3
      lib/toolchain.cc
  16. 146
    6
      test/src/BXParser.t.cc

+ 1
- 1
Makefile View File

@@ -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
- 1
build.bx View File

@@ -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

+ 60
- 23
cmd/main.cc View File

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

+ 298
- 303
lib/BXParser.cc View File

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

+ 40
- 24
lib/BXParser.h View File

@@ -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_;
};

+ 16
- 7
lib/CompileStep.cc View File

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

+ 1
- 0
lib/CompileStep.h View File

@@ -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;

+ 4
- 0
lib/DepNode.h View File

@@ -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 {}; }

+ 15
- 3
lib/LinkStep.cc View File

@@ -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_;
}

+ 1
- 0
lib/LinkStep.h View File

@@ -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;

+ 5
- 1
lib/build.cc View File

@@ -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;
}

}

+ 5
- 0
lib/build.h View File

@@ -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);

}

+ 56
- 24
lib/sys.cc View File

@@ -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) {

+ 8
- 1
lib/sys.h View File

@@ -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();

+ 15
- 3
lib/toolchain.cc View File

@@ -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(':');

+ 146
- 6
test/src/BXParser.t.cc View File

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

Loading…
Cancel
Save