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
4 changes: 4 additions & 0 deletions src/plcdoc/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,15 @@ def _parse_file(self, filepath) -> bool:
plc_item = item.tag # I.e. "POU"

if plc_item not in self.XML_TYPES:
logger.warning(f"Skipping file with XML tag {plc_item}")
continue

# Name is repeated inside the declaration, use it from there instead
# name = item.attrib["Name"]

object_model = self._parse_declaration(item)
if object_model is None:
# Log entry is made inside _parse_declaration() already
continue

obj = PlcDeclaration(object_model, filepath)
Expand Down Expand Up @@ -287,6 +289,8 @@ def __init__(self, meta_model: TextXMetaClass, file=None):
self._objtype = "struct"
elif "Union" in type_str:
self._objtype = "union"
elif "Alias" in type_str:
self._objtype = "alias"
else:
raise ValueError(f"Could not categorize type `{type_str}`")

Expand Down
34 changes: 28 additions & 6 deletions src/plcdoc/st_declaration.tx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ Note: `FUNCTION*` does not have a closing call for some reason!

There are a lot of `comments*=CommentAny`. This will offer a list of comments, the relevant docblocks need to be
extracted later. There seems no better way to do this in TextX.
Comment captures should be move down (= more basic elements) as much as possible to limit their usage.
Comment captures should be moved down (= more basic elements) as much as possible to limit their usage.

There can be dynamic expressions in variable declarations, which are very tricky to parse. Therefore expected
expressions are parsed greedily as whole string. As a consequence, blocks like argument lists will result in a long
expressions are parsed greedily as whole strings. As a consequence, blocks like argument lists will result in a long
string including the parentheses and commas.
*/

Declaration:
types*=TypeDef
properties*=Property
Expand Down Expand Up @@ -41,7 +42,7 @@ TypeDef:
;

AnyType:
TypeStruct | TypeUnion | TypeEnum
TypeStruct | TypeUnion | TypeEnum | TypeAlias
;

TypeStruct:
Expand All @@ -66,7 +67,8 @@ TypeEnum:
CommentAny*
')'
(base_type=Fqn)?
';'
(default=EnumDefault)?
SemiColon
;

EnumOption:
Expand All @@ -77,6 +79,19 @@ EnumOption:
(comment=CommentLine)?
;

EnumDefault:
':='
option=ID
// Enum default must be a literal field, it cannot be e.g. an integer
;

TypeAlias:
base=VariableType
CommentAny*
// Catch trailing comments here, not at the end of `Variable`
SemiColon
;

/*
---------------------------------------------------
*/
Expand All @@ -91,7 +106,7 @@ Function:
('EXTENDS' extends=Fqn)?
('IMPLEMENTS' implements=Fqn)?
(':' return=VariableType (arglist=ArgList)?)?
(';')?
(SemiColon)?
lists*=VariableList
;

Expand Down Expand Up @@ -152,7 +167,7 @@ Variable:
type=VariableType
(arglist=ArgList)?
(AssignmentSymbol value=AssignmentValue)?
';'
SemiColon
comment=CommentLine?
;

Expand Down Expand Up @@ -220,6 +235,13 @@ Fqn[noskipws]:
/\s/*-
;

/*
Semi-colons may be repeated in valid code
*/
SemiColon:
';'+
;

/*
Anything that is considered a value: a literal, a variable, or e.g. a sum
*/
Expand Down
8 changes: 8 additions & 0 deletions tests/plc_code/E_Filter.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ TYPE E_Filter :
Filter697nm := 6 // Note: non-consecutive number!
) USINT; // Specify base type so API is explicit
END_TYPE

TYPE E_FilterDefault :
(
NoFilter := 0,
Filter434nm,
Filter697nm
) USINT := NoFilter;
END_TYPE
10 changes: 9 additions & 1 deletion tests/plc_code/E_Options.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
TYPE E_Options :
(
Default := 0,
Default := 0,
Option1,
Option2
);
END_TYPE

TYPE E_OptionsDefault :
(
Default := 0,
Option1,
Option2
) := DEFAULT;
END_TYPE
2 changes: 2 additions & 0 deletions tests/plc_code/FB_Variables.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ myfloat_no_ws:REAL;
mypointer2 : REFERENCE TO UDINT;
mypointer3 : REFERENCE TO FB_Motor REF= _motor;

extra_semicolons : INT := 7;;;;;;;;;;

timeout1 : TIME := T#2S;
timeout2 : TIME := T#12m13s14ms;

Expand Down
4 changes: 4 additions & 0 deletions tests/plc_code/T_ALIAS.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
TYPE T_INTERLOCK : WORD; END_TYPE

