Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 137 additions & 22 deletions scripts/scraper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,108 @@
from glob import glob


_PP_CALL = re.compile(
r'\bpp(?P<separator>[._])(?P<method>[A-Za-z_]\w*)'
r'(?:\s*<\s*(?P<template>[^<>]+?)\s*>)?\s*\('
)


def _matching_parenthesis(text, opening):
"""Return the closing parenthesis for a C++ call, or None if unbalanced."""
depth = 0
quote = None
escaped = False

for i in range(opening, len(text)):
char = text[i]

if quote:
if escaped:
escaped = False
elif char == '\\':
escaped = True
elif char == quote:
quote = None
continue

if char in ['"', "'"]:
quote = char
elif char == '(':
depth += 1
elif char == ')':
depth -= 1
if depth == 0:
return i

return None


def _split_cpp_arguments(arguments):
"""Split C++ call arguments on commas outside (), [], {}, and strings."""
ret = []
start = 0
depths = {'(': 0, '[': 0, '{': 0}
closing = {')': '(', ']': '[', '}': '{'}
quote = None
escaped = False

for i, char in enumerate(arguments):
if quote:
if escaped:
escaped = False
elif char == '\\':
escaped = True
elif char == quote:
quote = None
continue

if char in ['"', "'"]:
quote = char
elif char in depths:
depths[char] += 1
elif char in closing:
opener = closing[char]
depths[opener] = max(0, depths[opener] - 1)
elif char == ',' and not any(depths.values()):
ret.append(arguments[start:i].strip())
start = i + 1

ret.append(arguments[start:].strip())
return ret


def _parse_pp_call(line, methods):
"""Parse a complete pp call while allowing nested C++ argument expressions."""
for match in _PP_CALL.finditer(line):
if match.group('method') not in methods:
continue

opening = match.end() - 1
closing = _matching_parenthesis(line, opening)
if closing is None:
continue

trailing = re.fullmatch(
r'\s*;\s*(?://\s*(.*?))?\s*', line[closing + 1:], re.DOTALL
)
if not trailing:
continue

return {
'method': match.group('method'),
'template': match.group('template'),
'arguments': _split_cpp_arguments(line[opening + 1:closing]),
'doc': trailing.group(1) or '',
}

return None


def _string_argument(argument):
match = re.fullmatch(r'\s*"([^"]+)"\s*', argument)
return match.group(1) if match else None


def allclassnames(root = "../src/"):
allclassnames = set()
for dirname, subdirlist, filelist in os.walk(root):
Expand Down Expand Up @@ -99,9 +201,7 @@ class inputdoc: pass
pp = r'pp[._]'
stringmatch = r'\s*"([^"]+)"\s*'
variablematch = r',\s*[\w.]+\s*'
unitmatch = r'(?:,\s*([^,()]+(?:\([^()]*\))?))?'
docmatch = r'\s*;\s*(?:\/\/\s*(.*))?$'
defaultmatch = r',\s*([^,)\s][^,)]*)\s*'
templatematch = r'\s*<([^>]+)>\s*'
stringarraymatch = r',\s*\{(.*)\}\s*'
nargs = r',*\s*([^)]*)'
Expand All @@ -113,13 +213,19 @@ class inputdoc: pass


# Catch standard pp.query and pp.queryarr inputs
match = re.findall(rf'{pp}(query(?:arr)?(?:_required)?(?:_file)?)\s*\({stringmatch}{variablematch}{unitmatch}\){docmatch}',line)
if match:
parsed = _parse_pp_call(line, {
"query", "queryarr", "query_required", "queryarr_required", "query_file"
})
if parsed and len(parsed["arguments"]) in [2, 3]:
name = _string_argument(parsed["arguments"][0])
else:
name = None
if name is not None:
query = dict()
query["type"] = match[0][0]
query["string"] = match[0][1]
query["unit"] = match[0][2]
query["doc"] = match[0][3]
query["type"] = parsed["method"]
query["string"] = name
query["unit"] = parsed["arguments"][2] if len(parsed["arguments"]) == 3 else ""
query["doc"] = parsed["doc"]
query["file"] = filename
query["line"] = i+1

Expand All @@ -133,14 +239,18 @@ class inputdoc: pass
continue

