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

feat: Properties #1396

Merged
merged 95 commits into from
Feb 26, 2025
Merged

feat: Properties #1396

merged 95 commits into from
Feb 26, 2025

Conversation

volsa
Copy link
Member

@volsa volsa commented Jan 27, 2025

This PR introduces the PROPERTY language feature. Properties in itself are getter and setter methods defined in a (stateful) POU and have the form of

FUNCTION_BLOCK fb
  VAR
    localPrivateVariable : DINT;
  END_VAR

  PROPERTY myProp : DINT
    GET
      // other statements potentially modifying the `localPrivateVariable`
      myProp := localPrivateVariable;
    END_GET
    
    SET
      // other statements potentially modifying internal variables
      localPrivateVariable := myProp;
    END_SET
  END_PROPERTY
END_FUNCTION_BLOCK

They are invoked by other POUs by simply accessing the property name. For example

FUNCTION main
  VAR
    x : DINT
    instance : fb;
  END_VAR

  instance.myProp := 5; // Internally resolves to `instance.__set_myprop(5)`
  x := instance.myProp; // Internally resolved to `x := instance.__get_myprop(5)`
END_FUNCTION

Specifically, references to properties always resolve to get calls with the exception in assignments when the property itself is accessed on the lhs.

The compiler will initially handle these properties as dedicated Property nodes in the CompilationUnit to have a 1:1 representation of the user code and only later on lower these properties to methods and property references to calls.

mhasel and others added 30 commits January 20, 2025 13:39
Introduces the PROPERTY, GET, SET and their END_* counterpart tokens
This commit lowers properties into concrete POUs and Implementations. For example
a property such as
```
FUNCTION_BLOCK fb
    PROPERTY foo : DINT
        GET
        END_GET

        SET
        END_SET
    END_PROPERTY
END_FUNCTION_BLOCK
```
will be lowered into a
```
FUNCTION_BLOCK fb
    VAR_PROPERTY
        foo : DINT
    END_VAR

    METHOD __get_foo : DINT
        // ...
        __get_foo := foo;
    END_METHOD

    METHOD __set_foo : DINT
        VAR_INPUT
            __in : DINT;
        END_VAR

        foo := __in;
        // ...
    END_METHOD
END_FUNCTION_BLOCK
```
This commit transforms references of properties such as `myFb.myProp := 5` and `myVar := myFb.myProp`
into setter and getter methods respectively. Broadly speaking when dealing with assignments
the lowerer will check if the left side is a property and transform the whole node into a set call
by `<lhs_node>(<rhs node);`. Otherwise, the visitor will visit every statement and check if the
reference is a property and if so replace it with a getter call.
Copy link
Member

@mhasel mhasel left a comment

Choose a reason for hiding this comment

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

There is an issue when accessing into an array-backing field directly. The getter logic is never called.

FUNCTION_BLOCK fb
    VAR
        localPrivateVariable : DINT := 5;
    END_VAR

    PROPERTY foo : ARRAY[0..9] OF STRING
        GET
            printf('getting$N');
        END_GET

        SET
            printf('setting$N');
        END_SET
    END_PROPERTY
END_FUNCTION_BLOCK


FUNCTION main
    VAR
        instance : fb;
    END_VAR
    instance.foo; // this prints 'getting'
    instance.foo[1] := 'hello world'; // this does not, it immediately accesses the array without calling the getter
    printf('%s$N', REF(instance.foo[1])); // same as above
END_FUNCTION

Another such example:

FUNCTION_BLOCK fb
    PROPERTY foo : ARRAY[0..9] OF DINT
        GET
            printf('getting foo$N');
        END_GET

        SET
            printf('setting foo$N');
        END_SET
    END_PROPERTY

    PROPERTY bar: INT
        GET
            printf('getting bar$N');
        END_GET

        SET
            printf('setting bar$N');
        END_SET
    END_PROPERTY
END_FUNCTION_BLOCK


FUNCTION main
    VAR
        instance : fb;
    END_VAR
    instance.foo[1] := 5;
    instance.bar := 1;
    printf('%d$N', instance.foo[instance.bar]); // the getter for `bar` is never called here
END_FUNCTION

@volsa volsa requested a review from mhasel February 25, 2025 11:42
mhasel
mhasel previously approved these changes Feb 25, 2025
@mhasel
Copy link
Member

mhasel commented Feb 25, 2025

Mhh, this looks nice.

@volsa volsa merged commit e9de3b6 into master Feb 26, 2025
19 checks passed
@volsa volsa deleted the volsa/property2 branch February 26, 2025 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants