Skip to content

Commit bca3c31

Browse files
authored
feat: parse VAR_CONFIG variables (#1299)
1 parent c2b7712 commit bca3c31

19 files changed

+372
-9
lines changed

compiler/plc_ast/src/ast.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,29 @@ impl PouType {
285285
}
286286
}
287287

288+
#[derive(Debug, PartialEq)]
289+
pub struct ConfigVariable {
290+
pub name_segments: Vec<String>,
291+
pub data_type: DataTypeDeclaration,
292+
pub address: AstNode,
293+
pub location: SourceLocation,
294+
}
295+
296+
impl ConfigVariable {
297+
pub fn new(
298+
name_segments: Vec<String>,
299+
data_type: DataTypeDeclaration,
300+
address: AstNode,
301+
location: SourceLocation,
302+
) -> Self {
303+
Self { name_segments, data_type, address, location }
304+
}
305+
}
306+
288307
#[derive(Debug, PartialEq)]
289308
pub struct CompilationUnit {
290309
pub global_vars: Vec<VariableBlock>,
310+
pub var_config: Vec<ConfigVariable>,
291311
pub units: Vec<Pou>,
292312
pub implementations: Vec<Implementation>,
293313
pub user_types: Vec<UserTypeDeclaration>,
@@ -298,6 +318,7 @@ impl CompilationUnit {
298318
pub fn new(file_name: &str) -> Self {
299319
CompilationUnit {
300320
global_vars: Vec::new(),
321+
var_config: Vec::new(),
301322
units: Vec::new(),
302323
implementations: Vec::new(),
303324
user_types: Vec::new(),

compiler/plc_ast/src/pre_processor.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use plc_util::convention::internal_type_name;
66

77
use crate::{
88
ast::{
9-
flatten_expression_list, Assignment, AstFactory, AstNode, AstStatement, CompilationUnit, DataType,
10-
DataTypeDeclaration, Operator, Pou, UserTypeDeclaration, Variable, VariableBlock, VariableBlockType,
9+
flatten_expression_list, Assignment, AstFactory, AstNode, AstStatement, CompilationUnit,
10+
ConfigVariable, DataType, DataTypeDeclaration, Operator, Pou, UserTypeDeclaration, Variable,
11+
VariableBlock, VariableBlockType,
1112
},
1213
literals::AstLiteral,
1314
provider::IdProvider,
@@ -37,6 +38,7 @@ pub fn pre_process(unit: &mut CompilationUnit, mut id_provider: IdProvider) {
3738

3839
//process all variables from GVLs
3940
process_global_variables(unit, &mut id_provider);
41+
process_var_config_variables(unit);
4042

4143
//process all variables in dataTypes
4244
let mut new_types = vec![];
@@ -182,6 +184,10 @@ fn process_global_variables(unit: &mut CompilationUnit, id_provider: &mut IdProv
182184
}
183185
}
184186

187+
update_generated_globals(unit, mangled_globals);
188+
}
189+
190+
fn update_generated_globals(unit: &mut CompilationUnit, mangled_globals: Vec<Variable>) {
185191
let mut block = if let Some(index) = unit.global_vars.iter().position(|block| {
186192
block.variable_block_type == VariableBlockType::Global && block.location.is_internal()
187193
}) {
@@ -197,6 +203,31 @@ fn process_global_variables(unit: &mut CompilationUnit, id_provider: &mut IdProv
197203
unit.global_vars.push(block);
198204
}
199205

206+
fn process_var_config_variables(unit: &mut CompilationUnit) {
207+
let variables =
208+
unit.var_config.iter().filter_map(|ConfigVariable { data_type, address, location, .. }| {
209+
let AstStatement::HardwareAccess(hardware) = &address.stmt else {
210+
unreachable!("Must be parsed as hardware access")
211+
};
212+
213+
if hardware.is_template() {
214+
return None;
215+
}
216+
217+
let name = hardware.get_mangled_variable_name();
218+
219+
Some(Variable {
220+
name,
221+
data_type_declaration: data_type.clone(),
222+
initializer: None,
223+
address: None,
224+
location: location.clone(),
225+
})
226+
});
227+
228+
update_generated_globals(unit, variables.collect())
229+
}
230+
200231
fn build_enum_initializer(
201232
last_name: &Option<String>,
202233
location: &SourceLocation,

compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__jump_and_label_converted_to_ast.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ expression: ast
44
---
55
CompilationUnit {
66
global_vars: [],
7+
var_config: [],
78
units: [
89
POU {
910
name: "program_0",

compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__negated_jump_ast.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ expression: ast
44
---
55
CompilationUnit {
66
global_vars: [],
7+
var_config: [],
78
units: [
89
POU {
910
name: "program_0",

compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unconnected_jump_generated_as_empty_statement.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ expression: ast
44
---
55
CompilationUnit {
66
global_vars: [],
7+
var_config: [],
78
units: [
89
POU {
910
name: "program_0",

compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unnamed_controls.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ expression: ast
44
---
55
CompilationUnit {
66
global_vars: [],
7+
var_config: [],
78
units: [
89
POU {
910
name: "program_0",

src/index/tests/index_tests.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,3 +2090,46 @@ fn if_two_aliased_var_of_different_types_use_the_same_address_the_first_wins() {
20902090
)
20912091
"###);
20922092
}
2093+
2094+
#[test]
2095+
fn var_config_hardware_address_creates_global_variable() {
2096+
// Given some configured hardware access variable like `foo.bar AT %IX1.2.3.4 : BOOL` we expect the index to have
2097+
// an internally created global variable named `__PI_1.2.3.4` of type BOOL.
2098+
let (_, index) = index(
2099+
r"
2100+
VAR_CONFIG
2101+
foo.bar AT %IX1.2.3.4 : BOOL;
2102+
END_VAR
2103+
",
2104+
);
2105+
2106+
assert_debug_snapshot!(index.find_global_variable("__PI_1_2_3_4").unwrap(), @r###"
2107+
VariableIndexEntry {
2108+
name: "__PI_1_2_3_4",
2109+
qualified_name: "__PI_1_2_3_4",
2110+
initial_value: None,
2111+
argument_type: ByVal(
2112+
Global,
2113+
),
2114+
is_constant: false,
2115+
data_type_name: "BOOL",
2116+
location_in_parent: 0,
2117+
linkage: Internal,
2118+
binding: None,
2119+
source_location: SourceLocation {
2120+
span: Range(
2121+
TextLocation {
2122+
line: 2,
2123+
column: 16,
2124+
offset: 40,
2125+
}..TextLocation {
2126+
line: 2,
2127+
column: 26,
2128+
offset: 50,
2129+
},
2130+
),
2131+
},
2132+
varargs: None,
2133+
}
2134+
"###);
2135+
}

src/lexer/tokens.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub enum Token {
4646
#[token("VAR", ignore(case))]
4747
KeywordVar,
4848

49+
#[token("VAR_CONFIG", ignore(case))]
50+
KeywordVarConfig,
51+
4952
#[token("ABSTRACT", ignore(case))]
5053
KeywordAbstract,
5154

src/lowering/initializers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ fn new_implementation(name: &str, statements: Vec<AstNode>, location: &SourceLoc
309309
fn new_unit(pou: Pou, implementation: Implementation, file_name: &str) -> CompilationUnit {
310310
CompilationUnit {
311311
global_vars: vec![],
312+
var_config: Default::default(),
312313
units: vec![pou],
313314
implementations: vec![implementation],
314315
user_types: vec![],

src/parser.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use std::ops::Range;
55
use plc_ast::{
66
ast::{
77
AccessModifier, ArgumentProperty, AstFactory, AstNode, AstStatement, AutoDerefType, CompilationUnit,
8-
DataType, DataTypeDeclaration, DirectAccessType, GenericBinding, HardwareAccessType, Implementation,
9-
LinkageType, PolymorphismMode, Pou, PouType, ReferenceAccess, ReferenceExpr, TypeNature,
10-
UserTypeDeclaration, Variable, VariableBlock, VariableBlockType,
8+
ConfigVariable, DataType, DataTypeDeclaration, DirectAccessType, GenericBinding, HardwareAccessType,
9+
Implementation, LinkageType, PolymorphismMode, Pou, PouType, ReferenceAccess, ReferenceExpr,
10+
TypeNature, UserTypeDeclaration, Variable, VariableBlock, VariableBlockType,
1111
},
1212
provider::IdProvider,
1313
};
@@ -77,6 +77,8 @@ pub fn parse(mut lexer: ParseSession, lnk: LinkageType, file_name: &str) -> Pars
7777
continue;
7878
}
7979
KeywordVarGlobal => unit.global_vars.push(parse_variable_block(&mut lexer, linkage)),
80+
KeywordVarConfig => unit.var_config.extend(parse_config_variables(&mut lexer)),
81+
8082
KeywordProgram | KeywordClass | KeywordFunction | KeywordFunctionBlock => {
8183
let params = match lexer.token {
8284
KeywordProgram => (PouType::Program, KeywordEndProgram),
@@ -120,7 +122,6 @@ pub fn parse(mut lexer: ParseSession, lnk: LinkageType, file_name: &str) -> Pars
120122
}
121123
//the match in the loop will always return
122124
}
123-
124125
fn parse_actions(
125126
lexer: &mut ParseSession,
126127
linkage: LinkageType,
@@ -1075,6 +1076,65 @@ fn parse_variable_list(lexer: &mut ParseSession) -> Vec<Variable> {
10751076
variables
10761077
}
10771078

1079+
fn parse_config_variables(lexer: &mut ParseSession) -> Vec<ConfigVariable> {
1080+
parse_any_in_region(lexer, vec![KeywordEndVar], |lexer| {
1081+
lexer.advance();
1082+
let mut variables = vec![];
1083+
while lexer.token == Identifier {
1084+
if let Some(configured_var) =
1085+
parse_any_in_region(lexer, vec![KeywordSemicolon], try_parse_config_var)
1086+
{
1087+
variables.push(configured_var);
1088+
}
1089+
}
1090+
variables
1091+
})
1092+
}
1093+
1094+
fn try_parse_config_var(lexer: &mut ParseSession) -> Option<ConfigVariable> {
1095+
let start = lexer.location();
1096+
let mut segments: Vec<String> = vec![];
1097+
while lexer.token == Identifier {
1098+
segments.push(lexer.slice_and_advance());
1099+
1100+
if lexer.token == KeywordColon || lexer.token == KeywordAt {
1101+
break;
1102+
}
1103+
1104+
lexer.try_consume(&KeywordDot);
1105+
}
1106+
1107+
let location = start.span(&lexer.location());
1108+
if !lexer.try_consume(&KeywordAt) {
1109+
lexer.accept_diagnostic(Diagnostic::missing_token("AT", lexer.location()));
1110+
}
1111+
1112+
let HardwareAccess((direction, access_type)) = lexer.token else {
1113+
lexer.accept_diagnostic(Diagnostic::missing_token("hardware access", lexer.location()));
1114+
return None;
1115+
};
1116+
1117+
let address = parse_hardware_access(lexer, direction, access_type)?;
1118+
1119+
if !lexer.try_consume(&KeywordColon) {
1120+
lexer.accept_diagnostic(Diagnostic::missing_token(
1121+
format!("{KeywordColon:?}").as_str(),
1122+
lexer.location(),
1123+
));
1124+
}
1125+
1126+
parse_data_type_definition(lexer, None).map(|(dt, init)| {
1127+
if init.is_some() {
1128+
lexer.accept_diagnostic(Diagnostic::unexpected_token_found(
1129+
format!("{KeywordSemicolon:?}").as_str(),
1130+
"Initializer",
1131+
lexer.last_location().span(&lexer.location()),
1132+
))
1133+
}
1134+
ConfigVariable::new(segments, dt, address, location)
1135+
})
1136+
}
1137+
10781138
fn parse_aliasing(lexer: &mut ParseSession, names: &(String, Range<usize>)) -> Option<Variable> {
10791139
let reference = parse_reference(lexer);
10801140
if !lexer.try_consume(&KeywordColon) {

0 commit comments

Comments
 (0)