# Catch standard pp.query_default and pp.queryarr_default inputs
match = re.findall(rf'{pp}(query(?:arr)?_default)\s*\({stringmatch}{variablematch}{defaultmatch}{unitmatch}\){docmatch}',line)
if match:
parsed = _parse_pp_call(line, {"query_default", "queryarr_default"})
if parsed and len(parsed["arguments"]) in [3, 4]:
name = _string_argument(parsed["arguments"][0])
else:
name = None
if name is not None:
query = dict()
query["type"] = match[0][0]
query["string"] = match[0][1]
query["default"] = match[0][2]
query["unit"] = match[0][3]
query["doc"] = match[0][4]
query["type"] = parsed["method"]
query["string"] = name
query["default"] = parsed["arguments"][2]
query["unit"] = parsed["arguments"][3] if len(parsed["arguments"]) == 4 else ""
query["doc"] = parsed["doc"]
query["file"] = filename
query["line"] = i+1

Expand Down Expand Up @@ -174,14 +284,20 @@ class inputdoc: pass
rets.append(query)
continue

# Catch standard pp.query_validate
match = re.findall(rf'{pp}query_exactly\s*<\s*(\d+)\s*>\s*\(\s*\{{([^}}]*)\}}\s*,[^)]*\)\s*;\s*(?:\/\/\s*(.*))?',line)
if match:
# Catch pp.query_exactly, including nested unit expressions.
parsed = _parse_pp_call(line, {"query_exactly"})
if parsed and parsed["template"] and len(parsed["arguments"]) in [2, 3]:
number = re.fullmatch(r'\s*(\d+)\s*', parsed["template"])
possibles = re.fullmatch(r'\s*\{(.*)\}\s*', parsed["arguments"][0], re.DOTALL)
else:
number = None
possibles = None
if number and possibles:
query = dict()
query["type"] = "query_exactly"
query["number"] = match[0][0]
query["possibles"] = match[0][1]
query["doc"] = match[0][2]
query["number"] = number.group(1)
query["possibles"] = possibles.group(1)
query["doc"] = parsed["doc"]
query["file"] = filename
query["line"] = i+1
query["default"] = True
Expand Down Expand Up @@ -489,4 +605,3 @@ def alphabetize_with_abstract_first(key):
data[classname]['mainfile'] = f'src/{dirname.replace(root,"")}/{basefilename}.cc'

return data

15 changes: 5 additions & 10 deletions src/IC/PointList.H
Original file line number Diff line number Diff line change
Expand Up @@ -341,16 +341,11 @@ public:

// Per-polygon void flag: nonzero marks a polygon as a void/negative region that is cut out of
// the solid field instead of being unioned into it. Must have one entry per polygon if given.
if (pp.queryarr("invert", value.invert))
{
Util::AssertException(INFO, TEST(value.invert.size() == value.polygons.size()),
"'invert' must have one entry per polygon: expected ", value.polygons.size(),
" but got ", value.invert.size());
}
else
{
value.invert.assign(value.polygons.size(), 0);
}
pp.queryarr("invert", value.invert);

Util::AssertException( INFO, TEST(value.invert.size() == value.polygons.size()),
"'invert' must have one entry per polygon: expected ", value.polygons.size(),
" but got ", value.invert.size());
}

private:
Expand Down
6 changes: 4 additions & 2 deletions src/Integrator/Base/Mechanics.H
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public:
if (value.m_type == Type::Static)
{
// Read parameters for :ref:`Solver::Nonlocal::Newton` solver
pp_queryclass("solver", value.solver);
pp.queryclass("solver", value.solver);
}
if (value.m_type == Type::Dynamic)
{
Expand Down Expand Up @@ -195,7 +195,9 @@ protected:
elastic_op.SetHomogeneous(false);
elastic_op.SetBC(bc);
IO::ParmParse pp("elasticop");
pp_queryclass(elastic_op);

// Elastic operator
pp.queryclass(elastic_op);

solver.Define(elastic_op);
if (psi_on) solver.setPsi(psi_mf);
Expand Down
1 change: 1 addition & 0 deletions src/Integrator/Hydro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ Hydro::Parse(Hydro& value, IO::ParmParse& pp)
if (prescribedflowmode_str == "absolute") value.prescribedflowmode = PrescribedFlowMode::Absolute;
else if (prescribedflowmode_str == "relative") value.prescribedflowmode = PrescribedFlowMode::Relative;

// Gravitational acceleration vector
pp.queryarr_default("g",value.g,Set::Vector::Zero());

bool allow_unused;
Expand Down
Loading
Loading