Skip to content

Commit

Permalink
feat: builtins ADR and REF now have a CONSTANT return specifier so th…
Browse files Browse the repository at this point in the history
…ey can be used in initializers

Allows parsing of the `CONSTANT` keyword when parsing POUs, however for now only the builtins are actually allowed to make use of this keyword.
This enables the const-evaluator to check for constant builtins when trying to evaluate call-statements in variable initializers.
  • Loading branch information
mhasel committed Oct 3, 2024
1 parent bb5fe4b commit cb99b76
Show file tree
Hide file tree
Showing 16 changed files with 320 additions and 34 deletions.
1 change: 1 addition & 0 deletions compiler/plc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub struct Pou {
pub generics: Vec<GenericBinding>,
pub linkage: LinkageType,
pub super_class: Option<String>,
pub is_const: bool,
}

#[derive(Debug, PartialEq, Eq)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ lazy_static! {
E102, Error, include_str!("./error_codes/E102.md"), // Template variable without hardware binding
E103, Error, include_str!("./error_codes/E103.md"), // Immutable Hardware Binding
E104, Error, include_str!("./error_codes/E104.md"), // Config Variable With Incomplete Address
E105, Error, include_str!("./error_codes/E105.md"), // CONSTANT keyword in POU
);
}

Expand Down
12 changes: 12 additions & 0 deletions compiler/plc_diagnostics/src/diagnostics/error_codes/E105.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# CONSTANT keyword in POU

The `CONSTANT` keyword is not allowed for POU declarations, only variables can be `CONSTANT`

