Skip to content

Commit

Permalink
Improved declared-variable parsing (#75)
Browse files Browse the repository at this point in the history
* [Refactor] Use CDeclarator instead of namedtuple

- Replaces the use of namedtuple 'CDeclaration' by new class
- Thus far only replicates what was possible previously, but should also
  enable more-robust handling of user/component `DECLARE`d values.

* [Refactor] move formatting for struct to class

- The function pointer component struct member now work.
- A compiled test shows how this _could_ be used, but
  doesn't motivate why you might want to use such a feature.

* [Ref] cleanup out of date comments, unused code

* [Fix] some static analysis issues

* [Fix] python 3.9 zip doesn't accept strict
  • Loading branch information
g5t authored Sep 27, 2024
1 parent 6f7efaf commit 47c029c
Show file tree
Hide file tree
Showing 18 changed files with 648 additions and 511 deletions.
19 changes: 10 additions & 9 deletions src/mccode_antlr/assembler/assembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,19 @@ def user_vars(self, string, source=None, line=-1):
def ensure_user_var(self, string, source=None, line=-1):
# tying the Assembler to work with C might not be great
from mccode_antlr.translators.c_listener import extract_c_declared_variables as parse
input = parse(string)
if len(input) == 0:
variables = parse(string)
if len(variables) == 0:
raise ValueError(f'The provided input {string} does not specify a C parameter declaration.')
if len(input) != 1:
print(f'The provided input {string} specifies {len(input)} C parameter declarations, using only the first')
name = list(input.keys())[0]
dtype, _ = input[name]
if len(variables) != 1:
print(f'The provided input {string} specifies {len(variables)} C parameter declarations, using only the first')
decl = variables[0]
name = decl.name
dtype = decl.dtype
for user_vars in self.instrument.user:
dec_type_init_dict = parse(user_vars.source)
if any(d == dtype and n == name for n, (d, _) in dec_type_init_dict.items()):
uv_variables = parse(user_vars.source)
if any(x.dtype == dtype and x.name == name for x in uv_variables):
return
if any(n == name for n in dec_type_init_dict):
if any(x.name == name for x in uv_variables):
print(f'A USERVARS variable with name {name} but type different than {dtype} has already been defined.')
return
return self.user_vars(string, source=source, line=line)
Expand Down
4 changes: 2 additions & 2 deletions src/mccode_antlr/common/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ def __mul__(self, other):
if self.is_value(-1):
return (-other).as_type(pdt)
if other.is_value(1):
return (self).as_type(pdt)
return self.as_type(pdt)
if other.is_value(-1):
return (-self).as_type(pdt)
if other.is_op or self.is_id or other.is_id:
Expand All @@ -888,7 +888,7 @@ def __truediv__(self, other):
if self.is_zero:
return Value(0, DataType.int if pdt.is_str else pdt)
if other.is_value(1):
return (self).as_type(pdt)
return self.as_type(pdt)
if other.is_value(-1):
return (-self).as_type(pdt)
if other.is_zero:
Expand Down
24 changes: 12 additions & 12 deletions src/mccode_antlr/comp/comp.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ class Comp:
"""
name: str = None # Component *type* name, e.g. {name}.comp
category: str = None # Component type catagory -- nearly free-form
define: tuple[ComponentParameter] = field(default_factory=tuple) # C #define'ed parameters
setting: tuple[ComponentParameter] = field(default_factory=tuple) # Formal 'setting' parameters
output: tuple[ComponentParameter] = field(default_factory=tuple) # 'output' parameters
metadata: tuple[MetaData] = field(default_factory=tuple) # metadata for use by simulation consumers
define: tuple[ComponentParameter, ...] = field(default_factory=tuple) # C #define'ed parameters
setting: tuple[ComponentParameter, ...] = field(default_factory=tuple) # Formal 'setting' parameters
output: tuple[ComponentParameter, ...] = field(default_factory=tuple) # 'output' parameters
metadata: tuple[MetaData, ...] = field(default_factory=tuple) # metadata for use by simulation consumers
dependency: str = None # compile-time dependency
acc: bool = True # False if this component *can not* work under OpenACC
# literal strings writen into C source files
share: tuple[RawC] = field(default_factory=tuple) # function(s) for all instances of this class
user: tuple[RawC] = field(default_factory=tuple) # struct members for _particle
declare: tuple[RawC] = field(default_factory=tuple) # global parameters used in component trace
initialize: tuple[RawC] = field(default_factory=tuple) # initialization of global declare parameters
trace: tuple[RawC] = field(default_factory=tuple) # per-particle interaction executed at TRACE time
save: tuple[RawC] = field(default_factory=tuple) # statements executed after TRACE to save results
final: tuple[RawC] = field(default_factory=tuple) # clean-up memory for global declare parameters
display: tuple[RawC] = field(default_factory=tuple) # draw this component
share: tuple[RawC, ...] = field(default_factory=tuple) # function(s) for all instances of this class
user: tuple[RawC, ...] = field(default_factory=tuple) # struct members for _particle
declare: tuple[RawC, ...] = field(default_factory=tuple) # global parameters used in component trace
initialize: tuple[RawC, ...] = field(default_factory=tuple) # initialization of global declare parameters
trace: tuple[RawC, ...] = field(default_factory=tuple) # per-particle interaction executed at TRACE time
save: tuple[RawC, ...] = field(default_factory=tuple) # statements executed after TRACE to save results
final: tuple[RawC, ...] = field(default_factory=tuple) # clean-up memory for global declare parameters
display: tuple[RawC, ...] = field(default_factory=tuple) # draw this component

def __hash__(self):
return hash(repr(self))
Expand Down
2 changes: 1 addition & 1 deletion src/mccode_antlr/instr/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def set_parameter(self, name: str, value, overwrite=False, allow_repeated=True):

self.parameters += (ComponentParameter(p.name, value), )

def verify_parameters(self, instrument_parameters: tuple[InstrumentParameter]):
def verify_parameters(self, instrument_parameters: tuple[InstrumentParameter, ...]):
"""Check for instance parameters which are identifiers that match instrument parameter names,
and flag them as parameter objects"""
instrument_parameter_names = [x.name for x in instrument_parameters]
Expand Down
22 changes: 11 additions & 11 deletions src/mccode_antlr/instr/instr.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ class Instr:
"""
name: str = None # Instrument name, e.g. {name}.instr (typically)
source: str = None # Instrument *file* name
parameters: tuple[InstrumentParameter] = field(default_factory=tuple) # runtime-set instrument parameters
metadata: tuple[MetaData] = field(default_factory=tuple) # metadata for use by simulation consumers
components: tuple[Instance] = field(default_factory=tuple) #
included: tuple[str] = field(default_factory=tuple) # names of included instr definition(s)
user: tuple[RawC] = field(default_factory=tuple) # struct members for _particle
declare: tuple[RawC] = field(default_factory=tuple) # global parameters used in component trace
initialize: tuple[RawC] = field(default_factory=tuple) # initialization of global declare parameters
save: tuple[RawC] = field(default_factory=tuple) # statements executed after TRACE to save results
final: tuple[RawC] = field(default_factory=tuple) # clean-up memory for global declare parameters
parameters: tuple[InstrumentParameter, ...] = field(default_factory=tuple) # runtime-set instrument parameters
metadata: tuple[MetaData, ...] = field(default_factory=tuple) # metadata for use by simulation consumers
components: tuple[Instance, ...] = field(default_factory=tuple) #
included: tuple[str, ...] = field(default_factory=tuple) # names of included instr definition(s)
user: tuple[RawC, ...] = field(default_factory=tuple) # struct members for _particle
declare: tuple[RawC, ...] = field(default_factory=tuple) # global parameters used in component trace
initialize: tuple[RawC, ...] = field(default_factory=tuple) # initialization of global declare parameters
save: tuple[RawC, ...] = field(default_factory=tuple) # statements executed after TRACE to save results
final: tuple[RawC, ...] = field(default_factory=tuple) # clean-up memory for global declare parameters
groups: dict[str, Group] = field(default_factory=dict)
flags: tuple[str] = field(default_factory=tuple) # (C) flags needed for compilation of the (translated) instrument
registries: tuple[Registry] = field(default_factory=tuple) # the registries used by the reader to populate this
flags: tuple[str, ...] = field(default_factory=tuple) # (C) flags needed for compilation of the (translated) instrument
registries: tuple[Registry, ...] = field(default_factory=tuple) # the registries used by the reader to populate this

def to_file(self, output=None, wrapper=None):
if output is None:
Expand Down
12 changes: 6 additions & 6 deletions src/mccode_antlr/instr/orientation.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ def axes_euler_angles(m: Rotation, degrees) -> Angles:
@dataclass
class Part:
"""The Seitz matrix part of any arbitrary projective affine transformation"""
_axes: Seitz = field(default_factory=Seitz)
_axes: Seitz = field(default_factory=lambda: Seitz())

def __post_init__(self):
"""If this is not defined, the subclass' __post_init__ may not be called"""
Expand All @@ -449,7 +449,7 @@ def is_rotation(self):
# The first condition _should_ always be true -- the second is only true if this is not the identity matrix
if round((self._axes.inverse() * self._axes).trace(), 12) == Expr.float(3.):
return round(self._axes.trace(), 12) != Expr.float(3.)
loging.info(f'Not a rotation matrix: {self._axes}')
logger.info(f'Not a rotation matrix: {self._axes}')
return False

@property
Expand All @@ -474,11 +474,11 @@ def rotation_axis_angle(self) -> tuple[Vector, Expr, str]:
# The eigenvalues, dd, are (1+0j, a+bj, a-bj) of which we want 1+0j.
axis = vv[:, argmin(sqrt(real(conj(dd-1) * (dd-1))))]
if sum(imag(axis)) != 0:
loging.warning(f'Imaginary rotation axis {real(axis)} + j {imag(axis)}')
logger.warning(f'Imaginary rotation axis {real(axis)} + j {imag(axis)}')
axis = real(axis)
cos_angle = (matrix[0][0] + matrix[1][1] + matrix[2][2] - 1) / 2
if abs(cos_angle) > 1:
loging.warning(f'Invalid cos(angle) {cos_angle} for {self}')
logger.warning(f'Invalid cos(angle) {cos_angle} for {self}')
cos_angle = 1 if cos_angle > 0 else -1
angle = Expr.float(acos_degree(cos_angle))
axis = Vector(Expr.float(axis[0]), Expr.float(axis[1]), Expr.float(axis[2]))
Expand Down Expand Up @@ -560,7 +560,7 @@ def __contains__(self, value):
@dataclass
class TranslationPart(Part):
"""A specialization to the translation-only part of a projective affine transformation"""
v: Vector = field(default_factory=Vector)
v: Vector = field(default_factory=lambda: Vector())

def __str__(self):
return f'({self.v[0]}, {self.v[1]}, {self.v[2]}) [0, 0, 0]'
Expand Down Expand Up @@ -733,7 +733,7 @@ def __repr__(self):
def stack(self):
return self._stack

def _copy(self, deep: bool = True) -> tuple[Part]:
def _copy(self, deep: bool = True) -> tuple[Part, ...]:
if deep:
from copy import deepcopy
return tuple([deepcopy(x) for x in self._stack])
Expand Down
2 changes: 2 additions & 0 deletions src/mccode_antlr/loader/loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from pathlib import Path
from typing import Union
from mccode_antlr.instr import Instr
Expand Down
46 changes: 9 additions & 37 deletions src/mccode_antlr/translators/c.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,10 @@
"""Translates a McComp instrument from its intermediate form to a C runtime source file."""
from loguru import logger
from collections import namedtuple
from dataclasses import dataclass
from ..reader import Registry, LIBC_REGISTRY
from ..instr import Instr, Instance
from ..reader import LIBC_REGISTRY
from .target import TargetVisitor
from .c_listener import extract_c_declared_variables

# For use in keeping track of 'USERVAR' particle struct injections
CDeclaration = namedtuple("CDeclaration", "name type init is_pointer is_array orig")


def append_cdeclaration_name(decl: CDeclaration, suffix):
return CDeclaration(f'{decl.name}_{suffix}', decl.type, decl.init, decl.is_pointer, decl.is_array, decl.orig)


def extract_declaration(dec, c_type, init):
is_pointer = '*' in dec
is_array = '[' in dec and ']' in dec
if is_pointer:
name = dec.translate(str.maketrans('', '', '*'))
elif is_array:
# since dec could be 'name[x][y][z]...[a]' don't attempt to parse the size of the array
name = dec.split('[', 1)[0]
else:
name = dec
return CDeclaration(name, c_type, init, is_pointer, is_array, dec)


def extracted_declares(declares):
return [extract_declaration(dec, c_type, init) for dec, (c_type, init) in declares.items()]


@dataclass
class CInclude:
Expand Down Expand Up @@ -166,18 +140,18 @@ def _handle_raw_c_include(self, parent: str, raw_c: str):
return includes, raw_c

def _parse_libraries_for_typedefs(self):
from .c_listener import extract_c_declared_variables_and_defined_types as parse
from .c_listener import extract_c_defined_types as parse
typedefs = set()
for include in self.includes:
# logger.debug(f'library {include.name}')
# The files '%include'-d can themselves use the '%include' mechanism :/
declares, defined_types = parse(include.content, user_types=list(typedefs))
defined_types = parse(include.content, user_types=list(typedefs))
typedefs = typedefs.union(set(defined_types))
# logger.debug(f'{include.name} done')
# types can also be defined in component 'SHARE' blocks:
for block in [share for comp in self.source.component_types() if len(comp.share) for share in comp.share]:
# TODO decide if this should be block.translated or block.source
declares, defined_types = parse(block.to_c(), user_types=list(typedefs))
defined_types = parse(block.to_c(), user_types=list(typedefs))
typedefs = typedefs.union(set(defined_types))
self.typedefs = list(typedefs)

Expand All @@ -199,7 +173,7 @@ def _determine_uservars(self):
"""
def extract_declares(name, raw_c_obj):
# TODO decide if this should be raw_c_obj.translated or raw_c_obj.source
c_decs = extracted_declares(extract_c_declared_variables(raw_c_obj.to_c(), user_types=self.typedefs))
c_decs = extract_c_declared_variables(raw_c_obj.to_c(), user_types=self.typedefs)
if any(d.init is not None for d in c_decs):
logger.critical(f'Warning USERVARS block from {name} contains assignment(s) (= sign).')
logger.critical(' Move them to an EXTEND section. May fail at compile')
Expand Down Expand Up @@ -236,8 +210,7 @@ def extract_declares(name, raw_c_obj):
i_declares = []
for index, instance in enumerate(self.source.components):
if instance.type.name == comp_name:
for dec in a_declares:
i_declares.append(append_cdeclaration_name(dec, index+1))
i_declares.extend([d.copy(suffix=str(index+1)) for d in a_declares])
inst_declares[comp_name] = i_declares
# Replace the component definition 'base' uservar declares with the 'real' ones:
comp_declares = inst_declares
Expand Down Expand Up @@ -284,13 +257,12 @@ def __post_init__(self):
self._parse_libraries_for_typedefs()

# pull together the per-component-type defined parameters into a dictionary... since this is required
# in multiple places :/ -- now using CDeclaration named tuples
sc = str.maketrans('', '', '*[] ') # for '* name' or '*name', or '**** name' or 'name[]' -> 'name'
# in multiple places :/ -- now using CDeclarator class objects
for typ in inst.component_types():
dp = []
for block in typ.declare:
# TODO decide if this should be block.translated or block.source
dp.extend(extracted_declares(extract_c_declared_variables(block.to_c(), user_types=self.typedefs)))
dp.extend(extract_c_declared_variables(block.to_c(), user_types=self.typedefs))
dp = list(dict.fromkeys(dp))
self.component_declared_parameters[typ.name] = dp
self._determine_uservars()
Expand All @@ -313,7 +285,7 @@ def visit_header(self):
uuv = self._instrument_and_component_uservars()

is_mcstas = self.is_mcstas
self.out(header_pre_runtime(is_mcstas, self.source, self.runtime, self.config, self.typedefs, uuv))
self.out(header_pre_runtime(is_mcstas, self.source, self.runtime, self.config, uuv))
# runtime part
if self.config.get('include_runtime'):
self.out('#define MC_EMBEDDED_RUNTIME')
Expand Down
19 changes: 2 additions & 17 deletions src/mccode_antlr/translators/c_decls.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ def component_type_declaration(comp, typedefs: list, declared_parameters: list):
# The call tree for functions that access `comp->def->out_par` is such that the pointer is not used before it
# is replaced, so at least there is no ambiguity between DECLARE-found parameters and OUTPUT PARAMETERS in cogen.

warnings = 0
lines = [
f"/* Parameter definition for component type '{comp.name}' */",
f'struct _struct_{comp.name}_parameters {{',
Expand All @@ -179,22 +178,8 @@ def component_type_declaration(comp, typedefs: list, declared_parameters: list):
lines.append(f' {par.value.mccode_c_type} {par.name};')

# This is the loop over the *replaced* `comp->def->out_par` e.g., found DECLARE parameters
lines.append(f"/* Component type '{comp.name}' private parameters */")
for x in declared_parameters:
# Switch these to use CDeclarations, then we have (.name, .type, .init, .is_pointer, .is_array, .orig)
# and the append would be f' {x.type} {x.orig}; /* {"Not initialized" if x.init is None else x.init} */'
# But of course, we need to do a bit more work to initialize any static array, so we instead would
# branch on x.is_array and then either count the number of initializer elements or punt to 16384 elements
# as McCode-3 does.
if x.is_array:
if x.init is None:
# hopefully handle all size-specified cases...
lines.append(f' {x.type} {x.orig}; /* Not initialized */')
else:
n_inits = 16384 if not isinstance(x.init, str) else min(len(x.init.split(',')), 16384)
lines.append(f' {x.type} {x.name}[{n_inits}]; /* {x.init} */')
else:
lines.append(f' {x.type} {x.orig}; /* {"Not initialized" if x.init is None else x.init} */')
lines.append(f" /* Component type '{comp.name}' private parameters */")
lines.extend([f' {x.as_struct_member()};' for x in declared_parameters])

if len(comp.setting) + len(declared_parameters) == 0:
lines.append(f' char {comp.name}_has_no_parameters;')
Expand Down
5 changes: 3 additions & 2 deletions src/mccode_antlr/translators/c_defines.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .c_listener import CDeclarator
from ..comp import Comp
from ..common import ComponentParameter

Expand All @@ -10,13 +11,13 @@ def undef(a: ComponentParameter):
return f'#undef {a.name}'


def cogen_parameter_define(comp: Comp, declares: list):
def cogen_parameter_define(comp: Comp, declares: list[CDeclarator]):
# All parameters get defines? Not just DEFINE PARAMETERS
lines = [define(par) for pars in (comp.define, comp.setting, comp.output, declares) for par in pars]
return '\n'.join(lines)


def cogen_parameter_undef(comp: Comp, declares: list):
def cogen_parameter_undef(comp: Comp, declares: list[CDeclarator]):
# The same parameters that were defined need to be undefined:
lines = [undef(par) for pars in (comp.define, comp.setting, comp.output, declares) for par in pars]
return '\n'.join(lines)
Loading

0 comments on commit 47c029c

Please sign in to comment.