Qualified Name Scoping vs Class Member Scoping for struct members in language without block scope. Plus other questions (VS Code extension for existing SCL language) #1612
-
In summary my questions isFor a language that:
Would Qualified Name Scoping be sufficient here for setting up scoping/resolving of references for dot-members? Any suggestion with regards to how I ought to go about this would also be appreciated. Both from the side of grammar, and the overriding of the scope computation. More informationBackground and goalI am a newbie with regards to language engineering. But I am interested in learning. My initial and current motivation is to create an extensions for VS Code to get things like auto complete and validation for an existing language (SCL, a Structured Text dialect, based on Pascal). I am now trying to create a VS Code extension that will give auto-complete suggestions for variables etc. Using Langium, it was surprisingly easy to achieve auto-complete for variables declared on the top-most level. But I am unsure of the smartest way to start tackling access to child elements. The language I am targetingThe language is SCL, which is a dialect of Structured Text for PLC programming. Variable declarationVariables are declared at the top of the file, before the program section starts. It is not possible to declare any variables in the program sections (below a The variables types declared will essentially be either:
Member callWhen calling members we access the local ones by starting with
My next goal now is to get linking / auto-complete for elements inside the struct, both when the struct is locally defined, and when it is referenced as a UDT in the declaration. Target language example and my grammarThe grammar is not complete yet, and would probably benefit from several modifications. I attach the grammar here for reference with relation to the question, and also in case someone would give some hints as to where I am heading in the right direction, and where I am veering heavily off course. Target languageFB_MyTestDB_A.scl (click arrow to expand to see code)FUNCTION_BLOCK "FB_MyTestFB_A"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
xboMyInput1A { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : BOOL;
xboMyInput2A { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool; // Comment for xboMyInput2A
xboMyInput3A : Bool; // Comment for xboMyInput3A
xiMyInput4A { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DINT;
xstiMyStringInputA { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : String;
END_VAR
VAR_IN_OUT
xyboMyInOut1A { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool; // Comment for xyboMyInOut1A
xyboMyInOut2A { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool; // Comment for xyboMyInOut2A
xyuStatusObjectA : "U_Status";
END_VAR
VAR
uStatusObjectA : "U_Status";
myStruct : STRUCT
intenal1 : DINT;
intenal2 : DINT;
intenal3 : DINT;
myInternal4NestedStruct : STRUCT
nestedIntenal1 : DINT;
nestedIntenal2 : DINT;
END_STRUCT;
intenal5 : DINT;
END_STRUCT;
END_VAR
VAR_TEMP
tiMyTemp1A : DINT;
myVar1 : DINT;
myVar2 : DINT;
myVar3 : DINT;
myVar4 : DINT;
myVar5 : DINT;
END_VAR
BEGIN
(*
# Some block comment
blah blah blah
*)
#tiMyTemp1A := xiMyInput4A;
//**********************
//**** Basics ****
//**********************
#tiMyTemp1A := xiMyInput4A;
#uStatusObjectA.good := TRUE;
#uStatusObjectA.warning := #uStatusObjectA.good;
IF xboMyInput1A THEN
#tiMyTemp1A := xiMyInput4A;
IF xboMyInput2A THEN
#tiMyTemp1A := xiMyInput4A;
END_IF;
END_IF;
IF #uStatusObjectA.good THEN
#tiMyTemp1A := xiMyInput4A;
ELSIF xboMyInput2A THEN
#tiMyTemp1A := xiMyInput4A;
ELSIF #myStruct.intenal1 THEN
#tiMyTemp1A := xiMyInput4A;
ELSIF xboMyInput2A THEN
#tiMyTemp1A := xiMyInput4A;
ELSE
#tiMyTemp1A := xiMyInput4A;
END_IF;
CASE #xiMyInput4A OF
1:
#myVar1 := 11;
2:
#myVar2 := 22;
3:
#myVar2 := 23;
#myVar3 := 33;
4:
#myVar4 := 44;
END_CASE;
CASE #xiMyInput4A OF
xboMyInput1A:
#myVar2 := 23;
#myVar3 := 33;
#xboMyInput2A,
#xboMyInput3A:
#myVar4 := 44;
4:
#myVar4 := 444;
END_CASE;
//**************************
//**** Jump label ****
//**************************
LocationsAlwaysVisible: //Jump label
#myVar1 := 11;
IF TRUE THEN
GOTO LocationsAlwaysVisible;
END_IF;
END_FUNCTION_BLOCK
U_Status.udt (click arrow to expand to see code)TYPE "U_Status"
VERSION : 0.1
STRUCT
good : Bool;
warning : Bool;
fault : Bool;
critical : Bool;
END_STRUCT;
END_TYPE
My grammar so farMy grammar (click arrow to expand to see code)
The separate scl-part-types fileThis was just placed in a separate file to not clutter up the main file too much while working on it.
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
Hey @BjAlvestad, great question, I think we can probably do a better job at outlining the different uses cases in which you use FQN (fully qualified name) vs class member scoping. Right now the explanation is fairly technical with little pro & con arguments for each type of scoping. Generally, I always try to recommend using class member scoping (assuming it is applicable), as there are multiple benefits to it compared to FQN scoping. These benefits are mainly concerning some LSP features like finding and going to references or stuff like auto completion. Most languages simply work better when using class member scoping. FQN scoping works best for languages that statically expose their elements under a fully qualified name. We mainly have the FQN guide for people migrating from Xtext, because FQN scoping is a builtin feature in Xtext, which also kind of inherited this from Java/EMF. I think for ST/SCL, class member scoping would be the way to go. As for the grammar: I would recommend you to take a look at our lox language since it contains a full implementation of this kind of scoping. |
Beta Was this translation helpful? Give feedback.
Hey @BjAlvestad,
great question, I think we can probably do a better job at outlining the different uses cases in which you use FQN (fully qualified name) vs class member scoping. Right now the explanation is fairly technical with little pro & con arguments for each type of scoping.
Generally, I always try to recommend using class member scoping (assuming it is applicable), as there are multiple benefits to it compared to FQN scoping. These benefits are mainly concerning some LSP features like finding and going to references or stuff like auto completion. Most languages simply work better when using class member scoping. FQN scoping works best for languages that statically expose their el…