Erroneous code example:
```
FUNCTION FOO : BOOL CONSTANT
VAR_INPUT
END_VAR
// ...
END_FUNCTION
```
4 changes: 2 additions & 2 deletions src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ lazy_static! {
(
"ADR",
BuiltIn {
decl: "FUNCTION ADR<U: ANY> : LWORD
decl: "FUNCTION ADR<U: ANY> : LWORD CONSTANT
VAR_INPUT
in : U;
END_VAR
Expand Down Expand Up @@ -63,7 +63,7 @@ lazy_static! {
(
"REF",
BuiltIn {
decl: "FUNCTION REF<U: ANY> : REF_TO U
decl: "FUNCTION REF<U: ANY> : REF_TO U CONSTANT
VAR_INPUT
in : U;
END_VAR
Expand Down
9 changes: 9 additions & 0 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ pub enum PouIndexEntry {
is_variadic: bool,
location: SourceLocation,
is_generated: bool, // true if this entry was added automatically (e.g. by generics)
is_const: bool,
},
Class {
name: String,
Expand Down Expand Up @@ -529,6 +530,7 @@ impl PouIndexEntry {
linkage: LinkageType,
is_variadic: bool,
location: SourceLocation,
is_const: bool,
) -> PouIndexEntry {
PouIndexEntry::Function {
name: name.into(),
Expand All @@ -538,6 +540,7 @@ impl PouIndexEntry {
is_variadic,
location,
is_generated: false,
is_const,
}
}

Expand All @@ -550,6 +553,7 @@ impl PouIndexEntry {
linkage: LinkageType,
is_variadic: bool,
location: SourceLocation,
is_const: bool,
) -> PouIndexEntry {
PouIndexEntry::Function {
name: name.into(),
Expand All @@ -559,6 +563,7 @@ impl PouIndexEntry {
is_variadic,
location,
is_generated: true,
is_const,
}
}

Expand Down Expand Up @@ -752,6 +757,10 @@ impl PouIndexEntry {
matches!(self, PouIndexEntry::Method { .. })
}

pub(crate) fn is_constant(&self) -> bool {
matches!(self, PouIndexEntry::Function { is_const: true, .. })
}

pub fn get_location(&self) -> &SourceLocation {
match self {
PouIndexEntry::Program { location, .. }
Expand Down
1 change: 1 addition & 0 deletions src/index/tests/index_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,7 @@ fn a_program_pou_is_indexed() {
is_variadic: false,
location: source_location_factory.create_range(65..75),
is_generated: false,
is_const: false,
}),
index.find_pou("myFunction"),
);
Expand Down
2 changes: 2 additions & 0 deletions src/index/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub fn visit_pou(index: &mut Index, pou: &Pou) {
pou.linkage,
has_varargs,
pou.name_location.clone(),
pou.is_const,
));
index.register_pou_type(datatype);
}
Expand All @@ -206,6 +207,7 @@ pub fn visit_pou(index: &mut Index, pou: &Pou) {
pou.linkage,
has_varargs,
pou.name_location.clone(),
false,
));
index.register_init_function(&pou.name);
index.register_pou_type(datatype);
Expand Down
1 change: 1 addition & 0 deletions src/lowering/initializers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ fn new_pou(name: &str, variable_blocks: Vec<VariableBlock>, location: &SourceLoc
generics: vec![],
linkage: LinkageType::Internal,
super_class: None,
is_const: false,
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@ fn parse_pou(
// classes do not have a return type (check in validator)
let return_type = parse_return_type(lexer, &pou_type);

// parse optional const specifier for const-builtins
let constant = lexer.try_consume(&KeywordConstant);
if constant && !matches!(linkage, LinkageType::BuiltIn) {
lexer.accept_diagnostic(
Diagnostic::new("CONSTANT specifier is not allowed in POU declaration")
.with_location(
lexer
.source_range_factory
.create_range(lexer.last_range.start..lexer.last_range.end),
)
.with_error_code("E105"),
);
}
// parse variable declarations. note that var in/out/inout
// blocks are not allowed inside of class declarations.
let mut variable_blocks = vec![];
Expand Down Expand Up @@ -247,6 +260,7 @@ fn parse_pou(
generics,
linkage,
super_class,
is_const: constant,
}];
pous.append(&mut impl_pous);

Expand Down Expand Up @@ -426,6 +440,16 @@ fn parse_method(
let (name, name_location) = parse_identifier(lexer)?;
let generics = parse_generics(lexer);
let return_type = parse_return_type(lexer, &pou_type);
let constant = lexer.try_consume(&KeywordConstant);
if constant {
lexer.accept_diagnostic(
Diagnostic::new("CONSTANT specifier is not allowed in POU declaration")
.with_location(
lexer.source_range_factory.create_range(lexer.last_range.start..lexer.last_range.end),
)
.with_error_code("E105"),
);
}

let mut variable_blocks = vec![];
while lexer.token == KeywordVar
Expand Down Expand Up @@ -465,6 +489,7 @@ fn parse_method(
generics,
linkage,
super_class: None,
is_const: constant,
},
implementation,
))
Expand Down
2 changes: 2 additions & 0 deletions src/parser/tests/expressions_parser_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,7 @@ fn sized_string_as_function_return() {
generics: vec![],
linkage: LinkageType::Internal,
super_class: None,
is_const: false,
};

assert_eq!(format!("{:?}", ast.units[0]), format!("{expected:?}"));
Expand Down Expand Up @@ -1639,6 +1640,7 @@ fn array_type_as_function_return() {
generics: vec![],
linkage: LinkageType::Internal,
super_class: None,
is_const: false,
};

assert_eq!(format!("{:?}", ast.units[0]), format!("{expected:?}"));
Expand Down
120 changes: 120 additions & 0 deletions src/parser/tests/function_parser_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ fn varargs_parameters_can_be_parsed() {
generics: vec![],
linkage: LinkageType::Internal,
super_class: None,
is_const: false,
};
assert_eq!(format!("{expected:#?}"), format!("{x:#?}").as_str());
}
Expand Down Expand Up @@ -310,6 +311,7 @@ fn sized_varargs_parameters_can_be_parsed() {
generics: vec![],
linkage: LinkageType::Internal,
super_class: None,
is_const: false,
};
assert_eq!(format!("{expected:#?}"), format!("{x:#?}").as_str());
}
Expand Down Expand Up @@ -520,3 +522,121 @@ fn var_input_by_ref_parsed() {
}
"###)
}

#[test]
fn constant_keyword_can_be_parsed_but_errs() {
let src = r#"
FUNCTION_BLOCK foo CONSTANT END_FUNCTION_BLOCK
PROGRAM bar CONSTANT END_PROGRAM
CLASS qux CONSTANT
METHOD quux : DINT CONSTANT END_METHOD
END_CLASS
FUNCTION corge : BOOL CONSTANT END_FUNCTION
"#;
let (_, diagnostics) = parse(src);

insta::assert_debug_snapshot!(diagnostics, @r###"
[
Diagnostic {
message: "CONSTANT specifier is not allowed in POU declaration",
primary_location: SourceLocation {
span: Range(
TextLocation {
line: 1,
column: 27,
offset: 28,
}..TextLocation {
line: 1,
column: 35,
offset: 36,
},
),
},
secondary_locations: None,
error_code: "E105",
sub_diagnostics: [],
internal_error: None,
},
Diagnostic {
message: "CONSTANT specifier is not allowed in POU declaration",
primary_location: SourceLocation {
span: Range(
TextLocation {
line: 2,
column: 20,
offset: 76,
}..TextLocation {
line: 2,
column: 28,
offset: 84,
},
),
},
secondary_locations: None,
error_code: "E105",
sub_diagnostics: [],
internal_error: None,
},
Diagnostic {
message: "CONSTANT specifier is not allowed in POU declaration",
primary_location: SourceLocation {
span: Range(
TextLocation {
line: 3,
column: 18,
offset: 116,
}..TextLocation {
line: 3,
column: 26,
offset: 124,
},
),
},
secondary_locations: None,
error_code: "E105",
sub_diagnostics: [],
internal_error: None,
},
Diagnostic {
message: "CONSTANT specifier is not allowed in POU declaration",
primary_location: SourceLocation {
span: Range(
TextLocation {
line: 4,
column: 31,
offset: 157,
}..TextLocation {
line: 4,
column: 39,
offset: 165,
},
),
},
secondary_locations: None,
error_code: "E105",
sub_diagnostics: [],
internal_error: None,
},
Diagnostic {
message: "CONSTANT specifier is not allowed in POU declaration",
primary_location: SourceLocation {
span: Range(
TextLocation {
line: 6,
column: 31,
offset: 236,
}..TextLocation {
line: 6,
column: 39,
offset: 244,
},
),
},
secondary_locations: None,
error_code: "E105",
sub_diagnostics: [],
internal_error: None,
},
]
"###);
}
20 changes: 12 additions & 8 deletions src/resolver/const_evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,16 +571,20 @@ fn evaluate_with_target_hint(
AstStatement::ParenExpression(expr) => {
evaluate_with_target_hint(expr, scope, index, target_type, lhs)?
}
AstStatement::CallStatement(plc_ast::ast::CallStatement { parameters, .. }) => {
let Some(arg) = parameters else {
AstStatement::CallStatement(plc_ast::ast::CallStatement { operator, .. }) => {
if let Some(pou) = operator.as_ref().get_flat_reference_name().and_then(|it| index.find_pou(it)) {
if !(pou.is_constant() && index.get_builtin_function(pou.get_name()).is_some()) {
return Err(UnresolvableKind::Misc(format!(
"Call-statement '{}' in initializer is not constant.",
pou.get_name()
)));
}
} else {
// POU not found
return Err(UnresolvableKind::Misc(format!("Cannot resolve constant: {:#?}", initial)));
};
return match evaluate_with_target_hint(arg, scope, index, target_type, lhs) {
// arg to const fn call could not be found in the index => unresolvable (only ref/adr are supported for now, must have arg)
Ok(None) => Err(UnresolvableKind::Misc(format!("Cannot resolve constant: {arg:#?}"))),
// we found a local or global parameter for REF/ADR, but it cannot be resolved as constant since the address is not yet known. Resolve during codegen
_ => Err(UnresolvableKind::Address(InitData::new(Some(initial), target_type, scope, lhs))),
};

return Err(UnresolvableKind::Address(InitData::new(Some(initial), target_type, scope, lhs)));
}
_ => return Err(UnresolvableKind::Misc(format!("Cannot resolve constant: {initial:#?}"))),
};
Expand Down
1 change: 1 addition & 0 deletions src/resolver/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ impl<'i> TypeAnnotator<'i> {
LinkageType::External, //it has to be external, we should have already found this in the global index if it was internal
generic_function.is_variadic(),
generic_function.get_location().clone(),
generic_function.is_constant(),
));

// register the member-variables (interface) of the new function
Expand Down
Loading

0 comments on commit cb99b76

Please sign in to comment.