diff --git a/docs/index.rst b/docs/index.rst index 269e6b1..cd9d8f2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,6 +29,7 @@ PLC Doc's documentation! src/directives src/examples src/modules + src/limitations ################## diff --git a/docs/src/limitations.rst b/docs/src/limitations.rst new file mode 100644 index 0000000..80bc10c --- /dev/null +++ b/docs/src/limitations.rst @@ -0,0 +1,49 @@ +###################### +Limitations and Issues +###################### + +For an up-to-date insight on issues, see https://github.com/DEMCON/plcdoc . +Please help maintaining this project by adding any new issues there. + +Grammar +======= + +Grammar refers to the parsing of PLC code. +This is done through a language specification, custom made in TextX. +And so there is no guarantee that all possible PLC code can be parsed without error. +Nonetheless, much of the most occurring code will parse with no problems. + +Expressions +----------- + +With expressions we mean all the literally typed constructions, from a simple ``5`` or ``'Hello World!'`` to a more complex ``CONCAT(INT_TO_STRING(5 + 3 * 8), '...')``. + +Currently, expressions are not parsed recursively as they would be for a real interpreter. +Instead, when an expression is expected the whole item is simply matched as a string. +So in the following... + +.. code-block:: + + my_int : INT := 1 + 1; + my_string : STRING := CONCAT("+", MY_CONST); + +...the initial values are registered as just ``"1 + 1"`` and ``"CONCAT("+", MY_CONST)"``. +This means e.g. variables are never recognized in variable initialization and won't be linked. +Aside from this, the expression should be printed normally in your generated docs item. + +String Escape +------------- + +Because `expressions <#Expressions>`_ are typically matched until the next ``;``, this breaks when a literal semicolon appears in a string. +This is avoided specifically for singular string expressions, but not for any expression: + +.. code-block:: + + str1 : STRING := 'Hi;'; // This will parse + str2 : STRING := CONCAT('Hi', ';'); // This will cause a parsing error + +Workaround +^^^^^^^^^^ + +For now you might have to use ``$3B`` as a `string constant `_ as a replacement for the literal semicolon. +Or introduce (const) variables to break up those expressions. diff --git a/src/plcdoc/st_declaration.tx b/src/plcdoc/st_declaration.tx index 059761f..5ef2cf6 100644 --- a/src/plcdoc/st_declaration.tx +++ b/src/plcdoc/st_declaration.tx @@ -246,6 +246,21 @@ SemiColon: Anything that is considered a value: a literal, a variable, or e.g. a sum */ Expression: + ExpressionString | ExpressionAnything +; + +/* +Because a string expression could use a syntax character, we need to make an effort match string content, to +escape the content. + +We use a literal string match, instead of TextX's `STRING`, because we need to keep the quotes so we can later +still distinguish a literal string type (over e.g. a variable name). +*/ +ExpressionString: + /'.*'/ +; + +ExpressionAnything: /[^;]*/ // Match anything, including parentheses, up to (but excluding) the semicolon ; diff --git a/tests/plc_code/FB_Variables.txt b/tests/plc_code/FB_Variables.txt index 8028425..3d09dee 100644 --- a/tests/plc_code/FB_Variables.txt +++ b/tests/plc_code/FB_Variables.txt @@ -28,6 +28,10 @@ myfloat_no_ws:REAL; mystring_size5 : STRING(35) := 'Unknown'; mystring_size6 : STRING[Module.SIZE] := 'Unknown'; + mystring_escape : STRING := ':;"'; + + mystring_concat : STRING := CONCAT('abc', 'xyz'); + myint : INT := SomeConstant; myint2 : INT := E_Error.NoError; diff --git a/tests/test_st_grammar.py b/tests/test_st_grammar.py index f829f9a..f4458e8 100644 --- a/tests/test_st_grammar.py +++ b/tests/test_st_grammar.py @@ -124,6 +124,8 @@ def test_grammar_variables(meta_model): ("mystring_size4", "STRING[SIZE]", None, None, None, None), ("mystring_size5", "STRING(35)", "'Unknown'", None, None, None), ("mystring_size6", "STRING[Module.SIZE]", "'Unknown'", None, None, None), + ("mystring_escape", "STRING", "':;\"'", None, None, None), + ("mystring_concat", "STRING", "CONCAT('abc','xyz')", None, None, None), ("myint", "INT", "SomeConstant", None, None, None), ("myint2", "INT", "E_Error.NoError", None, None, None), ("mylist", "BOOL", None, None, "0..4", None), @@ -151,7 +153,7 @@ def test_grammar_variables(meta_model): ("inline1", "INT", None, None, None, None), ] - assert len(variables) == 45 + assert len(variables) == 47 for i, expected in enumerate(expected_list): assert_variable(variables[i], expected)