mirror of
https://github.com/open-simh/simh.git
synced 2026-01-14 07:40:35 +00:00
Make generate.py resuable outside of open-simh, as suggested and
motivated by Richard Cornwell's simulator repository.
- Make the "experimental" rule optional. Do not generate a Python
"KeyError" if the rule is missing.
- Add documentation on how to use the CMake infrastructure outside
of open-simh: Customize the packaging.py script, season to taste.
- Update the KA10 simulator customization, moving it to its own
Python script, simgen/pdp10_simulator.py. Preparatory move that
anticipates additional frontpanel and display options.
- generate.py option "--skip-orphans": Skip the orphaned simulator
check (i.e., don't cross-reference the simulators in packaging.py
with what was scraped from the makefile.)
- Add "TEST_ARGS" argument to CMake's add_simulator function so that the
IBM 1130 simulator can pass to "-g" on the command line to disable the
GUI when running RegisterSanityCheck, i.e.:
ibm1130 RegisterSanityCheck -g
This fixes an edge case Heisenbug encountered during Github CI/CD
tests where ibm1130 appears to hang indefinitely on the Windows
runners.
The cause is the GUI's Pump() thread function being prematurely
terminated before all GUI resources are acquired. The net result is an
infinite loop in the MS C runtime trying to exit the process with
unstable internal state. (Separate patch: synchronization across main
and Pump() threads to ensure resource acquisition completes.)
This issue never shows up on non-Windows platforms or the SIMH makefile.
- cmake/generator.py, cmake/simgen: Add a "test_args" keyword argument
to the BasicSimulator constructor that holds the tests argument
parameter emitted as the "TEST_ARGS" argument to a simulator's
add_simulator(). Ensure that the IBM 1130 emits 'TEST_ARG "-g"' in its
add_simulator().
- scp.c: reset_all_p() adds 'P' to the existing switches, versus saving
sim_switches and ONLY setting the 'P' power-up reset switch. Net effect
is that the IBM 1130 simulator actually sees the 'G' flag that inhibits
the GUI during the console device reset.
195 lines
6.2 KiB
Python
195 lines
6.2 KiB
Python
"""Makefile parsing and variable expansion.
|
|
|
|
Read and collect variable, rule and action information from a [Mm]akefile.
|
|
This isn't a precise collection; for example, it does not respect GNU Makefile
|
|
directives such as 'ifeq' and 'ifneq'.
|
|
"""
|
|
|
|
import re
|
|
|
|
# Regexes needed for parsing Makefile (and similar syntaxes,
|
|
# like old-style Setup files).
|
|
_variable_rx = re.compile(r"\s*([A-Za-z][\w_-]+)\s*=\s*(.*)")
|
|
_rule_rx = re.compile(r"(((\$[({])*\w[\w_-]+[)}]*)+)\s*:\s*(.*)")
|
|
|
|
# Regex that recognizes variables. Group 1 is the variable's name.
|
|
_var_pattern = r"[A-Za-z][\w_-]*"
|
|
_var_rx = re.compile(r"^\$[{(](" + _var_pattern + r")[)}]$")
|
|
_var_rx2 = re.compile(r"\$[{(](" + _var_pattern + r")[)}]")
|
|
_norm_var_rx = re.compile(r"\$[(](" + _var_pattern + r")[)]")
|
|
|
|
def parse_makefile(fn, g_vars=None, g_rules=None, g_actions=None):
|
|
"""Parse a Makefile-style file.
|
|
|
|
Collects all of the variable definitions, rules and actions associated with rules.
|
|
|
|
"""
|
|
## Python 3.11 and onward dropped distuitls, so import our local copy.
|
|
from simgen.text_file import TextFile
|
|
|
|
fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape")
|
|
|
|
if g_vars is None:
|
|
g_vars = {}
|
|
if g_rules is None:
|
|
g_rules = {}
|
|
if g_actions is None:
|
|
g_actions = {}
|
|
done = {}
|
|
rules = {}
|
|
actions = {}
|
|
|
|
line = fp.readline()
|
|
while line is not None:
|
|
vmatch = _variable_rx.match(line)
|
|
rmatch = _rule_rx.match(line)
|
|
if vmatch:
|
|
n, v = vmatch.group(1, 2)
|
|
v = v.strip()
|
|
|
|
try:
|
|
v = int(v)
|
|
except ValueError:
|
|
# insert literal `$'
|
|
done[n] = v.replace('$$', '$')
|
|
else:
|
|
done[n] = v
|
|
|
|
line = fp.readline()
|
|
elif rmatch:
|
|
## make allows "$(VAR)" and "${VAR}" to be used interchangably, so there's a
|
|
## possibility that the sim developer used both forms in the target, e.g.:
|
|
##
|
|
## foosim: $(BIN)foosim$(EXE)
|
|
##
|
|
## ${BIN}foosim${EXE}: stuff that foosim depends on...
|
|
|
|
n, v = rmatch.group(1, 4)
|
|
rules[normalize_variables(n)] = normalize_variables(v)
|
|
|
|
## Collect the actions:
|
|
collected = []
|
|
line = fp.readline()
|
|
while line is not None:
|
|
m = _variable_rx.match(line) or _rule_rx.match(line)
|
|
if m is None:
|
|
collected.append(line.lstrip())
|
|
line = fp.readline()
|
|
else:
|
|
break
|
|
actions[n] = collected
|
|
else:
|
|
line = fp.readline()
|
|
|
|
fp.close()
|
|
|
|
# strip spurious spaces
|
|
for k, v in done.items():
|
|
if isinstance(v, str):
|
|
done[k] = v.strip().replace('\t', ' ')
|
|
|
|
# save the results in the global dictionary
|
|
g_vars.update(done)
|
|
g_rules.update(rules)
|
|
g_actions.update(actions)
|
|
return (g_vars, g_rules, g_actions)
|
|
|
|
|
|
def target_dep_list(target, rules, defs):
|
|
return (rules.get(target) or '').split()
|
|
|
|
def expand_vars(s, defs):
|
|
"""Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in
|
|
'string' according to 'defs' (a dictionary mapping variable names to
|
|
values). Variables not present in 'defs' are silently expanded to the
|
|
empty string.
|
|
|
|
Returns a variable-expanded version of 's'.
|
|
"""
|
|
|
|
# This algorithm does multiple expansion, so if defs['foo'] contains
|
|
# "${bar}", it will expand ${foo} to ${bar}, and then expand
|
|
# ${bar}... and so forth. This is fine as long as 'defs' comes from
|
|
# 'parse_makefile()', which takes care of such expansions eagerly,
|
|
# according to make's variable expansion semantics.
|
|
|
|
while True:
|
|
m = _var_rx2.search(s)
|
|
if m:
|
|
(beg, end) = m.span()
|
|
s = s[0:beg] + (defs.get(m.group(1)) or '') + s[end:]
|
|
else:
|
|
break
|
|
return s
|
|
|
|
|
|
def shallow_expand_vars(s, defs):
|
|
"""Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in
|
|
'string' according to 'defs' (a dictionary mapping variable names to
|
|
values). Variables not present in 'defs' are silently expanded to the
|
|
empty string.
|
|
|
|
Returns a variable-expanded version of 's'.
|
|
"""
|
|
|
|
# This algorithm does multiple expansion, so if defs['foo'] contains
|
|
# "${bar}", it will expand ${foo} to ${bar}, and then expand
|
|
# ${bar}... and so forth. This is fine as long as 'defs' comes from
|
|
# 'parse_makefile()', which takes care of such expansions eagerly,
|
|
# according to make's variable expansion semantics.
|
|
|
|
m = _var_rx2.search(s)
|
|
if m:
|
|
(beg, end) = m.span()
|
|
return s[0:beg] + (defs.get(m.group(1)) or '') + shallow_expand_vars(s[end:], defs)
|
|
|
|
return s
|
|
|
|
|
|
def extract_variables(varstr):
|
|
"""Extracct all variable references, e.g., "${foo}" or "$(foo)"
|
|
from a string.
|
|
"""
|
|
retval = []
|
|
tmp = varstr
|
|
while True:
|
|
m = _var_rx2.search(tmp)
|
|
if m:
|
|
retval.append(m[1])
|
|
tmp = tmp[m.end():]
|
|
else:
|
|
break
|
|
return retval
|
|
|
|
|
|
def normalize_variables(varstr):
|
|
"""Convert '$(var)' to '${var}' -- normalizes all variables to a consistent
|
|
form.
|
|
"""
|
|
retval = ""
|
|
tmp = varstr
|
|
while tmp:
|
|
m = _norm_var_rx.search(tmp)
|
|
if m:
|
|
retval += tmp[:m.start()] + "${" + m[1] + "}"
|
|
tmp = tmp[m.end():]
|
|
else:
|
|
retval += tmp
|
|
tmp = ""
|
|
return retval
|
|
|
|
|
|
def test_rule_rx():
|
|
result = _rule_rx.match('${BIN}frontpaneltest${EXE} : frontpanel/FrontPanelTest.c sim_sock.c sim_frontpanel.c')
|
|
print('{0}: {1}'.format('${BIN}frontpaneltest${EXE}...', result))
|
|
print(result.groups())
|
|
|
|
|
|
def test_normalize_variables():
|
|
result = normalize_variables('foo: bar baz')
|
|
print('{0}: {1}'.format('foo:...', result))
|
|
result = normalize_variables('$(var): dep1 dep2')
|
|
print('{0}: {1}'.format('$(var)...', result))
|
|
result = normalize_variables('$(var): dep1 ${var2} dep2 $(var3)')
|
|
print('{0}: {1}'.format('$(var)...', result))
|