Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: support access specifiers for function block declarations #22

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 .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mostly a vim user, I'm not too familiar with how vscode settings are distributed with source packages. Is version-controlling settings.json generally good practice?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an excellent question!

In earnest, I think it's really at your discretion...

I like to keep the .vscode settings in the repo so that they'll be picked up across platforms and by any other developers using VSCode, that said, there's no inherent NEED for them, and could just as easily be lobbed into the .gitignore file. I find it generally helpful to keep them around just like any other development tooling files (like ..gitignore, or .pre-comit-config.yaml, or anything else).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. I'm OK with leaving it in for now, at least.

"python.linting.flake8Enabled": true,
"python.linting.enabled": true
}
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ include LICENSE
include blark/_version.py
include blark/*.lark
include blark/docs/*.css
recursive-include blark/tests *.TcPOU *.st
34 changes: 11 additions & 23 deletions blark/iec.lark
Original file line number Diff line number Diff line change
Expand Up @@ -520,11 +520,18 @@ DOTTED_IDENTIFIER: IDENTIFIER ( "." IDENTIFIER )*
?function_block_type_name: standard_function_block_name
| derived_function_block_name

ABSTRACT: "ABSTRACT"i

ACCESS_SPECIFIER: "ABSTRACT"i
| "PUBLIC"i
| "PRIVATE"i
| "PROTECTED"i
| "INTERNAL"i
| "FINAL"i
access_specifier: ACCESS_SPECIFIER+
extends: "EXTENDS"i DOTTED_IDENTIFIER
implements: "IMPLEMENTS"i DOTTED_IDENTIFIER ("," DOTTED_IDENTIFIER)*

function_block_type_declaration: FUNCTION_BLOCK [ ABSTRACT ] derived_function_block_name [ extends ] [ implements ] fb_var_declaration* [ function_block_body ] END_FUNCTION_BLOCK ";"*
function_block_type_declaration: FUNCTION_BLOCK [ access_specifier ] derived_function_block_name [ extends ] [ implements ] fb_var_declaration* [ function_block_body ] END_FUNCTION_BLOCK ";"*

FUNCTION_BLOCK: "FUNCTION_BLOCK"i
| "FUNCTIONBLOCK"i
Expand All @@ -545,16 +552,6 @@ temp_var_decls: "VAR_TEMP"i var_body "END_VAR"i ";"*

?function_block_body: statement_list

// TODO: really only a couple of these
METHOD_ACCESS: "ABSTRACT"i
| "PUBLIC"i
| "PRIVATE"i
| "PROTECTED"i
| "INTERNAL"i
| "FINAL"i

method_access: METHOD_ACCESS+

var_inst_declaration: "VAR_INST"i [ variable_attributes ] var_body "END_VAR"i ";"*

?method_var_declaration: fb_var_declaration
Expand All @@ -563,22 +560,13 @@ var_inst_declaration: "VAR_INST"i [ variable_attributes ] var_body "END_VAR"i ";

?method_return_type: _located_var_spec_init

function_block_method_declaration: "METHOD"i [ method_access ] DOTTED_IDENTIFIER [ ":" method_return_type ] ";"* method_var_declaration* [ function_block_body ] "END_METHOD"i ";"*

PROPERTY_ACCESS: "ABSTRACT"i
| "PUBLIC"i
| "PRIVATE"i
| "PROTECTED"i
| "INTERNAL"i
| "FINAL"i

property_access: PROPERTY_ACCESS+
function_block_method_declaration: "METHOD"i [ access_specifier ] DOTTED_IDENTIFIER [ ":" method_return_type ] ";"* method_var_declaration* [ function_block_body ] "END_METHOD"i ";"*

?property_var_declaration: fb_var_declaration

?property_return_type: _located_var_spec_init

function_block_property_declaration: "PROPERTY"i [ property_access ] DOTTED_IDENTIFIER [ ":" property_return_type ] ";"* property_var_declaration* [ function_block_body ] "END_PROPERTY"i ";"*
function_block_property_declaration: "PROPERTY"i [ access_specifier ] DOTTED_IDENTIFIER [ ":" property_return_type ] ";"* property_var_declaration* [ function_block_body ] "END_PROPERTY"i ";"*

// B.1.5.3
?program_type_name: IDENTIFIER
Expand Down
6 changes: 6 additions & 0 deletions blark/tests/source/fb_w_public_access.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FUNCTION_BLOCK PUBLIC class_PublicFB
klauer marked this conversation as resolved.
Show resolved Hide resolved
VAR_OUTPUT
(*Some comment.*)
Error : BOOL;
END_VAR
END_FUNCTION_BLOCK
28 changes: 26 additions & 2 deletions blark/tests/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from ..parse import parse, summarize
from ..parse import parse, parse_source_code, summarize
from .conftest import get_grammar

TEST_PATH = pathlib.Path(__file__).parent
Expand All @@ -14,13 +14,20 @@
if additional_pous.exists():
pous += open(additional_pous, "rt").read().splitlines()

sources = list(str(path) for path in TEST_PATH.glob("**/*.st"))


@pytest.fixture(params=pous)
def pou_filename(request):
return request.param


def test_parsing(pou_filename):
@pytest.fixture(params=sources)
def source_filename(request):
return request.param


def test_parsing_tcpous(pou_filename):
try:
((_, result),) = list(parse(pou_filename))
except FileNotFoundError:
Expand All @@ -34,6 +41,23 @@ def test_parsing(pou_filename):
print(summarize(result))

