Skip to content

Commit

Permalink
support markers in neurolucida ascii files (arbor-sim#1867)
Browse files Browse the repository at this point in the history
* add support for parsing markers defined in asc files
* add hitherto unknown "High" leaf branch type
  • Loading branch information
bcumming authored and jlubo committed Mar 28, 2022
1 parent 56d7051 commit c79edd8
Showing 1 changed file with 90 additions and 5 deletions.
95 changes: 90 additions & 5 deletions arborio/neurolucida.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ bool symbol_matches(const char* match, const asc::token& t) {
return t.kind==tok::symbol && !std::strcmp(match, t.spelling.c_str());
}

// A list of symbols that indicate markers
bool is_marker_symbol(const asc::token& t) {
return symbol_matches("Dot", t)
|| symbol_matches("OpenCircle", t)
|| symbol_matches("Cross", t);
};


// Parse a color expression, which have been observed in the wild in two forms:
// (Color Red) ; labeled
// (Color RGB (152, 251, 152)) ; RGB literal
Expand Down Expand Up @@ -313,6 +321,74 @@ parse_hopefully<arb::mpoint> parse_spine(asc::lexer& L) {

#define PARSE_SPINE(L, X) if (auto rval__ = parse_spine(L)) X=std::move(*rval__); else return FORWARD_PARSE_ERROR(rval__.error());

parse_hopefully<std::string> parse_name(asc::lexer& L) {
EXPECT_TOKEN(L, tok::lparen);
if (!symbol_matches("Name", L.current())) {
return unexpected(PARSE_ERROR("expected Name symbol missing", L.current().loc));
}

// consume Name symbol
auto t = L.next();
if (t.kind != tok::string) {
return unexpected(PARSE_ERROR("expected a string in name description", t.loc));
}
std::string name = t.spelling;

L.next();
EXPECT_TOKEN(L, tok::rparen);

return name;
}

#define PARSE_NAME(L, X) {if (auto rval__ = parse_name(L)) X=*rval__; else return FORWARD_PARSE_ERROR(rval__.error());}

struct marker_set {
asc_color color;
std::string name;
std::vector<arb::mpoint> locations;
};

[[maybe_unused]]
std::ostream& operator<<(std::ostream& o, const marker_set& ms) {
o << "(marker-set \"" << ms.name << "\" " << ms.color;
for (auto& l: ms.locations) o << " " << l;
return o << ")";

}

parse_hopefully<marker_set> parse_markers(asc::lexer& L) {
EXPECT_TOKEN(L, tok::lparen);

marker_set markers;

// parse marker kind keyword
auto t = L.current();
if (!is_marker_symbol(t)) {
return unexpected(PARSE_ERROR("expected a valid marker type", t.loc));
}
L.next();
while (L.current().kind==tok::lparen) {
auto n = L.peek();
if (symbol_matches("Color", n)) {
PARSE_COLOR(L, markers.color);
}
else if (symbol_matches("Name", n)) {
PARSE_NAME(L, markers.name);
}
else {
arb::mpoint loc;
PARSE_POINT(L, loc);
markers.locations.push_back(loc);
}
}

EXPECT_TOKEN(L, tok::rparen);

return markers;
}

#define PARSE_MARKER(L, X) if (auto rval__ = parse_markers(L)) X=std::move(*rval__); else return FORWARD_PARSE_ERROR(rval__.error());

struct branch {
std::vector<arb::mpoint> samples;
std::vector<branch> children;
Expand Down Expand Up @@ -348,10 +424,11 @@ parse_hopefully<branch> parse_branch(asc::lexer& L) {
};

// One of these symbols must always be present at what Arbor calls a terminal.
auto branch_end_symbol = [] (const asc::token& t) {
return symbol_matches("Incomplete", t)
auto is_branch_end_symbol = [] (const asc::token& t) {
return symbol_matches("Normal", t)
|| symbol_matches("High", t)
|| symbol_matches("Low", t)
|| symbol_matches("Normal", t)
|| symbol_matches("Incomplete", t)
|| symbol_matches("Generated", t);
};

Expand All @@ -365,16 +442,24 @@ parse_hopefully<branch> parse_branch(asc::lexer& L) {
PARSE_POINT(L, sample);
B.samples.push_back(sample);
}
// A marker statement is always of the form ( MARKER_TYPE ...)
else if (t.kind==tok::lparen && is_marker_symbol(p)) {
marker_set markers;
PARSE_MARKER(L, markers);
// Parse the markers, but don't record information about them.
// These could be grouped into locset by name and added to the label dictionary.
}
// Spines are marked by a "less than", i.e. "<", symbol.
else if (t.kind==tok::lt) {
arb::mpoint spine;
PARSE_SPINE(L, spine);
// parse the spine, but don't record the location.
}
// Test for a symbol that indicates a terminal.
else if (branch_end_symbol(t)) {
else if (is_branch_end_symbol(t)) {
L.next(); // Consume symbol
if (!branch_end(t)) {
return unexpected(PARSE_ERROR("Incomplete, Normal, Low or Generated not at a branch terminal", t.loc));
return unexpected(PARSE_ERROR("Incomplete, Normal, High, Low or Generated not at a branch terminal", t.loc));
}
finished = true;
}
Expand Down

0 comments on commit c79edd8

Please sign in to comment.