Skip to content

Commit

Permalink
[Ref] C block declare and typedef parsing
Browse files Browse the repository at this point in the history
In order to insert DECLARE variables into the component-type parameters
struct, we need to be able to identify their type information.
These types _could_ be user-defined `typedef` aliases, provided in,
e.g., the SHARE section of the current or any-other component (probably
preceding only, but possibly trailing definitions, since SHARE goes
before all initialization).

Previously parsing for both variable declaration and typedefs was
performed in one pass, but this led to a confusing Listener class which
mistakenly identified typenames for `typedef` `struct`s incorrectly,
sometimes.
E.g.,

```
typdef struct the_struct_label {
    double a_double;
    int an_int;
} the_struct_alias;
```
would have been misidentified as the equivalent of
`typedef struct the_struct_label an_int;`

By splitting `typedef` and declaration parsing into two passes, there
are now two slightly-simpler Listener classes that handle the above case
plus all others implemented in a new test file.
This double-pass invariably means slower parsing, but this seems a
worthwhile trade-off at the moment.
  • Loading branch information
g5t committed Sep 25, 2024
1 parent 0e9f865 commit 23f1cce
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 32 deletions.
66 changes: 37 additions & 29 deletions src/mccode_antlr/translators/c_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,33 @@ def syntaxError(self, recognizer, offendingSymbol, *args, **kwargs):

return ErrorListener()

class TypedefCListener(CListener):
def __init__(self, typedefs: list = None):
self.typedefs = [] if typedefs is None else [x for x in typedefs]
self.is_typedef = False
self.typedef_name = None

def enterDeclaration(self, ctx: CParser.DeclarationContext):
self.is_typedef = False
self.typedef_name = None

def exitDeclaration(self, ctx: CParser.DeclarationContext):
if self.is_typedef and self.typedef_name is not None:
typename = self.typedef_name.replace('*', '').strip()
if typename not in self.typedefs:
self.typedefs.append(typename)

def enterStorageClassSpecifier(self, ctx: CParser.StorageClassSpecifierContext):
self.is_typedef = 'typedef' in literal_string(ctx)

def enterTypedefName(self, ctx: CParser.TypedefNameContext):
self.typedef_name = literal_string(ctx)


class DeclaresCListener(CListener):
def __init__(self, typedefs: list = None, verbose=False):
self.verbose = verbose
self.typedefs = [] if typedefs is None else typedefs
self.typedefs = [] if typedefs is None else [x for x in typedefs]
self.variables = dict() # self.variables['name'] = type, value set by listeners
self.last_type = None
self.last_name = None
Expand All @@ -62,18 +84,7 @@ def enterDeclaration(self, ctx: CParser.DeclarationContext):

# Exit a parse tree produced by CParser#declaration.
def exitDeclaration(self, ctx: CParser.DeclarationContext):
if self.is_typedef:
typename = self.not_type_name if self.last_name is None else self.last_name
if typename is None:
print(literal_string(ctx))
print(self.last_type)
print(self.last_name)
print(self.last_value)
print(self.not_type_name)
return
self.typedefs.append(typename.replace('*', '').strip())
self.is_typedef = False
elif not self.stored and self.not_type_name is not None and self.last_type.endswith(self.not_type_name):
if not self.stored and self.not_type_name is not None and self.last_type.endswith(self.not_type_name):
# this can only happen for declaration lines? where no initialization is performed.
new_type = self.last_type[:-len(self.not_type_name)].strip()
self.debug(f'Storing declaration for {self.not_type_name} = ({new_type}, {self.last_value})')
Expand All @@ -85,19 +96,19 @@ def exitDeclaration(self, ctx: CParser.DeclarationContext):
self.last_name = None
self.last_value = None
self.is_declaration = False
self.is_typedef = False
self.debug('Exit declaration')

def enterDeclarationSpecifiers(self, ctx: CParser.DeclarationSpecifiersContext):
self.last_type = literal_string(ctx)
self.debug(f'Enter Declaration Specifier {self.last_type=}')

def enterStorageClassSpecifier(self, ctx: CParser.StorageClassSpecifierContext):
self.is_typedef = 'typedef' in literal_string(ctx)
self.debug(f'Enter storage class specifier {self.is_typedef=}')
self.debug(f'Enter Declaration Specifier last_type=\n{self.last_type}')

def enterTypeSpecifier(self, ctx: CParser.TypeSpecifierContext):
self.was_type = True
self.debug(f'Enter type specifier')
self.debug(f'Enter type specifier ctx=\n{literal_string(ctx)}')

def enterStorageClassSpecifier(self, ctx: CParser.StorageClassSpecifierContext):
self.is_typedef = 'typedef' in literal_string(ctx)