klauer marked this conversation as resolved.
Show resolved Hide resolved

def test_parsing_source(source_filename):
"""Test plain source 61131 files."""
try:
with open(source_filename, "r", encoding="utf-8") as src:
content = src.read()
result = parse_source_code(content)
except FileNotFoundError:
pytest.skip(f"Missing file: {source_filename}")
else:
print("transformed:")
print(result)
print("summary:")
if isinstance(result, Exception):
raise result
print(summarize(result))


def pytest_html_results_table_row(report, cells):
# pytest results using pytest-html; show only failures for now:
if report.passed:
Expand Down
30 changes: 30 additions & 0 deletions blark/tests/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,36 @@ def test_global_roundtrip(rule_name, value):
END_FUNCTION_BLOCK
"""
)),
param("function_block_type_declaration", tf.multiline_code_block(
"""
FUNCTION_BLOCK PRIVATE fbName EXTENDS OtherFbName
END_FUNCTION_BLOCK
"""
)),
param("function_block_type_declaration", tf.multiline_code_block(
"""
FUNCTION_BLOCK PUBLIC fbName EXTENDS OtherFbName
END_FUNCTION_BLOCK
"""
)),
param("function_block_type_declaration", tf.multiline_code_block(
"""
FUNCTION_BLOCK INTERNAL fbName EXTENDS OtherFbName
END_FUNCTION_BLOCK
"""
)),
param("function_block_type_declaration", tf.multiline_code_block(
"""
FUNCTION_BLOCK PROTECTED fbName EXTENDS OtherFbName
END_FUNCTION_BLOCK
"""
)),
param("function_block_type_declaration", tf.multiline_code_block(
"""
FUNCTION_BLOCK FINAL fbName EXTENDS OtherFbName
END_FUNCTION_BLOCK
"""
)),
param("function_block_type_declaration", tf.multiline_code_block(
"""
FUNCTION_BLOCK fbName
Expand Down
27 changes: 13 additions & 14 deletions blark/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,9 @@ class VariableAttributes(_FlagHelper, enum.Flag):


@_rule_handler(
"method_access",
"property_access",
"access_specifier",
)
class MethodAccess(_FlagHelper, enum.Flag):
class AccessSpecifier(_FlagHelper, enum.Flag):
public = 0b0000_0001
private = 0b0000_0010
abstract = 0b0000_0100
Expand Down Expand Up @@ -1788,7 +1787,7 @@ def __str__(self) -> str:
@_rule_handler("function_block_type_declaration", comments=True)
class FunctionBlock:
name: lark.Token
abstract: bool
access: Optional[AccessSpecifier]
extends: Optional[Extends]
implements: Optional[Implements]
declarations: List[VariableDeclarationBlock]
Expand All @@ -1798,7 +1797,7 @@ class FunctionBlock:
@staticmethod
def from_lark(
fb_token: lark.Token,
abstract: Optional[lark.Token],
access: Optional[AccessSpecifier],
derived_name: lark.Token,
extends: Optional[Extends],
implements: Optional[Implements],
Expand All @@ -1807,16 +1806,16 @@ def from_lark(
*declarations, body, _ = args
return FunctionBlock(
name=derived_name,
abstract=abstract is not None,
access=access,
extends=extends,
implements=implements,
declarations=list(declarations),
body=body,
)

def __str__(self) -> str:
abstract = "ABSTRACT " if self.abstract else ""
header = f"FUNCTION_BLOCK {abstract}{self.name}"
access_and_name = join_if(self.access, " ", self.name)
header = f"FUNCTION_BLOCK {access_and_name}"
header = join_if(header, " ", self.implements)
header = join_if(header, " ", self.extends)
return "\n".join(
Expand Down Expand Up @@ -1913,7 +1912,7 @@ def __str__(self) -> str:
@dataclass
@_rule_handler("function_block_method_declaration", comments=True)
class Method:
access: Optional[MethodAccess]
access: Optional[AccessSpecifier]
name: lark.Token
return_type: Optional[LocatedVariableSpecInit]
declarations: List[VariableDeclarationBlock]
Expand All @@ -1922,7 +1921,7 @@ class Method:

@staticmethod
def from_lark(
access: Optional[MethodAccess],
access: Optional[AccessSpecifier],
name: lark.Token,
return_type: Optional[LocatedVariableSpecInit],
*args
Expand Down Expand Up @@ -1954,7 +1953,7 @@ def __str__(self) -> str:
@dataclass
@_rule_handler("function_block_property_declaration", comments=True)
class Property:
access: Optional[MethodAccess]
access: Optional[AccessSpecifier]
name: lark.Token
return_type: Optional[LocatedVariableSpecInit]
declarations: List[VariableDeclarationBlock]
Expand All @@ -1963,7 +1962,7 @@ class Property:

@staticmethod
def from_lark(
access: Optional[MethodAccess],
access: Optional[AccessSpecifier],
name: lark.Token,
return_type: Optional[LocatedVariableSpecInit],
*args
Expand Down Expand Up @@ -3035,8 +3034,8 @@ def merge_comments(source: Any, comments: List[lark.Token]):
# Optional apischema deserializers

@apischema.deserializer
def _method_access_deserializer(access: int) -> MethodAccess:
return MethodAccess(access)
def _method_access_deserializer(access: int) -> AccessSpecifier:
return AccessSpecifier(access)

@apischema.deserializer
def _var_attrs_deserializer(attrs: int) -> VariableAttributes:
Expand Down