TYPE T_Message : STRING[50];
END_TYPE
15 changes: 15 additions & 0 deletions tests/roots/test-plc-project/src_plc/DUTs/E_Error.TcDUT
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.11">
<DUT Name="E_Error" Id="{c568c63f-be9a-4c9b-92a0-4c4517e2c530}">
<Declaration><![CDATA[{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_Error :
(
OK := 0,
SomeError := 5,
OtherError
) USINT;
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>
12 changes: 12 additions & 0 deletions tests/roots/test-plc-project/src_plc/DUTs/ST_MyStruct.TcDUT
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.11">
<DUT Name="ST_MyStruct" Id="{111e5770-8d5b-4d8e-bf7d-bb6f31cde823}">
<Declaration><![CDATA[TYPE ST_MyStruct :
STRUCT
number : LREAL;
text : STRING(10);
END_STRUCT
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>
8 changes: 8 additions & 0 deletions tests/roots/test-plc-project/src_plc/DUTs/T_ALIAS.TcDUT
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.11">
<DUT Name="T_ALIAS" Id="{211c8563-7ef4-49a4-abc1-63eb79b1eac8}">
<Declaration><![CDATA[TYPE T_ALIAS : WORD;
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>
12 changes: 12 additions & 0 deletions tests/roots/test-plc-project/src_plc/MyPLC.plcproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Include="DUTs\E_Error.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="DUTs\ST_MyStruct.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="DUTs\T_ALIAS.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="POUs\F_SyntaxError.TcPOU">
<SubType>Code</SubType>
</Compile>
<Compile Include="POUs\FB_MyBlock.TcPOU">
<SubType>Code</SubType>
</Compile>
Expand Down
12 changes: 12 additions & 0 deletions tests/roots/test-plc-project/src_plc/POUs/F_SyntaxError.TcPOU
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.11">
<POU Name="F_SyntaxError" Id="{245635d7-7d54-4aa8-92df-775383b63265}" SpecialFunc="None">
<Declaration><![CDATA[FUNCTION F_SyntaxError RETURNS BOOL
VAR_INPUT
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[]]></ST>
</Implementation>
</POU>
</TcPlcObject>
7 changes: 6 additions & 1 deletion tests/test_plc_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def test_project_interpret(app, status, warning):
"functionblock": ["FB_MyBlock", "FB_SecondBlock", "PlainFunctionBlock"],
"function": ["PlainFunction", "RegularFunction"],
"program": ["MAIN"],
"enum": ["E_Error"],
"struct": ["ST_MyStruct"],
"alias": ["T_ALIAS"],
}

for objtype, objects in expected.items():
Expand All @@ -29,6 +32,8 @@ def test_project_interpret(app, status, warning):


@pytest.mark.sphinx("dummy", testroot="plc-project")
def test_project_build(app, status, warning):
def test_project_build(app, status, warning, caplog):
"""Test building a document loading a project."""
app.builder.build_all()
# Project contains a function with an outright syntax error, but the project
# completes nonetheless.
14 changes: 8 additions & 6 deletions tests/test_st_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest

import os
from textx import metamodel_from_file
from textx import metamodel_from_file, TextXSyntaxError
import re


Expand All @@ -30,6 +30,7 @@ def meta_model():
"E_Filter.txt",
"Properties.txt",
"Unions.txt",
"T_ALIAS.txt",
"GlobalVariableList.txt",
"Main.txt",
]
Expand All @@ -41,8 +42,8 @@ def test_grammar_on_files(meta_model, file):
filepath = os.path.realpath(tests_dir + "/plc_code/" + file)
try:
model = meta_model.model_from_file(filepath)
except:
pytest.fail(f"Error when analyzing the file `{file}`")
except TextXSyntaxError as err: # noqa
pytest.fail(f"Error when analyzing the file `{file}`: {err}")
else:
assert model is not None
assert (
Expand Down Expand Up @@ -79,8 +80,8 @@ def test_grammar_variables(meta_model):
filepath = os.path.realpath(tests_dir + "/plc_code/" + filename)
try:
model = meta_model.model_from_file(filepath)
except:
pytest.fail(f"Error when analyzing the file `{filename}`")
except TextXSyntaxError as err:
pytest.fail(f"Error when analyzing the file `{filename}`: {err}")
else:
assert model.functions
fb = model.functions[0]
Expand Down Expand Up @@ -144,12 +145,13 @@ def test_grammar_variables(meta_model):
("mypointer1", "UDINT", None, None, None, "POINTER"),
("mypointer2", "UDINT", None, None, None, "REFERENCE"),
("mypointer3", "FB_Motor", "_motor", None, None, "REFERENCE"),
("extra_semicolons", "INT", "7", None, None, None),
("timeout1", "TIME", "T#2S", None, None, None),
("timeout2", "TIME", "T#12m13s14ms", None, None, None),
("inline1", "INT", None, None, None, None),
]

assert len(variables) == 44
assert len(variables) == 45

for i, expected in enumerate(expected_list):
assert_variable(variables[i], expected)
Expand Down