def enterTypedefName(self, ctx: CParser.TypedefNameContext):
# Check here if this is a known typedef'd name or not :/
Expand Down Expand Up @@ -158,23 +169,20 @@ def extract_c_declared_variables_and_defined_types(block: str, user_types: list
from antlr4 import ParseTreeWalker
from antlr4.error.ErrorListener import ErrorListener
from ..grammar import CLexer
# self.debug('Load block into ANTLR4 stream')
# self.debug(f'{block}')
stream = InputStream(block)
# self.debug('Run lexer')
lexer = CLexer(stream)
# self.debug('Tokenize stream')
tokens = CommonTokenStream(lexer)
# self.debug('Parse tokens')
parser = CParser(tokens)
parser.addErrorListener(make_error_listener(ErrorListener, block))
# self.debug('Extract compilation unit tree')
tree = parser.compilationUnit()
# self.debug('Initialize listener')
listener = DeclaresCListener(user_types, verbose=verbose)
# self.debug('Initialize walker')
# Go through a first time to identify types -- this _could_ be problematic
# if typedefs happen _after_ their name(s) are used. But we ignore that for now.
typedef_listener = TypedefCListener(user_types)
typedef_walker = ParseTreeWalker()
typedef_walker.walk(typedef_listener, tree)
# Go through again to get the declarations:
listener = DeclaresCListener(typedef_listener.typedefs, verbose=verbose)
walker = ParseTreeWalker()
# self.debug('Walk the tree')
walker.walk(listener, tree)
return listener.variables, listener.typedefs

Expand Down
26 changes: 23 additions & 3 deletions tests/runtime/test_examples.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from mccode_antlr.loader.loader import parse_mcstas_instr
from textwrap import dedent

from .compiled import compiled, compile_and_run

@compiled
def test_template_instr():
"""
This test failed previously because its name is a reserved word in C++ (template)
"""
from mccode_antlr.loader.loader import parse_mcstas_instr
from textwrap import dedent
instr = parse_mcstas_instr(dedent("""\
DEFINE INSTRUMENT template(Par1=1)
DECLARE %{ %}
Expand All @@ -17,4 +18,23 @@ def test_template_instr():
FINALLY %{ %}
END
"""))
compile_and_run(instr, "-n 1 Par1=2")
compile_and_run(instr, "-n 1 Par1=2")


@compiled
def test_component_declare_variable_initialised():
instr = parse_mcstas_instr(dedent("""\
DEFINE INSTRUMENT namedsomething(dummy=0)
DECLARE %{
int initialized;
%}
INITIALIZE %{
initialized = -1;
%}
TRACE
component Origin = Progress_bar() at (0, 0, 0) absolute
component mirror = SupermirrorFlat() at (0, 0, 0) absolute
FINALLY %{ %}
END
"""))
compile_and_run(instr, '-n 1 dummy=1')
127 changes: 127 additions & 0 deletions tests/test_c_type_declaration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from mccode_antlr.translators.c_listener import (
extract_c_declared_variables_and_defined_types as extract
)
from textwrap import dedent

def test_alias_types():
block = dedent("""\
typedef unsigned int age_t;
""")
variables, types = extract(block)
assert len(variables) == 0
assert len(types) == 1
assert types[0] == 'age_t'


def test_stackoverflow_struct_type():
block = dedent("""\
typedef struct nodeT {
void *content;
struct nodeT *next;
} Node;
""")
variables, types = extract(block)
assert len(variables) == 0
assert len(types) == 1
assert 'Node' == types[0]

def test_stackoverflow_enum_type():
block = dedent("""\
typedef enum season_t { SPRING, SUMMER, FALL, WINTER } Season;
""")
variables, types = extract(block)
assert len(variables) == 0
assert len(types) == 1
assert 'Season' == types[0]

def test_repeated_struct_name():
block = dedent("""\
typedef struct PolyhedronIndexValuePair {
int index;
double value;
} PolyhedronIndexValuePair;
""")
variables, types = extract(block)

assert len(variables) == 0
assert len(types) == 1
assert types[0] == 'PolyhedronIndexValuePair'


def test_mccode_style_typedef():
block = dedent("""\
struct _struct_Progress_bar {
char _name[256]; /* e.g. instance of Progress_bar name */
char _type[13]; /* Progress_bar */
long _index; /* index in TRACE list */
Coords _position_absolute;
Coords _position_relative; /* wrt PREVIOUS */
Rotation _rotation_absolute;
Rotation _rotation_relative; /* wrt PREVIOUS */
int _rotation_is_identity;
int _position_relative_is_zero;
_class_Progress_bar_parameters _parameters;
};
typedef struct _struct_Progress_bar _class_Progress_bar;
""")
variables, types = extract(block)
assert len(variables) == 0
assert len(types) == 1
assert types[0] == "_class_Progress_bar"


def test_assignments():
block = dedent("""\
int blah=1;
double yarg;
// yarg = 2.0; // this parser only handles _declarations_ no out-of-declaration assignments allowed.
char mmmm[11] = "0123456789";
""")
variables, types = extract(block)
assert len(variables) == 3
assert len(types) == 0
assert 'blah' in variables
assert variables['blah'] == ('int', '1')
assert 'yarg' in variables
assert variables['yarg'] == ('double', None)
assert 'mmmm[11]' in variables
assert variables['mmmm[11]'] == ('char', '"0123456789"')

def test_struct_declaration():
block = dedent("""\
struct my_struct_type the_struct;
struct another_struct a_struct_with_values={0, 1.0, "two"};
struct the_third_s * ptr_to_third_struct;
""")
variables, types = extract(block)
assert len(variables) == 3
assert len(types) == 0
expected = {
'the_struct': ('struct my_struct_type', None),
'a_struct_with_values': ('struct another_struct', '{0, 1.0, "two"}'),
'* ptr_to_third_struct': ('struct the_third_s', None),
}
assert all(x in variables for x in expected)
for name, (dtype, value) in expected.items():
assert(variables[name] == (dtype, value))


def test_typedef_declaration():
block = dedent("""\
typedef double blah;
blah really_a_double=1.0f;
blah * double_ptr=NULL;
blah double_array[10];
""")
variables, types = extract(block)
assert len(variables) == 3
assert len(types) == 1
assert types[0] == "blah"
expected = {
'really_a_double': ('blah', '1.0f'),
'* double_ptr': ('blah', 'NULL'),
'double_array[10]': ('blah', None),
}
assert all(x in variables for x in expected)
for name, (dtype, value) in expected.items():
assert(variables[name] == (dtype, value))

0 comments on commit 23f1cce

Please sign in to comment.